Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable service worker for legacy build #21177

Merged
merged 1 commit into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .browserslistrc
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,9 @@ not dead
unreleased versions
last 7 years
> 0.05% and supports websockets

[legacy-sw]
# Same as legacy plus supports service workers
unreleased versions
last 7 years
> 0.05% and supports websockets and supports serviceworkers
55 changes: 55 additions & 0 deletions .yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
diff --git a/build/inject-manifest.js b/build/inject-manifest.js
index 60e3d2bb51c11a19fbbedbad65e101082ec41c36..fed6026630f43f86e25446383982cf6fb694313b 100644
--- a/build/inject-manifest.js
+++ b/build/inject-manifest.js
@@ -104,7 +104,7 @@ async function injectManifest(config) {
replaceString: manifestString,
searchString: options.injectionPoint,
});
- filesToWrite[options.swDest] = source;
+ filesToWrite[options.swDest] = source.replace(url, encodeURI(upath_1.default.basename(destPath)));
filesToWrite[destPath] = map;
}
else {
diff --git a/build/lib/translate-url-to-sourcemap-paths.js b/build/lib/translate-url-to-sourcemap-paths.js
index 3220c5474eeac6e8a56ca9b2ac2bd9be48529e43..5f003879a904d4840529a42dd056d288fd213771 100644
--- a/build/lib/translate-url-to-sourcemap-paths.js
+++ b/build/lib/translate-url-to-sourcemap-paths.js
@@ -22,7 +22,7 @@ function translateURLToSourcemapPaths(url, swSrc, swDest) {
const possibleSrcPath = upath_1.default.resolve(upath_1.default.dirname(swSrc), url);
if (fs_extra_1.default.existsSync(possibleSrcPath)) {
srcPath = possibleSrcPath;
- destPath = upath_1.default.resolve(upath_1.default.dirname(swDest), url);
+ destPath = `${swDest}.map`;
}
else {
warning = `${errors_1.errors['cant-find-sourcemap']} ${possibleSrcPath}`;
diff --git a/src/inject-manifest.ts b/src/inject-manifest.ts
index 8795ddcaa77aea7b0356417e4bc4b19e2b3f860c..fcdc68342d9ac53936c9ed40a9ccfc2f5070cad3 100644
--- a/src/inject-manifest.ts
+++ b/src/inject-manifest.ts
@@ -129,7 +129,10 @@ export async function injectManifest(
searchString: options.injectionPoint!,
});

- filesToWrite[options.swDest] = source;
+ filesToWrite[options.swDest] = source.replace(
+ url!,
+ encodeURI(upath.basename(destPath)),
+ );
filesToWrite[destPath] = map;
} else {
// If there's no sourcemap associated with swSrc, a simple string
diff --git a/src/lib/translate-url-to-sourcemap-paths.ts b/src/lib/translate-url-to-sourcemap-paths.ts
index 072eac40d4ef5d095a01cb7f7e392a9e034853bd..f0bbe69e88ef3a415de18a7e9cb264daea273d71 100644
--- a/src/lib/translate-url-to-sourcemap-paths.ts
+++ b/src/lib/translate-url-to-sourcemap-paths.ts
@@ -28,7 +28,7 @@ export function translateURLToSourcemapPaths(
const possibleSrcPath = upath.resolve(upath.dirname(swSrc), url);
if (fse.existsSync(possibleSrcPath)) {
srcPath = possibleSrcPath;
- destPath = upath.resolve(upath.dirname(swDest), url);
+ destPath = `${swDest}.map`;
} else {
warning = `${errors['cant-find-sourcemap']} ${possibleSrcPath}`;
}
19 changes: 15 additions & 4 deletions build-scripts/bundle.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>

module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
__DEV__: !isProdBuild,
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
__BUILD__: JSON.stringify(latestBuild ? "modern" : "legacy"),
__VERSION__: JSON.stringify(env.version()),
__DEMO__: false,
__SUPERVISOR__: false,
Expand Down Expand Up @@ -79,15 +79,20 @@ module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({
sourceMap: !isTestBuild,
});

module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
module.exports.babelOptions = ({
latestBuild,
isProdBuild,
isTestBuild,
sw,
}) => ({
babelrc: false,
compact: false,
assumptions: {
privateFieldsAsProperties: true,
setPublicClassFields: true,
setSpreadProperties: true,
},
browserslistEnv: latestBuild ? "modern" : "legacy",
browserslistEnv: latestBuild ? "modern" : `legacy${sw ? "-sw" : ""}`,
presets: [
[
"@babel/preset-env",
Expand Down Expand Up @@ -215,7 +220,13 @@ module.exports.config = {
return {
name: "frontend" + nameSuffix(latestBuild),
steverep marked this conversation as resolved.
Show resolved Hide resolved
entry: {
service_worker: "./src/entrypoints/service_worker.ts",
"service-worker":
!env.useRollup() && !latestBuild
? {
import: "./src/entrypoints/service-worker.ts",
layer: "sw",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do these layers work, can't find anything about it in the webpack docs...

Copy link
Member Author

@steverep steverep Jun 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the docs on it are quite sparse. It's more or less a tag/label for a module. You can assign it by entry like this, or via a condition in the module config. Then you can act on it in cache groups, by issuer layer in the module config (like I do here to change the loader options), and they also get grouped in the stats output.

In this PR, it boils down to:

  1. The service worker entry and everything it imports is assigned to the "sw" layer.
  2. For any module in the "sw" layer, Babel transpiles using the "legacy-sw" environment instead of "legacy".

}
: "./src/entrypoints/service-worker.ts",
app: "./src/entrypoints/app.ts",
authorize: "./src/entrypoints/authorize.ts",
onboarding: "./src/entrypoints/onboarding.ts",
Expand Down
144 changes: 66 additions & 78 deletions build-scripts/gulp/service-worker.js
Original file line number Diff line number Diff line change
@@ -1,93 +1,81 @@
// Generate service worker.
// Based on manifest, create a file with the content as service_worker.js
// Generate service workers

import fs from "fs-extra";
import { deleteAsync } from "del";
import gulp from "gulp";
import path from "path";
import sourceMapUrl from "source-map-url";
import workboxBuild from "workbox-build";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join, relative } from "node:path";
import { injectManifest } from "workbox-build";
import paths from "../paths.cjs";

const swDest = path.resolve(paths.app_output_root, "service_worker.js");
const SW_MAP = {
[paths.app_output_latest]: "modern",
[paths.app_output_es5]: "legacy",
};

const writeSW = (content) => fs.outputFileSync(swDest, content.trim() + "\n");

gulp.task("gen-service-worker-app-dev", (done) => {
writeSW(
`
const SW_DEV =
`
console.debug('Service worker disabled in development');

self.addEventListener('install', (event) => {
// This will activate the dev service worker,
// removing any prod service worker the dev might have running
self.skipWaiting();
});
`
);
done();
});
`.trim() + "\n";

steverep marked this conversation as resolved.
Show resolved Hide resolved
gulp.task("gen-service-worker-app-prod", async () => {
// Read bundled source file
const bundleManifestLatest = fs.readJsonSync(
path.resolve(paths.app_output_latest, "manifest.json")
gulp.task("gen-service-worker-app-dev", async () => {
await mkdir(paths.app_output_root, { recursive: true });
await Promise.all(
Object.values(SW_MAP).map((build) =>
writeFile(join(paths.app_output_root, `sw-${build}.js`), SW_DEV, {
encoding: "utf-8",
})
)
);
let serviceWorkerContent = fs.readFileSync(
paths.app_output_root + bundleManifestLatest["service_worker.js"],
"utf-8"
);

// Delete old file from frontend_latest so manifest won't pick it up
fs.removeSync(
paths.app_output_root + bundleManifestLatest["service_worker.js"]
);
fs.removeSync(
paths.app_output_root + bundleManifestLatest["service_worker.js.map"]
);

// Remove ES5
const bundleManifestES5 = fs.readJsonSync(
path.resolve(paths.app_output_es5, "manifest.json")
);
fs.removeSync(paths.app_output_root + bundleManifestES5["service_worker.js"]);
fs.removeSync(
paths.app_output_root + bundleManifestES5["service_worker.js.map"]
);

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.app_output_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
// 'v' added as valid hash letter because in dev we hash with 'dev'
"static/translations/**/en-+([a-fv0-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);
}

// 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);
});

gulp.task("gen-service-worker-app-prod", () =>
Promise.all(
Object.entries(SW_MAP).map(async ([outPath, build]) => {
const manifest = JSON.parse(
await readFile(join(outPath, "manifest.json"), "utf-8")
);
const swSrc = join(paths.app_output_root, manifest["service-worker.js"]);
const buildDir = relative(paths.app_output_root, outPath);
const { warnings } = await injectManifest({
swSrc,
swDest: join(paths.app_output_root, `sw-${build}.js`),
injectionPoint: "__WB_MANIFEST__",
// Files that mach this pattern will be considered unique and skip revision check
// ignore JS files + translation files
dontCacheBustURLsMatching: new RegExp(
`(?:${buildDir}/.+|static/translations/.+)`
),
globDirectory: paths.app_output_root,
globPatterns: [
`${buildDir}/*.js`,
// Cache all English translations because we catch them as fallback
// Using pattern to match hash instead of * to avoid caching en-GB
// 'v' added as valid hash letter because in dev we hash with 'dev'
"static/translations/**/en-+([a-fv0-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",
],
globIgnores: [`${buildDir}/service-worker*`],
});
if (warnings.length > 0) {
console.warn(
`Problems while injecting ${build} service worker:\n`,
warnings.join("\n")
);
}
await deleteAsync(`${swSrc}?(.map)`);
})
)
);
12 changes: 9 additions & 3 deletions build-scripts/webpack.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,19 @@ const createWebpackConfig = ({
rules: [
{
test: /\.m?js$|\.ts$/,
use: {
use: (info) => ({
loader: "babel-loader",
options: {
...bundle.babelOptions({ latestBuild, isProdBuild, isTestBuild }),
...bundle.babelOptions({
latestBuild,
isProdBuild,
isTestBuild,
sw: info.issuerLayer === "sw",
}),
cacheDirectory: !isProdBuild,
cacheCompression: false,
},
},
}),
resolve: {
fullySpecified: false,
},
Expand Down Expand Up @@ -228,6 +233,7 @@ const createWebpackConfig = ({
),
},
experiments: {
layers: true,
outputModule: true,
},
};
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,6 @@
"rollup-plugin-visualizer": "5.12.0",
"serve-handler": "6.1.5",
"sinon": "18.0.0",
"source-map-url": "0.4.1",
"systemjs": "6.15.1",
"tar": "7.4.0",
"terser-webpack-plugin": "5.3.10",
Expand All @@ -244,7 +243,7 @@
"webpack-manifest-plugin": "5.0.0",
"webpack-stats-plugin": "1.1.3",
"webpackbar": "6.0.1",
"workbox-build": "7.1.1"
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
},
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
"resolutions": {
Expand Down
2 changes: 1 addition & 1 deletion src/entrypoints/custom-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function initialize(
);
}

if (__BUILD__ === "es5") {
if (__BUILD__ === "legacy") {
start = start.then(() => window.loadES5Adapter());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,16 @@ import {
StaleWhileRevalidate,
} from "workbox-strategies";

declare const __WB_MANIFEST__: Parameters<typeof precacheAndRoute>[0];

const noFallBackRegEx =
/\/(api|static|auth|frontend_latest|frontend_es5|local)\/.*/;

const initRouting = () => {
precacheAndRoute(
// @ts-ignore
WB_MANIFEST,
{
// Ignore all URL parameters.
ignoreURLParametersMatching: [/.*/],
}
);
precacheAndRoute(__WB_MANIFEST__, {
// Ignore all URL parameters.
ignoreURLParametersMatching: [/.*/],
});

// Cache static content (including translations) on first access.
registerRoute(
Expand Down Expand Up @@ -56,11 +54,8 @@ const initRouting = () => {
// Get api from network.
registerRoute(/\/(api|auth)\/.*/, new NetworkOnly());

// Get manifest, service worker, onboarding from network.
registerRoute(
/\/(service_worker.js|manifest.json|onboarding.html)/,
new NetworkOnly()
);
// Get manifest and onboarding from network.
registerRoute(/\/(?:manifest\.json|onboarding\.html)/, new NetworkOnly());

// For the root "/" we ignore search
registerRoute(
Expand Down
2 changes: 1 addition & 1 deletion src/panels/config/info/ha-config-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class HaConfigInfo extends LitElement {
)}
</span>
<span class="version">
${JS_VERSION}${JS_TYPE !== "latest" ? ` ⸱ ${JS_TYPE}` : ""}
${JS_VERSION}${JS_TYPE !== "modern" ? ` ⸱ ${JS_TYPE}` : ""}
</span>
</li>
</ul>
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ declare global {
/* eslint-disable no-var, no-redeclare */
var __DEV__: boolean;
var __DEMO__: boolean;
var __BUILD__: "latest" | "es5";
var __BUILD__: "modern" | "legacy";
var __VERSION__: string;
var __STATIC_PATH__: string;
var __BACKWARDS_COMPAT__: boolean;
Expand Down
2 changes: 1 addition & 1 deletion src/util/custom-panel/load-custom-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const getUrl = (

// if both module and JS provided, base url on frontend build
if (panelConfig.module_url && panelConfig.js_url) {
if (__BUILD__ === "latest") {
if (__BUILD__ === "modern") {
return {
type: "module",
url: panelConfig.module_url,
Expand Down
Loading
Loading