mirror of
https://github.com/TriliumNext/Trilium.git
synced 2026-10-11 22:49:56 +00:00
Making `scripts.js` render-blocking kept the tree from showing at the top before its position was restored, but held the first paint until the whole theme had loaded and run: search, table of contents, theme buttons, footer and the rest, none of which has to run first. `setupTreeState()` and its helpers move from `navigation.ts`, which imports CSS, to `page/tree_state.ts`, and a new `tree.ts` entry runs it through `whenActivated()`, so a prerendered page restores once it is shown. `build.ts` builds it as `dist/tree.js` in a second esbuild pass without code splitting: `whenActivated()` is inlined instead of moved into a chunk shared with `scripts.js`, so the file loads alone, 4.4KB with no imports, and `scripts.js` drops from 30KB to 25.5KB. The share server and the static export put `assets/tree.js` first in `jsToLoad`, so `page.ejs`'s first-entry rule makes it the only render-blocking script; `scripts.js` and the page's `~shareJs` scripts load as before. The server, the export and standalone each serve or copy the whole `dist` folder, so the file reaches all three. In Chromium the first frame still shows the pane at its restored position. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
120 lines
4.3 KiB
TypeScript
120 lines
4.3 KiB
TypeScript
import path from "node:path";
|
|
|
|
import * as esbuild from "esbuild";
|
|
import { rmSync, writeFileSync } from "node:fs";
|
|
import { createRequire } from "node:module";
|
|
import { pathToFileURL } from "node:url";
|
|
import {
|
|
buildVsCodeThemeCss, VS_CODE_DARK, VS_CODE_LIGHT
|
|
} from "@triliumnext/highlightjs/src/vs_code_theme.js";
|
|
import * as sass from "sass";
|
|
|
|
const packageJson = process.env.npm_package_json;
|
|
if (!packageJson) {
|
|
throw new Error("Run the build through pnpm, which sets npm_package_json.");
|
|
}
|
|
const rootDir = path.dirname(packageJson);
|
|
|
|
// Sass resolves relative paths on its own; bare specifiers such as
|
|
// "katex/src/styles/katex.scss" go through Node.
|
|
const nodeModulesImporter: sass.FileImporter<"sync"> = {
|
|
findFileUrl(url) {
|
|
if (url.startsWith(".") || url.startsWith("/")) {
|
|
return null;
|
|
}
|
|
|
|
return pathToFileURL(createRequire(path.join(rootDir, "package.json")).resolve(url));
|
|
}
|
|
};
|
|
|
|
// esbuild has no Sass support of its own. katex.scss needs it to build KaTeX's
|
|
// stylesheet without the woff and ttf faces.
|
|
const sassPlugin: esbuild.Plugin = {
|
|
name: "sass",
|
|
setup(build) {
|
|
build.onLoad({ filter: /\.scss$/ }, (args) => ({
|
|
contents: sass.compile(args.path, { importers: [nodeModulesImporter] }).css,
|
|
loader: "css",
|
|
resolveDir: path.dirname(args.path)
|
|
}));
|
|
}
|
|
};
|
|
|
|
// Serves `virtual:code-themes.css`: the VS Code highlighting themes code notes also use, one per
|
|
// share theme mode. `:where()` keeps the block rules as weak as a stock highlight.js theme's, so the
|
|
// share theme's own `.ck-content code` colors still win inside text notes.
|
|
const codeThemesPlugin: esbuild.Plugin = {
|
|
name: "code-themes",
|
|
setup(build) {
|
|
build.onResolve({ filter: /^virtual:code-themes\.css$/ }, (args) => ({
|
|
path: args.path,
|
|
namespace: "code-themes"
|
|
}));
|
|
build.onLoad({ filter: /.*/, namespace: "code-themes" }, () => ({
|
|
contents: [
|
|
buildVsCodeThemeCss(VS_CODE_LIGHT, ":where(html.theme-light)"),
|
|
buildVsCodeThemeCss(VS_CODE_DARK, ":where(html.theme-dark)")
|
|
].join("\n"),
|
|
loader: "css"
|
|
}));
|
|
}
|
|
};
|
|
|
|
const outDir = path.join(rootDir, "dist");
|
|
|
|
async function runBuild(watch: boolean) {
|
|
const before = performance.now();
|
|
|
|
// esbuild leaves its outdir as it found it, and every `pnpm install` writes an unminified
|
|
// build there, so a minified release build would land beside those files and both sets would
|
|
// be copied into the app.
|
|
rmSync(outDir, { recursive: true, force: true });
|
|
|
|
const opts: esbuild.BuildOptions = {
|
|
entryPoints: [ { in: path.join(rootDir, "src", "index.ts"), out: "scripts" } ],
|
|
bundle: true,
|
|
splitting: true,
|
|
outdir: outDir,
|
|
format: "esm",
|
|
target: ["chrome96"],
|
|
loader: {
|
|
".png": "dataurl",
|
|
".gif": "dataurl",
|
|
".woff": "file",
|
|
".woff2": "file",
|
|
".ttf": "file",
|
|
".eot": "empty",
|
|
".svg": "empty",
|
|
".html": "text",
|
|
".css": "css"
|
|
},
|
|
plugins: [ sassPlugin, codeThemesPlugin ],
|
|
logLevel: "info",
|
|
metafile: true,
|
|
minify: process.argv.includes("--minify")
|
|
};
|
|
// `tree.js` runs before the page is first drawn, so it is built without splitting: the code it
|
|
// shares with `scripts.js` is inlined rather than moved into a chunk it would wait for.
|
|
const treeOpts: esbuild.BuildOptions = {
|
|
entryPoints: [ { in: path.join(rootDir, "src", "tree.ts"), out: "tree" } ],
|
|
bundle: true,
|
|
outdir: outDir,
|
|
format: "esm",
|
|
target: opts.target,
|
|
logLevel: "info",
|
|
minify: opts.minify
|
|
};
|
|
if (watch) {
|
|
const contexts = await Promise.all([ esbuild.context(opts), esbuild.context(treeOpts) ]);
|
|
await Promise.all(contexts.map((context) => context.watch()));
|
|
} else {
|
|
const [ result ] = await Promise.all([ esbuild.build(opts), esbuild.build(treeOpts) ]);
|
|
const after = performance.now();
|
|
writeFileSync("meta.json", JSON.stringify(result.metafile, null, 2));
|
|
console.log(`Build actually took ${(after - before).toFixed(2)}ms`);
|
|
}
|
|
}
|
|
|
|
const watch = process.argv.includes("--watch");
|
|
runBuild(watch).catch(console.error);
|