diff --git a/package.json b/package.json index d15d8cc24812..e07020cc5ee6 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,10 @@ "build:v2": "yarn workspace docusaurus-2-website build", "serve:v1": "serve website-1.x/build/docusaurus", "serve:v2": "serve website/build", + "serve:v2:ssl": "yarn serve:v2:ssl:gencert && yarn serve:v2:ssl:message && yarn serve:v2:ssl:serve", + "serve:v2:ssl:gencert": "openssl req -x509 -nodes -days 365 -newkey rsa:4096 -subj \"/C=US/ST=Docusaurus/L=Anywhere/O=Dis/CN=localhost\" -keyout ./website/.docusaurus/selfsigned.key -out ./website/.docusaurus/selfsigned.crt", + "serve:v2:ssl:message": "echo '\n\n\nServing Docusaurus with HTTPS on localhost requires to disable the Chrome security: chrome://flags/#allow-insecure-localhost\n\n\n'", + "serve:v2:ssl:serve": "serve website/build --ssl-cert ./website/.docusaurus/selfsigned.crt --ssl-key ./website/.docusaurus/selfsigned.key", "changelog": "lerna-changelog", "postinstall": "yarn build:packages", "prettier": "prettier --config .prettierrc --write \"**/*.{js,ts}\"", diff --git a/packages/docusaurus-plugin-pwa/package.json b/packages/docusaurus-plugin-pwa/package.json new file mode 100644 index 000000000000..e04c7aa068f0 --- /dev/null +++ b/packages/docusaurus-plugin-pwa/package.json @@ -0,0 +1,29 @@ +{ + "name": "@docusaurus/plugin-pwa", + "version": "2.0.0-alpha.58", + "description": "Docusaurus Plugin to add PWA support", + "main": "src/index.js", + "publishConfig": { + "access": "public" + }, + "license": "MIT", + "dependencies": { + "@babel/preset-env": "^7.9.0", + "@babel/plugin-proposal-optional-chaining": "^7.10.3", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.1", + "@hapi/joi": "^17.1.1", + "babel-loader": "^8.1.0", + "core-js": "^2.6.5", + "terser-webpack-plugin": "^2.3.5", + "webpack-merge": "^4.2.2", + "webpack": "^4.41.2", + "workbox-build": "^5.1.2", + "workbox-precaching": "^5.1.2", + "workbox-window": "^5.1.2" + }, + "peerDependencies": { + "@docusaurus/core": "^2.0.0", + "react": "^16.8.4", + "react-dom": "^16.8.4" + } +} diff --git a/packages/docusaurus-plugin-pwa/src/index.js b/packages/docusaurus-plugin-pwa/src/index.js new file mode 100644 index 000000000000..dcdcf0b6b094 --- /dev/null +++ b/packages/docusaurus-plugin-pwa/src/index.js @@ -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); +}; diff --git a/packages/docusaurus-plugin-pwa/src/pluginOptionSchema.js b/packages/docusaurus-plugin-pwa/src/pluginOptionSchema.js new file mode 100644 index 000000000000..bfcc4d37f129 --- /dev/null +++ b/packages/docusaurus-plugin-pwa/src/pluginOptionSchema.js @@ -0,0 +1,43 @@ +/** + * 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 Joi = require('@hapi/joi'); +const path = require('path'); + +const DEFAULT_OPTIONS = { + debug: false, + offlineModeActivationStrategies: ['appInstalled', 'queryString'], + injectManifestConfig: {}, + pwaHead: [], + swCustom: undefined, + swRegister: path.join(__dirname, 'registerSw.js'), + reloadPopup: '@theme/PwaReloadPopup', +}; + +exports.PluginOptionSchema = Joi.object({ + debug: Joi.bool().default(DEFAULT_OPTIONS.debug), + offlineModeActivationStrategies: Joi.array() + .items( + Joi.string() + .valid('appInstalled', 'queryString', 'mobile', 'saveData', 'always') + .required(), + ) + .default(DEFAULT_OPTIONS.offlineModeActivationStrategies), + injectManifestConfig: Joi.object().default( + DEFAULT_OPTIONS.injectManifestConfig, + ), + pwaHead: Joi.array() + .items(Joi.object({tagName: Joi.string().required()}).unknown().required()) + .default(DEFAULT_OPTIONS.pwaHead), + swCustom: Joi.string(), + swRegister: Joi.alternatives() + .try(Joi.string(), Joi.bool().valid(false)) + .default(DEFAULT_OPTIONS.swRegister), + reloadPopup: Joi.alternatives() + .try(Joi.string(), Joi.bool().valid(false)) + .default(DEFAULT_OPTIONS.reloadPopup), +}); diff --git a/packages/docusaurus-plugin-pwa/src/registerSw.js b/packages/docusaurus-plugin-pwa/src/registerSw.js new file mode 100644 index 000000000000..a0504cf73896 --- /dev/null +++ b/packages/docusaurus-plugin-pwa/src/registerSw.js @@ -0,0 +1,211 @@ +/** + * 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. + */ + +// First: read the env variables (provided by Webpack) +/* eslint-disable prefer-destructuring */ +const PWA_SERVICE_WORKER_URL = process.env.PWA_SERVICE_WORKER_URL; +const PWA_RELOAD_POPUP = process.env.PWA_RELOAD_POPUP; +const PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES = + process.env.PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES; +const PWA_DEBUG = process.env.PWA_DEBUG; +/* eslint-enable prefer-destructuring */ + +const debug = PWA_DEBUG; // shortcut + +async function clearRegistrations() { + const registrations = await navigator.serviceWorker.getRegistrations(); + if (debug && registrations.length > 0) { + console.log( + `[Docusaurus-PWA][registerSW]: unregister service workers`, + registrations, + ); + } + registrations.forEach((registration) => { + registration.registration.unregister(); + }); + window.location.reload(); +} + +const MAX_MOBILE_WIDTH = 940; +const APP_INSTALLED_KEY = 'docusaurus.pwa.appInstalled'; + +const OfflineModeActivationStrategiesImplementations = { + always: () => true, + mobile: () => window.innerWidth <= MAX_MOBILE_WIDTH, + saveData: () => !!(navigator.connection && navigator.connection.saveData), + appInstalled: () => !!localStorage.getItem(APP_INSTALLED_KEY), + queryString: () => + new URLSearchParams(window.location.search).get('offlineMode') === 'true', +}; + +function isOfflineModeEnabled() { + const activeStrategies = PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES.filter( + (strategyName) => { + const strategyImpl = + OfflineModeActivationStrategiesImplementations[strategyName]; + return strategyImpl(); + }, + ); + const enabled = activeStrategies.length > 0; + if (debug) { + const logObject = { + activeStrategies, + availableStrategies: PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES, + }; + if (enabled) { + console.log( + '[Docusaurus-PWA][registerSW]: offline mode enabled, because of activation strategies', + logObject, + ); + } else { + console.log( + '[Docusaurus-PWA][registerSW]: offline mode disabled, because none of the offlineModeActivationStrategies could be used', + logObject, + ); + } + } + return enabled; +} + +function createServiceWorkerUrl(params) { + const paramsQueryString = JSON.stringify(params); + const url = `${PWA_SERVICE_WORKER_URL}?params=${encodeURIComponent( + paramsQueryString, + )}`; + if (debug) { + console.log(`[Docusaurus-PWA][registerSW]: service worker url`, { + url, + params, + }); + } + return url; +} + +(async () => { + if (typeof window === 'undefined') { + return; + } + + if (debug) { + console.log('[Docusaurus-PWA][registerSW]: debug mode enabled'); + } + + if ('serviceWorker' in navigator) { + const {Workbox} = await import('workbox-window'); + + const offlineMode = isOfflineModeEnabled(); + + const url = createServiceWorkerUrl({offlineMode, debug}); + const wb = new Workbox(url); + + const registration = await wb.register(); + + const sendSkipWaiting = () => wb.messageSW({type: 'SKIP_WAITING'}); + + const handleServiceWorkerWaiting = async () => { + if (debug) { + console.log('[Docusaurus-PWA][registerSW]: handleServiceWorkerWaiting'); + } + // Immediately load new service worker when files aren't cached + if (!offlineMode) { + sendSkipWaiting(); + } else if (PWA_RELOAD_POPUP) { + const renderReloadPopup = (await import('./renderReloadPopup')).default; + renderReloadPopup({ + onReload() { + wb.addEventListener('controlling', () => { + window.location.reload(); + }); + sendSkipWaiting(); + }, + }); + } + }; + + if (debug) { + if (registration.active) { + console.log( + '[Docusaurus-PWA][registerSW]: registration.active', + registration, + ); + } + if (registration.installing) { + console.log( + '[Docusaurus-PWA][registerSW]: registration.installing', + registration, + ); + } + if (registration.waiting) { + console.log( + '[Docusaurus-PWA][registerSW]: registration.waiting', + registration, + ); + } + } + + // Update service worker if the next one is already in the waiting state. + // This happens when the user doesn't click on `reload` in the popup. + if (registration.waiting) { + handleServiceWorkerWaiting(); + } + + // Update the current service worker when the next one has finished + // installing and transitions to waiting state. + wb.addEventListener('waiting', (event) => { + if (debug) { + console.log('[Docusaurus-PWA][registerSW]: event waiting', event); + } + handleServiceWorkerWaiting(); + }); + + // Update current service worker if the next one finishes installing and + // moves to waiting state in another tab. + wb.addEventListener('externalwaiting', (event) => { + if (debug) { + console.log( + '[Docusaurus-PWA][registerSW]: event externalwaiting', + event, + ); + } + handleServiceWorkerWaiting(); + }); + + window.addEventListener('appinstalled', (event) => { + if (debug) { + console.log('[Docusaurus-PWA][registerSW]: event appinstalled', event); + } + localStorage.setItem(APP_INSTALLED_KEY, true); + + // After the app is installed, we register a service worker with the path + // `/sw?enabled`. Since the previous service worker was `/sw`, it'll be + // treated as a new one. The previous registration will need to be + // cleared, otherwise the reload popup will show. + clearRegistrations(); + }); + + window.addEventListener('beforeinstallprompt', (event) => { + if (debug) { + console.log('[Docusaurus-PWA][registerSW]: event appinstalled', event); + } + // TODO instead of default browser install UI, show custom docusaurus prompt? + // event.preventDefault(); + + if (localStorage.getItem(APP_INSTALLED_KEY)) { + localStorage.removeItem(APP_INSTALLED_KEY); + + // After uninstalling the app, if the user doesn't clear all data, then + // the previous service worker will continue serving cached files. We + // need to clear registrations and reload, otherwise the popup will show. + clearRegistrations(); + } + }); + } else if (debug) { + console.log( + '[Docusaurus-PWA][registerSW]: browser does not support service workers', + ); + } +})(); diff --git a/packages/docusaurus-plugin-pwa/src/renderReloadPopup.js b/packages/docusaurus-plugin-pwa/src/renderReloadPopup.js new file mode 100644 index 000000000000..b26202ac3c5b --- /dev/null +++ b/packages/docusaurus-plugin-pwa/src/renderReloadPopup.js @@ -0,0 +1,26 @@ +/** + * 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. + */ + +import React from 'react'; +import {render} from 'react-dom'; + +const POPUP_CONTAINER_ID = 'pwa-popup-container'; + +const getContainer = () => document.getElementById(POPUP_CONTAINER_ID); + +const createContainer = () => { + const container = document.createElement('div'); + container.id = POPUP_CONTAINER_ID; + document.body.appendChild(container); + return container; +}; + +export default async function renderReloadPopup(props) { + const container = getContainer() || createContainer(); + const {default: ReloadPopup} = await import(process.env.PWA_RELOAD_POPUP); + render(, container); +} diff --git a/packages/docusaurus-plugin-pwa/src/sw.js b/packages/docusaurus-plugin-pwa/src/sw.js new file mode 100644 index 000000000000..458d760fdbe5 --- /dev/null +++ b/packages/docusaurus-plugin-pwa/src/sw.js @@ -0,0 +1,120 @@ +/** + * 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. + */ +/* eslint-disable no-restricted-globals */ + +import {PrecacheController} from 'workbox-precaching'; + +function parseSwParams() { + const params = JSON.parse( + new URLSearchParams(self.location.search).get('params'), + ); + if (params.debug) { + console.log('[Docusaurus-PWA][SW]: Service Worker params:', params); + } + return params; +} + +// doc advise against dynamic imports in SW +// https://developers.google.com/web/tools/workbox/guides/using-bundlers#code_splitting_and_dynamic_imports +// https://twitter.com/sebastienlorber/status/1280155204575518720 +// but I think it's working fine as it's inlined by webpack, need to double check? +async function runSWCustomCode(params) { + if (process.env.PWA_SW_CUSTOM) { + const customSW = await import(process.env.PWA_SW_CUSTOM); + if (typeof customSW.default === 'function') { + customSW.default(params); + } else if (params.debug) { + console.warn( + '[Docusaurus-PWA][SW]: swCustom should have a default export function', + ); + } + } +} + +/** + * Gets different possible variations for a request URL. Similar to + * https://git.io/JvixK + * + * @param {String} url + */ +function getPossibleURLs(url) { + const possibleURLs = []; + const urlObject = new URL(url, self.location.href); + + if (urlObject.origin !== self.location.origin) { + return possibleURLs; + } + + // Ignore search params and hash + urlObject.search = ''; + urlObject.hash = ''; + + // /blog.html + possibleURLs.push(urlObject.href); + + // /blog/ => /blog/index.html + if (urlObject.pathname.endsWith('/')) { + possibleURLs.push(`${urlObject.href}index.html`); + } else { + // /blog => /blog/index.html + possibleURLs.push(`${urlObject.href}/index.html`); + } + + return possibleURLs; +} + +(async () => { + const params = parseSwParams(); + + const precacheManifest = self.__WB_MANIFEST; + const controller = new PrecacheController(); + + if (params.offlineMode) { + controller.addToCacheList(precacheManifest); + } + + await runSWCustomCode(params); + + self.addEventListener('install', (event) => { + event.waitUntil(controller.install()); + }); + + self.addEventListener('activate', (event) => { + event.waitUntil(controller.activate()); + }); + + self.addEventListener('fetch', async (event) => { + if (params.offlineMode) { + const requestURL = event.request.url; + const possibleURLs = getPossibleURLs(requestURL); + for (let i = 0; i < possibleURLs.length; i += 1) { + const possibleURL = possibleURLs[i]; + const cacheKey = controller.getCacheKeyForURL(possibleURL); + if (cacheKey) { + if (params.debug) { + console.log('[Docusaurus-PWA][SW]: serving cached asset', { + requestURL, + possibleURL, + possibleURLs, + cacheKey, + }); + } + event.respondWith(caches.match(cacheKey)); + break; + } + } + } + }); + + self.addEventListener('message', async (event) => { + const type = event.data && event.data.type; + + if (type === 'SKIP_WAITING') { + self.skipWaiting(); + } + }); +})(); diff --git a/packages/docusaurus-plugin-pwa/src/theme/PwaReloadPopup/index.js b/packages/docusaurus-plugin-pwa/src/theme/PwaReloadPopup/index.js new file mode 100644 index 000000000000..3324cb87c0d5 --- /dev/null +++ b/packages/docusaurus-plugin-pwa/src/theme/PwaReloadPopup/index.js @@ -0,0 +1,42 @@ +/** + * 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. + */ + +import React, {useState} from 'react'; +import clsx from 'clsx'; + +import styles from './styles.module.css'; + +export default function PwaReloadPopup({onReload}) { + const [isVisible, setIsVisible] = useState(true); + + return ( + isVisible && ( +
+

New version available

+
+ + + +
+
+ ) + ); +} diff --git a/packages/docusaurus-plugin-pwa/src/theme/PwaReloadPopup/styles.module.css b/packages/docusaurus-plugin-pwa/src/theme/PwaReloadPopup/styles.module.css new file mode 100644 index 000000000000..1d341c114e17 --- /dev/null +++ b/packages/docusaurus-plugin-pwa/src/theme/PwaReloadPopup/styles.module.css @@ -0,0 +1,39 @@ +/** + * 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. + */ + +.popup { + position: fixed; + bottom: 16px; + right: 16px; + width: 344px; + display: flex; + align-items: center; + justify-content: space-between; + z-index: 999; +} + +.popup p { + margin: 0; +} + +@media screen and (max-width: 500px) { + .popup { + width: 100%; + bottom: 0; + right: 0; + } +} + +.buttonContainer { + display: flex; + align-items: center; +} + +.buttonContainer :global(.close) { + margin: 0; + padding: 0; +} diff --git a/packages/docusaurus/package.json b/packages/docusaurus/package.json index 70c5980c11f9..39f306247335 100644 --- a/packages/docusaurus/package.json +++ b/packages/docusaurus/package.json @@ -31,6 +31,7 @@ }, "devDependencies": { "@docusaurus/module-type-aliases": "^2.0.0-alpha.58", + "@types/detect-port": "^1.3.0", "@types/hapi__joi": "^17.1.2" }, "dependencies": { diff --git a/packages/docusaurus/src/choosePort.ts b/packages/docusaurus/src/choosePort.ts index 9bd5ee6c0858..8a07bfe2e77a 100644 --- a/packages/docusaurus/src/choosePort.ts +++ b/packages/docusaurus/src/choosePort.ts @@ -89,6 +89,7 @@ export default async function choosePort( host: string, defaultPort: number, ): Promise { + // @ts-expect-error: bad lib typedef? return detect(defaultPort, host).then( (port) => new Promise((resolve) => { diff --git a/packages/docusaurus/src/commands/build.ts b/packages/docusaurus/src/commands/build.ts index 92cec8bb4512..d1ab30645607 100644 --- a/packages/docusaurus/src/commands/build.ts +++ b/packages/docusaurus/src/commands/build.ts @@ -10,7 +10,7 @@ import CopyWebpackPlugin from 'copy-webpack-plugin'; import fs from 'fs-extra'; import path from 'path'; import ReactLoadableSSRAddon from 'react-loadable-ssr-addon'; -import webpack, {Configuration, Plugin, Stats} from 'webpack'; +import {Configuration, Plugin} from 'webpack'; import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer'; import merge from 'webpack-merge'; import {STATIC_DIR_NAME} from '../constants'; @@ -18,43 +18,9 @@ import {load} from '../server'; import {BuildCLIOptions, Props} from '@docusaurus/types'; import createClientConfig from '../webpack/client'; import createServerConfig from '../webpack/server'; -import {applyConfigureWebpack} from '../webpack/utils'; +import {compile, applyConfigureWebpack} from '../webpack/utils'; import CleanWebpackPlugin from '../webpack/plugins/CleanWebpackPlugin'; -function compile(config: Configuration[]): Promise { - return new Promise((resolve, reject) => { - const compiler = webpack(config); - compiler.run((err, stats) => { - if (err) { - reject(err); - } - if (stats.hasErrors()) { - stats.toJson('errors-only').errors.forEach((e) => { - console.error(e); - }); - reject(new Error('Failed to compile with errors.')); - } - if (stats.hasWarnings()) { - // Custom filtering warnings (see https://github.com/webpack/webpack/issues/7841). - let {warnings} = stats.toJson('errors-warnings'); - const warningsFilter = ((config[0].stats as Stats.ToJsonOptionsObject) - ?.warningsFilter || []) as any[]; - - if (Array.isArray(warningsFilter)) { - warnings = warnings.filter((warning) => - warningsFilter.every((str) => !warning.includes(str)), - ); - } - - warnings.forEach((warning) => { - console.warn(warning); - }); - } - resolve(); - }); - }); -} - export default async function build( siteDir: string, cliOptions: Partial = {}, diff --git a/packages/docusaurus/src/webpack/utils.ts b/packages/docusaurus/src/webpack/utils.ts index e8f6e3f95ad9..dd837a6ae738 100644 --- a/packages/docusaurus/src/webpack/utils.ts +++ b/packages/docusaurus/src/webpack/utils.ts @@ -8,7 +8,7 @@ import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import env from 'std-env'; import merge from 'webpack-merge'; -import {Configuration, Loader, RuleSetRule} from 'webpack'; +import webpack, {Configuration, Loader, RuleSetRule, Stats} from 'webpack'; import {TransformOptions} from '@babel/core'; import {ConfigureWebpackFn} from '@docusaurus/types'; import {version as cacheLoaderVersion} from 'cache-loader/package.json'; @@ -138,6 +138,40 @@ export function applyConfigureWebpack( return config; } +export function compile(config: Configuration[]): Promise { + return new Promise((resolve, reject) => { + const compiler = webpack(config); + compiler.run((err, stats) => { + if (err) { + reject(err); + } + if (stats.hasErrors()) { + stats.toJson('errors-only').errors.forEach((e) => { + console.error(e); + }); + reject(new Error('Failed to compile with errors.')); + } + if (stats.hasWarnings()) { + // Custom filtering warnings (see https://github.com/webpack/webpack/issues/7841). + let {warnings} = stats.toJson('errors-warnings'); + const warningsFilter = ((config[0].stats as Stats.ToJsonOptionsObject) + ?.warningsFilter || []) as any[]; + + if (Array.isArray(warningsFilter)) { + warnings = warnings.filter((warning) => + warningsFilter.every((str) => !warning.includes(str)), + ); + } + + warnings.forEach((warning) => { + console.warn(warning); + }); + } + resolve(); + }); + }); +} + // Inspired by https://github.com/gatsbyjs/gatsby/blob/8e6e021014da310b9cc7d02e58c9b3efe938c665/packages/gatsby/src/utils/webpack-utils.ts#L447 export function getFileLoaderUtils() { const assetsRelativeRoot = 'assets/'; diff --git a/website/docs/using-plugins.md b/website/docs/using-plugins.md index 174f47a3e2f4..845a6140ecb4 100644 --- a/website/docs/using-plugins.md +++ b/website/docs/using-plugins.md @@ -612,3 +612,276 @@ module.exports = { ], }; ``` + +### `@docusaurus/plugin-pwa` + +Docusaurus Plugin to add PWA support using [Workbox](https://developers.google.com/web/tools/workbox). This plugin generates a [Service Worker](https://developers.google.com/web/fundamentals/primers/service-workers) in production build only, and allows you to create fully PWA-compliant documentation site with offline and installation support. + +```bash npm2yarn +npm install --save @docusaurus/plugin-pwa +``` + +Create a [PWA manifest](https://web.dev/add-manifest/) at `./static/manifest.json`. + +Modify `docusaurus.config.js` with a minimal PWA config, like: + +```js title="docusaurus.config.js" +module.exports = { + plugins: [ + [ + '@docusaurus/plugin-pwa', + { + debug: true, + offlineModeActivationStrategies: ['appInstalled', 'queryString'], + pwaHead: [ + { + tagName: 'link', + rel: 'icon', + href: '/img/docusaurus.png', + }, + { + tagName: 'link', + rel: 'manifest', + href: '/manifest.json', // your PWA manifest + }, + { + tagName: 'meta', + name: 'theme-color', + content: 'rgb(37, 194, 160)', + }, + ], + }, + ], + ], +}; +``` + +#### Progressive Web App + +Having a service worker installed is not enough to make your application a PWA. You'll need to at least include a [Web App Manifest](https://developer.mozilla.org/en-US/docs/Web/Manifest) and have the correct tags in `` ([Options > pwaHead](#pwahead)). + +After deployment, you can use [Lighthouse](https://developers.google.com/web/tools/lighthouse) to run an audit on your site. + +For a more exhaustive list of what it takes for your site to be a PWA, refer to the [PWA Checklist](https://developers.google.com/web/progressive-web-apps/checklist) + +#### App installation support + +If your browser supports it, you should be able to install a Docusaurus site as an app. + +![pwa_install.gif](/img/pwa_install.gif) + +#### Offline mode (precaching) + +We enable users to browse a Docusaurus site offline, by using service-worker precaching. + +> ### [What is Precaching?](https://developers.google.com/web/tools/workbox/modules/workbox-precaching) +> +> One feature of service workers is the ability to save a set of files to the cache when the service worker is installing. This is often referred to as "precaching", since you are caching content ahead of the service worker being used. +> +> The main reason for doing this is that it gives developers control over the cache, meaning they can determine when and how long a file is cached as well as serve it to the browser without going to the network, meaning it can be used to create web apps that work offline. +> +> Workbox takes a lot of the heavy lifting out of precaching by simplifying the API and ensuring assets are downloaded efficiently. + +By default, offline mode is enabled when the site is installed as an app. See the `offlineModeActivationStrategies` option for details. + +After the site has been precached, the service worker will serve cached responses for later visits. When a new build is deployed along with a new service worker, the new one will begin installing and eventually move to a waiting state. During this waiting state, a reload popup will show and ask the user to reload the page for new content. Until the user either clears the application cache or clicks the `reload` button on the popup, the service worker will continue serving the old content. + +:::caution + +Offline mode / precaching requires downloading all the static assets of the site ahead of time, and can consume unnecessary bandwidth. It may not be a good idea to activate it for all kind of sites. + +::: + +##### Options + +##### `debug` + +- Type: `boolean` +- Default: `false` + +Turn debug mode on: + +- Workbox logs +- Additional Docusaurus logs +- Unoptimized SW file output +- Source maps + +##### `offlineModeActivationStrategies` + +- Type: `Array<'appInstalled' | 'mobile' | 'saveData'| 'queryString' | 'always'>` +- Default: `['appInstalled','queryString']` + +Strategies used to turn the offline mode on: + +- `appInstalled`: activates for users having installed the site as an app +- `queryString`: activates if queryString contains `offlineMode=true` (convenient for PWA debugging) +- `mobile`: activates for mobile users (width <= 940px) +- `saveData`: activates for users with `navigator.connection.saveData === true` +- `always`: activates for all users + +:::caution + +Use this carefully: some users may not like to be forced to use the offline mode. + +::: + +##### `injectManifestConfig` + +[Workbox options](https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-build#.injectManifest) to pass to `workbox.injectManifest()`. This gives you control over which assets will be precached, and be available offline. + +- Type: `InjectManifestOptions` +- Default: `{}` + +```js title="docusaurus.config.js" +module.exports = { + plugins: [ + [ + '@docusaurus/plugin-pwa', + { + injectManifestConfig: { + manifestTransforms: [ + //... + ], + modifyURLPrefix: { + //... + }, + // We already add regular static assets (html, images...) to be available offline + // You can add more files according to your needs + globPatterns: ['**/*.{pdf,docx,xlsx}'], + // ... + }, + }, + ], + ], +}; +``` + +##### `reloadPopup` + +- Type: `string | false` +- Default: `'@theme/PwaReloadPopup'` + +Module path to reload popup component. This popup is rendered when a new service worker is waiting to be installed, and we suggest a reload to the user. + +Passing `false` will disable the popup, but this is not recommended: users won't have a way to get up-to-date content. + +A custom component can be used, as long as it accepts `onReload` as a prop. The `onReload` callback should be called when the `reload` button is clicked. This will tell the service worker to install the waiting service worker and reload the page. + +```ts +interface PwaReloadPopupProps { + onReload: () => void; +} +``` + +The default theme includes an implementation for the reload popup and uses [Infima Alerts](https://facebookincubator.github.io/infima/docs/components/alert). + +![pwa_reload.gif](/img/pwa_reload.gif) + +##### `pwaHead` + +- Type: `Array<{ tagName: string } & Record>` +- Default: `[]` + +Array of objects containing `tagName` and key-value pairs for attributes to inject into the `` tag. Technically you can inject any head tag through this, but it's ideally used for tags to make your site PWA compliant. Here's a list of tag to make your app fully compliant: + +```js +module.exports = { + plugins: [ + [ + '@docusaurus/plugin-pwa', + { + pwaHead: [ + { + tagName: 'link', + rel: 'icon', + href: '/img/docusaurus.png', + }, + { + tagName: 'link', + rel: 'manifest', + href: '/manifest.json', + }, + { + tagName: 'meta', + name: 'theme-color', + content: 'rgb(37, 194, 160)', + }, + { + tagName: 'meta', + name: 'apple-mobile-web-app-capable', + content: 'yes', + }, + { + tagName: 'meta', + name: 'apple-mobile-web-app-status-bar-style', + content: '#000', + }, + { + tagName: 'link', + rel: 'apple-touch-icon', + href: '/img/docusaurus.png', + }, + { + tagName: 'link', + rel: 'mask-icon', + href: '/img/docusaurus.svg', + color: 'rgb(37, 194, 160)', + }, + { + tagName: 'meta', + name: 'msapplication-TileImage', + content: '/img/docusaurus.png', + }, + { + tagName: 'meta', + name: 'msapplication-TileColor', + content: '#000', + }, + ], + }, + ], + ], +}; +``` + +##### `swCustom` + +- Type: `string | undefined` +- Default: `undefined` + +Useful for additional Workbox rules. You can do whatever a service worker can do here, and use the full power of workbox libraries. The code is transpiled, so you can use modern ES6+ syntax here. + +For example, to cache files from external routes: + +```js +import {registerRoute} from 'workbox-routing'; +import {StaleWhileRevalidate} from 'workbox-strategies'; + +// default fn export receiving some useful params +export default function swCustom(params) { + const { + debug, // :boolean + offlineMode, // :boolean + } = params; + + // Cache responses from external resources + registerRoute((context) => { + return [ + /graph\.facebook\.com\/.*\/picture/, + /netlify\.com\/img/, + /avatars1\.githubusercontent/, + ].some((regex) => context.url.href.match(regex)); + }, new StaleWhileRevalidate()); +} +``` + +The module should have a `default` function export, and receives some params. + +##### `swRegister` + +- Type: `string | false` +- Default: `'docusaurus-plugin-pwa/src/registerSW.js'` + +Adds an entry before the Docusaurus app so that registration can happen before the app runs. The default `registerSW.js` file is enough for simple registration. + +Passing `false` will disable registration entirely. diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 219060905248..0872fd35839a 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +const path = require('path'); const versions = require('./versions.json'); const allDocHomesPaths = [ @@ -49,6 +50,63 @@ module.exports = { steps: 2, // the max number of images generated between min and max (inclusive) }, ], + [ + '@docusaurus/plugin-pwa', + { + debug: false, + offlineModeActivationStrategies: ['appInstalled', 'queryString'], + // swRegister: false, + swCustom: path.resolve(__dirname, 'src/sw.js'), + pwaHead: [ + { + tagName: 'link', + rel: 'icon', + href: '/img/docusaurus.png', + }, + { + tagName: 'link', + rel: 'manifest', + href: '/manifest.json', + }, + { + tagName: 'meta', + name: 'theme-color', + content: 'rgb(37, 194, 160)', + }, + { + tagName: 'meta', + name: 'apple-mobile-web-app-capable', + content: 'yes', + }, + { + tagName: 'meta', + name: 'apple-mobile-web-app-status-bar-style', + content: '#000', + }, + { + tagName: 'link', + rel: 'apple-touch-icon', + href: '/img/docusaurus.png', + }, + { + tagName: 'link', + rel: 'mask-icon', + href: '/img/docusaurus.svg', + color: 'rgb(62, 204, 94)', + }, + { + tagName: 'meta', + name: 'msapplication-TileImage', + content: '/img/docusaurus.png', + }, + { + tagName: 'meta', + name: 'msapplication-TileColor', + content: '#000', + }, + ], + }, + ], ], presets: [ [ diff --git a/website/package.json b/website/package.json index eb741472015b..74c95eb77051 100644 --- a/website/package.json +++ b/website/package.json @@ -12,13 +12,16 @@ "@docusaurus/core": "^2.0.0-alpha.58", "@docusaurus/plugin-client-redirects": "^2.0.0-alpha.58", "@docusaurus/plugin-ideal-image": "^2.0.0-alpha.58", + "@docusaurus/plugin-pwa": "^2.0.0-alpha.58", "@docusaurus/preset-classic": "^2.0.0-alpha.58", "@docusaurus/theme-live-codeblock": "^2.0.0-alpha.58", "clsx": "^1.1.1", "color": "^3.1.2", "npm-to-yarn": "^1.0.0-2", + "react-dom": "^16.8.4", "react": "^16.8.4", - "react-dom": "^16.8.4" + "workbox-routing": "^5.0.0", + "workbox-strategies": "^5.0.0" }, "browserslist": { "production": [ diff --git a/website/src/sw.js b/website/src/sw.js new file mode 100644 index 000000000000..87297c2e8e08 --- /dev/null +++ b/website/src/sw.js @@ -0,0 +1,24 @@ +/** + * 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. + */ + +import {registerRoute} from 'workbox-routing'; +import {StaleWhileRevalidate} from 'workbox-strategies'; + +export default function swCustom(params) { + if (params.debug) { + console.log('[Docusaurus-PWA][SW]: running swCustom code', params); + } + + // Cache responses from external resources + registerRoute((context) => { + return [ + /graph\.facebook\.com\/.*\/picture/, + /netlify\.com\/img/, + /avatars1\.githubusercontent/, + ].some((regex) => context.url.href.match(regex)); + }, new StaleWhileRevalidate()); +} diff --git a/website/static/img/icons/icon-128x128.png b/website/static/img/icons/icon-128x128.png new file mode 100644 index 000000000000..664a1c1da08e Binary files /dev/null and b/website/static/img/icons/icon-128x128.png differ diff --git a/website/static/img/icons/icon-144x144.png b/website/static/img/icons/icon-144x144.png new file mode 100644 index 000000000000..e48ccb4e2526 Binary files /dev/null and b/website/static/img/icons/icon-144x144.png differ diff --git a/website/static/img/icons/icon-152x152.png b/website/static/img/icons/icon-152x152.png new file mode 100644 index 000000000000..d048e21c2679 Binary files /dev/null and b/website/static/img/icons/icon-152x152.png differ diff --git a/website/static/img/icons/icon-192x192.png b/website/static/img/icons/icon-192x192.png new file mode 100644 index 000000000000..34660b75db21 Binary files /dev/null and b/website/static/img/icons/icon-192x192.png differ diff --git a/website/static/img/icons/icon-384x384.png b/website/static/img/icons/icon-384x384.png new file mode 100644 index 000000000000..93ae4b0be1fd Binary files /dev/null and b/website/static/img/icons/icon-384x384.png differ diff --git a/website/static/img/icons/icon-512x512.png b/website/static/img/icons/icon-512x512.png new file mode 100644 index 000000000000..93ae4b0be1fd Binary files /dev/null and b/website/static/img/icons/icon-512x512.png differ diff --git a/website/static/img/icons/icon-72x72.png b/website/static/img/icons/icon-72x72.png new file mode 100644 index 000000000000..81d75ddabbef Binary files /dev/null and b/website/static/img/icons/icon-72x72.png differ diff --git a/website/static/img/icons/icon-96x96.png b/website/static/img/icons/icon-96x96.png new file mode 100644 index 000000000000..32c2f5660a70 Binary files /dev/null and b/website/static/img/icons/icon-96x96.png differ diff --git a/website/static/img/pwa_install.gif b/website/static/img/pwa_install.gif new file mode 100644 index 000000000000..d44c6198fa0f Binary files /dev/null and b/website/static/img/pwa_install.gif differ diff --git a/website/static/img/pwa_reload.gif b/website/static/img/pwa_reload.gif new file mode 100755 index 000000000000..683a9ca55aa9 Binary files /dev/null and b/website/static/img/pwa_reload.gif differ diff --git a/website/static/manifest.json b/website/static/manifest.json new file mode 100644 index 000000000000..ee1d064e7b70 --- /dev/null +++ b/website/static/manifest.json @@ -0,0 +1,51 @@ +{ + "name": "Docusaurus v2", + "short_name": "Docusaurus", + "theme_color": "#2196f3", + "background_color": "#424242", + "display": "standalone", + "scope": "/", + "start_url": "/", + "icons": [ + { + "src": "/img/icons/icon-72x72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "/img/icons/icon-96x96.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "/img/icons/icon-128x128.png", + "sizes": "128x128", + "type": "image/png" + }, + { + "src": "/img/icons/icon-144x144.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "/img/icons/icon-152x152.png", + "sizes": "152x152", + "type": "image/png" + }, + { + "src": "/img/icons/icon-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/img/icons/icon-384x384.png", + "sizes": "384x384", + "type": "image/png" + }, + { + "src": "/img/icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/yarn.lock b/yarn.lock index 73a604e5bc93..0619fa96294b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -71,7 +71,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@>=7.2.2", "@babel/core@^7.7.5", "@babel/core@^7.9.0": +"@babel/core@>=7.2.2", "@babel/core@^7.7.5", "@babel/core@^7.8.4", "@babel/core@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== @@ -264,7 +264,7 @@ dependencies: "@babel/types" "^7.8.3" -"@babel/helper-module-imports@^7.8.3": +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== @@ -989,7 +989,7 @@ core-js "^2.6.5" regenerator-runtime "^0.13.4" -"@babel/preset-env@^7.9.0": +"@babel/preset-env@^7.8.4", "@babel/preset-env@^7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.0.tgz#a5fc42480e950ae8f5d9f8f2bbc03f52722df3a8" integrity sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ== @@ -2594,6 +2594,32 @@ os-homedir "^1.0.1" regexpu-core "^4.5.4" +"@rollup/plugin-node-resolve@^7.1.1": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.1.tgz#8c6e59c4b28baf9d223028d0e450e06a485bb2b7" + integrity sha512-14ddhD7TnemeHE97a4rLOhobfYvUVcaYuqTnL8Ti7Jxi9V9Jr5LY7Gko4HZ5k4h4vqQM0gBQt6tsp9xXW94WPA== + dependencies: + "@rollup/pluginutils" "^3.0.6" + "@types/resolve" "0.0.8" + builtin-modules "^3.1.0" + is-module "^1.0.0" + resolve "^1.14.2" + +"@rollup/plugin-replace@^2.3.1": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-2.3.1.tgz#16fb0563628f9e6c6ef9e05d48d3608916d466f5" + integrity sha512-qDcXj2VOa5+j0iudjb+LiwZHvBRRgWbHPhRmo1qde2KItTjuxDVQO21rp9/jOlzKR5YO0EsgRQoyox7fnL7y/A== + dependencies: + "@rollup/pluginutils" "^3.0.4" + magic-string "^0.25.5" + +"@rollup/pluginutils@^3.0.4", "@rollup/pluginutils@^3.0.6": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.0.8.tgz#4e94d128d94b90699e517ef045422960d18c8fde" + integrity sha512-rYGeAc4sxcZ+kPG/Tw4/fwJODC3IXHYDH4qusdN/b6aLw5LPUbzpecYbEJh4sVQGPFJxd2dBU4kc1H3oy9/bnw== + dependencies: + estree-walker "^1.0.1" + "@samverschueren/stream-to-observable@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f" @@ -2618,6 +2644,14 @@ dependencies: type-detect "4.0.8" +"@surma/rollup-plugin-off-main-thread@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-1.1.1.tgz#f30bd28de9c5ec614ab9d5143eb1bd809a15520c" + integrity sha512-ZvxbzJ6oQusBnW2CDYpdNZ2yhhHlzfiREcKBgtCfGjkhNnRQcak1qh+3Y61nGq9OI3IVsizh/eBjRJVAlHprwA== + dependencies: + ejs "^2.6.1" + magic-string "^0.25.0" + "@svgr/babel-plugin-add-jsx-attribute@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906" @@ -2808,6 +2842,11 @@ dependencies: "@types/node" "*" +"@types/detect-port@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@types/detect-port/-/detect-port-1.3.0.tgz#3e9cbd049ec29e84a2ff7852dbc629c81245774c" + integrity sha512-NnDUDk1Ry5cHljTFetg0BNT79FaJSddTh9RsGOS2v/97DwOUJ+hBkgxtQHF6T8IarZD4i+bFEL787Nz+xpodfA== + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" @@ -3162,6 +3201,13 @@ resolved "https://registry.yarnpkg.com/@types/relateurl/-/relateurl-0.2.28.tgz#6bda7db8653fa62643f5ee69e9f69c11a392e3a6" integrity sha1-a9p9uGU/piZD9e5p6facEaOS46Y= +"@types/resolve@0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" + integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== + dependencies: + "@types/node" "*" + "@types/semver@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.1.0.tgz#c8c630d4c18cd326beff77404887596f96408408" @@ -4172,6 +4218,13 @@ babel-eslint@^10.0.3: eslint-visitor-keys "^1.0.0" resolve "^1.12.0" +babel-extract-comments@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/babel-extract-comments/-/babel-extract-comments-1.0.0.tgz#0a2aedf81417ed391b85e18b4614e693a0351a21" + integrity sha512-qWWzi4TlddohA91bFwgt6zO/J0X+io7Qp184Fw0m2JYRSTZnJbFR8+07KmzudHCZgOiKRCrjhylwv9Xd8gfhVQ== + dependencies: + babylon "^6.18.0" + babel-jest@^25.2.6: version "25.2.6" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.2.6.tgz#fe67ff4d0db3626ca8082da8881dd5e84e07ae75" @@ -4243,6 +4296,19 @@ babel-plugin-jest-hoist@^25.2.6: dependencies: "@types/babel__traverse" "^7.0.6" +babel-plugin-syntax-object-rest-spread@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" + integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U= + +babel-plugin-transform-object-rest-spread@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" + integrity sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY= + dependencies: + babel-plugin-syntax-object-rest-spread "^6.8.0" + babel-runtime "^6.26.0" + babel-preset-jest@^25.2.6: version "25.2.6" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-25.2.6.tgz#5d3f7c99e2a8508d61775c9d68506d143b7f71b5" @@ -4252,6 +4318,14 @@ babel-preset-jest@^25.2.6: "@babel/plugin-syntax-object-rest-spread" "^7.0.0" babel-plugin-jest-hoist "^25.2.6" +babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + babylon@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" @@ -4736,6 +4810,11 @@ buffer@^5.2.0, buffer@^5.2.1: base64-js "^1.0.2" ieee754 "^1.1.4" +builtin-modules@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" + integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== + builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -5547,6 +5626,11 @@ commander@~2.8.1: dependencies: graceful-readlink ">= 1.0.0" +common-tags@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" + integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -5912,7 +5996,7 @@ core-js@^1.0.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= -core-js@^2.4.1, core-js@^2.6.5: +core-js@^2.4.0, core-js@^2.4.1, core-js@^2.6.5: version "2.6.11" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== @@ -6059,6 +6143,11 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-random-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" + integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -7548,6 +7637,16 @@ estraverse@^5.1.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== +estree-walker@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" + integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== + +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -7886,7 +7985,7 @@ fast-glob@^3.1.1: micromatch "^4.0.2" picomatch "^2.2.1" -fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -10125,6 +10224,11 @@ is-lambda@^1.0.1: resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU= +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= + is-natural-number@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" @@ -10815,6 +10919,14 @@ jest-watcher@^25.2.7: jest-util "^25.2.6" string-length "^3.1.0" +jest-worker@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5" + integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== + dependencies: + merge-stream "^2.0.0" + supports-color "^6.1.0" + jest-worker@^25.1.0: version "25.2.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.2.1.tgz#209617015c768652646aa33a7828cc2ab472a18a" @@ -11693,6 +11805,13 @@ macos-release@^2.2.0: resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA== +magic-string@^0.25.0: + version "0.25.6" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.6.tgz#5586387d1242f919c6d223579cc938bf1420795e" + integrity sha512-3a5LOMSGoCTH5rbqobC2HuDNRtE2glHZ8J7pK+QZYppyWA36yuNpsX994rIY2nCuyP7CZYy7lQq/X2jygiZ89g== + dependencies: + sourcemap-codec "^1.4.4" + magic-string@^0.25.1, magic-string@^0.25.2: version "0.25.4" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.4.tgz#325b8a0a79fc423db109b77fd5a19183b7ba5143" @@ -11700,6 +11819,13 @@ magic-string@^0.25.1, magic-string@^0.25.2: dependencies: sourcemap-codec "^1.4.4" +magic-string@^0.25.5: + version "0.25.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" + integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== + dependencies: + sourcemap-codec "^1.4.4" + make-dir@^1.0.0, make-dir@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -14549,6 +14675,11 @@ prettier@^2.0.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.2.tgz#1ba8f3eb92231e769b7fcd7cb73ae1b6b74ade08" integrity sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg== +pretty-bytes@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" + integrity sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg== + pretty-error@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" @@ -15459,6 +15590,11 @@ regenerate@^1.4.0: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + regenerator-runtime@^0.13.2: version "0.13.3" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" @@ -15943,7 +16079,7 @@ resolve@^1.1.6, resolve@^1.8.1: dependencies: path-parse "^1.0.6" -resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.3.2: +resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.15.1, resolve@^1.3.2: version "1.15.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== @@ -16046,6 +16182,41 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +rollup-plugin-babel@^4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/rollup-plugin-babel/-/rollup-plugin-babel-4.3.3.tgz#7eb5ac16d9b5831c3fd5d97e8df77ba25c72a2aa" + integrity sha512-tKzWOCmIJD/6aKNz0H1GMM+lW1q9KyFubbWzGiOG540zxPPifnEAHTZwjo0g991Y+DyOZcLqBgqOdqazYE5fkw== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + rollup-pluginutils "^2.8.1" + +rollup-plugin-terser@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.3.0.tgz#9c0dd33d5771df9630cd027d6a2559187f65885e" + integrity sha512-XGMJihTIO3eIBsVGq7jiNYOdDMb3pVxuzY0uhOE/FM4x/u9nQgr3+McsjzqBn3QfHIpNSZmFnpoKAwHBEcsT7g== + dependencies: + "@babel/code-frame" "^7.5.5" + jest-worker "^24.9.0" + rollup-pluginutils "^2.8.2" + serialize-javascript "^2.1.2" + terser "^4.6.2" + +rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" + integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== + dependencies: + estree-walker "^0.6.1" + +rollup@^1.31.1: + version "1.32.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.32.1.tgz#4480e52d9d9e2ae4b46ba0d9ddeaf3163940f9c4" + integrity sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A== + dependencies: + "@types/estree" "*" + "@types/node" "*" + acorn "^7.1.0" + rst-selector-parser@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" @@ -17117,6 +17288,14 @@ strip-color@^0.1.0: resolved "https://registry.yarnpkg.com/strip-color/-/strip-color-0.1.0.tgz#106f65d3d3e6a2d9401cac0eb0ce8b8a702b4f7b" integrity sha1-EG9l09PmotlAHKwOsM6LinArT3s= +strip-comments@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-1.0.2.tgz#82b9c45e7f05873bee53f37168af930aa368679d" + integrity sha512-kL97alc47hoyIQSV165tTt9rG5dn4w1dNnBhOQ3bOU1Nc1hel09jnXANaHJ7vzHLd4Ju8kseDGzlev96pghLFw== + dependencies: + babel-extract-comments "^1.0.0" + babel-plugin-transform-object-rest-spread "^6.26.0" + strip-dirs@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5" @@ -17453,6 +17632,15 @@ tempfile@^2.0.0: temp-dir "^1.0.0" uuid "^3.0.1" +tempy@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/tempy/-/tempy-0.3.0.tgz#6f6c5b295695a16130996ad5ab01a8bd726e8bf8" + integrity sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ== + dependencies: + temp-dir "^1.0.0" + type-fest "^0.3.1" + unique-string "^1.0.0" + term-size@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" @@ -17512,6 +17700,15 @@ terser@^4.1.2, terser@^4.3.9, terser@^4.4.3: source-map "~0.6.1" source-map-support "~0.5.12" +terser@^4.6.2: + version "4.6.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.3.tgz#e33aa42461ced5238d352d2df2a67f21921f8d87" + integrity sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + terser@^4.6.3: version "4.6.10" resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.10.tgz#90f5bd069ff456ddbc9503b18e52f9c493d3b7c2" @@ -17909,7 +18106,7 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@^0.3.0: +type-fest@^0.3.0, type-fest@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ== @@ -18106,6 +18303,13 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" +unique-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" + integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= + dependencies: + crypto-random-string "^1.0.0" + unique-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -18849,6 +19053,167 @@ wordwrap@~0.0.2: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= +workbox-background-sync@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-5.1.2.tgz#e5f8f5a44c2a2a1b91c29904f5a5b8eaeee61f9a" + integrity sha512-oARjpHgLOmtClnBK0pjIv1nVvXJnhptsZowzGEcC4ynryDqJBUxnTmEvFycaUz74yAZ7vTih5oooYtxQspl5xg== + dependencies: + workbox-core "^5.1.2" + +workbox-broadcast-update@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-5.1.2.tgz#84143118552e08a2f2c6dd8e2367f61af74a0e16" + integrity sha512-mLNtdzxyAphl7gdBhM4ms1+OuU77H+BYQ5tneoTtJRiif7+0K3rYRfLISf4cBIMjalN0RYJ064dd2xWeRVy0/Q== + dependencies: + workbox-core "^5.1.2" + +workbox-build@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-5.1.2.tgz#c460eef313ece2f988ff41006c42c5629a63e35d" + integrity sha512-6FHeCu/udfJW3C6sKeKE5RKkaTQKf8XECKpSvbRDh2b7NFD5G//ax3xNMafke+5yRBIn9Xu4akr+QaAOt7lrHA== + dependencies: + "@babel/core" "^7.8.4" + "@babel/preset-env" "^7.8.4" + "@babel/runtime" "^7.8.4" + "@hapi/joi" "^15.1.0" + "@rollup/plugin-node-resolve" "^7.1.1" + "@rollup/plugin-replace" "^2.3.1" + "@surma/rollup-plugin-off-main-thread" "^1.1.1" + common-tags "^1.8.0" + fast-json-stable-stringify "^2.1.0" + fs-extra "^8.1.0" + glob "^7.1.6" + lodash.template "^4.5.0" + pretty-bytes "^5.3.0" + rollup "^1.31.1" + rollup-plugin-babel "^4.3.3" + rollup-plugin-terser "^5.2.0" + source-map "^0.7.3" + source-map-url "^0.4.0" + stringify-object "^3.3.0" + strip-comments "^1.0.2" + tempy "^0.3.0" + upath "^1.2.0" + workbox-background-sync "^5.1.2" + workbox-broadcast-update "^5.1.2" + workbox-cacheable-response "^5.1.2" + workbox-core "^5.1.2" + workbox-expiration "^5.1.2" + workbox-google-analytics "^5.1.2" + workbox-navigation-preload "^5.1.2" + workbox-precaching "^5.1.2" + workbox-range-requests "^5.1.2" + workbox-routing "^5.1.2" + workbox-strategies "^5.1.2" + workbox-streams "^5.1.2" + workbox-sw "^5.1.2" + workbox-window "^5.1.2" + +workbox-cacheable-response@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-5.1.2.tgz#416afa01c56702c9bd13042f72a28355d08e1bc5" + integrity sha512-uleOC4YUkZeawgS+0FXS8ntfM5H1yJ4FiKhoQEC/7ABInB3HSTqAlznkW8FZ3zyheV3shWEtlu+bDqWFpDKCmA== + dependencies: + workbox-core "^5.1.2" + +workbox-core@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-5.0.0.tgz#3cc95d172381c0fd0d762562a3cc49743e3093fa" + integrity sha512-vzUKj0Ac5l2IC66pnbIeXOjzoR/FfYaQR/q3ZWriLTEy7i7nbPPOVLGnaJbpAoEfPrlRjVysX7HIs9PRaZswig== + +workbox-core@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-5.1.2.tgz#c0df3f74df780f63fc1568eabd64f1c4beae2de6" + integrity sha512-w9DBIEE5+mgja5KRWr1ilk4g9YrmXmylb4wqHx/77XxwK3OD+a44RXNWUCrzxgixRDyfyt7TSc9Iqb0GZkoBxg== + +workbox-expiration@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-5.1.2.tgz#41d5d35b833d0fe7371b2aac0063c322d7b66ece" + integrity sha512-iPkcHR3VF4uUHIZNzIHrDdKIRAiZ2BCMq0F4JJqOkluSH2AzLtVDl2FPeicoeo/pReNi/Jwl2lZtLrUKGuB6JA== + dependencies: + workbox-core "^5.1.2" + +workbox-google-analytics@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-5.1.2.tgz#d605ff3b6fe5f1fd1e4957ebba59d8de8c1163f9" + integrity sha512-8P3ecXCwxZehnN2Sh88kS/eSaRpm9nVlHCZYIUYcPQPs0yKLFBquu54VX69lcQhDN/lZo93FFzTlc0GCL8Z4HA== + dependencies: + workbox-background-sync "^5.1.2" + workbox-core "^5.1.2" + workbox-routing "^5.1.2" + workbox-strategies "^5.1.2" + +workbox-navigation-preload@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-5.1.2.tgz#ec09d13660895b9246a5f7cf5891d831fabe73da" + integrity sha512-Xelgl5B4xTwU2W8It1ycThHpffSLfdWF8kgXP79OxtqBQw8SzWyTe2wddd/6RLNjXX5cRFL5EmPNqXZJuPiBIw== + dependencies: + workbox-core "^5.1.2" + +workbox-precaching@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-5.1.2.tgz#0e0feb5cc193afd5df74af0fbd18febfdf65711e" + integrity sha512-Ytun7H/8wb5wO/j5rpZYWrHTjBVY7nW8Pkwc3B2pnuzR1GwJIH9q492qglrsuz2ecq59NXpLd8c8fYdjFBLxrg== + dependencies: + workbox-core "^5.1.2" + +workbox-range-requests@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-5.1.2.tgz#a1f0b482227f9993f32b9a6631fa8306ff8cfb57" + integrity sha512-uXDA5mVRQHk2/6NjO6TNUx0XyZYO8sL7KToASahMS/w5K3N/HK/fIAKQxihVkYExDYHWNub1WWuPUrlZgtSmZw== + dependencies: + workbox-core "^5.1.2" + +workbox-routing@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-5.0.0.tgz#d8caf5ff5f01996d764e18c72cfe34f95b75ae6e" + integrity sha512-PbuuMKzXjDSjaKWMVGclElL2x4qvEcP7HUtDeO+qPJLCXedgyQ/TuXO/gicSh/7XEdX7iHeOd9HMYSksJ8BihA== + dependencies: + workbox-core "^5.0.0" + +workbox-routing@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-5.1.2.tgz#912dfd06febcdcee8c575165538e3ca9e94dd427" + integrity sha512-bb2H/ScLgMVKdbRqnw6CpviCw9PLb75iKIvURVAcI5vN+KhR/jtJPFLXEntEtHI05m6pUZGYZsc86trVby8PDg== + dependencies: + workbox-core "^5.1.2" + +workbox-strategies@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-5.0.0.tgz#ab6ee86075d038c188cd908ef2ad44cc422045f9" + integrity sha512-hvRch6eoclqnaPHfl04VHQou54ziu1J5KJ6YKIyx4nsYD/9xw4cbyGnKTaLJvHFMjK3ROB52zkGYYWbl7E9SfA== + dependencies: + workbox-core "^5.0.0" + workbox-routing "^5.0.0" + +workbox-strategies@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-5.1.2.tgz#d78c9282b998330c8b47c0b2d0e42b55f1fc6dad" + integrity sha512-FaKS/Zytu6g/JhW4ihqlfVXscFhS+8Ayvn/y3FOiUrGexahUcMcGSBvZNNyOIvy2yqO48WTKquKoeHbZJan8aQ== + dependencies: + workbox-core "^5.1.2" + workbox-routing "^5.1.2" + +workbox-streams@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-5.1.2.tgz#4ccb6832891e6fd6f9205b66bc8d4353ab6c50f3" + integrity sha512-ia7m6il4zXPxPWDKOAP/lifcMmVsO8FfmDSOOq+Vi0ZZCGCrMbCGyLjPMLaB3jRkxNSEwdeQ8jFyl3HFHAcvxQ== + dependencies: + workbox-core "^5.1.2" + workbox-routing "^5.1.2" + +workbox-sw@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-5.1.2.tgz#7018d2c465a0f126007e3ae888e8ac3aa0035770" + integrity sha512-P1VbCSXc2AsWVItmxwQIgnMqbdTFNsK+AhSFnFTDYEPHSck0NNitI6eWtg6EZkuAEE7Ux9g786/d5+EszxSRgg== + +workbox-window@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-5.1.2.tgz#dfcecac28919960beb83ba54c206295d1b31ffd1" + integrity sha512-8eS3VSQYmUvJiBRJL+fz1mDXDrNobVgOiAQ+f0eutMlBUQEArP5Fd6Ig+h8j2KWtntAKJbgyhhb1cYouJSDQyQ== + dependencies: + workbox-core "^5.1.2" + worker-farm@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"