This repository has been archived by the owner on Jan 8, 2019. It is now read-only.
forked from hsch/node-goog
-
Notifications
You must be signed in to change notification settings - Fork 16
/
nclosurebase.js
350 lines (303 loc) · 10.9 KB
/
nclosurebase.js
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
/**
* @fileoverview This does 3 tasks. It firstly detects sets up 2 interception
* points intended for goog.js (or anyone else) to be notified of scripts
* loaded or to veto the loading of a certain script. Then it checks if we
* are trying to run a closure jsunit test. If we are then it kills the
* current process and starts a new instance of nctest <filename>.
*
* If this is NOT a test then we minimaly initialise the closure framework.
* This means that we just load the base.js file as specified in the
* closureBasePath
*
* @author guido@tapia.com.au (Guido Tapia)
* @suppress {checkTypes}
*/
if (typeof(goog) !== 'undefined') {
goog.provide('nclosure.base');
goog.require('nclosure.opts');
goog.require('nclosure.settingsLoader');
} else {
global['nclosure'] = global['nclosure'] || {};
}
/**
* @constructor
*/
nclosure.base = function() {
/**
* The settings loaded in the current context.
* @type {nclosure.opts}
*/
this.globalClosureSettings;
/**
* The path to closure library's goog directory where deps.js and base.js
* reside
* @type {string}
*/
this.closureBasePath;
/**
* Stores a reference to the name of all the scripts loaded so we don't load
* things twice.
*
* @private
* @type {Object.<boolean>}
*/
this.scriptsWritten_ = {};
/**
* @private
* @type {nclosure.settingsLoader}
*/
this.settingsLoader_ = require('./settingsloader').settingsLoader;
/*
* This snippet is the main 'brains' of this entire module.
*
* If the currently executing file (being interpreted by Node.js) is a test
* file, we stop current execution and run the file using nctest.
*/
if (this.isRunningTestingFile_()) {
this.runCurrentFileInGoogTest_();
this.killCurrentThread_();
} else {
this.loadBaseScript_();
this.loadGoogDeps_();
this.loadAllDependencies(this.globalClosureSettings);
this.interceptGoogRequires_();
}
};
/**
* If this method returns false then the loadScript funciton above will abort
* the loading of a specified script. Intended to be shadowed by goog.js
* @param {string} dir The directory where the file resides.
* @param {string} file The file to load.
* @return {boolean} Wether to continue loading the script.
*/
nclosure.base.prototype.closureScriptLoading = function(dir, file) {
return true;
};
/**
* Notification after a script is loaded. Intended to be shadowed by goog.js
* @param {string} dir The directory where the file resides.
* @param {string} file The file to load.
*/
nclosure.base.prototype.closureScriptLoaded = function(dir, file) { };
/**
* @param {nclosure.opts} opts The settings object passed in to goog()
* call which may contain some dependencies.
*/
nclosure.base.prototype.loadAllDependencies = function(opts) {
this.loadAdditionalDepsInSettingsObject_(opts);
this.loadCurrentScriptDeps_();
};
/**
* @private
* This function allows other classes to use goog.require and pass in Node
* modules that will be pumped into the global scope of the executing module.
*/
nclosure.base.prototype.interceptGoogRequires_ = function() {
var nodeRequire = require;
var googRequire = goog.require;
var nodeRequires = this.globalClosureSettings.nodeRequires;
var that = this;
goog.require = function intercept(namespace) {
// If tests are requiring 'nclosure' then lets load the test
// additionalDeps if any specified. Otherwise ignore the call
// to require('nclosure') as we are already initialised
if (namespace === 'node.process') { return { goog: that }; }
// Ignore these 'herlper' classes which are not infact proper closure
// classes as they exists before closure (base.js) has been loaded.
// This means they must be called manually using node's require().
// If anyone is the using goog.require() for these types its only
// to get a bit of compiler support
else if (namespace.indexOf('nclosure_') === 0) { return; }
// Explicitly use node require if it's registered
// as a node require.
else if (nodeRequires && nodeRequires.indexOf(namespace) > -1) {
global[namespace] = nodeRequire(namespace);
} else {
googRequire(namespace);
}
};
};
/**
* Determines wether the currently running file, i.e. The file that is being
* executed by the node.js interpreter is a test file or not.
*
* TODO [GT]: Is there a better way to determine if this is a test file, this
* regex seems a little fragile?
*
* @return {boolean} Wether the currently running file is a testing file
* or not.
* @private
*/
nclosure.base.prototype.isRunningTestingFile_ = function() {
if (process.argv[1].indexOf('nctest.js') >= 0) return false;
var contents = require('fs').
readFileSync(process.argv[1]).toString();
return contents.toString().search(
/\s*goog\s*\.\s*require\(\s*['"]goog\.testing\.jsunit['"]\)/g) >= 0;
};
/**
* If the current file being executed is a test we actually stop processing and
* start a new child process that runs this file using nctest
* @private
*/
nclosure.base.prototype.runCurrentFileInGoogTest_ = function() {
var command = 'nctest';
var args = process.argv.splice(1);
var test = require('child_process').spawn(command, args);
var printMsg = function(data) {
data = data.toString();
if (data.charAt(data.length - 1) === '\n') {
data = data.substring(0, data.length - 1);
}
console.error(data);
};
test.stdout.on('data', printMsg);
test.stderr.on('data', printMsg);
test.on('uncaughtException', function(err) {
console.error(err.stack);
});
};
/**
* Kills the current thread (stops the Node session)
* @private
*/
nclosure.base.prototype.killCurrentThread_ = function() {
console.log('Killing the current thread.')
process.once('uncaughtException', function(ex) {});
throw new Error();
};
/**
* Loads the base script
* @private
*/
nclosure.base.prototype.loadBaseScript_ = function() {
this.globalClosureSettings = this.settingsLoader_.readSettingsObject();
this.closureBasePath = this.resolveClosureBasePath_();
this.loadBaseScriptImpl_();
};
/**
* We can now load the Closure dependency tree.
* @private
*/
nclosure.base.prototype.loadGoogDeps_ = function() {
this.loadDependenciesFile(this.closureBasePath, 'deps.js');
};
/**
* Resolves the path to the goog directory in the closure library. This
* directory holds the deps.js and base.js file.
* @return {string} The goog directory in the closure library. This
* directory holds the deps.js and base.js file.
* @private
*/
nclosure.base.prototype.resolveClosureBasePath_ = function() {
return require('path').resolve(
this.globalClosureSettings.closureBasePath, 'closure/goog');
};
/**
* Loads the closure's base.js file which gets the running context into a
* minimally initiated closure state. We also update the following references:
*
* goog.global points to the 'window' object in a browser environment. We
* replace that with our own global context. We also set window and top which
* are used by various different portions of the closure library and testing
* code.
*
* Closure loads files by writing out <script> tags. This is fine in a browser
* but obviously we need to replace this behaviour to make things work on Node.
* @private
*/
nclosure.base.prototype.loadBaseScriptImpl_ = function() {
this.loadScript(this.closureBasePath, 'base.js');
goog.global = goog.window = global.top = global;
var that = this;
global.goog.writeScriptTag_ = function(filename) {
that.loadScript(that.closureBasePath, filename);
return false;
};
};
/**
* Loads a script into the current global context.
*
* TODO: This is how we will load and execute JavaScript files. The
* synchronous implementation seems to be appropriate for loading modules,
* but one could think about asynchronous loading anyway (especially for
* later calls to goog.require(...)).
*
* TODO: We need to ensure that no script is loaded
* twice because this would lead to an exception when
* a Closure namespace is declared more than once.
* Not sure how Closure handles this, or whether the
* problem is only circumvented by their compiler.
*
* @param {string} dir The directory where the file resides.
* @param {string} file The file to load.
*/
nclosure.base.prototype.loadScript = function(dir, file) {
// Give goog.js a chance to stop the loading of certain scripts
if (!this.closureScriptLoading(dir, file)) { return; }
// TODO: Do we need absolute file support?
var path = (file.search(/[\/\\]/) === 0 ? file :
require('path').resolve(dir, file));
if (this.scriptsWritten_[path]) { return; }
this.scriptsWritten_[path] = true;
var contents = require('fs').readFileSync(path).toString();
contents = this.settingsLoader_.removeShebang(contents);
try {
process.binding('evals').NodeScript.
runInThisContext.call(global, contents, file);
} catch (ex) {
console.error('Could not goog.require("' + path + '")\n' + ex.stack);
process.exit(1);
}
this.closureScriptLoaded(dir, file);
};
/**
* This can be called by any 'nclosure' initiaised system to load
* additional deps files dynamically. This is safe to be called even if
* you are not sure wether the file exists as it will be ignored if the file
* does not exist. It is also safe to be called multiple times for the
* same file as multiple requests are ignored.
*
* @param {string} dir The directory with the specified file.
* @param {string} file The dependencies file to load.
*/
nclosure.base.prototype.loadDependenciesFile = function(dir, file) {
var path = this.settingsLoader_.getPath(dir, file);
if (!require('fs').existsSync(path)) { return; }
this.loadScript(dir, file);
};
/**
* If the currently executed script has deps.js in its directory also load it
* as it will contain some additional dependencies which may not be in the
* additionalDeps declaration of the settings file.
*
* @private
*/
nclosure.base.prototype.loadCurrentScriptDeps_ = function() {
var file = process.argv[process.argv.length - 1];
if (require('fs').existsSync(file)) {
var depsPath = !file ? null : this.settingsLoader_.getFileDirectory(file);
if (depsPath) this.loadDependenciesFile(depsPath, 'deps.js');
}
this.loadDependenciesFile(__dirname, 'deps.js');
this.loadDependenciesFile(__dirname, '../bin/deps.js');
};
/**
* @private
* @param {nclosure.opts} opts The options object which we will parse for
* additional deps.
*/
nclosure.base.prototype.loadAdditionalDepsInSettingsObject_ =
function(opts) {
if (!opts || !opts.additionalDeps) { return; }
for (var i = 0, len = opts.additionalDeps.length; i < len; i++) {
var fileName = opts.additionalDeps[i];
var idx = fileName.search(/[\/\\][^\/\\]*$/g);
this.loadScript(fileName.substring(0, idx + 1),
fileName.substring(idx + 1));
}
};
/**
* @type {nclosure.base}
*/
exports.nclosurebase = new nclosure.base();