Skip to content

Commit

Permalink
[patch] update sw-precache plugin (#505)
Browse files Browse the repository at this point in the history
  • Loading branch information
jchip committed Aug 6, 2017
1 parent daa3779 commit 02da615
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
/* Service Worker Precache WebPack Plugin */
/* From https://github.com/goldhand/sw-precache-webpack-plugin */
/* Copied locally to turn off warning */

"use strict";

var _extends = Object.assign;
Expand All @@ -16,10 +15,6 @@ var _url = require("url");

var _url2 = _interopRequireDefault(_url);

var _del = require("del");

var _del2 = _interopRequireDefault(_del);

var _swPrecache = require("sw-precache");

var _swPrecache2 = _interopRequireDefault(_swPrecache);
Expand All @@ -28,30 +23,32 @@ var _uglifyJs = require("uglify-js");

var _uglifyJs2 = _interopRequireDefault(_uglifyJs);

var _fs = require("fs");

var _fs2 = _interopRequireDefault(_fs);
var _util = require("util");

function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}

const FILEPATH_WARNING =
"sw-prechache-webpack-plugin filepath: You are using a custom path for your service worker, this may prevent the service worker from working correctly if it is not available in the same path as your application.";
"sw-prechache-webpack-plugin [filepath]: You are using a custom path for your service worker, this may prevent the service worker from working correctly if it is not available in the same path as your application.";
const FORCEDELETE_WARNING =
"sw-prechache-webpack-plugin [forceDelete]: You are specifying the option forceDelete. This was removed in v0.10. It should not affect your build but should no longer be required.";

const DEFAULT_CACHE_ID = "sw-precache-webpack-plugin",
DEFAULT_WORKER_FILENAME = "service-worker.js",
DEFAULT_OUTPUT_PATH = "",
DEFAULT_PUBLIC_PATH = "",
DEFAULT_IMPORT_SCRIPTS = [],
DEFAULT_IGNORE_PATTERNS = [];
DEFAULT_IGNORE_PATTERNS = [],
CHUNK_NAME_NOT_FOUND_ERROR = 'Could not locate files for chunkName: "%s"',
// eslint-disable-next-line max-len
CHUNK_NAME_OVERRIDES_FILENAME_WARNING =
"Don't use chunkName & filename together; importScripts[<index>].filename overriden by specified chunkName: %j";

