/ script / lib / prebuild-less-cache.js
prebuild-less-cache.js
  1  'use strict';
  2  
  3  const fs = require('fs');
  4  const klawSync = require('klaw-sync');
  5  const glob = require('glob');
  6  const path = require('path');
  7  const LessCache = require('less-cache');
  8  
  9  const CONFIG = require('../config');
 10  const LESS_CACHE_VERSION = require('less-cache/package.json').version;
 11  const FALLBACK_VARIABLE_IMPORTS =
 12    '@import "variables/ui-variables";\n@import "variables/syntax-variables";\n';
 13  
 14  module.exports = function() {
 15    const cacheDirPath = path.join(
 16      CONFIG.intermediateAppPath,
 17      'less-compile-cache'
 18    );
 19    console.log(`Generating pre-built less cache in ${cacheDirPath}`);
 20  
 21    // Group bundled packages into UI themes, syntax themes, and non-theme packages
 22    const uiThemes = [];
 23    const syntaxThemes = [];
 24    const nonThemePackages = [];
 25    for (let packageName in CONFIG.appMetadata.packageDependencies) {
 26      const packageMetadata = require(path.join(
 27        CONFIG.intermediateAppPath,
 28        'node_modules',
 29        packageName,
 30        'package.json'
 31      ));
 32      if (packageMetadata.theme === 'ui') {
 33        uiThemes.push(packageName);
 34      } else if (packageMetadata.theme === 'syntax') {
 35        syntaxThemes.push(packageName);
 36      } else {
 37        nonThemePackages.push(packageName);
 38      }
 39    }
 40  
 41    CONFIG.snapshotAuxiliaryData.lessSourcesByRelativeFilePath = {};
 42    function saveIntoSnapshotAuxiliaryData(absoluteFilePath, content) {
 43      const relativeFilePath = path.relative(
 44        CONFIG.intermediateAppPath,
 45        absoluteFilePath
 46      );
 47      if (
 48        !CONFIG.snapshotAuxiliaryData.lessSourcesByRelativeFilePath.hasOwnProperty(
 49          relativeFilePath
 50        )
 51      ) {
 52        CONFIG.snapshotAuxiliaryData.lessSourcesByRelativeFilePath[
 53          relativeFilePath
 54        ] = {
 55          content: content,
 56          digest: LessCache.digestForContent(content)
 57        };
 58      }
 59    }
 60  
 61    CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath = {};
 62    // Warm cache for every combination of the default UI and syntax themes,
 63    // because themes assign variables which may be used in any style sheet.
 64    for (let uiTheme of uiThemes) {
 65      for (let syntaxTheme of syntaxThemes) {
 66        // Build a LessCache instance with import paths based on the current theme combination
 67        const lessCache = new LessCache({
 68          cacheDir: cacheDirPath,
 69          fallbackDir: path.join(
 70            CONFIG.atomHomeDirPath,
 71            'compile-cache',
 72            'prebuild-less',
 73            LESS_CACHE_VERSION
 74          ),
 75          syncCaches: true,
 76          resourcePath: CONFIG.intermediateAppPath,
 77          importPaths: [
 78            path.join(
 79              CONFIG.intermediateAppPath,
 80              'node_modules',
 81              syntaxTheme,
 82              'styles'
 83            ),
 84            path.join(
 85              CONFIG.intermediateAppPath,
 86              'node_modules',
 87              uiTheme,
 88              'styles'
 89            ),
 90            path.join(CONFIG.intermediateAppPath, 'static', 'variables'),
 91            path.join(CONFIG.intermediateAppPath, 'static')
 92          ]
 93        });
 94  
 95        // Store file paths located at the import paths so that we can avoid scanning them at runtime.
 96        for (const absoluteImportPath of lessCache.getImportPaths()) {
 97          const relativeImportPath = path.relative(
 98            CONFIG.intermediateAppPath,
 99            absoluteImportPath
100          );
101          if (
102            !CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath.hasOwnProperty(
103              relativeImportPath
104            )
105          ) {
106            CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath[
107              relativeImportPath
108            ] = [];
109            for (const importedFile of klawSync(absoluteImportPath, {
110              nodir: true
111            })) {
112              CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath[
113                relativeImportPath
114              ].push(
115                path.relative(CONFIG.intermediateAppPath, importedFile.path)
116              );
117            }
118          }
119        }
120  
121        // Cache all styles in static; don't append variable imports
122        for (let lessFilePath of glob.sync(
123          path.join(CONFIG.intermediateAppPath, 'static', '**', '*.less')
124        )) {
125          cacheCompiledCSS(lessCache, lessFilePath, false);
126        }
127  
128        // Cache styles for all bundled non-theme packages
129        for (let nonThemePackage of nonThemePackages) {
130          for (let lessFilePath of glob.sync(
131            path.join(
132              CONFIG.intermediateAppPath,
133              'node_modules',
134              nonThemePackage,
135              '**',
136              '*.less'
137            )
138          )) {
139            cacheCompiledCSS(lessCache, lessFilePath, true);
140          }
141        }
142  
143        // Cache styles for this UI theme
144        const uiThemeMainPath = path.join(
145          CONFIG.intermediateAppPath,
146          'node_modules',
147          uiTheme,
148          'index.less'
149        );
150        cacheCompiledCSS(lessCache, uiThemeMainPath, true);
151        for (let lessFilePath of glob.sync(
152          path.join(
153            CONFIG.intermediateAppPath,
154            'node_modules',
155            uiTheme,
156            '**',
157            '*.less'
158          )
159        )) {
160          if (lessFilePath !== uiThemeMainPath) {
161            saveIntoSnapshotAuxiliaryData(
162              lessFilePath,
163              fs.readFileSync(lessFilePath, 'utf8')
164            );
165          }
166        }
167  
168        // Cache styles for this syntax theme
169        const syntaxThemeMainPath = path.join(
170          CONFIG.intermediateAppPath,
171          'node_modules',
172          syntaxTheme,
173          'index.less'
174        );
175        cacheCompiledCSS(lessCache, syntaxThemeMainPath, true);
176        for (let lessFilePath of glob.sync(
177          path.join(
178            CONFIG.intermediateAppPath,
179            'node_modules',
180            syntaxTheme,
181            '**',
182            '*.less'
183          )
184        )) {
185          if (lessFilePath !== syntaxThemeMainPath) {
186            saveIntoSnapshotAuxiliaryData(
187              lessFilePath,
188              fs.readFileSync(lessFilePath, 'utf8')
189            );
190          }
191        }
192      }
193    }
194  
195    for (let lessFilePath of glob.sync(
196      path.join(
197        CONFIG.intermediateAppPath,
198        'node_modules',
199        'atom-ui',
200        '**',
201        '*.less'
202      )
203    )) {
204      saveIntoSnapshotAuxiliaryData(
205        lessFilePath,
206        fs.readFileSync(lessFilePath, 'utf8')
207      );
208    }
209  
210    function cacheCompiledCSS(lessCache, lessFilePath, importFallbackVariables) {
211      let lessSource = fs.readFileSync(lessFilePath, 'utf8');
212      if (importFallbackVariables) {
213        lessSource = FALLBACK_VARIABLE_IMPORTS + lessSource;
214      }
215      lessCache.cssForFile(lessFilePath, lessSource);
216      saveIntoSnapshotAuxiliaryData(lessFilePath, lessSource);
217    }
218  };