UNPKG

16.1 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3var path_1 = require("path");
4var interfaces_1 = require("./util/interfaces");
5var errors_1 = require("./util/errors");
6var bundle_1 = require("./bundle");
7var fs_extra_1 = require("fs-extra");
8var config_1 = require("./util/config");
9var logger_1 = require("./logger/logger");
10var logger_sass_1 = require("./logger/logger-sass");
11var logger_diagnostics_1 = require("./logger/logger-diagnostics");
12var node_sass_1 = require("node-sass");
13var postcss = require("postcss");
14var autoprefixer = require("autoprefixer");
15function sass(context, configFile) {
16 configFile = config_1.getUserConfigFile(context, taskInfo, configFile);
17 var logger = new logger_1.Logger('sass');
18 return sassWorker(context, configFile)
19 .then(function (outFile) {
20 context.sassState = interfaces_1.BuildState.SuccessfulBuild;
21 logger.finish();
22 return outFile;
23 })
24 .catch(function (err) {
25 context.sassState = interfaces_1.BuildState.RequiresBuild;
26 throw logger.fail(err);
27 });
28}
29exports.sass = sass;
30function sassUpdate(changedFiles, context) {
31 var configFile = config_1.getUserConfigFile(context, taskInfo, null);
32 var logger = new logger_1.Logger('sass update');
33 return sassWorker(context, configFile)
34 .then(function (outFile) {
35 context.sassState = interfaces_1.BuildState.SuccessfulBuild;
36 logger.finish();
37 return outFile;
38 })
39 .catch(function (err) {
40 context.sassState = interfaces_1.BuildState.RequiresBuild;
41 throw logger.fail(err);
42 });
43}
44exports.sassUpdate = sassUpdate;
45function sassWorker(context, configFile) {
46 var sassConfig = getSassConfig(context, configFile);
47 var bundlePromise = [];
48 if (!context.moduleFiles && !sassConfig.file) {
49 // sass must always have a list of all the used module files
50 // so ensure we bundle if moduleFiles are currently unknown
51 bundlePromise.push(bundle_1.bundle(context));
52 }
53 return Promise.all(bundlePromise).then(function () {
54 logger_diagnostics_1.clearDiagnostics(context, logger_diagnostics_1.DiagnosticsType.Sass);
55 // where the final css output file is saved
56 if (!sassConfig.outFile) {
57 sassConfig.outFile = path_1.join(context.buildDir, sassConfig.outputFilename);
58 }
59 logger_1.Logger.debug("sass outFile: " + sassConfig.outFile);
60 // import paths where the sass compiler will look for imports
61 sassConfig.includePaths.unshift(path_1.join(context.srcDir));
62 logger_1.Logger.debug("sass includePaths: " + sassConfig.includePaths);
63 // sass import sorting algorithms incase there was something to tweak
64 sassConfig.sortComponentPathsFn = (sassConfig.sortComponentPathsFn || defaultSortComponentPathsFn);
65 sassConfig.sortComponentFilesFn = (sassConfig.sortComponentFilesFn || defaultSortComponentFilesFn);
66 if (!sassConfig.file) {
67 // if the sass config was not given an input file, then
68 // we're going to dynamically generate the sass data by
69 // scanning through all the components included in the bundle
70 // and generate the sass on the fly
71 generateSassData(context, sassConfig);
72 }
73 else {
74 sassConfig.file = config_1.replacePathVars(context, sassConfig.file);
75 }
76 return render(context, sassConfig);
77 });
78}
79exports.sassWorker = sassWorker;
80function getSassConfig(context, configFile) {
81 configFile = config_1.getUserConfigFile(context, taskInfo, configFile);
82 return config_1.fillConfigDefaults(configFile, taskInfo.defaultConfigFile);
83}
84exports.getSassConfig = getSassConfig;
85function generateSassData(context, sassConfig) {
86 /**
87 * 1) Import user sass variables first since user variables
88 * should have precedence over default library variables.
89 * 2) Import all library sass files next since library css should
90 * be before user css, and potentially have library css easily
91 * overridden by user css selectors which come after the
92 * library's in the same file.
93 * 3) Import the user's css last since we want the user's css to
94 * potentially easily override library css with the same
95 * css specificity.
96 */
97 var moduleDirectories = [];
98 if (context.moduleFiles) {
99 context.moduleFiles.forEach(function (moduleFile) {
100 var moduleDirectory = path_1.dirname(moduleFile);
101 if (moduleDirectories.indexOf(moduleDirectory) < 0) {
102 moduleDirectories.push(moduleDirectory);
103 }
104 });
105 }
106 logger_1.Logger.debug("sass moduleDirectories: " + moduleDirectories.length);
107 // gather a list of all the sass variable files that should be used
108 // these variable files will be the first imports
109 var userSassVariableFiles = sassConfig.variableSassFiles.map(function (f) {
110 return config_1.replacePathVars(context, f);
111 });
112 // gather a list of all the sass files that are next to components we're bundling
113 var componentSassFiles = getComponentSassFiles(moduleDirectories, context, sassConfig);
114 logger_1.Logger.debug("sass userSassVariableFiles: " + userSassVariableFiles.length);
115 logger_1.Logger.debug("sass componentSassFiles: " + componentSassFiles.length);
116 var sassImports = userSassVariableFiles.concat(componentSassFiles).map(function (sassFile) { return '"' + sassFile.replace(/\\/g, '\\\\') + '"'; });
117 if (sassImports.length) {
118 sassConfig.data = "@charset \"UTF-8\"; @import " + sassImports.join(',') + ";";
119 }
120}
121function getComponentSassFiles(moduleDirectories, context, sassConfig) {
122 var collectedSassFiles = [];
123 var componentDirectories = getComponentDirectories(moduleDirectories, sassConfig);
124 // sort all components with the library components being first
125 // and user components coming last, so it's easier for user css
126 // to override library css with the same specificity
127 var sortedComponentPaths = componentDirectories.sort(sassConfig.sortComponentPathsFn);
128 sortedComponentPaths.forEach(function (componentPath) {
129 addComponentSassFiles(componentPath, collectedSassFiles, context, sassConfig);
130 });
131 return collectedSassFiles;
132}
133function addComponentSassFiles(componentPath, collectedSassFiles, context, sassConfig) {
134 var siblingFiles = getSiblingSassFiles(componentPath, sassConfig);
135 if (!siblingFiles.length && componentPath.indexOf(path_1.sep + 'node_modules') === -1) {
136 // if we didn't find anything, see if this module is mapped to another directory
137 for (var k in sassConfig.directoryMaps) {
138 if (sassConfig.directoryMaps.hasOwnProperty(k)) {
139 var actualDirectory = config_1.replacePathVars(context, k);
140 var mappedDirectory = config_1.replacePathVars(context, sassConfig.directoryMaps[k]);
141 componentPath = componentPath.replace(actualDirectory, mappedDirectory);
142 siblingFiles = getSiblingSassFiles(componentPath, sassConfig);
143 if (siblingFiles.length) {
144 break;
145 }
146 }
147 }
148 }
149 if (siblingFiles.length) {
150 siblingFiles = siblingFiles.sort(sassConfig.sortComponentFilesFn);
151 siblingFiles.forEach(function (componentFile) {
152 collectedSassFiles.push(componentFile);
153 });
154 }
155}
156function getSiblingSassFiles(componentPath, sassConfig) {
157 try {
158 return fs_extra_1.readdirSync(componentPath).filter(function (f) {
159 return isValidSassFile(f, sassConfig);
160 }).map(function (f) {
161 return path_1.join(componentPath, f);
162 });
163 }
164 catch (ex) {
165 // it's an invalid path
166 return [];
167 }
168}
169function isValidSassFile(filename, sassConfig) {
170 for (var i = 0; i < sassConfig.includeFiles.length; i++) {
171 if (sassConfig.includeFiles[i].test(filename)) {
172 // filename passes the test to be included
173 for (var j = 0; j < sassConfig.excludeFiles.length; j++) {
174 if (sassConfig.excludeFiles[j].test(filename)) {
175 // however, it also passed the test that it should be excluded
176 logger_1.Logger.debug("sass excluded: " + filename);
177 return false;
178 }
179 }
180 return true;
181 }
182 }
183 return false;
184}
185function getComponentDirectories(moduleDirectories, sassConfig) {
186 // filter out module directories we know wouldn't have sibling component sass file
187 // just a way to reduce the amount of lookups to be done later
188 return moduleDirectories.filter(function (moduleDirectory) {
189 // normalize this directory is using / between directories
190 moduleDirectory = moduleDirectory.replace(/\\/g, '/');
191 for (var i = 0; i < sassConfig.excludeModules.length; i++) {
192 if (moduleDirectory.indexOf('/node_modules/' + sassConfig.excludeModules[i] + '/') > -1) {
193 return false;
194 }
195 }
196 return true;
197 });
198}
199function render(context, sassConfig) {
200 return new Promise(function (resolve, reject) {
201 sassConfig.omitSourceMapUrl = false;
202 if (sassConfig.sourceMap) {
203 sassConfig.sourceMapContents = true;
204 }
205 node_sass_1.render(sassConfig, function (sassError, sassResult) {
206 var diagnostics = logger_sass_1.runSassDiagnostics(context, sassError);
207 if (diagnostics.length) {
208 logger_diagnostics_1.printDiagnostics(context, logger_diagnostics_1.DiagnosticsType.Sass, diagnostics, true, true);
209 // sass render error :(
210 reject(new errors_1.BuildError('Failed to render sass to css'));
211 }
212 else {
213 // sass render success :)
214 renderSassSuccess(context, sassResult, sassConfig).then(function (outFile) {
215 resolve(outFile);
216 }).catch(function (err) {
217 reject(new errors_1.BuildError(err));
218 });
219 }
220 });
221 });
222}
223function renderSassSuccess(context, sassResult, sassConfig) {
224 if (sassConfig.autoprefixer) {
225 // with autoprefixer
226 var autoPrefixerMapOptions = false;
227 if (sassConfig.sourceMap) {
228 autoPrefixerMapOptions = {
229 inline: false,
230 prev: generateSourceMaps(sassResult, sassConfig)
231 };
232 }
233 var postcssOptions = {
234 to: path_1.basename(sassConfig.outFile),
235 map: autoPrefixerMapOptions,
236 from: void 0
237 };
238 logger_1.Logger.debug("sass, start postcss/autoprefixer");
239 var postCssPlugins = [autoprefixer(sassConfig.autoprefixer)];
240 if (sassConfig.postCssPlugins) {
241 postCssPlugins = sassConfig.postCssPlugins.concat(postCssPlugins);
242 }
243 return postcss(postCssPlugins)
244 .process(sassResult.css, postcssOptions).then(function (postCssResult) {
245 postCssResult.warnings().forEach(function (warn) {
246 logger_1.Logger.warn(warn.toString());
247 });
248 var apMapResult = null;
249 if (sassConfig.sourceMap && postCssResult.map) {
250 logger_1.Logger.debug("sass, parse postCssResult.map");
251 apMapResult = generateSourceMaps(postCssResult, sassConfig);
252 }
253 logger_1.Logger.debug("sass: postcss/autoprefixer completed");
254 return writeOutput(context, sassConfig, postCssResult.css, apMapResult);
255 });
256 }
257 // without autoprefixer
258 var sassMapResult = generateSourceMaps(sassResult, sassConfig);
259 return writeOutput(context, sassConfig, sassResult.css.toString(), sassMapResult);
260}
261function generateSourceMaps(sassResult, sassConfig) {
262 // this can be async and nothing needs to wait on it
263 // build Source Maps!
264 if (sassResult.map) {
265 logger_1.Logger.debug("sass, generateSourceMaps");
266 // transform map into JSON
267 var sassMap = JSON.parse(sassResult.map.toString());
268 // grab the stdout and transform it into stdin
269 var sassMapFile = sassMap.file.replace(/^stdout$/, 'stdin');
270 // grab the base file name that's being worked on
271 var sassFileSrc = sassConfig.outFile;
272 // grab the path portion of the file that's being worked on
273 var sassFileSrcPath_1 = path_1.dirname(sassFileSrc);
274 if (sassFileSrcPath_1) {
275 // prepend the path to all files in the sources array except the file that's being worked on
276 var sourceFileIndex_1 = sassMap.sources.indexOf(sassMapFile);
277 sassMap.sources = sassMap.sources.map(function (source, index) {
278 return (index === sourceFileIndex_1) ? source : path_1.join(sassFileSrcPath_1, source);
279 });
280 }
281 // remove 'stdin' from souces and replace with filenames!
282 sassMap.sources = sassMap.sources.filter(function (src) {
283 if (src !== 'stdin') {
284 return src;
285 }
286 });
287 return sassMap;
288 }
289}
290function writeOutput(context, sassConfig, cssOutput, sourceMap) {
291 var mappingsOutput = JSON.stringify(sourceMap);
292 return new Promise(function (resolve, reject) {
293 logger_1.Logger.debug("sass start write output: " + sassConfig.outFile);
294 var buildDir = path_1.dirname(sassConfig.outFile);
295 fs_extra_1.ensureDirSync(buildDir);
296 fs_extra_1.writeFile(sassConfig.outFile, cssOutput, function (cssWriteErr) {
297 if (cssWriteErr) {
298 reject(new errors_1.BuildError("Error writing css file, " + sassConfig.outFile + ": " + cssWriteErr));
299 }
300 else {
301 logger_1.Logger.debug("sass saved output: " + sassConfig.outFile);
302 if (mappingsOutput) {
303 // save the css map file too
304 // this save completes async and does not hold up the resolve
305 var sourceMapPath_1 = path_1.join(buildDir, path_1.basename(sassConfig.outFile) + '.map');
306 logger_1.Logger.debug("sass start write css map: " + sourceMapPath_1);
307 fs_extra_1.writeFile(sourceMapPath_1, mappingsOutput, function (mapWriteErr) {
308 if (mapWriteErr) {
309 logger_1.Logger.error("Error writing css map file, " + sourceMapPath_1 + ": " + mapWriteErr);
310 }
311 else {
312 logger_1.Logger.debug("sass saved css map: " + sourceMapPath_1);
313 }
314 });
315 }
316 // css file all saved
317 // note that we're not waiting on the css map to finish saving
318 resolve(sassConfig.outFile);
319 }
320 });
321 });
322}
323function defaultSortComponentPathsFn(a, b) {
324 var aIndexOfNodeModules = a.indexOf('node_modules');
325 var bIndexOfNodeModules = b.indexOf('node_modules');
326 if (aIndexOfNodeModules > -1 && bIndexOfNodeModules > -1) {
327 return (a > b) ? 1 : -1;
328 }
329 if (aIndexOfNodeModules > -1 && bIndexOfNodeModules === -1) {
330 return -1;
331 }
332 if (aIndexOfNodeModules === -1 && bIndexOfNodeModules > -1) {
333 return 1;
334 }
335 return (a > b) ? 1 : -1;
336}
337function defaultSortComponentFilesFn(a, b) {
338 var aPeriods = a.split('.').length;
339 var bPeriods = b.split('.').length;
340 var aDashes = a.split('-').length;
341 var bDashes = b.split('-').length;
342 if (aPeriods > bPeriods) {
343 return 1;
344 }
345 else if (aPeriods < bPeriods) {
346 return -1;
347 }
348 if (aDashes > bDashes) {
349 return 1;
350 }
351 else if (aDashes < bDashes) {
352 return -1;
353 }
354 return (a > b) ? 1 : -1;
355}
356var taskInfo = {
357 fullArg: '--sass',
358 shortArg: '-s',
359 envVar: 'IONIC_SASS',
360 packageConfig: 'ionic_sass',
361 defaultConfigFile: 'sass.config'
362};