From 47f252bfea604e1e9090076c573cfd6b0e91a077 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 11 Feb 2018 17:57:49 -0800 Subject: [PATCH] Add support for optional dependencies (#788) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Put a `require` call inside a try…catch block, and it becomes optional. If that dependency cannot be resolved, it will throw a runtime error instead of a build time one. --- src/Bundler.js | 9 ++++++++- src/assets/JSAsset.js | 2 +- src/visitors/dependencies.js | 5 +++-- test/integration/optional-dep/index.js | 5 +++++ test/javascript.js | 21 +++++++++++++++++++++ 5 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 test/integration/optional-dep/index.js diff --git a/src/Bundler.js b/src/Bundler.js index b9ab34e9648..6c26f70e1ea 100644 --- a/src/Bundler.js +++ b/src/Bundler.js @@ -328,6 +328,10 @@ class Bundler extends EventEmitter { let thrown = err; if (thrown.message.indexOf(`Cannot find module '${dep.name}'`) === 0) { + if (dep.optional) { + return; + } + thrown.message = `Cannot resolve dependency '${dep.name}'`; // Add absolute path to the error message if the dependency specifies a relative path @@ -403,7 +407,10 @@ class Bundler extends EventEmitter { this.watch(dep.name, asset); } else { let assetDep = await this.resolveDep(asset, dep); - await this.loadAsset(assetDep); + if (assetDep) { + await this.loadAsset(assetDep); + } + return assetDep; } }) diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index abcec0e1261..1f6be95af2d 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -87,7 +87,7 @@ class JSAsset extends Asset { } collectDependencies() { - this.traverseFast(collectDependencies); + walk.ancestor(this.ast, collectDependencies, this); } async pretransform() { diff --git a/src/visitors/dependencies.js b/src/visitors/dependencies.js index b9d535c9ab1..181f55338e5 100644 --- a/src/visitors/dependencies.js +++ b/src/visitors/dependencies.js @@ -30,7 +30,7 @@ module.exports = { asset.isES6Module = true; }, - CallExpression(node, asset) { + CallExpression(node, asset, ancestors) { let {callee, arguments: args} = node; let isRequire = @@ -40,7 +40,8 @@ module.exports = { types.isStringLiteral(args[0]); if (isRequire) { - addDependency(asset, args[0]); + let optional = ancestors.some(a => types.isTryStatement(a)) || undefined; + addDependency(asset, args[0], {optional}); return; } diff --git a/test/integration/optional-dep/index.js b/test/integration/optional-dep/index.js new file mode 100644 index 00000000000..179fa6c113c --- /dev/null +++ b/test/integration/optional-dep/index.js @@ -0,0 +1,5 @@ +try { + require('optional-dep'); +} catch (err) { + module.exports = err; +} diff --git a/test/javascript.js b/test/javascript.js index 41409e08430..4c8693a760a 100644 --- a/test/javascript.js +++ b/test/javascript.js @@ -676,4 +676,25 @@ describe('javascript', function() { let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); assert(file.includes('h("div"')); }); + + it('should support optional dependencies in try...catch blocks', async function() { + let b = await bundle(__dirname + '/integration/optional-dep/index.js'); + + assertBundleTree(b, { + name: 'index.js', + assets: ['index.js'], + childBundles: [ + { + type: 'map' + } + ] + }); + + let output = run(b); + + let err = new Error('Cannot find module "optional-dep"'); + err.code = 'MODULE_NOT_FOUND'; + + assert.deepEqual(output, err); + }); });