const DEFAULT_OPTIONS = {
cacheId: DEFAULT_CACHE_ID,
filename: DEFAULT_WORKER_FILENAME,
importScripts: DEFAULT_IMPORT_SCRIPTS,
staticFileGlobsIgnorePatterns: DEFAULT_IGNORE_PATTERNS,
forceDelete: false,
mergeStaticsConfig: false,
minify: false
};
Expand All @@ -60,33 +57,15 @@ class SWPrecacheWebpackPlugin {
/**
* SWPrecacheWebpackPlugin - A wrapper for sw-precache to use with webpack
* @constructor
* @param {object} options - All parameters should be passed as a single options object
*
* // sw-precache options:
* @param {string} [options.cacheId]
* @param {string} [options.directoryIndex]
* @param {object|array} [options.dynamicUrlToDependencies]
* @param {boolean} [options.handleFetch]
* @param {array} [options.ignoreUrlParametersMatching]
* @param {array} [options.importScripts]
* @param {function} [options.logger]
* @param {number} [options.maximumFileSizeToCacheInBytes]
* @param {array} [options.navigateFallbackWhitelist]
* @param {string} [options.replacePrefix]
* @param {array} [options.runtimeCaching]
* @param {array} [options.staticFileGlobs]
* @param {string} [options.stripPrefix]
* @param {string} [options.stripPrefixMulti]
* @param {string} [options.templateFilePath]
* @param {boolean} [options.verbose]
* @param {object} options - All parameters should be passed as a single options object. All sw-precache options can be passed here in addition to plugin options.
*
* // plugin options:
* @param {string} [options.filename] - Service worker filename, default is 'service-worker.js'
* @param {string} [options.filepath] - Service worker path and name, default is to use webpack.output.path + options.filename
* @param {RegExp} [options.staticFileGlobsIgnorePatterns[]] - Define an optional array of regex patterns to filter out of staticFileGlobs
* @param {boolean} [options.forceDelete=false] - Pass force option to del
* @param {boolean} [options.mergeStaticsConfig=false] - Merge provided staticFileGlobs and stripPrefix(Multi) with webpack's config, rather than having those take precedence
* @param {boolean} [options.minify=false] - Minify the generated Service worker file using UglifyJS
* @param {boolean} [options.debug=false] - Output error and warning messages
*/
constructor(options) {
// generated configuration options
Expand All @@ -95,6 +74,8 @@ class SWPrecacheWebpackPlugin {
this.options = _extends({}, DEFAULT_OPTIONS, options);
// generated configuration that will override user options
this.overrides = {};
// push warning messages here
this.warnings = [];
}

/**
Expand All @@ -105,92 +86,173 @@ class SWPrecacheWebpackPlugin {
}

apply(compiler) {
// sw-precache needs physical files to reference so we MUST wait until after assets are emitted before generating the service-worker.
compiler.plugin("after-emit", (compilation, callback) => {
// get the defaults from options
var _options = this.options;
const importScripts = _options.importScripts,
staticFileGlobsIgnorePatterns = _options.staticFileGlobsIgnorePatterns,
mergeStaticsConfig = _options.mergeStaticsConfig;
this.configure(compiler, compilation); // configure the serviceworker options
this.checkWarnings(compilation);

// generate service worker then write to file system
this.createServiceWorker()
.then(serviceWorker => this.writeServiceWorker(serviceWorker, compiler))
.then(() => callback())
.catch(err => callback(err));
});
}

// get the output path specified in webpack config
configure(compiler, compilation) {
// get the defaults from options
var _options = this.options;
const importScripts = _options.importScripts,
staticFileGlobsIgnorePatterns = _options.staticFileGlobsIgnorePatterns,
mergeStaticsConfig = _options.mergeStaticsConfig;
var _options$stripPrefixM = _options.stripPrefixMulti;
const stripPrefixMulti = _options$stripPrefixM === undefined ? {} : _options$stripPrefixM;

const outputPath = compiler.options.output.path || DEFAULT_OUTPUT_PATH;
// get the output path used by webpack

// get the public path specified in webpack config
var _compiler$options$out = compiler.options.output.publicPath;
const publicPath = _compiler$options$out === undefined
? DEFAULT_PUBLIC_PATH
: _compiler$options$out;
const outputPath = compiler.outputPath;

if (this.options.filepath && !this.options.noWarning) {
// warn about changing filepath
compilation.warnings.push(new Error(FILEPATH_WARNING));
}
// outputPath + filename or the user option

// get all assets outputted by webpack
const assetGlobs = Object.keys(compilation.assets).map(f =>
_path2.default.join(outputPath, f)
);
var _options$filepath = this.options.filepath;
const filepath =
_options$filepath === undefined
? _path2.default.resolve(outputPath, this.options.filename)
: _options$filepath;

// merge assetGlobs with provided staticFileGlobs and filter using staticFileGlobsIgnorePatterns
const staticFileGlobs = assetGlobs
.concat(this.options.staticFileGlobs || [])
.filter(text => !staticFileGlobsIgnorePatterns.some(regex => regex.test(text)));
// get the public path specified in webpack config

const stripPrefixMulti = _extends({}, this.options.stripPrefixMulti);
var _compiler$options$out = compiler.options.output.publicPath;
const publicPath =
_compiler$options$out === undefined ? DEFAULT_PUBLIC_PATH : _compiler$options$out;

if (outputPath) {
// strip the webpack config's output.path
stripPrefixMulti[`${outputPath}${_path2.default.sep}`] = publicPath;
}
// get all assets outputted by webpack

const assetGlobs = Object.keys(compilation.assets).map(f => _path2.default.join(outputPath, f));

this.config = _extends({}, this.config, {
// merge assetGlobs with provided staticFileGlobs and filter using staticFileGlobsIgnorePatterns
const staticFileGlobs = assetGlobs
.concat(this.options.staticFileGlobs || [])
.filter(text => !staticFileGlobsIgnorePatterns.some(regex => regex.test(text)));

if (outputPath) {
// strip the webpack config's output.path (replace for windows users)
stripPrefixMulti[`${outputPath}${_path2.default.sep}`.replace(/\\/g, "/")] = publicPath;
}

this.config = _extends({}, this.config, {
staticFileGlobs: staticFileGlobs,
stripPrefixMulti: stripPrefixMulti
});

// set the actual filepath
this.overrides.filepath = filepath;

// resolve [hash] used in importScripts
this.configureImportScripts(importScripts, publicPath, compiler, compilation);

if (mergeStaticsConfig) {
// merge generated and user provided options
this.overrides = _extends({}, this.overrides, {
staticFileGlobs: staticFileGlobs,
stripPrefixMulti: stripPrefixMulti
});
}
}

configureImportScripts(importScripts, publicPath, compiler, compilation) {
if (!importScripts) {
return;
}

var _compilation$getStats = compilation.getStats().toJson({ hash: true, chunks: true });

if (importScripts) {
this.overrides.importScripts = importScripts
.map(f => f.replace(/\[hash\]/g, compilation.hash)) // need to override importScripts with stats.hash
.map(f => _url2.default.resolve(publicPath, f)); // add publicPath to importScripts
const hash = _compilation$getStats.hash,
chunks = _compilation$getStats.chunks;

this.overrides.importScripts = importScripts.reduce((fileList, criteria) => {
// legacy support for importScripts items defined as string
if (typeof criteria === "string") {
criteria = { filename: criteria };
}

if (mergeStaticsConfig) {
// merge generated and user provided options
this.overrides = _extends({}, this.overrides, {
staticFileGlobs: staticFileGlobs,
stripPrefixMulti: stripPrefixMulti
});
const hasFileName = !!criteria.filename;
const hasChunkName = !!criteria.chunkName;

if (hasFileName && hasChunkName) {
this.warnings.push(
new Error((0, _util.format)(CHUNK_NAME_OVERRIDES_FILENAME_WARNING, criteria))
);
}

const done = () => callback();
const error = err => callback(err);
if (hasChunkName) {
const chunk = chunks.find(c => c.names.includes(criteria.chunkName));

this.writeServiceWorker(compiler).then(done, error);
if (!chunk) {
compilation.errors.push(
new Error((0, _util.format)(CHUNK_NAME_NOT_FOUND_ERROR, criteria.chunkName))
);
return fileList;
}

const chunkFileName = chunk.files[chunk.names.indexOf(criteria.chunkName)];
fileList.push(_url2.default.resolve(publicPath, chunkFileName));
} else if (hasFileName) {
const hashedFilename = criteria.filename.replace(/\[hash\]/g, hash);
fileList.push(_url2.default.resolve(publicPath, hashedFilename));
}

return fileList;
}, []);
}

createServiceWorker() {
return _swPrecache2.default.generate(this.workerOptions).then(serviceWorkerFileContents => {
if (this.options.minify) {
const uglifyFiles = {};
uglifyFiles[this.options.filename] = serviceWorkerFileContents;
return _uglifyJs2.default.minify(uglifyFiles).code;
}
return serviceWorkerFileContents;
});
}

writeServiceWorker(compiler) {
const fileDir = compiler.options.output.path || DEFAULT_OUTPUT_PATH;
var _options$filepath = this.options.filepath;
const filepath = _options$filepath === undefined
? _path2.default.join(fileDir, this.options.filename)
: _options$filepath;

return (0, _del2.default)(filepath, { force: this.options.forceDelete })
.then(() => _swPrecache2.default.generate(this.workerOptions))
.then(serviceWorkerFileContents => {
if (this.options.minify) {
const uglifyFiles = {};
uglifyFiles[this.options.filename] = serviceWorkerFileContents;
const minifedCodeObj = _uglifyJs2.default.minify(uglifyFiles, { fromString: true });
return minifedCodeObj.code;
}
return serviceWorkerFileContents;
})
.then(possiblyMinifiedServiceWorkerFileContents =>
_fs2.default.writeFileSync(filepath, possiblyMinifiedServiceWorkerFileContents)
);
writeServiceWorker(serviceWorker, compiler) {
const filepath = this.workerOptions.filepath;
var _compiler$outputFileS = compiler.outputFileSystem;
const mkdirp = _compiler$outputFileS.mkdirp,
writeFile = _compiler$outputFileS.writeFile;

// use the outputFileSystem api to manually write service workers rather than adding to the compilation assets

return new Promise(resolve => {
mkdirp(_path2.default.resolve(filepath, ".."), () => {
writeFile(filepath, serviceWorker, resolve);
});
});
}

/**
* Push plugin warnings to webpack log
* @param {object} compilation - webpack compilation
* @returns {void}
*/
checkWarnings(compilation) {
if (!this.options.noWarnings) {
if (this.options.filepath) {
// warn about changing filepath
this.warnings.push(new Error(FILEPATH_WARNING));
}

if (this.options.forceDelete) {
// deprecate forceDelete
this.warnings.push(new Error(FORCEDELETE_WARNING));
}
}

if (this.workerOptions.debug) {
this.warnings.forEach(warning => compilation.warnings.push(warning));
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/electrode-archetype-react-app-dev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
"convert-source-map": "^1.5.0",
"css-loader": "^0.26.1",
"css-split-webpack-plugin": "^0.2.0",
"del": "^2.2.2",
"electrode-bundle-analyzer": "^1.0.0",
"electrode-cdn-file-loader": "^1.0.0",
"electrode-electrify": "^1.0.0",
Expand Down Expand Up @@ -106,6 +105,7 @@
"stylus-relative-loader": "^3.0.0",
"sw-precache": "^5.0.0",
"sw-toolbox": "^3.4.0",
"uglify-js": "^3.0.26",
"url-loader": "^0.5.6",
"web-app-manifest-loader": "^0.1.1",
"webpack": "^2.2.0",
Expand Down

0 comments on commit 02da615

Please sign in to comment.