Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds brotli support for modern javascript #674

Merged
merged 36 commits into from
Apr 5, 2019
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0507885
WIP adding sw as a webpack dependency
prateekbh Nov 20, 2018
c5d5246
Merge branch 'babel-bug-fix' into sw-webpack
prateekbh Nov 20, 2018
5a37b9f
adding sw-config
prateekbh Nov 20, 2018
65a507c
Brotli support for modern javascript
prateekbh Nov 27, 2018
02498fe
adding brotli plugin to webpack
prateekbh Nov 27, 2018
375c545
fixing conditional plugins
prateekbh Nov 27, 2018
9c34521
lint fixes
prateekbh Nov 28, 2018
e9e1cdc
fixing error
prateekbh Nov 29, 2018
8b84b4d
bug-fix
prateekbh Nov 29, 2018
cc29725
WIP fix watch mode
prateekbh Dec 5, 2018
1500698
Merge branch 'next' of https://github.com/developit/preact-cli into s…
prateekbh Dec 5, 2018
86f0f78
fix watch mode
prateekbh Dec 5, 2018
3d1ac3a
add the capability to build sw from user land
prateekbh Dec 5, 2018
1369f2c
fixing comments
prateekbh Dec 10, 2018
3135b3b
fixing spacing
prateekbh Dec 10, 2018
c1fd332
fixing in place mutation
prateekbh Dec 10, 2018
8aec475
no multi compilers
prateekbh Dec 19, 2018
8bc9521
Update run-webpack.js
prateekbh Dec 19, 2018
dcc8f41
removing unwanted files
prateekbh Dec 19, 2018
434a813
Merge branch 'sw-webpack' of https://github.com/developit/preact-cli …
prateekbh Dec 19, 2018
9ed7049
Update sw-plugin.js
prateekbh Dec 21, 2018
fb28138
Update sw-plugin.js
prateekbh Dec 21, 2018
81bb193
Merge branch 'next' into sw-webpack
ForsakenHarmony Jan 26, 2019
dc64998
making changes for workbox v4
prateekbh Jan 31, 2019
9a04494
fixes for regexp
prateekbh Jan 31, 2019
95d3ccb
addressing comments
prateekbh Feb 20, 2019
400fdb2
precacing only index.html
prateekbh Mar 6, 2019
7f21e24
Merge branch 'next' into sw-webpack
prateekbh Mar 6, 2019
fdf81b4
Update run-webpack.js
prateekbh Mar 6, 2019
37d9ec7
Update run-webpack.js
prateekbh Mar 6, 2019
9054e9b
fixing kluer dep
prateekbh Mar 6, 2019
126cd49
fixing package.json
prateekbh Mar 6, 2019
c95b6dc
fixing unhashed bundle bug
prateekbh Mar 7, 2019
4302de4
no json parse
prateekbh Mar 7, 2019
8935426
fixing comments
prateekbh Apr 5, 2019
b12603b
Merge branch 'master' of https://github.com/developit/preact-cli into…
prateekbh Apr 5, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/cli/lib/commands/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const rimraf = require('rimraf');
const { resolve } = require('path');
const { promisify } = require('util');
const { isDir, error } = require('../util');
const { yellow } = require('kleur');
const runWebpack = require('../lib/webpack/run-webpack');

const toBool = val => val === void 0 || (val === 'false' ? false : val);
Expand All @@ -22,6 +23,15 @@ module.exports = async function(src, argv) {
);
}

if (argv.brotli) {
console.log(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

guess this should use the util log fn

yellow(
'⚛️ ATTENTION! You have enabled BROTLI support. ' +
"In order for this to work correctly, make sure .js.br files are served with 'content-encoding: br' header."
)
);
}

if (argv.clean === void 0) {
let dest = resolve(cwd, argv.dest);
await promisify(rimraf)(dest);
Expand Down
1 change: 1 addition & 0 deletions packages/cli/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ prog
)
.option('-c, --config', 'Path to custom CLI config', 'preact.config.js')
.option('--esm', 'Builds ES-2015 bundles for your code.', true)
.option('--brotli', 'Adds brotli redirects to the service worker.', false)
.option('--inline-css', 'Adds critical css to the prerendered markup.', true)
.option('-v, --verbose', 'Verbose output')
.action(commands.build);
Expand Down
46 changes: 46 additions & 0 deletions packages/cli/lib/lib/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
self.__precacheManifest = [].concat(self.__precacheManifest || []);

