@praxisjs/vite-plugin
Changelog for @praxisjs/vite-plugin — Vite integration with JSX transform and HMR support.
@praxisjs/vite-plugin
4.2.2
Depends on the other @praxisjs/* packages through caret ranges (^2.3.0) instead of exact versions, so npm no longer installs a second copy of a package when two releases disagree, and upgrades no longer end in peer conflicts. The @praxisjs/css peer accepts any 0.3.x release.
4.2.1
Compiled templates write a static boolean aria-* attribute as "true" or "false", matching @praxisjs/runtime 0.8.1 (it used to be an empty attribute, or left out for false).
4.2.0
PraxisJS libraries can be compiled like the app's own code. A component library built with tsc (@morphos/*, for one) used to get none of the plugin's work: its decorated fields were defined and then replaced by their decorator, leaving its instances in V8's dictionary mode, and its elements were built by jsx(). A library now opts in with "praxisjs": { "optimize": true } in its package.json: the decorated fields of its compiled files are claimed as in the app's own code. With "rootDir" and "outDir" as well, and its sources shipped, imports of its compiled files load its sources instead, which get compiled templates and keyed lists too. The new libraries option opts packages in or out by name ({ "@morphos/*": { rootDir: "src", outDir: "dist" } }), or turns the feature off (false). In the dev server the same transforms run inside Vite's dependency optimizer, so libraries stay pre-bundled, and the optimizer's cache follows the options. Libraries that opt in are added to ssr.noExternal. Packages that don't opt in are compiled exactly as before. With five @morphos/inputs components, mounting took 16 to 37% less time from the compiled files and 23 to 44% less from the sources. See Libraries.
4.1.0
Shorter output. Compiled templates whose tree the HTML parser reads back unchanged are emitted as markup (_$html("<tr><td class=col-md-1>…")) instead of one builder call per element; any other tree keeps the builder, so the DOM is the same either way (see Compiled templates). The decoration of public fields and methods with a literal name, of the class and of its decorator metadata calls shorter helpers of virtual:praxisjs/decorators that build the same contexts as __esDecorate, about 200 bytes less per decorated member once minified. The create-praxisjs templates' entry bundles are 2.7 to 4.5 kB smaller (minimal 21.7 to 19.0 kB).
The published package no longer contains the package's tests (compiled tests in dist/__tests__, test sources in src/__tests__).
Decorator helpers are shared. The helpers TypeScript's decorator lowering calls (__esDecorate, __runInitializers, …) and the claimed fields' __pxField were written into every decorated module; they now come from one virtual module, virtual:praxisjs/decorators, which each lowered module imports. The copies tsc wrote into the files of a library that ships ES modules (@praxisjs/router's components, for one) are replaced with imports of it too, but only when a helper is exactly the one TypeScript writes. The output keeps its lines, so source maps are unchanged. In the create-praxisjs templates this removes 4 to 6 kB of minified JavaScript from the router, blog and full apps.
Hot module replacement works. The hmr option used to send an event nothing listened to, so every component edit reloaded the page. In the dev server, the plugin now registers each top-level class of the app's modules with @praxisjs/runtime, and a module that only exports component classes replaces them in place: mounted instances render again with the new code, without a reload. Modules that export anything else keep Vite's default handling. See Hot module replacement.
Keyed .map() lists are rewritten into For. {() => list.map((item) => <Row key={item.id} />)} used to rebuild every row whenever the list changed, since the key prop had no runtime effect. The new praxisjs:keyed-lists transform turns this pattern into @praxisjs/runtime's For, with each row re-rendering on its own when its item, its index or anything else it reads changes, so the output matches the original while untouched rows are kept. Keyed .map() calls that don't match the pattern exactly (block bodies, keys that read something other than the callback's parameters, …) are left unchanged and reported as build warnings. It uses the TypeScript compiler the plugin already depends on and emits a source map. Options: praxisjs({ keyedLists: false }) turns it off, { keyedLists: { warn: false } } silences the warnings.
Decorated fields are defined by their decorator. After lowering decorators, a decorated field is no longer emitted as a class field that its decorator then replaces: at the step where its decorators' initializers run, the innermost one defines the property directly (@praxisjs/decorators 2.0.0), or the field is defined as before when that decorator does not support it. Every step keeps its order, the initializer still runs in the field's own scope (with its this, super and new.target, never seeing a constructor's parameters or locals), nothing is added to the prototype, and the source map keeps its lines.
Compiled templates (templates option, on by default). JSX trees of HTML/SVG elements are compiled into a template built once and cloned per render; only expressions, arrow functions, handlers, refs and components are bound per instance. A lone element is compiled when it has a literal attribute, literal text or a reactive child. The output DOM is identical to jsx(), except that a reactive child that is the only content of its element needs no anchor comment. Event props are bound through the runtime's delegated path, which attaches no listener at render. Each event prop name used in a module is resolved once, in a hoisted constant, and a reactive class/className (an arrow or function literal) gets a dedicated binding helper instead of the generic prop one. A template that is the whole body of an arrow function (a row renderer such as (row) => <tr>…</tr>) or the operand of a return (a component's render()) is emitted as plain statements instead of a function created and called on every run. Text keeps the whitespace JSX keeps (Page {page} of {total}, a <b /> c), with the same rules as the JSX transform. Trees with spread attributes, an explicit children attribute, unknown HTML entities or an await/yield in an expression stay on jsx(). See Compiled templates.
4.0.3
Fixed decoratorLoweringPlugin() (used internally by praxisjs(), and by @praxisjs/storybook's Vite config) silently dropping decorated class fields that have no initializer, e.g. @Styled(BadgeStyles) $s!: BadgeStyles; or @Ref<HTMLElement>() containerRef!: Ref<HTMLElement>;. The plugin ran in Vite's default "normal" transform tier, after Vite's built-in esbuild TypeScript transform had already parsed the file — esbuild's TC39 decorator support drops such fields entirely instead of lowering them, so the field and its decorator were already gone by the time decoratorLoweringPlugin ran. At runtime the field read back as undefined, and since neither render() nor onMount() swallowed the resulting error, the component silently rendered nothing. decoratorLoweringPlugin() now sets enforce: "pre", running before Vite's esbuild transform ever sees the file.
4.0.2
Fixed praxisjsCSS() build-time extraction treating tsconfig path aliases (e.g. @/components/..., mapped via compilerOptions.paths) as external npm packages. The extraction sandbox resolved these to an empty stub instead of the real local file, so any Stylesheet that imported values through an alias — most commonly a shared design-token module — could end up with undefined values or a throwing constructor, silently dropping that component's CSS from the production build. Aliased imports are now resolved through esbuild's own tsconfig-aware resolver instead of being externalized.
4.0.1
Fixed Cannot find package 'esbuild' errors in scaffolded projects (create-praxisjs) and other consumers. esbuild moved from devDependencies to dependencies — it's imported at runtime by the CSS static-extraction step, so it needs to be installed alongside the plugin itself, not just present in this monorepo's workspace.
4.0.0
Upgraded to Vite 8 (rolldown-vite). Vite 8's oxc-based transform doesn't lower TC39 (non-legacy) decorators yet (oxc-project/oxc#9170), so praxisjs() now uses the TypeScript compiler (ts.transpileModule) to lower decorator syntax into runtime-executable code, leaving JSX untouched for oxc's own JSX pipeline. No third-party decorator transform is bundled — TypeScript's decorator lowering keeps class fields as native ES2022 fields, so a decorated field's addInitializer callback already runs after super() in a derived class's explicit constructor without any extra fix-up pass.
The esbuild config key (deprecated in Vite 8) was replaced with oxc in the plugin's own config() hook.
typescript moved from a devDependency to a regular dependency, since the decorator-lowering pass now calls it at runtime — it's pulled in automatically, no action needed.
Breaking: requires Vite ^8.0.0. Apps still on Vite 7 should stay on the previous major version.
Also requires useDefineForClassFields: true in tsconfig.json (previously false) — see the Getting Started guide. With false, oxc silently drops a class field's decorator during its own TypeScript stripping, before the lowering step ever sees it.
decoratorLoweringPlugin() is now exported alongside praxisjs(), so it can be registered directly in vitest.config.ts (or any other Vite-based tool) to lower decorator syntax outside of the full praxisjs() plugin — see Using decorators in Vitest.
0.2.0
praxisjsCSS() — static CSS extraction plugin for @praxisjs/css.
Scans TypeScript source files for Stylesheet subclasses, keyframes(), globalStyle(), and @Themed() decorators, evaluates them in a sandboxed Node.js context, and emits all CSS into virtual:praxisjs/styles.css. In production, __PRAXIS_CSS_STATIC__ is set to true and runtime <style> injection becomes a no-op. In development, the virtual module is empty and CSS is injected at runtime as usual.
// vite.config.ts
import { praxisjs, praxisjsCSS } from '@praxisjs/vite-plugin'
export default defineConfig({
plugins: [praxisjs(), praxisjsCSS()],
})@praxisjs/css is now a peer dependency.
0.1.1
Refreshed workspace dependencies for stability and security (@types/node, eslint, unocss).
0.1.0
Initial release — Vite plugin that configures TypeScript decorators (esbuild target: 'ES2022'), the @praxisjs/jsx import source, and optional component-level HMR.