@docusaurus/plugin-pwa 2.0.0-beta.1ec2c95e3 → 2.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +10 -0
- package/lib/index.js +164 -0
- package/lib/options.d.ts +9 -0
- package/lib/options.js +46 -0
- package/lib/registerSw.d.ts +18 -0
- package/lib/registerSw.js +229 -0
- package/lib/renderReloadPopup.d.ts +8 -0
- package/lib/renderReloadPopup.js +21 -0
- package/lib/sw.d.ts +7 -0
- package/lib/sw.js +111 -0
- package/lib/theme/PwaReloadPopup/index.d.ts +9 -0
- package/{src → lib}/theme/PwaReloadPopup/index.js +0 -4
- package/lib/theme/PwaReloadPopup/styles.module.css +39 -0
- package/package.json +33 -18
- package/src/{index.js → index.ts} +72 -45
- package/src/{pluginOptionSchema.js → options.ts} +17 -8
- package/src/plugin-pwa.d.ts +88 -0
- package/src/{registerSw.js → registerSw.ts} +32 -19
- package/src/{renderReloadPopup.js → renderReloadPopup.tsx} +5 -4
- package/src/{sw.js → sw.ts} +36 -44
- package/src/theme/PwaReloadPopup/index.tsx +59 -0
- package/src/types.d.ts +8 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.popup {
|
|
9
|
+
position: fixed;
|
|
10
|
+
bottom: 16px;
|
|
11
|
+
right: 16px;
|
|
12
|
+
width: 344px;
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: space-between;
|
|
16
|
+
z-index: 999;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.popup p {
|
|
20
|
+
margin: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@media screen and (max-width: 500px) {
|
|
24
|
+
.popup {
|
|
25
|
+
width: 100%;
|
|
26
|
+
bottom: 0;
|
|
27
|
+
right: 0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.buttonContainer {
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.buttonContainer :global(.close) {
|
|
37
|
+
margin: 0;
|
|
38
|
+
padding: 0;
|
|
39
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/plugin-pwa",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.21",
|
|
4
4
|
"description": "Docusaurus Plugin to add PWA support.",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "src/plugin-pwa.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc --build && node ../../admin/scripts/copyUntypedFiles.js && prettier --config ../../.prettierrc --write \"lib/theme/**/*.js\"",
|
|
9
|
+
"watch": "run-p -c copy:watch build:watch",
|
|
10
|
+
"build:watch": "tsc --build --watch",
|
|
11
|
+
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
|
|
12
|
+
},
|
|
6
13
|
"publishConfig": {
|
|
7
14
|
"access": "public"
|
|
8
15
|
},
|
|
@@ -13,26 +20,34 @@
|
|
|
13
20
|
},
|
|
14
21
|
"license": "MIT",
|
|
15
22
|
"dependencies": {
|
|
16
|
-
"@babel/
|
|
17
|
-
"@babel/
|
|
18
|
-
"@
|
|
19
|
-
"@docusaurus/
|
|
20
|
-
"@docusaurus/theme-
|
|
21
|
-
"@docusaurus/utils
|
|
22
|
-
"
|
|
23
|
+
"@babel/core": "^7.18.2",
|
|
24
|
+
"@babel/preset-env": "^7.18.2",
|
|
25
|
+
"@docusaurus/core": "2.0.0-beta.21",
|
|
26
|
+
"@docusaurus/theme-common": "2.0.0-beta.21",
|
|
27
|
+
"@docusaurus/theme-translations": "2.0.0-beta.21",
|
|
28
|
+
"@docusaurus/utils": "2.0.0-beta.21",
|
|
29
|
+
"@docusaurus/utils-validation": "2.0.0-beta.21",
|
|
30
|
+
"babel-loader": "^8.2.5",
|
|
23
31
|
"clsx": "^1.1.1",
|
|
24
|
-
"core-js": "^
|
|
25
|
-
"terser-webpack-plugin": "^5.1
|
|
26
|
-
"
|
|
27
|
-
"webpack
|
|
28
|
-
"
|
|
29
|
-
"workbox-
|
|
30
|
-
"workbox-
|
|
32
|
+
"core-js": "^3.22.7",
|
|
33
|
+
"terser-webpack-plugin": "^5.3.1",
|
|
34
|
+
"tslib": "^2.4.0",
|
|
35
|
+
"webpack": "^5.72.1",
|
|
36
|
+
"webpack-merge": "^5.8.0",
|
|
37
|
+
"workbox-build": "^6.5.3",
|
|
38
|
+
"workbox-precaching": "^6.5.3",
|
|
39
|
+
"workbox-window": "^6.5.3"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@docusaurus/module-type-aliases": "2.0.0-beta.21",
|
|
43
|
+
"fs-extra": "^10.1.0"
|
|
31
44
|
},
|
|
32
45
|
"peerDependencies": {
|
|
33
|
-
"@babel/core": "^7.0.0",
|
|
34
46
|
"react": "^16.8.4 || ^17.0.0",
|
|
35
47
|
"react-dom": "^16.8.4 || ^17.0.0"
|
|
36
48
|
},
|
|
37
|
-
"
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=16.14"
|
|
51
|
+
},
|
|
52
|
+
"gitHead": "69ac49fc6909517f13615ee40290c4bd00c39df4"
|
|
38
53
|
}
|
|
@@ -5,14 +5,16 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import webpack, {type Configuration} from 'webpack';
|
|
10
|
+
import Terser from 'terser-webpack-plugin';
|
|
11
|
+
import {injectManifest} from 'workbox-build';
|
|
12
|
+
import {normalizeUrl} from '@docusaurus/utils';
|
|
13
|
+
import {compile} from '@docusaurus/core/lib/webpack/utils';
|
|
14
|
+
import LogPlugin from '@docusaurus/core/lib/webpack/plugins/LogPlugin';
|
|
15
|
+
import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations';
|
|
16
|
+
import type {HtmlTags, LoadContext, Plugin} from '@docusaurus/types';
|
|
17
|
+
import type {PluginOptions} from '@docusaurus/plugin-pwa';
|
|
16
18
|
|
|
17
19
|
const isProd = process.env.NODE_ENV === 'production';
|
|
18
20
|
|
|
@@ -33,22 +35,23 @@ function getSWBabelLoader() {
|
|
|
33
35
|
},
|
|
34
36
|
],
|
|
35
37
|
],
|
|
36
|
-
plugins: [
|
|
37
|
-
require.resolve('@babel/plugin-proposal-object-rest-spread'),
|
|
38
|
-
require.resolve('@babel/plugin-proposal-optional-chaining'),
|
|
39
|
-
require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'),
|
|
40
|
-
],
|
|
41
38
|
},
|
|
42
39
|
};
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
function
|
|
46
|
-
|
|
42
|
+
export default function pluginPWA(
|
|
43
|
+
context: LoadContext,
|
|
44
|
+
options: PluginOptions,
|
|
45
|
+
): Plugin<void> {
|
|
46
|
+
const {
|
|
47
|
+
outDir,
|
|
48
|
+
baseUrl,
|
|
49
|
+
i18n: {currentLocale},
|
|
50
|
+
} = context;
|
|
47
51
|
const {
|
|
48
52
|
debug,
|
|
49
53
|
offlineModeActivationStrategies,
|
|
50
54
|
injectManifestConfig,
|
|
51
|
-
reloadPopup,
|
|
52
55
|
pwaHead,
|
|
53
56
|
swCustom,
|
|
54
57
|
swRegister,
|
|
@@ -58,11 +61,21 @@ function plugin(context, options) {
|
|
|
58
61
|
name: 'docusaurus-plugin-pwa',
|
|
59
62
|
|
|
60
63
|
getThemePath() {
|
|
61
|
-
return
|
|
64
|
+
return '../lib/theme';
|
|
65
|
+
},
|
|
66
|
+
getTypeScriptThemePath() {
|
|
67
|
+
return '../src/theme';
|
|
62
68
|
},
|
|
63
69
|
|
|
64
70
|
getClientModules() {
|
|
65
|
-
return isProd ? [swRegister] : [];
|
|
71
|
+
return isProd && swRegister ? [swRegister] : [];
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
getDefaultCodeTranslationMessages() {
|
|
75
|
+
return readDefaultCodeTranslationMessages({
|
|
76
|
+
locale: currentLocale,
|
|
77
|
+
name: 'plugin-pwa',
|
|
78
|
+
});
|
|
66
79
|
},
|
|
67
80
|
|
|
68
81
|
configureWebpack(config) {
|
|
@@ -75,25 +88,40 @@ function plugin(context, options) {
|
|
|
75
88
|
new webpack.EnvironmentPlugin({
|
|
76
89
|
PWA_DEBUG: debug,
|
|
77
90
|
PWA_SERVICE_WORKER_URL: path.posix.resolve(
|
|
78
|
-
`${config.output
|
|
91
|
+
`${(config.output?.publicPath as string) || '/'}`,
|
|
79
92
|
'sw.js',
|
|
80
93
|
),
|
|
81
|
-
PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES:
|
|
82
|
-
|
|
94
|
+
PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES:
|
|
95
|
+
offlineModeActivationStrategies,
|
|
83
96
|
}),
|
|
84
97
|
],
|
|
85
98
|
};
|
|
86
99
|
},
|
|
87
100
|
|
|
88
101
|
injectHtmlTags() {
|
|
89
|
-
const headTags = [];
|
|
90
|
-
if (isProd
|
|
91
|
-
pwaHead.forEach(({tagName, ...attributes}) =>
|
|
92
|
-
|
|
102
|
+
const headTags: HtmlTags = [];
|
|
103
|
+
if (isProd) {
|
|
104
|
+
pwaHead.forEach(({tagName, ...attributes}) => {
|
|
105
|
+
(['href', 'content'] as const).forEach((attribute) => {
|
|
106
|
+
const attributeValue = attributes[attribute];
|
|
107
|
+
|
|
108
|
+
if (!attributeValue) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const attributePath =
|
|
113
|
+
!!path.extname(attributeValue) && attributeValue;
|
|
114
|
+
|
|
115
|
+
if (attributePath && !attributePath.startsWith(baseUrl)) {
|
|
116
|
+
attributes[attribute] = normalizeUrl([baseUrl, attributeValue]);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return headTags.push({
|
|
93
121
|
tagName,
|
|
94
122
|
attributes,
|
|
95
|
-
})
|
|
96
|
-
);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
97
125
|
}
|
|
98
126
|
return {headTags};
|
|
99
127
|
},
|
|
@@ -105,8 +133,8 @@ function plugin(context, options) {
|
|
|
105
133
|
|
|
106
134
|
const swSourceFileTest = /\.m?js$/;
|
|
107
135
|
|
|
108
|
-
const swWebpackConfig = {
|
|
109
|
-
entry:
|
|
136
|
+
const swWebpackConfig: Configuration = {
|
|
137
|
+
entry: require.resolve('./sw.js'),
|
|
110
138
|
output: {
|
|
111
139
|
path: outDir,
|
|
112
140
|
filename: 'sw.js',
|
|
@@ -118,17 +146,19 @@ function plugin(context, options) {
|
|
|
118
146
|
optimization: {
|
|
119
147
|
splitChunks: false,
|
|
120
148
|
minimize: !debug,
|
|
121
|
-
//
|
|
122
|
-
minimizer:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
149
|
+
// See https://developers.google.com/web/tools/workbox/guides/using-bundlers#webpack
|
|
150
|
+
minimizer: debug
|
|
151
|
+
? []
|
|
152
|
+
: [
|
|
153
|
+
new Terser({
|
|
154
|
+
test: swSourceFileTest,
|
|
155
|
+
}),
|
|
156
|
+
],
|
|
128
157
|
},
|
|
129
158
|
plugins: [
|
|
130
159
|
new webpack.EnvironmentPlugin({
|
|
131
|
-
|
|
160
|
+
// Fallback value required with Webpack 5
|
|
161
|
+
PWA_SW_CUSTOM: swCustom ?? '',
|
|
132
162
|
}),
|
|
133
163
|
new LogPlugin({
|
|
134
164
|
name: 'Service Worker',
|
|
@@ -139,7 +169,7 @@ function plugin(context, options) {
|
|
|
139
169
|
rules: [
|
|
140
170
|
{
|
|
141
171
|
test: swSourceFileTest,
|
|
142
|
-
exclude: /
|
|
172
|
+
exclude: /node_modules/,
|
|
143
173
|
use: getSWBabelLoader(),
|
|
144
174
|
},
|
|
145
175
|
],
|
|
@@ -156,9 +186,10 @@ function plugin(context, options) {
|
|
|
156
186
|
'**/*.{js,json,css,html}',
|
|
157
187
|
'**/*.{png,jpg,jpeg,gif,svg,ico}',
|
|
158
188
|
'**/*.{woff,woff2,eot,ttf,otf}',
|
|
159
|
-
|
|
189
|
+
// @ts-expect-error: internal API?
|
|
190
|
+
...((injectManifest.globPatterns as string[] | undefined) ?? []),
|
|
160
191
|
],
|
|
161
|
-
//
|
|
192
|
+
// Those attributes are not overrideable
|
|
162
193
|
swDest,
|
|
163
194
|
swSrc: swDest,
|
|
164
195
|
globDirectory: props.outDir,
|
|
@@ -167,8 +198,4 @@ function plugin(context, options) {
|
|
|
167
198
|
};
|
|
168
199
|
}
|
|
169
200
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
plugin.validateOptions = function validateOptions({validate, options}) {
|
|
173
|
-
return validate(PluginOptionSchema, options);
|
|
174
|
-
};
|
|
201
|
+
export {validateOptions} from './options';
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
import {Joi} from '@docusaurus/utils-validation';
|
|
9
|
+
import type {OptionValidationContext} from '@docusaurus/types';
|
|
10
|
+
import type {PluginOptions} from '@docusaurus/plugin-pwa';
|
|
10
11
|
|
|
11
12
|
const DEFAULT_OPTIONS = {
|
|
12
13
|
debug: false,
|
|
@@ -18,11 +19,10 @@ const DEFAULT_OPTIONS = {
|
|
|
18
19
|
injectManifestConfig: {},
|
|
19
20
|
pwaHead: [],
|
|
20
21
|
swCustom: undefined,
|
|
21
|
-
swRegister:
|
|
22
|
-
reloadPopup: '@theme/PwaReloadPopup',
|
|
22
|
+
swRegister: './registerSw.js',
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
const optionsSchema = Joi.object<PluginOptions>({
|
|
26
26
|
debug: Joi.bool().default(DEFAULT_OPTIONS.debug),
|
|
27
27
|
offlineModeActivationStrategies: Joi.array()
|
|
28
28
|
.items(
|
|
@@ -48,7 +48,16 @@ exports.PluginOptionSchema = Joi.object({
|
|
|
48
48
|
swRegister: Joi.alternatives()
|
|
49
49
|
.try(Joi.string(), Joi.bool().valid(false))
|
|
50
50
|
.default(DEFAULT_OPTIONS.swRegister),
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
.
|
|
51
|
+
// @ts-expect-error: forbidden
|
|
52
|
+
reloadPopup: Joi.any().forbidden().messages({
|
|
53
|
+
'any.unknown':
|
|
54
|
+
'The reloadPopup option is removed in favor of swizzling. See https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-pwa#customizing-reload-popup for how to customize the reload popup using swizzling.',
|
|
55
|
+
}),
|
|
54
56
|
});
|
|
57
|
+
|
|
58
|
+
export function validateOptions({
|
|
59
|
+
validate,
|
|
60
|
+
options,
|
|
61
|
+
}: OptionValidationContext<PluginOptions, PluginOptions>): PluginOptions {
|
|
62
|
+
return validate(optionsSchema, options);
|
|
63
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
declare module '@docusaurus/plugin-pwa' {
|
|
9
|
+
import type {InjectManifestOptions} from 'workbox-build';
|
|
10
|
+
|
|
11
|
+
export type PluginOptions = {
|
|
12
|
+
/**
|
|
13
|
+
* Turn debug mode on:
|
|
14
|
+
*
|
|
15
|
+
* - Workbox logs
|
|
16
|
+
* - Additional Docusaurus logs
|
|
17
|
+
* - Unoptimized SW file output
|
|
18
|
+
* - Source maps
|
|
19
|
+
*/
|
|
20
|
+
debug?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Strategies used to turn the offline mode on:
|
|
23
|
+
*
|
|
24
|
+
* - `appInstalled`: activates for users having installed the site as an app
|
|
25
|
+
* (not 100% reliable)
|
|
26
|
+
* - `standalone`: activates for users running the app as standalone (often
|
|
27
|
+
* the case once a PWA is installed)
|
|
28
|
+
* - `queryString`: activates if queryString contains `offlineMode=true`
|
|
29
|
+
* (convenient for PWA debugging)
|
|
30
|
+
* - `mobile`: activates for mobile users (width <= 940px)
|
|
31
|
+
* - `saveData`: activates for users with `navigator.connection.saveData ===
|
|
32
|
+
* true`
|
|
33
|
+
* - `always`: activates for all users
|
|
34
|
+
*/
|
|
35
|
+
offlineModeActivationStrategies: (
|
|
36
|
+
| 'appInstalled'
|
|
37
|
+
| 'queryString'
|
|
38
|
+
| 'standalone'
|
|
39
|
+
| 'mobile'
|
|
40
|
+
| 'saveData'
|
|
41
|
+
| 'always'
|
|
42
|
+
)[];
|
|
43
|
+
/**
|
|
44
|
+
* Workbox options to pass to `workbox.injectManifest()`. This gives you
|
|
45
|
+
* control over which assets will be precached, and be available offline.
|
|
46
|
+
* @see https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-build#.injectManifest
|
|
47
|
+
*/
|
|
48
|
+
injectManifestConfig: InjectManifestOptions;
|
|
49
|
+
/**
|
|
50
|
+
* Array of objects containing `tagName` and key-value pairs for attributes
|
|
51
|
+
* to inject into the `<head>` tag. Technically you can inject any head tag
|
|
52
|
+
* through this, but it's ideally used for tags to make your site PWA-
|
|
53
|
+
* compliant.
|
|
54
|
+
*/
|
|
55
|
+
pwaHead: {
|
|
56
|
+
tagName: string;
|
|
57
|
+
href?: string;
|
|
58
|
+
content?: string;
|
|
59
|
+
[attributeName: string]: string | boolean | undefined;
|
|
60
|
+
}[];
|
|
61
|
+
/**
|
|
62
|
+
* Useful for additional Workbox rules. You can do whatever a service worker
|
|
63
|
+
* can do here, and use the full power of workbox libraries. The code is
|
|
64
|
+
* transpiled, so you can use modern ES6+ syntax here.
|
|
65
|
+
*/
|
|
66
|
+
swCustom?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Adds an entry before the Docusaurus app so that registration can happen
|
|
69
|
+
* before the app runs. The default `registerSW.js` file is enough for
|
|
70
|
+
* simple registration. Passing `false` will disable registration entirely.
|
|
71
|
+
*/
|
|
72
|
+
swRegister: string | false;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type Options = Partial<PluginOptions>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
declare module '@theme/PwaReloadPopup' {
|
|
79
|
+
export interface Props {
|
|
80
|
+
/**
|
|
81
|
+
* The popup should call this callback when the `reload` button is clicked.
|
|
82
|
+
* This will tell the service worker to install the waiting service worker
|
|
83
|
+
* and reload the page.
|
|
84
|
+
*/
|
|
85
|
+
readonly onReload: () => void;
|
|
86
|
+
}
|
|
87
|
+
export default function PwaReloadPopup(props: Props): JSX.Element;
|
|
88
|
+
}
|
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
+
|
|
7
8
|
import {createStorageSlot} from '@docusaurus/theme-common';
|
|
8
9
|
|
|
9
10
|
// First: read the env variables (provided by Webpack)
|
|
10
11
|
/* eslint-disable prefer-destructuring */
|
|
11
|
-
const PWA_SERVICE_WORKER_URL = process.env.PWA_SERVICE_WORKER_URL
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
process.env.PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES;
|
|
12
|
+
const PWA_SERVICE_WORKER_URL = process.env.PWA_SERVICE_WORKER_URL!;
|
|
13
|
+
const PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES = process.env
|
|
14
|
+
.PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES as unknown as (keyof typeof OfflineModeActivationStrategiesImplementations)[];
|
|
15
15
|
const PWA_DEBUG = process.env.PWA_DEBUG;
|
|
16
16
|
/* eslint-enable prefer-destructuring */
|
|
17
17
|
|
|
18
|
-
const debug = PWA_DEBUG; //
|
|
18
|
+
const debug = PWA_DEBUG; // Shortcut
|
|
19
19
|
|
|
20
20
|
const MAX_MOBILE_WIDTH = 940;
|
|
21
21
|
|
|
@@ -33,7 +33,7 @@ async function clearRegistrations() {
|
|
|
33
33
|
}
|
|
34
34
|
await Promise.all(
|
|
35
35
|
registrations.map(async (registration) => {
|
|
36
|
-
const result = await registration
|
|
36
|
+
const result = await registration.unregister();
|
|
37
37
|
if (debug) {
|
|
38
38
|
console.log(
|
|
39
39
|
`[Docusaurus-PWA][registerSw]: unregister() service worker registration`,
|
|
@@ -64,6 +64,14 @@ https://stackoverflow.com/questions/51735869/check-if-user-has-already-installed
|
|
|
64
64
|
async function isAppInstalledEventFired() {
|
|
65
65
|
return AppInstalledEventFiredStorage.get() === 'true';
|
|
66
66
|
}
|
|
67
|
+
|
|
68
|
+
declare global {
|
|
69
|
+
interface Navigator {
|
|
70
|
+
getInstalledRelatedApps: () => Promise<{platform: string}[]>;
|
|
71
|
+
connection?: {effectiveType: string; saveData: boolean};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
67
75
|
async function isAppInstalledRelatedApps() {
|
|
68
76
|
if ('getInstalledRelatedApps' in window.navigator) {
|
|
69
77
|
const relatedApps = await navigator.getInstalledRelatedApps();
|
|
@@ -78,7 +86,7 @@ function isStandaloneDisplayMode() {
|
|
|
78
86
|
const OfflineModeActivationStrategiesImplementations = {
|
|
79
87
|
always: () => true,
|
|
80
88
|
mobile: () => window.innerWidth <= MAX_MOBILE_WIDTH,
|
|
81
|
-
saveData: () => !!
|
|
89
|
+
saveData: () => !!navigator.connection?.saveData,
|
|
82
90
|
appInstalled: async () => {
|
|
83
91
|
const installedEventFired = await isAppInstalledEventFired();
|
|
84
92
|
const installedRelatedApps = await isAppInstalledRelatedApps();
|
|
@@ -89,7 +97,9 @@ const OfflineModeActivationStrategiesImplementations = {
|
|
|
89
97
|
new URLSearchParams(window.location.search).get('offlineMode') === 'true',
|
|
90
98
|
};
|
|
91
99
|
|
|
92
|
-
async function isStrategyActive(
|
|
100
|
+
async function isStrategyActive(
|
|
101
|
+
strategyName: keyof typeof OfflineModeActivationStrategiesImplementations,
|
|
102
|
+
) {
|
|
93
103
|
return OfflineModeActivationStrategiesImplementations[strategyName]();
|
|
94
104
|
}
|
|
95
105
|
|
|
@@ -100,7 +110,7 @@ async function getActiveStrategies() {
|
|
|
100
110
|
return isActive ? strategyName : undefined;
|
|
101
111
|
}),
|
|
102
112
|
);
|
|
103
|
-
return activeStrategies.filter(Boolean);
|
|
113
|
+
return activeStrategies.filter(Boolean);
|
|
104
114
|
}
|
|
105
115
|
|
|
106
116
|
async function isOfflineModeEnabled() {
|
|
@@ -126,7 +136,7 @@ async function isOfflineModeEnabled() {
|
|
|
126
136
|
return enabled;
|
|
127
137
|
}
|
|
128
138
|
|
|
129
|
-
function createServiceWorkerUrl(params) {
|
|
139
|
+
function createServiceWorkerUrl(params: object) {
|
|
130
140
|
const paramsQueryString = JSON.stringify(params);
|
|
131
141
|
const url = `${PWA_SERVICE_WORKER_URL}?params=${encodeURIComponent(
|
|
132
142
|
paramsQueryString,
|
|
@@ -159,7 +169,7 @@ async function registerSW() {
|
|
|
159
169
|
// Immediately load new service worker when files aren't cached
|
|
160
170
|
if (!offlineMode) {
|
|
161
171
|
sendSkipWaiting();
|
|
162
|
-
} else
|
|
172
|
+
} else {
|
|
163
173
|
const renderReloadPopup = (await import('./renderReloadPopup')).default;
|
|
164
174
|
await renderReloadPopup({
|
|
165
175
|
onReload() {
|
|
@@ -172,7 +182,7 @@ async function registerSW() {
|
|
|
172
182
|
}
|
|
173
183
|
};
|
|
174
184
|
|
|
175
|
-
if (debug) {
|
|
185
|
+
if (debug && registration) {
|
|
176
186
|
if (registration.active) {
|
|
177
187
|
console.log(
|
|
178
188
|
'[Docusaurus-PWA][registerSw]: registration.active',
|
|
@@ -204,6 +214,7 @@ async function registerSW() {
|
|
|
204
214
|
|
|
205
215
|
// Update current service worker if the next one finishes installing and
|
|
206
216
|
// moves to waiting state in another tab.
|
|
217
|
+
// @ts-expect-error: not present in the API typings anymore
|
|
207
218
|
wb.addEventListener('externalwaiting', (event) => {
|
|
208
219
|
if (debug) {
|
|
209
220
|
console.log('[Docusaurus-PWA][registerSw]: event externalwaiting', event);
|
|
@@ -213,13 +224,13 @@ async function registerSW() {
|
|
|
213
224
|
|
|
214
225
|
// Update service worker if the next one is already in the waiting state.
|
|
215
226
|
// This happens when the user doesn't click on `reload` in the popup.
|
|
216
|
-
if (registration
|
|
227
|
+
if (registration?.waiting) {
|
|
217
228
|
await handleServiceWorkerWaiting();
|
|
218
229
|
}
|
|
219
230
|
}
|
|
220
231
|
|
|
221
|
-
// TODO these events still works in chrome but have been removed from the spec
|
|
222
|
-
// See https://github.com/w3c/manifest/pull/836
|
|
232
|
+
// TODO these events still works in chrome but have been removed from the spec
|
|
233
|
+
// in 2019! See https://github.com/w3c/manifest/pull/836
|
|
223
234
|
function addLegacyAppInstalledEventsListeners() {
|
|
224
235
|
if (typeof window !== 'undefined') {
|
|
225
236
|
if (debug) {
|
|
@@ -247,7 +258,8 @@ function addLegacyAppInstalledEventsListeners() {
|
|
|
247
258
|
await clearRegistrations();
|
|
248
259
|
});
|
|
249
260
|
|
|
250
|
-
// TODO this event still works in chrome but has been removed from the spec
|
|
261
|
+
// TODO this event still works in chrome but has been removed from the spec
|
|
262
|
+
// in 2019!!!
|
|
251
263
|
window.addEventListener('beforeinstallprompt', async (event) => {
|
|
252
264
|
if (debug) {
|
|
253
265
|
console.log(
|
|
@@ -255,7 +267,8 @@ function addLegacyAppInstalledEventsListeners() {
|
|
|
255
267
|
event,
|
|
256
268
|
);
|
|
257
269
|
}
|
|
258
|
-
// TODO instead of default browser install UI, show custom docusaurus
|
|
270
|
+
// TODO instead of default browser install UI, show custom docusaurus
|
|
271
|
+
// prompt?
|
|
259
272
|
// event.preventDefault();
|
|
260
273
|
if (debug) {
|
|
261
274
|
console.log(
|
|
@@ -272,7 +285,7 @@ function addLegacyAppInstalledEventsListeners() {
|
|
|
272
285
|
}
|
|
273
286
|
// After uninstalling the app, if the user doesn't clear all data, then
|
|
274
287
|
// the previous service worker will continue serving cached files. We
|
|
275
|
-
// need to clear registrations and reload, otherwise the popup
|
|
288
|
+
// need to clear registrations and reload, otherwise the popup shows.
|
|
276
289
|
await clearRegistrations();
|
|
277
290
|
}
|
|
278
291
|
});
|
|
@@ -298,6 +311,6 @@ if (typeof window !== 'undefined') {
|
|
|
298
311
|
addLegacyAppInstalledEventsListeners();
|
|
299
312
|
|
|
300
313
|
// Then try to register the SW using lazy/dynamic imports
|
|
301
|
-
registerSW().catch((e) => console.error('registerSW failed', e));
|
|
314
|
+
registerSW().catch((e: unknown) => console.error('registerSW failed', e));
|
|
302
315
|
}
|
|
303
316
|
}
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import React from 'react';
|
|
9
|
-
import
|
|
9
|
+
import ReactDOM from 'react-dom';
|
|
10
|
+
import type {Props} from '@theme/PwaReloadPopup';
|
|
10
11
|
|
|
11
12
|
const POPUP_CONTAINER_ID = 'pwa-popup-container';
|
|
12
13
|
|
|
@@ -19,8 +20,8 @@ const createContainer = () => {
|
|
|
19
20
|
return container;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
export default async function renderReloadPopup(props) {
|
|
23
|
+
export default async function renderReloadPopup(props: Props): Promise<void> {
|
|
23
24
|
const container = getContainer() || createContainer();
|
|
24
|
-
const
|
|
25
|
-
render(<ReloadPopup {...props} />, container);
|
|
25
|
+
const ReloadPopup = (await import('@theme/PwaReloadPopup')).default;
|
|
26
|
+
ReactDOM.render(<ReloadPopup {...props} />, container);
|
|
26
27
|
}
|