Skip to content

Restore CSS injection, alias vtk-lite.js, docs/build cleanups#1

Merged
daker merged 17 commits into
daker:vite-vitest-migrationfrom
PaulHax:pr-3435
May 18, 2026
Merged

Restore CSS injection, alias vtk-lite.js, docs/build cleanups#1
daker merged 17 commits into
daker:vite-vitest-migrationfrom
PaulHax:pr-3435

Conversation

@PaulHax
Copy link
Copy Markdown

@PaulHax PaulHax commented May 17, 2026

12 small commits addressing review findings.

  • CSS injection restored for UMD and ESM (was silently dropped in migration)
  • Docs cssRuntimePlugin replaced with Vite native CSS Modules + post-process inject
  • vtk-lite.js shipped as byte-identical alias of vtk.js (BREAKING_CHANGES entry added)
  • tsconfig.umd-check.json restored (ESM counterpart was kept)
  • Vitest config keys corrected to Vitest 4 spellings
  • test-only-check.sh extended to it.only / describe.only
  • App demo bundles minified again (~35% smaller)
  • Generated docs API/example md gitignored
  • Sidebar /vtk-js/vtk-js/coverage/ doubling fixed
  • WebXR doc links updated to current example paths
  • Example wrappers forward location.search / hash to iframes
  • Dropped invalid rollup compact: false and dead NOLINT=1

PaulHax added 13 commits May 17, 2026 13:35
…y-guard

The top-level fullyParallel/forbidOnly/retries/workers/use keys are
ignored by Vitest, so GPU/browser tests were not being forced to a
single worker and CI retries were not enabled. Move into test: with
the actual Vitest 4 spellings (fileParallelism, maxWorkers, retry,
allowOnly). Drop use.trace since it is a Playwright Test option that
has no Vitest browser equivalent.
The migrated Vitest suite uses it()/describe() in addition to test(),
so the .only-guard needs to catch all three forms. Switch to an -E
pattern so a forgotten it.only(...) or describe.only(...) fails CI.
compact is a Rollup input option, not an output option, so this key
was a no-op that produced a warning during ESM build.
The old webpack (UMD via style-loader) and rollup (ESM via
rollup-plugin-postcss with default inject:true) both injected
stylesheets as <style> tags on import. The Vite migration silently
dropped this: UMD emitted vtk.js.css alongside vtk.js, and ESM emitted
orphan .module.css files that nothing imported, so UI/widget consumers
lost their styling without any warning.

- inlineUmdCssPlugin: add enforce:'post' so generateBundle sees the
  CSS asset emitted by Vite's css-post plugin. Without it, the bundle
  filter returned empty and the plugin no-op'd.
- injectEsmCssPlugin: new plugin that inlines each *.module.css into
  its corresponding *.module.css.js wrapper as a <style> tag injected
  on first evaluation, then deletes the orphan stylesheet from the
  bundle.

