diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index 683248c5b96..0bdb64a9260 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -15,6 +15,7 @@ const config = require('../utils/config'); const IMPORT_RE = /\b(?:import\b|export\b|require\s*\()/; const GLOBAL_RE = /\b(?:process|__dirname|__filename|global|Buffer)\b/; const FS_RE = /\breadFileSync\b/; +const SW_RE = /\bnavigator\s*\.\s*serviceWorker\s*\.\s*register\s*\(/; class JSAsset extends Asset { constructor(name, pkg, options) { @@ -30,7 +31,8 @@ class JSAsset extends Asset { return ( !/.js$/.test(this.name) || IMPORT_RE.test(this.contents) || - GLOBAL_RE.test(this.contents) + GLOBAL_RE.test(this.contents) || + SW_RE.test(this.contents) ); } diff --git a/src/visitors/dependencies.js b/src/visitors/dependencies.js index e1a0609a299..711e4b7c0c1 100644 --- a/src/visitors/dependencies.js +++ b/src/visitors/dependencies.js @@ -1,8 +1,12 @@ const types = require('babel-types'); const template = require('babel-template'); +const urlJoin = require('../utils/urlJoin'); +const isURL = require('../utils/is-url'); +const matchesPattern = require('./matches-pattern'); const requireTemplate = template('require("_bundle_loader")'); const argTemplate = template('require.resolve(MODULE)'); +const serviceWorkerPattern = ['navigator', 'serviceWorker', 'register']; module.exports = { ImportDeclaration(node, asset) { @@ -37,6 +41,7 @@ module.exports = { if (isRequire) { addDependency(asset, args[0]); + return; } let isDynamicImport = @@ -51,6 +56,21 @@ module.exports = { node.callee = requireTemplate().expression; node.arguments[0] = argTemplate({MODULE: args[0]}).expression; asset.isAstDirty = true; + return; + } + + const isRegisterServiceWorker = + types.isStringLiteral(args[0]) && + matchesPattern(callee, serviceWorkerPattern); + + if (isRegisterServiceWorker) { + let assetPath = asset.addURLDependency(args[0].value); + if (!isURL(assetPath)) { + assetPath = urlJoin(asset.options.publicURL, assetPath); + } + args[0].value = assetPath; + asset.isAstDirty = true; + return; } } }; diff --git a/src/visitors/globals.js b/src/visitors/globals.js index 7646a845f70..2bb8ca701fa 100644 --- a/src/visitors/globals.js +++ b/src/visitors/globals.js @@ -1,5 +1,6 @@ const Path = require('path'); const types = require('babel-types'); +const matchesPattern = require('./matches-pattern'); const VARS = { process: asset => { @@ -62,47 +63,6 @@ function inScope(ancestors) { return false; } -// from babel-types. remove when we upgrade to babel 7. -// https://github.com/babel/babel/blob/0189b387026c35472dccf45d14d58312d249f799/packages/babel-types/src/index.js#L347 -function matchesPattern(member, match) { - // not a member expression - if (!types.isMemberExpression(member)) { - return false; - } - - const parts = Array.isArray(match) ? match : match.split('.'); - const nodes = []; - - let node; - for (node = member; types.isMemberExpression(node); node = node.object) { - nodes.push(node.property); - } - - nodes.push(node); - - if (nodes.length !== parts.length) { - return false; - } - - for (let i = 0, j = nodes.length - 1; i < parts.length; i++, j--) { - const node = nodes[j]; - let value; - if (types.isIdentifier(node)) { - value = node.name; - } else if (types.isStringLiteral(node)) { - value = node.value; - } else { - return false; - } - - if (parts[i] !== value) { - return false; - } - } - - return true; -} - // replace object properties function morph(object, newProperties) { for (let key in object) { diff --git a/src/visitors/matches-pattern.js b/src/visitors/matches-pattern.js new file mode 100644 index 00000000000..e1dbbb113e5 --- /dev/null +++ b/src/visitors/matches-pattern.js @@ -0,0 +1,36 @@ +const types = require('babel-types'); + +// from babel-types. remove when we upgrade to babel 7. +// https://github.com/babel/babel/blob/0189b387026c35472dccf45d14d58312d249f799/packages/babel-types/src/index.js#L347 +module.exports = function matchesPattern(member, match, allowPartial) { + // not a member expression + if (!types.isMemberExpression(member)) return false; + + const parts = Array.isArray(match) ? match : match.split('.'); + const nodes = []; + + let node; + for (node = member; types.isMemberExpression(node); node = node.object) { + nodes.push(node.property); + } + nodes.push(node); + + if (nodes.length < parts.length) return false; + if (!allowPartial && nodes.length > parts.length) return false; + + for (let i = 0, j = nodes.length - 1; i < parts.length; i++, j--) { + const node = nodes[j]; + let value; + if (types.isIdentifier(node)) { + value = node.name; + } else if (types.isStringLiteral(node)) { + value = node.value; + } else { + return false; + } + + if (parts[i] !== value) return false; + } + + return true; +}; diff --git a/test/integration/service-worker/index.js b/test/integration/service-worker/index.js new file mode 100644 index 00000000000..a06a9c931a8 --- /dev/null +++ b/test/integration/service-worker/index.js @@ -0,0 +1 @@ +navigator.serviceWorker.register('worker.js', { scope: './' }); diff --git a/test/integration/service-worker/worker.js b/test/integration/service-worker/worker.js new file mode 100644 index 00000000000..d0965140fe7 --- /dev/null +++ b/test/integration/service-worker/worker.js @@ -0,0 +1 @@ +self.addEventListener('message', () => {}); diff --git a/test/javascript.js b/test/javascript.js index f69b5a4d4f0..8dbf91faa96 100644 --- a/test/javascript.js +++ b/test/javascript.js @@ -58,6 +58,21 @@ describe('javascript', function() { assert.equal(await output(), 3); }); + it('should support bundling service workers', async function() { + let b = await bundle(__dirname + '/integration/service-worker/index.js'); + + assertBundleTree(b, { + name: 'index.js', + assets: ['index.js'], + childBundles: [ + { + assets: ['worker.js'], + childBundles: [] + } + ] + }); + }); + it('should dynamic import files which import raw files', async function() { let b = await bundle( __dirname + '/integration/dynamic-references-raw/index.js'