react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / react-tools / node_modules / commoner / lib / commoner.js
81159 viewsvar assert = require("assert");1var path = require("path");2var fs = require("fs");3var Q = require("q");4var iconv = require("iconv-lite");5var ReadFileCache = require("./cache").ReadFileCache;6var Watcher = require("./watcher").Watcher;7var contextModule = require("./context");8var BuildContext = contextModule.BuildContext;9var PreferredFileExtension = contextModule.PreferredFileExtension;10var ModuleReader = require("./reader").ModuleReader;11var output = require("./output");12var DirOutput = output.DirOutput;13var StdOutput = output.StdOutput;14var util = require("./util");15var log = util.log;16var Ap = Array.prototype;17var each = Ap.forEach;1819// Better stack traces for promises.20Q.longStackSupport = true;2122function Commoner() {23var self = this;24assert.ok(self instanceof Commoner);2526Object.defineProperties(self, {27customVersion: { value: null, writable: true },28customOptions: { value: [] },29resolvers: { value: [] },30processors: { value: [] }31});32}3334var Cp = Commoner.prototype;3536Cp.version = function(version) {37this.customVersion = version;38return this; // For chaining.39};4041// Add custom command line options42Cp.option = function() {43this.customOptions.push(Ap.slice.call(arguments));44return this; // For chaining.45};4647// A resolver is a function that takes a module identifier and returns48// the unmodified source of the corresponding module, either as a string49// or as a promise for a string.50Cp.resolve = function() {51each.call(arguments, function(resolver) {52assert.strictEqual(typeof resolver, "function");53this.resolvers.push(resolver);54}, this);5556return this; // For chaining.57};5859// A processor is a function that takes a module identifier and a string60// representing the source of the module and returns a modified version of61// the source, either as a string or as a promise for a string.62Cp.process = function(processor) {63each.call(arguments, function(processor) {64assert.strictEqual(typeof processor, "function");65this.processors.push(processor);66}, this);6768return this; // For chaining.69};7071Cp.buildP = function(options, roots) {72var self = this;73var sourceDir = options.sourceDir;74var outputDir = options.outputDir;75var readFileCache = new ReadFileCache(sourceDir, options.sourceCharset);76var waiting = 0;77var output = outputDir78? new DirOutput(outputDir)79: new StdOutput;8081if (self.watch) {82new Watcher(readFileCache).on("changed", function(file) {83log.err(file + " changed; rebuilding...", "yellow");84rebuild();85});86}8788function outputModules(modules) {89// Note that output.outputModules comes pre-bound.90modules.forEach(output.outputModule);91return modules;92}9394function finish(result) {95rebuild.ing = false;9697if (waiting > 0) {98waiting = 0;99process.nextTick(rebuild);100}101102return result;103}104105function rebuild() {106if (rebuild.ing) {107waiting += 1;108return;109}110111rebuild.ing = true;112113var context = new BuildContext(options, readFileCache);114115if (self.preferredFileExtension)116context.setPreferredFileExtension(117self.preferredFileExtension);118119context.setCacheDirectory(self.cacheDir);120121context.setIgnoreDependencies(self.ignoreDependencies);122123context.setRelativize(self.relativize);124125context.setUseProvidesModule(self.useProvidesModule);126127return new ModuleReader(128context,129self.resolvers,130self.processors131).readMultiP(context.expandIdsOrGlobsP(roots))132.then(context.ignoreDependencies ? pass : collectDepsP)133.then(outputModules)134.then(outputDir ? printModuleIds : pass)135.then(finish, function(err) {136log.err(err.stack);137138if (!self.watch) {139// If we're not building with --watch, throw the error140// so that cliBuildP can call process.exit(-1).141throw err;142}143144finish();145});146}147148return (149// If outputDir is falsy, we can't (and don't need to) mkdirP it.150outputDir ? util.mkdirP : Q151)(outputDir).then(rebuild);152};153154function pass(modules) {155return modules;156}157158function collectDepsP(rootModules) {159var modules = [];160var seenIds = {};161162function traverse(module) {163if (seenIds.hasOwnProperty(module.id))164return Q(modules);165seenIds[module.id] = true;166167return module.getRequiredP().then(function(reqs) {168return Q.all(reqs.map(traverse));169}).then(function() {170modules.push(module);171return modules;172});173}174175return Q.all(rootModules.map(traverse)).then(176function() { return modules });177}178179function printModuleIds(modules) {180log.out(JSON.stringify(modules.map(function(module) {181return module.id;182})));183184return modules;185}186187Cp.forceResolve = function(forceId, source) {188this.resolvers.unshift(function(id) {189if (id === forceId)190return source;191});192};193194Cp.cliBuildP = function() {195var version = this.customVersion || require("../package.json").version;196return Q.spread([this, version], cliBuildP);197};198199function cliBuildP(commoner, version) {200var options = require("commander");201var workingDir = process.cwd();202var sourceDir = workingDir;203var outputDir = null;204var roots;205206options.version(version)207.usage("[options] <source directory> <output directory> [<module ID> [<module ID> ...]]")208.option("-c, --config [file]", "JSON configuration file (no file or - means STDIN)")209.option("-w, --watch", "Continually rebuild")210.option("-x, --extension <js | coffee | ...>",211"File extension to assume when resolving module identifiers")212.option("--relativize", "Rewrite all module identifiers to be relative")213.option("--follow-requires", "Scan modules for required dependencies")214.option("--use-provides-module", "Respect @providesModules pragma in files")215.option("--cache-dir <directory>", "Alternate directory to use for disk cache")216.option("--no-cache-dir", "Disable the disk cache")217.option("--source-charset <utf8 | win1252 | ...>",218"Charset of source (default: utf8)")219.option("--output-charset <utf8 | win1252 | ...>",220"Charset of output (default: utf8)");221222commoner.customOptions.forEach(function(customOption) {223options.option.apply(options, customOption);224});225226options.parse(process.argv.slice(0));227228var pfe = new PreferredFileExtension(options.extension || "js");229230// TODO Decide whether passing options to buildP via instance231// variables is preferable to passing them as arguments.232commoner.preferredFileExtension = pfe;233commoner.watch = options.watch;234commoner.ignoreDependencies = !options.followRequires;235commoner.relativize = options.relativize;236commoner.useProvidesModule = options.useProvidesModule;237commoner.sourceCharset = normalizeCharset(options.sourceCharset);238commoner.outputCharset = normalizeCharset(options.outputCharset);239240function fileToId(file) {241file = absolutePath(workingDir, file);242assert.ok(fs.statSync(file).isFile(), file);243return pfe.trim(path.relative(sourceDir, file));244}245246var args = options.args.slice(0);247var argc = args.length;248if (argc === 0) {249if (options.config === true) {250log.err("Cannot read --config from STDIN when reading " +251"source from STDIN");252process.exit(-1);253}254255sourceDir = workingDir;256outputDir = null;257roots = ["<stdin>"];258commoner.forceResolve("<stdin>", util.readFromStdinP());259260// Ignore dependencies because we wouldn't know how to find them.261commoner.ignoreDependencies = true;262263} else {264var first = absolutePath(workingDir, args[0]);265var stats = fs.statSync(first);266267if (argc === 1) {268var firstId = fileToId(first);269sourceDir = workingDir;270outputDir = null;271roots = [firstId];272commoner.forceResolve(273firstId,274util.readFileP(first, commoner.sourceCharset)275);276277// Ignore dependencies because we wouldn't know how to find them.278commoner.ignoreDependencies = true;279280} else if (stats.isDirectory(first)) {281sourceDir = first;282outputDir = absolutePath(workingDir, args[1]);283roots = args.slice(2);284if (roots.length === 0)285roots.push(commoner.preferredFileExtension.glob());286287} else {288options.help();289process.exit(-1);290}291}292293commoner.cacheDir = null;294if (options.cacheDir === false) {295// Received the --no-cache-dir option, so disable the disk cache.296} else if (typeof options.cacheDir === "string") {297commoner.cacheDir = absolutePath(workingDir, options.cacheDir);298} else if (outputDir) {299// The default cache directory lives inside the output directory.300commoner.cacheDir = path.join(outputDir, ".module-cache");301}302303var promise = getConfigP(304workingDir,305options.config306).then(function(config) {307var cleanOptions = {};308309options.options.forEach(function(option) {310var name = util.camelize(option.name());311if (options.hasOwnProperty(name)) {312cleanOptions[name] = options[name];313}314});315316cleanOptions.version = version;317cleanOptions.config = config;318cleanOptions.sourceDir = sourceDir;319cleanOptions.outputDir = outputDir;320cleanOptions.sourceCharset = commoner.sourceCharset;321cleanOptions.outputCharset = commoner.outputCharset;322323return commoner.buildP(cleanOptions, roots);324});325326if (!commoner.watch) {327// If we're building from the command line without --watch, any328// build errors should immediately terminate the process with a329// non-zero error code.330promise = promise.catch(function(err) {331log.err(err.stack);332process.exit(-1);333});334}335336return promise;337}338339function normalizeCharset(charset) {340charset = charset341&& charset.replace(/[- ]/g, "").toLowerCase()342|| "utf8";343344assert.ok(345iconv.encodingExists(charset),346"Unrecognized charset: " + charset347);348349return charset;350}351352function absolutePath(workingDir, pathToJoin) {353if (pathToJoin) {354workingDir = path.normalize(workingDir);355pathToJoin = path.normalize(pathToJoin);356// TODO: use path.isAbsolute when Node < 0.10 is unsupported357if (path.resolve(pathToJoin) !== pathToJoin) {358pathToJoin = path.join(workingDir, pathToJoin);359}360}361return pathToJoin;362}363364function getConfigP(workingDir, configFile) {365if (typeof configFile === "undefined")366return Q({}); // Empty config.367368if (configFile === true || // --config is present but has no argument369configFile === "<stdin>" ||370configFile === "-" ||371configFile === path.sep + path.join("dev", "stdin")) {372return util.readJsonFromStdinP(3731000, // Time limit in milliseconds before warning displayed.374"Expecting configuration from STDIN (pass --config <file> " +375"if stuck here)...",376"yellow"377);378}379380return util.readJsonFileP(absolutePath(workingDir, configFile));381}382383exports.Commoner = Commoner;384385386