_headers & _redirects generation (#240)

* Generate `_headers` and `_redirects` by passing assets through ejs templates.

* PR feedback

* Excluding service worker stuff from prerender

* Build SW in dev

* Let's give this a try

* lol

* Is this how it works?
This commit is contained in:
Jason Miller
2018-11-09 13:49:01 -05:00
committed by Jake Archibald
parent 9b572f9541
commit 6994cc3d15
6 changed files with 100 additions and 2 deletions

15
_headers.ejs Normal file
View File

@@ -0,0 +1,15 @@
# Long-term cache by default.
/*
Cache-Control: max-age=31536000
# And here are the exceptions:
/
Cache-Control: no-cache
/serviceworker.js
Cache-Control: no-cache
# URLs in /assets do not include a hash and are mutable.
# But it isn't a big deal if the user gets an old version.
/assets/*
Cache-Control: must-revalidate, max-age=3600

View File

@@ -0,0 +1,47 @@
const path = require('path');
const fs = require('fs');
const ejs = require('ejs');
const AssetsPlugin = require('assets-webpack-plugin');
module.exports = class AssetTemplatePlugin extends AssetsPlugin {
constructor(options) {
options = options || {};
if (!options.template) throw Error('AssetTemplatePlugin: template option is required.');
super({
useCompilerPath: true,
filename: options.filename,
processOutput: files => this._processOutput(files)
});
this._template = path.resolve(process.cwd(), options.template);
const ignore = options.ignore || /(manifest\.json|\.DS_Store)$/;
this._ignore = typeof ignore === 'function' ? ({ test: ignore }) : ignore;
}
_processOutput(files) {
const mapping = {
all: [],
byType: {},
entries: {}
};
for (const entryName in files) {
// non-entry-point-derived assets are collected under an empty string key
// since that's a bit awkward, we'll call them "assets"
const name = entryName === '' ? 'assets' : entryName;
const listing = files[entryName];
const entry = mapping.entries[name] = {
all: [],
byType: {}
};
for (let type in listing) {
const list = [].concat(listing[type]).filter(file => !this._ignore.test(file));
if (!list.length) continue;
mapping.all = mapping.all.concat(list);
mapping.byType[type] = (mapping.byType[type] || []).concat(list);
entry.all = entry.all.concat(list);
entry.byType[type] = (entry.byType[type] || []).concat(list);
}
}
mapping.files = mapping.all;
return ejs.render(fs.readFileSync(this._template, 'utf8'), mapping);
}
};

27
package-lock.json generated
View File

@@ -578,6 +578,27 @@
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
"dev": true
},
"assets-webpack-plugin": {
"version": "3.9.7",
"resolved": "https://registry.npmjs.org/assets-webpack-plugin/-/assets-webpack-plugin-3.9.7.tgz",
"integrity": "sha512-yxo4MlSb++B88qQFE27Wf56ykGaDHZeKcSbrstSFOOwOxv33gWXtM49+yfYPSErlXPAMT5lVy3YPIhWlIFjYQw==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
"escape-string-regexp": "^1.0.3",
"lodash.assign": "^4.2.0",
"lodash.merge": "^4.6.1",
"mkdirp": "^0.5.1"
},
"dependencies": {
"camelcase": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz",
"integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==",
"dev": true
}
}
},
"assign-symbols": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
@@ -7923,6 +7944,12 @@
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
"dev": true
},
"lodash.merge": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz",
"integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==",
"dev": true
},
"lodash.mergewith": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",

View File

@@ -19,6 +19,7 @@
"@types/pretty-bytes": "^5.1.0",
"@types/webassembly-js-api": "0.0.1",
"@webcomponents/custom-elements": "^1.2.1",
"assets-webpack-plugin": "^3.9.7",
"babel-loader": "^7.1.5",
"babel-plugin-jsx-pragmatic": "^1.0.2",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
@@ -36,6 +37,7 @@
"copy-webpack-plugin": "^4.5.3",
"critters-webpack-plugin": "^2.0.1",
"css-loader": "^0.28.11",
"ejs": "^2.6.1",
"exports-loader": "^0.7.0",
"file-drop-element": "^0.0.9",
"file-loader": "^1.1.11",

View File

@@ -42,6 +42,9 @@ async function updateReady(reg: ServiceWorkerRegistration): Promise<void> {
/** Set up the service worker and monitor changes */
export async function offliner(showSnack: SnackBarElement['showSnackbar']) {
// This needs to be a typeof because Webpack.
if (typeof PRERENDER === 'boolean') return;
if (process.env.NODE_ENV === 'production') {
navigator.serviceWorker.register('../sw');
}

View File

@@ -14,6 +14,7 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPl
const WorkerPlugin = require('worker-plugin');
const AutoSWPlugin = require('./config/auto-sw-plugin');
const CrittersPlugin = require('critters-webpack-plugin');
const AssetTemplatePlugin = require('./config/asset-template-plugin');
function readJson (filename) {
return JSON.parse(fs.readFileSync(filename));
@@ -238,8 +239,11 @@ module.exports = function (_, env) {
compile: true
}),
new AutoSWPlugin({
version: VERSION
new AutoSWPlugin({ version: VERSION }),
isProd && new AssetTemplatePlugin({
template: path.join(__dirname, '_headers.ejs'),
filename: '_headers',
}),
new ScriptExtHtmlPlugin({