fix: keep plugin css below core utilities and version plugin assets by their css

This commit is contained in:
LukeGus committed 2026-09-29 19:06:57 -05:00
1 parent 80944301dd
commit 1a6f9ffad6
5 files changed
+92 -21

No files matched your search

+23 -14
View File
@@ -93,11 +93,16 @@ export function describePluginFrontend(
const entry = frontendEntry(plugin);
if (!entry || !fileExists(entry.file)) return { ...none, locales };
const assetVersion = contentVersion(entry.file, plugin.manifest.version);
const cssFile = path.join(entry.dir, "frontend.css");
const css = fileExists(cssFile);
const assetVersion = contentVersion(
css ? [entry.file, cssFile] : [entry.file],
plugin.manifest.version,
);
return {
frontend: true,
css: fileExists(path.join(entry.dir, "frontend.css")),
css,
assetVersion,
locales: locales.sort(),
};
@@ -108,21 +113,25 @@ const versionCache = new Map<string, { stamp: string; version: string }>();
/**
* A hash of the bundle's bytes, not its mtime: assets are cached as immutable
* for a year, and a rebuilt .tmxplug has a fixed mtime, so a same-size rebuild
* would otherwise keep serving the stale copy.
* would otherwise keep serving the stale copy. The CSS is hashed with the JS
* because both are served under the same version.
*/
function contentVersion(file: string, version: string): string {
const stat = fs.statSync(file);
function contentVersion(files: string[], version: string): string {
// ctime, unlike mtime, cannot be set by tar, so an unpack always changes it.
const stamp = `${version}:${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}`;
const cached = versionCache.get(file);
const stamp = [
version,
...files.map((file) => {
const stat = fs.statSync(file);
return `${file}:${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}`;
}),
].join("|");
const key = files.join("|");
const cached = versionCache.get(key);
if (cached?.stamp === stamp) return cached.version;
const hashed = crypto
.createHash("sha256")
.update(version)
.update(fs.readFileSync(file))
.digest("hex")
.slice(0, 12);
versionCache.set(file, { stamp, version: hashed });
const hash = crypto.createHash("sha256").update(version);
for (const file of files) hash.update(fs.readFileSync(file));
const hashed = hash.digest("hex").slice(0, 12);
versionCache.set(key, { stamp, version: hashed });
return hashed;
}
+11
View File
@@ -63,6 +63,17 @@ describe("describePluginFrontend", () => {
expect(info.locales).toEqual(["de_DE", "en"]);
});
it("changes the version when only the stylesheet changes", () => {
const before = describePluginFrontend(plugin).assetVersion;
const css = path.join(root, "gizmo", "dist", "frontend.css");
fs.writeFileSync(css, ".gizmo{color:red}");
try {
expect(describePluginFrontend(plugin).assetVersion).not.toBe(before);
} finally {
fs.writeFileSync(css, ".gizmo{}");
}
});
it("reports nothing for an unknown plugin", () => {
expect(describePluginFrontend(undefined)).toEqual({
frontend: false,
+3 -3
View File
@@ -1,3 +1,6 @@
/* Plugin utilities (termix-plugin build) rank above base, below core's own. */
@layer properties, termix-plugin-properties, theme, base, components,
termix-plugin-utilities, utilities;
@import "tailwindcss";
@import "tw-animate-css";
@import "./theme.css";
@@ -13,9 +16,6 @@
@import "@fontsource/source-code-pro/700.css";
@import "@fontsource/source-code-pro/700-italic.css";
/* Plugins compile their own classes (termix-plugin build). */
@source not "../../plugins";
@font-face {
font-family: "Caskaydia Cove Nerd Font Mono";
src: url("/fonts/CaskaydiaCoveNerdFontMono-Regular.ttf") format("truetype");