Skip to content

Commit 217a5dc

Browse files
committed
add option to use nodejs/v8 cached code when creating a script
1 parent f7ac3b1 commit 217a5dc

File tree

2 files changed

+185
-17
lines changed

2 files changed

+185
-17
lines changed

src/loader.js

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,20 @@ var AMDLoader;
191191
if (!Array.isArray(options.nodeModules)) {
192192
options.nodeModules = [];
193193
}
194+
if (typeof options.nodeCachedDataWriteDelay !== 'number' || options.nodeCachedDataWriteDelay < 0) {
195+
options.nodeCachedDataWriteDelay = 1000 * 7;
196+
}
197+
if (typeof options.onNodeCachedDataError !== 'function') {
198+
options.onNodeCachedDataError = function (err) {
199+
if (err.errorCode === 'cachedDataRejected') {
200+
console.warn('Rejected cached data from file: ' + err.path);
201+
}
202+
else if (err.errorCode === 'unlink' || err.errorCode === 'writeFile') {
203+
console.error('Problems writing cached data file: ' + err.path);
204+
console.error(err.detail);
205+
}
206+
};
207+
}
194208
return options;
195209
};
196210
ConfigurationOptionsUtil.mergeConfigurationOptions = function (overwrite, base) {
@@ -1771,19 +1785,66 @@ var AMDLoader;
17711785
contents = prefix + data + suffix;
17721786
}
17731787
contents = nodeInstrumenter(contents, vmScriptSrc);
1774-
var r;
1775-
if (/^v0\.12/.test(process.version)) {
1776-
r = _this._vm.runInThisContext(contents, { filename: vmScriptSrc });
1788+
if (!opts.nodeCachedDataDir) {
1789+
var r = _this._vm.runInThisContext(contents, { filename: vmScriptSrc });
1790+
r.call(global, RequireFunc, DefineFunc, vmScriptSrc, _this._path.dirname(scriptSrc));
1791+
recorder.record(LoaderEventType.NodeEndEvaluatingScript, scriptSrc);
1792+
callback();
17771793
}
17781794
else {
1779-
r = _this._vm.runInThisContext(contents, vmScriptSrc);
1795+
var cachedDataPath_1 = _this._path.join(opts.nodeCachedDataDir, scriptSrc.replace(/\\|\//g, '') + '.code');
1796+
_this._fs.readFile(cachedDataPath_1, function (err, data) {
1797+
// create script options
1798+
var scriptOptions = {
1799+
filename: vmScriptSrc,
1800+
produceCachedData: typeof data === 'undefined',
1801+
cachedData: data
1802+
};
1803+
// create script, run script
1804+
var script = new _this._vm.Script(contents, scriptOptions);
1805+
var r = script.runInThisContext(scriptOptions);
1806+
r.call(global, RequireFunc, DefineFunc, vmScriptSrc, _this._path.dirname(scriptSrc));
1807+
// signal done
1808+
recorder.record(LoaderEventType.NodeEndEvaluatingScript, scriptSrc);
1809+
callback();
1810+
// cached code after math
1811+
if (script.cachedDataRejected) {
1812+
// data rejected => delete cache file
1813+
opts.onNodeCachedDataError({
1814+
errorCode: 'cachedDataRejected',
1815+
path: cachedDataPath_1
1816+
});
1817+
NodeScriptLoader._runSoon(function () { return _this._fs.unlink(cachedDataPath_1, function (err) {
1818+
if (err) {
1819+
opts.onNodeCachedDataError({
1820+
errorCode: 'unlink',
1821+
path: cachedDataPath_1,
1822+
detail: err
1823+
});
1824+
}
1825+
}); }, opts.nodeCachedDataWriteDelay);
1826+
}
1827+
else if (script.cachedDataProduced) {
1828+
// data produced => write cache file
1829+
NodeScriptLoader._runSoon(function () { return _this._fs.writeFile(cachedDataPath_1, script.cachedData, function (err) {
1830+
if (err) {
1831+
opts.onNodeCachedDataError({
1832+
errorCode: 'writeFile',
1833+
path: cachedDataPath_1,
1834+
detail: err
1835+
});
1836+
}
1837+
}); }, opts.nodeCachedDataWriteDelay);
1838+
}
1839+
});
17801840
}
1781-
r.call(global, RequireFunc, DefineFunc, vmScriptSrc, _this._path.dirname(scriptSrc));
1782-
recorder.record(LoaderEventType.NodeEndEvaluatingScript, scriptSrc);
1783-
callback();
17841841
});
17851842
}
17861843
};
1844+
NodeScriptLoader._runSoon = function (callback, minTimeout) {
1845+
var timeout = minTimeout + Math.ceil(Math.random() * minTimeout);
1846+
setTimeout(callback, timeout);
1847+
};
17871848
NodeScriptLoader._BOM = 0xFEFF;
17881849
return NodeScriptLoader;
17891850
}());

