mirror of
https://github.com/element-hq/matrix-authentication-service.git
synced 2026-10-24 06:25:39 +00:00
Replace vite-plugin-sri with our own plugin
This avoids rewriting the manifest file multiple times, and it's also slightly more efficient, as it doesn't re-read the assets from disk
This commit is contained in:
3 files changed
+96
-70
No files matched your search
Generated
-8
@@ -68,7 +68,6 @@
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "7.3.0",
|
||||
"vite-plugin-graphql-codegen": "^3.7.0",
|
||||
"vite-plugin-manifest-sri": "^0.2.0",
|
||||
"vitest": "^4.0.15"
|
||||
}
|
||||
},
|
||||
@@ -13310,13 +13309,6 @@
|
||||
"vite": ">=2.7.0 <8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-manifest-sri": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-manifest-sri/-/vite-plugin-manifest-sri-0.2.0.tgz",
|
||||
"integrity": "sha512-Zt5jt19xTIJ91LOuQTCtNG7rTFc5OziAjBz2H5NdCGqaOD1nxrWExLhcKW+W4/q8/jOPCg/n5ncYEQmqCxiGQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.15.tgz",
|
||||
|
||||
@@ -78,7 +78,6 @@
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "7.3.0",
|
||||
"vite-plugin-graphql-codegen": "^3.7.0",
|
||||
"vite-plugin-manifest-sri": "^0.2.0",
|
||||
"vitest": "^4.0.15"
|
||||
},
|
||||
"msw": {
|
||||
|
||||
+96
-61
@@ -12,9 +12,8 @@ import { tanstackRouter } from "@tanstack/router-plugin/vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import browserslistToEsbuild from "browserslist-to-esbuild";
|
||||
import { globSync } from "tinyglobby";
|
||||
import type { Manifest, PluginOption } from "vite";
|
||||
import type { Environment, Manifest, PluginOption } from "vite";
|
||||
import codegen from "vite-plugin-graphql-codegen";
|
||||
import manifestSRI from "vite-plugin-manifest-sri";
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
function i18nHotReload(): PluginOption {
|
||||
@@ -86,6 +85,100 @@ function compression(): PluginOption {
|
||||
};
|
||||
}
|
||||
|
||||
declare module "vite" {
|
||||
interface ManifestChunk {
|
||||
integrity: string;
|
||||
}
|
||||
}
|
||||
|
||||
// Custom plugin to make sure that each asset has an entry in the manifest
|
||||
// This is needed so that the preloading & asset integrity generation works
|
||||
// It also calculates integrity hashes for the assets
|
||||
function augmentManifest(): PluginOption {
|
||||
// Store a per-environment state, in case the build is run multiple times, like in watch mode
|
||||
const state = new Map<Environment, Record<string, Promise<string>>>();
|
||||
return {
|
||||
name: "augment-manifest",
|
||||
apply: "build",
|
||||
enforce: "post",
|
||||
|
||||
perEnvironmentStartEndDuringDev: true,
|
||||
buildStart() {
|
||||
state.set(this.environment, {});
|
||||
},
|
||||
|
||||
generateBundle(_outputOptions, bundle) {
|
||||
const envState = state.get(this.environment);
|
||||
if (!envState) throw new Error("No state for environment");
|
||||
|
||||
for (const [fileName, assetOrChunk] of Object.entries(bundle)) {
|
||||
// Start calculating hash of the asset. We can let that run in the
|
||||
// background
|
||||
const source =
|
||||
assetOrChunk.type === "asset"
|
||||
? assetOrChunk.source
|
||||
: assetOrChunk.code;
|
||||
|
||||
envState[fileName] = (async (): Promise<string> => {
|
||||
const digest = await crypto.subtle.digest(
|
||||
"SHA-384",
|
||||
Buffer.from(source),
|
||||
);
|
||||
return `sha384-${Buffer.from(digest).toString("base64")}`;
|
||||
})();
|
||||
}
|
||||
},
|
||||
|
||||
async writeBundle({ dir }): Promise<void> {
|
||||
const envState = state.get(this.environment);
|
||||
if (!envState) throw new Error("No state for environment");
|
||||
state.delete(this.environment);
|
||||
|
||||
const manifestPath = resolve(dir, "manifest.json");
|
||||
|
||||
let manifestHandle: FileHandle;
|
||||
try {
|
||||
manifestHandle = await open(manifestPath, "r+");
|
||||
} catch (error) {
|
||||
// Manifest does not exist, nothing to do but still warn about
|
||||
this.warn(`Failed to open manifest at ${manifestPath}: ${error}`);
|
||||
return;
|
||||
}
|
||||
const rawManifest = await manifestHandle.readFile("utf-8");
|
||||
const manifest = JSON.parse(rawManifest) as Manifest;
|
||||
|
||||
const existing: Set<string> = new Set();
|
||||
const needs: Set<string> = new Set();
|
||||
|
||||
for (const chunk of Object.values(manifest)) {
|
||||
existing.add(chunk.file);
|
||||
chunk.integrity = await envState[chunk.file];
|
||||
for (const css of chunk.css ?? []) needs.add(css);
|
||||
for (const sub of chunk.assets ?? []) needs.add(sub);
|
||||
}
|
||||
|
||||
const missing = Array.from(needs).filter((a) => !existing.has(a));
|
||||
|
||||
for (const asset of missing) {
|
||||
manifest[asset] = {
|
||||
file: asset,
|
||||
integrity: await envState[asset],
|
||||
};
|
||||
}
|
||||
|
||||
// Overwrite the manifest with the augmented entries
|
||||
// XXX: you'd think that doing `manifestHandle.writeFile` would work, as
|
||||
// the docs says that it 'overwrites the file if it exists'. Turns out, it
|
||||
// reuses the previous position from `readFile`, so that would append on
|
||||
// the existing, so we have to use `write` with an explicit position.
|
||||
// Truncating the file just in case the output is smaller than before.
|
||||
await manifestHandle.truncate(0);
|
||||
await manifestHandle.write(JSON.stringify(manifest, null, 2), 0, "utf-8");
|
||||
await manifestHandle.close();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default defineConfig((env) => ({
|
||||
base: "./",
|
||||
|
||||
@@ -124,65 +217,7 @@ export default defineConfig((env) => ({
|
||||
|
||||
react(),
|
||||
|
||||
// Custom plugin to make sure that each asset has an entry in the manifest
|
||||
// This is needed so that the preloading & asset integrity generation works
|
||||
{
|
||||
name: "manifest-missing-assets",
|
||||
|
||||
apply: "build",
|
||||
enforce: "post",
|
||||
writeBundle: {
|
||||
// This needs to be executed sequentially before the manifestSRI plugin
|
||||
sequential: true,
|
||||
order: "pre",
|
||||
async handler({ dir }): Promise<void> {
|
||||
const manifestPath = resolve(dir, "manifest.json");
|
||||
|
||||
let manifestHandle: FileHandle;
|
||||
try {
|
||||
manifestHandle = await open(manifestPath, "r+");
|
||||
} catch (error) {
|
||||
// Manifest does not exist, nothing to do but still warn about
|
||||
this.warn(`Failed to open manifest at ${manifestPath}: ${error}`);
|
||||
return;
|
||||
}
|
||||
const rawManifest = await manifestHandle.readFile("utf-8");
|
||||
const manifest = JSON.parse(rawManifest) as Manifest;
|
||||
|
||||
const existing: Set<string> = new Set();
|
||||
const needs: Set<string> = new Set();
|
||||
|
||||
for (const chunk of Object.values(manifest)) {
|
||||
existing.add(chunk.file);
|
||||
for (const css of chunk.css ?? []) needs.add(css);
|
||||
for (const sub of chunk.assets ?? []) needs.add(sub);
|
||||
}
|
||||
|
||||
const missing = Array.from(needs).filter((a) => !existing.has(a));
|
||||
|
||||
if (missing.length > 0) {
|
||||
for (const asset of missing) {
|
||||
manifest[asset] = {
|
||||
file: asset,
|
||||
integrity: "",
|
||||
};
|
||||
}
|
||||
|
||||
// Overwrite the manifest with the new entries
|
||||
await manifestHandle.truncate(0);
|
||||
await manifestHandle.write(
|
||||
JSON.stringify(manifest, null, 2),
|
||||
0,
|
||||
"utf-8",
|
||||
);
|
||||
}
|
||||
|
||||
await manifestHandle.close();
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
manifestSRI(),
|
||||
augmentManifest(),
|
||||
|
||||
compression(),
|
||||
|
||||
|
||||
Reference in new issue
Block a user