diff --git a/README.md b/README.md index ac0bc91..3ea9d8d 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ Options: -x, --extension File extension to assume when resolving module identifiers --relativize Rewrite all module identifiers to be relative --follow-requires Scan modules for required dependencies + --ignore-dependencies Ignore modules defined as dependencies in package.json + --ignore-node-core Ignore Node's core modules ('fs', 'events', etc.) --cache-dir Alternate directory to use for disk cache --no-cache-dir Disable the disk cache --source-charset Charset of source (default: utf8) diff --git a/lib/commoner.js b/lib/commoner.js index 689a2dc..29055cc 100644 --- a/lib/commoner.js +++ b/lib/commoner.js @@ -27,6 +27,7 @@ function Commoner() { Object.defineProperties(self, { customVersion: { value: null, writable: true }, customOptions: { value: [] }, + ignorePatterns: { value: [] }, resolvers: { value: [] }, processors: { value: [] } }); @@ -69,6 +70,16 @@ Cp.process = function(processor) { return this; // For chaining. }; +// Specify specific id patterns that should be ignored and not processed +Cp.ignore = function() { + each.call(arguments, function(pattern) { + assert.ok(pattern instanceof RegExp, "ignore pattern must be a regular expression"); + this.ignorePatterns.push(pattern); + }, this); + + return this; // For chaining. +}; + Cp.buildP = function(options, roots) { var self = this; var sourceDir = options.sourceDir; @@ -119,18 +130,28 @@ Cp.buildP = function(options, roots) { context.setCacheDirectory(self.cacheDir); - context.setIgnoreDependencies(self.ignoreDependencies); + context.setFollowRequires(self.followRequires); context.setRelativize(self.relativize); context.setUseProvidesModule(self.useProvidesModule); + if (self.ignoreDependencies) { + self.ignorePatterns.push(util.makeDepsIgnorePattern()); + } + + if (self.ignoreNodeCore) { + self.ignorePatterns.push(util.makeCoreIgnorePattern()); + } + + context.setIgnorePatterns(self.ignorePatterns); + return new ModuleReader( context, self.resolvers, self.processors ).readMultiP(context.expandIdsOrGlobsP(roots)) - .then(context.ignoreDependencies ? pass : collectDepsP) + .then(context.followRequires ? collectRequiresP : pass) .then(outputModules) .then(outputDir ? printModuleIds : pass) .then(finish, function(err) { @@ -156,7 +177,7 @@ function pass(modules) { return modules; } -function collectDepsP(rootModules) { +function collectRequiresP(rootModules) { var modules = []; var seenIds = {}; @@ -212,6 +233,8 @@ function cliBuildP(commoner, version) { "File extension to assume when resolving module identifiers") .option("--relativize", "Rewrite all module identifiers to be relative") .option("--follow-requires", "Scan modules for required dependencies") + .option("--ignore-dependencies", "Ignore modules defined as dependencies in package.json") + .option("--ignore-node-core", "Ignore Node's core modules ('fs', 'events', etc.)") .option("--use-provides-module", "Respect @providesModules pragma in files") .option("--cache-dir ", "Alternate directory to use for disk cache") .option("--no-cache-dir", "Disable the disk cache") @@ -232,7 +255,9 @@ function cliBuildP(commoner, version) { // variables is preferable to passing them as arguments. commoner.preferredFileExtension = pfe; commoner.watch = options.watch; - commoner.ignoreDependencies = !options.followRequires; + commoner.followRequires = options.followRequires; + commoner.ignoreDependencies = options.ignoreDependencies; + commoner.ignoreNodeCore = options.ignoreNodeCore; commoner.relativize = options.relativize; commoner.useProvidesModule = options.useProvidesModule; commoner.sourceCharset = normalizeCharset(options.sourceCharset); @@ -258,8 +283,8 @@ function cliBuildP(commoner, version) { roots = [""]; commoner.forceResolve("", util.readFromStdinP()); - // Ignore dependencies because we wouldn't know how to find them. - commoner.ignoreDependencies = true; + // Don't follow requires because we wouldn't know how to find them. + commoner.followRequires = false; } else { var first = absolutePath(workingDir, args[0]); @@ -275,8 +300,8 @@ function cliBuildP(commoner, version) { util.readFileP(first, commoner.sourceCharset) ); - // Ignore dependencies because we wouldn't know how to find them. - commoner.ignoreDependencies = true; + // Don't follow requires because we wouldn't know how to find them. + commoner.followRequires = false; } else if (stats.isDirectory(first)) { sourceDir = first; diff --git a/lib/context.js b/lib/context.js index fabae99..6976601 100644 --- a/lib/context.js +++ b/lib/context.js @@ -82,14 +82,14 @@ BCp.spawnP = function(command, args, kwargs) { return deferred.promise; }; -BCp.setIgnoreDependencies = function(value) { - Object.defineProperty(this, "ignoreDependencies", { +BCp.setFollowRequires = function(value) { + Object.defineProperty(this, "followRequires", { value: !!value }); }; // This default can be overridden by individual BuildContext instances. -BCp.setIgnoreDependencies(false); +BCp.setFollowRequires(true); BCp.setRelativize = function(value) { Object.defineProperty(this, "relativize", { @@ -109,6 +109,15 @@ BCp.setUseProvidesModule = function(value) { // This default can be overridden by individual BuildContext instances. BCp.setUseProvidesModule(false); +BCp.setIgnorePatterns = function(ignorePatterns) { + Object.defineProperty(this, "ignorePatterns", { + value: ignorePatterns || [] + }); +}; + +// This default can be overridden by individual BuildContext instances. +BCp.setIgnorePatterns([]); + BCp.setCacheDirectory = function(dir) { if (!dir) { // Disable the cache directory. diff --git a/lib/reader.js b/lib/reader.js index bea6500..fb457c6 100644 --- a/lib/reader.js +++ b/lib/reader.js @@ -34,7 +34,7 @@ function ModuleReader(context, resolvers, processors) { resolvers = hashCallbacks("resolvers", resolvers, warnMissingModule); var procArgs = [processors]; - if (context.relativize && !context.ignoreDependencies) + if (context.relativize && context.followRequires) procArgs.push(require("./relative").getProcessor(self)); processors = hashCallbacks("processors", procArgs); @@ -215,11 +215,24 @@ ModuleReader.prototype = { readMultiP: function(ids) { var reader = this; + var ignorePatterns = reader.context.ignorePatterns; return Q(ids).all().then(function(ids) { if (ids.length === 0) return ids; // Shortcut. + if (ignorePatterns.length) { + ids = ids.filter(function(id) { + for (var i = 0; i < ignorePatterns.length; i++) { + if (ignorePatterns[i].test(id)) { + return false; + } + } + + return true; + }); + } + var modulePs = ids.map(reader.readModuleP, reader); return Q(modulePs).all().then(function(modules) { var seen = {}; diff --git a/lib/relative.js b/lib/relative.js index 992dfec..645231b 100644 --- a/lib/relative.js +++ b/lib/relative.js @@ -78,6 +78,14 @@ Rp.absolutizeP = function(moduleId, requiredId) { }; Rp.relativizeP = function(moduleId, requiredId) { + var ignorePatterns = this.reader && this.reader.context.ignorePatterns || []; + + for (var i = 0; i < ignorePatterns.length; i++) { + if (ignorePatterns[i].test(requiredId)) { + return Q(requiredId); + } + } + return this.absolutizeP( moduleId, requiredId diff --git a/lib/util.js b/lib/util.js index c6070e9..cb4c0b3 100644 --- a/lib/util.js +++ b/lib/util.js @@ -5,6 +5,7 @@ var Q = require("q"); var createHash = require("crypto").createHash; var mkdirp = require("mkdirp"); var iconv = require("iconv-lite"); +var findup = require('findup-sync'); var Ap = Array.prototype; var slice = Ap.slice; var join = Ap.join; @@ -368,3 +369,21 @@ function camelize(hyphenated) { }); } exports.camelize = camelize; + +function makeIgnorePattern(modules) { + return new RegExp("^(" + modules.join('|') + ")(\\/|$)"); +} +exports.makeIgnorePattern = makeIgnorePattern; + +function makeDepsIgnorePattern() { + var packageInfo = require(findup("package.json")); + var deps = Object.keys(packageInfo.dependencies || {}); + var devDeps = Object.keys(packageInfo.devDependencies || {}); + return makeIgnorePattern(deps.concat(devDeps)); +} +exports.makeDepsIgnorePattern = makeDepsIgnorePattern; + +function makeCoreIgnorePattern() { + return makeIgnorePattern(require("repl")._builtinLibs); +} +exports.makeCoreIgnorePattern = makeCoreIgnorePattern; diff --git a/main.js b/main.js index bf9adca..07a7be6 100644 --- a/main.js +++ b/main.js @@ -14,3 +14,4 @@ function defCallback(name) { defCallback("version"); defCallback("resolve"); defCallback("process"); +defCallback("ignore"); diff --git a/package.json b/package.json index 5026f5d..26e99fa 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ "mkdirp": "~0.5.0", "private": "~0.1.6", "install": "~0.1.7", - "iconv-lite": "~0.4.5" + "iconv-lite": "~0.4.5", + "findup-sync": "~0.2.1" }, "devDependencies": { "mocha": "~2.0.1" diff --git a/test/run.js b/test/run.js index 27214d0..49dc0a2 100644 --- a/test/run.js +++ b/test/run.js @@ -29,6 +29,7 @@ function getNewContext(options) { context.setCacheDirectory(path.join(outputDir, options.cacheDirectory)); context.setRelativize(options.relative === undefined || options.relative); context.setUseProvidesModule(options.useProvidesModule === undefined || options.useProvidesModule ); + context.setIgnorePatterns(options.ignorePatterns || [ /ignored/, /^react[\/$]/, util.makeDepsIgnorePattern(), util.makeCoreIgnorePattern() ]); return context; } @@ -76,11 +77,17 @@ function checkHome(assert, home) { assert.strictEqual(home.id, "home"); assert.strictEqual(typeof home.source, "string"); assert.notEqual(home.source.indexOf("exports"), -1); - assert.strictEqual(home.source.indexOf('require("./assert");'), 0); + assert.strictEqual(home.source.indexOf('require("./myassert");'), 0); + assert.notEqual(home.source.indexOf('require("ignored-module");'), -1); + assert.notEqual(home.source.indexOf('require("react/addons");'), -1); + assert.notEqual(home.source.indexOf('require("recast");'), -1); + assert.notEqual(home.source.indexOf('require("recast/lib/types");'), -1); + assert.notEqual(home.source.indexOf('require("mocha");'), -1); + assert.notEqual(home.source.indexOf('require("fs");'), -1); return home; }).invoke("getRequiredP").then(function(reqs) { assert.strictEqual(reqs.length, 1); - assert.strictEqual(reqs[0].id, "assert"); + assert.strictEqual(reqs[0].id, "myassert"); }); } @@ -401,7 +408,7 @@ describe("canonical module identifiers", function() { it("should replace non-canonical required identifiers", function(done) { function helperP(context) { - assert.strictEqual(context.ignoreDependencies, false); + assert.strictEqual(context.followRequires, true); var reader = new ModuleReader(context, [ getProvidedP, @@ -422,7 +429,7 @@ describe("canonical module identifiers", function() { ), 2); assert.strictEqual(strCount( - 'require("../assert")', + 'require("../myassert")', follow.source ), 2); }); diff --git a/test/source/assert.js b/test/source/assert.js deleted file mode 100644 index 781b6a3..0000000 --- a/test/source/assert.js +++ /dev/null @@ -1,7 +0,0 @@ -function assert(test, msg) { - if (!test) { - throw new Error(msg); - } -} - -module.exports = assert.ok = assert; diff --git a/test/source/home.js b/test/source/home.js index 49e1089..d4a04f8 100644 --- a/test/source/home.js +++ b/test/source/home.js @@ -1,5 +1,14 @@ -require("assert"); -require("./assert"); -require("./tests/../assert"); +require("myassert"); +require("./myassert"); +require("./tests/../myassert"); + +require("ignored-module"); +require("react/addons"); // also ignored + +require("recast"); // defined as dependency in package.json +require("recast/lib/types"); +require("mocha"); // a dev dependency + +require("fs"); // node built-in exports.name = "home"; diff --git a/test/source/myassert.js b/test/source/myassert.js new file mode 100644 index 0000000..0a1a2e7 --- /dev/null +++ b/test/source/myassert.js @@ -0,0 +1,7 @@ +function myassert(test, msg) { + if (!test) { + throw new Error(msg); + } +} + +module.exports = myassert.ok = myassert; diff --git a/test/source/widget/follow.js b/test/source/widget/follow.js index 95b2ae7..c384b12 100644 --- a/test/source/widget/follow.js +++ b/test/source/widget/follow.js @@ -9,9 +9,9 @@ require("../WidgetShare"); require("../widget/gallery"); require("./gallery"); -// These both become "../assert". -require("assert"); -require("../assert"); +// These both become "../myassert". +require("myassert"); +require("../myassert"); // These circular references should both become "./follow". require("./follow");