/* global workbox */
/** We are sure brotli is enabled for browsers supporting script type=module
* so we do brotli support only for them.
* We can do brolti support for other browsers but there is no good way of
* feature detect the same at the time of pre-caching.
*/
if (process.env.ENABLE_BROTLI && process.env.ES_BUILD) {
// Alter the precache manifest to precache brotli files instead of gzip files.
self.__precacheManifest = self.__precacheManifest.map(asset => {
if (/.*.js$/.test(asset.url)) {
asset.url = asset.url.replace(/.esm.js$/, '.esm.js.br');
}
return asset;
});

class BrotliRedirectPlugin {
// Before saving the response in cache, we need to treat the headers.
async cacheWillUpdate({ response }) {
const clonedResponse = response.clone();
if (/.js.br(\?.*)?$/.test(clonedResponse.url)) {
const headers = new Headers(clonedResponse.headers);
headers.set('content-type', 'application/javascript');
return new Response(await clonedResponse.text(), { headers });
}
return response;
}
}
workbox.precaching.addPlugins([new BrotliRedirectPlugin()]);
}

const precacheOptions = {};
if (process.env.ENABLE_BROTLI) {
developit marked this conversation as resolved.
Show resolved Hide resolved
precacheOptions['urlManipulation'] = ({ url }) => {
if (/.esm.js$/.test(url.href)) {
url.href = url.href + '.br';
}
return [url];
};
}

workbox.precaching.precacheAndRoute(self.__precacheManifest, precacheOptions);
workbox.routing.registerNavigationRoute(
workbox.precaching.getCacheKeyForURL('/index.html')
);
113 changes: 113 additions & 0 deletions packages/cli/lib/lib/webpack/sw-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
const BabelEsmPlugin = require('babel-esm-plugin');
const { DefinePlugin } = require('webpack');
const fs = require('fs');
const { resolve } = require('path');
const { blue } = require('chalk');

class SWBuilderPlugin {
constructor(config) {
const { src, brotli, esm } = config;
this.brotli_ = brotli;
this.esm_ = esm;
this.src_ = src;
}
apply(compiler) {
let swSrc = resolve(__dirname, '../sw.js');
const exists = fs.existsSync(resolve(`${this.src_}/sw.js`));
if (exists) {
if (exists) {
console.log(
blue(
'⚛️ Detected custom sw.js: compiling instead of default Service Worker.'
)
);
} else {
console.log(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing here?

blue('⚛️ No custom sw.js detected: compiling default Service Worker.')
);
}
}
compiler.hooks.make.tapAsync(
this.constructor.name,
(compilation, callback) => {
const outputOptions = compiler.options;
const plugins = [
new BabelEsmPlugin({
filename: '[name]-esm.js',
excludedPlugins: ['BabelEsmPlugin', this.constructor.name],
beforeStartExecution: plugins => {
plugins.forEach(plugin => {
if (plugin.constructor.name === 'DefinePlugin') {
if (!plugin.definitions)
throw Error(
'ESM Error: DefinePlugin found without definitions.'
);
plugin.definitions['process.env.ES_BUILD'] = true;
}
});
},
}),
new DefinePlugin({
'process.env.ENABLE_BROTLI': this.brotli_,
'process.env.ES_BUILD': false,
'process.env.NODE_ENV': 'production',
}),
];

/**
* We are deliberatly not passing plugins in createChildCompiler.
* All webpack does with plugins is to call `apply` method on them
* with the childCompiler.
* But by then we haven't given childCompiler a fileSystem or other options
* which a few plugins might expect while execution the apply method.
* We do call the `apply` method of all plugins by ourselves later in the code
*/
const childCompiler = compilation.createChildCompiler(
this.constructor.name
);

childCompiler.context = compiler.context;
childCompiler.options = Object.assign({}, outputOptions);
childCompiler.options.entry = {
sw: swSrc,
};
childCompiler.options.target = 'webworker';
childCompiler.options.output = JSON.parse(
Copy link
Member Author

@prateekbh prateekbh Mar 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@developit @ForsakenHarmony do we have any dep already which can help me do this deepcopy?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm fixed it

JSON.stringify(childCompiler.options.output)
);
childCompiler.options.output.filename = '[name].js';

// Call the `apply` method of all plugins by ourselves.
if (Array.isArray(plugins)) {
for (const plugin of plugins) {
plugin.apply(childCompiler);
}
}

childCompiler.apply(
new SingleEntryPlugin(compiler.context, swSrc, 'sw')
);

compilation.hooks.additionalAssets.tapAsync(
this.constructor.name,
childProcessDone => {
childCompiler.runAsChild((err, entries, childCompilation) => {
if (!err) {
compilation.assets = Object.assign(
childCompilation.assets,
compilation.assets
);
}
err && compilation.errors.push(err);
childProcessDone();
});
}
);
callback();
}
);
}
}

