Skip to content

Commit a75202f

Browse files
authored
Feature: @webdoc/default-template service worker (#190)
# Synopsis This PR adds a service worker to the sites generated by the default template to * cache all core assets (CSS & JavaScript) * add support for a offline storage setting (in the new settings page) It also adds an IndexedDB layer that holds the manifest data. The manifest is used to get all the pages in the site to be cached. # Service Worker The protocol/ folder in the site source contains the interface b/w the main & service-worker threads. * `webdocService` lets the main application initialize and pass messages to the service worker. * `webdocDB` is a wrapper around IndexedDB for data persistence. The "settings" store holds the application settings and the manifest url & hash. The service worker accepts 2 types of messages: * "lifecycle:init" − This is to be sent whenever the manifest changes (or the site is loaded for the first time). The manifest hash is to be fetched on each page load to check for changes. * "trigger:settings" − This lets the service worker know if a setting changed (like offline storage). ## Caching There are 2 types of caches: * The main cache is permanent and holds the core assets like react, react-dom scripts & icons that are versioned or won't change. * The ephemeral cache. This cache evicts items when the manifest hash changes, which is detected by attaching a "x-manifest-hash" header to cached responses and comparing it to the hash in the settings from IndexedDB (the exact settings object is found by the request origin). The "offline storage" setting lets the user cache the whole website immediately (well all the pages in the manifest registry, which doesn't include things like README and settings pages). It's an opt-in setting; however, the service worker will cache each HTML page when the user visits them into the ephemeral cache anyway. # IndexedDB There are 2 object stores in the current database model: * "settings" − This holds the application settings (identified by the application name or the site origin). It holds the manifest hash and the manifest URL. * "hyperlinks" − This holds all the documentation hyperlinks in the website. Offline storage uses it to identify which pages need to be cached. # UI ## Toolbar in explorer & settings page This toolbar has the new settings page link [button] <img width="297" alt="Screen Shot 2022-08-25 at 6 37 50 AM" src="https://user-images.githubusercontent.com/22450567/186643536-854af31e-9c94-412e-a764-4bb7f93f15c3.png"> The settings page lets you toggle offline storage: <img width="510" alt="Screen Shot 2022-08-25 at 6 38 44 AM" src="https://user-images.githubusercontent.com/22450567/186643694-3d8647dd-6366-4ea2-8da0-87f0a68ed26d.png"> ## Toasts for offline / stale pages These toasts let the user know if * the page was served from their offline cache (i.e. verifying the manifest hash failed) * the page is stale (the fetched hash was different from what was in IndexedDB). <img width="371" alt="Screen Shot 2022-08-25 at 6 33 45 AM" src="https://user-images.githubusercontent.com/22450567/186643048-3da8e064-057b-408b-b664-5a6e48f63a7c.png"> <img width="357" alt="Screen Shot 2022-08-25 at 6 34 18 AM" src="https://user-images.githubusercontent.com/22450567/186643055-e71d348e-d33f-47ba-a08f-f42eeef2e60b.png">
1 parent 87a9737 commit a75202f

35 files changed

+1107
-52
lines changed

.idea/runConfigurations.xml

Lines changed: 0 additions & 10 deletions
This file was deleted.

common/config/rush/pnpm-lock.yaml

Lines changed: 11 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/config/rush/repo-state.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
22
{
3-
"pnpmShrinkwrapHash": "b24765a3c2eeef5de5c2127972cc6959228bc2f2"
3+
"pnpmShrinkwrapHash": "9f03bf6671b1db458704136d5544b8a572f5f566"
44
}

core/webdoc-cli/src/main.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// @flow
22

33
import * as Sentry from "@sentry/node";
4+
import * as crypto from "crypto";
45
import * as external from "@webdoc/externalize";
56
import * as yargs from "yargs";
67
import {LogLevel, log, tag} from "missionlog";
@@ -134,7 +135,8 @@ export async function main(argv: yargs.Argv): Promise<void> {
134135
} catch (e) {
135136
// Make sure we get that API structure out so the user can debug the problem!
136137
if (config.opts && config.opts.export) {
137-
fs.writeFileSync(config.opts.export, external.write(external.fromTree(documentTree), true));
138+
fs.writeFileSync((config.opts.export: any),
139+
external.write(external.fromTree(documentTree), true));
138140
}
139141

140142
throw e;
@@ -181,7 +183,15 @@ export async function main(argv: yargs.Argv): Promise<void> {
181183
}
182184

183185
if (config.opts && config.opts.export) {
184-
fs.writeFileSync(config.opts.export, external.write(manifest, argv.verbose));
186+
const file = config.opts.export;
187+
const contents = external.write(manifest, argv.verbose);
188+
const hash = crypto
189+
.createHash("md5")
190+
.update(contents)
191+
.digest("hex");
192+
193+
fs.writeFileSync(file, contents);
194+
fs.writeFileSync(`${file}.md5`, hash);
185195
}
186196

187197
console.log(`@webdoc took ${Math.ceil(performance.now() - start)}ms to run!`);

core/webdoc-default-template/.eslintrc.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,13 @@
22
"ignorePatterns": "static/scripts/*",
33
"extends": [
44
"@webdoc/eslint-config"
5+
],
6+
"overrides": [
7+
{
8+
"files": ["./src/service-worker/**"],
9+
"env": {
10+
"serviceworker": true
11+
}
12+
}
513
]
614
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
static/scripts/default-template.js
22
static/scripts/default-template.js.LICENSE.txt
3+
static/service-worker.js
4+
static/service-worker.js.LICENSE.txt

core/webdoc-default-template/helper/crawl/crawl-reference-explorer.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
Doc,
99
DocType,
1010
PackageDoc,
11-
RootDoc
11+
RootDoc,
1212
} from "@webdoc/types";
1313
import type {CategorizedDocumentList} from "./crawl";
1414
*/
@@ -30,7 +30,7 @@ const HINTS = {
3030
};
3131

3232
// Crawls the tree searching for the API reference
33-
function crawlReference(doc /*: Doc */, index /*: string */) {
33+
function crawlReference(doc /*: RootDoc */, index /*: string */) {
3434
if (!doc.members.length) {
3535
return null;
3636
}
@@ -52,6 +52,7 @@ function getPage(doc /*: Doc */) {
5252
type ExplorerNode = {
5353
doc: Doc,
5454
children: CategorizedDocumentList,
55+
};
5556
*/
5657

5758
function buildExplorerHierarchy(rootDoc /*: RootDoc */, multiPackage = false) /*: ExplorerNode */ {

core/webdoc-default-template/helper/crawl/crawl.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ function buildLinks(manifest /*: Manifest */) /*: void */ {
7979

8080
registry[packageDoc.id].uri = linker.getURI(packageDoc);
8181
});
82+
for (const tutorialDoc of doc.tutorials) {
83+
if (tutorialDoc.route) {
84+
if (!registry[tutorialDoc.id]) registry[tutorialDoc.id] = {};
85+
registry[tutorialDoc.id].uri = linker.getURI(tutorialDoc);
86+
}
87+
}
88+
8289

8390
return;
8491
}

core/webdoc-default-template/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"webdoc-template"
1010
],
1111
"author": "Shukant Pal <[email protected]>",
12-
"homepage": "https://github.com/SukantPal/webdoc/tree/master/packages/default-template#readme",
12+
"homepage": "https://github.com/webdoc-labs/webdoc/tree/master/packages/default-template#readme",
1313
"license": "The Prosperity Public License 3.0.0",
1414
"main": "publish.js",
1515
"directories": {
@@ -31,6 +31,7 @@
3131
},
3232
"scripts": {
3333
"build": "webpack build --config ./webpack.config.js",
34+
"flow_": "flow check",
3435
"lint": "eslint -c .eslintrc.json .",
3536
"unit-test": "",
3637
"test": "echo \"Error: run tests from root\" && exit 1"
@@ -70,6 +71,7 @@
7071
"eslint": "8.18.0",
7172
"extract-loader": "^5.1.0",
7273
"file-loader": "^6.0.0",
74+
"flow-bin": "^0.130.0",
7375
"flow-typed": "^3.2.1",
7476
"lodash": "^4.17.20",
7577
"mini-css-extract-plugin": "^0.9.0",

core/webdoc-default-template/publish.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ exports.publish = async function publish(options /*: PublishOptions */) {
7878
const assetsDir = path.join(outDir, "./assets");
7979
const index = config.template.readme ? linker.createURI("index") : null;
8080
const indexRelative = index ? index.replace(`/${linker.siteRoot}/`, "") : null;
81+
const settings = linker.createURI("settings");
82+
const settingsRelative = settings.replace(`/${linker.siteRoot}/`, "");
83+
const manifestNormalized = config.opts.export ? path.normalize(config.opts.export) : null;
84+
const manifest = config.opts.export && manifestNormalized.startsWith(outDir) ?
85+
path.join(linker.siteRoot, manifestNormalized.replace(outDir, "")) :
86+
null;
8187

8288
fse.ensureDir(outDir);
8389

@@ -133,6 +139,13 @@ exports.publish = async function publish(options /*: PublishOptions */) {
133139
appBar: {
134140
items: appBarItems,
135141
},
142+
manifest: {url: manifest},
143+
pages: {
144+
settings,
145+
relative: {
146+
settings: settingsRelative,
147+
},
148+
},
136149
variant: config.template.variant,
137150
});
138151
if (config.template.repository) {
@@ -169,6 +182,7 @@ exports.publish = async function publish(options /*: PublishOptions */) {
169182
outSource(outDir, pipeline, options.config, source, options.cmdLine.mainThread || false),
170183
outExplorerData(outDir, crawlData),
171184
outMainPage(indexRelative ? path.join(outDir, indexRelative) : null, pipeline, options.config),
185+
outPages(outDir, pipeline, options.config),
172186
outIndexes(outDir, pipeline, options.config, crawlData.index),
173187
outReference(outDir, pipeline, options.config, docTree, crawlData.reference),
174188
outTutorials(outDir, pipeline, options.config, docTree, crawlData.tutorials),
@@ -291,6 +305,19 @@ async function outMainPage(
291305
}
292306
}
293307

308+
async function outPages(
309+
outDir /*: string */,
310+
pipeline /*: TemplatePipeline */,
311+
config /*: WebdocConfig */,
312+
)/*: Promise<void> */ {
313+
pipeline.render("pages/settings.tmpl", {
314+
env: config,
315+
title: "Site Settings",
316+
}, {
317+
outputFile: path.join(outDir, pipeline.renderer.data.pages.relative.settings),
318+
});
319+
}
320+
294321
async function outSource(
295322
outDir /*: string */,
296323
pipeline /*: TemplatePipeline */,

0 commit comments

Comments
 (0)