/ src / native-compile-cache.js
native-compile-cache.js
  1  const Module = require('module');
  2  const path = require('path');
  3  const crypto = require('crypto');
  4  const vm = require('vm');
  5  
  6  function computeHash(contents) {
  7    return crypto
  8      .createHash('sha1')
  9      .update(contents, 'utf8')
 10      .digest('hex');
 11  }
 12  
 13  class NativeCompileCache {
 14    constructor() {
 15      this.cacheStore = null;
 16      this.previousModuleCompile = null;
 17    }
 18  
 19    setCacheStore(store) {
 20      this.cacheStore = store;
 21    }
 22  
 23    setV8Version(v8Version) {
 24      this.v8Version = v8Version.toString();
 25    }
 26  
 27    install() {
 28      this.savePreviousModuleCompile();
 29      this.overrideModuleCompile();
 30    }
 31  
 32    uninstall() {
 33      this.restorePreviousModuleCompile();
 34    }
 35  
 36    savePreviousModuleCompile() {
 37      this.previousModuleCompile = Module.prototype._compile;
 38    }
 39  
 40    runInThisContext(code, filename) {
 41      // TodoElectronIssue:  produceCachedData is deprecated after Node 10.6, so we'll
 42      // will need to update this for Electron v4 to use script.createCachedData().
 43      const script = new vm.Script(code, { filename, produceCachedData: true });
 44      return {
 45        result: script.runInThisContext(),
 46        cacheBuffer: script.cachedDataProduced ? script.cachedData : null
 47      };
 48    }
 49  
 50    runInThisContextCached(code, filename, cachedData) {
 51      const script = new vm.Script(code, { filename, cachedData });
 52      return {
 53        result: script.runInThisContext(),
 54        wasRejected: script.cachedDataRejected
 55      };
 56    }
 57  
 58    overrideModuleCompile() {
 59      let self = this;
 60      // Here we override Node's module.js
 61      // (https://github.com/atom/node/blob/atom/lib/module.js#L378), changing
 62      // only the bits that affect compilation in order to use the cached one.
 63      Module.prototype._compile = function(content, filename) {
 64        let moduleSelf = this;
 65        // remove shebang
 66        content = content.replace(/^#!.*/, '');
 67        function require(path) {
 68          return moduleSelf.require(path);
 69        }
 70        require.resolve = function(request) {
 71          return Module._resolveFilename(request, moduleSelf);
 72        };
 73        require.main = process.mainModule;
 74  
 75        // Enable support to add extra extension types
 76        require.extensions = Module._extensions;
 77        require.cache = Module._cache;
 78  
 79        let dirname = path.dirname(filename);
 80  
 81        // create wrapper function
 82        let wrapper = Module.wrap(content);
 83  
 84        let cacheKey = computeHash(wrapper + self.v8Version);
 85        let compiledWrapper = null;
 86        if (self.cacheStore.has(cacheKey)) {
 87          let buffer = self.cacheStore.get(cacheKey);
 88          let compilationResult = self.runInThisContextCached(
 89            wrapper,
 90            filename,
 91            buffer
 92          );
 93          compiledWrapper = compilationResult.result;
 94          if (compilationResult.wasRejected) {
 95            self.cacheStore.delete(cacheKey);
 96          }
 97        } else {
 98          let compilationResult;
 99          try {
100            compilationResult = self.runInThisContext(wrapper, filename);
101          } catch (err) {
102            console.error(`Error running script ${filename}`);
103            throw err;
104          }
105          if (compilationResult.cacheBuffer) {
106            self.cacheStore.set(cacheKey, compilationResult.cacheBuffer);
107          }
108          compiledWrapper = compilationResult.result;
109        }
110  
111        let args = [
112          moduleSelf.exports,
113          require,
114          moduleSelf,
115          filename,
116          dirname,
117          process,
118          global,
119          Buffer
120        ];
121        return compiledWrapper.apply(moduleSelf.exports, args);
122      };
123    }
124  
125    restorePreviousModuleCompile() {
126      Module.prototype._compile = this.previousModuleCompile;
127    }
128  }
129  
130  module.exports = new NativeCompileCache();