Commit Graph
6 Commits
Author SHA1 Message Date
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
Elian DoranandClaude Opus 5.5 39f541c510 build(spreadsheet): strip the data of Univer's emoji picker
Univer 1.0 adds an emoji picker to `@univerjs/ui` whose data is generated
into the package: a 1,655-emoji table in the entry (~200 kB) and, spread
into `ui.emojiPicker` of every locale, a search index and titles (~520 kB
in English, up to ~980 kB in Russian). Only the Univer Docs ribbon's
Insert menu opens the picker (`doc.menu.insert-emoji` in `docs-ui`), so a
spreadsheet never shows it, but `UIController` registers the component
unconditionally and the data cannot be tree-shaken.

`stripUniverEmojiData()` empties both at build time, in the client and
the standalone build. The table keeps its category keys, which
`EMOJI_CATEGORIES` is built from; `getEmojiLocaleData()` and the lookups
already treat the locale data as optional. The transform throws if a
region no longer matches, and the spec checks the installed version, so
an upgrade that moves the data fails the build instead of silently
bringing it back. No upstream issue tracks the size; 1.0.2 ships the
data the same way.

Client `dist`: 59.93 MB on 0.25.1, 66.10 MB on 1.0, 60.97 MB with this.
`Spreadsheet` 6.64 -> 6.41 MB; `en-US` locale 940 -> 356 kB; `ru-RU`
1,569 -> 587 kB. The remaining growth is new Univer code and more
translated formula descriptions.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-27 11:04:22 +03:00
Elian DoranandClaude Opus 5.5 b801f5eb6d feat(share): load the client's mermaid instead of bundling a copy
The share theme bundled its own mermaid with esbuild: 5.6 MB of its 6.1 MB
`dist/` (mermaid 2.75 MB, ELK 1.46 MB, cytoscape 0.57 MB, minified). The
client already ships the same mermaid, so server and desktop installs carried
it twice, and every share-theme ZIP export copied the whole folder whether or
not a note had a diagram. Visitors were never affected, since the theme
already loaded mermaid on demand.

Shared pages now use the client's copy:

- `src/share_mermaid.ts` re-exports mermaid. `shareMermaidManifest()` emits it
  as a chunk with `preserveSignature: "exports-only"`: an entry listed in
  `input` loses its exports in a Vite app build, which produced a 35-byte file
  exporting nothing. The plugin also writes `share_mermaid.json`, listing the
  entry and every file it loads (136 files, 5.13 MB), all from one directory.
  The client writes it to `src/`, standalone to `share/assets/client/`.
- The share theme fetches `client/share_mermaid.json` relative to `scripts.js`
  and imports the entry it names, so custom share templates keep working. The
  server serves the client's `src/` at `/share/assets/client/`; its dev server
  and standalone's point the manifest at the source module.
- A share-theme export copies the listed files into `assets/client/` only
  when `hasMermaidDiagrams()` finds a text note's `language-mermaid` block or
  a Markdown note's fenced one in the subtree.
- `build-docs` exports in development mode, where no client build exists, so
  it now builds the client as well; the User Guide's Markdown page has a
  diagram.

The share theme's `dist/` drops to 836 KB (51 files), standalone's
`share/assets/` to 1.5 MB, and an export without diagrams carries no mermaid.
A development server without `apps/client/dist` exports diagrams as code
blocks and logs why.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-26 10:54:57 +03:00
Elian DoranandClaude Opus 5 e6c7286140 fix(client): build the Univer hyphenation stubs on Windows
`new URL().pathname` returns `/C:/…/franc_min.ts` for a Windows path. The
leading slash before the drive letter makes it unopenable, so rolldown
failed the client build with UNLOADABLE_DEPENDENCY (os error 123) on both
Windows nightly jobs. `fileURLToPath()` returns a native path on Windows
and the same string on POSIX.

`apps/standalone` imports the same plugin, so its Windows build was
affected too.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-25 23:12:40 +03:00
Elian DoranandClaude Opus 5 2819da5948 perf(spreadsheet): stub Univer's language detector out of the bundle
@univerjs/engine-render's LanguageDetector wraps franc-min, whose trigram
model is 102 kB of the package's 116 kB. shaping() calls it on every
paragraph it lays out:

    const lang = languageDetector.detect(content);
    const needHyphen = hyphenConfig(paragraphStyle, sectionBreakConfig);
    if (lang !== "unknown" && needHyphen) { ... }

Detection runs ahead of the gate that decides whether to hyphenate at all, so
a spreadsheet pays for it and then discards the result -- autoHyphenation is a
Univer Docs setting the spreadsheet note type never turns on. franc costs
about 240 us per distinct sentence-length string; short strings fall below its
minimum length and return und in under a microsecond. sheet.render-skeleton.ts
reaches the layout for rich-text cells, rotated cells, and column auto-fit.

The stub returns und, which LANG_MAP_TO_HYPHEN_LANG already maps to unknown --
the value that makes shaping() skip hyphenation, which is what happens today.
Two things make it safe: engine-render is the only consumer of franc-min in
the tree, and the detected language has exactly one reader, the hyphenation
branch two lines below the call. The rule is keyed on the importer anyway, so
a second consumer would still resolve the real package.

The plugin now covers both halves of the hyphenation machinery, so it is
stripUniverHyphenation rather than stripUniverHyphenationPatterns.

Client Spreadsheet chunk 5,462.5 KiB -> 5,355.8 KiB, dist 59.380 -> 59.276
MiB; standalone matches. Verified by probing the built chunk for a run of
Spanish trigrams from franc-min/data.js, which is gone.

The canary asserts the entry still writes `import { franc } from "franc-min"`
and that its map still carries und -> unknown, so an upgrade that reaches for
francAll fails on the missing named export instead of detecting nothing.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-24 19:21:19 +03:00
Elian DoranandClaude Opus 5 84e1d5fc30 perf(spreadsheet): drop Univer's hyphenation pattern tables from the bundle
@univerjs/engine-render ships 77 TeX hyphenation tables from
components/docs/layout/hyphenation/patterns/. Its prebuilt lib/es/index.js
registers them in a generated PATTERN_LOADERS map, one live `() => import()`
each, so nothing tree-shakes them: they become 77 lazy chunks totalling
4.36 MiB, 6.85% of the client build. Hungarian alone is 747 kB, the three
Norwegian tables 334 kB each, the three German orthographies 258-264 kB.

shaping() only calls loadPattern() for a paragraph whose section sets
autoHyphenation, which is a Univer Docs document setting; the spreadsheet
note type never turns it on, so no table is ever read. Hyphen's constructor
is the one exception -- it eagerly loads en-gb, and DocumentSkeleton holds a
Hyphen, so sheet.render-skeleton.ts pulled 103 kB on the first cell render.

The entry makes exactly 77 relative imports and every one is a table, so
resolving all of them to one empty module is exact. loadPattern() reads a
table off the namespace under its Pascal-cased locale and returns early when
it is missing, leaving hasPattern() false and LineBreakerHyphenEnhancer
unbuilt, so the stub disables hyphenation rather than breaking layout.

Standalone builds the client from source instead of copying its dist and was
emitting the same 77 chunks, so it applies the plugin too. Server and desktop
copy apps/client/dist and inherit the saving.

Measured on the same tree, plugin off then on: 63.74 MiB -> 59.38 MiB, and
77 chunks -> one 30-byte stub.

vite-plugins.spec.ts reads the installed dependency and scrapes PATTERN_LOADERS,
so an upgrade that renames the entry, inlines the tables, or adds a real
relative sibling import fails there instead of silently restoring the 4.36 MiB.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-24 18:59:43 +03:00