diff --git a/.travis.yml b/.travis.yml index 06416e8e0..6c3f25c49 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,7 @@ cache: - apm/node_modules - script/node_modules - ~/.atom/compile-cache + - ~/.atom/snapshot-cache notifications: email: diff --git a/apm/package.json b/apm/package.json index e57d07aee..5e3dcb1e0 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "1.16.1" + "atom-package-manager": "1.17.0" } } diff --git a/appveyor.yml b/appveyor.yml index 3d8c0b274..e94e75441 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -52,3 +52,4 @@ cache: - '%APPVEYOR_BUILD_FOLDER%\electron' - '%USERPROFILE%\.atom\.apm' - '%USERPROFILE%\.atom\compile-cache' + - '%USERPROFILE%\.atom\snapshot-cache' diff --git a/circle.yml b/circle.yml index c264754d4..5890c45e9 100644 --- a/circle.yml +++ b/circle.yml @@ -29,6 +29,7 @@ dependencies: - script/node_modules - node_modules - ~/.atom/compile-cache + - ~/.atom/snapshot-cache test: override: diff --git a/package.json b/package.json index 1a8fc774c..699559ac7 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/atom/atom/issues" }, "license": "MIT", - "electronVersion": "1.3.13", + "electronVersion": "1.3.14", "dependencies": { "async": "0.2.6", "atom-keymap": "8.0.2", @@ -33,7 +33,7 @@ "cached-run-in-this-context": "0.4.1", "chai": "3.5.0", "chart.js": "^2.3.0", - "clear-cut": "^2.0.1", + "clear-cut": "^2.0.2", "coffee-script": "1.11.1", "color": "^0.7.3", "dedent": "^0.6.0", diff --git a/script/build b/script/build index bee0c9514..2b45e425d 100755 --- a/script/build +++ b/script/build @@ -35,6 +35,7 @@ const dumpSymbols = require('./lib/dump-symbols') const generateAPIDocs = require('./lib/generate-api-docs') const generateMetadata = require('./lib/generate-metadata') const generateModuleCache = require('./lib/generate-module-cache') +const generateStartupSnapshot = require('./lib/generate-startup-snapshot') const installApplication = require('./lib/install-application') const packageApplication = require('./lib/package-application') const prebuildLessCache = require('./lib/prebuild-less-cache') @@ -61,6 +62,7 @@ generateMetadata() generateAPIDocs() dumpSymbols() .then(packageApplication) + .then(packagedAppPath => generateStartupSnapshot(packagedAppPath).then(() => packagedAppPath)) .then(packagedAppPath => { if (process.platform === 'darwin') { if (argv.codeSign) { diff --git a/script/lib/check-chromedriver-version.js b/script/lib/check-chromedriver-version.js index 90bc220e5..6fd313fc7 100644 --- a/script/lib/check-chromedriver-version.js +++ b/script/lib/check-chromedriver-version.js @@ -7,16 +7,27 @@ const semver = require('semver') module.exports = function () { // Chromedriver should be specified as ~x.y where x and y match Electron major/minor const chromedriverVer = buildMetadata.dependencies['electron-chromedriver'] + const mksnapshotVer = buildMetadata.dependencies['electron-mksnapshot'] // Always use tilde on electron-chromedriver so that it can pick up the best patch vesion if (!chromedriverVer.startsWith('~')) { throw new Error(`electron-chromedriver version in script/package.json should start with a tilde to match latest patch version.`) } + if (!mksnapshotVer.startsWith('~')) { + throw new Error(`electron-mksnapshot version in script/package.json should start with a tilde to match latest patch version.`) + } + const electronVer = CONFIG.appMetadata.electronVersion if (!semver.satisfies(electronVer, chromedriverVer)) { throw new Error(`electron-chromedriver ${chromedriverVer} incompatible with electron ${electronVer}.\n` + 'Did you upgrade electron in package.json and forget to upgrade electron-chromedriver in ' + `script/package.json to '~${semver.major(electronVer)}.${semver.minor(electronVer)}' ?`) } + + if (!semver.satisfies(electronVer, mksnapshotVer)) { + throw new Error(`electron-mksnapshot ${mksnapshotVer} incompatible with electron ${electronVer}.\n` + + 'Did you upgrade electron in package.json and forget to upgrade electron-mksnapshot in ' + + `script/package.json to '~${semver.major(electronVer)}.${semver.minor(electronVer)}' ?`) + } } diff --git a/script/lib/create-debian-package.js b/script/lib/create-debian-package.js index 03196816d..fd3e07b92 100644 --- a/script/lib/create-debian-package.js +++ b/script/lib/create-debian-package.js @@ -92,7 +92,7 @@ module.exports = function (packagedAppPath) { console.log(`Copying icon into "${debianPackageIconsDirPath}"`) fs.copySync( - path.join(packagedAppPath, 'resources', 'app.asar.unpacked', 'resources', 'atom.png'), + path.join(packagedAppPath, 'resources', 'app', 'resources', 'atom.png'), path.join(debianPackageIconsDirPath, `${atomExecutableName}.png`) ) diff --git a/script/lib/generate-metadata.js b/script/lib/generate-metadata.js index 89ef04f69..8267b1cee 100644 --- a/script/lib/generate-metadata.js +++ b/script/lib/generate-metadata.js @@ -27,8 +27,8 @@ function buildBundledPackagesMetadata () { const packagePath = path.join(CONFIG.intermediateAppPath, 'node_modules', packageName) const packageMetadataPath = path.join(packagePath, 'package.json') const packageMetadata = JSON.parse(fs.readFileSync(packageMetadataPath, 'utf8')) - normalizePackageData(packageMetadata, () => { - throw new Error(`Invalid package metadata. ${metadata.name}: ${msg}`) + normalizePackageData(packageMetadata, (msg) => { + console.warn(`Invalid package metadata. ${packageMetadata.name}: ${msg}`) }, true) if (packageMetadata.repository && packageMetadata.repository.url && packageMetadata.repository.type === 'git') { packageMetadata.repository.url = packageMetadata.repository.url.replace(/^git\+/, '') diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js new file mode 100644 index 000000000..0021cf1ab --- /dev/null +++ b/script/lib/generate-startup-snapshot.js @@ -0,0 +1,86 @@ +const childProcess = require('child_process') +const fs = require('fs') +const path = require('path') +const electronLink = require('electron-link') +const CONFIG = require('../config') +const vm = require('vm') + +module.exports = function (packagedAppPath) { + const snapshotScriptPath = path.join(CONFIG.buildOutputPath, 'startup.js') + const coreModules = new Set(['electron', 'atom', 'shell', 'WNdb', 'lapack', 'remote']) + const baseDirPath = path.join(CONFIG.intermediateAppPath, 'static') + let processedFiles = 0 + + return electronLink({ + baseDirPath, + mainPath: path.resolve(baseDirPath, '..', 'src', 'initialize-application-window.js'), + cachePath: path.join(CONFIG.atomHomeDirPath, 'snapshot-cache'), + shouldExcludeModule: (modulePath) => { + if (processedFiles > 0) { + process.stdout.write('\r') + } + process.stdout.write(`Generating snapshot script at "${snapshotScriptPath}" (${++processedFiles})`) + + const relativePath = path.relative(baseDirPath, modulePath) + return ( + modulePath.endsWith('.node') || + coreModules.has(modulePath) || + (relativePath.startsWith(path.join('..', 'src')) && relativePath.endsWith('-element.js')) || + relativePath == path.join('..', 'exports', 'atom.js') || + relativePath == path.join('..', 'src', 'config-schema.js') || + relativePath == path.join('..', 'src', 'electron-shims.js') || + relativePath == path.join('..', 'src', 'safe-clipboard.js') || + relativePath == path.join('..', 'node_modules', 'atom-keymap', 'lib', 'command-event.js') || + relativePath == path.join('..', 'node_modules', 'babel-core', 'index.js') || + relativePath == path.join('..', 'node_modules', 'cached-run-in-this-context', 'lib', 'main.js') || + relativePath == path.join('..', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || + relativePath == path.join('..', 'node_modules', 'cson-parser', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') || + relativePath == path.join('..', 'node_modules', 'decompress-zip', 'lib', 'decompress-zip.js') || + relativePath == path.join('..', 'node_modules', 'debug', 'node.js') || + relativePath == path.join('..', 'node_modules', 'git-utils', 'lib', 'git.js') || + relativePath == path.join('..', 'node_modules', 'glob', 'glob.js') || + relativePath == path.join('..', 'node_modules', 'htmlparser2', 'lib', 'index.js') || + relativePath == path.join('..', 'node_modules', 'iconv-lite', 'encodings', 'index.js') || + relativePath == path.join('..', 'node_modules', 'less', 'index.js') || + relativePath == path.join('..', 'node_modules', 'less', 'lib', 'less', 'fs.js') || + relativePath == path.join('..', 'node_modules', 'less', 'lib', 'less-node', 'index.js') || + relativePath == path.join('..', 'node_modules', 'less', 'node_modules', 'graceful-fs', 'graceful-fs.js') || + relativePath == path.join('..', 'node_modules', 'superstring', 'index.js') || + relativePath == path.join('..', 'node_modules', 'oniguruma', 'lib', 'oniguruma.js') || + relativePath == path.join('..', 'node_modules', 'request', 'index.js') || + relativePath == path.join('..', 'node_modules', 'resolve', 'index.js') || + relativePath == path.join('..', 'node_modules', 'resolve', 'lib', 'core.js') || + relativePath == path.join('..', 'node_modules', 'settings-view', 'node_modules', 'glob', 'glob.js') || + relativePath == path.join('..', 'node_modules', 'spellchecker', 'lib', 'spellchecker.js') || + relativePath == path.join('..', 'node_modules', 'spelling-manager', 'node_modules', 'natural', 'lib', 'natural', 'index.js') || + relativePath == path.join('..', 'node_modules', 'tar', 'tar.js') || + relativePath == path.join('..', 'node_modules', 'temp', 'lib', 'temp.js') || + relativePath == path.join('..', 'node_modules', 'tmp', 'lib', 'tmp.js') + ) + } + }).then((snapshotScriptContent) => { + fs.writeFileSync(snapshotScriptPath, snapshotScriptContent) + process.stdout.write('\n') + + console.log('Verifying if snapshot can be executed via `mksnapshot`') + vm.runInNewContext(snapshotScriptContent, undefined, {filename: snapshotScriptPath, displayErrors: true}) + + const generatedStartupBlobPath = path.join(CONFIG.buildOutputPath, 'snapshot_blob.bin') + console.log(`Generating startup blob at "${generatedStartupBlobPath}"`) + childProcess.execFileSync( + path.join(CONFIG.repositoryRootPath, 'script', 'node_modules', 'electron-mksnapshot', 'bin', 'mksnapshot'), + [snapshotScriptPath, '--startup_blob', generatedStartupBlobPath] + ) + + let startupBlobDestinationPath + if (process.platform === 'darwin') { + startupBlobDestinationPath = `${packagedAppPath}/Contents/Frameworks/Electron Framework.framework/Resources/snapshot_blob.bin` + } else { + startupBlobDestinationPath = path.join(packagedAppPath, 'snapshot_blob.bin') + } + + console.log(`Moving generated startup blob into "${startupBlobDestinationPath}"`) + fs.unlinkSync(startupBlobDestinationPath) + fs.renameSync(generatedStartupBlobPath, startupBlobDestinationPath) + }) +} diff --git a/script/lib/package-application.js b/script/lib/package-application.js index 1e63b8dc0..5e01e5543 100644 --- a/script/lib/package-application.js +++ b/script/lib/package-application.js @@ -19,7 +19,6 @@ module.exports = function () { 'app-copyright': `Copyright © 2014-${(new Date()).getFullYear()} GitHub, Inc. All rights reserved.`, 'app-version': CONFIG.appMetadata.version, 'arch': process.platform === 'darwin' ? 'x64' : process.arch, // OS X is 64-bit only - 'asar': {unpack: buildAsarUnpackGlobExpression()}, 'build-version': CONFIG.appMetadata.version, 'download': {cache: CONFIG.electronDownloadPath}, 'dir': CONFIG.intermediateAppPath, @@ -96,20 +95,6 @@ function chmodNodeFiles (packagedAppPath) { childProcess.execSync(`find "${packagedAppPath}" -type f -name *.node -exec chmod a-x {} \\;`) } -function buildAsarUnpackGlobExpression () { - const unpack = [ - '*.node', - 'ctags-config', - 'ctags-darwin', - 'ctags-linux', - 'ctags-win32.exe', - path.join('**', 'node_modules', 'spellchecker', '**'), - path.join('**', 'resources', 'atom.png') - ] - - return `{${unpack.join(',')}}` -} - function getAppName () { if (process.platform === 'darwin') { return CONFIG.channel === 'beta' ? 'Atom Beta' : 'Atom' diff --git a/script/package.json b/script/package.json index 89beaa492..9441213ac 100644 --- a/script/package.json +++ b/script/package.json @@ -8,6 +8,8 @@ "csslint": "1.0.2", "donna": "1.0.13", "electron-chromedriver": "~1.3", + "electron-link": "0.0.18", + "electron-mksnapshot": "~1.3", "electron-packager": "7.3.0", "electron-winstaller": "2.5.1", "fs-extra": "0.30.0", diff --git a/spec/file-system-blob-store-spec.coffee b/spec/file-system-blob-store-spec.coffee index a2ed39014..ff1c81eb6 100644 --- a/spec/file-system-blob-store-spec.coffee +++ b/spec/file-system-blob-store-spec.coffee @@ -14,78 +14,75 @@ describe "FileSystemBlobStore", -> fs.removeSync(storageDirectory) it "is empty when the file doesn't exist", -> - expect(blobStore.get("foo", "invalidation-key-1")).toBeUndefined() - expect(blobStore.get("bar", "invalidation-key-2")).toBeUndefined() + expect(blobStore.get("foo")).toBeUndefined() + expect(blobStore.get("bar")).toBeUndefined() it "allows to read and write buffers from/to memory without persisting them", -> - blobStore.set("foo", "invalidation-key-1", new Buffer("foo")) - blobStore.set("bar", "invalidation-key-2", new Buffer("bar")) + blobStore.set("foo", new Buffer("foo")) + blobStore.set("bar", new Buffer("bar")) - expect(blobStore.get("foo", "invalidation-key-1")).toEqual(new Buffer("foo")) - expect(blobStore.get("bar", "invalidation-key-2")).toEqual(new Buffer("bar")) + expect(blobStore.get("foo")).toEqual(new Buffer("foo")) + expect(blobStore.get("bar")).toEqual(new Buffer("bar")) - expect(blobStore.get("foo", "unexisting-key")).toBeUndefined() - expect(blobStore.get("bar", "unexisting-key")).toBeUndefined() + expect(blobStore.get("baz")).toBeUndefined() + expect(blobStore.get("qux")).toBeUndefined() it "persists buffers when saved and retrieves them on load, giving priority to in-memory ones", -> - blobStore.set("foo", "invalidation-key-1", new Buffer("foo")) - blobStore.set("bar", "invalidation-key-2", new Buffer("bar")) + blobStore.set("foo", new Buffer("foo")) + blobStore.set("bar", new Buffer("bar")) blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - expect(blobStore.get("foo", "invalidation-key-1")).toEqual(new Buffer("foo")) - expect(blobStore.get("bar", "invalidation-key-2")).toEqual(new Buffer("bar")) - expect(blobStore.get("foo", "unexisting-key")).toBeUndefined() - expect(blobStore.get("bar", "unexisting-key")).toBeUndefined() + expect(blobStore.get("foo")).toEqual(new Buffer("foo")) + expect(blobStore.get("bar")).toEqual(new Buffer("bar")) + expect(blobStore.get("baz")).toBeUndefined() + expect(blobStore.get("qux")).toBeUndefined() - blobStore.set("foo", "new-key", new Buffer("changed")) + blobStore.set("foo", new Buffer("changed")) - expect(blobStore.get("foo", "new-key")).toEqual(new Buffer("changed")) - expect(blobStore.get("foo", "invalidation-key-1")).toBeUndefined() + expect(blobStore.get("foo")).toEqual(new Buffer("changed")) - it "persists both in-memory and previously stored buffers when saved", -> - blobStore.set("foo", "invalidation-key-1", new Buffer("foo")) - blobStore.set("bar", "invalidation-key-2", new Buffer("bar")) + it "persists in-memory and previously stored buffers, and deletes unused keys when saved", -> + blobStore.set("foo", new Buffer("foo")) + blobStore.set("bar", new Buffer("bar")) blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - blobStore.set("bar", "invalidation-key-3", new Buffer("changed")) - blobStore.set("qux", "invalidation-key-4", new Buffer("qux")) + blobStore.set("bar", new Buffer("changed")) + blobStore.set("qux", new Buffer("qux")) blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - expect(blobStore.get("foo", "invalidation-key-1")).toEqual(new Buffer("foo")) - expect(blobStore.get("bar", "invalidation-key-3")).toEqual(new Buffer("changed")) - expect(blobStore.get("qux", "invalidation-key-4")).toEqual(new Buffer("qux")) - expect(blobStore.get("foo", "unexisting-key")).toBeUndefined() - expect(blobStore.get("bar", "invalidation-key-2")).toBeUndefined() - expect(blobStore.get("qux", "unexisting-key")).toBeUndefined() + expect(blobStore.get("foo")).toBeUndefined() + expect(blobStore.get("bar")).toEqual(new Buffer("changed")) + expect(blobStore.get("qux")).toEqual(new Buffer("qux")) it "allows to delete keys from both memory and stored buffers", -> - blobStore.set("a", "invalidation-key-1", new Buffer("a")) - blobStore.set("b", "invalidation-key-2", new Buffer("b")) + blobStore.set("a", new Buffer("a")) + blobStore.set("b", new Buffer("b")) blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - blobStore.set("b", "invalidation-key-3", new Buffer("b")) - blobStore.set("c", "invalidation-key-4", new Buffer("c")) + blobStore.get("a") # prevent the key from being deleted on save + blobStore.set("b", new Buffer("b")) + blobStore.set("c", new Buffer("c")) blobStore.delete("b") blobStore.delete("c") blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - expect(blobStore.get("a", "invalidation-key-1")).toEqual(new Buffer("a")) - expect(blobStore.get("b", "invalidation-key-2")).toBeUndefined() - expect(blobStore.get("b", "invalidation-key-3")).toBeUndefined() - expect(blobStore.get("c", "invalidation-key-4")).toBeUndefined() + expect(blobStore.get("a")).toEqual(new Buffer("a")) + expect(blobStore.get("b")).toBeUndefined() + expect(blobStore.get("b")).toBeUndefined() + expect(blobStore.get("c")).toBeUndefined() it "ignores errors when loading an invalid blob store", -> - blobStore.set("a", "invalidation-key-1", new Buffer("a")) - blobStore.set("b", "invalidation-key-2", new Buffer("b")) + blobStore.set("a", new Buffer("a")) + blobStore.set("b", new Buffer("b")) blobStore.save() # Simulate corruption @@ -95,14 +92,14 @@ describe "FileSystemBlobStore", -> blobStore = FileSystemBlobStore.load(storageDirectory) - expect(blobStore.get("a", "invalidation-key-1")).toBeUndefined() - expect(blobStore.get("b", "invalidation-key-2")).toBeUndefined() + expect(blobStore.get("a")).toBeUndefined() + expect(blobStore.get("b")).toBeUndefined() - blobStore.set("a", "invalidation-key-1", new Buffer("x")) - blobStore.set("b", "invalidation-key-2", new Buffer("y")) + blobStore.set("a", new Buffer("x")) + blobStore.set("b", new Buffer("y")) blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - expect(blobStore.get("a", "invalidation-key-1")).toEqual(new Buffer("x")) - expect(blobStore.get("b", "invalidation-key-2")).toEqual(new Buffer("y")) + expect(blobStore.get("a")).toEqual(new Buffer("x")) + expect(blobStore.get("b")).toEqual(new Buffer("y")) diff --git a/spec/native-compile-cache-spec.coffee b/spec/native-compile-cache-spec.coffee index 1531deaf9..a43cbe815 100644 --- a/spec/native-compile-cache-spec.coffee +++ b/spec/native-compile-cache-spec.coffee @@ -9,16 +9,18 @@ describe "NativeCompileCache", -> beforeEach -> cachedFiles = [] fakeCacheStore = jasmine.createSpyObj("cache store", ["set", "get", "has", "delete"]) - fakeCacheStore.has.andCallFake (cacheKey, invalidationKey) -> - fakeCacheStore.get(cacheKey, invalidationKey)? - fakeCacheStore.get.andCallFake (cacheKey, invalidationKey) -> + + fakeCacheStore.has.andCallFake (cacheKey) -> + fakeCacheStore.get(cacheKey)? + + fakeCacheStore.get.andCallFake (cacheKey) -> for entry in cachedFiles by -1 continue if entry.cacheKey isnt cacheKey - continue if entry.invalidationKey isnt invalidationKey return entry.cacheBuffer return - fakeCacheStore.set.andCallFake (cacheKey, invalidationKey, cacheBuffer) -> - cachedFiles.push({cacheKey, invalidationKey, cacheBuffer}) + + fakeCacheStore.set.andCallFake (cacheKey, cacheBuffer) -> + cachedFiles.push({cacheKey, cacheBuffer}) nativeCompileCache.setCacheStore(fakeCacheStore) nativeCompileCache.setV8Version("a-v8-version") @@ -29,13 +31,10 @@ describe "NativeCompileCache", -> fn2 = require('./fixtures/native-cache/file-2') expect(cachedFiles.length).toBe(2) - - expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-1')) expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0) expect(fn1()).toBe(1) - expect(cachedFiles[1].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-2')) expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array) expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) expect(fn2()).toBe(2) @@ -51,7 +50,6 @@ describe "NativeCompileCache", -> fn4 = require('./fixtures/native-cache/file-4') expect(cachedFiles.length).toBe(1) - expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4')) expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0) expect(fn4()).toBe("file-4") @@ -61,8 +59,6 @@ describe "NativeCompileCache", -> fn4 = require('./fixtures/native-cache/file-4') expect(cachedFiles.length).toBe(2) - expect(cachedFiles[1].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4')) - expect(cachedFiles[1].invalidationKey).not.toBe(cachedFiles[0].invalidationKey) expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array) expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) @@ -79,7 +75,6 @@ describe "NativeCompileCache", -> fn5 = require('./fixtures/native-cache/file-5') expect(cachedFiles.length).toBe(1) - expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-5')) expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0) expect(fn5()).toBe("file-5") @@ -89,8 +84,6 @@ describe "NativeCompileCache", -> fn5 = require('./fixtures/native-cache/file-5') expect(cachedFiles.length).toBe(2) - expect(cachedFiles[1].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-5')) - expect(cachedFiles[1].invalidationKey).not.toBe(cachedFiles[0].invalidationKey) expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array) expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) @@ -100,5 +93,5 @@ describe "NativeCompileCache", -> fn3 = require('./fixtures/native-cache/file-3') - expect(fakeCacheStore.delete).toHaveBeenCalledWith(require.resolve('./fixtures/native-cache/file-3')) + expect(fakeCacheStore.delete).toHaveBeenCalled() expect(fn3()).toBe(3) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index e45e11a10..7594f5de9 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -131,7 +131,7 @@ class AtomEnvironment extends Model # Call .loadOrCreate instead constructor: (params={}) -> - {@blobStore, @applicationDelegate, @window, @document, @clipboard, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params + {@applicationDelegate, @window, @document, @blobStore, @clipboard, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params @nextProxyRequestId = 0 @unloaded = false @@ -740,6 +740,10 @@ class AtomEnvironment extends Model @saveBlobStoreSync() @unloaded = true + saveBlobStoreSync: -> + if @enablePersistence + @blobStore.save() + openInitialEmptyEditorIfNecessary: -> return unless @config.get('core.openEmptyEditorOnStart') if @getLoadSettings().initialPaths?.length is 0 and @workspace.getPaneItems().length is 0 @@ -873,11 +877,6 @@ class AtomEnvironment extends Model showSaveDialogSync: (options={}) -> @applicationDelegate.showSaveDialog(options) - saveBlobStoreSync: -> - return unless @enablePersistence - - @blobStore.save() - saveState: (options) -> new Promise (resolve, reject) => if @enablePersistence and @project diff --git a/src/compile-cache.js b/src/compile-cache.js index 8a4451d90..9b1966fc8 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -7,6 +7,7 @@ var path = require('path') var fs = require('fs-plus') +var sourceMapSupport = require('source-map-support') var PackageTranspilationRegistry = require('./package-transpilation-registry') var CSON = null @@ -113,109 +114,112 @@ function writeCachedJavascript (relativeCachePath, code) { var INLINE_SOURCE_MAP_REGEXP = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/mg -require('source-map-support').install({ - handleUncaughtExceptions: false, +exports.install = function (nodeRequire) { + sourceMapSupport.install({ + handleUncaughtExceptions: false, - // Most of this logic is the same as the default implementation in the - // source-map-support module, but we've overridden it to read the javascript - // code from our cache directory. - retrieveSourceMap: function (filePath) { - if (!cacheDirectory || !fs.isFileSync(filePath)) { - return null - } + // Most of this logic is the same as the default implementation in the + // source-map-support module, but we've overridden it to read the javascript + // code from our cache directory. + retrieveSourceMap: function (filePath) { + if (!cacheDirectory || !fs.isFileSync(filePath)) { + return null + } - try { - var sourceCode = fs.readFileSync(filePath, 'utf8') - } catch (error) { - console.warn('Error reading source file', error.stack) - return null - } + try { + var sourceCode = fs.readFileSync(filePath, 'utf8') + } catch (error) { + console.warn('Error reading source file', error.stack) + return null + } - var compiler = COMPILERS[path.extname(filePath)] - if (!compiler) compiler = COMPILERS['.js'] + var compiler = COMPILERS[path.extname(filePath)] + if (!compiler) compiler = COMPILERS['.js'] - try { - var fileData = readCachedJavascript(compiler.getCachePath(sourceCode, filePath)) - } catch (error) { - console.warn('Error reading compiled file', error.stack) - return null - } + try { + var fileData = readCachedJavascript(compiler.getCachePath(sourceCode, filePath)) + } catch (error) { + console.warn('Error reading compiled file', error.stack) + return null + } - if (fileData == null) { - return null - } + if (fileData == null) { + return null + } - var match, lastMatch - INLINE_SOURCE_MAP_REGEXP.lastIndex = 0 - while ((match = INLINE_SOURCE_MAP_REGEXP.exec(fileData))) { - lastMatch = match - } - if (lastMatch == null) { - return null - } + var match, lastMatch + INLINE_SOURCE_MAP_REGEXP.lastIndex = 0 + while ((match = INLINE_SOURCE_MAP_REGEXP.exec(fileData))) { + lastMatch = match + } + if (lastMatch == null) { + return null + } - var sourceMappingURL = lastMatch[1] - var rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(',') + 1) + var sourceMappingURL = lastMatch[1] + var rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(',') + 1) - try { - var sourceMap = JSON.parse(new Buffer(rawData, 'base64')) - } catch (error) { - console.warn('Error parsing source map', error.stack) - return null - } + try { + var sourceMap = JSON.parse(new Buffer(rawData, 'base64')) + } catch (error) { + console.warn('Error parsing source map', error.stack) + return null + } - return { - map: sourceMap, - url: null - } - } -}) - -var prepareStackTraceWithSourceMapping = Error.prepareStackTrace -var prepareStackTrace = prepareStackTraceWithSourceMapping - -function prepareStackTraceWithRawStackAssignment (error, frames) { - if (error.rawStack) { // avoid infinite recursion - return prepareStackTraceWithSourceMapping(error, frames) - } else { - error.rawStack = frames - return prepareStackTrace(error, frames) - } -} - -Error.stackTraceLimit = 30 - -Object.defineProperty(Error, 'prepareStackTrace', { - get: function () { - return prepareStackTraceWithRawStackAssignment - }, - - set: function (newValue) { - prepareStackTrace = newValue - process.nextTick(function () { - prepareStackTrace = prepareStackTraceWithSourceMapping - }) - } -}) - -Error.prototype.getRawStack = function () { // eslint-disable-line no-extend-native - // Access this.stack to ensure prepareStackTrace has been run on this error - // because it assigns this.rawStack as a side-effect - this.stack - return this.rawStack -} - -Object.keys(COMPILERS).forEach(function (extension) { - var compiler = COMPILERS[extension] - - Object.defineProperty(require.extensions, extension, { - enumerable: true, - writable: false, - value: function (module, filePath) { - var code = compileFileAtPath(compiler, filePath, extension) - return module._compile(code, filePath) + return { + map: sourceMap, + url: null + } } }) -}) + var prepareStackTraceWithSourceMapping = Error.prepareStackTrace + var prepareStackTrace = prepareStackTraceWithSourceMapping + + function prepareStackTraceWithRawStackAssignment (error, frames) { + if (error.rawStack) { // avoid infinite recursion + return prepareStackTraceWithSourceMapping(error, frames) + } else { + error.rawStack = frames + return prepareStackTrace(error, frames) + } + } + + Error.stackTraceLimit = 30 + + Object.defineProperty(Error, 'prepareStackTrace', { + get: function () { + return prepareStackTraceWithRawStackAssignment + }, + + set: function (newValue) { + prepareStackTrace = newValue + process.nextTick(function () { + prepareStackTrace = prepareStackTraceWithSourceMapping + }) + } + }) + + Error.prototype.getRawStack = function () { // eslint-disable-line no-extend-native + // Access this.stack to ensure prepareStackTrace has been run on this error + // because it assigns this.rawStack as a side-effect + this.stack + return this.rawStack + } + + Object.keys(COMPILERS).forEach(function (extension) { + var compiler = COMPILERS[extension] + + Object.defineProperty(nodeRequire.extensions, extension, { + enumerable: true, + writable: false, + value: function (module, filePath) { + var code = compileFileAtPath(compiler, filePath, extension) + return module._compile(code, filePath) + } + }) + }) +} + +exports.supportedExtensions = Object.keys(COMPILERS) exports.resetCacheStats() diff --git a/src/file-system-blob-store.js b/src/file-system-blob-store.js index 7bbbdcb14..67a959735 100644 --- a/src/file-system-blob-store.js +++ b/src/file-system-blob-store.js @@ -14,16 +14,15 @@ class FileSystemBlobStore { constructor (directory) { this.blobFilename = path.join(directory, 'BLOB') this.blobMapFilename = path.join(directory, 'MAP') - this.invalidationKeysFilename = path.join(directory, 'INVKEYS') this.lockFilename = path.join(directory, 'LOCK') this.reset() } reset () { this.inMemoryBlobs = new Map() - this.invalidationKeys = {} this.storedBlob = new Buffer(0) this.storedBlobMap = {} + this.usedKeys = new Set() } load () { @@ -33,14 +32,10 @@ class FileSystemBlobStore { if (!fs.existsSync(this.blobFilename)) { return } - if (!fs.existsSync(this.invalidationKeysFilename)) { - return - } try { this.storedBlob = fs.readFileSync(this.blobFilename) this.storedBlobMap = JSON.parse(fs.readFileSync(this.blobMapFilename)) - this.invalidationKeys = JSON.parse(fs.readFileSync(this.invalidationKeysFilename)) } catch (e) { this.reset() } @@ -50,7 +45,6 @@ class FileSystemBlobStore { let dump = this.getDump() let blobToStore = Buffer.concat(dump[0]) let mapToStore = JSON.stringify(dump[1]) - let invalidationKeysToStore = JSON.stringify(this.invalidationKeys) let acquiredLock = false try { @@ -59,7 +53,6 @@ class FileSystemBlobStore { fs.writeFileSync(this.blobFilename, blobToStore) fs.writeFileSync(this.blobMapFilename, mapToStore) - fs.writeFileSync(this.invalidationKeysFilename, invalidationKeysToStore) } catch (error) { // Swallow the exception silently only if we fail to acquire the lock. if (error.code !== 'EEXIST') { @@ -72,20 +65,19 @@ class FileSystemBlobStore { } } - has (key, invalidationKey) { - let containsKey = this.inMemoryBlobs.has(key) || this.storedBlobMap.hasOwnProperty(key) - let isValid = this.invalidationKeys[key] === invalidationKey - return containsKey && isValid + has (key) { + return this.inMemoryBlobs.has(key) || this.storedBlobMap.hasOwnProperty(key) } - get (key, invalidationKey) { - if (this.has(key, invalidationKey)) { + get (key) { + if (this.has(key)) { + this.usedKeys.add(key) return this.getFromMemory(key) || this.getFromStorage(key) } } - set (key, invalidationKey, buffer) { - this.invalidationKeys[key] = invalidationKey + set (key, buffer) { + this.usedKeys.add(key) return this.inMemoryBlobs.set(key, buffer) } @@ -119,11 +111,13 @@ class FileSystemBlobStore { } for (let key of this.inMemoryBlobs.keys()) { - dump(key, this.getFromMemory.bind(this)) + if (this.usedKeys.has(key)) { + dump(key, this.getFromMemory.bind(this)) + } } for (let key of Object.keys(this.storedBlobMap)) { - if (!blobMap[key]) { + if (!blobMap[key] && this.usedKeys.has(key)) { dump(key, this.getFromStorage.bind(this)) } } diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee index be13ce6c6..f0ea0ed12 100644 --- a/src/initialize-application-window.coffee +++ b/src/initialize-application-window.coffee @@ -1,3 +1,60 @@ +AtomEnvironment = require './atom-environment' +ApplicationDelegate = require './application-delegate' +Clipboard = require './clipboard' +TextEditor = require './text-editor' +TextEditorComponent = require './text-editor-component' +FileSystemBlobStore = require './file-system-blob-store' +NativeCompileCache = require './native-compile-cache' +CompileCache = require './compile-cache' +ModuleCache = require './module-cache' + +require('about') +require('archive-view') +require('autocomplete-atom-api') +require('autocomplete-css') +require('autocomplete-html') +require('autocomplete-plus') +require('autocomplete-snippets') +require('autoflow') +require('autosave') +require('background-tips') +require('bookmarks') +require('bracket-matcher') +require('command-palette') +require('deprecation-cop') +require('dev-live-reload') +require('encoding-selector') +require('exception-reporting') +require('dalek') +require('find-and-replace') +require('fuzzy-finder') +require('git-diff') +require('go-to-line') +require('grammar-selector') +require('image-view') +require('incompatible-packages') +require('keybinding-resolver') +require('line-ending-selector') +require('link') +require('markdown-preview') +require('metrics') +require('notifications') +require('open-on-github') +require('package-generator') +require('settings-view') +require('snippets') +require('spell-check') +require('status-bar') +require('styleguide') +require('symbols-view') +require('tabs') +require('timecop') +require('tree-view') +require('update-package-dependencies') +require('welcome') +require('whitespace') +require('wrap-guide') + # Like sands through the hourglass, so are the days of our lives. module.exports = ({blobStore}) -> {updateProcessEnv} = require('./update-process-env') @@ -16,11 +73,6 @@ module.exports = ({blobStore}) -> # Make React faster process.env.NODE_ENV ?= 'production' unless devMode - AtomEnvironment = require './atom-environment' - ApplicationDelegate = require './application-delegate' - Clipboard = require './clipboard' - TextEditor = require './text-editor' - clipboard = new Clipboard TextEditor.setClipboard(clipboard) @@ -32,7 +84,7 @@ module.exports = ({blobStore}) -> env: process.env }) - atom.startEditorWindow().then -> + window.atom.startEditorWindow().then -> # Workaround for focus getting cleared upon window creation windowFocused = -> window.removeEventListener('focus', windowFocused) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 6c9271179..02d396021 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -2,24 +2,24 @@ CursorsComponent = require './cursors-component' LinesTileComponent = require './lines-tile-component' TiledComponent = require './tiled-component' -DummyLineNode = document.createElement('div') -DummyLineNode.className = 'line' -DummyLineNode.style.position = 'absolute' -DummyLineNode.style.visibility = 'hidden' -DummyLineNode.appendChild(document.createElement('span')) -DummyLineNode.appendChild(document.createElement('span')) -DummyLineNode.appendChild(document.createElement('span')) -DummyLineNode.appendChild(document.createElement('span')) -DummyLineNode.children[0].textContent = 'x' -DummyLineNode.children[1].textContent = '我' -DummyLineNode.children[2].textContent = 'ハ' -DummyLineNode.children[3].textContent = '세' - module.exports = class LinesComponent extends TiledComponent placeholderTextDiv: null constructor: ({@views, @presenter, @domElementPool, @assert}) -> + @DummyLineNode = document.createElement('div') + @DummyLineNode.className = 'line' + @DummyLineNode.style.position = 'absolute' + @DummyLineNode.style.visibility = 'hidden' + @DummyLineNode.appendChild(document.createElement('span')) + @DummyLineNode.appendChild(document.createElement('span')) + @DummyLineNode.appendChild(document.createElement('span')) + @DummyLineNode.appendChild(document.createElement('span')) + @DummyLineNode.children[0].textContent = 'x' + @DummyLineNode.children[1].textContent = '我' + @DummyLineNode.children[2].textContent = 'ハ' + @DummyLineNode.children[3].textContent = '세' + @domNode = document.createElement('div') @domNode.classList.add('lines') @tilesNode = document.createElement("div") @@ -78,15 +78,15 @@ class LinesComponent extends TiledComponent getTilesNode: -> @tilesNode measureLineHeightAndDefaultCharWidth: -> - @domNode.appendChild(DummyLineNode) + @domNode.appendChild(@DummyLineNode) - lineHeightInPixels = DummyLineNode.getBoundingClientRect().height - defaultCharWidth = DummyLineNode.children[0].getBoundingClientRect().width - doubleWidthCharWidth = DummyLineNode.children[1].getBoundingClientRect().width - halfWidthCharWidth = DummyLineNode.children[2].getBoundingClientRect().width - koreanCharWidth = DummyLineNode.children[3].getBoundingClientRect().width + lineHeightInPixels = @DummyLineNode.getBoundingClientRect().height + defaultCharWidth = @DummyLineNode.children[0].getBoundingClientRect().width + doubleWidthCharWidth = @DummyLineNode.children[1].getBoundingClientRect().width + halfWidthCharWidth = @DummyLineNode.children[2].getBoundingClientRect().width + koreanCharWidth = @DummyLineNode.children[3].getBoundingClientRect().width - @domNode.removeChild(DummyLineNode) + @domNode.removeChild(@DummyLineNode) @presenter.setLineHeight(lineHeightInPixels) @presenter.setBaseCharacterWidth(defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth) diff --git a/src/main-process/main.js b/src/main-process/main.js index 7ccd1a6c3..d63de0677 100644 --- a/src/main-process/main.js +++ b/src/main-process/main.js @@ -1,3 +1,7 @@ +if (typeof snapshotResult !== 'undefined') { + snapshotResult.setGlobals(global, process, global, {}, require) // eslint-disable-line no-undef +} + const startTime = Date.now() const electron = require('electron') diff --git a/src/main-process/start.js b/src/main-process/start.js index f54d263e0..368370939 100644 --- a/src/main-process/start.js +++ b/src/main-process/start.js @@ -83,4 +83,5 @@ function handleStartupEventWithSquirrel () { function setupCompileCache () { const CompileCache = require('../compile-cache') CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) + CompileCache.install(require) } diff --git a/src/module-cache.coffee b/src/module-cache.coffee index 8c6a7c312..5bc162ab1 100644 --- a/src/module-cache.coffee +++ b/src/module-cache.coffee @@ -20,7 +20,7 @@ class Range extends semver.Range @unmatchedVersions.add(version) matches -nativeModules = process.binding('natives') +nativeModules = null cache = builtins: {} @@ -171,6 +171,7 @@ resolveModulePath = (relativePath, parentModule) -> return unless relativePath return unless parentModule?.filename + nativeModules ?= process.binding('natives') return if nativeModules.hasOwnProperty(relativePath) return if relativePath[0] is '.' return if isAbsolute(relativePath) @@ -212,35 +213,6 @@ registerBuiltins = (devMode) -> for builtin in rendererBuiltins cache.builtins[builtin] = path.join(rendererRoot, "#{builtin}.js") -if cache.debug - cache.findPathCount = 0 - cache.findPathTime = 0 - cache.loadCount = 0 - cache.requireTime = 0 - global.moduleCache = cache - - originalLoad = Module::load - Module::load = -> - cache.loadCount++ - originalLoad.apply(this, arguments) - - originalRequire = Module::require - Module::require = -> - startTime = Date.now() - exports = originalRequire.apply(this, arguments) - cache.requireTime += Date.now() - startTime - exports - - originalFindPath = Module._findPath - Module._findPath = (request, paths) -> - cacheKey = JSON.stringify({request, paths}) - cache.findPathCount++ unless Module._pathCache[cacheKey] - - startTime = Date.now() - foundPath = originalFindPath.apply(global, arguments) - cache.findPathTime += Date.now() - startTime - foundPath - exports.create = (modulePath) -> fs = require 'fs-plus' diff --git a/src/native-compile-cache.js b/src/native-compile-cache.js index e4e7fc146..09a62b186 100644 --- a/src/native-compile-cache.js +++ b/src/native-compile-cache.js @@ -1,5 +1,3 @@ -'use strict' - const Module = require('module') const path = require('path') const cachedVm = require('cached-run-in-this-context') @@ -38,7 +36,6 @@ class NativeCompileCache { overrideModuleCompile () { let self = this - let resolvedArgv = null // Here we override Node's module.js // (https://github.com/atom/node/blob/atom/lib/module.js#L378), changing // only the bits that affect compilation in order to use the cached one. @@ -63,11 +60,10 @@ class NativeCompileCache { // create wrapper function let wrapper = Module.wrap(content) - let cacheKey = filename - let invalidationKey = computeHash(wrapper + self.v8Version) + let cacheKey = computeHash(wrapper + self.v8Version) let compiledWrapper = null - if (self.cacheStore.has(cacheKey, invalidationKey)) { - let buffer = self.cacheStore.get(cacheKey, invalidationKey) + if (self.cacheStore.has(cacheKey)) { + let buffer = self.cacheStore.get(cacheKey) let compilationResult = cachedVm.runInThisContextCached(wrapper, filename, buffer) compiledWrapper = compilationResult.result if (compilationResult.wasRejected) { @@ -82,29 +78,11 @@ class NativeCompileCache { throw err } if (compilationResult.cacheBuffer) { - self.cacheStore.set(cacheKey, invalidationKey, compilationResult.cacheBuffer) + self.cacheStore.set(cacheKey, compilationResult.cacheBuffer) } compiledWrapper = compilationResult.result } - if (global.v8debug) { - if (!resolvedArgv) { - // we enter the repl if we're not given a filename argument. - if (process.argv[1]) { - resolvedArgv = Module._resolveFilename(process.argv[1], null) - } else { - resolvedArgv = 'repl' - } - } - // Set breakpoint on module start - if (filename === resolvedArgv) { - // Installing this dummy debug event listener tells V8 to start - // the debugger. Without it, the setBreakPoint() fails with an - // 'illegal access' error. - global.v8debug.Debug.setListener(function () {}) - global.v8debug.Debug.setBreakPoint(compiledWrapper, 0, 0) - } - } let args = [moduleSelf.exports, require, moduleSelf, filename, dirname, process, global] return compiledWrapper.apply(moduleSelf.exports, args) } diff --git a/src/package.coffee b/src/package.coffee index 63efbf02c..ed0f7aa87 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -42,7 +42,8 @@ class Package @metadata ?= @packageManager.loadPackageMetadata(@path) @bundledPackage = @packageManager.isBundledPackagePath(@path) @name = @metadata?.name ? path.basename(@path) - ModuleCache.add(@path, @metadata) + unless @bundledPackage + ModuleCache.add(@path, @metadata) @reset() ### @@ -502,7 +503,7 @@ class Package path.join(@path, @metadata.main) else path.join(@path, 'index') - @mainModulePath = fs.resolveExtension(mainModulePath, ["", _.keys(require.extensions)...]) + @mainModulePath = fs.resolveExtension(mainModulePath, ["", CompileCache.supportedExtensions...]) activationShouldBeDeferred: -> @hasActivationCommands() or @hasActivationHooks() diff --git a/src/task.coffee b/src/task.coffee index fc8c5bd6b..5c7b08b8a 100644 --- a/src/task.coffee +++ b/src/task.coffee @@ -70,7 +70,13 @@ class Task compileCachePath = require('./compile-cache').getCacheDirectory() taskBootstrapRequire = "require('#{require.resolve('./task-bootstrap')}');" bootstrap = """ - #{compileCacheRequire}.setCacheDirectory('#{compileCachePath}'); + if (typeof snapshotResult !== 'undefined') { + snapshotResult.setGlobals(global, process, global, {}, require) + } + + CompileCache = #{compileCacheRequire} + CompileCache.setCacheDirectory('#{compileCachePath}'); + CompileCache.install(require) #{taskBootstrapRequire} """ bootstrap = bootstrap.replace(/\\/g, "\\\\") diff --git a/src/text-editor-registry.js b/src/text-editor-registry.js index 30600ff08..3343ce89c 100644 --- a/src/text-editor-registry.js +++ b/src/text-editor-registry.js @@ -1,7 +1,7 @@ /** @babel */ import {Emitter, Disposable, CompositeDisposable} from 'event-kit' -import {Point, Range} from 'atom' +import {Point, Range} from 'text-buffer' import TextEditor from './text-editor' import ScopeDescriptor from './scope-descriptor' diff --git a/static/index.js b/static/index.js index aa57a594a..6297eb5ef 100644 --- a/static/index.js +++ b/static/index.js @@ -1,35 +1,66 @@ (function () { - var path = require('path') - var FileSystemBlobStore = require('../src/file-system-blob-store') - var NativeCompileCache = require('../src/native-compile-cache') - var getWindowLoadSettings = require('../src/get-window-load-settings') + // Eagerly require cached-run-in-this-context to prevent a circular require + // when using `NativeCompileCache` for the first time. + require('cached-run-in-this-context') - var blobStore = null + const electron = require('electron') + const path = require('path') + const Module = require('module') + const getWindowLoadSettings = require('../src/get-window-load-settings') + const entryPointDirPath = __dirname + let blobStore = null + let useSnapshot = false window.onload = function () { try { - var startTime = Date.now() + const startTime = Date.now() process.on('unhandledRejection', function (error, promise) { console.error('Unhandled promise rejection %o with error: %o', promise, error) }) - blobStore = FileSystemBlobStore.load( - path.join(process.env.ATOM_HOME, 'blob-store/') - ) - NativeCompileCache.setCacheStore(blobStore) - NativeCompileCache.setV8Version(process.versions.v8) - NativeCompileCache.install() - // Normalize to make sure drive letter case is consistent on Windows process.resourcesPath = path.normalize(process.resourcesPath) - var devMode = getWindowLoadSettings().devMode || !getWindowLoadSettings().resourcePath.startsWith(process.resourcesPath + path.sep) + setupAtomHome() + const devMode = getWindowLoadSettings().devMode || !getWindowLoadSettings().resourcePath.startsWith(process.resourcesPath + path.sep) + useSnapshot = !devMode && typeof snapshotResult !== 'undefined' if (devMode) { - setupDeprecatedPackages() + const metadata = require('../package.json') + if (!metadata._deprecatedPackages) { + try { + metadata._deprecatedPackages = require('../script/deprecated-packages.json') + } catch (requireError) { + console.error('Failed to setup deprecated packages list', requireError.stack) + } + } + } else if (useSnapshot) { + Module.prototype.require = function (module) { + const absoluteFilePath = Module._resolveFilename(module, this, false) + let relativeFilePath = path.relative(entryPointDirPath, absoluteFilePath) + if (process.platform === 'win32') { + relativeFilePath = relativeFilePath.replace(/\\/g, '/') + } + let cachedModule = snapshotResult.customRequire.cache[relativeFilePath] // eslint-disable-line no-undef + if (!cachedModule) { + cachedModule = {exports: Module._load(module, this, false)} + snapshotResult.customRequire.cache[relativeFilePath] = cachedModule // eslint-disable-line no-undef + } + return cachedModule.exports + } + + snapshotResult.setGlobals(global, process, window, document, require) // eslint-disable-line no-undef } + const FileSystemBlobStore = useSnapshot ? snapshotResult.customRequire('../src/file-system-blob-store.js') : require('../src/file-system-blob-store') // eslint-disable-line no-undef + blobStore = FileSystemBlobStore.load(path.join(process.env.ATOM_HOME, 'blob-store')) + + const NativeCompileCache = useSnapshot ? snapshotResult.customRequire('../src/native-compile-cache.js') : require('../src/native-compile-cache') // eslint-disable-line no-undef + NativeCompileCache.setCacheStore(blobStore) + NativeCompileCache.setV8Version(process.versions.v8) + NativeCompileCache.install() + if (getWindowLoadSettings().profileStartup) { profileStartup(Date.now() - startTime) } else { @@ -48,7 +79,7 @@ } function handleSetupError (error) { - var currentWindow = require('electron').remote.getCurrentWindow() + const currentWindow = electron.remote.getCurrentWindow() currentWindow.setSize(800, 600) currentWindow.center() currentWindow.show() @@ -57,53 +88,30 @@ } function setupWindow () { - var CompileCache = require('../src/compile-cache') + const CompileCache = useSnapshot ? snapshotResult.customRequire('../src/compile-cache.js') : require('../src/compile-cache') // eslint-disable-line no-undef CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) + CompileCache.install(require) - var ModuleCache = require('../src/module-cache') + const ModuleCache = useSnapshot ? snapshotResult.customRequire('../src/module-cache.js') : require('../src/module-cache') // eslint-disable-line no-undef ModuleCache.register(getWindowLoadSettings()) - ModuleCache.add(getWindowLoadSettings().resourcePath) - // By explicitly passing the app version here, we could save the call - // of "require('remote').require('app').getVersion()". - var startCrashReporter = require('../src/crash-reporter-start') + const startCrashReporter = useSnapshot ? snapshotResult.customRequire('../src/crash-reporter-start.js') : require('../src/crash-reporter-start') // eslint-disable-line no-undef startCrashReporter({_version: getWindowLoadSettings().appVersion}) - setupVmCompatibility() - setupCsonCache(CompileCache.getCacheDirectory()) + const CSON = useSnapshot ? snapshotResult.customRequire('../node_modules/season/lib/cson.js') : require('season') // eslint-disable-line no-undef + CSON.setCacheDir(path.join(CompileCache.getCacheDirectory(), 'cson')) - var initialize = require(getWindowLoadSettings().windowInitializationScript) + const initScriptPath = path.relative(entryPointDirPath, getWindowLoadSettings().windowInitializationScript) + const initialize = useSnapshot ? snapshotResult.customRequire(initScriptPath) : require(initScriptPath) // eslint-disable-line no-undef return initialize({blobStore: blobStore}).then(function () { - require('electron').ipcRenderer.send('window-command', 'window:loaded') + electron.ipcRenderer.send('window-command', 'window:loaded') }) } - function setupCsonCache (cacheDir) { - require('season').setCacheDir(path.join(cacheDir, 'cson')) - } - - function setupVmCompatibility () { - var vm = require('vm') - if (!vm.Script.createContext) { - vm.Script.createContext = vm.createContext - } - } - - function setupDeprecatedPackages () { - var metadata = require('../package.json') - if (!metadata._deprecatedPackages) { - try { - metadata._deprecatedPackages = require('../script/deprecated-packages.json') - } catch (requireError) { - console.error('Failed to setup deprecated packages list', requireError.stack) - } - } - } - function profileStartup (initialTime) { function profile () { console.profile('startup') - var startTime = Date.now() + const startTime = Date.now() setupWindow().then(function () { setLoadTime(Date.now() - startTime + initialTime) console.profileEnd('startup') @@ -111,7 +119,7 @@ }) } - const webContents = require('electron').remote.getCurrentWindow().webContents + const webContents = electron.remote.getCurrentWindow().webContents if (webContents.devToolsWebContents) { profile() } else { @@ -120,7 +128,7 @@ } } - var setupAtomHome = function () { + function setupAtomHome () { if (process.env.ATOM_HOME) { return } @@ -132,6 +140,4 @@ process.env.ATOM_HOME = getWindowLoadSettings().atomHome } } - - setupAtomHome() })()