src/loader.ts

Lines changed: 117 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,18 @@ module AMDLoader {
270270
nodeMain?:string;
271271
nodeModules?:string[];
272272
checksum?:boolean;
273+
/**
274+
* Optional data directory for reading/writing v8 cached data (http://v8project.blogspot.co.uk/2015/07/code-caching.html)
275+
*/
276+
nodeCachedDataDir?: string;
277+
/**
278+
* Optional delay for filesystem write/delete operations
279+
*/
280+
nodeCachedDataWriteDelay?: number;
281+
/**
282+
* Optional callback that will be invoked when errors with cached code occur
283+
*/
284+
onNodeCachedDataError?: (err: any) => void;
273285
}
274286

275287
export class ConfigurationOptionsUtil {
@@ -341,6 +353,20 @@ module AMDLoader {
341353
if (!Array.isArray(options.nodeModules)) {
342354
options.nodeModules = [];
343355
}
356+
if (typeof options.nodeCachedDataWriteDelay !== 'number' || options.nodeCachedDataWriteDelay < 0) {
357+
options.nodeCachedDataWriteDelay = 1000 * 7;
358+
}
359+
if (typeof options.onNodeCachedDataError !== 'function') {
360+
options.onNodeCachedDataError = (err) => {
361+
if (err.errorCode === 'cachedDataRejected') {
362+
console.warn('Rejected cached data from file: ' + err.path);
363+
364+
} else if (err.errorCode === 'unlink' || err.errorCode === 'writeFile') {
365+
console.error('Problems writing cached data file: ' + err.path);
366+
console.error(err.detail);
367+
}
368+
};
369+
}
344370

345371
return options;
346372
}
@@ -2192,18 +2218,40 @@ module AMDLoader {
21922218
}
21932219
}
21942220

2221+
declare class Buffer {
2222+
2223+
}
2224+
21952225
interface INodeFS {
2196-
readFile(filename:string, options:{encoding?:string; flag?:string}, callback:(err:any, data:any)=>void): void;
2226+
readFile(filename: string, options: { encoding?: string; flag?: string }, callback: (err: any, data: any) => void): void;
2227+
readFile(filename: string, callback: (err: any, data: Buffer) => void): void;
2228+
writeFile(filename: string, data: Buffer, callback: (err: any) => void): void;
2229+
unlink(path: string, callback: (err: any) => void): void;
2230+
}
2231+
2232+
interface INodeVMScriptOptions {
2233+
filename: string;
2234+
produceCachedData?: boolean;
2235+
cachedData?: Buffer;
2236+
}
2237+
2238+
interface INodeVMScript {
2239+
cachedData: Buffer;
2240+
cachedDataProduced: boolean;
2241+
cachedDataRejected: boolean;
2242+
runInThisContext(options: INodeVMScriptOptions);
21972243
}
21982244

21992245
interface INodeVM {
2246+
Script: { new (contents: string, options: INodeVMScriptOptions): INodeVMScript }
22002247
runInThisContext(contents:string, { filename:string });
22012248
runInThisContext(contents:string, filename:string);
22022249
}
22032250

