@alepha/react 0.14.4 → 0.15.1
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/README.md +10 -0
- package/dist/auth/index.browser.js +603 -242
- package/dist/auth/index.browser.js.map +1 -1
- package/dist/auth/index.d.ts +2 -2
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1317 -952
- package/dist/auth/index.js.map +1 -1
- package/dist/core/index.d.ts +17 -17
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +20 -20
- package/dist/core/index.js.map +1 -1
- package/dist/form/index.d.ts +9 -10
- package/dist/form/index.d.ts.map +1 -1
- package/dist/form/index.js +15 -15
- package/dist/form/index.js.map +1 -1
- package/dist/head/index.browser.js +20 -0
- package/dist/head/index.browser.js.map +1 -1
- package/dist/head/index.d.ts +62 -64
- package/dist/head/index.d.ts.map +1 -1
- package/dist/head/index.js +20 -0
- package/dist/head/index.js.map +1 -1
- package/dist/i18n/index.d.ts +9 -9
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js.map +1 -1
- package/dist/router/index.browser.js +605 -244
- package/dist/router/index.browser.js.map +1 -1
- package/dist/router/index.d.ts +100 -111
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +1317 -952
- package/dist/router/index.js.map +1 -1
- package/dist/websocket/index.d.ts +0 -1
- package/dist/websocket/index.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/auth/__tests__/$auth.spec.ts +164 -150
- package/src/auth/index.ts +9 -3
- package/src/auth/services/ReactAuth.ts +15 -5
- package/src/core/hooks/useAction.ts +1 -2
- package/src/core/index.ts +4 -4
- package/src/form/errors/FormValidationError.ts +4 -6
- package/src/form/hooks/useFormState.ts +1 -1
- package/src/form/index.ts +1 -1
- package/src/form/services/FormModel.ts +31 -25
- package/src/head/helpers/SeoExpander.ts +2 -1
- package/src/head/hooks/useHead.spec.tsx +2 -2
- package/src/head/index.browser.ts +2 -2
- package/src/head/index.ts +4 -4
- package/src/head/interfaces/Head.ts +15 -3
- package/src/head/primitives/$head.ts +2 -5
- package/src/head/providers/BrowserHeadProvider.ts +55 -0
- package/src/head/providers/HeadProvider.ts +4 -1
- package/src/i18n/__tests__/integration.spec.tsx +1 -1
- package/src/i18n/components/Localize.spec.tsx +2 -2
- package/src/i18n/hooks/useI18n.browser.spec.tsx +2 -2
- package/src/i18n/index.ts +1 -1
- package/src/i18n/primitives/$dictionary.ts +1 -1
- package/src/i18n/providers/I18nProvider.spec.ts +1 -1
- package/src/i18n/providers/I18nProvider.ts +1 -1
- package/src/router/__tests__/page-head-browser.browser.spec.ts +5 -1
- package/src/router/__tests__/page-head.spec.ts +11 -7
- package/src/router/__tests__/seo-head.spec.ts +7 -3
- package/src/router/atoms/ssrManifestAtom.ts +2 -11
- package/src/router/components/ErrorViewer.tsx +626 -167
- package/src/router/components/Link.tsx +4 -2
- package/src/router/components/NestedView.tsx +7 -9
- package/src/router/components/NotFound.tsx +2 -2
- package/src/router/hooks/useQueryParams.ts +1 -1
- package/src/router/hooks/useRouter.ts +1 -1
- package/src/router/hooks/useRouterState.ts +1 -1
- package/src/router/index.browser.ts +10 -11
- package/src/router/index.shared.ts +7 -7
- package/src/router/index.ts +10 -7
- package/src/router/primitives/$page.browser.spec.tsx +6 -1
- package/src/router/primitives/$page.spec.tsx +7 -1
- package/src/router/primitives/$page.ts +5 -9
- package/src/router/providers/ReactBrowserProvider.ts +17 -6
- package/src/router/providers/ReactBrowserRouterProvider.ts +1 -1
- package/src/router/providers/ReactPageProvider.ts +4 -3
- package/src/router/providers/ReactServerProvider.ts +32 -50
- package/src/router/providers/ReactServerTemplateProvider.ts +336 -155
- package/src/router/providers/SSRManifestProvider.ts +17 -60
- package/src/router/services/ReactPageService.ts +4 -1
- package/src/router/services/ReactRouter.ts +6 -5
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { AlephaContext } from "@alepha/react";
|
|
2
|
-
import type { Head } from "../index.ts";
|
|
3
|
-
import { useHead } from "../index.ts";
|
|
4
2
|
import { render } from "@testing-library/react";
|
|
5
3
|
import { Alepha } from "alepha";
|
|
6
4
|
import { act, type ReactNode } from "react";
|
|
7
5
|
import { beforeEach, describe, it, vi } from "vitest";
|
|
6
|
+
import type { Head } from "../index.ts";
|
|
7
|
+
import { useHead } from "../index.ts";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @vitest-environment jsdom
|
|
@@ -5,10 +5,10 @@ import { BrowserHeadProvider } from "./providers/BrowserHeadProvider.ts";
|
|
|
5
5
|
|
|
6
6
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
7
7
|
|
|
8
|
-
export * from "./
|
|
8
|
+
export * from "./helpers/SeoExpander.ts";
|
|
9
9
|
export * from "./hooks/useHead.ts";
|
|
10
10
|
export * from "./interfaces/Head.ts";
|
|
11
|
-
export * from "./
|
|
11
|
+
export * from "./primitives/$head.ts";
|
|
12
12
|
export * from "./providers/BrowserHeadProvider.ts";
|
|
13
13
|
|
|
14
14
|
// ---------------------------------------------------------------------------------------------------------------------
|
package/src/head/index.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { AlephaReact } from "@alepha/react";
|
|
2
2
|
import { $module } from "alepha";
|
|
3
|
+
import { SeoExpander } from "./helpers/SeoExpander.ts";
|
|
3
4
|
import { $head } from "./primitives/$head.ts";
|
|
4
5
|
import { BrowserHeadProvider } from "./providers/BrowserHeadProvider.ts";
|
|
5
6
|
import { HeadProvider } from "./providers/HeadProvider.ts";
|
|
6
|
-
import { SeoExpander } from "./helpers/SeoExpander.ts";
|
|
7
7
|
import { ServerHeadProvider } from "./providers/ServerHeadProvider.ts";
|
|
8
8
|
|
|
9
9
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
10
10
|
|
|
11
|
-
export * from "./
|
|
11
|
+
export * from "./helpers/SeoExpander.ts";
|
|
12
12
|
export * from "./hooks/useHead.ts";
|
|
13
13
|
export * from "./interfaces/Head.ts";
|
|
14
|
-
export * from "./
|
|
15
|
-
export * from "./providers/ServerHeadProvider.ts";
|
|
14
|
+
export * from "./primitives/$head.ts";
|
|
16
15
|
export * from "./providers/BrowserHeadProvider.ts";
|
|
16
|
+
export * from "./providers/ServerHeadProvider.ts";
|
|
17
17
|
|
|
18
18
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
19
19
|
|
|
@@ -74,9 +74,21 @@ export interface SimpleHead {
|
|
|
74
74
|
/** Meta tags - supports both name and property attributes */
|
|
75
75
|
meta?: Array<HeadMeta>;
|
|
76
76
|
/** Link tags (e.g., stylesheets, preload, canonical) */
|
|
77
|
-
link?: Array<{
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
link?: Array<{
|
|
78
|
+
rel: string;
|
|
79
|
+
href: string;
|
|
80
|
+
type?: string;
|
|
81
|
+
as?: string;
|
|
82
|
+
crossorigin?: string;
|
|
83
|
+
}>;
|
|
84
|
+
/** Script tags - string for inline code, or object with attributes */
|
|
85
|
+
script?: Array<
|
|
86
|
+
| string
|
|
87
|
+
| (Record<string, string | boolean | undefined> & {
|
|
88
|
+
/** Inline JavaScript code */
|
|
89
|
+
content?: string;
|
|
90
|
+
})
|
|
91
|
+
>;
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
export interface HeadMeta {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $inject, createPrimitive,
|
|
1
|
+
import { $inject, createPrimitive, KIND, Primitive } from "alepha";
|
|
2
2
|
import type { Head } from "../interfaces/Head.ts";
|
|
3
3
|
import { HeadProvider } from "../providers/HeadProvider.ts";
|
|
4
4
|
|
|
@@ -18,10 +18,7 @@ export type HeadPrimitiveOptions = Head | (() => Head);
|
|
|
18
18
|
export class HeadPrimitive extends Primitive<HeadPrimitiveOptions> {
|
|
19
19
|
protected readonly provider = $inject(HeadProvider);
|
|
20
20
|
protected onInit() {
|
|
21
|
-
this.provider.global = [
|
|
22
|
-
...(this.provider.global ?? []),
|
|
23
|
-
this.options,
|
|
24
|
-
];
|
|
21
|
+
this.provider.global = [...(this.provider.global ?? []), this.options];
|
|
25
22
|
}
|
|
26
23
|
}
|
|
27
24
|
|
|
@@ -116,10 +116,65 @@ export class BrowserHeadProvider {
|
|
|
116
116
|
link = document.createElement("link");
|
|
117
117
|
link.setAttribute("rel", rel);
|
|
118
118
|
link.setAttribute("href", href);
|
|
119
|
+
if (it.type) {
|
|
120
|
+
link.setAttribute("type", it.type);
|
|
121
|
+
}
|
|
122
|
+
if (it.as) {
|
|
123
|
+
link.setAttribute("as", it.as);
|
|
124
|
+
}
|
|
125
|
+
if (it.crossorigin != null) {
|
|
126
|
+
link.setAttribute("crossorigin", "");
|
|
127
|
+
}
|
|
119
128
|
document.head.appendChild(link);
|
|
120
129
|
}
|
|
121
130
|
}
|
|
122
131
|
}
|
|
132
|
+
|
|
133
|
+
if (head.script) {
|
|
134
|
+
for (const it of head.script) {
|
|
135
|
+
this.renderScriptTag(document, it);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
protected renderScriptTag(
|
|
141
|
+
document: Document,
|
|
142
|
+
script:
|
|
143
|
+
| string
|
|
144
|
+
| (Record<string, string | boolean | undefined> & { content?: string }),
|
|
145
|
+
): void {
|
|
146
|
+
const el = document.createElement("script");
|
|
147
|
+
|
|
148
|
+
// Handle plain string as inline script
|
|
149
|
+
if (typeof script === "string") {
|
|
150
|
+
el.textContent = script;
|
|
151
|
+
document.head.appendChild(el);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const { content, ...attrs } = script;
|
|
156
|
+
|
|
157
|
+
// For scripts with src, check if already exists
|
|
158
|
+
if (attrs.src) {
|
|
159
|
+
const existing = document.querySelector(`script[src="${attrs.src}"]`);
|
|
160
|
+
if (existing) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
166
|
+
if (value === true) {
|
|
167
|
+
el.setAttribute(key, "");
|
|
168
|
+
} else if (value !== undefined && value !== false) {
|
|
169
|
+
el.setAttribute(key, String(value));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (content) {
|
|
174
|
+
el.textContent = content;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
document.head.appendChild(el);
|
|
123
178
|
}
|
|
124
179
|
|
|
125
180
|
protected renderMetaTag(document: Document, meta: HeadMeta): void {
|
|
@@ -136,7 +136,10 @@ export class HeadProvider {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
if (head.script) {
|
|
139
|
-
state.head.script = [
|
|
139
|
+
state.head.script = [
|
|
140
|
+
...(state.head.script ?? []),
|
|
141
|
+
...(head.script ?? []),
|
|
142
|
+
];
|
|
140
143
|
}
|
|
141
144
|
}
|
|
142
145
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Alepha } from "alepha";
|
|
2
2
|
import { describe, test } from "vitest";
|
|
3
|
-
import { $dictionary } from "../primitives/$dictionary.ts";
|
|
4
3
|
import { AlephaReactI18n } from "../index.ts";
|
|
4
|
+
import { $dictionary } from "../primitives/$dictionary.ts";
|
|
5
5
|
import { I18nProvider } from "../providers/I18nProvider.ts";
|
|
6
6
|
|
|
7
7
|
describe("I18n Integration Tests", () => {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { AlephaContext } from "@alepha/react";
|
|
2
|
-
import {
|
|
3
|
-
import { Alepha, t } from "alepha";
|
|
2
|
+
import { Alepha } from "alepha";
|
|
4
3
|
import { DateTimeProvider } from "alepha/datetime";
|
|
5
4
|
import type { ReactNode } from "react";
|
|
6
5
|
import { renderToString } from "react-dom/server";
|
|
7
6
|
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { I18nProvider, Localize } from "../index.ts";
|
|
8
8
|
|
|
9
9
|
describe("<Localize/>", () => {
|
|
10
10
|
const setup = () => {
|
|
@@ -4,10 +4,10 @@ import { Alepha } from "alepha";
|
|
|
4
4
|
import type { ReactNode } from "react";
|
|
5
5
|
import { act } from "react";
|
|
6
6
|
import { describe, test } from "vitest";
|
|
7
|
-
import { $dictionary } from "../primitives/$dictionary.ts";
|
|
8
|
-
import { useI18n } from "./useI18n.ts";
|
|
9
7
|
import { AlephaReactI18n } from "../index.ts";
|
|
8
|
+
import { $dictionary } from "../primitives/$dictionary.ts";
|
|
10
9
|
import { I18nProvider } from "../providers/I18nProvider.ts";
|
|
10
|
+
import { useI18n } from "./useI18n.ts";
|
|
11
11
|
|
|
12
12
|
describe("useI18n hook", () => {
|
|
13
13
|
const renderWithAlepha = (alepha: Alepha, element: ReactNode) => {
|
package/src/i18n/index.ts
CHANGED
|
@@ -6,8 +6,8 @@ import { I18nProvider } from "./providers/I18nProvider.ts";
|
|
|
6
6
|
|
|
7
7
|
export type { LocalizeProps } from "./components/Localize.tsx";
|
|
8
8
|
export { default as Localize } from "./components/Localize.tsx";
|
|
9
|
-
export * from "./primitives/$dictionary.ts";
|
|
10
9
|
export * from "./hooks/useI18n.ts";
|
|
10
|
+
export * from "./primitives/$dictionary.ts";
|
|
11
11
|
export * from "./providers/I18nProvider.ts";
|
|
12
12
|
|
|
13
13
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Alepha } from "alepha";
|
|
2
2
|
import { describe, test } from "vitest";
|
|
3
|
-
import { $dictionary } from "../primitives/$dictionary.ts";
|
|
4
3
|
import { AlephaReactI18n } from "../index.ts";
|
|
4
|
+
import { $dictionary } from "../primitives/$dictionary.ts";
|
|
5
5
|
import { I18nProvider } from "./I18nProvider.ts";
|
|
6
6
|
|
|
7
7
|
describe("I18nProvider", () => {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
AlephaReactHead,
|
|
3
|
+
BrowserHeadProvider,
|
|
4
|
+
type Head,
|
|
5
|
+
} from "@alepha/react/head";
|
|
2
6
|
import { Alepha } from "alepha";
|
|
3
7
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
4
8
|
import { $page } from "../index.browser.ts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { $head, AlephaReactHead } from "@alepha/react/head";
|
|
1
2
|
import { Alepha } from "alepha";
|
|
2
3
|
import { describe, it } from "vitest";
|
|
3
|
-
import { $head, AlephaReactHead } from "@alepha/react/head";
|
|
4
4
|
import { $page } from "../index.ts";
|
|
5
5
|
|
|
6
6
|
class App {
|
|
@@ -31,14 +31,18 @@ describe("PageHead", () => {
|
|
|
31
31
|
const result = await a.hello.render({ html: true, hydration: false });
|
|
32
32
|
|
|
33
33
|
// Check key parts of the HTML output (streaming adds newlines between sections)
|
|
34
|
-
expect(result.html).toContain(
|
|
34
|
+
expect(result.html).toContain("<!DOCTYPE html>");
|
|
35
35
|
expect(result.html).toContain('<html lang="fr" x-data-custom="ok">');
|
|
36
|
-
expect(result.html).toContain(
|
|
37
|
-
expect(result.html).toContain(
|
|
38
|
-
|
|
36
|
+
expect(result.html).toContain("<title>Hello World</title>");
|
|
37
|
+
expect(result.html).toContain(
|
|
38
|
+
'<meta name="description" content="This is a test page.">',
|
|
39
|
+
);
|
|
40
|
+
expect(result.html).toContain(
|
|
41
|
+
'<meta name="keywords" content="test, alepha, react">',
|
|
42
|
+
);
|
|
39
43
|
expect(result.html).toContain('<body class="hello-world">');
|
|
40
44
|
expect(result.html).toContain('<div id="root">');
|
|
41
|
-
expect(result.html).toContain(
|
|
42
|
-
expect(result.html).toContain(
|
|
45
|
+
expect(result.html).toContain("</body>");
|
|
46
|
+
expect(result.html).toContain("</html>");
|
|
43
47
|
});
|
|
44
48
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { $head, AlephaReactHead } from "@alepha/react/head";
|
|
1
2
|
import { Alepha } from "alepha";
|
|
2
3
|
import { describe, it } from "vitest";
|
|
3
|
-
import { $head, AlephaReactHead } from "@alepha/react/head";
|
|
4
4
|
import { $page } from "../index.ts";
|
|
5
5
|
|
|
6
6
|
class App {
|
|
@@ -49,7 +49,9 @@ describe("SEO Head", () => {
|
|
|
49
49
|
expect(result.html).toContain(
|
|
50
50
|
'<meta property="og:url" content="https://alepha.dev/">',
|
|
51
51
|
);
|
|
52
|
-
expect(result.html).toContain(
|
|
52
|
+
expect(result.html).toContain(
|
|
53
|
+
'<meta property="og:type" content="website">',
|
|
54
|
+
);
|
|
53
55
|
expect(result.html).toContain(
|
|
54
56
|
'<meta property="og:site_name" content="Alepha">',
|
|
55
57
|
);
|
|
@@ -113,7 +115,9 @@ describe("SEO Head on Page", () => {
|
|
|
113
115
|
expect(result.html).toContain(
|
|
114
116
|
'<meta name="description" content="Read our latest articles">',
|
|
115
117
|
);
|
|
116
|
-
expect(result.html).toContain(
|
|
118
|
+
expect(result.html).toContain(
|
|
119
|
+
'<meta property="og:type" content="article">',
|
|
120
|
+
);
|
|
117
121
|
expect(result.html).toContain(
|
|
118
122
|
'<meta property="og:image" content="https://example.com/blog-og.png">',
|
|
119
123
|
);
|
|
@@ -10,26 +10,18 @@ export const ssrManifestAtomSchema = t.object({
|
|
|
10
10
|
*/
|
|
11
11
|
preload: t.optional(t.record(t.string(), t.string())),
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* SSR manifest mapping source files to their required chunks.
|
|
15
|
-
*/
|
|
16
|
-
ssr: t.optional(t.record(t.string(), t.array(t.string()))),
|
|
17
|
-
|
|
18
13
|
/**
|
|
19
14
|
* Client manifest mapping source files to their output information.
|
|
15
|
+
* Only includes fields actually used for preloading.
|
|
20
16
|
*/
|
|
21
17
|
client: t.optional(
|
|
22
18
|
t.record(
|
|
23
19
|
t.string(),
|
|
24
20
|
t.object({
|
|
25
21
|
file: t.string(),
|
|
26
|
-
src: t.optional(t.string()),
|
|
27
22
|
isEntry: t.optional(t.boolean()),
|
|
28
|
-
isDynamicEntry: t.optional(t.boolean()),
|
|
29
23
|
imports: t.optional(t.array(t.string())),
|
|
30
|
-
dynamicImports: t.optional(t.array(t.string())),
|
|
31
24
|
css: t.optional(t.array(t.string())),
|
|
32
|
-
assets: t.optional(t.array(t.string())),
|
|
33
25
|
}),
|
|
34
26
|
),
|
|
35
27
|
),
|
|
@@ -49,8 +41,7 @@ export type SsrManifestAtomSchema = typeof ssrManifestAtomSchema;
|
|
|
49
41
|
*
|
|
50
42
|
* The manifest includes:
|
|
51
43
|
* - preload: Maps short hash keys to source paths (from viteAlephaSsrPreload)
|
|
52
|
-
* -
|
|
53
|
-
* - client: Maps source files to their output info including imports/css
|
|
44
|
+
* - client: Maps source files to their output info (file, imports, css)
|
|
54
45
|
*/
|
|
55
46
|
export const ssrManifestAtom = $atom({
|
|
56
47
|
name: "alepha.react.ssr.manifest",
|