module.exports = SWBuilderPlugin;
97 changes: 63 additions & 34 deletions packages/cli/lib/lib/webpack/webpack-client-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const { existsSync } = require('fs');
const merge = require('webpack-merge');
const { filter } = require('minimatch');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
Expand All @@ -13,14 +12,16 @@ const RenderHTMLPlugin = require('./render-html-plugin');
const PushManifestPlugin = require('./push-manifest');
const baseConfig = require('./webpack-base-config');
const BabelEsmPlugin = require('babel-esm-plugin');
const { InjectManifest } = require('workbox-webpack-plugin');
const BrotliPlugin = require('brotli-webpack-plugin');
const { normalizePath } = require('../../util');
const SWBuilderPlugin = require('./sw-plugin');

const cleanFilename = name =>
name.replace(
/(^\/(routes|components\/(routes|async))\/|(\/index)?\.js$)/g,
''
);

function clientConfig(env) {
const { isProd, source, src /*, port? */ } = env;

Expand Down Expand Up @@ -208,49 +209,69 @@ function isProd(config) {
new OptimizeCssAssetsPlugin({
cssProcessorOptions: {
// Fix keyframes in different CSS chunks minifying to colliding names:
reduceIdents: false
}
reduceIdents: false,
},
}),
],
},
};

if (config.sw) {
if (config.esm) {
prodConfig.plugins.push(
new SWPrecacheWebpackPlugin({
filename: 'sw.js',
navigateFallback: 'index.html',
navigateFallbackWhitelist: [/^(?!\/__).*/],
minify: true,
stripPrefix: config.cwd,
staticFileGlobsIgnorePatterns: [
/\.esm\.js$/,
/polyfills(\..*)?\.js$/,
/\.map$/,
/push-manifest\.json$/,
/.DS_Store/,
/\.git/,
],
new BabelEsmPlugin({
filename: '[name].[chunkhash:5].esm.js',
chunkFilename: '[name].chunk.[chunkhash:5].esm.js',
excludedPlugins: ['BabelEsmPlugin', 'SWBuilderPlugin'],
beforeStartExecution: (plugins, newConfig) => {
const babelPlugins = newConfig.plugins;
newConfig.plugins = babelPlugins.filter(plugin => {
if (
Array.isArray(plugin) &&
plugin[0].indexOf('fast-async') !== -1
) {
return false;
}
return true;
});
plugins.forEach(plugin => {
if (
plugin.constructor.name === 'DefinePlugin' &&
plugin.definitions
) {
for (const definition in plugin.definitions) {
if (definition === 'process.env.ES_BUILD') {
plugin.definitions[definition] = true;
}
}
} else if (
plugin.constructor.name === 'DefinePlugin' &&
!plugin.definitions
) {
throw new Error(
'WebpackDefinePlugin found but not `process.env.ES_BUILD`.'
);
}
});
},
})
);
config.sw &&
prodConfig.plugins.push(
new InjectManifest({
swSrc: 'sw-esm.js',
include: [/index\.html$/, /\.esm.js$/, /\.css$/, /\.(png|jpg)$/],
precacheManifestFilename: 'precache-manifest.[manifestHash].esm.js',
})
);
}

if (config.esm && config.sw) {
if (config.sw) {
prodConfig.plugins.push(new SWBuilderPlugin(config));
prodConfig.plugins.push(
new SWPrecacheWebpackPlugin({
filename: 'sw-esm.js',
navigateFallback: 'index.html',
navigateFallbackWhitelist: [/^(?!\/__).*/],
minify: true,
stripPrefix: config.cwd,
staticFileGlobsIgnorePatterns: [
/(\.[\w]{5}\.js)/,
/polyfills(\..*)?\.js$/,
/\.map$/,
/push-manifest\.json$/,
/.DS_Store/,
/\.git/,
],
new InjectManifest({
swSrc: 'sw.js',
include: [/index\.html$/, /\.js$/, /\.css$/, /\.(png|jpg)$/],
exclude: [/\.esm\.js$/],
})
);
}
Expand All @@ -263,6 +284,14 @@ function isProd(config) {
prodConfig.plugins.push(new BundleAnalyzerPlugin());
}

if (config.brotli) {
prodConfig.plugins.push(
new BrotliPlugin({
test: /\.esm\.js$/,
})
);
}

return prodConfig;
}

Expand Down
4 changes: 3 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"babel-loader": "^8.0.5",
"babel-plugin-macros": "^2.4.5",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"brotli-webpack-plugin": "^1.0.0",
"console-clear": "^1.0.0",
"copy-webpack-plugin": "^5.0.0",
"critters-webpack-plugin": "^1.3.3",
Expand Down Expand Up @@ -117,6 +118,7 @@
"webpack-fix-style-only-entries": "^0.2.0",
"webpack-merge": "^4.1.0",
"webpack-plugin-replace": "^1.2.0",
"which": "^1.2.14"
"which": "^1.2.14",
"workbox-webpack-plugin": "^4.0.0-beta.2"
}
}
Loading