@nifrajs/web-solid 5.0.0 → 5.2.0
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/CHANGELOG.md +1251 -0
- package/dist/i18n.d.ts +12 -3
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +33 -7
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -3
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +2 -2
- package/dist/plugin.js.map +1 -1
- package/package.json +7 -6
- package/src/i18n.ts +37 -6
- package/src/index.ts +44 -3
- package/src/plugin.ts +7 -2
package/dist/i18n.d.ts
CHANGED
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
* `Formatter` (a `createMemo`, so switching locale rebuilds it) and provides it; `useT()` reads it.
|
|
4
4
|
* Both `locale` + `messages` are serializable, so a loader returns them, the page renders with the
|
|
5
5
|
* negotiated catalog on the server, and the client rebuilds the same formatter on hydrate (no
|
|
6
|
-
* mismatch).
|
|
6
|
+
* mismatch). In an app that registers its locale routing (`i18n` in `backend/framework.ts`), `locale`
|
|
7
|
+
* defaults to the page's and `useLocale()` names it. No JSX (`createComponent`).
|
|
7
8
|
*/
|
|
8
9
|
import { type Formatter, type FormatterOptions, type MessageKey, type RegisteredMessages, type Translation } from "@nifrajs/i18n";
|
|
9
|
-
import { type JSX } from "solid-js";
|
|
10
|
+
import { type Accessor, type JSX } from "solid-js";
|
|
10
11
|
export interface I18nProviderProps extends FormatterOptions {
|
|
11
|
-
|
|
12
|
+
/** The locale to format in. Default: the page's locale tag, in an app that registers its locale
|
|
13
|
+
* routing. */
|
|
14
|
+
readonly locale?: string;
|
|
12
15
|
/** The catalog for `locale`, checked against the registered catalog type when one is declared. */
|
|
13
16
|
readonly messages: Translation;
|
|
14
17
|
readonly children?: JSX.Element;
|
|
@@ -20,6 +23,12 @@ export declare function I18nProvider(props: I18nProviderProps): JSX.Element;
|
|
|
20
23
|
/** Read the current {@link Formatter} (`{ locale, t, get, n, d }`). Throws if no `<I18nProvider>` is above.
|
|
21
24
|
* nifra switches locale by re-navigating, which re-runs the consuming component with the new catalog. */
|
|
22
25
|
export declare function useT(): Formatter;
|
|
26
|
+
/**
|
|
27
|
+
* The page's locale key (`ur`, the URL segment), in an app that registers its locale routing (`i18n`
|
|
28
|
+
* in `backend/framework.ts`): from the request on the server, from the URL after each navigation.
|
|
29
|
+
* Without one, the nearest `<I18nProvider>`'s locale; with neither, reading it throws.
|
|
30
|
+
*/
|
|
31
|
+
export declare function useLocale(): Accessor<string>;
|
|
23
32
|
/** Tag handlers for {@link rich}, by tag name: each receives its tag's content as one node. */
|
|
24
33
|
export type RichTags = Readonly<Record<string, (content: JSX.Element) => JSX.Element>>;
|
|
25
34
|
/**
|
package/dist/i18n.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,WAAW,EACjB,MAAM,eAAe,CAAA;AAGtB,OAAO,EACL,KAAK,QAAQ,EAIb,KAAK,GAAG,EAET,MAAM,UAAU,CAAA;AAMjB,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD;kBACc;IACd,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,kGAAkG;IAClG,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAA;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;CAChC;AAED;;sDAEsD;AACtD,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,GAAG,CAAC,OAAO,CAsBlE;AAED;yGACyG;AACzG,wBAAgB,IAAI,IAAI,SAAS,CAMhC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,QAAQ,CAAC,MAAM,CAAC,CAY5C;AAED,+FAA+F;AAC/F,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,KAAK,GAAG,CAAC,OAAO,CAAC,CAAC,CAAA;AAStF;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,GAAG,kBAAkB,EACxD,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,EACvB,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,EAClB,IAAI,CAAC,EAAE,QAAQ,EACf,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACvC,GAAG,CAAC,OAAO,CAEb"}
|
package/dist/i18n.js
CHANGED
|
@@ -3,23 +3,33 @@
|
|
|
3
3
|
* `Formatter` (a `createMemo`, so switching locale rebuilds it) and provides it; `useT()` reads it.
|
|
4
4
|
* Both `locale` + `messages` are serializable, so a loader returns them, the page renders with the
|
|
5
5
|
* negotiated catalog on the server, and the client rebuilds the same formatter on hydrate (no
|
|
6
|
-
* mismatch).
|
|
6
|
+
* mismatch). In an app that registers its locale routing (`i18n` in `backend/framework.ts`), `locale`
|
|
7
|
+
* defaults to the page's and `useLocale()` names it. No JSX (`createComponent`).
|
|
7
8
|
*/
|
|
8
9
|
import { createFormatter, } from "@nifrajs/i18n";
|
|
9
10
|
import { renderRich } from "@nifrajs/i18n/rich";
|
|
11
|
+
import { localeOfProps } from "@nifrajs/web/client";
|
|
10
12
|
import { createComponent, createContext, createMemo, useContext, } from "solid-js";
|
|
11
13
|
import { Dynamic } from "solid-js/web";
|
|
14
|
+
import { RenderPropsContext } from "./router.js";
|
|
12
15
|
const I18nContext = createContext();
|
|
13
16
|
/** Provide a {@link Formatter} (built from `locale` + `messages`, with the optional `fallback`,
|
|
14
17
|
* `onMissing`, `timeZone` and `numberingSystem` of `createFormatter`) to the subtree. Memoized
|
|
15
18
|
* on those props, so switching locale rebuilds it. */
|
|
16
19
|
export function I18nProvider(props) {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const renderProps = useContext(RenderPropsContext);
|
|
21
|
+
const formatter = createMemo(() => {
|
|
22
|
+
const locale = props.locale ?? localeOfProps(renderProps)?.lang;
|
|
23
|
+
if (locale === undefined) {
|
|
24
|
+
throw new Error("[nifra/web-solid] <I18nProvider> needs a `locale`, or an app that registers its locale routing (export `i18n` from backend/framework.ts)");
|
|
25
|
+
}
|
|
26
|
+
return createFormatter(locale, props.messages, {
|
|
27
|
+
fallback: props.fallback,
|
|
28
|
+
onMissing: props.onMissing,
|
|
29
|
+
timeZone: props.timeZone,
|
|
30
|
+
numberingSystem: props.numberingSystem,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
23
33
|
return createComponent(I18nContext.Provider, {
|
|
24
34
|
value: formatter,
|
|
25
35
|
get children() {
|
|
@@ -36,6 +46,22 @@ export function useT() {
|
|
|
36
46
|
}
|
|
37
47
|
return formatter();
|
|
38
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* The page's locale key (`ur`, the URL segment), in an app that registers its locale routing (`i18n`
|
|
51
|
+
* in `backend/framework.ts`): from the request on the server, from the URL after each navigation.
|
|
52
|
+
* Without one, the nearest `<I18nProvider>`'s locale; with neither, reading it throws.
|
|
53
|
+
*/
|
|
54
|
+
export function useLocale() {
|
|
55
|
+
const renderProps = useContext(RenderPropsContext);
|
|
56
|
+
const formatter = useContext(I18nContext);
|
|
57
|
+
return createMemo(() => {
|
|
58
|
+
const locale = localeOfProps(renderProps)?.key ?? formatter?.().locale;
|
|
59
|
+
if (locale === undefined) {
|
|
60
|
+
throw new Error("[nifra/web-solid] useLocale() needs an app that registers its locale routing (export `i18n` from backend/framework.ts), or an <I18nProvider> above it");
|
|
61
|
+
}
|
|
62
|
+
return locale;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
39
65
|
const join = (chunks) => chunks.length === 0 ? undefined : chunks.length === 1 ? chunks[0] : [...chunks];
|
|
40
66
|
const SOLID_RICH = {
|
|
41
67
|
join,
|
package/dist/i18n.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,eAAe,GAMhB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAsC,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAEL,eAAe,EACf,aAAa,EACb,UAAU,EAEV,UAAU,GACX,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAEhD,MAAM,WAAW,GAAG,aAAa,EAAuB,CAAA;AAWxD;;sDAEsD;AACtD,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,WAAW,GAAG,UAAU,CAAC,kBAAkB,CAAC,CAAA;IAClD,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;QAChC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,aAAa,CAAC,WAAW,CAAC,EAAE,IAAI,CAAA;QAC/D,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,0IAA0I,CAC3I,CAAA;QACH,CAAC;QACD,OAAO,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,EAAE;YAC7C,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,eAAe,EAAE,KAAK,CAAC,eAAe;SACvC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IACF,OAAO,eAAe,CAAC,WAAW,CAAC,QAAQ,EAAE;QAC3C,KAAK,EAAE,SAAS;QAChB,IAAI,QAAQ;YACV,OAAO,KAAK,CAAC,QAAQ,CAAA;QACvB,CAAC;KACF,CAAC,CAAA;AACJ,CAAC;AAED;yGACyG;AACzG,MAAM,UAAU,IAAI;IAClB,MAAM,SAAS,GAAG,UAAU,CAAC,WAAW,CAAC,CAAA;IACzC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAA;IACnF,CAAC;IACD,OAAO,SAAS,EAAE,CAAA;AACpB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,WAAW,GAAG,UAAU,CAAC,kBAAkB,CAAC,CAAA;IAClD,MAAM,SAAS,GAAG,UAAU,CAAC,WAAW,CAAC,CAAA;IACzC,OAAO,UAAU,CAAC,GAAG,EAAE;QACrB,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,CAAC,EAAE,GAAG,IAAI,SAAS,EAAE,EAAE,CAAC,MAAM,CAAA;QACtE,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,uJAAuJ,CACxJ,CAAA;QACH,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC,CAAC,CAAA;AACJ,CAAC;AAKD,MAAM,IAAI,GAAG,CAAC,MAA+B,EAAe,EAAE,CAC5D,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAA;AACjF,MAAM,UAAU,GAA8B;IAC5C,IAAI;IACJ,SAAS,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;CAC/D,CAAA;AAED;;;;;;GAMG;AACH,MAAM,UAAU,IAAI,CAClB,SAAuB,EACvB,GAAkB,EAClB,IAAe,EACf,IAAwC;IAExC,OAAO,UAAU,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;AAC3D,CAAC"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAgDjD,6EAA6E;AAC7E,eAAO,MAAM,YAAY,EAAE,aA8B1B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
|
+
import { sharedConfig } from "solid-js";
|
|
1
2
|
import { generateHydrationScript, renderToStream as solidRenderToStream, renderToString as solidRenderToString, } from "solid-js/web";
|
|
2
3
|
import { compose } from "./compose.js";
|
|
3
4
|
const HYDRATION_HEAD = generateHydrationScript();
|
|
5
|
+
// An unset NODE_ENV counts as production, as for the framework's own error pages.
|
|
6
|
+
const showsErrors = () => {
|
|
7
|
+
const nodeEnv = typeof process === "undefined" ? undefined : process.env?.NODE_ENV;
|
|
8
|
+
return nodeEnv === "development" || nodeEnv === "test";
|
|
9
|
+
};
|
|
10
|
+
/** What the browser receives for an error Solid hands to hydration: its text names server internals. */
|
|
11
|
+
function redactedError() {
|
|
12
|
+
// Built bare: a constructed Error carries a stack and, on Bun, the line, column and file it was made
|
|
13
|
+
// at, and Solid serializes every own property.
|
|
14
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object.create over Error.prototype with a message is an Error minus its stack.
|
|
15
|
+
return Object.create(Error.prototype, {
|
|
16
|
+
message: { value: "Internal Server Error", writable: true, configurable: true },
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Render `app` with every Error that Solid serializes for hydration replaced. An `<ErrorBoundary>` that
|
|
21
|
+
* caught on the server, or a failed resource, otherwise ships the message and stack in an inline script.
|
|
22
|
+
* Nested contexts copy this one, so they keep the wrapped `serialize`.
|
|
23
|
+
*/
|
|
24
|
+
function redactingErrors(app) {
|
|
25
|
+
if (showsErrors())
|
|
26
|
+
return app;
|
|
27
|
+
return () => {
|
|
28
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Solid's server renderers set a context with serialize; the check below handles its absence.
|
|
29
|
+
const context = sharedConfig.context;
|
|
30
|
+
const serialize = context?.serialize;
|
|
31
|
+
if (context !== undefined && serialize !== undefined) {
|
|
32
|
+
context.serialize = (id, value) => serialize(id, value instanceof Error ? redactedError() : value);
|
|
33
|
+
}
|
|
34
|
+
return app();
|
|
35
|
+
};
|
|
36
|
+
}
|
|
4
37
|
/** The Solid server render adapter - pass to @nifrajs/web's `renderPage`. */
|
|
5
38
|
export const solidAdapter = {
|
|
6
39
|
// Synchronous one-pass render for non-deferred pages (renderPage's buffered fast path). Solid's
|
|
@@ -8,8 +41,11 @@ export const solidAdapter = {
|
|
|
8
41
|
// `generateHydrationScript()`) as `renderToStream`, but skips the TransformStream + Solid's
|
|
9
42
|
// streaming machinery - the heaviest of the five renderers on Bun. A page that defer()s/Suspends
|
|
10
43
|
// takes `renderToStream` below (progressive resolution needs it).
|
|
11
|
-
renderToString(chain, props) {
|
|
12
|
-
|
|
44
|
+
renderToString(chain, props, options) {
|
|
45
|
+
const app = redactingErrors(compose(chain, props));
|
|
46
|
+
return options?.nonce === undefined
|
|
47
|
+
? solidRenderToString(app)
|
|
48
|
+
: solidRenderToString(app, { nonce: options.nonce });
|
|
13
49
|
},
|
|
14
50
|
renderToStream(chain, props, options) {
|
|
15
51
|
// Solid's `renderToStream` streams `Uint8Array` chunks into a Web `WritableStream` via
|
|
@@ -18,7 +54,7 @@ export const solidAdapter = {
|
|
|
18
54
|
// <head>) seeds client hydration. A render failure errors `ts.readable`, which `renderPage`
|
|
19
55
|
// surfaces on the response body. The nonce reaches the resource and boundary scripts Solid streams.
|
|
20
56
|
const ts = new TransformStream();
|
|
21
|
-
const app = compose(chain, props);
|
|
57
|
+
const app = redactingErrors(compose(chain, props));
|
|
22
58
|
const stream = options?.nonce === undefined
|
|
23
59
|
? solidRenderToStream(app)
|
|
24
60
|
: solidRenderToStream(app, { nonce: options.nonce });
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,uBAAuB,EACvB,cAAc,IAAI,mBAAmB,EACrC,cAAc,IAAI,mBAAmB,GACtC,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,MAAM,cAAc,GAAG,uBAAuB,EAAE,CAAA;AAEhD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,YAAY,GAAkB;IACzC,gGAAgG;IAChG,mFAAmF;IACnF,4FAA4F;IAC5F,iGAAiG;IACjG,kEAAkE;IAClE,cAAc,CAAC,KAAK,EAAE,KAAK
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AAGpC;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CA0BjE"}
|
package/dist/plugin.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { transformAsync } from "@babel/core";
|
|
7
7
|
// @ts-expect-error - no type declarations published
|
|
8
8
|
import presetTypeScript from "@babel/preset-typescript";
|
|
9
|
-
import { devServerCompile, normalizeFilePath, rewriteSsrImports } from "@nifrajs/web/plugins/kit";
|
|
9
|
+
import { devServerCompile, normalizeFilePath, readSource, rewriteSsrImports, } from "@nifrajs/web/plugins/kit";
|
|
10
10
|
// @ts-expect-error - no type declarations published
|
|
11
11
|
import presetSolid from "babel-preset-solid";
|
|
12
12
|
import solidRefreshBunHot, { SOLID_HOT_MODULE } from "./refresh-babel.js";
|
|
@@ -28,7 +28,7 @@ export function solidBunPlugin(generate) {
|
|
|
28
28
|
// cache); strip it before reading the file off disk.
|
|
29
29
|
build.onLoad({ filter: /\.tsx(\?|$)/ }, async (args) => {
|
|
30
30
|
const path = normalizeFilePath(args.path);
|
|
31
|
-
const source = await
|
|
31
|
+
const source = await readSource(build, path);
|
|
32
32
|
const result = await transformAsync(source, {
|
|
33
33
|
filename: path,
|
|
34
34
|
// babel applies presets last→first: strip TS first, then Solid transforms JSX.
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,oDAAoD;AACpD,OAAO,gBAAgB,MAAM,0BAA0B,CAAA;AACvD,OAAO,
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/web-solid",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "Solid render adapter for @nifrajs/web - SSR + hydration, with the Solid Babel build plugin (@nifrajs/web-solid/plugin).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -108,7 +108,8 @@
|
|
|
108
108
|
},
|
|
109
109
|
"files": [
|
|
110
110
|
"dist",
|
|
111
|
-
"src"
|
|
111
|
+
"src",
|
|
112
|
+
"CHANGELOG.md"
|
|
112
113
|
],
|
|
113
114
|
"sideEffects": false,
|
|
114
115
|
"scripts": {
|
|
@@ -121,10 +122,10 @@
|
|
|
121
122
|
"dependencies": {
|
|
122
123
|
"@babel/core": "^7.26.0",
|
|
123
124
|
"@babel/preset-typescript": "^7.26.0",
|
|
124
|
-
"@nifrajs/core": "5.
|
|
125
|
-
"@nifrajs/i18n": "5.
|
|
126
|
-
"@nifrajs/image": "5.
|
|
127
|
-
"@nifrajs/web": "5.
|
|
125
|
+
"@nifrajs/core": "5.2.0",
|
|
126
|
+
"@nifrajs/i18n": "5.2.0",
|
|
127
|
+
"@nifrajs/image": "5.2.0",
|
|
128
|
+
"@nifrajs/web": "5.2.0",
|
|
128
129
|
"babel-preset-solid": "^1.9.3",
|
|
129
130
|
"solid-refresh": "^0.7.8"
|
|
130
131
|
},
|
package/src/i18n.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* `Formatter` (a `createMemo`, so switching locale rebuilds it) and provides it; `useT()` reads it.
|
|
4
4
|
* Both `locale` + `messages` are serializable, so a loader returns them, the page renders with the
|
|
5
5
|
* negotiated catalog on the server, and the client rebuilds the same formatter on hydrate (no
|
|
6
|
-
* mismatch).
|
|
6
|
+
* mismatch). In an app that registers its locale routing (`i18n` in `backend/framework.ts`), `locale`
|
|
7
|
+
* defaults to the page's and `useLocale()` names it. No JSX (`createComponent`).
|
|
7
8
|
*/
|
|
8
9
|
import {
|
|
9
10
|
createFormatter,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
type Translation,
|
|
15
16
|
} from "@nifrajs/i18n"
|
|
16
17
|
import { type RichChunks, type RichRenderer, renderRich } from "@nifrajs/i18n/rich"
|
|
18
|
+
import { localeOfProps } from "@nifrajs/web/client"
|
|
17
19
|
import {
|
|
18
20
|
type Accessor,
|
|
19
21
|
createComponent,
|
|
@@ -23,11 +25,14 @@ import {
|
|
|
23
25
|
useContext,
|
|
24
26
|
} from "solid-js"
|
|
25
27
|
import { Dynamic } from "solid-js/web"
|
|
28
|
+
import { RenderPropsContext } from "./router.ts"
|
|
26
29
|
|
|
27
30
|
const I18nContext = createContext<Accessor<Formatter>>()
|
|
28
31
|
|
|
29
32
|
export interface I18nProviderProps extends FormatterOptions {
|
|
30
|
-
|
|
33
|
+
/** The locale to format in. Default: the page's locale tag, in an app that registers its locale
|
|
34
|
+
* routing. */
|
|
35
|
+
readonly locale?: string
|
|
31
36
|
/** The catalog for `locale`, checked against the registered catalog type when one is declared. */
|
|
32
37
|
readonly messages: Translation
|
|
33
38
|
readonly children?: JSX.Element
|
|
@@ -37,14 +42,21 @@ export interface I18nProviderProps extends FormatterOptions {
|
|
|
37
42
|
* `onMissing`, `timeZone` and `numberingSystem` of `createFormatter`) to the subtree. Memoized
|
|
38
43
|
* on those props, so switching locale rebuilds it. */
|
|
39
44
|
export function I18nProvider(props: I18nProviderProps): JSX.Element {
|
|
40
|
-
const
|
|
41
|
-
|
|
45
|
+
const renderProps = useContext(RenderPropsContext)
|
|
46
|
+
const formatter = createMemo(() => {
|
|
47
|
+
const locale = props.locale ?? localeOfProps(renderProps)?.lang
|
|
48
|
+
if (locale === undefined) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"[nifra/web-solid] <I18nProvider> needs a `locale`, or an app that registers its locale routing (export `i18n` from backend/framework.ts)",
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
return createFormatter(locale, props.messages, {
|
|
42
54
|
fallback: props.fallback,
|
|
43
55
|
onMissing: props.onMissing,
|
|
44
56
|
timeZone: props.timeZone,
|
|
45
57
|
numberingSystem: props.numberingSystem,
|
|
46
|
-
})
|
|
47
|
-
)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
48
60
|
return createComponent(I18nContext.Provider, {
|
|
49
61
|
value: formatter,
|
|
50
62
|
get children() {
|
|
@@ -63,6 +75,25 @@ export function useT(): Formatter {
|
|
|
63
75
|
return formatter()
|
|
64
76
|
}
|
|
65
77
|
|
|
78
|
+
/**
|
|
79
|
+
* The page's locale key (`ur`, the URL segment), in an app that registers its locale routing (`i18n`
|
|
80
|
+
* in `backend/framework.ts`): from the request on the server, from the URL after each navigation.
|
|
81
|
+
* Without one, the nearest `<I18nProvider>`'s locale; with neither, reading it throws.
|
|
82
|
+
*/
|
|
83
|
+
export function useLocale(): Accessor<string> {
|
|
84
|
+
const renderProps = useContext(RenderPropsContext)
|
|
85
|
+
const formatter = useContext(I18nContext)
|
|
86
|
+
return createMemo(() => {
|
|
87
|
+
const locale = localeOfProps(renderProps)?.key ?? formatter?.().locale
|
|
88
|
+
if (locale === undefined) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
"[nifra/web-solid] useLocale() needs an app that registers its locale routing (export `i18n` from backend/framework.ts), or an <I18nProvider> above it",
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
return locale
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
66
97
|
/** Tag handlers for {@link rich}, by tag name: each receives its tag's content as one node. */
|
|
67
98
|
export type RichTags = Readonly<Record<string, (content: JSX.Element) => JSX.Element>>
|
|
68
99
|
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Bun-plugin in `@nifrajs/web-solid/plugin`.
|
|
6
6
|
*/
|
|
7
7
|
import type { RenderAdapter } from "@nifrajs/web"
|
|
8
|
+
import { sharedConfig } from "solid-js"
|
|
8
9
|
import {
|
|
9
10
|
generateHydrationScript,
|
|
10
11
|
renderToStream as solidRenderToStream,
|
|
@@ -14,6 +15,43 @@ import { compose } from "./compose.ts"
|
|
|
14
15
|
|
|
15
16
|
const HYDRATION_HEAD = generateHydrationScript()
|
|
16
17
|
|
|
18
|
+
// An unset NODE_ENV counts as production, as for the framework's own error pages.
|
|
19
|
+
const showsErrors = (): boolean => {
|
|
20
|
+
const nodeEnv = typeof process === "undefined" ? undefined : process.env?.NODE_ENV
|
|
21
|
+
return nodeEnv === "development" || nodeEnv === "test"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** What the browser receives for an error Solid hands to hydration: its text names server internals. */
|
|
25
|
+
function redactedError(): Error {
|
|
26
|
+
// Built bare: a constructed Error carries a stack and, on Bun, the line, column and file it was made
|
|
27
|
+
// at, and Solid serializes every own property.
|
|
28
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object.create over Error.prototype with a message is an Error minus its stack.
|
|
29
|
+
return Object.create(Error.prototype, {
|
|
30
|
+
message: { value: "Internal Server Error", writable: true, configurable: true },
|
|
31
|
+
}) as Error
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Render `app` with every Error that Solid serializes for hydration replaced. An `<ErrorBoundary>` that
|
|
36
|
+
* caught on the server, or a failed resource, otherwise ships the message and stack in an inline script.
|
|
37
|
+
* Nested contexts copy this one, so they keep the wrapped `serialize`.
|
|
38
|
+
*/
|
|
39
|
+
function redactingErrors<T>(app: () => T): () => T {
|
|
40
|
+
if (showsErrors()) return app
|
|
41
|
+
return () => {
|
|
42
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Solid's server renderers set a context with serialize; the check below handles its absence.
|
|
43
|
+
const context = sharedConfig.context as
|
|
44
|
+
| { serialize?: (id: string, value: unknown) => void }
|
|
45
|
+
| undefined
|
|
46
|
+
const serialize = context?.serialize
|
|
47
|
+
if (context !== undefined && serialize !== undefined) {
|
|
48
|
+
context.serialize = (id, value) =>
|
|
49
|
+
serialize(id, value instanceof Error ? redactedError() : value)
|
|
50
|
+
}
|
|
51
|
+
return app()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
17
55
|
/** The Solid server render adapter - pass to @nifrajs/web's `renderPage`. */
|
|
18
56
|
export const solidAdapter: RenderAdapter = {
|
|
19
57
|
// Synchronous one-pass render for non-deferred pages (renderPage's buffered fast path). Solid's
|
|
@@ -21,8 +59,11 @@ export const solidAdapter: RenderAdapter = {
|
|
|
21
59
|
// `generateHydrationScript()`) as `renderToStream`, but skips the TransformStream + Solid's
|
|
22
60
|
// streaming machinery - the heaviest of the five renderers on Bun. A page that defer()s/Suspends
|
|
23
61
|
// takes `renderToStream` below (progressive resolution needs it).
|
|
24
|
-
renderToString(chain, props) {
|
|
25
|
-
|
|
62
|
+
renderToString(chain, props, options) {
|
|
63
|
+
const app = redactingErrors(compose(chain, props))
|
|
64
|
+
return options?.nonce === undefined
|
|
65
|
+
? solidRenderToString(app)
|
|
66
|
+
: solidRenderToString(app, { nonce: options.nonce })
|
|
26
67
|
},
|
|
27
68
|
renderToStream(chain, props, options) {
|
|
28
69
|
// Solid's `renderToStream` streams `Uint8Array` chunks into a Web `WritableStream` via
|
|
@@ -31,7 +72,7 @@ export const solidAdapter: RenderAdapter = {
|
|
|
31
72
|
// <head>) seeds client hydration. A render failure errors `ts.readable`, which `renderPage`
|
|
32
73
|
// surfaces on the response body. The nonce reaches the resource and boundary scripts Solid streams.
|
|
33
74
|
const ts = new TransformStream<Uint8Array, Uint8Array>()
|
|
34
|
-
const app = compose(chain, props)
|
|
75
|
+
const app = redactingErrors(compose(chain, props))
|
|
35
76
|
const stream =
|
|
36
77
|
options?.nonce === undefined
|
|
37
78
|
? solidRenderToStream(app)
|
package/src/plugin.ts
CHANGED
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
import { transformAsync } from "@babel/core"
|
|
7
7
|
// @ts-expect-error - no type declarations published
|
|
8
8
|
import presetTypeScript from "@babel/preset-typescript"
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
devServerCompile,
|
|
11
|
+
normalizeFilePath,
|
|
12
|
+
readSource,
|
|
13
|
+
rewriteSsrImports,
|
|
14
|
+
} from "@nifrajs/web/plugins/kit"
|
|
10
15
|
// @ts-expect-error - no type declarations published
|
|
11
16
|
import presetSolid from "babel-preset-solid"
|
|
12
17
|
import type { BunPlugin } from "bun"
|
|
@@ -31,7 +36,7 @@ export function solidBunPlugin(generate: "dom" | "ssr"): BunPlugin {
|
|
|
31
36
|
// cache); strip it before reading the file off disk.
|
|
32
37
|
build.onLoad({ filter: /\.tsx(\?|$)/ }, async (args) => {
|
|
33
38
|
const path = normalizeFilePath(args.path)
|
|
34
|
-
const source = await
|
|
39
|
+
const source = await readSource(build, path)
|
|
35
40
|
const result = await transformAsync(source, {
|
|
36
41
|
filename: path,
|
|
37
42
|
// babel applies presets last→first: strip TS first, then Solid transforms JSX.
|