22042251
interface INodePath {
22052252
dirname(filename:string): string;
2206-
normalize(filename:string): string;
2253+
normalize(filename: string):string;
2254+
join(...parts: string[]): string;
22072255
}
22082256

22092257
interface INodeCryptoHash {
@@ -2313,21 +2361,80 @@ module AMDLoader {
23132361

23142362
contents = nodeInstrumenter(contents, vmScriptSrc);
23152363

2316-
let r;
2317-
if (/^v0\.12/.test(process.version)) {
2318-
r = this._vm.runInThisContext(contents, { filename:vmScriptSrc });
2364+
if (!opts.nodeCachedDataDir) {
2365+
2366+
const r = this._vm.runInThisContext(contents, { filename: vmScriptSrc });
2367+
2368+
r.call(global, RequireFunc, DefineFunc, vmScriptSrc, this._path.dirname(scriptSrc));
2369+
2370+
recorder.record(LoaderEventType.NodeEndEvaluatingScript, scriptSrc);
2371+
2372+
callback();
2373+
23192374
} else {
2320-
r = this._vm.runInThisContext(contents, vmScriptSrc);
2321-
}
23222375

2323-
r.call(global, RequireFunc, DefineFunc, vmScriptSrc, this._path.dirname(scriptSrc));
2376+
const cachedDataPath = this._path.join(opts.nodeCachedDataDir, scriptSrc.replace(/\\|\//g, '') + '.code');
23242377

2325-
recorder.record(LoaderEventType.NodeEndEvaluatingScript, scriptSrc);
2378+
this._fs.readFile(cachedDataPath, (err, data) => {
23262379

2327-
callback();
2380+
// create script options
2381+
const scriptOptions: INodeVMScriptOptions = {
2382+
filename: vmScriptSrc,
2383+
produceCachedData: typeof data === 'undefined',
2384+
cachedData: data
2385+
};
2386+
2387+
// create script, run script
2388+
const script = new this._vm.Script(contents, scriptOptions);
2389+
const r = script.runInThisContext(scriptOptions);
2390+
r.call(global, RequireFunc, DefineFunc, vmScriptSrc, this._path.dirname(scriptSrc));
2391+
2392+
// signal done
2393+
recorder.record(LoaderEventType.NodeEndEvaluatingScript, scriptSrc);
2394+
callback();
2395+
2396+
// cached code after math
2397+
if (script.cachedDataRejected) {
2398+
// data rejected => delete cache file
2399+
2400+
opts.onNodeCachedDataError({
2401+
errorCode: 'cachedDataRejected',
2402+
path: cachedDataPath
2403+
});
2404+
2405+
NodeScriptLoader._runSoon(() => this._fs.unlink(cachedDataPath, err => {
2406+
if (err) {
2407+
opts.onNodeCachedDataError({
2408+
errorCode: 'unlink',
2409+
path: cachedDataPath,
2410+
detail: err
2411+
});
2412+
}
2413+
}), opts.nodeCachedDataWriteDelay);
2414+
2415+
} else if (script.cachedDataProduced) {
2416+
// data produced => write cache file
2417+
2418+
NodeScriptLoader._runSoon(() => this._fs.writeFile(cachedDataPath, script.cachedData, err => {
2419+
if (err) {
2420+
opts.onNodeCachedDataError({
2421+
errorCode: 'writeFile',
2422+
path: cachedDataPath,
2423+
detail: err
2424+
});
2425+
}
2426+
}), opts.nodeCachedDataWriteDelay);
2427+
}
2428+
});
2429+
}
23282430
});
23292431
}
23302432
}
2433+
2434+
private static _runSoon(callback: Function, minTimeout: number): void {
2435+
const timeout = minTimeout + Math.ceil(Math.random() * minTimeout);
2436+
setTimeout(callback, timeout);
2437+
}
23312438
}
23322439

23332440
// ------------------------------------------------------------------------

0 commit comments

Comments
 (0)