mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2024-12-22 19:14:52 +01:00
Merge pull request #1174 from element-hq/midhun/support-sw-everywhere
Support service worker in all environments
This commit is contained in:
commit
841ed2527c
35
.eslintrc.js
35
.eslintrc.js
@ -1,26 +1,27 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
"env": {
|
env: {
|
||||||
"browser": true,
|
browser: true,
|
||||||
"es6": true
|
es6: true,
|
||||||
},
|
},
|
||||||
"extends": "eslint:recommended",
|
extends: "eslint:recommended",
|
||||||
"parserOptions": {
|
parserOptions: {
|
||||||
"ecmaVersion": 2020,
|
ecmaVersion: 2020,
|
||||||
"sourceType": "module"
|
sourceType: "module",
|
||||||
},
|
},
|
||||||
"rules": {
|
rules: {
|
||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
"no-empty": "off",
|
"no-empty": "off",
|
||||||
"no-prototype-builtins": "off",
|
"no-prototype-builtins": "off",
|
||||||
"no-unused-vars": "warn"
|
"no-unused-vars": "warn",
|
||||||
},
|
},
|
||||||
"globals": {
|
globals: {
|
||||||
"DEFINE_VERSION": "readonly",
|
DEFINE_VERSION: "readonly",
|
||||||
"DEFINE_GLOBAL_HASH": "readonly",
|
DEFINE_GLOBAL_HASH: "readonly",
|
||||||
"DEFINE_PROJECT_DIR": "readonly",
|
DEFINE_IS_SDK: "readonly",
|
||||||
|
DEFINE_PROJECT_DIR: "readonly",
|
||||||
// only available in sw.js
|
// only available in sw.js
|
||||||
"DEFINE_UNHASHED_PRECACHED_ASSETS": "readonly",
|
DEFINE_UNHASHED_PRECACHED_ASSETS: "readonly",
|
||||||
"DEFINE_HASHED_PRECACHED_ASSETS": "readonly",
|
DEFINE_HASHED_PRECACHED_ASSETS: "readonly",
|
||||||
"DEFINE_HASHED_CACHED_ON_REQUEST_ASSETS": "readonly"
|
DEFINE_HASHED_CACHED_ON_REQUEST_ASSETS: "readonly",
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
const fs = require('fs/promises');
|
const path = require("path");
|
||||||
const path = require('path');
|
const xxhash = require("xxhashjs");
|
||||||
const xxhash = require('xxhashjs');
|
|
||||||
|
|
||||||
function contentHash(str) {
|
function contentHash(str) {
|
||||||
var hasher = new xxhash.h32(0);
|
var hasher = new xxhash.h32(0);
|
||||||
@ -8,16 +7,21 @@ function contentHash(str) {
|
|||||||
return hasher.digest();
|
return hasher.digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
function injectServiceWorker(swFile, findUnhashedFileNamesFromBundle, placeholdersPerChunk) {
|
function injectServiceWorker(
|
||||||
|
swFile,
|
||||||
|
findUnhashedFileNamesFromBundle,
|
||||||
|
placeholdersPerChunk
|
||||||
|
) {
|
||||||
const swName = path.basename(swFile);
|
const swName = path.basename(swFile);
|
||||||
let root;
|
|
||||||
let version;
|
let version;
|
||||||
let logger;
|
let logger;
|
||||||
|
let mode;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "hydrogen:injectServiceWorker",
|
name: "hydrogen:injectServiceWorker",
|
||||||
apply: "build",
|
apply: "build",
|
||||||
enforce: "post",
|
enforce: "post",
|
||||||
|
|
||||||
buildStart() {
|
buildStart() {
|
||||||
this.emitFile({
|
this.emitFile({
|
||||||
type: "chunk",
|
type: "chunk",
|
||||||
@ -25,39 +29,63 @@ function injectServiceWorker(swFile, findUnhashedFileNamesFromBundle, placeholde
|
|||||||
id: swFile,
|
id: swFile,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
configResolved: config => {
|
|
||||||
root = config.root;
|
configResolved: (config) => {
|
||||||
|
mode = config.mode;
|
||||||
version = JSON.parse(config.define.DEFINE_VERSION); // unquote
|
version = JSON.parse(config.define.DEFINE_VERSION); // unquote
|
||||||
logger = config.logger;
|
logger = config.logger;
|
||||||
},
|
},
|
||||||
generateBundle: async function(options, bundle) {
|
|
||||||
|
generateBundle: async function (options, bundle) {
|
||||||
const otherUnhashedFiles = findUnhashedFileNamesFromBundle(bundle);
|
const otherUnhashedFiles = findUnhashedFileNamesFromBundle(bundle);
|
||||||
const unhashedFilenames = [swName].concat(otherUnhashedFiles);
|
const unhashedFilenames = [swName].concat(otherUnhashedFiles);
|
||||||
const unhashedFileContentMap = unhashedFilenames.reduce((map, fileName) => {
|
const unhashedFileContentMap = unhashedFilenames.reduce(
|
||||||
|
(map, fileName) => {
|
||||||
const chunkOrAsset = bundle[fileName];
|
const chunkOrAsset = bundle[fileName];
|
||||||
if (!chunkOrAsset) {
|
if (!chunkOrAsset) {
|
||||||
throw new Error("could not get content for uncached asset or chunk " + fileName);
|
throw new Error(
|
||||||
|
"could not get content for uncached asset or chunk " +
|
||||||
|
fileName
|
||||||
|
);
|
||||||
}
|
}
|
||||||
map[fileName] = chunkOrAsset.source || chunkOrAsset.code;
|
map[fileName] = chunkOrAsset.source || chunkOrAsset.code;
|
||||||
return map;
|
return map;
|
||||||
}, {});
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
const assets = Object.values(bundle);
|
const assets = Object.values(bundle);
|
||||||
const hashedFileNames = assets.map(o => o.fileName).filter(fileName => !unhashedFileContentMap[fileName]);
|
const hashedFileNames = assets
|
||||||
const globalHash = getBuildHash(hashedFileNames, unhashedFileContentMap);
|
.map((o) => o.fileName)
|
||||||
|
.filter((fileName) => !unhashedFileContentMap[fileName]);
|
||||||
|
const globalHash = getBuildHash(
|
||||||
|
hashedFileNames,
|
||||||
|
unhashedFileContentMap
|
||||||
|
);
|
||||||
const placeholderValues = {
|
const placeholderValues = {
|
||||||
DEFINE_GLOBAL_HASH: `"${globalHash}"`,
|
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})`);
|
logger.info(`\nBuilt ${version} (${globalHash})`);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBuildHash(hashedFileNames, unhashedFileContentMap) {
|
function getBuildHash(hashedFileNames, unhashedFileContentMap) {
|
||||||
const unhashedHashes = Object.entries(unhashedFileContentMap).map(([fileName, content]) => {
|
const unhashedHashes = Object.entries(unhashedFileContentMap).map(
|
||||||
|
([fileName, content]) => {
|
||||||
return `${fileName}-${contentHash(Buffer.from(content))}`;
|
return `${fileName}-${contentHash(Buffer.from(content))}`;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
const globalHashAssets = hashedFileNames.concat(unhashedHashes);
|
const globalHashAssets = hashedFileNames.concat(unhashedHashes);
|
||||||
globalHashAssets.sort();
|
globalHashAssets.sort();
|
||||||
return contentHash(globalHashAssets.join(",")).toString();
|
return contentHash(globalHashAssets.join(",")).toString();
|
||||||
@ -67,27 +95,36 @@ const NON_PRECACHED_JS = [
|
|||||||
"hydrogen-legacy",
|
"hydrogen-legacy",
|
||||||
"olm_legacy.js",
|
"olm_legacy.js",
|
||||||
// most environments don't need the worker
|
// most environments don't need the worker
|
||||||
"main.js"
|
"main.js",
|
||||||
];
|
];
|
||||||
|
|
||||||
function isPreCached(asset) {
|
function isPreCached(asset) {
|
||||||
const {name, fileName} = asset;
|
const { name, fileName } = asset;
|
||||||
return name.endsWith(".svg") ||
|
return (
|
||||||
name.endsWith(".png") ||
|
name?.endsWith(".svg") ||
|
||||||
name.endsWith(".css") ||
|
name?.endsWith(".png") ||
|
||||||
name.endsWith(".wasm") ||
|
name?.endsWith(".css") ||
|
||||||
name.endsWith(".html") ||
|
name?.endsWith(".wasm") ||
|
||||||
|
name?.endsWith(".html") ||
|
||||||
// the index and vendor chunks don't have an extension in `name`, so check extension on `fileName`
|
// 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));
|
(fileName.endsWith(".js") &&
|
||||||
|
!NON_PRECACHED_JS.includes(path.basename(name)))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCacheFileNamePlaceholderValues(swName, unhashedFilenames, assets) {
|
function getCacheFileNamePlaceholderValues(
|
||||||
|
swName,
|
||||||
|
unhashedFilenames,
|
||||||
|
assets,
|
||||||
|
mode
|
||||||
|
) {
|
||||||
const unhashedPreCachedAssets = [];
|
const unhashedPreCachedAssets = [];
|
||||||
const hashedPreCachedAssets = [];
|
const hashedPreCachedAssets = [];
|
||||||
const hashedCachedOnRequestAssets = [];
|
const hashedCachedOnRequestAssets = [];
|
||||||
|
|
||||||
|
if (mode === "production") {
|
||||||
for (const asset of assets) {
|
for (const asset of assets) {
|
||||||
const {name, fileName} = asset;
|
const { name, fileName } = asset;
|
||||||
// the service worker should not be cached at all,
|
// the service worker should not be cached at all,
|
||||||
// it's how updates happen
|
// it's how updates happen
|
||||||
if (fileName === swName) {
|
if (fileName === swName) {
|
||||||
@ -100,26 +137,44 @@ function getCacheFileNamePlaceholderValues(swName, unhashedFilenames, assets) {
|
|||||||
hashedCachedOnRequestAssets.push(fileName);
|
hashedCachedOnRequestAssets.push(fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
DEFINE_UNHASHED_PRECACHED_ASSETS: JSON.stringify(unhashedPreCachedAssets),
|
DEFINE_UNHASHED_PRECACHED_ASSETS: JSON.stringify(
|
||||||
|
unhashedPreCachedAssets
|
||||||
|
),
|
||||||
DEFINE_HASHED_PRECACHED_ASSETS: JSON.stringify(hashedPreCachedAssets),
|
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)) {
|
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) {
|
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 replacedValue = placeholderValues[placeholderName];
|
||||||
const oldCode = chunk.code;
|
const oldCode = chunk.code;
|
||||||
chunk.code = chunk.code.replaceAll(placeholderLiteral, replacedValue);
|
chunk.code = chunk.code.replaceAll(
|
||||||
|
placeholderLiteral,
|
||||||
|
replacedValue
|
||||||
|
);
|
||||||
if (chunk.code === oldCode) {
|
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
|
* transformation will touch them (minifying, ...) and we can do a
|
||||||
* string replacement still at the end of the build. */
|
* string replacement still at the end of the build. */
|
||||||
function definePlaceholderValue(mode, name, devValue) {
|
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
|
// 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
|
// 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.
|
// as something that will not be transformed.
|
||||||
@ -145,13 +200,44 @@ function definePlaceholderValue(mode, name, devValue) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the short sha for the latest git commit
|
||||||
|
* @see https://stackoverflow.com/a/35778030
|
||||||
|
*/
|
||||||
|
function getLatestGitCommitHash() {
|
||||||
|
try {
|
||||||
|
return require("child_process")
|
||||||
|
.execSync("git rev-parse --short HEAD")
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
|
} catch {
|
||||||
|
return "could_not_fetch_sha";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createPlaceholderValues(mode) {
|
function createPlaceholderValues(mode) {
|
||||||
return {
|
return {
|
||||||
DEFINE_GLOBAL_HASH: definePlaceholderValue(mode, "DEFINE_GLOBAL_HASH", null),
|
DEFINE_GLOBAL_HASH: definePlaceholderValue(
|
||||||
DEFINE_UNHASHED_PRECACHED_ASSETS: definePlaceholderValue(mode, "UNHASHED_PRECACHED_ASSETS", []),
|
mode,
|
||||||
DEFINE_HASHED_PRECACHED_ASSETS: definePlaceholderValue(mode, "HASHED_PRECACHED_ASSETS", []),
|
"DEFINE_GLOBAL_HASH",
|
||||||
DEFINE_HASHED_CACHED_ON_REQUEST_ASSETS: definePlaceholderValue(mode, "HASHED_CACHED_ON_REQUEST_ASSETS", []),
|
`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 };
|
||||||
|
67
scripts/build-plugins/sw-dev.js
Normal file
67
scripts/build-plugins/sw-dev.js
Normal file
@ -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;
|
||||||
|
}
|
@ -8,8 +8,8 @@ shopt -s extglob
|
|||||||
# Only remove the directory contents instead of the whole directory to maintain
|
# Only remove the directory contents instead of the whole directory to maintain
|
||||||
# the `npm link`/`yarn link` symlink
|
# the `npm link`/`yarn link` symlink
|
||||||
rm -rf target/*
|
rm -rf target/*
|
||||||
yarn run vite build -c vite.sdk-assets-config.js
|
yarn run vite build -c vite.sdk-assets-config.js --mode sdk
|
||||||
yarn run vite build -c vite.sdk-lib-config.js
|
yarn run vite build -c vite.sdk-lib-config.js --mode sdk
|
||||||
yarn tsc -p tsconfig-declaration.json
|
yarn tsc -p tsconfig-declaration.json
|
||||||
./scripts/sdk/create-manifest.js ./target/package.json
|
./scripts/sdk/create-manifest.js ./target/package.json
|
||||||
mkdir target/paths
|
mkdir target/paths
|
||||||
|
@ -103,8 +103,22 @@ export class ServiceWorkerHandler {
|
|||||||
if (document.hidden) {
|
if (document.hidden) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const version = await this._sendAndWaitForReply("version", null, this._registration.waiting);
|
const version = await this._sendAndWaitForReply(
|
||||||
if (confirm(`Version ${version.version} (${version.buildHash}) is available. Reload to apply?`)) {
|
"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
|
// prevent any fetch requests from going to the service worker
|
||||||
// from any client, so that it is not kept active
|
// from any client, so that it is not kept active
|
||||||
// when calling skipWaiting on the new one
|
// when calling skipWaiting on the new one
|
||||||
|
@ -19,9 +19,7 @@
|
|||||||
import {Platform} from "./Platform";
|
import {Platform} from "./Platform";
|
||||||
import configURL from "./assets/config.json?url";
|
import configURL from "./assets/config.json?url";
|
||||||
import assetPaths from "./sdk/paths/vite";
|
import assetPaths from "./sdk/paths/vite";
|
||||||
if (import.meta.env.PROD) {
|
|
||||||
assetPaths.serviceWorker = "sw.js";
|
assetPaths.serviceWorker = "sw.js";
|
||||||
}
|
|
||||||
const platform = new Platform({
|
const platform = new Platform({
|
||||||
container: document.body,
|
container: document.body,
|
||||||
assetPaths,
|
assetPaths,
|
||||||
|
BIN
src/platform/web/public/icon.png
Normal file
BIN
src/platform/web/public/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@ -15,8 +15,9 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import NOTIFICATION_BADGE_ICON from "./assets/icon.png?url";
|
const NOTIFICATION_BADGE_ICON = "icon.png";
|
||||||
// replaced by the service worker build plugin
|
|
||||||
|
// These are replaced by rollup plugins
|
||||||
const UNHASHED_PRECACHED_ASSETS = DEFINE_UNHASHED_PRECACHED_ASSETS;
|
const UNHASHED_PRECACHED_ASSETS = DEFINE_UNHASHED_PRECACHED_ASSETS;
|
||||||
const HASHED_PRECACHED_ASSETS = DEFINE_HASHED_PRECACHED_ASSETS;
|
const HASHED_PRECACHED_ASSETS = DEFINE_HASHED_PRECACHED_ASSETS;
|
||||||
const HASHED_CACHED_ON_REQUEST_ASSETS = DEFINE_HASHED_CACHED_ON_REQUEST_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 hashedCacheName = `hydrogen-assets`;
|
||||||
const mediaThumbnailCacheName = `hydrogen-media-thumbnails-v2`;
|
const mediaThumbnailCacheName = `hydrogen-media-thumbnails-v2`;
|
||||||
|
|
||||||
self.addEventListener('install', function(e) {
|
self.addEventListener("install", function (e) {
|
||||||
e.waitUntil((async () => {
|
e.waitUntil(
|
||||||
|
(async () => {
|
||||||
const unhashedCache = await caches.open(unhashedCacheName);
|
const unhashedCache = await caches.open(unhashedCacheName);
|
||||||
await unhashedCache.addAll(UNHASHED_PRECACHED_ASSETS);
|
await unhashedCache.addAll(UNHASHED_PRECACHED_ASSETS);
|
||||||
const hashedCache = await caches.open(hashedCacheName);
|
const hashedCache = await caches.open(hashedCacheName);
|
||||||
await Promise.all(HASHED_PRECACHED_ASSETS.map(async asset => {
|
await Promise.all(
|
||||||
if (!await hashedCache.match(asset)) {
|
HASHED_PRECACHED_ASSETS.map(async (asset) => {
|
||||||
|
if (!(await hashedCache.match(asset))) {
|
||||||
await hashedCache.add(asset);
|
await hashedCache.add(asset);
|
||||||
}
|
}
|
||||||
}));
|
})
|
||||||
})());
|
);
|
||||||
|
})()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('activate', (event) => {
|
self.addEventListener("activate", (event) => {
|
||||||
// on a first page load/sw install,
|
// on a first page load/sw install,
|
||||||
// start using the service worker on all pages straight away
|
// start using the service worker on all pages straight away
|
||||||
self.clients.claim();
|
self.clients.claim();
|
||||||
@ -49,26 +54,29 @@ async function purgeOldCaches() {
|
|||||||
// remove any caches we don't know about
|
// remove any caches we don't know about
|
||||||
const keyList = await caches.keys();
|
const keyList = await caches.keys();
|
||||||
for (const key of keyList) {
|
for (const key of keyList) {
|
||||||
if (key !== unhashedCacheName && key !== hashedCacheName && key !== mediaThumbnailCacheName) {
|
if (
|
||||||
|
key !== unhashedCacheName &&
|
||||||
|
key !== hashedCacheName &&
|
||||||
|
key !== mediaThumbnailCacheName
|
||||||
|
) {
|
||||||
await caches.delete(key);
|
await caches.delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// remove the cache for any old hashed resource
|
// remove the cache for any old hashed resource
|
||||||
const hashedCache = await caches.open(hashedCacheName);
|
const hashedCache = await caches.open(hashedCacheName);
|
||||||
const keys = await hashedCache.keys();
|
const keys = await hashedCache.keys();
|
||||||
const hashedAssetURLs =
|
const hashedAssetURLs = HASHED_PRECACHED_ASSETS.concat(
|
||||||
HASHED_PRECACHED_ASSETS
|
HASHED_CACHED_ON_REQUEST_ASSETS
|
||||||
.concat(HASHED_CACHED_ON_REQUEST_ASSETS)
|
).map((a) => new URL(a, self.registration.scope).href);
|
||||||
.map(a => new URL(a, self.registration.scope).href);
|
|
||||||
|
|
||||||
for (const request of keys) {
|
for (const request of keys) {
|
||||||
if (!hashedAssetURLs.some(url => url === request.url)) {
|
if (!hashedAssetURLs.some((url) => url === request.url)) {
|
||||||
hashedCache.delete(request);
|
hashedCache.delete(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addEventListener('fetch', (event) => {
|
self.addEventListener("fetch", (event) => {
|
||||||
/*
|
/*
|
||||||
service worker shouldn't handle xhr uploads because otherwise
|
service worker shouldn't handle xhr uploads because otherwise
|
||||||
the progress events won't fire.
|
the progress events won't fire.
|
||||||
@ -95,12 +103,18 @@ let pendingFetchAbortController = new AbortController();
|
|||||||
|
|
||||||
async function handleRequest(request) {
|
async function handleRequest(request) {
|
||||||
try {
|
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);
|
return handleStaleWhileRevalidateRequest(request);
|
||||||
}
|
}
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
// rewrite / to /index.html so it hits the cache
|
// 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));
|
request = new Request(new URL("index.html", baseURL.href));
|
||||||
}
|
}
|
||||||
let response = await readCache(request);
|
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
|
// 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
|
// https://developers.google.com/web/tools/chrome-devtools/progressive-web-apps?utm_source=devtools#opaque-responses
|
||||||
if (isCacheableThumbnail(url)) {
|
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 {
|
} else {
|
||||||
response = await fetch(request, {signal: pendingFetchAbortController.signal});
|
response = await fetch(request, {
|
||||||
|
signal: pendingFetchAbortController.signal,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
await updateCache(request, response);
|
await updateCache(request, response);
|
||||||
}
|
}
|
||||||
@ -198,9 +218,10 @@ async function readCache(request) {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addEventListener('message', (event) => {
|
self.addEventListener("message", (event) => {
|
||||||
const reply = payload => event.source.postMessage({replyTo: event.data.id, payload});
|
const reply = (payload) =>
|
||||||
const {replyTo} = event.data;
|
event.source.postMessage({ replyTo: event.data.id, payload });
|
||||||
|
const { replyTo } = event.data;
|
||||||
if (replyTo) {
|
if (replyTo) {
|
||||||
const resolve = pendingReplies.get(replyTo);
|
const resolve = pendingReplies.get(replyTo);
|
||||||
if (resolve) {
|
if (resolve) {
|
||||||
@ -210,7 +231,10 @@ self.addEventListener('message', (event) => {
|
|||||||
} else {
|
} else {
|
||||||
switch (event.data?.type) {
|
switch (event.data?.type) {
|
||||||
case "version":
|
case "version":
|
||||||
reply({version: DEFINE_VERSION, buildHash: DEFINE_GLOBAL_HASH});
|
reply({
|
||||||
|
version: DEFINE_VERSION,
|
||||||
|
buildHash: DEFINE_GLOBAL_HASH,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case "skipWaiting":
|
case "skipWaiting":
|
||||||
self.skipWaiting();
|
self.skipWaiting();
|
||||||
@ -220,8 +244,10 @@ self.addEventListener('message', (event) => {
|
|||||||
break;
|
break;
|
||||||
case "closeSession":
|
case "closeSession":
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
closeSession(event.data.payload.sessionId, event.source.id)
|
closeSession(
|
||||||
.finally(() => reply())
|
event.data.payload.sessionId,
|
||||||
|
event.source.id
|
||||||
|
).finally(() => reply())
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -235,29 +261,40 @@ async function openClientFromNotif(event) {
|
|||||||
console.log("clicked notif with tag", event.notification.tag);
|
console.log("clicked notif with tag", event.notification.tag);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const {sessionId, roomId} = event.notification.data;
|
const { sessionId, roomId } = event.notification.data;
|
||||||
const sessionHash = `#/session/${sessionId}`;
|
const sessionHash = `#/session/${sessionId}`;
|
||||||
const roomHash = `${sessionHash}/room/${roomId}`;
|
const roomHash = `${sessionHash}/room/${roomId}`;
|
||||||
const clientWithSession = await findClient(async client => {
|
const clientWithSession = await findClient(async (client) => {
|
||||||
return await sendAndWaitForReply(client, "hasSessionOpen", {sessionId});
|
return await sendAndWaitForReply(client, "hasSessionOpen", {
|
||||||
|
sessionId,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
if (clientWithSession) {
|
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
|
// use a message rather than clientWithSession.navigate here as this refreshes the page on chrome
|
||||||
clientWithSession.postMessage({type: "openRoom", payload: {roomId}});
|
clientWithSession.postMessage({
|
||||||
if ('focus' in clientWithSession) {
|
type: "openRoom",
|
||||||
|
payload: { roomId },
|
||||||
|
});
|
||||||
|
if ("focus" in clientWithSession) {
|
||||||
try {
|
try {
|
||||||
await clientWithSession.focus();
|
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) {
|
} 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;
|
const roomURL = new URL(`./${roomHash}`, baseURL).href;
|
||||||
await self.clients.openWindow(roomURL);
|
await self.clients.openWindow(roomURL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addEventListener('notificationclick', event => {
|
self.addEventListener("notificationclick", (event) => {
|
||||||
event.notification.close();
|
event.notification.close();
|
||||||
event.waitUntil(openClientFromNotif(event));
|
event.waitUntil(openClientFromNotif(event));
|
||||||
});
|
});
|
||||||
@ -268,19 +305,30 @@ async function handlePushNotification(n) {
|
|||||||
let sender = n.sender_display_name || n.sender;
|
let sender = n.sender_display_name || n.sender;
|
||||||
if (sender && n.event_id) {
|
if (sender && n.event_id) {
|
||||||
const roomId = n.room_id;
|
const roomId = n.room_id;
|
||||||
const hasFocusedClientOnRoom = !!await findClient(async client => {
|
const hasFocusedClientOnRoom = !!(await findClient(async (client) => {
|
||||||
if (client.visibilityState === "visible" && client.focused) {
|
if (client.visibilityState === "visible" && client.focused) {
|
||||||
return await sendAndWaitForReply(client, "hasRoomOpen", {sessionId, roomId});
|
return await sendAndWaitForReply(client, "hasRoomOpen", {
|
||||||
}
|
sessionId,
|
||||||
|
roomId,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
if (hasFocusedClientOnRoom) {
|
if (hasFocusedClientOnRoom) {
|
||||||
console.log("client is focused, room is open, don't show notif");
|
console.log("client is focused, room is open, don't show notif");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newMessageNotifs = Array.from(await self.registration.getNotifications({tag: NOTIF_TAG_NEW_MESSAGE}));
|
const newMessageNotifs = Array.from(
|
||||||
const notifsForRoom = newMessageNotifs.filter(n => n.data.roomId === roomId);
|
await self.registration.getNotifications({
|
||||||
const hasMultiNotification = notifsForRoom.some(n => n.data.multi);
|
tag: NOTIF_TAG_NEW_MESSAGE,
|
||||||
const hasSingleNotifsForRoom = newMessageNotifs.some(n => !n.data.multi);
|
})
|
||||||
|
);
|
||||||
|
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;
|
const roomName = n.room_name || n.room_alias;
|
||||||
let multi = false;
|
let multi = false;
|
||||||
let label;
|
let label;
|
||||||
@ -304,9 +352,9 @@ async function handlePushNotification(n) {
|
|||||||
}
|
}
|
||||||
await self.registration.showNotification(label, {
|
await self.registration.showNotification(label, {
|
||||||
body,
|
body,
|
||||||
data: {sessionId, roomId, multi},
|
data: { sessionId, roomId, multi },
|
||||||
tag: NOTIF_TAG_NEW_MESSAGE,
|
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
|
// 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
|
// when no client is visible, see https://goo.gl/yqv4Q4
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addEventListener('push', event => {
|
self.addEventListener("push", (event) => {
|
||||||
event.waitUntil(handlePushNotification(event.data.json()));
|
event.waitUntil(handlePushNotification(event.data.json()));
|
||||||
});
|
});
|
||||||
|
|
||||||
async function closeSession(sessionId, requestingClientId) {
|
async function closeSession(sessionId, requestingClientId) {
|
||||||
const clients = await self.clients.matchAll();
|
const clients = await self.clients.matchAll();
|
||||||
await Promise.all(clients.map(async client => {
|
await Promise.all(
|
||||||
|
clients.map(async (client) => {
|
||||||
if (client.id !== requestingClientId) {
|
if (client.id !== requestingClientId) {
|
||||||
await sendAndWaitForReply(client, "closeSession", {sessionId});
|
await sendAndWaitForReply(client, "closeSession", {
|
||||||
|
sessionId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function haltRequests() {
|
async function haltRequests() {
|
||||||
// first ask all clients to block sending any more requests
|
// first ask all clients to block sending any more requests
|
||||||
const clients = await self.clients.matchAll({type: "window"});
|
const clients = await self.clients.matchAll({ type: "window" });
|
||||||
await Promise.all(clients.map(client => {
|
await Promise.all(
|
||||||
|
clients.map((client) => {
|
||||||
return sendAndWaitForReply(client, "haltRequests");
|
return sendAndWaitForReply(client, "haltRequests");
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
// and only then abort the current requests
|
// and only then abort the current requests
|
||||||
pendingFetchAbortController.abort();
|
pendingFetchAbortController.abort();
|
||||||
}
|
}
|
||||||
@ -343,15 +397,15 @@ let messageIdCounter = 0;
|
|||||||
function sendAndWaitForReply(client, type, payload) {
|
function sendAndWaitForReply(client, type, payload) {
|
||||||
messageIdCounter += 1;
|
messageIdCounter += 1;
|
||||||
const id = messageIdCounter;
|
const id = messageIdCounter;
|
||||||
const promise = new Promise(resolve => {
|
const promise = new Promise((resolve) => {
|
||||||
pendingReplies.set(id, resolve);
|
pendingReplies.set(id, resolve);
|
||||||
});
|
});
|
||||||
client.postMessage({type, id, payload});
|
client.postMessage({ type, id, payload });
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findClient(predicate) {
|
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) {
|
for (const client of clientList) {
|
||||||
if (await predicate(client)) {
|
if (await predicate(client)) {
|
||||||
return client;
|
return client;
|
||||||
|
@ -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 flexbugsFixes = require("postcss-flexbugs-fixes");
|
||||||
const compileVariables = require("./scripts/postcss/css-compile-variables");
|
const compileVariables = require("./scripts/postcss/css-compile-variables");
|
||||||
const urlVariables = require("./scripts/postcss/css-url-to-variables");
|
const urlVariables = require("./scripts/postcss/css-url-to-variables");
|
||||||
const urlProcessor = require("./scripts/postcss/css-url-processor");
|
const urlProcessor = require("./scripts/postcss/css-url-processor");
|
||||||
const fs = require("fs");
|
const appManifest = require("./package.json");
|
||||||
const path = require("path");
|
const sdkManifest = require("./scripts/sdk/base-manifest.json");
|
||||||
const manifest = require("./package.json");
|
|
||||||
const version = manifest.version;
|
|
||||||
const compiledVariables = new Map();
|
const compiledVariables = new Map();
|
||||||
import {buildColorizedSVG as replacer} from "./scripts/postcss/svg-builder.mjs";
|
import { buildColorizedSVG as replacer } from "./scripts/postcss/svg-builder.mjs";
|
||||||
import {derive} from "./src/platform/web/theming/shared/color.mjs";
|
import { derive } from "./src/platform/web/theming/shared/color.mjs";
|
||||||
|
|
||||||
const commonOptions = {
|
const commonOptions = (mode) => {
|
||||||
|
const definePlaceholders = createPlaceholderValues(mode);
|
||||||
|
return {
|
||||||
logLevel: "warn",
|
logLevel: "warn",
|
||||||
publicDir: false,
|
publicDir: false,
|
||||||
server: {
|
server: {
|
||||||
hmr: false
|
hmr: false,
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
// these should only be imported by the base-x package in any runtime code
|
// these should only be imported by the base-x package in any runtime code
|
||||||
// and works in the browser with a Uint8Array shim,
|
// and works in the browser with a Uint8Array shim,
|
||||||
// rather than including a ton of polyfill code
|
// rather than including a ton of polyfill code
|
||||||
"safe-buffer": "./scripts/package-overrides/safe-buffer/index.js",
|
"safe-buffer":
|
||||||
"buffer": "./scripts/package-overrides/buffer/index.js",
|
"./scripts/package-overrides/safe-buffer/index.js",
|
||||||
}
|
buffer: "./scripts/package-overrides/buffer/index.js",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
assetsInlineLimit: 0,
|
assetsInlineLimit: 0,
|
||||||
polyfillModulePreload: false,
|
polyfillModulePreload: false,
|
||||||
},
|
},
|
||||||
assetsInclude: ['**/config.json'],
|
assetsInclude: ["**/config.json"],
|
||||||
define: {
|
define: Object.assign(
|
||||||
DEFINE_VERSION: JSON.stringify(version),
|
{
|
||||||
|
DEFINE_VERSION: `"${getVersion(mode)}"`,
|
||||||
DEFINE_GLOBAL_HASH: JSON.stringify(null),
|
DEFINE_GLOBAL_HASH: JSON.stringify(null),
|
||||||
|
DEFINE_IS_SDK: mode === "sdk" ? "true" : "false",
|
||||||
|
DEFINE_PROJECT_DIR: JSON.stringify(__dirname),
|
||||||
},
|
},
|
||||||
|
definePlaceholders
|
||||||
|
),
|
||||||
css: {
|
css: {
|
||||||
postcss: {
|
postcss: {
|
||||||
plugins: [
|
plugins: [
|
||||||
compileVariables({derive, compiledVariables}),
|
compileVariables({ derive, compiledVariables }),
|
||||||
urlVariables({compiledVariables}),
|
urlVariables({ compiledVariables }),
|
||||||
urlProcessor({replacer}),
|
urlProcessor({ replacer }),
|
||||||
// cssvariables({
|
flexbugsFixes(),
|
||||||
// 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()
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 };
|
module.exports = { commonOptions, compiledVariables };
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
const injectWebManifest = require("./scripts/build-plugins/manifest");
|
const injectWebManifest = require("./scripts/build-plugins/manifest");
|
||||||
const {injectServiceWorker, createPlaceholderValues} = require("./scripts/build-plugins/service-worker");
|
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 themeBuilder = require("./scripts/build-plugins/rollup-plugin-build-themes");
|
||||||
const {defineConfig} = require('vite');
|
const { defineConfig } = require("vite");
|
||||||
const mergeOptions = require('merge-options').bind({concatArrays: true});
|
const mergeOptions = require("merge-options").bind({ concatArrays: true });
|
||||||
const {commonOptions, compiledVariables} = require("./vite.common-config.js");
|
const { commonOptions, compiledVariables } = require("./vite.common-config.js");
|
||||||
|
|
||||||
export default defineConfig(({mode}) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const definePlaceholders = createPlaceholderValues(mode);
|
const definePlaceholders = createPlaceholderValues(mode);
|
||||||
return mergeOptions(commonOptions, {
|
const options = commonOptions(mode);
|
||||||
|
return mergeOptions(options, {
|
||||||
root: "src/platform/web",
|
root: "src/platform/web",
|
||||||
base: "./",
|
base: "./",
|
||||||
|
publicDir: "./public",
|
||||||
build: {
|
build: {
|
||||||
outDir: "../../../target",
|
outDir: "../../../target",
|
||||||
minify: true,
|
minify: true,
|
||||||
@ -19,18 +24,17 @@ export default defineConfig(({mode}) => {
|
|||||||
assetFileNames: (asset) => {
|
assetFileNames: (asset) => {
|
||||||
if (asset.name.includes("config.json")) {
|
if (asset.name.includes("config.json")) {
|
||||||
return "[name][extname]";
|
return "[name][extname]";
|
||||||
}
|
} else if (asset.name.match(/theme-.+\.json/)) {
|
||||||
else if (asset.name.match(/theme-.+\.json/)) {
|
|
||||||
return "assets/[name][extname]";
|
return "assets/[name][extname]";
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return "assets/[name].[hash][extname]";
|
return "assets/[name].[hash][extname]";
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
transformServiceWorkerInDevServer(),
|
||||||
themeBuilder({
|
themeBuilder({
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
themes: ["./src/platform/web/ui/css/themes/element"],
|
themes: ["./src/platform/web/ui/css/themes/element"],
|
||||||
@ -41,17 +45,19 @@ export default defineConfig(({mode}) => {
|
|||||||
// important this comes before service worker
|
// important this comes before service worker
|
||||||
// otherwise the manifest and the icons it refers to won't be cached
|
// otherwise the manifest and the icons it refers to won't be cached
|
||||||
injectWebManifest("assets/manifest.json"),
|
injectWebManifest("assets/manifest.json"),
|
||||||
injectServiceWorker("./src/platform/web/sw.js", findUnhashedFileNamesFromBundle, {
|
injectServiceWorker(
|
||||||
|
"./src/platform/web/sw.js",
|
||||||
|
findUnhashedFileNamesFromBundle,
|
||||||
|
{
|
||||||
// placeholders to replace at end of build by chunk name
|
// placeholders to replace at end of build by chunk name
|
||||||
index: {
|
index: {
|
||||||
DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH,
|
DEFINE_GLOBAL_HASH:
|
||||||
|
definePlaceholders.DEFINE_GLOBAL_HASH,
|
||||||
},
|
},
|
||||||
sw: definePlaceholders,
|
sw: definePlaceholders,
|
||||||
}),
|
}
|
||||||
|
),
|
||||||
],
|
],
|
||||||
define: Object.assign({
|
|
||||||
DEFINE_PROJECT_DIR: JSON.stringify(__dirname)
|
|
||||||
}, definePlaceholders),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
const path = require("path");
|
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 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
|
// 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
|
// that we can reference in our `package.json` `exports`. And so people can import
|
||||||
@ -13,7 +14,9 @@ const pathsToExport = [
|
|||||||
"theme-element-dark.css",
|
"theme-element-dark.css",
|
||||||
];
|
];
|
||||||
|
|
||||||
export default mergeOptions(commonOptions, {
|
export default defineConfig(({ mode }) => {
|
||||||
|
const options = commonOptions(mode);
|
||||||
|
return mergeOptions(options, {
|
||||||
root: "src/",
|
root: "src/",
|
||||||
base: "./",
|
base: "./",
|
||||||
build: {
|
build: {
|
||||||
@ -24,14 +27,18 @@ export default mergeOptions(commonOptions, {
|
|||||||
// Get rid of the hash so we can consistently reference these
|
// Get rid of the hash so we can consistently reference these
|
||||||
// files in our `package.json` `exports`. And so people can
|
// files in our `package.json` `exports`. And so people can
|
||||||
// import them with a consistent path.
|
// import them with a consistent path.
|
||||||
if(pathsToExport.includes(path.basename(chunkInfo.name))) {
|
if (
|
||||||
|
pathsToExport.includes(
|
||||||
|
path.basename(chunkInfo.name)
|
||||||
|
)
|
||||||
|
) {
|
||||||
return "assets/[name].[ext]";
|
return "assets/[name].[ext]";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "assets/[name]-[hash][extname]";
|
return "assets/[name]-[hash][extname]";
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
themeBuilder({
|
themeBuilder({
|
||||||
@ -42,4 +49,5 @@ export default mergeOptions(commonOptions, {
|
|||||||
compiledVariables,
|
compiledVariables,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const mergeOptions = require('merge-options');
|
const { defineConfig } = require("vite");
|
||||||
const {commonOptions} = require("./vite.common-config.js");
|
const mergeOptions = require("merge-options").bind({ concatArrays: true });
|
||||||
|
const { commonOptions } = require("./vite.common-config.js");
|
||||||
const manifest = require("./package.json");
|
const manifest = require("./package.json");
|
||||||
|
const {
|
||||||
|
injectServiceWorker,
|
||||||
|
createPlaceholderValues,
|
||||||
|
} = require("./scripts/build-plugins/service-worker");
|
||||||
|
|
||||||
const externalDependencies = Object.keys(manifest.dependencies)
|
const externalDependencies = Object.keys(manifest.dependencies)
|
||||||
// just in case for safety in case fake-indexeddb wouldn't be
|
// just in case for safety in case fake-indexeddb wouldn't be
|
||||||
@ -9,16 +14,26 @@ const externalDependencies = Object.keys(manifest.dependencies)
|
|||||||
.concat(Object.keys(manifest.devDependencies))
|
.concat(Object.keys(manifest.devDependencies))
|
||||||
// bundle bs58 because it uses buffer indirectly, which is a pain to bundle,
|
// bundle bs58 because it uses buffer indirectly, which is a pain to bundle,
|
||||||
// so we don't annoy our library users with it.
|
// so we don't annoy our library users with it.
|
||||||
.filter(d => d !== "bs58");
|
.filter((d) => d !== "bs58");
|
||||||
const moduleDir = path.join(__dirname, "node_modules");
|
|
||||||
|
|
||||||
export default mergeOptions(commonOptions, {
|
export default defineConfig(({ mode }) => {
|
||||||
|
const options = commonOptions(mode);
|
||||||
|
const definePlaceholders = createPlaceholderValues(mode);
|
||||||
|
return mergeOptions(options, {
|
||||||
root: "src/",
|
root: "src/",
|
||||||
|
plugins: [
|
||||||
|
injectServiceWorker("./src/platform/web/sw.js", () => [], {
|
||||||
|
lib: {
|
||||||
|
DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH,
|
||||||
|
},
|
||||||
|
sw: definePlaceholders,
|
||||||
|
}),
|
||||||
|
],
|
||||||
build: {
|
build: {
|
||||||
lib: {
|
lib: {
|
||||||
entry: path.resolve(__dirname, 'src/lib.ts'),
|
entry: path.resolve(__dirname, "src/lib.ts"),
|
||||||
formats: ["cjs", "es"],
|
formats: ["cjs", "es"],
|
||||||
fileName: format => `hydrogen.${format}.js`,
|
fileName: (format) => `hydrogen.${format}.js`,
|
||||||
},
|
},
|
||||||
minify: false,
|
minify: false,
|
||||||
sourcemap: false,
|
sourcemap: false,
|
||||||
@ -26,22 +41,11 @@ export default mergeOptions(commonOptions, {
|
|||||||
// don't bundle any dependencies, they should be imported/required
|
// don't bundle any dependencies, they should be imported/required
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external(id) {
|
external(id) {
|
||||||
return externalDependencies.some(d => id === d || id.startsWith(d + "/"));
|
return externalDependencies.some(
|
||||||
|
(d) => id === d || id.startsWith(d + "/")
|
||||||
|
);
|
||||||
},
|
},
|
||||||
/* 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"
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user