-
Notifications
You must be signed in to change notification settings - Fork 2
/
bootStrapCodeCache.ts
173 lines (168 loc) · 5.61 KB
/
bootStrapCodeCache.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/// <reference types="node" />
import * as vm from "vm";
import * as fs from "fs";
/*
This is probably not okay.
It however ensures that this module will function correctly if invoked to bootstrap some other
script transitively. I.e. node bootStrapper -> actualScript
where bootStrapper simply calls require("bootStrapCodeCache")("actualScript",etc)
vm.runInThisContext does NOT expose local variables/enclosing params. This actually binds
the require param (from Node's module.wrapper) to the global scope of jsFile. This will probably blow up in my face
at some point. require is passed along just fine under Electron(in renderer mode) if this module is invoked from within <script> tags in html.
This leads me to believe Electron(in renderer mode) is therefore binding Node's real, global require to the process' global at startup.
This fix is only required when run under Node. Maybe an environment check should be made prior to actually doing this.
*/
if(!(<any>global).require)
(<any>global).require = require;
/**
* Load the cached data at cdata, using the js source file at jsFile as the reference source.
* Returns 0 on success.
* Returns 1 if the cached data at cdata does not exist.
* Returns 2 if the js source file at jsFile does not exist.
* Returns 3 if the cached data was rejected by V8.
*
* @param {string} jsFile - Path to Javascript file to load
* @param {string} cdata - Path to jsFile's cached data
* @returns {number}
*/
function loadFromCache(jsFile : string,cdata : string) : number
{
let cache : Buffer;
let jsFileCode : string;
try
{
cache = fs.readFileSync(cdata);
}
catch(err)
{
console.log("Could not load "+cdata);
return 1;
}
try
{
jsFileCode = (<any>fs.readFileSync(jsFile));
}
catch(err)
{
console.log("Could not load "+jsFile+" fatal");
return 2;
}
let compiledCode : vm.Script = new vm.Script(
jsFileCode,<vm.ScriptOptions>{
filename : jsFile,
lineOffset : 0,
displayErrors : true,
cachedData : cache
}
);
if(!(<any>compiledCode).cachedDataRejected)
{
console.log("Successfully loaded from "+cdata);
compiledCode.runInThisContext();
return 0;
}
else
{
console.log("Cached code "+cdata+" was rejected.");
return 3;
}
}
/**
* Compile cached data for the js source file at jsFile, saves to the path given by cdata.
* Returns 0 on success.
* Returns 1 if the js source file at jsFile failed to compile.
* Returns 2 if the js source file at jsFile does not exist.
* Returns 3 if the compiled cached data for the js source file at jsFile could not be written to the path given by cdata.
*
* @param {string} jsFile - Path to Javascript file to load
* @param {string} cdata - Path to save cached data to
* @returns {number}
*/
function compileCache(jsFile : string,cdata : string) : number
{
let jsFileCode : string;
let cache : Buffer;
let compiler : vm.Script;
try
{
jsFileCode = (<any>fs.readFileSync(jsFile));
}
catch(err)
{
console.log("Could not load "+jsFile+" fatal");
return 2;
}
compiler = new vm.Script(
jsFileCode,<vm.ScriptOptions>{
filename : jsFile,
lineOffset : 0,
displayErrors : true,
produceCachedData : true
}
);
if((<any>compiler).cachedDataProduced && (<any>compiler).cachedData)
{
cache = (<any>compiler).cachedData;
console.log("Successfully compiled "+jsFile);
try
{
fs.writeFileSync(cdata,cache);
}
catch(err)
{
console.log("Failed to write "+cdata);
return 3;
}
return 0;
}
else
{
console.log("Failed to compile "+jsFile);
return 1;
}
}
/**
* Bootstraps a code cache for the js source file at jsFile.
* Trys to first load the cached data at cdata using the js source file at jsFile as the reference source.
* On failure, will try to compile the js source at jsFile, save it to cdata and then load it.
* On failure to compile, failure to write or failure to load compiled cached data, will fall back to requiring jsModule.
*
* @export
* @param {string} jsFile - Path to Javascript file to bootstrap
* @param {string} jsModule - Fallback path for resolution algorithm
* @param {string} cdata - Path to save cached data to
* @returns {void}
*/
export function bootStrapCodeCache(jsFile : string,jsModule : string,cdata : string) : void
{
if("production" !== process.env.NODE_ENV)
{
require(`./../${jsModule}`);
return;
}
let cacheStatus : number = loadFromCache(jsFile,cdata);
//cdata exists, is valid and has been executed
if(cacheStatus == 0)
return;
//either the cache does not exist, or is invalid for some reason
if(cacheStatus == 1 || cacheStatus == 3)
{
//try to compile a new cache
let compilerStatus = compileCache(jsFile,cdata);
//try to load the new cache
let secondTry = loadFromCache(jsFile,cdata);
//if there was a failure in either operation then stop trying and just
//load the JS the old fashioned way
if(secondTry != 0 || compilerStatus != 0)
{
console.log("Falling back to require");
require(jsModule);
return;
}
}
//the file specified by jsFile does not exist. This is fatal and will break all other operations.
if(cacheStatus == 2)
{
throw new Error("Fatal error: Could not load file "+jsFile);
}
}