Workbox 5 in gulp (#5843)
This commit is contained in:
parent
10358abbec
commit
581fafdcc9
|
@ -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"
|
||||
)
|
||||
)
|
||||
);
|
||||
|
|
|
@ -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"]);
|
||||
});
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
|
@ -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;
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue