123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- var assert = require("assert");
- var path = require("path");
- var fs = require("fs");
- var Q = require("q");
- var iconv = require("iconv-lite");
- var createHash = require("crypto").createHash;
- var detective = require("detective");
- var util = require("./util");
- var BuildContext = require("./context").BuildContext;
- var slice = Array.prototype.slice;
- function getRequiredIDs(id, source) {
- var ids = {};
- detective(source).forEach(function (dep) {
- ids[path.normalize(path.join(id, "..", dep))] = true;
- });
- return Object.keys(ids);
- }
- function ModuleReader(context, resolvers, processors) {
- var self = this;
- assert.ok(self instanceof ModuleReader);
- assert.ok(context instanceof BuildContext);
- assert.ok(resolvers instanceof Array);
- assert.ok(processors instanceof Array);
- var hash = createHash("sha1").update(context.optionsHash + "\0");
- function hashCallbacks(salt) {
- hash.update(salt + "\0");
- var cbs = util.flatten(slice.call(arguments, 1));
- cbs.forEach(function(cb) {
- assert.strictEqual(typeof cb, "function");
- hash.update(cb + "\0");
- });
- return cbs;
- }
- resolvers = hashCallbacks("resolvers", resolvers, warnMissingModule);
- var procArgs = [processors];
- if (context.relativize && !context.ignoreDependencies)
- procArgs.push(require("./relative").getProcessor(self));
- processors = hashCallbacks("processors", procArgs);
- Object.defineProperties(self, {
- context: { value: context },
- idToHash: { value: {} },
- resolvers: { value: resolvers },
- processors: { value: processors },
- salt: { value: hash.digest("hex") }
- });
- }
- ModuleReader.prototype = {
- getSourceP: util.cachedMethod(function(id) {
- var context = this.context;
- var copy = this.resolvers.slice(0).reverse();
- assert.ok(copy.length > 0, "no source resolvers registered");
- function tryNextResolverP() {
- var resolve = copy.pop();
- try {
- var promise = Q(resolve && resolve.call(context, id));
- } catch (e) {
- promise = Q.reject(e);
- }
- return resolve ? promise.then(function(result) {
- if (typeof result === "string")
- return result;
- return tryNextResolverP();
- }, tryNextResolverP) : promise;
- }
- return tryNextResolverP();
- }),
- getCanonicalIdP: util.cachedMethod(function(id) {
- var reader = this;
- if (reader.context.useProvidesModule) {
- return reader.getSourceP(id).then(function(source) {
- return reader.context.getProvidedId(source) || id;
- });
- } else {
- return Q(id);
- }
- }),
- readModuleP: util.cachedMethod(function(id) {
- var reader = this;
- return reader.getSourceP(id).then(function(source) {
- if (reader.context.useProvidesModule) {
- // If the source contains a @providesModule declaration, treat
- // that declaration as canonical. Note that the Module object
- // returned by readModuleP might have an .id property whose
- // value differs from the original id parameter.
- id = reader.context.getProvidedId(source) || id;
- }
- assert.strictEqual(typeof source, "string");
- var hash = createHash("sha1")
- .update("module\0")
- .update(id + "\0")
- .update(reader.salt + "\0")
- .update(source.length + "\0" + source)
- .digest("hex");
- if (reader.idToHash.hasOwnProperty(id)) {
- // Ensure that the same module identifier is not
- // provided by distinct modules.
- assert.strictEqual(
- reader.idToHash[id], hash,
- "more than one module named " +
- JSON.stringify(id));
- } else {
- reader.idToHash[id] = hash;
- }
- return reader.buildModuleP(id, hash, source);
- });
- }),
- buildModuleP: util.cachedMethod(function(id, hash, source) {
- var reader = this;
- return reader.processOutputP(
- id, hash, source
- ).then(function(output) {
- return new Module(reader, id, hash, output);
- });
- }, function(id, hash, source) {
- return hash;
- }),
- processOutputP: function(id, hash, source) {
- var reader = this;
- var cacheDir = reader.context.cacheDir;
- var manifestDir = cacheDir && path.join(cacheDir, "manifest");
- var charset = reader.context.options.outputCharset;
- function buildP() {
- var promise = Q(source);
- reader.processors.forEach(function(build) {
- promise = promise.then(function(input) {
- return util.waitForValuesP(
- build.call(reader.context, id, input)
- );
- });
- });
- return promise.then(function(output) {
- if (typeof output === "string") {
- output = { ".js": output };
- } else {
- assert.strictEqual(typeof output, "object");
- }
- return util.waitForValuesP(output);
- }).then(function(output) {
- util.log.err(
- "built Module(" + JSON.stringify(id) + ")",
- "cyan"
- );
- return output;
- }).catch(function(err) {
- // Provide additional context for uncaught build errors.
- util.log.err("Error while reading module " + id + ":");
- throw err;
- });
- }
- if (manifestDir) {
- return util.mkdirP(manifestDir).then(function(manifestDir) {
- var manifestFile = path.join(manifestDir, hash + ".json");
- return util.readJsonFileP(manifestFile).then(function(manifest) {
- Object.keys(manifest).forEach(function(key) {
- var cacheFile = path.join(cacheDir, manifest[key]);
- manifest[key] = util.readFileP(cacheFile);
- });
- return util.waitForValuesP(manifest, true);
- }).catch(function(err) {
- return buildP().then(function(output) {
- var manifest = {};
- Object.keys(output).forEach(function(key) {
- var cacheFile = manifest[key] = hash + key;
- var fullPath = path.join(cacheDir, cacheFile);
- if (charset) {
- fs.writeFileSync(fullPath, iconv.encode(output[key], charset))
- } else {
- fs.writeFileSync(fullPath, output[key], "utf8");
- }
- });
- fs.writeFileSync(
- manifestFile,
- JSON.stringify(manifest),
- "utf8"
- );
- return output;
- });
- });
- });
- }
- return buildP();
- },
- readMultiP: function(ids) {
- var reader = this;
- return Q(ids).all().then(function(ids) {
- if (ids.length === 0)
- return ids; // Shortcut.
- var modulePs = ids.map(reader.readModuleP, reader);
- return Q(modulePs).all().then(function(modules) {
- var seen = {};
- var result = [];
- modules.forEach(function(module) {
- if (!seen.hasOwnProperty(module.id)) {
- seen[module.id] = true;
- result.push(module);
- }
- });
- return result;
- });
- });
- }
- };
- exports.ModuleReader = ModuleReader;
- function warnMissingModule(id) {
- // A missing module may be a false positive and therefore does not warrant
- // a fatal error, but a warning is certainly in order.
- util.log.err(
- "unable to resolve module " + JSON.stringify(id) + "; false positive?",
- "yellow");
- // Missing modules are installed as if they existed, but it's a run-time
- // error if one is ever actually required.
- var message = "nonexistent module required: " + id;
- return "throw new Error(" + JSON.stringify(message) + ");";
- }
- function Module(reader, id, hash, output) {
- assert.ok(this instanceof Module);
- assert.ok(reader instanceof ModuleReader);
- assert.strictEqual(typeof output, "object");
- var source = output[".js"];
- assert.strictEqual(typeof source, "string");
- Object.defineProperties(this, {
- reader: { value: reader },
- id: { value: id },
- hash: { value: hash }, // TODO Remove?
- deps: { value: getRequiredIDs(id, source) },
- source: { value: source },
- output: { value: output }
- });
- }
- Module.prototype = {
- getRequiredP: function() {
- return this.reader.readMultiP(this.deps);
- },
- writeVersionP: function(outputDir) {
- var id = this.id;
- var hash = this.hash;
- var output = this.output;
- var cacheDir = this.reader.context.cacheDir;
- var charset = this.reader.context.options.outputCharset;
- return Q.all(Object.keys(output).map(function(key) {
- var outputFile = path.join(outputDir, id + key);
- function writeCopy() {
- if (charset) {
- fs.writeFileSync(outputFile, iconv.encode(output[key], charset));
- } else {
- fs.writeFileSync(outputFile, output[key], "utf8");
- }
- return outputFile;
- }
- if (cacheDir) {
- var cacheFile = path.join(cacheDir, hash + key);
- return util.linkP(cacheFile, outputFile)
- // If the hard linking fails, the cache directory
- // might be on a different device, so fall back to
- // writing a copy of the file (slightly slower).
- .catch(writeCopy);
- }
- return util.mkdirP(path.dirname(outputFile)).then(writeCopy);
- }));
- },
- toString: function() {
- return "Module(" + JSON.stringify(this.id) + ")";
- },
- resolveId: function(id) {
- return util.absolutize(this.id, id);
- }
- };
|