Relaxjs uses the modern Stage 3 decorator syntax (the one being standardized by TC39, supported by TypeScript 5.0 and newer). This is different from the older "experimental" decorators that many guides still describe.
This page shows how to set up a Vite + TypeScript project so that relaxjs decorators like @RegisterValidator work out of the box.
| Tool | Minimum version | Why |
|---|---|---|
| Node.js | 18 or newer | Required by Vite 5+ |
| TypeScript | 5.0 or newer | Stage 3 decorator support |
| Vite | 5.3 or newer | Bundled esbuild transforms Stage 3 decorators |
If you created your project with npm create vite@latest recently, you already meet these.
npm create vite@latest my-app -- --template vanilla-ts
cd my-app
npm install
npm install @relax.js/coreThe vanilla-ts template gives you TypeScript without a framework, which is what relaxjs is designed for.
Open tsconfig.json and make sure these options are set:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"experimentalDecorators": false,
"useDefineForClassFields": true,
"verbatimModuleSyntax": true,
"strict": true,
"skipLibCheck": true,
"lib": ["ES2022", "DOM"]
},
"include": ["src"]
}The important lines for decorators:
"experimentalDecorators": false— this is the switch. Whenfalse(or omitted), TypeScript uses the new Stage 3 decorators. Whentrue, it uses the old legacy decorators, which are not compatible with relaxjs."target": "ES2022"— any value fromES2022upward works. Do not pick a lower target; esbuild needs a modern target to emit clean decorator output."useDefineForClassFields": true— required so class fields behave as the standard specifies. Relaxjs form components rely on this.
You do not need emitDecoratorMetadata. That option only applies to legacy decorators and is ignored here.
One more line is not about decorators but about components being registered at all:
-
"verbatimModuleSyntax": truekeeps every import that is not written asimport type. Without it, an import used only as a type is removed from the bundle:import { ProfilePage } from './ProfilePage'; navigate<ProfilePage>('profile');
ProfilePageappears only in a type position here, so the import is dropped,ProfilePage.tsnever runs and itscustomElements.definenever happens.defineRoutes()then throws because the tag is not defined, in a file that does import the component. With the option on, the import stays and the module runs. The price is writingimport type { ... }for imports that really are types only; TypeScript reports the ones that need it.
A minimal config is enough. Vite picks up your tsconfig.json automatically.
import { defineConfig } from 'vite';
export default defineConfig({
server: {
port: 3000,
},
});No plugins are required for decorators. Vite's built-in esbuild handles the transform.
Create src/main.ts:
import { RegisterValidator, ValidationContext } from '@relax.js/core/forms';
@RegisterValidator('even')
class EvenValidation {
validate(value: string, context: ValidationContext) {
const num = parseInt(value, 10);
if (isNaN(num) || num % 2 !== 0) {
context.addError('Value must be an even number');
}
}
}
console.log('EvenValidation registered');Run the dev server:
npm run devOpen the browser console. If you see EvenValidation registered and no errors, your decorator setup is working.
Your TypeScript version is older than 5.0. Update it:
npm install -D typescript@latestThen restart your editor so it picks up the new version.
You probably have "experimentalDecorators": true left over from an older template. Set it to false and restart the dev server.
Your Vite (and therefore esbuild) version is too old to transform Stage 3 decorators. Upgrade:
npm install -D vite@latestVite 5.3 and newer bundle an esbuild version that handles the new decorator syntax.
Make sure "lib" includes "DOM" in tsconfig.json. Without it, TypeScript does not know about browser APIs that relaxjs uses.
If you add Vitest for testing, no extra decorator configuration is needed. Vitest uses the same esbuild transform as Vite, so the settings above apply to tests automatically.
npm install -D vitest jsdom// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom',
globals: true,
},
});For relaxjs decorators to work, you need three things:
- TypeScript 5.0+
"experimentalDecorators": falseintsconfig.json- Vite 5.3+ (modern esbuild)
With those in place, decorators like @RegisterValidator work without any plugin or extra setup.