Verified by diffing against the published @kitware/vtk.js@35.15.1
artifacts, which inject CSS via styleInject; the new wrappers behave
equivalently.
Application demos are emitted as standalone single-file HTML (per each
app's index.md: "the only requirement is the single HTML file without
any web server"). The old Rollup path ran terser; the Vite migration
disabled minification, bloating each demo from ~1.5 MB to ~2.5 MB.
Re-enable esbuild minification, which is the fast path Vite already
bundles, no extra dependency.

Readable source for learning still lives in Examples/Applications/*
on GitHub; the inlined script in the deployable HTML was never
intended as a view-source artifact.
The old webpack ESLint plugin honored NOLINT to skip linting during
the release build (since lint runs separately on the line). The Vite
migration has no equivalent consumer, so the env var is dead config.
The previous cssRuntimePlugin hand-rolled ~130 lines of CSS Modules
logic — regex class-name parsing, sha256 hashing, manual composes
resolution — that re-implemented (incompletely) what Vite handles via
postcss-modules. Cross-file composes, nested rules, complex selectors,
and pseudo-classes were all silent footguns waiting for a contributor
to write a more complex CSS module.

Let Vite do the CSS parsing and class-name scoping. A new ~25-line
inlineExtractedCssPlugin walks chunk.viteMetadata.importedCss at
generateBundle time, inlines each chunk's extracted CSS as a <style>
injection IIFE, and deletes the orphan asset. Same end behavior as the
old plugin (CSS bundled into the standalone HTML, class names work),
but with Vite's battle-tested CSS Modules implementation underneath.

Also set assetsInlineLimit:Infinity on the ES module build to match
the application build; the old plugin always inlined url() refs as
data URIs, and extracted assets would otherwise resolve to /_assets/
paths that don't honor the VitePress base prefix.

Net: -196 / +41 lines.
Forrest Li added matched ESM and UMD .d.ts validation configs in 2022
(c925926); both have been in CI ever since. The Vite migration
kept the ESM half but dropped the UMD half, leaving the UMD .d.ts
rewriting pipeline (copyUmdAssetsPlugin in vite.config.js) with no
in-repo check.

The UMD .d.ts files use absolute "vtk.js/Sources/..." imports rewritten
from relative paths at build time. If that rewriting breaks, the only
signal today is downstream TypeScript users hitting unresolvable
imports. Restore the config and wire it into both build-test and
publish workflows so the next .d.ts regression fails at PR time.
Two related broken URL behaviors in the generated examples:

generate-examples.mjs hard-coded the iframe src and full-screen <a>
href to '${name}/index.html' without forwarding window.location.search
or window.location.hash, so navigating to examples/SkyboxViewer.html
?fileURL=... loaded the example wrapper but the iframe inside got the
URL without any parameters. Add a <script setup> block with an
onMounted hook that re-binds the iframe and link to the page URL plus
the current search/hash; Vue's :src/:href bindings then update both.

develop_webxr.md linked to legacy paths like GeometryViewer/Geometry-
Viewer.html and WebXRVolume/WebXRVolume.html that the new build never
emits. After the new build the working paths are GeometryViewer.html
(the VitePress wrapper, which forwards its query string to the iframe
per the change above) and WebXRVolume.html. Rewrite all ten broken
references and the four nested ones inside fileURL=[...] values.
The Testing sidebar items hard-coded link: '/vtk-js/coverage/...',
but VitePress automatically prepends base: '/vtk-js/' to absolute
links. The built sidebar ended up with /vtk-js/vtk-js/coverage/...
and docs:build logged "No matching file" warnings. Drop the manual
prefix so VitePress emits the intended single-prefixed path.

Coverage report files are still generated separately by CI and
served at /vtk-js/coverage/ on the deployed site; this fix only
corrects the sidebar link the docs site emits.
docs:generate writes ~200 files into Documentation/api/, ~179 into
Documentation/examples/, and a Documentation/examples/gallery.js
manifest. None were gitignored, so a casual `git add Documentation/`
after running the docs pipeline locally would commit hundreds of
build artifacts. Both directories keep one hand-authored index.md
that stays tracked via negated patterns.

sidebar.ts is left tracked-as-placeholder for now (config.ts imports
it; gitignoring needs a fresh-checkout strategy that's out of scope).
The Vite migration dropped the slimmed-down vtk-lite.js UMD bundle.
It was never documented but was published to npm and CDN for years,
and at least one downstream (sphinxcontrib-cadquery) vendors it.

Copy vtk.js to vtk-lite.js so existing <script src=.../vtk-lite.js>
and CDN consumers keep working. Byte-identical so the shared
sourceMappingURL still resolves vtk.js.map. Adds ~2.65MB to the UMD
npm tarball — accepted as a one-version deprecation alias.

BREAKING_CHANGES.md notes the alias and the one behavior change
worth flagging: anything indexing into the ColorMaps array by
position now sees the full preset set instead of the lite subset.
Comment thread vitest.config.js
@PaulHax
Copy link
Copy Markdown
Author

PaulHax commented May 18, 2026

i pushed a few more changes:

Extracted vite.config.js's inline plugins into Utilities/build/vtk-plugins.mjs, enabled ESM sourcemaps, fixed compareImages's expect() shape, rewrote the example-runner CLI as ESM with a direct config function, and defaulted vitest browser mode to headless

PaulHax added 3 commits May 18, 2026 09:44
vite.config.js had grown to ~450 lines holding six inline Rollup
plugins (copyEsmAssetsPlugin, copyUmdAssetsPlugin,
generateDtsReferencesPlugin, cleanupAssetsPlugin, injectEsmCssPlugin,
inlineUmdCssPlugin) plus five filesystem helpers and the
SOURCE_IGNORE_LIST/ignoreSourceFile pair. Move all of that to a new
Utilities/build/vtk-plugins.mjs alongside the existing generic
plugins.mjs, so vite.config.js becomes config wiring only (~150
lines). Same closeBundle/generateBundle behavior, including the
vtk-lite.js alias and the .module.css inlining.

Drive-by cleanups while moving:

- Extract flattenIndexEntry so both the ESM entryFileNames callback
  and generateDtsReferencesPlugin use a single Foo/index -> Foo
  helper instead of duplicate regexes.
- Tighten externals from new RegExp('^' + name) to ^name(/|$) so a
  hypothetical @types/webxray-foo wouldn't be matched by the
  @types/webxr pattern. Today nothing collides, but the old form
  was a quiet footgun.
- Replace the hand-rolled copyDir with fs.cpSync({recursive:true}),
  stable in the Node 22 we already pin in CI.
- Share the style-injection IIFE template between the ESM and UMD
  CSS plugins.

Also flip ESM sourcemap to true. UMD already had it; ESM was off
without a stated reason and the old rollup ESM build emitted them.
Per-file .map files are siblings of each preserveModules chunk.
The migration left compareImages calling expect() with the
condition as the value and the diagnostic message as the second
positional arg, then chaining .toBeTruthy():

  expect(minDelta < mismatchTolerance, '...').toBeTruthy();

This works because Vitest 4's expect(value, message) carries the
message into the assertion error, but it reads like the old tape
t.ok(cond, msg) shape and obscures both the matcher and the actual
value on failure.

Switch to expect(value, msg).toBeLessThan(...) so the failure
report shows the actual delta percentage instead of just "true is
not truthy". Mirror the dimensions check with .toBe(true).
example-runner-cli.js was CJS (require) that loaded Vite via
dynamic import, then spawned createServer({ configFile: ... })
pointing at vite.example.config.mjs (ESM). The two halves
communicated by mutating process.env with EXAMPLE_ENTRY,
EXAMPLE_NAME, EXAMPLE_HOST, etc., which the config file then read
back at module-load time. Mixing module formats in a modernization
PR is jarring, and the env-var protocol forces a singleton vite
process per CLI invocation.

Rewrite the CLI as .mjs and switch vite.example.config.mjs from a
defineConfig'd default export to an exported
createExampleConfig({ repoRoot, entry, name, host, port,
openBrowser, useHttps }) function. The CLI imports that function
and passes the result directly to createServer() with
configFile:false, so there's no env-var handoff and no file path
to keep in sync. Also expose --host and --port flags now that
they're cheap to thread through.

Run via the new path in package.json scripts:
  example, example:https, example:webgpu
@daker
Copy link
Copy Markdown
Owner

daker commented May 18, 2026

@PaulHax i am ok with the changes, do you want to merge or wait until you finish ?

Local runs popped the Vitest browser-mode test runner UI every
invocation. CI was already headless (vitest detects CI=1
automatically); set browser.headless explicitly so local matches.

browser.headless suppresses both the UI tab and the test
execution browser; setting it on the per-instance launch options
only affects the latter.
@PaulHax
Copy link
Copy Markdown
Author

PaulHax commented May 18, 2026

🙏 I done for now and ready to merge.

@daker
Copy link
Copy Markdown
Owner

daker commented May 18, 2026

@PaulHax rebase & merge right ?

@PaulHax
Copy link
Copy Markdown
Author

PaulHax commented May 18, 2026

rebase & merge right ?

sounds right, Hopoefully this updates your PR on main repo.

@daker daker merged commit ff33db7 into daker:vite-vitest-migration May 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants