Files
Trilium/apps/client/vite.config.mts
T
Elian DoranandClaude Opus 5.5 fb8518b549 build(share-theme): bundle the share theme with the client's Vite build
The share theme had a build of its own (esbuild, `packages/share-theme/scripts/build.ts`) whose
`dist/` the server, desktop, edit-docs, build-docs and standalone builds copied. It could share
nothing with the app: the previous commit had to load the client's mermaid through a dedicated
`share_mermaid` entry and a manifest to avoid shipping it twice. Any further library the share
pages want from the app (FullCalendar for collections) would need the same machinery.

`shareTheme()` in `apps/client/vite-plugins.mts` now builds the theme as part of the app, in both
the client and the standalone build:

- `scripts.js` is emitted as a chunk of the app's build, into `src/` beside the app's chunks, so
  the two share what both import. Mermaid is a plain `import("mermaid")` again; the
  `share_mermaid` entry, its manifest and the `/share/assets/client/` route are gone.
- `scripts.css` comes from a build of its own. In the app's build, the stylesheets the theme
  shares with the app (tabs, multicolumn, collapsible blocks, to-do lists) land in shared chunks,
  which put them before `ck-content.css` and changed the cascade. Built alone with
  `cssCodeSplit: false`, the stylesheet keeps the order `index.ts` imports in: a rule-by-rule
  comparison with the esbuild output shows the same rules in the same order, apart from
  lightningcss rewrites and six heading rules it drops as overridden.
- `tree.js` also builds alone, so it stays one file: the first paint waits for it, and the app's
  build would move what it shares with `scripts.js` into a chunk.
- Both separate builds target `chrome96`, as the esbuild build did, so `scripts.css` keeps
  `max-width` media queries rather than range syntax. `scripts.js` and its chunks use the
  client's `esnext`, as the shared mermaid already did.
- `src/share_theme.json` lists the files a page can load and, apart, the files only mermaid
  loads (130 files, 5 MB). The static export copies the first set always and the second only
  for a subtree with a diagram (`getShareThemeExportFiles()`). The stylesheets of the chunks
  `scripts.js` imports statically are in `scripts.css`, so they are listed only when a chunk
  loaded on demand imports them, since Vite's preload helper requests them then.

Serving:

- The server maps `/share/assets/` to the client build's `src/`. In development, the client's Vite
  middleware answers `scripts.js` and `tree.js` from source and `scripts.css` empty, since Vite
  injects the styles from the modules; a script or style change no longer needs
  `pnpm --filter share-theme dev`.
- A static host cannot map `/share/assets/` to `src/`, so the standalone build also writes the
  three files to `share/assets/`, `scripts.js` and `scripts.css` as one-line stubs importing their
  namesakes in `src/`.
- A share-theme export needs the client build: a development server without one now fails with
  a message instead of exporting without mermaid, and standalone's development server, which
  builds no share theme, says it needs a production build.

The eager load of a page is 19 KB of JavaScript (14 KB before), as the shared chunks bring
Vite's preload helper; the stylesheet is 94 KB (91 KB).

The share theme's own scripts, `esbuild` and `sass` go, along with `share-theme` in `prepare`,
the `triggerBuildAndCopyTo` calls and their `script` parameter, and the copy of `dist/` in the
Nix build-docs package.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-09 23:17:51 +03:00

177 lines
6.1 KiB
TypeScript

/// <reference types='vitest' />
import { codecovVitePlugin } from '@codecov/vite-plugin';
import prefresh from '@prefresh/vite';
import { join } from 'path';
import { defineConfig } from 'vite';
import { viteStaticCopy } from 'vite-plugin-static-copy'
import { shareTheme, stripUniverEmojiData, stripUniverHyphenation } from './vite-plugins.mjs';
const assets = [ "assets", "stylesheets", "fonts", "translations" ];
const isDev = process.env.NODE_ENV === "development";
let plugins: any = [];
if (isDev) {
plugins = [
// Prefresh keeps a growing list of vnodes per component type and scans it on every diff, so
// a view with thousands of instances of one component slows to a stop. Set TRILIUM_NO_HMR to
// work on such a view; components then reload with the page instead of in place.
...(process.env.TRILIUM_NO_HMR ? [] : [ prefresh() ]),
stripUniverHyphenation(),
stripUniverEmojiData(),
...shareTheme()
];
} else {
plugins = [
stripUniverHyphenation(),
stripUniverEmojiData(),
...shareTheme(),
viteStaticCopy({
targets: assets.map((asset) => ({
src: `src/${asset}/**/*`,
dest: asset,
rename: { stripBase: 2 }
}))
}),
viteStaticCopy({
targets: [
{
src: "../../node_modules/@excalidraw/excalidraw/dist/prod/fonts/**/*",
dest: "",
}
]
}),
// Put the Codecov vite plugin after all other plugins
codecovVitePlugin({
enableBundleAnalysis: process.env.CODECOV_TOKEN !== undefined,
bundleName: "client",
uploadToken: process.env.CODECOV_TOKEN
})
]
}
export default defineConfig(() => ({
root: import.meta.dirname,
cacheDir: '../../.cache/vite',
base: "",
plugins,
// Use oxc for JSX transformation (Vite 8+ replaced the deprecated `esbuild` option with `oxc`)
oxc: {
jsx: {
runtime: 'automatic',
importSource: 'preact',
development: isDev
}
},
css: {
transformer: 'lightningcss',
devSourcemap: isDev
},
server: {
watch: {
ignored: ["**/test-output/**"]
}
},
resolve: {
alias: [
{
find: "react",
replacement: "preact/compat"
},
{
find: "react-dom",
replacement: "preact/compat"
}
],
dedupe: [
"react",
"react-dom",
"preact",
"preact/compat",
"preact/hooks"
]
},
optimizeDeps: {
include: [
"ckeditor5",
"mathlive"
]
},
build: {
target: "esnext",
outDir: './dist',
emptyOutDir: true,
reportCompressedSize: true,
sourcemap: false,
rollupOptions: {
input: {
index: join(import.meta.dirname, "index.html"),
runtime: join(import.meta.dirname, "src", "runtime.ts"),
print: join(import.meta.dirname, "src", "print.tsx")
},
output: {
entryFileNames: (chunk) => {
// We enforce a hash in the main index file to avoid caching issues, this only works because we have the HTML entry point.
if (chunk.name === "index" || chunk.name === "print") {
return "src/[name]-[hash].js";
}
// For EJS-rendered pages (e.g. login) we need to have a stable name.
return "src/[name].js";
},
chunkFileNames: "src/[name]-[hash].js",
assetFileNames: "src/[name]-[hash].[ext]"
},
onwarn(warning, rollupWarn) {
if (warning.code === "MODULE_LEVEL_DIRECTIVE") {
return;
}
rollupWarn(warning);
}
}
},
test: {
environment: "happy-dom",
// Vitest skips CSS processing by default, which would hand `?inline` importers an empty
// string. The presentation themes are SCSS compiled through that path, so their specs need
// it on to assert against real rules.
css: { include: [/\.scss(\?|$)/] },
setupFiles: [
"./src/test/setup.ts"
],
server: {
deps: {
// The plugin reads `rrule` through a namespace import, which Node resolves to the
// package's CommonJS build with no named exports. Bundling it applies Vite's interop.
inline: ["@fullcalendar/rrule"]
}
},
reporters: [
"verbose",
["html", { outputFile: "./test-output/vitest/html/index.html" }],
["junit", { outputFile: "./test-output/vitest/junit.xml", addFileAttribute: true }]
],
coverage: {
reportsDirectory: "./test-output/vitest/coverage",
provider: "v8" as const,
// Codecov resolves an lcov `SF:` path by matching it against the repo's file list.
// Vitest defaults the lcov reporter's `projectRoot` to the Vite `root`, which would
// emit app-relative paths (`src/…`); the shallow ones are ambiguous in this monorepo
// and get attributed to whichever project wins the match. Anchor to the repo root so
// every path is unambiguous.
reporter: ["text", "html", ["lcov", { projectRoot: join(import.meta.dirname, "../..") }]],
include: ["src/**/*.{ts,tsx}"],
// Benchmarks are measured by `vitest bench`, which the test run never invokes, so a
// `*.bench.ts` left in scope reports as wholly uncovered source.
exclude: ["**/*.{test,spec}.{ts,mts,cts,tsx,js,jsx}", "**/*.bench.{ts,mts,cts,tsx}", "**/*.d.ts"]
},
},
commonjsOptions: {
transformMixedEsModules: true,
},
define: {
"process.env.IS_PREACT": JSON.stringify("true"),
}
}));