Atom/src/native-compile-cache.js

131 lines
3.5 KiB
JavaScript

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();