pax_global_header00006660000000000000000000000064144665173300014523gustar00rootroot0000000000000052 comment=b6bc035d337fbda0e6e3ec7936499048fc9deafc v8-compile-cache-2.4.0/000077500000000000000000000000001446651733000145525ustar00rootroot00000000000000v8-compile-cache-2.4.0/.eslintignore000066400000000000000000000000161446651733000172520ustar00rootroot00000000000000test/fixtures v8-compile-cache-2.4.0/.eslintrc.json000066400000000000000000000002761446651733000173530ustar00rootroot00000000000000{ "root": true, "parserOptions": { "ecmaVersion": 6 }, "env": { "es6": true, "node": true }, "extends": "eslint:recommended", "rules": { "strict": "warn" } } v8-compile-cache-2.4.0/.gitignore000066400000000000000000000000301446651733000165330ustar00rootroot00000000000000.DS_Store /node_modules v8-compile-cache-2.4.0/.travis.yml000066400000000000000000000011221446651733000166570ustar00rootroot00000000000000language: node_js os: - linux node_js: - "4" - "5.6.0" - "5" # Introduced "cachedData"/"produceCachedData" after 5.7, need 5.10 for Buffer.from() - "6" - "8" - "10" - "12" - "14" - "15" before_install: - case "$TRAVIS_NODE_VERSION" in 5|5.6.0) npm install -g npm@4; ;; esac script: - case "$TRAVIS_NODE_VERSION" in 4|5.6.0) tap test/node-version-test.js; ;; *) npm run tap; ;; esac - case "$TRAVIS_NODE_VERSION" in 4|5.6.0|5|6|8) ;; *) npm run eslint; npm run bench; ;; esac v8-compile-cache-2.4.0/CHANGELOG.md000066400000000000000000000041611446651733000163650ustar00rootroot00000000000000# `v8-module-cache` Changelog ## Next ## 2023-08-14, Version 2.4.0 * Fix segmentation fault with Rosetta on Apple Silicon [#45](https://github.com/zertosh/v8-compile-cache/pull/45). ## 2021-03-05, Version 2.3.0 * Fix use require.main instead of module.parent [#34](https://github.com/zertosh/v8-compile-cache/pull/34). ## 2020-10-28, Version 2.2.0 * Added `V8_COMPILE_CACHE_CACHE_DIR` option [#23](https://github.com/zertosh/v8-compile-cache/pull/23). ## 2020-05-30, Version 2.1.1 * Stop using process.umask() [#28](https://github.com/zertosh/v8-compile-cache/pull/28). ## 2019-08-04, Version 2.1.0 * Fix Electron by calling the module wrapper with `Buffer` [#10](https://github.com/zertosh/v8-compile-cache/pull/10). ## 2019-05-10, Version 2.0.3 * Add `LICENSE` file [#19](https://github.com/zertosh/v8-compile-cache/pull/19). * Add "repository" to `package.json` (see [eea336e](https://github.com/zertosh/v8-compile-cache/commit/eea336eaa8360f9ded9342b8aa928e56ac6a7529)). * Support `require.resolve.paths` (added in Node v8.9.0) [#20](https://github.com/zertosh/v8-compile-cache/pull/20)/[#22](https://github.com/zertosh/v8-compile-cache/pull/22). ## 2018-08-06, Version 2.0.2 * Re-publish. ## 2018-08-06, Version 2.0.1 * Support `require.resolve` options (added in Node v8.9.0). ## 2018-04-30, Version 2.0.0 * Use `Buffer.alloc` instead of `new Buffer()`. * Drop support for Node 5.x. ## 2018-01-23, Version 1.1.2 * Instead of checking for `process.versions.v8`, check that `script.cachedDataProduced` is `true` (rather than `null`/`undefined`) for support to be considered existent. ## 2018-01-23, Version 1.1.1 * Check for the existence of `process.versions.v8` before attaching hook (see [f8b0388](https://github.com/zertosh/v8-compile-cache/commit/f8b038848be94bc2c905880dd50447c73393f364)). ## 2017-03-27, Version 1.1.0 * Safer cache directory creation (see [bcb3b12](https://github.com/zertosh/v8-compile-cache/commit/bcb3b12c819ab0927ec4408e70f612a6d50a9617)). - The cache is now suffixed with the user's uid on POSIX systems (i.e. `/path/to/tmp/v8-compile-cache-1234`). ## 2017-02-21, Version 1.0.0 * Initial release. v8-compile-cache-2.4.0/LICENSE000066400000000000000000000020701446651733000155560ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2019 Andres Suarez Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. v8-compile-cache-2.4.0/README.md000066400000000000000000000041601446651733000160320ustar00rootroot00000000000000# v8-compile-cache [![Build Status](https://travis-ci.org/zertosh/v8-compile-cache.svg?branch=master)](https://travis-ci.org/zertosh/v8-compile-cache) `v8-compile-cache` attaches a `require` hook to use [V8's code cache](https://v8project.blogspot.com/2015/07/code-caching.html) to speed up instantiation time. The "code cache" is the work of parsing and compiling done by V8. The ability to tap into V8 to produce/consume this cache was introduced in [Node v5.7.0](https://nodejs.org/en/blog/release/v5.7.0/). ## Usage 1. Add the dependency: ```sh $ npm install --save v8-compile-cache ``` 2. Then, in your entry module add: ```js require('v8-compile-cache'); ``` **Requiring `v8-compile-cache` in Node <5.7.0 is a noop – but you need at least Node 4.0.0 to support the ES2015 syntax used by `v8-compile-cache`.** ## Options Set the environment variable `DISABLE_V8_COMPILE_CACHE=1` to disable the cache. Cache directory is defined by environment variable `V8_COMPILE_CACHE_CACHE_DIR` or defaults to `/v8-compile-cache-`. ## Internals Cache files are suffixed `.BLOB` and `.MAP` corresponding to the entry module that required `v8-compile-cache`. The cache is _entry module specific_ because it is faster to load the entire code cache into memory at once, than it is to read it from disk on a file-by-file basis. ## Benchmarks See https://github.com/zertosh/v8-compile-cache/tree/master/bench. **Load Times:** | Module | Without Cache | With Cache | | ---------------- | -------------:| ----------:| | `babel-core` | `218ms` | `185ms` | | `yarn` | `153ms` | `113ms` | | `yarn` (bundled) | `228ms` | `105ms` | _^ Includes the overhead of loading the cache itself._ ## Acknowledgements * `FileSystemBlobStore` and `NativeCompileCache` are based on Atom's implementation of their v8 compile cache: - https://github.com/atom/atom/blob/b0d7a8a/src/file-system-blob-store.js - https://github.com/atom/atom/blob/b0d7a8a/src/native-compile-cache.js * `mkdirpSync` is based on: - https://github.com/substack/node-mkdirp/blob/f2003bb/index.js#L55-L98 v8-compile-cache-2.4.0/bench/000077500000000000000000000000001446651733000156315ustar00rootroot00000000000000v8-compile-cache-2.4.0/bench/_measure.js000066400000000000000000000012371446651733000177720ustar00rootroot00000000000000'use strict'; module.exports = (name, withCache, callback) => { let s; const logs = []; logs.push(`node: ${parseInt(process.uptime() * 1000, 10)}ms`); // So each test gets its own cache module.filename = require.main.filename; s = Date.now(); if (withCache) require('../v8-compile-cache'); logs.push(`require-cache: ${Date.now() - s}ms`); module.filename = __filename; s = Date.now(); callback(); logs.push(`${name}: ${Date.now() - s}ms`); s = Date.now(); process.on('exit', () => { logs.push(`exit: ${Date.now() - s}ms`); logs.push(`total: ${parseInt(process.uptime() * 1000, 10)}ms`); console.log(logs.join('\t')); }); }; v8-compile-cache-2.4.0/bench/require-babel-core.js000077500000000000000000000003271446651733000216410ustar00rootroot00000000000000#!/usr/bin/env node 'use strict'; const WITH_CACHE = true; require('./_measure.js')('require-babel-core', WITH_CACHE, () => { process.argv.push('config', 'get', 'init.author.name'); require('babel-core'); }); v8-compile-cache-2.4.0/bench/require-flow-parser.js000077500000000000000000000002371446651733000221070ustar00rootroot00000000000000#!/usr/bin/env node 'use strict'; const WITH_CACHE = true; require('./_measure.js')('require-flow-parser', WITH_CACHE, () => { require('flow-parser'); }); v8-compile-cache-2.4.0/bench/require-rxjs-bundle.js000077500000000000000000000002541446651733000221020ustar00rootroot00000000000000#!/usr/bin/env node 'use strict'; const WITH_CACHE = true; require('./_measure.js')('require-rxjs-bundle', WITH_CACHE, () => { require('rxjs/bundles/rxjs.umd.js'); }); v8-compile-cache-2.4.0/bench/require-rxjs-module.js000077500000000000000000000002301446651733000221100ustar00rootroot00000000000000#!/usr/bin/env node 'use strict'; const WITH_CACHE = true; require('./_measure.js')('require-rxjs-module', WITH_CACHE, () => { require('rxjs'); }); v8-compile-cache-2.4.0/bench/require-yarn.js000077500000000000000000000003261446651733000206160ustar00rootroot00000000000000#!/usr/bin/env node 'use strict'; const WITH_CACHE = true; require('./_measure.js')('require-yarn', WITH_CACHE, () => { process.argv.push('config', 'get', 'init.author.name'); require('yarn/lib/cli.js'); }); v8-compile-cache-2.4.0/bench/run.sh000077500000000000000000000007641446651733000170030ustar00rootroot00000000000000#!/bin/bash set -eo pipefail V8_COMPILE_CACHE_CACHE_DIR=$(mktemp -d) export V8_COMPILE_CACHE_CACHE_DIR=$V8_COMPILE_CACHE_CACHE_DIR trap 'rm -r "$V8_COMPILE_CACHE_CACHE_DIR"' EXIT THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" : "${NODE_BIN:=node}" # shellcheck disable=SC2016 "$NODE_BIN" -p '`node ${process.versions.node}, v8 ${process.versions.v8}`' for f in "$THIS_DIR"/require-*.js; do printf 'Running "%s"\n' "$(basename "$f")" for _ in {1..5}; do "$NODE_BIN" "$f"; done done v8-compile-cache-2.4.0/package.json000066400000000000000000000015001446651733000170340ustar00rootroot00000000000000{ "name": "v8-compile-cache", "version": "2.4.0", "description": "Require hook for automatic V8 compile cache persistence", "main": "v8-compile-cache.js", "scripts": { "bench": "bench/run.sh", "eslint": "eslint --max-warnings=0 .", "tap": "tap test/*-test.js", "test": "npm run tap", "posttest": "npm run eslint" }, "author": "Andres Suarez ", "repository": { "type": "git", "url": "https://github.com/zertosh/v8-compile-cache.git" }, "files": [ "v8-compile-cache.js" ], "license": "MIT", "dependencies": {}, "devDependencies": { "babel-core": "6.26.3", "eslint": "^7.12.1", "flow-parser": "0.136.0", "rimraf": "^2.5.4", "rxjs": "6.6.3", "semver": "^5.3.0", "tap": "^9.0.0", "temp": "^0.8.3", "yarn": "1.22.10" } } v8-compile-cache-2.4.0/test/000077500000000000000000000000001446651733000155315ustar00rootroot00000000000000v8-compile-cache-2.4.0/test/FileSystemBlobStore-mock.js000066400000000000000000000017001446651733000227140ustar00rootroot00000000000000'use strict'; module.exports = class FileSystemBlobStoreMock { constructor() { this._cachedFiles = []; } has(key, invalidationKey) { return !!this._cachedFiles.find( file => file.key === key && file.invalidationKey === invalidationKey ); } get(key, invalidationKey) { if (this.has(key, invalidationKey)) { return this._cachedFiles.find( file => file.key === key && file.invalidationKey === invalidationKey ).buffer; } } set(key, invalidationKey, buffer) { const entry = this._cachedFiles.find( file => file.key === key && file.invalidationKey === invalidationKey ); if (entry == null) { this._cachedFiles.push({key, invalidationKey, buffer}); } else { entry.buffer = buffer; } return buffer; } delete(key) { const i = this._cachedFiles.findIndex(file => file.key === key); if (i != null) { this._cachedFiles.splice(i, 1); } } }; v8-compile-cache-2.4.0/test/FileSystemBlobStore-test.js000066400000000000000000000160241446651733000227470ustar00rootroot00000000000000'use strict'; const fs = require('fs'); const path = require('path'); const rimraf = require('rimraf'); const tap = require('tap'); const temp = require('temp'); process.env.DISABLE_V8_COMPILE_CACHE = 1; const FileSystemBlobStore = require('..').__TEST__.FileSystemBlobStore; temp.track(); let storageDirectory; let blobStore; tap.beforeEach(cb => { storageDirectory = temp.path('filesystemblobstore'); blobStore = new FileSystemBlobStore(storageDirectory); cb(); }); tap.afterEach(cb => { rimraf.sync(storageDirectory); cb(); }); tap.test('is empty when the file doesn\'t exist', t => { t.equal(blobStore.isDirty(), false); t.type(blobStore.get('foo', 'invalidation-key-1'), 'undefined'); t.type(blobStore.get('bar', 'invalidation-key-2'), 'undefined'); t.end(); }); tap.test('allows to read and write buffers from/to memory without persisting them', t => { blobStore.set('foo', 'invalidation-key-1', Buffer.from('foo')); blobStore.set('bar', 'invalidation-key-2', Buffer.from('bar')); t.same(blobStore.get('foo', 'invalidation-key-1'), Buffer.from('foo')); t.same(blobStore.get('bar', 'invalidation-key-2'), Buffer.from('bar')); t.type(blobStore.get('foo', 'unexisting-key'), 'undefined'); t.type(blobStore.get('bar', 'unexisting-key'), 'undefined'); t.end(); }); tap.test('persists buffers when saved and retrieves them on load, giving priority to in-memory ones', t => { blobStore.set('foo', 'invalidation-key-1', Buffer.from('foo')); blobStore.set('bar', 'invalidation-key-2', Buffer.from('bar')); blobStore.save(); blobStore = new FileSystemBlobStore(storageDirectory); t.same(blobStore.get('foo', 'invalidation-key-1'), Buffer.from('foo')); t.same(blobStore.get('bar', 'invalidation-key-2'), Buffer.from('bar')); t.type(blobStore.get('foo', 'unexisting-key'), 'undefined'); t.type(blobStore.get('bar', 'unexisting-key'), 'undefined'); blobStore.set('foo', 'new-key', Buffer.from('changed')); t.same(blobStore.get('foo', 'new-key'), Buffer.from('changed')); t.type(blobStore.get('foo', 'invalidation-key-1'), 'undefined'); t.done(); }); tap.test('persists both in-memory and previously stored buffers when saved', t => { blobStore.set('foo', 'invalidation-key-1', Buffer.from('foo')); blobStore.set('bar', 'invalidation-key-2', Buffer.from('bar')); blobStore.save(); blobStore = new FileSystemBlobStore(storageDirectory); blobStore.set('bar', 'invalidation-key-3', Buffer.from('changed')); blobStore.set('qux', 'invalidation-key-4', Buffer.from('qux')); blobStore.save(); blobStore = new FileSystemBlobStore(storageDirectory); t.same(blobStore.get('foo', 'invalidation-key-1'), Buffer.from('foo')); t.same(blobStore.get('bar', 'invalidation-key-3'), Buffer.from('changed')); t.same(blobStore.get('qux', 'invalidation-key-4'), Buffer.from('qux')); t.type(blobStore.get('foo', 'unexisting-key'), 'undefined'); t.type(blobStore.get('bar', 'invalidation-key-2'), 'undefined'); t.type(blobStore.get('qux', 'unexisting-key'), 'undefined'); t.end(); }); tap.test('allows to delete keys from both memory and stored buffers', t => { blobStore.set('a', 'invalidation-key-1', Buffer.from('a')); blobStore.set('b', 'invalidation-key-2', Buffer.from('b')); blobStore.save(); blobStore = new FileSystemBlobStore(storageDirectory); blobStore.set('b', 'invalidation-key-3', Buffer.from('b')); blobStore.set('c', 'invalidation-key-4', Buffer.from('c')); blobStore.delete('b'); blobStore.delete('c'); blobStore.save(); blobStore = new FileSystemBlobStore(storageDirectory); t.same(blobStore.get('a', 'invalidation-key-1'), Buffer.from('a')); t.type(blobStore.get('b', 'invalidation-key-2'), 'undefined'); t.type(blobStore.get('b', 'invalidation-key-3'), 'undefined'); t.type(blobStore.get('c', 'invalidation-key-4'), 'undefined'); t.end(); }); tap.test('ignores errors when loading an invalid blob store', t => { blobStore.set('a', 'invalidation-key-1', Buffer.from('a')); blobStore.set('b', 'invalidation-key-2', Buffer.from('b')); blobStore.save(); // Simulate corruption fs.writeFileSync(path.join(storageDirectory, 'MAP'), Buffer.from([0])); fs.writeFileSync(path.join(storageDirectory, 'BLOB'), Buffer.from([0])); blobStore = new FileSystemBlobStore(storageDirectory); t.type(blobStore.get('a', 'invalidation-key-1'), 'undefined'); t.type(blobStore.get('b', 'invalidation-key-2'), 'undefined'); blobStore.set('a', 'invalidation-key-1', Buffer.from('x')); blobStore.set('b', 'invalidation-key-2', Buffer.from('y')); blobStore.save(); blobStore = new FileSystemBlobStore(storageDirectory); t.same(blobStore.get('a', 'invalidation-key-1'), Buffer.from('x')); t.same(blobStore.get('b', 'invalidation-key-2'), Buffer.from('y')); t.end(); }); tap.test('object hash collision', t => { t.type(blobStore.get('constructor', 'invalidation-key-1'), 'undefined'); blobStore.delete('constructor'); t.type(blobStore.get('constructor', 'invalidation-key-1'), 'undefined'); blobStore.set('constructor', 'invalidation-key-1', Buffer.from('proto')); t.same(blobStore.get('constructor', 'invalidation-key-1'), Buffer.from('proto')); blobStore.save(); blobStore = new FileSystemBlobStore(storageDirectory); t.same(blobStore.get('constructor', 'invalidation-key-1'), Buffer.from('proto')); t.type(blobStore.get('hasOwnProperty', 'invalidation-key-2'), 'undefined'); t.end(); }); tap.test('dirty state (set)', t => { blobStore.set('foo', 'invalidation-key-1', Buffer.from('foo')); t.equal(blobStore.isDirty(), true); blobStore.save(); blobStore = new FileSystemBlobStore(storageDirectory); t.equal(blobStore.isDirty(), false); blobStore.set('foo', 'invalidation-key-2', Buffer.from('bar')); t.equal(blobStore.isDirty(), true); t.end(); }); tap.test('dirty state (delete memory)', t => { blobStore.delete('foo'); t.equal(blobStore.isDirty(), false); blobStore.set('foo', 'invalidation-key-1', Buffer.from('foo')); blobStore.delete('foo'); t.equal(blobStore.isDirty(), true); blobStore.save(); blobStore = new FileSystemBlobStore(storageDirectory); t.equal(blobStore.isDirty(), false); blobStore.set('foo', 'invalidation-key-2', Buffer.from('bar')); t.equal(blobStore.isDirty(), true); t.end(); }); tap.test('dirty state (delete stored)', t => { blobStore.set('foo', 'invalidation-key-1', Buffer.from('foo')); blobStore.save(); blobStore = new FileSystemBlobStore(storageDirectory); blobStore.delete('foo'); t.equal(blobStore.isDirty(), true); t.end(); }); tap.test('prefix', t => { blobStore.set('foo', 'invalidation-key-1', Buffer.from('foo')); blobStore.save(); t.ok(fs.existsSync(path.join(storageDirectory, 'MAP'))); t.ok(fs.existsSync(path.join(storageDirectory, 'BLOB'))); storageDirectory = temp.path('filesystemblobstore'); blobStore = new FileSystemBlobStore(storageDirectory, 'prefix'); blobStore.set('foo', 'invalidation-key-1', Buffer.from('foo')); blobStore.save(); t.ok(fs.existsSync(path.join(storageDirectory, 'prefix.MAP'))); t.ok(fs.existsSync(path.join(storageDirectory, 'prefix.BLOB'))); t.end(); }); v8-compile-cache-2.4.0/test/NativeCompileCache-test.js000066400000000000000000000077051446651733000225400ustar00rootroot00000000000000'use strict'; const Module = require('module'); const fs = require('fs'); const path = require('path'); const tap = require('tap'); const temp = require('temp'); temp.track(); const FileSystemBlobStore_mock = require('./FileSystemBlobStore-mock'); process.env.DISABLE_V8_COMPILE_CACHE = 1; const NativeCompileCache = require('..').__TEST__.NativeCompileCache; let cachedFiles; let fakeCacheStore; let nativeCompileCache; tap.beforeEach(cb => { fakeCacheStore = new FileSystemBlobStore_mock(); cachedFiles = fakeCacheStore._cachedFiles; nativeCompileCache = new NativeCompileCache(); nativeCompileCache.setCacheStore(fakeCacheStore); nativeCompileCache.install(); cb(); }); tap.afterEach(cb => { nativeCompileCache.uninstall(); cb(); }); tap.test('writes and reads from the cache storage when requiring files', t => { let fn1 = require('./fixtures/file-1'); const fn2 = require('./fixtures/file-2'); t.equal(cachedFiles.length, 2); t.equal(cachedFiles[0].key, require.resolve('./fixtures/file-1')); t.type(cachedFiles[0].buffer, Uint8Array); t.ok(cachedFiles[0].buffer.length > 0); t.equal(fn1(), 1); t.equal(cachedFiles[1].key, require.resolve('./fixtures/file-2')); t.type(cachedFiles[1].buffer, Uint8Array); t.ok(cachedFiles[1].buffer.length > 0); t.equal(fn2(), 2); delete Module._cache[require.resolve('./fixtures/file-1')]; fn1 = require('./fixtures/file-1'); t.equal(cachedFiles.length, 2); t.equal(fn1(), 1); t.end(); }); tap.test('when the cache changes it updates the new cache', t => { let fn4 = require('./fixtures/file-4'); t.equal(cachedFiles.length, 1); t.equal(cachedFiles[0].key, require.resolve('./fixtures/file-4')); t.type(cachedFiles[0].buffer, Uint8Array); t.ok(cachedFiles[0].buffer.length > 0); t.equal(fn4(), 'file-4'); const fakeCacheStore2 = new FileSystemBlobStore_mock(); const cachedFiles2 = fakeCacheStore._cachedFiles; nativeCompileCache.setCacheStore(fakeCacheStore2); delete Module._cache[require.resolve('./fixtures/file-4')]; fn4 = require('./fixtures/file-4'); t.equal(cachedFiles.length, 1); t.equal(cachedFiles2.length, 1); t.equal(cachedFiles[0].key, require.resolve('./fixtures/file-4')); t.equal(cachedFiles2[0].key, require.resolve('./fixtures/file-4')); t.equal(cachedFiles[0].invalidationKey, cachedFiles2[0].invalidationKey); t.type(cachedFiles[0].buffer, Uint8Array); t.type(cachedFiles2[0].buffer, Uint8Array); t.ok(cachedFiles[0].buffer.length > 0); t.ok(cachedFiles2[0].buffer.length > 0); t.end(); }); tap.test('replaces previously cached code when the cache is an invalid file', t => { fakeCacheStore.has = () => true; fakeCacheStore.get = () => Buffer.from('an invalid cache'); // NOTE: When `script.cachedDataProduced` is true, `_cacheStore` is updated // using `set` instead of `delete`. let setWasCalledWith = null; fakeCacheStore.set = arg => { setWasCalledWith = arg; }; const fn3 = require('./fixtures/file-3'); t.equal(setWasCalledWith, require.resolve('./fixtures/file-3')); t.equal(fn3(), 3); t.end(); }); tap.test('when a previously required and cached file changes removes it from the store and re-inserts it with the new cache', t => { const tmpDir = temp.mkdirSync('native-compile-cache-test'); const tmpFile = path.join(tmpDir, 'file-5.js'); fs.writeFileSync(tmpFile, 'module.exports = () => `file-5`;'); let fn5 = require(tmpFile); t.equal(cachedFiles.length, 1); t.equal(cachedFiles[0].key, require.resolve(tmpFile)); t.type(cachedFiles[0].buffer, Uint8Array); t.ok(cachedFiles[0].buffer.length > 0); t.equal(fn5(), 'file-5'); delete Module._cache[require.resolve(tmpFile)]; fs.appendFileSync(tmpFile, '\n\n'); fn5 = require(tmpFile); t.equal(cachedFiles.length, 2); t.equal(cachedFiles[1].key, require.resolve(tmpFile)); t.notEqual(cachedFiles[1].invalidationKey, cachedFiles[0].invalidationKey); t.type(cachedFiles[1].buffer, Uint8Array); t.ok(cachedFiles[1].buffer.length > 0); t.end(); }); v8-compile-cache-2.4.0/test/arch/000077500000000000000000000000001446651733000164465ustar00rootroot00000000000000v8-compile-cache-2.4.0/test/arch/run.sh000077500000000000000000000006631446651733000176160ustar00rootroot00000000000000#!/bin/bash set -eo pipefail THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" : "${NODE_BIN:=node}" if [ "$(/usr/bin/arch)" != "arm64" ]; then echo "arch/run.sh: This must be run on Apple Silicon." >&2 exit fi # shellcheck disable=SC2016 "$NODE_BIN" "$THIS_DIR/yarn.js" config get init.author.name >/dev/null /usr/bin/arch -x86_64 "$NODE_BIN" "$THIS_DIR/yarn.js" config get init.author.name >/dev/null echo "Success!" v8-compile-cache-2.4.0/test/arch/yarn.js000066400000000000000000000001221446651733000177500ustar00rootroot00000000000000'use strict'; require ('../../v8-compile-cache.js'); require('yarn/lib/cli.js'); v8-compile-cache-2.4.0/test/fixtures/000077500000000000000000000000001446651733000174025ustar00rootroot00000000000000v8-compile-cache-2.4.0/test/fixtures/file-1.js000066400000000000000000000000531446651733000210130ustar00rootroot00000000000000module.exports = function () { return 1; } v8-compile-cache-2.4.0/test/fixtures/file-2.js000066400000000000000000000000531446651733000210140ustar00rootroot00000000000000module.exports = function () { return 2; } v8-compile-cache-2.4.0/test/fixtures/file-3.js000066400000000000000000000000531446651733000210150ustar00rootroot00000000000000module.exports = function () { return 3; } v8-compile-cache-2.4.0/test/fixtures/file-4.js000066400000000000000000000000611446651733000210150ustar00rootroot00000000000000module.exports = function () { return "file-4" } v8-compile-cache-2.4.0/test/fixtures/print-main-name.js000066400000000000000000000000661446651733000227360ustar00rootroot00000000000000console.log(require('../..').__TEST__.getMainName()); v8-compile-cache-2.4.0/test/fixtures/require-resolve-paths.js000066400000000000000000000003341446651733000242060ustar00rootroot00000000000000console.log( '%j', { hasRequireResolve: typeof require.resolve.paths === 'function', value: typeof require.resolve.paths === 'function' ? require.resolve.paths(process.argv[2]) : undefined } ); v8-compile-cache-2.4.0/test/getCacheDir-test.js000066400000000000000000000036641446651733000212170ustar00rootroot00000000000000'use strict'; const path = require('path'); const os = require('os'); const tap = require('tap'); const vm = require('vm'); process.env.DISABLE_V8_COMPILE_CACHE = 1; const getCacheDir = require('..').__TEST__.getCacheDir; tap.test('getCacheDir (v8)', t => { const cacheDir = getCacheDir(); const parts = cacheDir.split(os.tmpdir()); const nameParts = parts[1].split(path.sep); t.match(nameParts[1], /^v8-compile-cache(-\d+)?$/); t.equal(nameParts[2], process.arch); t.equal(nameParts[3], process.versions.v8); t.done(); }); tap.test('getCacheDir (chakracore)', t => { const cacheDir = vm.runInNewContext( '(' + getCacheDir.toString() + ')();', { process: { arch: process.arch, getuid: process.getuid, versions: {chakracore: '1.2.3'}, env: {}, }, path, os, } ); const parts = cacheDir.split(os.tmpdir()); const nameParts = parts[1].split(path.sep); t.match(nameParts[1], /^v8-compile-cache(-\d+)?$/); t.equal(nameParts[3], 'chakracore-1.2.3'); t.done(); }); tap.test('getCacheDir (unknown)', t => { const cacheDir = vm.runInNewContext( '(' + getCacheDir.toString() + ')();', { process: { arch: process.arch, getuid: process.getuid, version: '1.2.3', versions: {}, env: {}, }, path, os, } ); const parts = cacheDir.split(os.tmpdir()); const nameParts = parts[1].split(path.sep); t.match(nameParts[1], /^v8-compile-cache(-\d+)?$/); t.equal(nameParts[3], 'node-1.2.3'); t.done(); }); tap.test('getCacheDir (env)', t => { const cacheDir = vm.runInNewContext( '(' + getCacheDir.toString() + ')();', { process: { arch: process.arch, getuid: process.getuid, versions: {}, env: { V8_COMPILE_CACHE_CACHE_DIR: 'from env', }, }, path, os, } ); t.equal(cacheDir, 'from env'); t.done(); }); v8-compile-cache-2.4.0/test/getMainName-test.js000066400000000000000000000026521446651733000212360ustar00rootroot00000000000000'use strict'; const tap = require('tap'); const child_process = require('child_process'); tap.beforeEach(cb => { delete process.env.DISABLE_V8_COMPILE_CACHE; cb(); }); tap.test('handles --require', t => { const ps = child_process.spawnSync( process.execPath, ['--require', '..', require.resolve('./fixtures/print-main-name')], {cwd: __dirname} ); t.equal(ps.status, 0); t.equal(String(ps.stdout).trim(), __dirname); t.end(); }); tap.test('bad require.main.filename', t => { const ps = child_process.spawnSync( process.execPath, ['--eval', ` module.filename = null; console.log(require('..').__TEST__.getMainName()); `], {cwd: __dirname} ); t.equal(ps.status, 0); t.equal(String(ps.stdout).trim(), __dirname); t.end(); }); tap.test('require.main.filename works with --eval', t => { const ps = child_process.spawnSync( process.execPath, ['--eval', 'require("..")'], {cwd: __dirname} ); t.equal(ps.status, 0); t.end(); }); tap.test('require.main.filename works with --require', t => { const ps = child_process.spawnSync( process.execPath, ['--require', '..'], {cwd: __dirname} ); t.equal(ps.status, 0); t.end(); }); tap.test('require.main.filename works with as arg script', t => { const ps = child_process.spawnSync( process.execPath, [require.resolve('..')], {cwd: __dirname} ); t.equal(ps.status, 0); t.end(); }); v8-compile-cache-2.4.0/test/mkdirpSync-test.js000066400000000000000000000017561446651733000212000ustar00rootroot00000000000000'use strict'; const fs = require('fs'); const path = require('path'); const tap = require('tap'); const temp = require('temp'); temp.track(); process.env.DISABLE_V8_COMPILE_CACHE = 1; const mkdirpSync = require('..').__TEST__.mkdirpSync; tap.test('creates nested dirs', t => { const dirname = path.join(temp.path('mkdirpSync-test'), 'a/b/c'); t.notOk(fs.existsSync(dirname)); mkdirpSync(dirname); t.ok(fs.existsSync(dirname)); t.doesNotThrow(() => { t.ok(fs.existsSync(dirname)); mkdirpSync(dirname); t.ok(fs.existsSync(dirname)); }); t.end(); }); tap.test('throws if trying to write over a file', t => { const dirname = path.join(temp.path('mkdirpSync-test'), 'a'); const filename = path.join(dirname, 'b'); t.notOk(fs.existsSync(dirname)); mkdirpSync(dirname); t.ok(fs.existsSync(dirname)); fs.writeFileSync(filename, '\n'); t.ok(fs.existsSync(dirname)); t.throws(() => { mkdirpSync(filename); }, /EEXIST: file already exists/); t.end(); }); v8-compile-cache-2.4.0/test/module-test.js000066400000000000000000000030411446651733000203270ustar00rootroot00000000000000'use strict'; const child_process = require('child_process'); const tap = require('tap'); const semver = require('semver'); tap.beforeEach(cb => { delete process.env.DISABLE_V8_COMPILE_CACHE; cb(); }); tap.test('require.resolve.paths module', t => { const psWithoutCache = child_process.spawnSync( process.execPath, [require.resolve('./fixtures/require-resolve-paths'), 'tap'], {cwd: __dirname} ); const psWithCache = child_process.spawnSync( process.execPath, [ '--require', '..', require.resolve('./fixtures/require-resolve-paths'), 'tap', ], {cwd: __dirname} ); const actual = JSON.parse(psWithCache.stdout); const expected = JSON.parse(psWithoutCache.stdout); t.same(actual, expected); t.equal(psWithCache.stderr.toString(), ''); t.equal(psWithCache.status, 0); t.equal( actual.hasRequireResolve, semver.satisfies(process.versions.node, '>=8.9.0') ); t.end(); }); tap.test('require.resolve.paths relative', t => { const psWithoutCache = child_process.spawnSync( process.execPath, [require.resolve('./fixtures/require-resolve-paths'), './foo'], {cwd: __dirname} ); const psWithCache = child_process.spawnSync( process.execPath, [ '--require', '..', require.resolve('./fixtures/require-resolve-paths'), './foo', ], {cwd: __dirname} ); t.same(JSON.parse(psWithCache.stdout), JSON.parse(psWithoutCache.stdout)); t.equal(psWithCache.stderr.toString(), ''); t.equal(psWithCache.status, 0); t.end(); }); v8-compile-cache-2.4.0/test/node-version-test.js000066400000000000000000000010731446651733000214550ustar00rootroot00000000000000'use strict'; // This test is to make sure that v8-compile-cache.js can at least load in // Node 4/5. const tap = require('tap'); const semver = require('semver'); process.env.DISABLE_V8_COMPILE_CACHE = 1; tap.test('loads without throwing', t => { t.doesNotThrow(() => { require('..'); }); t.end(); }); tap.test('supportsCachedData', t => { const hasV8WithCache = semver.satisfies(process.versions.node, '>=5.7.0'); const supportsCachedData = require('..').__TEST__.supportsCachedData; t.equal(supportsCachedData(), hasV8WithCache); t.end(); }); v8-compile-cache-2.4.0/test/slashEscape-test.js000066400000000000000000000006531446651733000213030ustar00rootroot00000000000000'use strict'; const tap = require('tap'); process.env.DISABLE_V8_COMPILE_CACHE = 1; const slashEscape = require('..').__TEST__.slashEscape; var escapes = { '/a/b/c/d': 'zSazSbzSczSd', '/z/zZ/a/': 'zSzZzSzZZzSazS', 'z:\\a/b': 'zZzCzBazSb', '\x00abc': 'z0abc', }; tap.test('escape', t => { for (const key of Object.keys(escapes)) { t.equal( slashEscape(key), escapes[key] ); } t.done(); }); v8-compile-cache-2.4.0/v8-compile-cache.js000066400000000000000000000251251446651733000201410ustar00rootroot00000000000000'use strict'; const Module = require('module'); const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); const vm = require('vm'); const os = require('os'); const hasOwnProperty = Object.prototype.hasOwnProperty; //------------------------------------------------------------------------------ // FileSystemBlobStore //------------------------------------------------------------------------------ class FileSystemBlobStore { constructor(directory, prefix) { const name = prefix ? slashEscape(prefix + '.') : ''; this._blobFilename = path.join(directory, name + 'BLOB'); this._mapFilename = path.join(directory, name + 'MAP'); this._lockFilename = path.join(directory, name + 'LOCK'); this._directory = directory; this._load(); } has(key, invalidationKey) { if (hasOwnProperty.call(this._memoryBlobs, key)) { return this._invalidationKeys[key] === invalidationKey; } else if (hasOwnProperty.call(this._storedMap, key)) { return this._storedMap[key][0] === invalidationKey; } return false; } get(key, invalidationKey) { if (hasOwnProperty.call(this._memoryBlobs, key)) { if (this._invalidationKeys[key] === invalidationKey) { return this._memoryBlobs[key]; } } else if (hasOwnProperty.call(this._storedMap, key)) { const mapping = this._storedMap[key]; if (mapping[0] === invalidationKey) { return this._storedBlob.slice(mapping[1], mapping[2]); } } } set(key, invalidationKey, buffer) { this._invalidationKeys[key] = invalidationKey; this._memoryBlobs[key] = buffer; this._dirty = true; } delete(key) { if (hasOwnProperty.call(this._memoryBlobs, key)) { this._dirty = true; delete this._memoryBlobs[key]; } if (hasOwnProperty.call(this._invalidationKeys, key)) { this._dirty = true; delete this._invalidationKeys[key]; } if (hasOwnProperty.call(this._storedMap, key)) { this._dirty = true; delete this._storedMap[key]; } } isDirty() { return this._dirty; } save() { const dump = this._getDump(); const blobToStore = Buffer.concat(dump[0]); const mapToStore = JSON.stringify(dump[1]); try { mkdirpSync(this._directory); fs.writeFileSync(this._lockFilename, 'LOCK', {flag: 'wx'}); } catch (error) { // Swallow the exception if we fail to acquire the lock. return false; } try { fs.writeFileSync(this._blobFilename, blobToStore); fs.writeFileSync(this._mapFilename, mapToStore); } finally { fs.unlinkSync(this._lockFilename); } return true; } _load() { try { this._storedBlob = fs.readFileSync(this._blobFilename); this._storedMap = JSON.parse(fs.readFileSync(this._mapFilename)); } catch (e) { this._storedBlob = Buffer.alloc(0); this._storedMap = {}; } this._dirty = false; this._memoryBlobs = {}; this._invalidationKeys = {}; } _getDump() { const buffers = []; const newMap = {}; let offset = 0; function push(key, invalidationKey, buffer) { buffers.push(buffer); newMap[key] = [invalidationKey, offset, offset + buffer.length]; offset += buffer.length; } for (const key of Object.keys(this._memoryBlobs)) { const buffer = this._memoryBlobs[key]; const invalidationKey = this._invalidationKeys[key]; push(key, invalidationKey, buffer); } for (const key of Object.keys(this._storedMap)) { if (hasOwnProperty.call(newMap, key)) continue; const mapping = this._storedMap[key]; const buffer = this._storedBlob.slice(mapping[1], mapping[2]); push(key, mapping[0], buffer); } return [buffers, newMap]; } } //------------------------------------------------------------------------------ // NativeCompileCache //------------------------------------------------------------------------------ class NativeCompileCache { constructor() { this._cacheStore = null; this._previousModuleCompile = null; } setCacheStore(cacheStore) { this._cacheStore = cacheStore; } install() { const self = this; const hasRequireResolvePaths = typeof require.resolve.paths === 'function'; this._previousModuleCompile = Module.prototype._compile; Module.prototype._compile = function(content, filename) { const mod = this; function require(id) { return mod.require(id); } // https://github.com/nodejs/node/blob/v10.15.3/lib/internal/modules/cjs/helpers.js#L28 function resolve(request, options) { return Module._resolveFilename(request, mod, false, options); } require.resolve = resolve; // https://github.com/nodejs/node/blob/v10.15.3/lib/internal/modules/cjs/helpers.js#L37 // resolve.resolve.paths was added in v8.9.0 if (hasRequireResolvePaths) { resolve.paths = function paths(request) { return Module._resolveLookupPaths(request, mod, true); }; } require.main = process.mainModule; // Enable support to add extra extension types require.extensions = Module._extensions; require.cache = Module._cache; const dirname = path.dirname(filename); const compiledWrapper = self._moduleCompile(filename, content); // We skip the debugger setup because by the time we run, node has already // done that itself. // `Buffer` is included for Electron. // See https://github.com/zertosh/v8-compile-cache/pull/10#issuecomment-518042543 const args = [mod.exports, require, mod, filename, dirname, process, global, Buffer]; return compiledWrapper.apply(mod.exports, args); }; } uninstall() { Module.prototype._compile = this._previousModuleCompile; } _moduleCompile(filename, content) { // https://github.com/nodejs/node/blob/v7.5.0/lib/module.js#L511 // Remove shebang var contLen = content.length; if (contLen >= 2) { if (content.charCodeAt(0) === 35/*#*/ && content.charCodeAt(1) === 33/*!*/) { if (contLen === 2) { // Exact match content = ''; } else { // Find end of shebang line and slice it off var i = 2; for (; i < contLen; ++i) { var code = content.charCodeAt(i); if (code === 10/*\n*/ || code === 13/*\r*/) break; } if (i === contLen) { content = ''; } else { // Note that this actually includes the newline character(s) in the // new output. This duplicates the behavior of the regular // expression that was previously used to replace the shebang line content = content.slice(i); } } } } // create wrapper function var wrapper = Module.wrap(content); var invalidationKey = crypto .createHash('sha1') .update(content, 'utf8') .digest('hex'); var buffer = this._cacheStore.get(filename, invalidationKey); var script = new vm.Script(wrapper, { filename: filename, lineOffset: 0, displayErrors: true, cachedData: buffer, produceCachedData: true, }); if (script.cachedDataProduced) { this._cacheStore.set(filename, invalidationKey, script.cachedData); } else if (script.cachedDataRejected) { this._cacheStore.delete(filename); } var compiledWrapper = script.runInThisContext({ filename: filename, lineOffset: 0, columnOffset: 0, displayErrors: true, }); return compiledWrapper; } } //------------------------------------------------------------------------------ // utilities // // https://github.com/substack/node-mkdirp/blob/f2003bb/index.js#L55-L98 // https://github.com/zertosh/slash-escape/blob/e7ebb99/slash-escape.js //------------------------------------------------------------------------------ function mkdirpSync(p_) { _mkdirpSync(path.resolve(p_), 0o777); } function _mkdirpSync(p, mode) { try { fs.mkdirSync(p, mode); } catch (err0) { if (err0.code === 'ENOENT') { _mkdirpSync(path.dirname(p)); _mkdirpSync(p); } else { try { const stat = fs.statSync(p); if (!stat.isDirectory()) { throw err0; } } catch (err1) { throw err0; } } } } function slashEscape(str) { const ESCAPE_LOOKUP = { '\\': 'zB', ':': 'zC', '/': 'zS', '\x00': 'z0', 'z': 'zZ', }; const ESCAPE_REGEX = /[\\:/\x00z]/g; // eslint-disable-line no-control-regex return str.replace(ESCAPE_REGEX, match => ESCAPE_LOOKUP[match]); } function supportsCachedData() { const script = new vm.Script('""', {produceCachedData: true}); // chakracore, as of v1.7.1.0, returns `false`. return script.cachedDataProduced === true; } function getCacheDir() { const v8_compile_cache_cache_dir = process.env.V8_COMPILE_CACHE_CACHE_DIR; if (v8_compile_cache_cache_dir) { return v8_compile_cache_cache_dir; } // Avoid cache ownership issues on POSIX systems. const dirname = typeof process.getuid === 'function' ? 'v8-compile-cache-' + process.getuid() : 'v8-compile-cache'; // Avoid cache incompatibility issues with Rosetta on Apple Silicon. const arch = process.arch; const version = typeof process.versions.v8 === 'string' ? process.versions.v8 : typeof process.versions.chakracore === 'string' ? 'chakracore-' + process.versions.chakracore : 'node-' + process.version; const cacheDir = path.join(os.tmpdir(), dirname, arch, version); return cacheDir; } function getMainName() { // `require.main.filename` is undefined or null when: // * node -e 'require("v8-compile-cache")' // * node -r 'v8-compile-cache' // * Or, requiring from the REPL. const mainName = require.main && typeof require.main.filename === 'string' ? require.main.filename : process.cwd(); return mainName; } //------------------------------------------------------------------------------ // main //------------------------------------------------------------------------------ if (!process.env.DISABLE_V8_COMPILE_CACHE && supportsCachedData()) { const cacheDir = getCacheDir(); const prefix = getMainName(); const blobStore = new FileSystemBlobStore(cacheDir, prefix); const nativeCompileCache = new NativeCompileCache(); nativeCompileCache.setCacheStore(blobStore); nativeCompileCache.install(); process.once('exit', () => { if (blobStore.isDirty()) { blobStore.save(); } nativeCompileCache.uninstall(); }); } module.exports.__TEST__ = { FileSystemBlobStore, NativeCompileCache, mkdirpSync, slashEscape, supportsCachedData, getCacheDir, getMainName, };