Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81158 views
1
var assert = require("assert");
2
var path = require("path");
3
var fs = require("fs");
4
var Q = require("q");
5
var iconv = require("iconv-lite");
6
var createHash = require("crypto").createHash;
7
var getRequiredIDs = require("install").getRequiredIDs;
8
var util = require("./util");
9
var BuildContext = require("./context").BuildContext;
10
var slice = Array.prototype.slice;
11
12
function ModuleReader(context, resolvers, processors) {
13
var self = this;
14
assert.ok(self instanceof ModuleReader);
15
assert.ok(context instanceof BuildContext);
16
assert.ok(resolvers instanceof Array);
17
assert.ok(processors instanceof Array);
18
19
var hash = createHash("sha1").update(context.optionsHash + "\0");
20
21
function hashCallbacks(salt) {
22
hash.update(salt + "\0");
23
24
var cbs = util.flatten(slice.call(arguments, 1));
25
26
cbs.forEach(function(cb) {
27
assert.strictEqual(typeof cb, "function");
28
hash.update(cb + "\0");
29
});
30
31
return cbs;
32
}
33
34
resolvers = hashCallbacks("resolvers", resolvers, warnMissingModule);
35
36
var procArgs = [processors];
37
if (context.relativize && !context.ignoreDependencies)
38
procArgs.push(require("./relative").getProcessor(self));
39
processors = hashCallbacks("processors", procArgs);
40
41
Object.defineProperties(self, {
42
context: { value: context },
43
idToHash: { value: {} },
44
resolvers: { value: resolvers },
45
processors: { value: processors },
46
salt: { value: hash.digest("hex") }
47
});
48
}
49
50
ModuleReader.prototype = {
51
getSourceP: util.cachedMethod(function(id) {
52
var context = this.context;
53
var copy = this.resolvers.slice(0).reverse();
54
assert.ok(copy.length > 0, "no source resolvers registered");
55
56
function tryNextResolverP() {
57
var resolve = copy.pop();
58
59
try {
60
var promise = Q(resolve && resolve.call(context, id));
61
} catch (e) {
62
promise = Q.reject(e);
63
}
64
65
return resolve ? promise.then(function(result) {
66
if (typeof result === "string")
67
return result;
68
return tryNextResolverP();
69
}, tryNextResolverP) : promise;
70
}
71
72
return tryNextResolverP();
73
}),
74
75
getCanonicalIdP: util.cachedMethod(function(id) {
76
var reader = this;
77
if (reader.context.useProvidesModule) {
78
return reader.getSourceP(id).then(function(source) {
79
return reader.context.getProvidedId(source) || id;
80
});
81
} else {
82
return Q(id);
83
}
84
}),
85
86
readModuleP: util.cachedMethod(function(id) {
87
var reader = this;
88
89
return reader.getSourceP(id).then(function(source) {
90
if (reader.context.useProvidesModule) {
91
// If the source contains a @providesModule declaration, treat
92
// that declaration as canonical. Note that the Module object
93
// returned by readModuleP might have an .id property whose
94
// value differs from the original id parameter.
95
id = reader.context.getProvidedId(source) || id;
96
}
97
98
assert.strictEqual(typeof source, "string");
99
100
var hash = createHash("sha1")
101
.update("module\0")
102
.update(id + "\0")
103
.update(reader.salt + "\0")
104
.update(source.length + "\0" + source)
105
.digest("hex");
106
107
if (reader.idToHash.hasOwnProperty(id)) {
108
// Ensure that the same module identifier is not
109
// provided by distinct modules.
110
assert.strictEqual(
111
reader.idToHash[id], hash,
112
"more than one module named " +
113
JSON.stringify(id));
114
} else {
115
reader.idToHash[id] = hash;
116
}
117
118
return reader.buildModuleP(id, hash, source);
119
});
120
}),
121
122
buildModuleP: util.cachedMethod(function(id, hash, source) {
123
var reader = this;
124
return reader.processOutputP(
125
id, hash, source
126
).then(function(output) {
127
return new Module(reader, id, hash, output);
128
});
129
}, function(id, hash, source) {
130
return hash;
131
}),
132
133
processOutputP: function(id, hash, source) {
134
var reader = this;
135
var cacheDir = reader.context.cacheDir;
136
var manifestDir = cacheDir && path.join(cacheDir, "manifest");
137
var charset = reader.context.options.outputCharset;
138
139
function buildP() {
140
var promise = Q(source);
141
142
reader.processors.forEach(function(build) {
143
promise = promise.then(function(input) {
144
return util.waitForValuesP(
145
build.call(reader.context, id, input)
146
);
147
});
148
});
149
150
return promise.then(function(output) {
151
if (typeof output === "string") {
152
output = { ".js": output };
153
} else {
154
assert.strictEqual(typeof output, "object");
155
}
156
157
return util.waitForValuesP(output);
158
159
}).then(function(output) {
160
util.log.err(
161
"built Module(" + JSON.stringify(id) + ")",
162
"cyan"
163
);
164
165
return output;
166
167
}).catch(function(err) {
168
// Provide additional context for uncaught build errors.
169
util.log.err("Error while reading module " + id + ":");
170
throw err;
171
});
172
}
173
174
if (manifestDir) {
175
return util.mkdirP(manifestDir).then(function(manifestDir) {
176
var manifestFile = path.join(manifestDir, hash + ".json");
177
178
return util.readJsonFileP(manifestFile).then(function(manifest) {
179
Object.keys(manifest).forEach(function(key) {
180
var cacheFile = path.join(cacheDir, manifest[key]);
181
manifest[key] = util.readFileP(cacheFile);
182
});
183
184
return util.waitForValuesP(manifest, true);
185
186
}).catch(function(err) {
187
return buildP().then(function(output) {
188
var manifest = {};
189
190
Object.keys(output).forEach(function(key) {
191
var cacheFile = manifest[key] = hash + key;
192
var fullPath = path.join(cacheDir, cacheFile);
193
194
if (charset) {
195
fs.writeFileSync(fullPath, iconv.encode(output[key], charset))
196
} else {
197
fs.writeFileSync(fullPath, output[key], "utf8");
198
}
199
});
200
201
fs.writeFileSync(
202
manifestFile,
203
JSON.stringify(manifest),
204
"utf8"
205
);
206
207
return output;
208
});
209
});
210
});
211
}
212
213
return buildP();
214
},
215
216
readMultiP: function(ids) {
217
var reader = this;
218
219
return Q(ids).all().then(function(ids) {
220
if (ids.length === 0)
221
return ids; // Shortcut.
222
223
var modulePs = ids.map(reader.readModuleP, reader);
224
return Q(modulePs).all().then(function(modules) {
225
var seen = {};
226
var result = [];
227
228
modules.forEach(function(module) {
229
if (!seen.hasOwnProperty(module.id)) {
230
seen[module.id] = true;
231
result.push(module);
232
}
233
});
234
235
return result;
236
});
237
});
238
}
239
};
240
241
exports.ModuleReader = ModuleReader;
242
243
function warnMissingModule(id) {
244
// A missing module may be a false positive and therefore does not warrant
245
// a fatal error, but a warning is certainly in order.
246
util.log.err(
247
"unable to resolve module " + JSON.stringify(id) + "; false positive?",
248
"yellow");
249
250
// Missing modules are installed as if they existed, but it's a run-time
251
// error if one is ever actually required.
252
var message = "nonexistent module required: " + id;
253
return "throw new Error(" + JSON.stringify(message) + ");";
254
}
255
256
function Module(reader, id, hash, output) {
257
assert.ok(this instanceof Module);
258
assert.ok(reader instanceof ModuleReader);
259
assert.strictEqual(typeof output, "object");
260
261
var source = output[".js"];
262
assert.strictEqual(typeof source, "string");
263
264
Object.defineProperties(this, {
265
reader: { value: reader },
266
id: { value: id },
267
hash: { value: hash }, // TODO Remove?
268
deps: { value: getRequiredIDs(id, source) },
269
source: { value: source },
270
output: { value: output }
271
});
272
}
273
274
Module.prototype = {
275
getRequiredP: function() {
276
return this.reader.readMultiP(this.deps);
277
},
278
279
writeVersionP: function(outputDir) {
280
var id = this.id;
281
var hash = this.hash;
282
var output = this.output;
283
var cacheDir = this.reader.context.cacheDir;
284
var charset = this.reader.context.options.outputCharset;
285
286
return Q.all(Object.keys(output).map(function(key) {
287
var outputFile = path.join(outputDir, id + key);
288
289
function writeCopy() {
290
if (charset) {
291
fs.writeFileSync(outputFile, iconv.encode(output[key], charset));
292
} else {
293
fs.writeFileSync(outputFile, output[key], "utf8");
294
}
295
return outputFile;
296
}
297
298
if (cacheDir) {
299
var cacheFile = path.join(cacheDir, hash + key);
300
return util.linkP(cacheFile, outputFile)
301
// If the hard linking fails, the cache directory
302
// might be on a different device, so fall back to
303
// writing a copy of the file (slightly slower).
304
.catch(writeCopy);
305
}
306
307
return util.mkdirP(path.dirname(outputFile)).then(writeCopy);
308
}));
309
},
310
311
toString: function() {
312
return "Module(" + JSON.stringify(this.id) + ")";
313
},
314
315
resolveId: function(id) {
316
return util.absolutize(this.id, id);
317
}
318
};
319
320