feat(v2): Plugin for Offline/PWA support (#2205)

* implement PWA plugin

* added pwa support for docusaurus website

* moved sw registration to client module

* moved compile function to webpack util

* build sw using webpack and render pwa popup

* implement @theme/PwaReloadPopup

* update website sw to use modules

* updated pwa readme

* fix header lint errors

* apply code formatting

* cache files only for mobile, saveData, or installed pwa

* added comments about clearing registrations

* fixed prettier error

* updated pwa README

* fix README JS

* move /blog => /blog/index.html logic to else branch

* add `alwaysPrecache` option

* updated docusaurus-plugin-pwa version

* added pwa to using-plugins.md

* review fixes

* re-disable restricted-globals to use self in service worker

* useless doc

* Update packages/docusaurus-plugin-pwa/README.md

Co-authored-by: Reece Dunham <me@rdil.rocks>

* Update packages/docusaurus-plugin-pwa/README.md

* update a bit pwa doc + minor refactors

* minor refactors + add workbox debug mode

* env PWA_ prefix

* typo

* minor refactor

* fix file output

* add serve:v2:ssl yarn command

* minor pwa fixes

* typo

* add dynamic import comment in SW

* comment

* let the PWA plugin implement its reload popup on his own

* pwa: add Joi options validation

* pwa plugin should have its own webpack/babel custom setup

* PWA:
- debug logs
- better SW params system
- offline mode activation strategies
- docs

* add pwa install gif

* pwa: popup -> reloadPopup + minor refactors

* fix process.env reading + better debug log

* minor fixes

* minor changes

* minor changes

Co-authored-by: slorber <lorber.sebastien@gmail.com>
Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
Co-authored-by: Reece Dunham <me@rdil.rocks>
This commit is contained in:
Jeremy Asuncion 2020-07-08 03:32:41 -07:00 committed by GitHub
parent 46f794b2ba
commit 9b3da59886
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 1508 additions and 45 deletions

View file

@ -0,0 +1,173 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const LogPlugin = require('@docusaurus/core/lib/webpack/plugins/LogPlugin');
const {compile} = require('@docusaurus/core/lib/webpack/utils');
const path = require('path');
const webpack = require('webpack');
const {injectManifest} = require('workbox-build');
const {PluginOptionSchema} = require('./pluginOptionSchema');
const Terser = require('terser-webpack-plugin');
const isProd = process.env.NODE_ENV === 'production';
function getSWBabelLoader() {
return {
loader: 'babel-loader',
options: {
babelrc: false,
configFile: false,
presets: [
[
require.resolve('@babel/preset-env'),
{
useBuiltIns: 'usage',
corejs: '2',
// See https://twitter.com/jeffposnick/status/1280223070876315649
targets: 'chrome >= 56',
},
],
],
plugins: [
require.resolve('@babel/plugin-proposal-object-rest-spread'),
require.resolve('@babel/plugin-proposal-optional-chaining'),
require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'),
],
},
};
}
function plugin(context, options) {
const {outDir, baseUrl} = context;
const {
debug,
offlineModeActivationStrategies,
injectManifestConfig,
reloadPopup,
pwaHead,
swCustom,
swRegister,
} = options;
return {
name: 'docusaurus-plugin-pwa',
getThemePath() {
return path.resolve(__dirname, './theme');
},
getClientModules() {
return isProd ? [swRegister] : [];
},
configureWebpack(config) {
if (!isProd) {
return {};
}
return {
plugins: [
new webpack.EnvironmentPlugin({
PWA_DEBUG: debug,
PWA_SERVICE_WORKER_URL: path.resolve(
`${config.output.publicPath || '/'}`,
'sw.js',
),
PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES: offlineModeActivationStrategies,
PWA_RELOAD_POPUP: reloadPopup,
}),
],
};
},
injectHtmlTags() {
const headTags = [];
if (isProd && pwaHead) {
pwaHead.forEach(({tagName, ...attributes}) =>
headTags.push({
tagName,
attributes,
}),
);
}
return {headTags};
},
async postBuild(props) {
if (!isProd) {
return;
}
const swSourceFileTest = /\.m?js$/;
const swWebpackConfig = {
entry: path.resolve(__dirname, 'sw.js'),
output: {
path: outDir,
filename: 'sw.js',
publicPath: baseUrl,
},
target: 'webworker',
mode: debug ? 'development' : 'production',
devtool: debug ? 'source-map' : false,
optimization: {
splitChunks: false,
minimize: !debug,
// see https://developers.google.com/web/tools/workbox/guides/using-bundlers#webpack
minimizer: [
!debug &&
new Terser({
test: swSourceFileTest,
}),
].filter(Boolean),
},
plugins: [
new webpack.EnvironmentPlugin({
PWA_SW_CUSTOM: swCustom,
}),
new LogPlugin({
name: 'Service Worker',
color: 'red',
}),
],
module: {
rules: [
{
test: swSourceFileTest,
exclude: /(node_modules)/,
use: getSWBabelLoader(),
},
],
},
};
await compile([swWebpackConfig]);
const swDest = path.resolve(props.outDir, 'sw.js');
await injectManifest({
...injectManifestConfig,
globPatterns: [
'**/*.{js,json,css,html}',
'**/*.{png,jpg,jpeg,gif,svg,ico}',
'**/*.{woff,woff2,eot,ttf,otf}',
...(injectManifest.globPatterns || []),
],
// those attributes are not overrideable
swDest,
swSrc: swDest,
globDirectory: props.outDir,
});
},
};
}
module.exports = plugin;
plugin.validateOptions = function validateOptions({validate, options}) {
return validate(PluginOptionSchema, options);
};