codekingpro/portable-devtools
114k
1'use strict';2 3const Module = require('module');4const crypto = require('crypto');5const fs = require('fs');6const path = require('path');7const vm = require('vm');8const os = require('os');9 10const hasOwnProperty = Object.prototype.hasOwnProperty;11 12//------------------------------------------------------------------------------13// FileSystemBlobStore14//------------------------------------------------------------------------------15 16class FileSystemBlobStore {17 constructor(directory, prefix) {18 const name = prefix ? slashEscape(prefix + '.') : '';19 this._blobFilename = path.join(directory, name + 'BLOB');20 this._mapFilename = path.join(directory, name + 'MAP');21 this._lockFilename = path.join(directory, name + 'LOCK');22 this._directory = directory;23 this._load();24 }25 26 has(key, invalidationKey) {27 if (hasOwnProperty.call(this._memoryBlobs, key)) {28 return this._invalidationKeys[key] === invalidationKey;29 } else if (hasOwnProperty.call(this._storedMap, key)) {30 return this._storedMap[key][0] === invalidationKey;31 }32 return false;33 }34 35 get(key, invalidationKey) {36 if (hasOwnProperty.call(this._memoryBlobs, key)) {37 if (this._invalidationKeys[key] === invalidationKey) {38 return this._memoryBlobs[key];39 }40 } else if (hasOwnProperty.call(this._storedMap, key)) {41 const mapping = this._storedMap[key];42 if (mapping[0] === invalidationKey) {43 return this._storedBlob.slice(mapping[1], mapping[2]);44 }45 }46 }47 48 set(key, invalidationKey, buffer) {49 this._invalidationKeys[key] = invalidationKey;50 this._memoryBlobs[key] = buffer;51 this._dirty = true;52 }53 54 delete(key) {55 if (hasOwnProperty.call(this._memoryBlobs, key)) {56 this._dirty = true;57 delete this._memoryBlobs[key];58 }59 if (hasOwnProperty.call(this._invalidationKeys, key)) {60 this._dirty = true;61 delete this._invalidationKeys[key];62 }63 if (hasOwnProperty.call(this._storedMap, key)) {64 this._dirty = true;65 delete this._storedMap[key];66 }67 }68 69 isDirty() {70 return this._dirty;71 }72 73 save() {74 const dump = this._getDump();75 const blobToStore = Buffer.concat(dump[0]);76 const mapToStore = JSON.stringify(dump[1]);77 78 try {79 mkdirpSync(this._directory);80 fs.writeFileSync(this._lockFilename, 'LOCK', {flag: 'wx'});81 } catch (error) {82 // Swallow the exception if we fail to acquire the lock.83 return false;84 }85 86 try {87 fs.writeFileSync(this._blobFilename, blobToStore);88 fs.writeFileSync(this._mapFilename, mapToStore);89 } catch (error) {90 throw error;91 } finally {92 fs.unlinkSync(this._lockFilename);93 }94 95 return true;96 }97 98 _load() {99 try {100 this._storedBlob = fs.readFileSync(this._blobFilename);101 this._storedMap = JSON.parse(fs.readFileSync(this._mapFilename));102 } catch (e) {103 this._storedBlob = Buffer.alloc(0);104 this._storedMap = {};105 }106 this._dirty = false;107 this._memoryBlobs = {};108 this._invalidationKeys = {};109 }110 111 _getDump() {112 const buffers = [];113 const newMap = {};114 let offset = 0;115 116 function push(key, invalidationKey, buffer) {117 buffers.push(buffer);118 newMap[key] = [invalidationKey, offset, offset + buffer.length];119 offset += buffer.length;120 }121 122 for (const key of Object.keys(this._memoryBlobs)) {123 const buffer = this._memoryBlobs[key];124 const invalidationKey = this._invalidationKeys[key];125 push(key, invalidationKey, buffer);126 }127 128 for (const key of Object.keys(this._storedMap)) {129 if (hasOwnProperty.call(newMap, key)) continue;130 const mapping = this._storedMap[key];131 const buffer = this._storedBlob.slice(mapping[1], mapping[2]);132 push(key, mapping[0], buffer);133 }134 135 return [buffers, newMap];136 }137}138 139//------------------------------------------------------------------------------140// NativeCompileCache141//------------------------------------------------------------------------------142 143class NativeCompileCache {144 constructor() {145 this._cacheStore = null;146 this._previousModuleCompile = null;147 }148 149 setCacheStore(cacheStore) {150 this._cacheStore = cacheStore;151 }152 153 install() {154 const self = this;155 this._previousModuleCompile = Module.prototype._compile;156 Module.prototype._compile = function(content, filename) {157 const mod = this;158 function require(id) {159 return mod.require(id);160 }161 require.resolve = function(request) {162 return Module._resolveFilename(request, mod);163 };164 require.main = process.mainModule;165 166 // Enable support to add extra extension types167 require.extensions = Module._extensions;168 require.cache = Module._cache;169 170 const dirname = path.dirname(filename);171 172 const compiledWrapper = self._moduleCompile(filename, content);173 174 // We skip the debugger setup because by the time we run, node has already175 // done that itself.176 177 const args = [mod.exports, require, mod, filename, dirname, process, global];178 return compiledWrapper.apply(mod.exports, args);179 };180 }181 182 uninstall() {183 Module.prototype._compile = this._previousModuleCompile;184 }185 186 _moduleCompile(filename, content) {187 // https://github.com/nodejs/node/blob/v7.5.0/lib/module.js#L511188 189 // Remove shebang190 var contLen = content.length;191 if (contLen >= 2) {192 if (content.charCodeAt(0) === 35/*#*/ &&193 content.charCodeAt(1) === 33/*!*/) {194 if (contLen === 2) {195 // Exact match196 content = '';197 } else {198 // Find end of shebang line and slice it off199 var i = 2;200 for (; i < contLen; ++i) {201 var code = content.charCodeAt(i);202 if (code === 10/*\n*/ || code === 13/*\r*/) break;203 }204 if (i === contLen) {205 content = '';206 } else {207 // Note that this actually includes the newline character(s) in the208 // new output. This duplicates the behavior of the regular209 // expression that was previously used to replace the shebang line210 content = content.slice(i);211 }212 }213 }214 }215 216 // create wrapper function217 var wrapper = Module.wrap(content);218 219 var invalidationKey = crypto220 .createHash('sha1')221 .update(content, 'utf8')222 .digest('hex');223 224 var buffer = this._cacheStore.get(filename, invalidationKey);225 226 var script = new vm.Script(wrapper, {227 filename: filename,228 lineOffset: 0,229 displayErrors: true,230 cachedData: buffer,231 produceCachedData: true,232 });233 234 if (script.cachedDataProduced) {235 this._cacheStore.set(filename, invalidationKey, script.cachedData);236 } else if (script.cachedDataRejected) {237 this._cacheStore.delete(filename);238 }239 240 var compiledWrapper = script.runInThisContext({241 filename: filename,242 lineOffset: 0,243 columnOffset: 0,244 displayErrors: true,245 });246 247 return compiledWrapper;248 }249}250 251//------------------------------------------------------------------------------252// utilities253//254// https://github.com/substack/node-mkdirp/blob/f2003bb/index.js#L55-L98255// https://github.com/zertosh/slash-escape/blob/e7ebb99/slash-escape.js256//------------------------------------------------------------------------------257 258function mkdirpSync(p_) {259 _mkdirpSync(path.resolve(p_), parseInt('0777', 8) & ~process.umask());260}261 262function _mkdirpSync(p, mode) {263 try {264 fs.mkdirSync(p, mode);265 } catch (err0) {266 if (err0.code === 'ENOENT') {267 _mkdirpSync(path.dirname(p));268 _mkdirpSync(p);269 } else {270 try {271 const stat = fs.statSync(p);272 if (!stat.isDirectory()) { throw err0; }273 } catch (err1) {274 throw err0;275 }276 }277 }278}279 280function slashEscape(str) {281 const ESCAPE_LOOKUP = {282 '\\': 'zB',283 ':': 'zC',284 '/': 'zS',285 '\x00': 'z0',286 'z': 'zZ',287 };288 return str.replace(/[\\:\/\x00z]/g, match => (ESCAPE_LOOKUP[match]));289}290 291function supportsCachedData() {292 const script = new vm.Script('""', {produceCachedData: true});293 // chakracore, as of v1.7.1.0, returns `false`.294 return script.cachedDataProduced === true;295}296 297function getCacheDir() {298 // Avoid cache ownership issues on POSIX systems.299 const dirname = typeof process.getuid === 'function'300 ? 'v8-compile-cache-' + process.getuid()301 : 'v8-compile-cache';302 const version = typeof process.versions.v8 === 'string'303 ? process.versions.v8304 : typeof process.versions.chakracore === 'string'305 ? 'chakracore-' + process.versions.chakracore306 : 'node-' + process.version;307 const cacheDir = path.join(os.tmpdir(), dirname, version);308 return cacheDir;309}310 311function getParentName() {312 // `module.parent.filename` is undefined or null when:313 // * node -e 'require("v8-compile-cache")'314 // * node -r 'v8-compile-cache'315 // * Or, requiring from the REPL.316 const parentName = module.parent && typeof module.parent.filename === 'string'317 ? module.parent.filename318 : process.cwd();319 return parentName;320}321 322//------------------------------------------------------------------------------323// main324//------------------------------------------------------------------------------325 326if (!process.env.DISABLE_V8_COMPILE_CACHE && supportsCachedData()) {327 const cacheDir = getCacheDir();328 const prefix = getParentName();329 const blobStore = new FileSystemBlobStore(cacheDir, prefix);330 331 const nativeCompileCache = new NativeCompileCache();332 nativeCompileCache.setCacheStore(blobStore);333 nativeCompileCache.install();334 335 process.once('exit', code => {336 if (blobStore.isDirty()) {337 blobStore.save();338 }339 nativeCompileCache.uninstall();340 });341}342 343module.exports.__TEST__ = {344 FileSystemBlobStore,345 NativeCompileCache,346 mkdirpSync,347 slashEscape,348 supportsCachedData,349 getCacheDir,350 getParentName,351};352 