From aae6b5a64eaf4cac331c4389d461a9f8a29895ef Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Fri, 9 Feb 2018 12:11:36 -0500 Subject: [PATCH 1/5] Support for importWorkboxFrom: 'local' in the InjectManifest plugin --- .../options/get-manifest-schema.js | 2 +- .../src/inject-manifest.js | 20 ++- .../node/generate-sw.js | 10 +- .../node/inject-manifest.js | 162 ++++++++++++++---- 4 files changed, 148 insertions(+), 46 deletions(-) diff --git a/packages/workbox-build/src/entry-points/options/get-manifest-schema.js b/packages/workbox-build/src/entry-points/options/get-manifest-schema.js index 4f54e17a6..31704c8ea 100644 --- a/packages/workbox-build/src/entry-points/options/get-manifest-schema.js +++ b/packages/workbox-build/src/entry-points/options/get-manifest-schema.js @@ -20,5 +20,5 @@ const baseSchema = require('./base-schema'); // Define some additional constraints. module.exports = baseSchema.keys({ - globDirectory: joi.string().required(), + globDirectory: joi.string(), }); diff --git a/packages/workbox-webpack-plugin/src/inject-manifest.js b/packages/workbox-webpack-plugin/src/inject-manifest.js index 6570a544f..55a9c0f67 100644 --- a/packages/workbox-webpack-plugin/src/inject-manifest.js +++ b/packages/workbox-webpack-plugin/src/inject-manifest.js @@ -72,13 +72,16 @@ class InjectManifest { * @private */ async handleEmit(compilation, readFile) { - if (this.config.importWorkboxFrom === 'local') { - throw new Error(`importWorkboxFrom can not be set to 'local' when using` + - ` InjectManifest. Please use 'cdn' or a chunk name instead.`); - } - const workboxSWImports = await getWorkboxSWImports( compilation, this.config); + + // this.config.modulePathPrefix may or may not have been set by + // getWorkboxSWImports(), depending on the other config options. If it was + // set, we need to pull it out and make use of it later, as it can't be + // used by the underlying workbox-build getManifest() method. + const modulePathPrefix = this.config.modulePathPrefix; + delete this.config.modulePathPrefix; + let entries = getManifestEntriesFromCompilation(compilation, this.config); const importScriptsArray = [].concat(this.config.importScripts); @@ -113,8 +116,13 @@ class InjectManifest { .map(JSON.stringify) .join(', '); - const postInjectionSWString = `importScripts(${importScriptsString}); + const setConfigString = modulePathPrefix + ? `workbox.setConfig({modulePathPrefix: ` + + `${JSON.stringify(modulePathPrefix)}});` + : ''; + const postInjectionSWString = `importScripts(${importScriptsString}); +${setConfigString} ${originalSWString} `; diff --git a/test/workbox-webpack-plugin/node/generate-sw.js b/test/workbox-webpack-plugin/node/generate-sw.js index c055ab51b..9383b8f11 100644 --- a/test/workbox-webpack-plugin/node/generate-sw.js +++ b/test/workbox-webpack-plugin/node/generate-sw.js @@ -80,10 +80,13 @@ describe(`[workbox-webpack-plugin] GenerateSW (End to End)`, function() { const compiler = webpack(config); compiler.run((webpackError) => { if (webpackError) { - expect(webpackError.message.includes('importWorkboxFrom')); - done(); + if (webpackError.message.includes('importWorkboxFrom')) { + done(); + } else { + done(new Error(`An unexpected error was thrown: ${webpackError.message}`)); + } } else { - done('Unexpected success.'); + done(new Error('Unexpected success.')); } }); }); @@ -304,7 +307,6 @@ describe(`[workbox-webpack-plugin] GenerateSW (End to End)`, function() { const basenames = libraryFiles.map((file) => path.basename(file)); expect(basenames).to.eql(ALL_WORKBOX_FILES); - // The correct importScripts path should use the versioned name of the // parent workbox libraries directory. We don't know that version ahead // of time, so we ensure that there's a match based on what actually diff --git a/test/workbox-webpack-plugin/node/inject-manifest.js b/test/workbox-webpack-plugin/node/inject-manifest.js index 7952a68ae..37859ddbf 100644 --- a/test/workbox-webpack-plugin/node/inject-manifest.js +++ b/test/workbox-webpack-plugin/node/inject-manifest.js @@ -3,6 +3,7 @@ const GenerateAssetPlugin = require('generate-asset-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const expect = require('chai').expect; const fse = require('fs-extra'); +const glob = require('glob'); const path = require('path'); const tempy = require('tempy'); const vm = require('vm'); @@ -17,6 +18,47 @@ describe(`[workbox-webpack-plugin] InjectManifest (End to End)`, function() { const WORKBOX_SW_FILE_NAME = getModuleUrl('workbox-sw'); const SRC_DIR = path.join(__dirname, '..', 'static', 'example-project-1'); const SW_SRC = path.join(__dirname, '..', 'static', 'sw-src.js'); + const WORKBOX_DIRECTORY_PREFIX = 'workbox-'; + const ALL_WORKBOX_FILES = [ + 'workbox-background-sync.dev.js', + 'workbox-background-sync.dev.js.map', + 'workbox-background-sync.prod.js', + 'workbox-background-sync.prod.js.map', + 'workbox-broadcast-cache-update.dev.js', + 'workbox-broadcast-cache-update.dev.js.map', + 'workbox-broadcast-cache-update.prod.js', + 'workbox-broadcast-cache-update.prod.js.map', + 'workbox-cache-expiration.dev.js', + 'workbox-cache-expiration.dev.js.map', + 'workbox-cache-expiration.prod.js', + 'workbox-cache-expiration.prod.js.map', + 'workbox-cacheable-response.dev.js', + 'workbox-cacheable-response.dev.js.map', + 'workbox-cacheable-response.prod.js', + 'workbox-cacheable-response.prod.js.map', + 'workbox-core.dev.js', + 'workbox-core.dev.js.map', + 'workbox-core.prod.js', + 'workbox-core.prod.js.map', + 'workbox-google-analytics.dev.js', + 'workbox-google-analytics.dev.js.map', + 'workbox-google-analytics.prod.js', + 'workbox-google-analytics.prod.js.map', + 'workbox-precaching.dev.js', + 'workbox-precaching.dev.js.map', + 'workbox-precaching.prod.js', + 'workbox-precaching.prod.js.map', + 'workbox-routing.dev.js', + 'workbox-routing.dev.js.map', + 'workbox-routing.prod.js', + 'workbox-routing.prod.js.map', + 'workbox-strategies.dev.js', + 'workbox-strategies.dev.js.map', + 'workbox-strategies.prod.js', + 'workbox-strategies.prod.js.map', + 'workbox-sw.js', + 'workbox-sw.js.map', + ]; describe(`[workbox-webpack-plugin] runtime errors`, function() { it(`should throw when swSrc is not set`, function() { @@ -44,39 +86,13 @@ describe(`[workbox-webpack-plugin] InjectManifest (End to End)`, function() { const compiler = webpack(config); compiler.run((webpackError) => { if (webpackError) { - expect(webpackError.message.includes('importWorkboxFrom')); - done(); - } else { - done('Unexpected success.'); - } - }); - }); - - it(`should throw when importWorkboxFrom is set to 'local'`, function(done) { - const outputDir = tempy.directory(); - const config = { - entry: { - entry1: path.join(SRC_DIR, WEBPACK_ENTRY_FILENAME), - }, - output: { - filename: '[name]-[chunkhash].js', - path: outputDir, - }, - plugins: [ - new InjectManifest({ - importWorkboxFrom: 'local', - swSrc: SW_SRC, - }), - ], - }; - - const compiler = webpack(config); - compiler.run((webpackError) => { - if (webpackError) { - expect(webpackError.message.includes('importWorkboxFrom')); - done(); + if (webpackError.message.includes('importWorkboxFrom')) { + done(); + } else { + done(new Error(`An unexpected error was thrown: ${webpackError.message}`)); + } } else { - done('Unexpected success.'); + done(new Error('Unexpected success.')); } }); }); @@ -240,12 +256,88 @@ describe(`[workbox-webpack-plugin] InjectManifest (End to End)`, function() { const swFile = path.join(outputDir, path.basename(SW_SRC)); try { + // First, validate that the generated service-worker.js meets some basic assumptions. + await validateServiceWorkerRuntime({swFile, expectedMethodCalls: { + importScripts: [[ + FILE_MANIFEST_NAME, + workboxEntryName, + ]], + suppressWarnings: [[]], + precacheAndRoute: [[[], {}]], + }}); + + // Next, test the generated manifest to ensure that it contains + // exactly the entries that we expect. + const manifestFileContents = await fse.readFile(path.join(outputDir, FILE_MANIFEST_NAME), 'utf-8'); + const context = {self: {}}; + vm.runInNewContext(manifestFileContents, context); + + const expectedEntries = [{ + url: 'entry2-0c3c00f8cd0d3271089c.js', + }, { + url: 'entry1-3865b3908d1988da1758.js', + }]; + expect(context.self.__precacheManifest).to.eql(expectedEntries); + + done(); + } catch (error) { + done(error); + } + }); + }); + + it(`should support setting importWorkboxFrom to 'local'`, function(done) { + const FILE_MANIFEST_NAME = 'precache-manifest.b6f6b1b151c4f027ee1e1aa3061eeaf7.js'; + const outputDir = tempy.directory(); + console.log({outputDir}); + const config = { + entry: { + entry1: path.join(SRC_DIR, WEBPACK_ENTRY_FILENAME), + entry2: path.join(SRC_DIR, WEBPACK_ENTRY_FILENAME), + }, + output: { + filename: '[name]-[chunkhash].js', + path: outputDir, + }, + plugins: [ + new InjectManifest({ + importWorkboxFrom: 'local', + swSrc: SW_SRC, + }), + ], + }; + + const compiler = webpack(config); + compiler.run(async (webpackError) => { + if (webpackError) { + return done(webpackError); + } + + const swFile = path.join(outputDir, path.basename(SW_SRC)); + try { + // Validate the copied library files. + const libraryFiles = glob.sync(`${WORKBOX_DIRECTORY_PREFIX}*/*.js*`, + {cwd: outputDir}); + + const modulePathPrefix = path.dirname(libraryFiles[0]); + + const basenames = libraryFiles.map((file) => path.basename(file)); + expect(basenames).to.eql(ALL_WORKBOX_FILES); + + // The correct importScripts path should use the versioned name of the + // parent workbox libraries directory. We don't know that version ahead + // of time, so we ensure that there's a match based on what actually + // got copied over. + const workboxSWImport = libraryFiles.filter( + (file) => file.endsWith('workbox-sw.js'))[0]; + // First, validate that the generated service-worker.js meets some basic assumptions. await validateServiceWorkerRuntime({swFile, expectedMethodCalls: { importScripts: [[ FILE_MANIFEST_NAME, - workboxEntryName, + workboxSWImport, ]], + setConfig: [[{modulePathPrefix}]], suppressWarnings: [[]], precacheAndRoute: [[[], {}]], }}); @@ -257,9 +349,9 @@ describe(`[workbox-webpack-plugin] InjectManifest (End to End)`, function() { vm.runInNewContext(manifestFileContents, context); const expectedEntries = [{ - url: 'entry2-0c3c00f8cd0d3271089c.js', + url: 'entry2-17c2a1b5c94290899539.js', }, { - url: 'entry1-3865b3908d1988da1758.js', + url: 'entry1-d7f4e7088b64a9896b23.js', }]; expect(context.self.__precacheManifest).to.eql(expectedEntries); From 782a25a254b7c7eaa7ab6b14866d4696ae21f829 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Fri, 9 Feb 2018 12:12:26 -0500 Subject: [PATCH 2/5] Linting --- test/workbox-webpack-plugin/node/inject-manifest.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/workbox-webpack-plugin/node/inject-manifest.js b/test/workbox-webpack-plugin/node/inject-manifest.js index 37859ddbf..b9d83378b 100644 --- a/test/workbox-webpack-plugin/node/inject-manifest.js +++ b/test/workbox-webpack-plugin/node/inject-manifest.js @@ -289,7 +289,6 @@ describe(`[workbox-webpack-plugin] InjectManifest (End to End)`, function() { it(`should support setting importWorkboxFrom to 'local'`, function(done) { const FILE_MANIFEST_NAME = 'precache-manifest.b6f6b1b151c4f027ee1e1aa3061eeaf7.js'; const outputDir = tempy.directory(); - console.log({outputDir}); const config = { entry: { entry1: path.join(SRC_DIR, WEBPACK_ENTRY_FILENAME), From fc12572475f4b7846fe7d864cd5b4a19c626ce69 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Fri, 9 Feb 2018 12:24:35 -0500 Subject: [PATCH 3/5] Updated getManifest() test. --- .../node/entry-points/get-manifest.js | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/test/workbox-build/node/entry-points/get-manifest.js b/test/workbox-build/node/entry-points/get-manifest.js index a7b71b9ac..d63592051 100644 --- a/test/workbox-build/node/entry-points/get-manifest.js +++ b/test/workbox-build/node/entry-points/get-manifest.js @@ -10,9 +10,9 @@ describe(`[workbox-build] entry-points/get-manifest.js (End to End)`, function() const BASE_OPTIONS = { globDirectory: SRC_DIR, }; - const REQUIRED_PARAMS = ['globDirectory']; const SUPPORTED_PARAMS = [ 'dontCacheBustUrlsMatching', + 'globDirectory', 'globFollow', 'globIgnores', 'globPatterns', @@ -38,23 +38,6 @@ describe(`[workbox-build] entry-points/get-manifest.js (End to End)`, function() 'swDest', ]; - describe('[workbox-build] required parameters', function() { - for (const requiredParam of REQUIRED_PARAMS) { - it(`should reject with a ValidationError when '${requiredParam}' is missing`, async function() { - const options = Object.assign({}, BASE_OPTIONS); - delete options[requiredParam]; - - try { - await getManifest(options); - throw new Error('Unexpected success.'); - } catch (error) { - expect(error.name).to.eql('ValidationError'); - expect(error.details[0].context.key).to.eql(requiredParam); - } - }); - } - }); - describe('[workbox-build] unsupported parameters', function() { for (const unsupportedParam of UNSUPPORTED_PARAMS) { it(`should reject with a ValidationError when '${unsupportedParam}' is present`, async function() { From 4a85cf21561ae9ec04cec24b24cc38a6b4841d23 Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Fri, 9 Feb 2018 12:25:37 -0500 Subject: [PATCH 4/5] Linting, again --- test/workbox-build/node/entry-points/get-manifest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/workbox-build/node/entry-points/get-manifest.js b/test/workbox-build/node/entry-points/get-manifest.js index d63592051..9049b4f7d 100644 --- a/test/workbox-build/node/entry-points/get-manifest.js +++ b/test/workbox-build/node/entry-points/get-manifest.js @@ -21,7 +21,7 @@ describe(`[workbox-build] entry-points/get-manifest.js (End to End)`, function() 'maximumFileSizeToCacheInBytes', 'modifyUrlPrefix', 'templatedUrls', - ].concat(REQUIRED_PARAMS); + ]; const UNSUPPORTED_PARAMS = [ 'cacheId', 'clientsClaim', From 3183d666e4726a4bcf34a7912429e258800112ee Mon Sep 17 00:00:00 2001 From: Jeff Posnick Date: Sat, 10 Feb 2018 01:52:18 -0500 Subject: [PATCH 5/5] Added a publicPath test. --- .../node/inject-manifest.js | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/test/workbox-webpack-plugin/node/inject-manifest.js b/test/workbox-webpack-plugin/node/inject-manifest.js index b9d83378b..ecf1be161 100644 --- a/test/workbox-webpack-plugin/node/inject-manifest.js +++ b/test/workbox-webpack-plugin/node/inject-manifest.js @@ -361,6 +361,83 @@ describe(`[workbox-webpack-plugin] InjectManifest (End to End)`, function() { }); }); + it(`should support setting importWorkboxFrom to 'local', and respect output.publicPath`, function(done) { + const FILE_MANIFEST_NAME = 'precache-manifest.1f4b80f3bf4cbdfc323cd47d280a9561.js'; + const outputDir = tempy.directory(); + const publicPath = 'https://testing.path/'; + const config = { + entry: { + entry1: path.join(SRC_DIR, WEBPACK_ENTRY_FILENAME), + entry2: path.join(SRC_DIR, WEBPACK_ENTRY_FILENAME), + }, + output: { + filename: '[name]-[chunkhash].js', + path: outputDir, + publicPath, + }, + plugins: [ + new InjectManifest({ + importWorkboxFrom: 'local', + swSrc: SW_SRC, + }), + ], + }; + + const compiler = webpack(config); + compiler.run(async (webpackError) => { + if (webpackError) { + return done(webpackError); + } + + const swFile = path.join(outputDir, path.basename(SW_SRC)); + try { + // Validate the copied library files. + const libraryFiles = glob.sync(`${WORKBOX_DIRECTORY_PREFIX}*/*.js*`, + {cwd: outputDir}); + + const modulePathPrefix = path.dirname(libraryFiles[0]); + + const basenames = libraryFiles.map((file) => path.basename(file)); + expect(basenames).to.eql(ALL_WORKBOX_FILES); + + // The correct importScripts path should use the versioned name of the + // parent workbox libraries directory. We don't know that version ahead + // of time, so we ensure that there's a match based on what actually + // got copied over. + const workboxSWImport = libraryFiles.filter( + (file) => file.endsWith('workbox-sw.js'))[0]; + + // First, validate that the generated service-worker.js meets some basic assumptions. + await validateServiceWorkerRuntime({swFile, expectedMethodCalls: { + importScripts: [[ + publicPath + FILE_MANIFEST_NAME, + publicPath + workboxSWImport, + ]], + setConfig: [[{modulePathPrefix}]], + suppressWarnings: [[]], + precacheAndRoute: [[[], {}]], + }}); + + // Next, test the generated manifest to ensure that it contains + // exactly the entries that we expect. + const manifestFileContents = await fse.readFile(path.join(outputDir, FILE_MANIFEST_NAME), 'utf-8'); + const context = {self: {}}; + vm.runInNewContext(manifestFileContents, context); + + const expectedEntries = [{ + url: publicPath + 'entry2-a73dfbc0c0f27e33c997.js', + }, { + url: publicPath + 'entry1-72d0a5a3a44942369363.js', + }]; + expect(context.self.__precacheManifest).to.eql(expectedEntries); + + done(); + } catch (error) { + done(error); + } + }); + }); + it(`should respect output.publicPath if importWorkboxFrom is set to a Webpack chunk name`, function(done) { const FILE_MANIFEST_NAME = 'precache-manifest.c8c87b7b2af4d79242a895050ff70e48.js'; const publicPath = 'https://testing.path/';