Workbox 5 in gulp (#5843)

This commit is contained in:
Paulus Schoutsen 2020-05-13 02:12:01 -07:00 committed by GitHub
parent 10358abbec
commit 581fafdcc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 783 additions and 243 deletions

View File

@ -20,7 +20,7 @@ gulp.task(
},
"clean",
gulp.parallel(
"gen-service-worker-dev",
"gen-service-worker-app-dev",
"gen-icons-json",
"gen-pages-dev",
"gen-index-app-dev",
@ -46,7 +46,7 @@ gulp.task(
gulp.parallel(
"gen-pages-prod",
"gen-index-app-prod",
"gen-service-worker-prod"
"gen-service-worker-app-prod"
)
)
);

View File

@ -5,13 +5,15 @@
const gulp = require("gulp");
const path = require("path");
const fs = require("fs-extra");
const config = require("../paths.js");
const workboxBuild = require("workbox-build");
const sourceMapUrl = require("source-map-url");
const paths = require("../paths.js");
const swPath = path.resolve(config.root, "service_worker.js");
const swDest = path.resolve(paths.root, "service_worker.js");
const writeSW = (content) => fs.outputFileSync(swPath, content.trim() + "\n");
const writeSW = (content) => fs.outputFileSync(swDest, content.trim() + "\n");
gulp.task("gen-service-worker-dev", (done) => {
gulp.task("gen-service-worker-app-dev", (done) => {
writeSW(
`
console.debug('Service worker disabled in development');
@ -24,10 +26,58 @@ self.addEventListener('install', (event) => {
done();
});
gulp.task("gen-service-worker-prod", (done) => {
fs.copySync(
path.resolve(config.output, "service_worker.js"),
path.resolve(config.root, "service_worker.js")
gulp.task("gen-service-worker-app-prod", async () => {
const workboxManifest = await workboxBuild.getManifest({
// Files that mach this pattern will be considered unique and skip revision check
// ignore JS files + translation files
dontCacheBustURLsMatching: /(frontend_latest\/.+|static\/translations\/.+)/,
globDirectory: paths.root,
globPatterns: [
"frontend_latest/*.js",
// Cache all English translations because we catch them as fallback
// Using pattern to match hash instead of * to avoid caching en-GB
"static/translations/**/en-+([a-f0-9]).json",
// Icon shown on splash screen
"static/icons/favicon-192x192.png",
"static/icons/favicon.ico",
// Common fonts
"static/fonts/roboto/Roboto-Light.woff2",
"static/fonts/roboto/Roboto-Medium.woff2",
"static/fonts/roboto/Roboto-Regular.woff2",
"static/fonts/roboto/Roboto-Bold.woff2",
],
});
for (const warning of workboxManifest.warnings) {
console.warn(warning);
}
// Replace `null` with 0 for better compression
for (const entry of workboxManifest.manifestEntries) {
if (entry.revision === null) {
entry.revision = 0;
}
}
const manifest = require(path.resolve(paths.output, "manifest.json"));
// Write bundled source file
let serviceWorkerContent = fs.readFileSync(
paths.root + manifest["service_worker.js"],
"utf-8"
);
done();
// remove source map and add WB manifest
serviceWorkerContent = sourceMapUrl.removeFrom(serviceWorkerContent);
serviceWorkerContent = serviceWorkerContent.replace(
"WB_MANIFEST",
JSON.stringify(workboxManifest.manifestEntries)
);
// Write new file to root
fs.writeFileSync(swDest, serviceWorkerContent);
// Delete old file from frontend_latest
fs.removeSync(paths.root + manifest["service_worker.js"]);
fs.removeSync(paths.root + manifest["service_worker.js.map"]);
});

View File

@ -1,7 +1,6 @@
const webpack = require("webpack");
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const WorkboxPlugin = require("workbox-webpack-plugin");
const ManifestPlugin = require("webpack-manifest-plugin");
const paths = require("./paths.js");
const env = require("./env.js");
@ -107,8 +106,9 @@ const createWebpackConfig = ({
};
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
const config = createWebpackConfig({
return createWebpackConfig({
entry: {
service_worker: "./src/entrypoints/service_worker.ts",
app: "./src/entrypoints/app.ts",
authorize: "./src/entrypoints/authorize.ts",
onboarding: "./src/entrypoints/onboarding.ts",
@ -121,48 +121,6 @@ const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
latestBuild,
isStatsBuild,
});
if (latestBuild) {
// Create an object mapping browser urls to their paths during build
const translationMetadata = require("../build-translations/translationMetadata.json");
const workBoxTranslationsTemplatedURLs = {};
const englishFilename = `en-${translationMetadata.translations.en.hash}.json`;
// core
workBoxTranslationsTemplatedURLs[
`/static/translations/${englishFilename}`
] = `build-translations/output/${englishFilename}`;
translationMetadata.fragments.forEach((fragment) => {
workBoxTranslationsTemplatedURLs[
`/static/translations/${fragment}/${englishFilename}`
] = `build-translations/output/${fragment}/${englishFilename}`;
});
config.plugins.push(
new WorkboxPlugin.InjectManifest({
swSrc: "./src/entrypoints/service-worker-hass.js",
swDest: "service_worker.js",
importWorkboxFrom: "local",
include: [/\.js$/],
templatedURLs: {
...workBoxTranslationsTemplatedURLs,
"/static/icons/favicon-192x192.png":
"public/icons/favicon-192x192.png",
"/static/fonts/roboto/Roboto-Light.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2",
"/static/fonts/roboto/Roboto-Medium.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2",
"/static/fonts/roboto/Roboto-Regular.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2",
"/static/fonts/roboto/Roboto-Bold.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2",
},
})
);
}
return config;
};
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {

View File

@ -114,6 +114,10 @@
"tslib": "^1.10.0",
"unfetch": "^4.1.0",
"web-animations-js": "^2.3.2",
"workbox-core": "^5.1.3",
"workbox-precaching": "^5.1.3",
"workbox-routing": "^5.1.3",
"workbox-strategies": "^5.1.3",
"xss": "^1.0.6"
},
"devDependencies": {
@ -177,6 +181,7 @@
"reify": "^0.18.1",
"require-dir": "^1.2.0",
"sinon": "^7.3.1",
"source-map-url": "^0.4.0",
"terser-webpack-plugin": "^1.2.3",
"ts-lit-plugin": "^1.1.10",
"ts-mocha": "^6.0.0",
@ -188,7 +193,7 @@
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.10.3",
"webpack-manifest-plugin": "^2.0.4",
"workbox-webpack-plugin": "^4.1.1",
"workbox-build": "^5.1.3",
"workerize-loader": "^1.1.0"
},
"_comment": "Polymer fixed to 3.1 because 3.2 throws on logbook page",

View File

@ -1,40 +1,51 @@
/*
This file is not run through webpack, but instead is directly manipulated
by Workbox Webpack plugin. So we cannot use __DEV__ or other constants.
*/
/* global workbox clients */
/* eslint-disable @typescript-eslint/triple-slash-reference */
// eslint-disable-next-line spaced-comment
/// <reference path="../types/service-worker.d.ts" />
/* eslint-env serviceworker */
import {
CacheFirst,
StaleWhileRevalidate,
NetworkOnly,
} from "workbox-strategies";
import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching";
import { registerRoute } from "workbox-routing";
import { cacheNames } from "workbox-core";
// Clean up caches from older workboxes and old service workers.
// Will help with cleaning up Workbox v4 stuff
cleanupOutdatedCaches();
function initRouting() {
workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
precacheAndRoute(
// @ts-ignore
WB_MANIFEST
);
// Cache static content (including translations) on first access.
workbox.routing.registerRoute(
registerRoute(
new RegExp(`${location.host}/(static|frontend_latest|frontend_es5)/.+`),
new workbox.strategies.CacheFirst()
new CacheFirst()
);
// Get api from network.
workbox.routing.registerRoute(
registerRoute(
new RegExp(`${location.host}/(api|auth)/.*`),
new workbox.strategies.NetworkOnly()
new NetworkOnly()
);
// Get manifest, service worker, onboarding from network.
workbox.routing.registerRoute(
registerRoute(
new RegExp(
`${location.host}/(service_worker.js|manifest.json|onboarding.html)`
),
new workbox.strategies.NetworkOnly()
new NetworkOnly()
);
// For rest of the files (on Home Assistant domain only) try both cache and network.
// This includes the root "/" or "/states" response and user files from "/local".
// First access might bring stale data from cache, but a single refresh will bring updated
// file.
workbox.routing.registerRoute(
new RegExp(`${location.host}/.*`),
new workbox.strategies.StaleWhileRevalidate()
);
registerRoute(new RegExp(`${location.host}/.*`), new StaleWhileRevalidate());
}
function initPushNotifications() {
@ -149,7 +160,7 @@ function initPushNotifications() {
self.addEventListener("install", (event) => {
// Delete all runtime caching, so that index.html has to be refetched.
const cacheName = workbox.core.cacheNames.runtime;
const cacheName = cacheNames.runtime;
event.waitUntil(caches.delete(cacheName));
});
@ -160,9 +171,5 @@ self.addEventListener("message", (message) => {
}
});
workbox.setConfig({
debug: false,
});
initRouting();
initPushNotifications();

126
src/types/service-worker.d.ts vendored Normal file
View File

@ -0,0 +1,126 @@
// From https://gist.githubusercontent.com/tiernan/c18a380935e45a6d942ac1e88c5bbaf3/raw/1d103eb5882504505ccc84cbc9398ac20418ef8a/serviceworker.d.ts
/**
* Copyright (c) 2018, Tiernan Cridland
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
* granted, provided that the above copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*
* Service Worker Typings to supplement lib.webworker.ts
* @author Tiernan Cridland
* @email tiernanc@gmail.com
* @license: ISC
*
* lib.webworker.d.ts as well as an es5+ library (es5, es2015, etc) are required.
* Recommended to be used with a triple slash directive in the files requiring the typings only.
* e.g. your-service-worker.js, register-service-worker.js
* e.g. /// <reference path="path/to/serviceworker.d.ts" />
*/
/* eslint-disable */
// Registration
interface WorkerNavigator {
readonly serviceWorker: ServiceWorkerContainer;
}
interface ServiceWorkerContainer {
readonly controller: ServiceWorker;
readonly ready: Promise<ServiceWorkerRegistration>;
oncontrollerchange:
| ((this: ServiceWorkerContainer, event: Event) => any)
| null;
onerror: ((this: ServiceWorkerContainer, event?: Event) => any) | null;
onmessage:
| ((this: ServiceWorkerContainer, event: ServiceWorkerMessageEvent) => any)
| null;
getRegistration(scope?: string): Promise<ServiceWorkerRegistration>;
getRegistrations(): Promise<ServiceWorkerRegistration[]>;
register(
url: string,
options?: ServiceWorkerRegistrationOptions
): Promise<ServiceWorkerRegistration>;
}
interface ServiceWorkerMessageEvent extends Event {
readonly data: any;
readonly lastEventId: string;
readonly origin: string;
readonly ports: ReadonlyArray<MessagePort> | null;
readonly source: ServiceWorker | MessagePort | null;
}
interface ServiceWorkerRegistrationOptions {
scope?: string;
}
// Client API
interface Client {
readonly frameType: ClientFrameType;
}
type ClientFrameType = "auxiliary" | "top-level" | "nested" | "none";
// Events
interface ActivateEvent extends ExtendableEvent {}
interface InstallEvent extends ExtendableEvent {
readonly activeWorker: ServiceWorker;
}
// Fetch API
interface Body {
readonly body: ReadableStream;
}
interface Headers {
entries(): string[][];
keys(): string[];
values(): string[];
}
interface Response extends Body {
readonly useFinalURL: boolean;
clone(): Response;
error(): Response;
redirect(): Response;
}
// Notification API
interface Notification {
readonly actions: NotificationAction[];
readonly requireInteraction: boolean;
readonly silent: boolean;
readonly tag: string;
readonly renotify: boolean;
readonly timestamp: number;
readonly title: string;
readonly vibrate: number[];
close(): void;
requestPermission(): Promise<string>;
}
interface NotificationAction {}
// ServiceWorkerGlobalScope
declare var clients: Clients;
declare var onactivate: ((event?: ActivateEvent) => any) | null;
declare var onfetch: ((event?: FetchEvent) => any) | null;
declare var oninstall: ((event?: InstallEvent) => any) | null;
declare var onnotificationclick: ((event?: NotificationEvent) => any) | null;
declare var onnotificationclose: ((event?: NotificationEvent) => any) | null;
declare var onpush: ((event?: PushEvent) => any) | null;
declare var onpushsubscriptionchange: (() => any) | null;
declare var onsync: ((event?: SyncEvent) => any) | null;
declare var registration: ServiceWorkerRegistration;
declare function skipWaiting(): void;

View File

@ -3,7 +3,7 @@
"target": "es2017",
"module": "esnext",
"moduleResolution": "node",
"lib": ["es2017", "dom", "dom.iterable"],
"lib": ["es2017", "dom", "dom.iterable", "WebWorker"],
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,

724
yarn.lock

File diff suppressed because it is too large Load Diff