From 3967d26f9dca2dde885749716d95356e89c3090d Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Fri, 16 Aug 2024 15:30:44 +0530 Subject: [PATCH 1/4] Make service worker work in dev --- scripts/build-plugins/sw-dev.js | 67 ++++++++++++ src/platform/web/index.html | 4 +- src/platform/web/public/icon.png | Bin 0 -> 15862 bytes src/platform/web/sw.js | 180 ++++++++++++++++++++----------- 4 files changed, 185 insertions(+), 66 deletions(-) create mode 100644 scripts/build-plugins/sw-dev.js create mode 100644 src/platform/web/public/icon.png diff --git a/scripts/build-plugins/sw-dev.js b/scripts/build-plugins/sw-dev.js new file mode 100644 index 00000000..876ba43e --- /dev/null +++ b/scripts/build-plugins/sw-dev.js @@ -0,0 +1,67 @@ +/* +Copyright 2024 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import fs from "fs/promises"; +import path from "path"; + +/** + * This rollup plugin makes it possible to use the serviceworker with the dev server. + * The service worker is located in `/src/platform/web/sw.js` and it contains some + * fields that need to be replaced with sensible values. + * + * We have a plugin that does this during build (see `./service-worker.js`). + * This plugin does more or less the same but for dev. + */ + +export function transformServiceWorkerInDevServer() { + // See https://vitejs.dev/config/shared-options.html#define + // Comes from vite.config.js + let define; + + return { + name: "hydrogen:transformServiceWorkerInDevServer", + apply: "serve", + enforce: "pre", + + configResolved(resolvedConfig) { + // store the resolved config + define = resolvedConfig.define; + }, + + async load(id) { + if (!id.includes("sw.js")) return null; + let code = await readServiceWorkerCode(); + for (const [key, value] of Object.entries(define)) { + code = code.replaceAll(key, value); + } + return code; + }, + }; +} + +/** + * Read service worker code from `src/platform/web/sw.js` + * @returns code as string + */ +async function readServiceWorkerCode() { + const resolvedLocation = path.resolve( + __dirname, + "../../", + "./src/platform/web/sw.js" + ); + const data = await fs.readFile(resolvedLocation, { encoding: "utf-8" }); + return data; +} diff --git a/src/platform/web/index.html b/src/platform/web/index.html index 16418699..37bfec1c 100644 --- a/src/platform/web/index.html +++ b/src/platform/web/index.html @@ -19,9 +19,7 @@ import {Platform} from "./Platform"; import configURL from "./assets/config.json?url"; import assetPaths from "./sdk/paths/vite"; - if (import.meta.env.PROD) { - assetPaths.serviceWorker = "sw.js"; - } + assetPaths.serviceWorker = "sw.js"; const platform = new Platform({ container: document.body, assetPaths, diff --git a/src/platform/web/public/icon.png b/src/platform/web/public/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b6c70c00a4957f34bda8d23ef1dd258f074f8764 GIT binary patch literal 15862 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 literal 0 HcmV?d00001 diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index 088bc059..2c0aca5f 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -15,8 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import NOTIFICATION_BADGE_ICON from "./assets/icon.png?url"; -// replaced by the service worker build plugin +const NOTIFICATION_BADGE_ICON = "icon.png"; + +// These are replaced by rollup plugins const UNHASHED_PRECACHED_ASSETS = DEFINE_UNHASHED_PRECACHED_ASSETS; const HASHED_PRECACHED_ASSETS = DEFINE_HASHED_PRECACHED_ASSETS; const HASHED_CACHED_ON_REQUEST_ASSETS = DEFINE_HASHED_CACHED_ON_REQUEST_ASSETS; @@ -25,20 +26,24 @@ const unhashedCacheName = `hydrogen-assets-${DEFINE_GLOBAL_HASH}`; const hashedCacheName = `hydrogen-assets`; const mediaThumbnailCacheName = `hydrogen-media-thumbnails-v2`; -self.addEventListener('install', function(e) { - e.waitUntil((async () => { - const unhashedCache = await caches.open(unhashedCacheName); - await unhashedCache.addAll(UNHASHED_PRECACHED_ASSETS); - const hashedCache = await caches.open(hashedCacheName); - await Promise.all(HASHED_PRECACHED_ASSETS.map(async asset => { - if (!await hashedCache.match(asset)) { - await hashedCache.add(asset); - } - })); - })()); +self.addEventListener("install", function (e) { + e.waitUntil( + (async () => { + const unhashedCache = await caches.open(unhashedCacheName); + await unhashedCache.addAll(UNHASHED_PRECACHED_ASSETS); + const hashedCache = await caches.open(hashedCacheName); + await Promise.all( + HASHED_PRECACHED_ASSETS.map(async (asset) => { + if (!(await hashedCache.match(asset))) { + await hashedCache.add(asset); + } + }) + ); + })() + ); }); -self.addEventListener('activate', (event) => { +self.addEventListener("activate", (event) => { // on a first page load/sw install, // start using the service worker on all pages straight away self.clients.claim(); @@ -49,26 +54,29 @@ async function purgeOldCaches() { // remove any caches we don't know about const keyList = await caches.keys(); for (const key of keyList) { - if (key !== unhashedCacheName && key !== hashedCacheName && key !== mediaThumbnailCacheName) { + if ( + key !== unhashedCacheName && + key !== hashedCacheName && + key !== mediaThumbnailCacheName + ) { await caches.delete(key); } } // remove the cache for any old hashed resource const hashedCache = await caches.open(hashedCacheName); const keys = await hashedCache.keys(); - const hashedAssetURLs = - HASHED_PRECACHED_ASSETS - .concat(HASHED_CACHED_ON_REQUEST_ASSETS) - .map(a => new URL(a, self.registration.scope).href); + const hashedAssetURLs = HASHED_PRECACHED_ASSETS.concat( + HASHED_CACHED_ON_REQUEST_ASSETS + ).map((a) => new URL(a, self.registration.scope).href); for (const request of keys) { - if (!hashedAssetURLs.some(url => url === request.url)) { + if (!hashedAssetURLs.some((url) => url === request.url)) { hashedCache.delete(request); } } } -self.addEventListener('fetch', (event) => { +self.addEventListener("fetch", (event) => { /* service worker shouldn't handle xhr uploads because otherwise the progress events won't fire. @@ -95,12 +103,18 @@ let pendingFetchAbortController = new AbortController(); async function handleRequest(request) { try { - if (request.url.includes("config.json") || /theme-.+\.json/.test(request.url)) { + if ( + request.url.includes("config.json") || + /theme-.+\.json/.test(request.url) + ) { return handleStaleWhileRevalidateRequest(request); } const url = new URL(request.url); // rewrite / to /index.html so it hits the cache - if (url.origin === baseURL.origin && url.pathname === baseURL.pathname) { + if ( + url.origin === baseURL.origin && + url.pathname === baseURL.pathname + ) { request = new Request(new URL("index.html", baseURL.href)); } let response = await readCache(request); @@ -108,9 +122,15 @@ async function handleRequest(request) { // use cors so the resource in the cache isn't opaque and uses up to 7mb // https://developers.google.com/web/tools/chrome-devtools/progressive-web-apps?utm_source=devtools#opaque-responses if (isCacheableThumbnail(url)) { - response = await fetch(request, {signal: pendingFetchAbortController.signal, mode: "cors", credentials: "omit"}); + response = await fetch(request, { + signal: pendingFetchAbortController.signal, + mode: "cors", + credentials: "omit", + }); } else { - response = await fetch(request, {signal: pendingFetchAbortController.signal}); + response = await fetch(request, { + signal: pendingFetchAbortController.signal, + }); } await updateCache(request, response); } @@ -184,7 +204,7 @@ async function readCache(request) { if (response) { return response; } - + const url = new URL(request.url); if (isCacheableThumbnail(url)) { const mediaThumbnailCache = await caches.open(mediaThumbnailCacheName); @@ -198,9 +218,10 @@ async function readCache(request) { return response; } -self.addEventListener('message', (event) => { - const reply = payload => event.source.postMessage({replyTo: event.data.id, payload}); - const {replyTo} = event.data; +self.addEventListener("message", (event) => { + const reply = (payload) => + event.source.postMessage({ replyTo: event.data.id, payload }); + const { replyTo } = event.data; if (replyTo) { const resolve = pendingReplies.get(replyTo); if (resolve) { @@ -210,7 +231,10 @@ self.addEventListener('message', (event) => { } else { switch (event.data?.type) { case "version": - reply({version: DEFINE_VERSION, buildHash: DEFINE_GLOBAL_HASH}); + reply({ + version: DEFINE_VERSION, + buildHash: DEFINE_GLOBAL_HASH, + }); break; case "skipWaiting": self.skipWaiting(); @@ -220,8 +244,10 @@ self.addEventListener('message', (event) => { break; case "closeSession": event.waitUntil( - closeSession(event.data.payload.sessionId, event.source.id) - .finally(() => reply()) + closeSession( + event.data.payload.sessionId, + event.source.id + ).finally(() => reply()) ); break; } @@ -235,29 +261,40 @@ async function openClientFromNotif(event) { console.log("clicked notif with tag", event.notification.tag); return; } - const {sessionId, roomId} = event.notification.data; + const { sessionId, roomId } = event.notification.data; const sessionHash = `#/session/${sessionId}`; const roomHash = `${sessionHash}/room/${roomId}`; - const clientWithSession = await findClient(async client => { - return await sendAndWaitForReply(client, "hasSessionOpen", {sessionId}); + const clientWithSession = await findClient(async (client) => { + return await sendAndWaitForReply(client, "hasSessionOpen", { + sessionId, + }); }); if (clientWithSession) { - console.log("notificationclick: client has session open, showing room there"); + console.log( + "notificationclick: client has session open, showing room there" + ); // use a message rather than clientWithSession.navigate here as this refreshes the page on chrome - clientWithSession.postMessage({type: "openRoom", payload: {roomId}}); - if ('focus' in clientWithSession) { + clientWithSession.postMessage({ + type: "openRoom", + payload: { roomId }, + }); + if ("focus" in clientWithSession) { try { await clientWithSession.focus(); - } catch (err) { console.error(err); } // I've had this throw on me on Android + } catch (err) { + console.error(err); + } // I've had this throw on me on Android } } else if (self.clients.openWindow) { - console.log("notificationclick: no client found with session open, opening new window"); + console.log( + "notificationclick: no client found with session open, opening new window" + ); const roomURL = new URL(`./${roomHash}`, baseURL).href; await self.clients.openWindow(roomURL); } } -self.addEventListener('notificationclick', event => { +self.addEventListener("notificationclick", (event) => { event.notification.close(); event.waitUntil(openClientFromNotif(event)); }); @@ -268,19 +305,30 @@ async function handlePushNotification(n) { let sender = n.sender_display_name || n.sender; if (sender && n.event_id) { const roomId = n.room_id; - const hasFocusedClientOnRoom = !!await findClient(async client => { + const hasFocusedClientOnRoom = !!(await findClient(async (client) => { if (client.visibilityState === "visible" && client.focused) { - return await sendAndWaitForReply(client, "hasRoomOpen", {sessionId, roomId}); + return await sendAndWaitForReply(client, "hasRoomOpen", { + sessionId, + roomId, + }); } - }); + })); if (hasFocusedClientOnRoom) { console.log("client is focused, room is open, don't show notif"); return; } - const newMessageNotifs = Array.from(await self.registration.getNotifications({tag: NOTIF_TAG_NEW_MESSAGE})); - const notifsForRoom = newMessageNotifs.filter(n => n.data.roomId === roomId); - const hasMultiNotification = notifsForRoom.some(n => n.data.multi); - const hasSingleNotifsForRoom = newMessageNotifs.some(n => !n.data.multi); + const newMessageNotifs = Array.from( + await self.registration.getNotifications({ + tag: NOTIF_TAG_NEW_MESSAGE, + }) + ); + const notifsForRoom = newMessageNotifs.filter( + (n) => n.data.roomId === roomId + ); + const hasMultiNotification = notifsForRoom.some((n) => n.data.multi); + const hasSingleNotifsForRoom = newMessageNotifs.some( + (n) => !n.data.multi + ); const roomName = n.room_name || n.room_alias; let multi = false; let label; @@ -304,9 +352,9 @@ async function handlePushNotification(n) { } await self.registration.showNotification(label, { body, - data: {sessionId, roomId, multi}, + data: { sessionId, roomId, multi }, tag: NOTIF_TAG_NEW_MESSAGE, - badge: NOTIFICATION_BADGE_ICON + badge: NOTIFICATION_BADGE_ICON, }); } // we could consider hiding previous notifications here based on the unread count @@ -315,25 +363,31 @@ async function handlePushNotification(n) { // when no client is visible, see https://goo.gl/yqv4Q4 } -self.addEventListener('push', event => { +self.addEventListener("push", (event) => { event.waitUntil(handlePushNotification(event.data.json())); }); async function closeSession(sessionId, requestingClientId) { const clients = await self.clients.matchAll(); - await Promise.all(clients.map(async client => { - if (client.id !== requestingClientId) { - await sendAndWaitForReply(client, "closeSession", {sessionId}); - } - })); + await Promise.all( + clients.map(async (client) => { + if (client.id !== requestingClientId) { + await sendAndWaitForReply(client, "closeSession", { + sessionId, + }); + } + }) + ); } async function haltRequests() { // first ask all clients to block sending any more requests - const clients = await self.clients.matchAll({type: "window"}); - await Promise.all(clients.map(client => { - return sendAndWaitForReply(client, "haltRequests"); - })); + const clients = await self.clients.matchAll({ type: "window" }); + await Promise.all( + clients.map((client) => { + return sendAndWaitForReply(client, "haltRequests"); + }) + ); // and only then abort the current requests pendingFetchAbortController.abort(); } @@ -343,15 +397,15 @@ let messageIdCounter = 0; function sendAndWaitForReply(client, type, payload) { messageIdCounter += 1; const id = messageIdCounter; - const promise = new Promise(resolve => { + const promise = new Promise((resolve) => { pendingReplies.set(id, resolve); }); - client.postMessage({type, id, payload}); + client.postMessage({ type, id, payload }); return promise; } async function findClient(predicate) { - const clientList = await self.clients.matchAll({type: "window"}); + const clientList = await self.clients.matchAll({ type: "window" }); for (const client of clientList) { if (await predicate(client)) { return client; From 8f7d94eb7593efa416bebc0733808a416573d6ef Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Fri, 16 Aug 2024 15:33:39 +0530 Subject: [PATCH 2/4] Make service worker work with sdk --- scripts/build-plugins/service-worker.js | 206 +++++++++++++------ scripts/sdk/build.sh | 4 +- src/platform/web/dom/ServiceWorkerHandler.js | 18 +- vite.common-config.js | 119 ++++++----- vite.config.js | 46 +++-- vite.sdk-assets-config.js | 66 +++--- vite.sdk-lib-config.js | 74 +++---- 7 files changed, 333 insertions(+), 200 deletions(-) diff --git a/scripts/build-plugins/service-worker.js b/scripts/build-plugins/service-worker.js index 85619545..9939440d 100644 --- a/scripts/build-plugins/service-worker.js +++ b/scripts/build-plugins/service-worker.js @@ -1,6 +1,5 @@ -const fs = require('fs/promises'); -const path = require('path'); -const xxhash = require('xxhashjs'); +const path = require("path"); +const xxhash = require("xxhashjs"); function contentHash(str) { var hasher = new xxhash.h32(0); @@ -8,16 +7,21 @@ function contentHash(str) { return hasher.digest(); } -function injectServiceWorker(swFile, findUnhashedFileNamesFromBundle, placeholdersPerChunk) { +function injectServiceWorker( + swFile, + findUnhashedFileNamesFromBundle, + placeholdersPerChunk +) { const swName = path.basename(swFile); - let root; let version; let logger; + let mode; return { name: "hydrogen:injectServiceWorker", apply: "build", enforce: "post", + buildStart() { this.emitFile({ type: "chunk", @@ -25,39 +29,63 @@ function injectServiceWorker(swFile, findUnhashedFileNamesFromBundle, placeholde id: swFile, }); }, - configResolved: config => { - root = config.root; + + configResolved: (config) => { + mode = config.mode; version = JSON.parse(config.define.DEFINE_VERSION); // unquote logger = config.logger; }, - generateBundle: async function(options, bundle) { + + generateBundle: async function (options, bundle) { const otherUnhashedFiles = findUnhashedFileNamesFromBundle(bundle); const unhashedFilenames = [swName].concat(otherUnhashedFiles); - const unhashedFileContentMap = unhashedFilenames.reduce((map, fileName) => { - const chunkOrAsset = bundle[fileName]; - if (!chunkOrAsset) { - throw new Error("could not get content for uncached asset or chunk " + fileName); - } - map[fileName] = chunkOrAsset.source || chunkOrAsset.code; - return map; - }, {}); + const unhashedFileContentMap = unhashedFilenames.reduce( + (map, fileName) => { + const chunkOrAsset = bundle[fileName]; + if (!chunkOrAsset) { + throw new Error( + "could not get content for uncached asset or chunk " + + fileName + ); + } + map[fileName] = chunkOrAsset.source || chunkOrAsset.code; + return map; + }, + {} + ); const assets = Object.values(bundle); - const hashedFileNames = assets.map(o => o.fileName).filter(fileName => !unhashedFileContentMap[fileName]); - const globalHash = getBuildHash(hashedFileNames, unhashedFileContentMap); + const hashedFileNames = assets + .map((o) => o.fileName) + .filter((fileName) => !unhashedFileContentMap[fileName]); + const globalHash = getBuildHash( + hashedFileNames, + unhashedFileContentMap + ); const placeholderValues = { DEFINE_GLOBAL_HASH: `"${globalHash}"`, - ...getCacheFileNamePlaceholderValues(swName, unhashedFilenames, assets, placeholdersPerChunk) + ...getCacheFileNamePlaceholderValues( + swName, + unhashedFilenames, + assets, + mode + ), }; - replacePlaceholdersInChunks(assets, placeholdersPerChunk, placeholderValues); + replacePlaceholdersInChunks( + assets, + placeholdersPerChunk, + placeholderValues + ); logger.info(`\nBuilt ${version} (${globalHash})`); - } + }, }; } function getBuildHash(hashedFileNames, unhashedFileContentMap) { - const unhashedHashes = Object.entries(unhashedFileContentMap).map(([fileName, content]) => { - return `${fileName}-${contentHash(Buffer.from(content))}`; - }); + const unhashedHashes = Object.entries(unhashedFileContentMap).map( + ([fileName, content]) => { + return `${fileName}-${contentHash(Buffer.from(content))}`; + } + ); const globalHashAssets = hashedFileNames.concat(unhashedHashes); globalHashAssets.sort(); return contentHash(globalHashAssets.join(",")).toString(); @@ -66,60 +94,87 @@ function getBuildHash(hashedFileNames, unhashedFileContentMap) { const NON_PRECACHED_JS = [ "hydrogen-legacy", "olm_legacy.js", - // most environments don't need the worker - "main.js" + // most environments don't need the worker + "main.js", ]; function isPreCached(asset) { - const {name, fileName} = asset; - return name.endsWith(".svg") || - name.endsWith(".png") || - name.endsWith(".css") || - name.endsWith(".wasm") || - name.endsWith(".html") || - // the index and vendor chunks don't have an extension in `name`, so check extension on `fileName` - fileName.endsWith(".js") && !NON_PRECACHED_JS.includes(path.basename(name)); + const { name, fileName } = asset; + return ( + name?.endsWith(".svg") || + name?.endsWith(".png") || + name?.endsWith(".css") || + name?.endsWith(".wasm") || + name?.endsWith(".html") || + // the index and vendor chunks don't have an extension in `name`, so check extension on `fileName` + (fileName.endsWith(".js") && + !NON_PRECACHED_JS.includes(path.basename(name))) + ); } -function getCacheFileNamePlaceholderValues(swName, unhashedFilenames, assets) { +function getCacheFileNamePlaceholderValues( + swName, + unhashedFilenames, + assets, + mode +) { const unhashedPreCachedAssets = []; const hashedPreCachedAssets = []; const hashedCachedOnRequestAssets = []; - for (const asset of assets) { - const {name, fileName} = asset; - // the service worker should not be cached at all, - // it's how updates happen - if (fileName === swName) { - continue; - } else if (unhashedFilenames.includes(fileName)) { - unhashedPreCachedAssets.push(fileName); - } else if (isPreCached(asset)) { - hashedPreCachedAssets.push(fileName); - } else { - hashedCachedOnRequestAssets.push(fileName); + if (mode === "production") { + for (const asset of assets) { + const { name, fileName } = asset; + // the service worker should not be cached at all, + // it's how updates happen + if (fileName === swName) { + continue; + } else if (unhashedFilenames.includes(fileName)) { + unhashedPreCachedAssets.push(fileName); + } else if (isPreCached(asset)) { + hashedPreCachedAssets.push(fileName); + } else { + hashedCachedOnRequestAssets.push(fileName); + } } } return { - DEFINE_UNHASHED_PRECACHED_ASSETS: JSON.stringify(unhashedPreCachedAssets), + DEFINE_UNHASHED_PRECACHED_ASSETS: JSON.stringify( + unhashedPreCachedAssets + ), DEFINE_HASHED_PRECACHED_ASSETS: JSON.stringify(hashedPreCachedAssets), - DEFINE_HASHED_CACHED_ON_REQUEST_ASSETS: JSON.stringify(hashedCachedOnRequestAssets) - } + DEFINE_HASHED_CACHED_ON_REQUEST_ASSETS: JSON.stringify( + hashedCachedOnRequestAssets + ), + }; } -function replacePlaceholdersInChunks(assets, placeholdersPerChunk, placeholderValues) { +function replacePlaceholdersInChunks( + assets, + placeholdersPerChunk, + placeholderValues +) { for (const [name, placeholderMap] of Object.entries(placeholdersPerChunk)) { - const chunk = assets.find(a => a.type === "chunk" && a.name === name); + const chunk = assets.find((a) => a.type === "chunk" && a.name === name); if (!chunk) { - throw new Error(`could not find chunk ${name} to replace placeholders`); + throw new Error( + `could not find chunk ${name} to replace placeholders` + ); } - for (const [placeholderName, placeholderLiteral] of Object.entries(placeholderMap)) { + for (const [placeholderName, placeholderLiteral] of Object.entries( + placeholderMap + )) { const replacedValue = placeholderValues[placeholderName]; const oldCode = chunk.code; - chunk.code = chunk.code.replaceAll(placeholderLiteral, replacedValue); + chunk.code = chunk.code.replaceAll( + placeholderLiteral, + replacedValue + ); if (chunk.code === oldCode) { - throw new Error(`Could not replace ${placeholderName} in ${name}, looking for literal ${placeholderLiteral}:\n${chunk.code}`); + throw new Error( + `Could not replace ${placeholderName} in ${name}, looking for literal ${placeholderLiteral}` + ); } } } @@ -134,7 +189,7 @@ function replacePlaceholdersInChunks(assets, placeholdersPerChunk, placeholderVa * transformation will touch them (minifying, ...) and we can do a * string replacement still at the end of the build. */ function definePlaceholderValue(mode, name, devValue) { - if (mode === "production") { + if (mode === "production" || mode === "sdk") { // note that `prompt(...)` will never be in the final output, it's replaced by the final value // once we know at the end of the build what it is and just used as a temporary value during the build // as something that will not be transformed. @@ -145,13 +200,40 @@ function definePlaceholderValue(mode, name, devValue) { } } +/** + * Returns the short sha for the latest git commit + * @see https://stackoverflow.com/a/35778030 + */ +function getLatestGitCommitHash() { + return require("child_process") + .execSync("git rev-parse --short HEAD") + .toString() + .trim(); +} + function createPlaceholderValues(mode) { return { - DEFINE_GLOBAL_HASH: definePlaceholderValue(mode, "DEFINE_GLOBAL_HASH", null), - DEFINE_UNHASHED_PRECACHED_ASSETS: definePlaceholderValue(mode, "UNHASHED_PRECACHED_ASSETS", []), - DEFINE_HASHED_PRECACHED_ASSETS: definePlaceholderValue(mode, "HASHED_PRECACHED_ASSETS", []), - DEFINE_HASHED_CACHED_ON_REQUEST_ASSETS: definePlaceholderValue(mode, "HASHED_CACHED_ON_REQUEST_ASSETS", []), + DEFINE_GLOBAL_HASH: definePlaceholderValue( + mode, + "DEFINE_GLOBAL_HASH", + `git commit: ${getLatestGitCommitHash()}` + ), + DEFINE_UNHASHED_PRECACHED_ASSETS: definePlaceholderValue( + mode, + "UNHASHED_PRECACHED_ASSETS", + [] + ), + DEFINE_HASHED_PRECACHED_ASSETS: definePlaceholderValue( + mode, + "HASHED_PRECACHED_ASSETS", + [] + ), + DEFINE_HASHED_CACHED_ON_REQUEST_ASSETS: definePlaceholderValue( + mode, + "HASHED_CACHED_ON_REQUEST_ASSETS", + [] + ), }; } -module.exports = {injectServiceWorker, createPlaceholderValues}; +module.exports = { injectServiceWorker, createPlaceholderValues }; diff --git a/scripts/sdk/build.sh b/scripts/sdk/build.sh index 68c0e472..9f063ac0 100755 --- a/scripts/sdk/build.sh +++ b/scripts/sdk/build.sh @@ -8,8 +8,8 @@ shopt -s extglob # Only remove the directory contents instead of the whole directory to maintain # the `npm link`/`yarn link` symlink rm -rf target/* -yarn run vite build -c vite.sdk-assets-config.js -yarn run vite build -c vite.sdk-lib-config.js +yarn run vite build -c vite.sdk-assets-config.js --mode sdk +yarn run vite build -c vite.sdk-lib-config.js --mode sdk yarn tsc -p tsconfig-declaration.json ./scripts/sdk/create-manifest.js ./target/package.json mkdir target/paths diff --git a/src/platform/web/dom/ServiceWorkerHandler.js b/src/platform/web/dom/ServiceWorkerHandler.js index 0bb8c725..4b92d413 100644 --- a/src/platform/web/dom/ServiceWorkerHandler.js +++ b/src/platform/web/dom/ServiceWorkerHandler.js @@ -103,8 +103,22 @@ export class ServiceWorkerHandler { if (document.hidden) { return; } - const version = await this._sendAndWaitForReply("version", null, this._registration.waiting); - if (confirm(`Version ${version.version} (${version.buildHash}) is available. Reload to apply?`)) { + const version = await this._sendAndWaitForReply( + "version", + null, + this._registration.waiting + ); + const isSdk = DEFINE_IS_SDK; + const isDev = this.version === "develop"; + // Don't ask for confirmation when being used as an sdk/ when being run in dev server + if ( + isSdk || + isDev || + confirm( + `Version ${version.version} (${version.buildHash}) is available. Reload to apply?` + ) + ) { + console.log("Service Worker has been updated!"); // prevent any fetch requests from going to the service worker // from any client, so that it is not kept active // when calling skipWaiting on the new one diff --git a/vite.common-config.js b/vite.common-config.js index 2fa09d46..adb35852 100644 --- a/vite.common-config.js +++ b/vite.common-config.js @@ -1,59 +1,78 @@ -const cssvariables = require("postcss-css-variables"); +const { + createPlaceholderValues, +} = require("./scripts/build-plugins/service-worker"); const flexbugsFixes = require("postcss-flexbugs-fixes"); const compileVariables = require("./scripts/postcss/css-compile-variables"); const urlVariables = require("./scripts/postcss/css-url-to-variables"); const urlProcessor = require("./scripts/postcss/css-url-processor"); -const fs = require("fs"); -const path = require("path"); -const manifest = require("./package.json"); -const version = manifest.version; +const appManifest = require("./package.json"); +const sdkManifest = require("./scripts/sdk/base-manifest.json"); const compiledVariables = new Map(); -import {buildColorizedSVG as replacer} from "./scripts/postcss/svg-builder.mjs"; -import {derive} from "./src/platform/web/theming/shared/color.mjs"; +import { buildColorizedSVG as replacer } from "./scripts/postcss/svg-builder.mjs"; +import { derive } from "./src/platform/web/theming/shared/color.mjs"; -const commonOptions = { - logLevel: "warn", - publicDir: false, - server: { - hmr: false - }, - resolve: { - alias: { - // these should only be imported by the base-x package in any runtime code - // and works in the browser with a Uint8Array shim, - // rather than including a ton of polyfill code - "safe-buffer": "./scripts/package-overrides/safe-buffer/index.js", - "buffer": "./scripts/package-overrides/buffer/index.js", - } - }, - build: { - emptyOutDir: true, - assetsInlineLimit: 0, - polyfillModulePreload: false, - }, - assetsInclude: ['**/config.json'], - define: { - DEFINE_VERSION: JSON.stringify(version), - DEFINE_GLOBAL_HASH: JSON.stringify(null), - }, - css: { - postcss: { - plugins: [ - compileVariables({derive, compiledVariables}), - urlVariables({compiledVariables}), - urlProcessor({replacer}), - // cssvariables({ - // preserve: (declaration) => { - // return declaration.value.indexOf("var(--ios-") == 0; - // } - // }), - // the grid option creates some source fragment that causes the vite warning reporter to crash because - // it wants to log a warning on a line that does not exist in the source fragment. - // autoprefixer({overrideBrowserslist: ["IE 11"], grid: "no-autoplace"}), - flexbugsFixes() - ] - } - } +const commonOptions = (mode) => { + const definePlaceholders = createPlaceholderValues(mode); + return { + logLevel: "warn", + publicDir: false, + server: { + hmr: false, + }, + resolve: { + alias: { + // these should only be imported by the base-x package in any runtime code + // and works in the browser with a Uint8Array shim, + // rather than including a ton of polyfill code + "safe-buffer": + "./scripts/package-overrides/safe-buffer/index.js", + buffer: "./scripts/package-overrides/buffer/index.js", + }, + }, + build: { + emptyOutDir: true, + assetsInlineLimit: 0, + polyfillModulePreload: false, + }, + assetsInclude: ["**/config.json"], + define: Object.assign( + { + DEFINE_VERSION: `"${getVersion(mode)}"`, + DEFINE_GLOBAL_HASH: JSON.stringify(null), + DEFINE_IS_SDK: mode === "sdk" ? "true" : "false", + DEFINE_PROJECT_DIR: JSON.stringify(__dirname), + }, + definePlaceholders + ), + css: { + postcss: { + plugins: [ + compileVariables({ derive, compiledVariables }), + urlVariables({ compiledVariables }), + urlProcessor({ replacer }), + flexbugsFixes(), + ], + }, + }, + }; }; +/** + * Get the version for this build + * @param mode Vite mode for this build + * @returns string representing version + */ +function getVersion(mode) { + if (mode === "production") { + // This is an app build, so return the version from root/package.json + return appManifest.version; + } else if (mode === "sdk") { + // For the sdk build, return version from base-manifest.json + return sdkManifest.version; + } else { + // For the develop server + return "develop"; + } +} + module.exports = { commonOptions, compiledVariables }; diff --git a/vite.config.js b/vite.config.js index 0bbeb4d4..6c36ee2c 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,15 +1,20 @@ const injectWebManifest = require("./scripts/build-plugins/manifest"); const {injectServiceWorker, createPlaceholderValues} = require("./scripts/build-plugins/service-worker"); +const { + transformServiceWorkerInDevServer, +} = require("./scripts/build-plugins/sw-dev"); const themeBuilder = require("./scripts/build-plugins/rollup-plugin-build-themes"); -const {defineConfig} = require('vite'); -const mergeOptions = require('merge-options').bind({concatArrays: true}); -const {commonOptions, compiledVariables} = require("./vite.common-config.js"); +const { defineConfig } = require("vite"); +const mergeOptions = require("merge-options").bind({ concatArrays: true }); +const { commonOptions, compiledVariables } = require("./vite.common-config.js"); -export default defineConfig(({mode}) => { +export default defineConfig(({ mode }) => { const definePlaceholders = createPlaceholderValues(mode); - return mergeOptions(commonOptions, { + const options = commonOptions(mode); + return mergeOptions(options, { root: "src/platform/web", base: "./", + publicDir: "./public", build: { outDir: "../../../target", minify: true, @@ -19,18 +24,17 @@ export default defineConfig(({mode}) => { assetFileNames: (asset) => { if (asset.name.includes("config.json")) { return "[name][extname]"; - } - else if (asset.name.match(/theme-.+\.json/)) { + } else if (asset.name.match(/theme-.+\.json/)) { return "assets/[name][extname]"; - } - else { + } else { return "assets/[name].[hash][extname]"; } - } + }, }, }, }, plugins: [ + transformServiceWorkerInDevServer(), themeBuilder({ themeConfig: { themes: ["./src/platform/web/ui/css/themes/element"], @@ -41,17 +45,19 @@ export default defineConfig(({mode}) => { // important this comes before service worker // otherwise the manifest and the icons it refers to won't be cached injectWebManifest("assets/manifest.json"), - injectServiceWorker("./src/platform/web/sw.js", findUnhashedFileNamesFromBundle, { - // placeholders to replace at end of build by chunk name - index: { - DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH, - }, - sw: definePlaceholders, - }), + injectServiceWorker( + "./src/platform/web/sw.js", + findUnhashedFileNamesFromBundle, + { + // placeholders to replace at end of build by chunk name + index: { + DEFINE_GLOBAL_HASH: + definePlaceholders.DEFINE_GLOBAL_HASH, + }, + sw: definePlaceholders, + } + ), ], - define: Object.assign({ - DEFINE_PROJECT_DIR: JSON.stringify(__dirname) - }, definePlaceholders), }); }); diff --git a/vite.sdk-assets-config.js b/vite.sdk-assets-config.js index 5c1f3196..cd97cee7 100644 --- a/vite.sdk-assets-config.js +++ b/vite.sdk-assets-config.js @@ -1,7 +1,8 @@ const path = require("path"); -const mergeOptions = require('merge-options'); +const mergeOptions = require("merge-options").bind({ concatArrays: true }); const themeBuilder = require("./scripts/build-plugins/rollup-plugin-build-themes"); -const {commonOptions, compiledVariables} = require("./vite.common-config.js"); +const { commonOptions, compiledVariables } = require("./vite.common-config.js"); +const { defineConfig } = require("vite"); // These paths will be saved without their hash so they have a consisent path // that we can reference in our `package.json` `exports`. And so people can import @@ -13,33 +14,40 @@ const pathsToExport = [ "theme-element-dark.css", ]; -export default mergeOptions(commonOptions, { - root: "src/", - base: "./", - build: { - outDir: "../target/asset-build/", - rollupOptions: { - output: { - assetFileNames: (chunkInfo) => { - // Get rid of the hash so we can consistently reference these - // files in our `package.json` `exports`. And so people can - // import them with a consistent path. - if(pathsToExport.includes(path.basename(chunkInfo.name))) { - return "assets/[name].[ext]"; - } +export default defineConfig(({ mode }) => { + const options = commonOptions(mode); + return mergeOptions(options, { + root: "src/", + base: "./", + build: { + outDir: "../target/asset-build/", + rollupOptions: { + output: { + assetFileNames: (chunkInfo) => { + // Get rid of the hash so we can consistently reference these + // files in our `package.json` `exports`. And so people can + // import them with a consistent path. + if ( + pathsToExport.includes( + path.basename(chunkInfo.name) + ) + ) { + return "assets/[name].[ext]"; + } - return "assets/[name]-[hash][extname]"; - } - } - } - }, - plugins: [ - themeBuilder({ - themeConfig: { - themes: ["./src/platform/web/ui/css/themes/element"], - default: "element", + return "assets/[name]-[hash][extname]"; + }, + }, }, - compiledVariables, - }), - ], + }, + plugins: [ + themeBuilder({ + themeConfig: { + themes: ["./src/platform/web/ui/css/themes/element"], + default: "element", + }, + compiledVariables, + }), + ], + }); }); diff --git a/vite.sdk-lib-config.js b/vite.sdk-lib-config.js index a5f11a53..2689da09 100644 --- a/vite.sdk-lib-config.js +++ b/vite.sdk-lib-config.js @@ -1,7 +1,12 @@ const path = require("path"); -const mergeOptions = require('merge-options'); -const {commonOptions} = require("./vite.common-config.js"); +const { defineConfig } = require("vite"); +const mergeOptions = require("merge-options").bind({ concatArrays: true }); +const { commonOptions } = require("./vite.common-config.js"); const manifest = require("./package.json"); +const { + injectServiceWorker, + createPlaceholderValues, +} = require("./scripts/build-plugins/service-worker"); const externalDependencies = Object.keys(manifest.dependencies) // just in case for safety in case fake-indexeddb wouldn't be @@ -9,39 +14,38 @@ const externalDependencies = Object.keys(manifest.dependencies) .concat(Object.keys(manifest.devDependencies)) // bundle bs58 because it uses buffer indirectly, which is a pain to bundle, // so we don't annoy our library users with it. - .filter(d => d !== "bs58"); -const moduleDir = path.join(__dirname, "node_modules"); + .filter((d) => d !== "bs58"); -export default mergeOptions(commonOptions, { - root: "src/", - build: { - lib: { - entry: path.resolve(__dirname, 'src/lib.ts'), - formats: ["cjs", "es"], - fileName: format => `hydrogen.${format}.js`, - }, - minify: false, - sourcemap: false, - outDir: "../target/lib-build", - // don't bundle any dependencies, they should be imported/required - rollupOptions: { - external(id) { - return externalDependencies.some(d => id === d || id.startsWith(d + "/")); +export default defineConfig(({ mode }) => { + const options = commonOptions(mode); + const definePlaceholders = createPlaceholderValues(mode); + return mergeOptions(options, { + root: "src/", + plugins: [ + injectServiceWorker("./src/platform/web/sw.js", () => [], { + lib: { + DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH, + }, + sw: definePlaceholders, + }), + ], + build: { + lib: { + entry: path.resolve(__dirname, "src/lib.ts"), + formats: ["cjs", "es"], + fileName: (format) => `hydrogen.${format}.js`, }, - /* don't bundle, so we can override imports per file at build time to replace components */ - // output: { - // manualChunks: (id) => { - // if (id.startsWith(srcDir)) { - // const idPath = id.substring(srcDir.length); - // const pathWithoutExt = idPath.substring(0, idPath.lastIndexOf(".")); - // return pathWithoutExt; - // } else { - // return "index"; - // } - // }, - // minifyInternalExports: false, - // chunkFileNames: "[format]/[name].js" - // } - } - }, + minify: false, + sourcemap: false, + outDir: "../target/lib-build", + // don't bundle any dependencies, they should be imported/required + rollupOptions: { + external(id) { + return externalDependencies.some( + (d) => id === d || id.startsWith(d + "/") + ); + }, + }, + }, + }); }); From 2263200561c595c821e865e46fe3f9b643413b82 Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Fri, 16 Aug 2024 15:46:00 +0530 Subject: [PATCH 3/4] Add global to eslint --- .eslintrc.js | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index eb23d387..a4306000 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,26 +1,27 @@ module.exports = { - "env": { - "browser": true, - "es6": true + env: { + browser: true, + es6: true, }, - "extends": "eslint:recommended", - "parserOptions": { - "ecmaVersion": 2020, - "sourceType": "module" + extends: "eslint:recommended", + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", }, - "rules": { + rules: { "no-console": "off", "no-empty": "off", "no-prototype-builtins": "off", - "no-unused-vars": "warn" + "no-unused-vars": "warn", }, - "globals": { - "DEFINE_VERSION": "readonly", - "DEFINE_GLOBAL_HASH": "readonly", - "DEFINE_PROJECT_DIR": "readonly", + globals: { + DEFINE_VERSION: "readonly", + DEFINE_GLOBAL_HASH: "readonly", + DEFINE_IS_SDK: "readonly", + DEFINE_PROJECT_DIR: "readonly", // only available in sw.js - "DEFINE_UNHASHED_PRECACHED_ASSETS": "readonly", - "DEFINE_HASHED_PRECACHED_ASSETS": "readonly", - "DEFINE_HASHED_CACHED_ON_REQUEST_ASSETS": "readonly" - } + DEFINE_UNHASHED_PRECACHED_ASSETS: "readonly", + DEFINE_HASHED_PRECACHED_ASSETS: "readonly", + DEFINE_HASHED_CACHED_ON_REQUEST_ASSETS: "readonly", + }, }; From aa85c4f87a27b205a64cb7eb22efe575a9bf1cc0 Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Fri, 16 Aug 2024 16:00:44 +0530 Subject: [PATCH 4/4] Fix docker build failing --- scripts/build-plugins/service-worker.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/build-plugins/service-worker.js b/scripts/build-plugins/service-worker.js index 9939440d..1916ddb2 100644 --- a/scripts/build-plugins/service-worker.js +++ b/scripts/build-plugins/service-worker.js @@ -205,10 +205,14 @@ function definePlaceholderValue(mode, name, devValue) { * @see https://stackoverflow.com/a/35778030 */ function getLatestGitCommitHash() { - return require("child_process") - .execSync("git rev-parse --short HEAD") - .toString() - .trim(); + try { + return require("child_process") + .execSync("git rev-parse --short HEAD") + .toString() + .trim(); + } catch { + return "could_not_fetch_sha"; + } } function createPlaceholderValues(mode) {