const Module = require('module'); const path = require('path'); const crypto = require('crypto'); const vm = require('vm'); function computeHash(contents) { return crypto .createHash('sha1') .update(contents, 'utf8') .digest('hex'); } class NativeCompileCache { constructor() { this.cacheStore = null; this.previousModuleCompile = null; } setCacheStore(store) { this.cacheStore = store; } setV8Version(v8Version) { this.v8Version = v8Version.toString(); } install() { this.savePreviousModuleCompile(); this.overrideModuleCompile(); } uninstall() { this.restorePreviousModuleCompile(); } savePreviousModuleCompile() { this.previousModuleCompile = Module.prototype._compile; } runInThisContext(code, filename) { // TodoElectronIssue: produceCachedData is deprecated after Node 10.6, so we'll // will need to update this for Electron v4 to use script.createCachedData(). const script = new vm.Script(code, { filename, produceCachedData: true }); return { result: script.runInThisContext(), cacheBuffer: script.cachedDataProduced ? script.cachedData : null }; } runInThisContextCached(code, filename, cachedData) { const script = new vm.Script(code, { filename, cachedData }); return { result: script.runInThisContext(), wasRejected: script.cachedDataRejected }; } overrideModuleCompile() { let self = this; // 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. Module.prototype._compile = function(content, filename) { let moduleSelf = this; // remove shebang content = content.replace(/^#!.*/, ''); function require(path) { return moduleSelf.require(path); } require.resolve = function(request) { return Module._resolveFilename(request, moduleSelf); }; require.main = process.mainModule; // Enable support to add extra extension types require.extensions = Module._extensions; require.cache = Module._cache; let dirname = path.dirname(filename); // create wrapper function let wrapper = Module.wrap(content); let cacheKey = computeHash(wrapper + self.v8Version); let compiledWrapper = null; if (self.cacheStore.has(cacheKey)) { let buffer = self.cacheStore.get(cacheKey); let compilationResult = self.runInThisContextCached( wrapper, filename, buffer ); compiledWrapper = compilationResult.result; if (compilationResult.wasRejected) { self.cacheStore.delete(cacheKey); } } else { let compilationResult; try { compilationResult = self.runInThisContext(wrapper, filename); } catch (err) { console.error(`Error running script ${filename}`); throw err; } if (compilationResult.cacheBuffer) { self.cacheStore.set(cacheKey, compilationResult.cacheBuffer); } compiledWrapper = compilationResult.result; } let args = [ moduleSelf.exports, require, moduleSelf, filename, dirname, process, global, Buffer ]; return compiledWrapper.apply(moduleSelf.exports, args); }; } restorePreviousModuleCompile() { Module.prototype._compile = this.previousModuleCompile; } } module.exports = new NativeCompileCache();