diff --git a/package.json b/package.json index 4f72a19ee..72d16010a 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "jasmine-tagged": "^1.1.4", "jquery": "2.1.4", "key-path-helpers": "^0.4.0", - "less-cache": "1.0.0", + "less-cache": "1.1.0", "line-top-index": "0.2.0", "marked": "^0.3.6", "minimatch": "^3.0.3", diff --git a/script/config.js b/script/config.js index 293af1136..2b9641fe2 100644 --- a/script/config.js +++ b/script/config.js @@ -26,7 +26,8 @@ module.exports = { repositoryRootPath, apmRootPath, scriptRootPath, buildOutputPath, docsOutputPath, intermediateAppPath, symbolsPath, electronDownloadPath, atomHomeDirPath, homeDirPath, - getApmBinPath, getNpmBinPath + getApmBinPath, getNpmBinPath, + snapshotAuxiliaryData: {} } function getChannel () { diff --git a/script/lib/generate-metadata.js b/script/lib/generate-metadata.js index 8267b1cee..eec39d5c1 100644 --- a/script/lib/generate-metadata.js +++ b/script/lib/generate-metadata.js @@ -2,7 +2,7 @@ const CSON = require('season') const deprecatedPackagesMetadata = require('../deprecated-packages') -const fs = require('fs-extra') +const fs = require('fs-plus') const normalizePackageData = require('normalize-package-data') const path = require('path') const semver = require('semver') @@ -76,6 +76,26 @@ function buildBundledPackagesMetadata () { } } + const packageStyleSheetsPath = path.join(packagePath, 'styles') + let styleSheets = null + if (packageMetadata.mainStyleSheet) { + styleSheets = [fs.resolve(packagePath, packageMetadata.mainStyleSheet)] + } else if (packageMetadata.styleSheets) { + styleSheets = packageMetadata.styleSheets.map((name) => ( + fs.resolve(packageStyleSheetsPath, name, ['css', 'less', '']) + )) + } else { + const indexStylesheet = fs.resolve(packagePath, 'index', ['css', 'less']) + if (indexStylesheet) { + styleSheets = [indexStylesheet] + } else { + styleSheets = fs.listSync(packageStyleSheetsPath, ['css', 'less']) + } + } + + packageNewMetadata.styleSheetPaths = + styleSheets.map(styleSheetPath => path.relative(packagePath, styleSheetPath)) + packages[packageMetadata.name] = packageNewMetadata if (packageModuleCache.extensions) { for (let extension of Object.keys(packageModuleCache.extensions)) { diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js index 1dfe05f51..dec5b597c 100644 --- a/script/lib/generate-startup-snapshot.js +++ b/script/lib/generate-startup-snapshot.js @@ -15,6 +15,7 @@ module.exports = function (packagedAppPath) { baseDirPath, mainPath: path.resolve(baseDirPath, '..', 'src', 'initialize-application-window.js'), cachePath: path.join(CONFIG.atomHomeDirPath, 'snapshot-cache'), + auxiliaryData: CONFIG.snapshotAuxiliaryData, shouldExcludeModule: (modulePath) => { if (processedFiles > 0) { process.stdout.write('\r') diff --git a/script/lib/prebuild-less-cache.js b/script/lib/prebuild-less-cache.js index 1a1432fc9..ee2dee6ec 100644 --- a/script/lib/prebuild-less-cache.js +++ b/script/lib/prebuild-less-cache.js @@ -1,6 +1,7 @@ 'use strict' const fs = require('fs') +const klawSync = require('klaw-sync') const glob = require('glob') const path = require('path') const LessCache = require('less-cache') @@ -28,6 +29,18 @@ module.exports = function () { } } + CONFIG.snapshotAuxiliaryData.lessSourcesByRelativeFilePath = {} + function saveIntoSnapshotAuxiliaryData (absoluteFilePath, content) { + const relativeFilePath = path.relative(CONFIG.intermediateAppPath, absoluteFilePath) + if (!CONFIG.snapshotAuxiliaryData.lessSourcesByRelativeFilePath.hasOwnProperty(relativeFilePath)) { + CONFIG.snapshotAuxiliaryData.lessSourcesByRelativeFilePath[relativeFilePath] = { + content: content, + digest: LessCache.digestForContent(content) + } + } + } + + CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath = {} // Warm cache for every combination of the default UI and syntax themes, // because themes assign variables which may be used in any style sheet. for (let uiTheme of uiThemes) { @@ -46,12 +59,26 @@ module.exports = function () { ] }) + // Store file paths located at the import paths so that we can avoid scanning them at runtime. + for (const absoluteImportPath of lessCache.getImportPaths()) { + const relativeImportPath = path.relative(CONFIG.intermediateAppPath, absoluteImportPath) + if (!CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath.hasOwnProperty(relativeImportPath)) { + CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath[relativeImportPath] = [] + for (const importedFile of klawSync(absoluteImportPath, {nodir: true})) { + CONFIG.snapshotAuxiliaryData.importedFilePathsByRelativeImportPath[relativeImportPath].push( + path.relative(CONFIG.intermediateAppPath, importedFile.path) + ) + } + } + } + function cacheCompiledCSS(lessFilePath, importFallbackVariables) { let lessSource = fs.readFileSync(lessFilePath, 'utf8') if (importFallbackVariables) { lessSource = FALLBACK_VARIABLE_IMPORTS + lessSource } lessCache.cssForFile(lessFilePath, lessSource) + saveIntoSnapshotAuxiliaryData(lessFilePath, lessSource) } // Cache all styles in static; don't append variable imports @@ -69,10 +96,24 @@ module.exports = function () { // Cache styles for this UI theme const uiThemeMainPath = path.join(CONFIG.intermediateAppPath, 'node_modules', uiTheme, 'index.less') cacheCompiledCSS(uiThemeMainPath, true) + for (let lessFilePath of glob.sync(path.join(CONFIG.intermediateAppPath, 'node_modules', uiTheme, '**', '*.less'))) { + if (lessFilePath !== uiThemeMainPath) { + saveIntoSnapshotAuxiliaryData(lessFilePath, fs.readFileSync(lessFilePath, 'utf8')) + } + } // Cache styles for this syntax theme const syntaxThemeMainPath = path.join(CONFIG.intermediateAppPath, 'node_modules', syntaxTheme, 'index.less') cacheCompiledCSS(syntaxThemeMainPath, true) + for (let lessFilePath of glob.sync(path.join(CONFIG.intermediateAppPath, 'node_modules', syntaxTheme, '**', '*.less'))) { + if (lessFilePath !== syntaxThemeMainPath) { + saveIntoSnapshotAuxiliaryData(lessFilePath, fs.readFileSync(lessFilePath, 'utf8')) + } + } } } + + for (let lessFilePath of glob.sync(path.join(CONFIG.intermediateAppPath, 'node_modules', 'atom-ui', '**', '*.less'))) { + saveIntoSnapshotAuxiliaryData(lessFilePath, fs.readFileSync(lessFilePath, 'utf8')) + } } diff --git a/script/package.json b/script/package.json index 540855abb..a11b7b55e 100644 --- a/script/package.json +++ b/script/package.json @@ -16,6 +16,7 @@ "fs-extra": "0.30.0", "glob": "7.0.3", "joanna": "0.0.8", + "klaw-sync": "^1.1.2", "legal-eagle": "0.14.0", "lodash.template": "4.4.0", "minidump": "0.9.0", diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index a34cdf9b1..06a13f7ed 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -232,7 +232,7 @@ class AtomEnvironment extends Model @styles.initialize({@configDirPath}) @packages.initialize({devMode, @configDirPath, resourcePath, safeMode}) - @themes.initialize({@configDirPath, resourcePath, safeMode}) + @themes.initialize({@configDirPath, resourcePath, safeMode, devMode}) @commandInstaller.initialize(@getVersion()) @workspace.initialize() diff --git a/src/less-compile-cache.coffee b/src/less-compile-cache.coffee index c70f312ee..84fd92247 100644 --- a/src/less-compile-cache.coffee +++ b/src/less-compile-cache.coffee @@ -4,9 +4,9 @@ LessCache = require 'less-cache' # {LessCache} wrapper used by {ThemeManager} to read stylesheets. module.exports = class LessCompileCache - @cacheDir: path.join(process.env.ATOM_HOME, 'compile-cache', 'less') + constructor: ({resourcePath, importPaths, lessSourcesByRelativeFilePath, importedFilePathsByRelativeImportPath}) -> + cacheDir = path.join(process.env.ATOM_HOME, 'compile-cache', 'less') - constructor: ({resourcePath, importPaths}) -> @lessSearchPaths = [ path.join(resourcePath, 'static', 'variables') path.join(resourcePath, 'static') @@ -17,11 +17,14 @@ class LessCompileCache else importPaths = @lessSearchPaths - @cache = new LessCache - cacheDir: @constructor.cacheDir - importPaths: importPaths - resourcePath: resourcePath + @cache = new LessCache({ + importPaths, + resourcePath, + lessSourcesByRelativeFilePath, + importedFilePathsByRelativeImportPath, + cacheDir, fallbackDir: path.join(resourcePath, 'less-compile-cache') + }) setImportPaths: (importPaths=[]) -> @cache.setImportPaths(importPaths.concat(@lessSearchPaths)) @@ -29,5 +32,5 @@ class LessCompileCache read: (stylesheetPath) -> @cache.readFileSync(stylesheetPath) - cssForFile: (stylesheetPath, lessContent) -> - @cache.cssForFile(stylesheetPath, lessContent) + cssForFile: (stylesheetPath, lessContent, digest) -> + @cache.cssForFile(stylesheetPath, lessContent, digest) diff --git a/src/package.coffee b/src/package.coffee index ed0f7aa87..33f64c12d 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -204,7 +204,17 @@ class Package else context = undefined - @stylesheetDisposables.add(@styleManager.addStyleSheet(source, {sourcePath, priority, context})) + @stylesheetDisposables.add( + @styleManager.addStyleSheet( + source, + { + sourcePath, + priority, + context, + skipDeprecatedSelectorsTransformation: @bundledPackage + } + ) + ) @stylesheetsActivated = true activateResources: -> @@ -348,15 +358,19 @@ class Package path.join(@path, 'styles') getStylesheetPaths: -> - stylesheetDirPath = @getStylesheetsPath() - if @metadata.mainStyleSheet - [fs.resolve(@path, @metadata.mainStyleSheet)] - else if @metadata.styleSheets - @metadata.styleSheets.map (name) -> fs.resolve(stylesheetDirPath, name, ['css', 'less', '']) - else if indexStylesheet = fs.resolve(@path, 'index', ['css', 'less']) - [indexStylesheet] + if @bundledPackage and @packageManager.packagesCache[@name]?.styleSheetPaths? + styleSheetPaths = @packageManager.packagesCache[@name].styleSheetPaths + styleSheetPaths.map (styleSheetPath) => path.join(@path, styleSheetPath) else - fs.listSync(stylesheetDirPath, ['css', 'less']) + stylesheetDirPath = @getStylesheetsPath() + if @metadata.mainStyleSheet + [fs.resolve(@path, @metadata.mainStyleSheet)] + else if @metadata.styleSheets + @metadata.styleSheets.map (name) -> fs.resolve(stylesheetDirPath, name, ['css', 'less', '']) + else if indexStylesheet = fs.resolve(@path, 'index', ['css', 'less']) + [indexStylesheet] + else + fs.listSync(stylesheetDirPath, ['css', 'less']) loadGrammarsSync: -> return if @grammarsLoaded diff --git a/src/style-manager.js b/src/style-manager.js index 718c1ee74..6ffc8de7c 100644 --- a/src/style-manager.js +++ b/src/style-manager.js @@ -137,29 +137,17 @@ module.exports = class StyleManager { } } - let transformed - if (this.cacheDirPath != null) { - const hash = crypto.createHash('sha1') - if (params.context != null) { - hash.update(params.context) - } - hash.update(source) - const cacheFilePath = path.join(this.cacheDirPath, hash.digest('hex')) - try { - transformed = JSON.parse(fs.readFileSync(cacheFilePath)) - } catch (e) { - transformed = transformDeprecatedShadowDOMSelectors(source, params.context) - fs.writeFileSync(cacheFilePath, JSON.stringify(transformed)) - } + if (params.skipDeprecatedSelectorsTransformation) { + styleElement.textContent = source } else { - transformed = transformDeprecatedShadowDOMSelectors(source, params.context) + const transformed = this.upgradeDeprecatedSelectorsForStyleSheet(source, params.context) + styleElement.textContent = transformed.source + if (transformed.deprecationMessage) { + this.deprecationsBySourcePath[params.sourcePath] = {message: transformed.deprecationMessage} + this.emitter.emit('did-update-deprecations') + } } - styleElement.textContent = transformed.source - if (transformed.deprecationMessage) { - this.deprecationsBySourcePath[params.sourcePath] = {message: transformed.deprecationMessage} - this.emitter.emit('did-update-deprecations') - } if (updated) { this.emitter.emit('did-update-style-element', styleElement) } else { @@ -171,9 +159,10 @@ module.exports = class StyleManager { addStyleElement (styleElement) { let insertIndex = this.styleElements.length if (styleElement.priority != null) { - for (let [index, existingElement] of this.styleElements.entries()) { + for (let i = 0; i < this.styleElements.length; i++) { + const existingElement = this.styleElements[i] if (existingElement.priority > styleElement.priority) { - insertIndex = index + insertIndex = i break } } @@ -197,6 +186,26 @@ module.exports = class StyleManager { } } + upgradeDeprecatedSelectorsForStyleSheet (styleSheet, context) { + if (this.cacheDirPath != null) { + const hash = crypto.createHash('sha1') + if (context != null) { + hash.update(context) + } + hash.update(styleSheet) + const cacheFilePath = path.join(this.cacheDirPath, hash.digest('hex')) + try { + return JSON.parse(fs.readFileSync(cacheFilePath)) + } catch (e) { + const transformed = transformDeprecatedShadowDOMSelectors(styleSheet, context) + fs.writeFileSync(cacheFilePath, JSON.stringify(transformed)) + return transformed + } + } else { + return transformDeprecatedShadowDOMSelectors(styleSheet, context) + } + } + getDeprecations () { return this.deprecationsBySourcePath } diff --git a/src/theme-manager.coffee b/src/theme-manager.coffee index f035889a2..0f019dbf0 100644 --- a/src/theme-manager.coffee +++ b/src/theme-manager.coffee @@ -3,6 +3,7 @@ _ = require 'underscore-plus' {Emitter, CompositeDisposable} = require 'event-kit' {File} = require 'pathwatcher' fs = require 'fs-plus' +LessCompileCache = require './less-compile-cache' # Extended: Handles loading and activating available themes. # @@ -18,7 +19,14 @@ class ThemeManager @packageManager.onDidActivateInitialPackages => @onDidChangeActiveThemes => @packageManager.reloadActivePackageStyleSheets() - initialize: ({@resourcePath, @configDirPath, @safeMode}) -> + initialize: ({@resourcePath, @configDirPath, @safeMode, devMode}) -> + @lessSourcesByRelativeFilePath = null + if devMode or typeof snapshotAuxiliaryData is 'undefined' + @lessSourcesByRelativeFilePath = {} + @importedFilePathsByRelativeImportPath = {} + else + @lessSourcesByRelativeFilePath = snapshotAuxiliaryData.lessSourcesByRelativeFilePath + @importedFilePathsByRelativeImportPath = snapshotAuxiliaryData.importedFilePathsByRelativeImportPath ### Section: Event Subscription @@ -126,10 +134,10 @@ class ThemeManager # # Returns a {Disposable} on which `.dispose()` can be called to remove the # required stylesheet. - requireStylesheet: (stylesheetPath) -> + requireStylesheet: (stylesheetPath, priority, skipDeprecatedSelectorsTransformation) -> if fullPath = @resolveStylesheet(stylesheetPath) content = @loadStylesheet(fullPath) - @applyStylesheet(fullPath, content) + @applyStylesheet(fullPath, content, priority, skipDeprecatedSelectorsTransformation) else throw new Error("Could not find a file at path '#{stylesheetPath}'") @@ -175,9 +183,7 @@ class ThemeManager @reloadBaseStylesheets() reloadBaseStylesheets: -> - @requireStylesheet('../static/atom') - if nativeStylesheetPath = fs.resolveOnLoadPath(process.platform, ['css', 'less']) - @requireStylesheet(nativeStylesheetPath) + @requireStylesheet('../static/atom', -2, true) stylesheetElementForId: (id) -> escapedId = id.replace(/\\/g, '\\\\') @@ -196,9 +202,12 @@ class ThemeManager fs.readFileSync(stylesheetPath, 'utf8') loadLessStylesheet: (lessStylesheetPath, importFallbackVariables=false) -> - unless @lessCache? - LessCompileCache = require './less-compile-cache' - @lessCache = new LessCompileCache({@resourcePath, importPaths: @getImportPaths()}) + @lessCache ?= new LessCompileCache({ + @resourcePath, + @lessSourcesByRelativeFilePath, + @importedFilePathsByRelativeImportPath, + importPaths: @getImportPaths() + }) try if importFallbackVariables @@ -206,8 +215,16 @@ class ThemeManager @import "variables/ui-variables"; @import "variables/syntax-variables"; """ - less = fs.readFileSync(lessStylesheetPath, 'utf8') - @lessCache.cssForFile(lessStylesheetPath, [baseVarImports, less].join('\n')) + relativeFilePath = path.relative(@resourcePath, lessStylesheetPath) + lessSource = @lessSourcesByRelativeFilePath[relativeFilePath] + if lessSource? + content = lessSource.content + digest = lessSource.digest + else + content = baseVarImports + '\n' + fs.readFileSync(lessStylesheetPath, 'utf8') + digest = null + + @lessCache.cssForFile(lessStylesheetPath, content, digest) else @lessCache.read(lessStylesheetPath) catch error @@ -231,8 +248,15 @@ class ThemeManager removeStylesheet: (stylesheetPath) -> @styleSheetDisposablesBySourcePath[stylesheetPath]?.dispose() - applyStylesheet: (path, text) -> - @styleSheetDisposablesBySourcePath[path] = @styleManager.addStyleSheet(text, sourcePath: path) + applyStylesheet: (path, text, priority, skipDeprecatedSelectorsTransformation) -> + @styleSheetDisposablesBySourcePath[path] = @styleManager.addStyleSheet( + text, + { + priority, + skipDeprecatedSelectorsTransformation, + sourcePath: path + } + ) activateThemes: -> new Promise (resolve) => diff --git a/src/workspace-element.js b/src/workspace-element.js index 65333e7d3..ae810cecd 100644 --- a/src/workspace-element.js +++ b/src/workspace-element.js @@ -59,7 +59,7 @@ class WorkspaceElement extends HTMLElement { font-family: ${this.config.get('editor.fontFamily')}; line-height: ${this.config.get('editor.lineHeight')}; }` - this.styles.addStyleSheet(styleSheetSource, {sourcePath: 'global-text-editor-styles'}) + this.styles.addStyleSheet(styleSheetSource, {sourcePath: 'global-text-editor-styles', priority: -1}) this.views.performDocumentPoll() }