@relax.js/core 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DependencyInjection.d.ts +3 -3
- package/dist/collections/LinkedList.d.ts +9 -8
- package/dist/collections/index.js +1 -1
- package/dist/collections/index.js.map +3 -3
- package/dist/collections/index.mjs +1 -1
- package/dist/collections/index.mjs.map +3 -3
- package/dist/di/index.js +1 -1
- package/dist/di/index.js.map +2 -2
- package/dist/di/index.mjs +1 -1
- package/dist/di/index.mjs.map +2 -2
- package/dist/elements/index.js +1 -1
- package/dist/elements/index.js.map +1 -1
- package/dist/forms/FormValidator.d.ts +2 -2
- package/dist/forms/ValidationRules.d.ts +2 -6
- package/dist/forms/index.js +1 -1
- package/dist/forms/index.js.map +3 -3
- package/dist/forms/index.mjs +1 -1
- package/dist/forms/index.mjs.map +3 -3
- package/dist/forms/setFormData.d.ts +39 -1
- package/dist/html/index.js +1 -1
- package/dist/html/index.js.map +3 -3
- package/dist/html/index.mjs +1 -1
- package/dist/html/index.mjs.map +3 -3
- package/dist/http/ServerSentEvents.d.ts +1 -1
- package/dist/http/SimpleWebSocket.d.ts +1 -1
- package/dist/http/index.js +1 -1
- package/dist/http/index.js.map +3 -3
- package/dist/http/index.mjs +1 -1
- package/dist/http/index.mjs.map +3 -3
- package/dist/i18n/icu.d.ts +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/index.js.map +2 -2
- package/dist/i18n/index.mjs +1 -1
- package/dist/i18n/index.mjs.map +2 -2
- package/dist/index.js +3 -3
- package/dist/index.js.map +3 -3
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +3 -3
- package/dist/routing/NavigateRouteEvent.d.ts +4 -4
- package/dist/routing/index.js +2 -2
- package/dist/routing/index.js.map +3 -3
- package/dist/routing/index.mjs +3 -3
- package/dist/routing/index.mjs.map +3 -3
- package/dist/routing/navigation.d.ts +1 -1
- package/dist/templates/NodeTemplate.d.ts +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +3 -3
- package/dist/utils/index.mjs +1 -1
- package/dist/utils/index.mjs.map +3 -3
- package/docs/GettingStarted.md +7 -0
- package/docs/api.json +34 -12
- package/docs/forms/reading-writing.md +101 -1
- package/docs/setup/bootstrapping.md +154 -0
- package/docs/setup/build-and-deploy.md +183 -0
- package/docs/setup/project-structure.md +170 -0
- package/docs/setup/vite.md +175 -0
- package/package.json +3 -2
- package/docs/api/.nojekyll +0 -1
- package/docs/api/assets/hierarchy.js +0 -1
- package/docs/api/assets/highlight.css +0 -120
- package/docs/api/assets/icons.js +0 -18
- package/docs/api/assets/icons.svg +0 -1
- package/docs/api/assets/main.js +0 -60
- package/docs/api/assets/navigation.js +0 -1
- package/docs/api/assets/search.js +0 -1
- package/docs/api/assets/style.css +0 -1633
- package/docs/api/classes/http.WebSocketClient.html +0 -26
- package/docs/api/classes/i18n.LocaleChangeEvent.html +0 -66
- package/docs/api/classes/index.Blueprint.html +0 -3
- package/docs/api/classes/index.BoundNode.html +0 -3
- package/docs/api/classes/index.DigitsValidation.html +0 -10
- package/docs/api/classes/index.FormValidator.html +0 -32
- package/docs/api/classes/index.HttpError.html +0 -13
- package/docs/api/classes/index.LinkedList.html +0 -26
- package/docs/api/classes/index.NavigateRouteEvent.html +0 -76
- package/docs/api/classes/index.Node.html +0 -15
- package/docs/api/classes/index.PageSelectedEvent.html +0 -61
- package/docs/api/classes/index.Pager.html +0 -4
- package/docs/api/classes/index.RangeValidation.html +0 -15
- package/docs/api/classes/index.RelaxError.html +0 -17
- package/docs/api/classes/index.RequiredValidation.html +0 -10
- package/docs/api/classes/index.RouteError.html +0 -11
- package/docs/api/classes/index.RouteGuardError.html +0 -12
- package/docs/api/classes/index.RouteLink.html +0 -779
- package/docs/api/classes/index.RouteTarget.html +0 -788
- package/docs/api/classes/index.SSEClient.html +0 -13
- package/docs/api/classes/index.SSEDataEvent.html +0 -63
- package/docs/api/classes/index.ServiceCollection.html +0 -28
- package/docs/api/classes/index.ServiceContainer.html +0 -24
- package/docs/api/classes/index.SortChangeEvent.html +0 -61
- package/docs/api/classes/index.TableRenderer.html +0 -5
- package/docs/api/classes/index.TableSorter.html +0 -4
- package/docs/api/enums/index.GuardResult.html +0 -9
- package/docs/api/functions/elements.formError.html +0 -6
- package/docs/api/functions/elements.selectOne.html +0 -6
- package/docs/api/functions/i18n.getCurrentLocale.html +0 -3
- package/docs/api/functions/i18n.loadNamespace.html +0 -7
- package/docs/api/functions/i18n.loadNamespaces.html +0 -6
- package/docs/api/functions/i18n.onMissingTranslation.html +0 -7
- package/docs/api/functions/i18n.setLocale.html +0 -7
- package/docs/api/functions/i18n.setMessageFormatter.html +0 -7
- package/docs/api/functions/i18n.t.html +0 -9
- package/docs/api/functions/index.BooleanConverter.html +0 -6
- package/docs/api/functions/index.ContainerService.html +0 -13
- package/docs/api/functions/index.DateConverter.html +0 -11
- package/docs/api/functions/index.Inject.html +0 -16
- package/docs/api/functions/index.NumberConverter.html +0 -5
- package/docs/api/functions/index.RegisterValidator.html +0 -7
- package/docs/api/functions/index.applyPipes.html +0 -17
- package/docs/api/functions/index.asyncHandler.html +0 -11
- package/docs/api/functions/index.capitalizePipe.html +0 -4
- package/docs/api/functions/index.clearPendingNavigations.html +0 -1
- package/docs/api/functions/index.compileTemplate.html +0 -26
- package/docs/api/functions/index.configure.html +0 -5
- package/docs/api/functions/index.createBluePrint.html +0 -1
- package/docs/api/functions/index.createConverterFromDataType.html +0 -4
- package/docs/api/functions/index.createConverterFromInputType.html +0 -5
- package/docs/api/functions/index.createPipeRegistry.html +0 -12
- package/docs/api/functions/index.currencyPipe.html +0 -9
- package/docs/api/functions/index.datePipe.html +0 -9
- package/docs/api/functions/index.daysAgoPipe.html +0 -8
- package/docs/api/functions/index.defaultPipe.html +0 -5
- package/docs/api/functions/index.defineRoutes.html +0 -8
- package/docs/api/functions/index.del.html +0 -8
- package/docs/api/functions/index.findRouteByName.html +0 -5
- package/docs/api/functions/index.findRouteByUrl.html +0 -4
- package/docs/api/functions/index.firstPipe.html +0 -4
- package/docs/api/functions/index.generateSequentialId.html +0 -21
- package/docs/api/functions/index.get.html +0 -9
- package/docs/api/functions/index.getDataConverter.html +0 -11
- package/docs/api/functions/index.getParentComponent.html +0 -18
- package/docs/api/functions/index.getValidator.html +0 -4
- package/docs/api/functions/index.html.html +0 -19
- package/docs/api/functions/index.joinPipe.html +0 -5
- package/docs/api/functions/index.keysPipe.html +0 -4
- package/docs/api/functions/index.lastPipe.html +0 -4
- package/docs/api/functions/index.lowercasePipe.html +0 -4
- package/docs/api/functions/index.mapFormToClass.html +0 -17
- package/docs/api/functions/index.matchRoute.html +0 -5
- package/docs/api/functions/index.navigate.html +0 -8
- package/docs/api/functions/index.onError.html +0 -8
- package/docs/api/functions/index.piecesPipe.html +0 -8
- package/docs/api/functions/index.post.html +0 -9
- package/docs/api/functions/index.printRoutes.html +0 -2
- package/docs/api/functions/index.put.html +0 -9
- package/docs/api/functions/index.readData.html +0 -17
- package/docs/api/functions/index.registerRouteTarget.html +0 -9
- package/docs/api/functions/index.reportError.html +0 -10
- package/docs/api/functions/index.request.html +0 -8
- package/docs/api/functions/index.resolveValue.html +0 -18
- package/docs/api/functions/index.setFetch.html +0 -6
- package/docs/api/functions/index.setFormData.html +0 -17
- package/docs/api/functions/index.shortenPipe.html +0 -5
- package/docs/api/functions/index.startRouting.html +0 -6
- package/docs/api/functions/index.ternaryPipe.html +0 -6
- package/docs/api/functions/index.trimPipe.html +0 -4
- package/docs/api/functions/index.unregisterRouteTarget.html +0 -3
- package/docs/api/functions/index.uppercasePipe.html +0 -4
- package/docs/api/hierarchy.html +0 -1
- package/docs/api/index.html +0 -323
- package/docs/api/interfaces/http.SimpleDataEvent.html +0 -3
- package/docs/api/interfaces/http.WebSocketAbstraction.html +0 -9
- package/docs/api/interfaces/http.WebSocketCodec.html +0 -4
- package/docs/api/interfaces/http.WebSocketOptions.html +0 -20
- package/docs/api/interfaces/index.CompiledTemplate.html +0 -10
- package/docs/api/interfaces/index.DataLoader.html +0 -19
- package/docs/api/interfaces/index.EngineConfig.html +0 -11
- package/docs/api/interfaces/index.ErrorContext.html +0 -4
- package/docs/api/interfaces/index.FormReaderOptions.html +0 -8
- package/docs/api/interfaces/index.HttpOptions.html +0 -16
- package/docs/api/interfaces/index.HttpResponse.html +0 -17
- package/docs/api/interfaces/index.LoadRoute.html +0 -7
- package/docs/api/interfaces/index.NavigateOptions.html +0 -7
- package/docs/api/interfaces/index.PipeRegistry.html +0 -12
- package/docs/api/interfaces/index.RegistrationOptions.html +0 -22
- package/docs/api/interfaces/index.RenderTemplate.html +0 -7
- package/docs/api/interfaces/index.RequestOptions.html +0 -11
- package/docs/api/interfaces/index.Routable.html +0 -10
- package/docs/api/interfaces/index.Route.html +0 -13
- package/docs/api/interfaces/index.RouteGuard.html +0 -2
- package/docs/api/interfaces/index.RouteValue.html +0 -6
- package/docs/api/interfaces/index.SSEOptions.html +0 -24
- package/docs/api/interfaces/index.ValidationContext.html +0 -8
- package/docs/api/interfaces/index.ValidatorOptions.html +0 -14
- package/docs/api/media/Architecture.md +0 -333
- package/docs/api/media/DependencyInjection.md +0 -277
- package/docs/api/media/GettingStarted.md +0 -231
- package/docs/api/media/HttpClient.md +0 -459
- package/docs/api/media/Pipes.md +0 -211
- package/docs/api/media/Routing.md +0 -332
- package/docs/api/media/WhyRelaxjs.md +0 -336
- package/docs/api/media/forms.md +0 -99
- package/docs/api/media/html.md +0 -175
- package/docs/api/media/i18n.md +0 -354
- package/docs/api/media/utilities.md +0 -143
- package/docs/api/media/validation.md +0 -351
- package/docs/api/modules/collections_Index.html +0 -1
- package/docs/api/modules/di.html +0 -1
- package/docs/api/modules/elements.html +0 -1
- package/docs/api/modules/forms.html +0 -1
- package/docs/api/modules/html.html +0 -1
- package/docs/api/modules/http.html +0 -1
- package/docs/api/modules/i18n.html +0 -1
- package/docs/api/modules/index.html +0 -1
- package/docs/api/modules/routing.html +0 -1
- package/docs/api/modules/utils.html +0 -1
- package/docs/api/modules.html +0 -1
- package/docs/api/types/http.WebSocketFactory.html +0 -2
- package/docs/api/types/i18n.MessageFormatter.html +0 -3
- package/docs/api/types/i18n.MissingTranslationHandler.html +0 -1
- package/docs/api/types/index.Constructor.html +0 -7
- package/docs/api/types/index.ConverterFunc.html +0 -2
- package/docs/api/types/index.DataType.html +0 -2
- package/docs/api/types/index.InputType.html +0 -2
- package/docs/api/types/index.PipeFunction.html +0 -6
- package/docs/api/types/index.RouteData.html +0 -1
- package/docs/api/types/index.RouteMatchResult.html +0 -9
- package/docs/api/types/index.RouteParamType.html +0 -1
- package/docs/api/types/index.RouteSegmentType.html +0 -2
- package/docs/api/types/index.SSEEventFactory.html +0 -5
- package/docs/api/types/index.ServiceScope.html +0 -10
- package/docs/api/types/index.SortColumn.html +0 -3
- package/docs/api/variables/i18n.formatICU.html +0 -3
- package/docs/api/variables/index.container.html +0 -6
- package/docs/api/variables/index.defaultPipes.html +0 -6
- package/docs/api/variables/index.internalRoutes.html +0 -1
- package/docs/api/variables/index.serviceCollection.html +0 -6
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Bootstrapping
|
|
2
|
+
=============
|
|
3
|
+
|
|
4
|
+
Relaxjs apps need three subsystems started in the right order: **dependency injection**, **i18n**, and **routing** (if used). Getting the order wrong produces "why is my service undefined?" bugs that are hard to trace.
|
|
5
|
+
|
|
6
|
+
This page shows the safe pattern for `src/main.ts`.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## The rule
|
|
11
|
+
|
|
12
|
+
> Register services **before** any component that uses `@Inject` is constructed.
|
|
13
|
+
> Load i18n namespaces **before** you render anything that calls `t()`.
|
|
14
|
+
> Start routing **last**.
|
|
15
|
+
|
|
16
|
+
A custom element is constructed the moment two things line up: the tag is defined with `customElements.define(...)` **and** the tag appears in the DOM. If DI is not ready at that moment, the component's `@Inject` fields become `undefined`.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Minimal `main.ts`
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// 1. Import service modules first so their @ContainerService decorators run.
|
|
24
|
+
import './services/ApiClient';
|
|
25
|
+
import './services/UserService';
|
|
26
|
+
|
|
27
|
+
// 2. Import component modules. Each file ends with customElements.define(...)
|
|
28
|
+
// but the tags don't render yet unless they are in index.html.
|
|
29
|
+
import './components/HomePage';
|
|
30
|
+
import './components/UserPanel';
|
|
31
|
+
|
|
32
|
+
// 3. Relaxjs subsystems
|
|
33
|
+
import { setLocale, loadNamespaces } from '@relax.js/core/i18n';
|
|
34
|
+
import { defineRoutes, startRouting } from '@relax.js/core/routing';
|
|
35
|
+
|
|
36
|
+
async function bootstrap() {
|
|
37
|
+
// i18n before anything calls t()
|
|
38
|
+
await setLocale(navigator.language || 'en');
|
|
39
|
+
await loadNamespaces(['r-validation']);
|
|
40
|
+
|
|
41
|
+
// Routing last — this is when components start mounting
|
|
42
|
+
defineRoutes([
|
|
43
|
+
{ name: 'home', path: '/', componentTagName: 'home-page' },
|
|
44
|
+
{ name: 'profile', path: '/profile/:id', componentTagName: 'user-panel' },
|
|
45
|
+
]);
|
|
46
|
+
startRouting();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
bootstrap();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The matching `index.html`:
|
|
53
|
+
|
|
54
|
+
```html
|
|
55
|
+
<!DOCTYPE html>
|
|
56
|
+
<html>
|
|
57
|
+
<body>
|
|
58
|
+
<r-route-target></r-route-target>
|
|
59
|
+
<script type="module" src="/src/main.ts"></script>
|
|
60
|
+
</body>
|
|
61
|
+
</html>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Notice the body contains **only** `<r-route-target>`. Page components are created by the router after `startRouting()` runs, so they never construct before DI and i18n are ready.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Why the import order matters
|
|
69
|
+
|
|
70
|
+
TypeScript imports run top-to-bottom. Decorator side effects (registering a service, defining a custom element) happen at import time.
|
|
71
|
+
|
|
72
|
+
If you flip the order:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import './components/UserPanel'; // ❌ custom element defined
|
|
76
|
+
import './services/UserService'; // ❌ service registered AFTER
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
...and `<user-panel>` appears in `index.html`, the browser constructs it before `UserService` is registered. Every `@Inject(UserService)` field resolves to `undefined`.
|
|
80
|
+
|
|
81
|
+
**Always import services before components.** Then import subsystems (i18n, routing) last.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Manual registration (when you can't use decorators)
|
|
86
|
+
|
|
87
|
+
Some services wrap existing instances (config objects, third-party clients). Register them before the `bootstrap()` call:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { serviceCollection } from '@relax.js/core/di';
|
|
91
|
+
|
|
92
|
+
const config = {
|
|
93
|
+
apiUrl: import.meta.env.VITE_API_URL ?? '/api',
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
serviceCollection.register(ConfigService, {
|
|
97
|
+
inject: [],
|
|
98
|
+
instance: config,
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Do this at the top of `main.ts`, before the subsystem imports.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Components that don't use routing
|
|
107
|
+
|
|
108
|
+
If your app uses [Level 1-3 patterns](../GettingStarted.md) (plain components, no router), the same rule applies, just without `startRouting()`:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import './services/ApiClient';
|
|
112
|
+
import './components/AppShell'; // defines <app-shell>
|
|
113
|
+
|
|
114
|
+
import { setLocale, loadNamespaces } from '@relax.js/core/i18n';
|
|
115
|
+
|
|
116
|
+
async function bootstrap() {
|
|
117
|
+
await setLocale('en');
|
|
118
|
+
await loadNamespaces(['r-validation']);
|
|
119
|
+
|
|
120
|
+
// Insert the root component now that everything is ready
|
|
121
|
+
document.body.appendChild(document.createElement('app-shell'));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
bootstrap();
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Key point: the root component is appended **after** async setup, not written directly in `index.html`.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Common mistakes
|
|
132
|
+
|
|
133
|
+
### `@Inject` field is undefined
|
|
134
|
+
|
|
135
|
+
Cause: a component constructed before its service was registered. Fix: move the service import above the component import in `main.ts`.
|
|
136
|
+
|
|
137
|
+
### `t('...')` returns the key unchanged
|
|
138
|
+
|
|
139
|
+
Cause: the namespace wasn't loaded, or the component rendered before `setLocale()` finished. Fix: `await loadNamespaces([...])` before rendering, and re-render on `localechange` if the user can switch locales.
|
|
140
|
+
|
|
141
|
+
### Routing renders a blank page
|
|
142
|
+
|
|
143
|
+
Cause: `startRouting()` ran before `defineRoutes(...)`. Fix: always call `defineRoutes` first.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Summary
|
|
148
|
+
|
|
149
|
+
1. Import service modules (side-effect imports run `@ContainerService`)
|
|
150
|
+
2. Import component modules (`customElements.define`)
|
|
151
|
+
3. `await setLocale(...)` and `await loadNamespaces(...)`
|
|
152
|
+
4. `defineRoutes(...)` then `startRouting()`
|
|
153
|
+
|
|
154
|
+
If you follow this order every time, components never see an undefined service or a missing translation.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
Build and Deploy
|
|
2
|
+
================
|
|
3
|
+
|
|
4
|
+
Relaxjs apps build and deploy like any Vite + TypeScript project. This page covers the two things beginners get wrong: the **base path** setting and the **SPA fallback** on the hosting side.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Building for production
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm run build
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Vite outputs static files to `dist/`:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
dist/
|
|
18
|
+
├── index.html
|
|
19
|
+
├── assets/
|
|
20
|
+
│ ├── index-a1b2c3.js
|
|
21
|
+
│ └── index-d4e5f6.css
|
|
22
|
+
└── favicon.ico
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
All paths inside `dist/` are relative to the root of your domain by default. If you are deploying to `https://example.com/`, you can upload `dist/` as-is.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Deploying to a subpath
|
|
30
|
+
|
|
31
|
+
If the app lives at `https://example.com/my-app/` instead of the domain root, set `base` in `vite.config.ts`:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { defineConfig } from 'vite';
|
|
35
|
+
|
|
36
|
+
export default defineConfig({
|
|
37
|
+
base: '/my-app/',
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The trailing slash matters. With this set, Vite rewrites every asset URL in `index.html` to start with `/my-app/`, and relaxjs routing respects it for `<r-link>` and `navigate(...)`.
|
|
42
|
+
|
|
43
|
+
If you deploy to both a subpath (staging) and the root (production), read the base from an environment variable:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
export default defineConfig({
|
|
47
|
+
base: process.env.VITE_BASE ?? '/',
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## SPA fallback (the most common deployment bug)
|
|
54
|
+
|
|
55
|
+
Relaxjs routing uses the History API. A route like `/profile/42` is a URL the browser shows, but it is **not** a file on disk. When the user reloads that URL, the server looks for `/profile/42/index.html`, doesn't find it, and returns a 404.
|
|
56
|
+
|
|
57
|
+
The fix is to tell the server: *for any URL that is not a real file, serve `index.html` instead*. How to configure this depends on the host.
|
|
58
|
+
|
|
59
|
+
### Nginx
|
|
60
|
+
|
|
61
|
+
```nginx
|
|
62
|
+
location / {
|
|
63
|
+
try_files $uri $uri/ /index.html;
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Apache (`.htaccess`)
|
|
68
|
+
|
|
69
|
+
```apache
|
|
70
|
+
RewriteEngine On
|
|
71
|
+
RewriteBase /
|
|
72
|
+
RewriteRule ^index\.html$ - [L]
|
|
73
|
+
RewriteCond %{REQUEST_FILENAME} !-f
|
|
74
|
+
RewriteCond %{REQUEST_FILENAME} !-d
|
|
75
|
+
RewriteRule . /index.html [L]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Netlify (`netlify.toml` or `_redirects`)
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
/* /index.html 200
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Vercel (`vercel.json`)
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"rewrites": [
|
|
89
|
+
{ "source": "/(.*)", "destination": "/index.html" }
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Static file servers (local testing)
|
|
95
|
+
|
|
96
|
+
For testing the built app locally:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npx serve dist --single
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The `--single` flag enables SPA fallback.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Multiple HTML layouts
|
|
107
|
+
|
|
108
|
+
[Level 7](../GettingStarted.md) of the Getting Started guide introduces layouts: separate HTML files for distinct UI structures (for example, `index.html` for the authenticated app and `public.html` for login/register pages).
|
|
109
|
+
|
|
110
|
+
Vite builds every HTML file listed in the `rollupOptions.input` config. Add each layout:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { defineConfig } from 'vite';
|
|
114
|
+
import { resolve } from 'path';
|
|
115
|
+
|
|
116
|
+
export default defineConfig({
|
|
117
|
+
build: {
|
|
118
|
+
rollupOptions: {
|
|
119
|
+
input: {
|
|
120
|
+
main: resolve(__dirname, 'index.html'),
|
|
121
|
+
public: resolve(__dirname, 'public.html'),
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The SPA fallback must still point at the correct layout. With layouts, the rewrite rules get more nuanced: only fall back to `index.html` for authenticated routes, and to `public.html` for public routes. Most hosts handle this with path-prefixed rewrite rules:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
/login → /public.html
|
|
132
|
+
/register → /public.html
|
|
133
|
+
/* → /index.html
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Environment variables
|
|
139
|
+
|
|
140
|
+
Vite exposes variables prefixed with `VITE_` as `import.meta.env.VITE_*` at build time:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
# .env.production
|
|
144
|
+
VITE_API_URL=https://api.example.com
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
const apiUrl = import.meta.env.VITE_API_URL;
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Do not put secrets here. Everything in `import.meta.env` ends up in the public bundle.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Checklist before deploying
|
|
156
|
+
|
|
157
|
+
- [ ] `npm run build` succeeds with no warnings
|
|
158
|
+
- [ ] `base` in `vite.config.ts` matches the deployed URL path
|
|
159
|
+
- [ ] SPA fallback is configured on the host
|
|
160
|
+
- [ ] Environment variables are set for the target (dev, staging, prod)
|
|
161
|
+
- [ ] `index.html` references assets via relative URLs (Vite handles this automatically when `base` is correct)
|
|
162
|
+
- [ ] `public/` contains only files that need fixed URLs
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Troubleshooting
|
|
167
|
+
|
|
168
|
+
### Assets load from the wrong path
|
|
169
|
+
|
|
170
|
+
The `base` config does not match the deploy path. For `https://example.com/my-app/`, `base` must be `/my-app/` with a trailing slash.
|
|
171
|
+
|
|
172
|
+
### Reloading a route shows 404
|
|
173
|
+
|
|
174
|
+
The SPA fallback is missing. Configure the host to serve `index.html` for unknown paths.
|
|
175
|
+
|
|
176
|
+
### Blank page in production, works in dev
|
|
177
|
+
|
|
178
|
+
Check the browser console. Common causes:
|
|
179
|
+
- A service was imported *after* a component that injects it
|
|
180
|
+
- `import.meta.env.VITE_*` is missing (undefined at runtime)
|
|
181
|
+
- A translation namespace failed to load because the JSON file was not copied to `dist/`
|
|
182
|
+
|
|
183
|
+
Translation files inside `src/i18n/locales/` are loaded via `fetch()` at runtime, so they need to be reachable from the browser. Either move them to `public/i18n/locales/` or import each JSON statically if your i18n loader is configured for it.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Project Structure
|
|
2
|
+
=================
|
|
3
|
+
|
|
4
|
+
Relaxjs doesn't force a folder layout, but the one below works well for Vite projects and matches how the examples in this documentation are written.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Recommended layout
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
my-app/
|
|
12
|
+
├── index.html
|
|
13
|
+
├── package.json
|
|
14
|
+
├── tsconfig.json
|
|
15
|
+
├── vite.config.ts
|
|
16
|
+
├── public/
|
|
17
|
+
│ └── favicon.ico
|
|
18
|
+
└── src/
|
|
19
|
+
├── main.ts
|
|
20
|
+
├── styles.css
|
|
21
|
+
├── components/
|
|
22
|
+
│ ├── HomePage.ts
|
|
23
|
+
│ ├── UserPanel.ts
|
|
24
|
+
│ └── AppShell.ts
|
|
25
|
+
├── services/
|
|
26
|
+
│ ├── ApiClient.ts
|
|
27
|
+
│ └── UserService.ts
|
|
28
|
+
└── i18n/
|
|
29
|
+
└── locales/
|
|
30
|
+
├── en/
|
|
31
|
+
│ ├── r-common.json
|
|
32
|
+
│ └── r-validation.json
|
|
33
|
+
└── sv/
|
|
34
|
+
├── r-common.json
|
|
35
|
+
└── r-validation.json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Each folder has one job. Beginners can skip folders they don't need yet — a small app may only have `src/main.ts` and `src/components/`.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## `index.html`
|
|
43
|
+
|
|
44
|
+
Vite treats `index.html` as the project entry. It loads `src/main.ts` as an ES module:
|
|
45
|
+
|
|
46
|
+
```html
|
|
47
|
+
<!DOCTYPE html>
|
|
48
|
+
<html>
|
|
49
|
+
<head>
|
|
50
|
+
<meta charset="UTF-8" />
|
|
51
|
+
<title>My App</title>
|
|
52
|
+
<link rel="stylesheet" href="/src/styles.css" />
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
<r-route-target></r-route-target>
|
|
56
|
+
<script type="module" src="/src/main.ts"></script>
|
|
57
|
+
</body>
|
|
58
|
+
</html>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Keep the body minimal. Put page content inside components, not in the HTML file.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## `src/main.ts`
|
|
66
|
+
|
|
67
|
+
The bootstrap file. It imports services, components, and starts i18n and routing. See [Bootstrapping](bootstrapping.md) for the exact order and a full example.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## `src/components/`
|
|
72
|
+
|
|
73
|
+
One file per custom element. Use **PascalCase filenames** and **kebab-case tag names**:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// src/components/UserPanel.ts
|
|
77
|
+
import { html } from '@relax.js/core/html';
|
|
78
|
+
|
|
79
|
+
export class UserPanel extends HTMLElement {
|
|
80
|
+
connectedCallback() {
|
|
81
|
+
const result = html`<h2>Profile</h2>`({});
|
|
82
|
+
this.appendChild(result.fragment);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
customElements.define('user-panel', UserPanel);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The file ends with `customElements.define(...)` so importing the file is enough to register the tag.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## `src/services/`
|
|
94
|
+
|
|
95
|
+
One file per injectable service. Services use `@ContainerService`:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// src/services/ApiClient.ts
|
|
99
|
+
import { ContainerService } from '@relax.js/core/di';
|
|
100
|
+
|
|
101
|
+
@ContainerService()
|
|
102
|
+
export class ApiClient {
|
|
103
|
+
async get(path: string) {
|
|
104
|
+
const res = await fetch(path);
|
|
105
|
+
return res.json();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Services are plain classes. They don't extend `HTMLElement`.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## `src/i18n/locales/`
|
|
115
|
+
|
|
116
|
+
Translation files, one JSON per locale per namespace:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
src/i18n/locales/en/r-common.json
|
|
120
|
+
src/i18n/locales/en/r-validation.json
|
|
121
|
+
src/i18n/locales/sv/r-common.json
|
|
122
|
+
src/i18n/locales/sv/r-validation.json
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The structure is fixed: `{locale}/{namespace}.json`. See [i18n](../i18n/i18n.md) for the file format.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## `public/`
|
|
130
|
+
|
|
131
|
+
Static files served at the web root. Use it for favicons, robots.txt, and assets that need stable URLs. Vite copies everything in `public/` to the build output unchanged.
|
|
132
|
+
|
|
133
|
+
Prefer imports for assets used by code:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import logoUrl from './assets/logo.svg';
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Use `public/` only when the file must have a known fixed URL.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## `src/styles.css`
|
|
144
|
+
|
|
145
|
+
Global styles live in one file, imported from `index.html`. Per-component styles can be inline `<style>` tags inside each component's template, or a sibling `.css` file imported by the component module.
|
|
146
|
+
|
|
147
|
+
Relaxjs does not ship reset styles. Style semantic HTML directly and use the `.form` class to scope form layouts. See [Forms](../forms/forms.md).
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Scaling up
|
|
152
|
+
|
|
153
|
+
As the app grows, split `components/` by feature:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
src/components/
|
|
157
|
+
├── home/
|
|
158
|
+
│ ├── HomePage.ts
|
|
159
|
+
│ └── WelcomeBanner.ts
|
|
160
|
+
├── profile/
|
|
161
|
+
│ ├── UserPanel.ts
|
|
162
|
+
│ └── EditForm.ts
|
|
163
|
+
└── shared/
|
|
164
|
+
├── AppShell.ts
|
|
165
|
+
└── NavBar.ts
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Keep `services/` flat until you have more than ten services, then group the same way.
|
|
169
|
+
|
|
170
|
+
Avoid a `utils/` dump folder. If a helper is shared by two features, move it into `src/shared/` with a clear filename.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
Vite Setup
|
|
2
|
+
==========
|
|
3
|
+
|
|
4
|
+
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.
|
|
5
|
+
|
|
6
|
+
This page shows how to set up a Vite + TypeScript project so that relaxjs decorators like `@RegisterValidator` work out of the box.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Requirements
|
|
11
|
+
|
|
12
|
+
| Tool | Minimum version | Why |
|
|
13
|
+
|------------|-----------------|-----|
|
|
14
|
+
| Node.js | 18 or newer | Required by Vite 5+ |
|
|
15
|
+
| TypeScript | 5.0 or newer | Stage 3 decorator support |
|
|
16
|
+
| Vite | 5.3 or newer | Bundled esbuild transforms Stage 3 decorators |
|
|
17
|
+
|
|
18
|
+
If you created your project with `npm create vite@latest` recently, you already meet these.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Step 1: Create a Vite project
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm create vite@latest my-app -- --template vanilla-ts
|
|
26
|
+
cd my-app
|
|
27
|
+
npm install
|
|
28
|
+
npm install @relax.js/core
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The `vanilla-ts` template gives you TypeScript without a framework, which is what relaxjs is designed for.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Step 2: Configure `tsconfig.json`
|
|
36
|
+
|
|
37
|
+
Open `tsconfig.json` and make sure these options are set:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"compilerOptions": {
|
|
42
|
+
"target": "ES2022",
|
|
43
|
+
"module": "ESNext",
|
|
44
|
+
"moduleResolution": "Bundler",
|
|
45
|
+
"experimentalDecorators": false,
|
|
46
|
+
"useDefineForClassFields": true,
|
|
47
|
+
"strict": true,
|
|
48
|
+
"skipLibCheck": true,
|
|
49
|
+
"lib": ["ES2022", "DOM"]
|
|
50
|
+
},
|
|
51
|
+
"include": ["src"]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The important lines for decorators:
|
|
56
|
+
|
|
57
|
+
- **`"experimentalDecorators": false`** — this is the switch. When `false` (or omitted), TypeScript uses the new Stage 3 decorators. When `true`, it uses the old legacy decorators, which are not compatible with relaxjs.
|
|
58
|
+
- **`"target": "ES2022"`** — any value from `ES2022` upward works. Do not pick a lower target; esbuild needs a modern target to emit clean decorator output.
|
|
59
|
+
- **`"useDefineForClassFields": true`** — required so class fields behave as the standard specifies. Relaxjs form components rely on this.
|
|
60
|
+
|
|
61
|
+
You do **not** need `emitDecoratorMetadata`. That option only applies to legacy decorators and is ignored here.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Step 3: `vite.config.ts`
|
|
66
|
+
|
|
67
|
+
A minimal config is enough. Vite picks up your `tsconfig.json` automatically.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { defineConfig } from 'vite';
|
|
71
|
+
|
|
72
|
+
export default defineConfig({
|
|
73
|
+
server: {
|
|
74
|
+
port: 3000,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
No plugins are required for decorators. Vite's built-in esbuild handles the transform.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Step 4: Try a relaxjs decorator
|
|
84
|
+
|
|
85
|
+
Create `src/main.ts`:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { RegisterValidator, ValidationContext } from '@relax.js/core/forms';
|
|
89
|
+
|
|
90
|
+
@RegisterValidator('even')
|
|
91
|
+
class EvenValidation {
|
|
92
|
+
validate(value: string, context: ValidationContext) {
|
|
93
|
+
const num = parseInt(value, 10);
|
|
94
|
+
if (isNaN(num) || num % 2 !== 0) {
|
|
95
|
+
context.addError('Value must be an even number');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log('EvenValidation registered');
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Run the dev server:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm run dev
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Open the browser console. If you see `EvenValidation registered` and no errors, your decorator setup is working.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Troubleshooting
|
|
114
|
+
|
|
115
|
+
### "Decorators are not valid here" or a parse error
|
|
116
|
+
|
|
117
|
+
Your TypeScript version is older than 5.0. Update it:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm install -D typescript@latest
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Then restart your editor so it picks up the new version.
|
|
124
|
+
|
|
125
|
+
### Code compiles but decorator never runs
|
|
126
|
+
|
|
127
|
+
You probably have `"experimentalDecorators": true` left over from an older template. Set it to `false` and restart the dev server.
|
|
128
|
+
|
|
129
|
+
### Syntax error at runtime, only in the browser
|
|
130
|
+
|
|
131
|
+
Your Vite (and therefore esbuild) version is too old to transform Stage 3 decorators. Upgrade:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npm install -D vite@latest
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Vite 5.3 and newer bundle an esbuild version that handles the new decorator syntax.
|
|
138
|
+
|
|
139
|
+
### Error about `ElementInternals` or missing DOM types
|
|
140
|
+
|
|
141
|
+
Make sure `"lib"` includes `"DOM"` in `tsconfig.json`. Without it, TypeScript does not know about browser APIs that relaxjs uses.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Vitest (optional)
|
|
146
|
+
|
|
147
|
+
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.
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npm install -D vitest jsdom
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
// vitest.config.ts
|
|
155
|
+
import { defineConfig } from 'vitest/config';
|
|
156
|
+
|
|
157
|
+
export default defineConfig({
|
|
158
|
+
test: {
|
|
159
|
+
environment: 'jsdom',
|
|
160
|
+
globals: true,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Summary
|
|
168
|
+
|
|
169
|
+
For relaxjs decorators to work, you need three things:
|
|
170
|
+
|
|
171
|
+
1. TypeScript 5.0+
|
|
172
|
+
2. `"experimentalDecorators": false` in `tsconfig.json`
|
|
173
|
+
3. Vite 5.3+ (modern esbuild)
|
|
174
|
+
|
|
175
|
+
With those in place, decorators like `@RegisterValidator` work without any plugin or extra setup.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@relax.js/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Ship faster with less code. Web Component library with routing, forms, DI, templating, and i18n. No virtual DOM, no build magic, no surprise re-renders.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"test:coverage": "vitest run --coverage",
|
|
12
12
|
"test:ui": "vitest --ui",
|
|
13
13
|
"buildOld": "tsc",
|
|
14
|
-
"build": "tsc --emitDeclarationOnly && node build.js
|
|
14
|
+
"build": "tsc --emitDeclarationOnly && node build.js",
|
|
15
|
+
"docs": "npx typedoc",
|
|
15
16
|
"yalc:publish": "yalc publish && yalc push",
|
|
16
17
|
"yalc:watch": "nodemon --ext ts,js,json --exec \"npm run yalc:publish\"",
|
|
17
18
|
"bp": "npm run build && npm run yalc:publish",
|
package/docs/api/.nojekyll
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
window.hierarchyData = "eJyVjr0KwjAUhd/lzGlFoeWSXdxdpUNorjSYJnCTgFDy7hI6FFzE6cD54TsbJMacoB800qQg/PQ8ZxdDgt5AIzUJZmVo3GPJfBWJAoWXCxb6fCGFIh4aszcpcTq5YPndH91+yauH2nNo5GS7Nu52Q2FenLfCoZ2gYaoKRMM39laM2H/Yx+DHgVrrB1OXV70="
|