@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/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). Imports only `solid-js` + `@nifrajs/i18n`; no JSX (`createComponent`).
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
- readonly locale: string;
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
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,WAAW,EACjB,MAAM,eAAe,CAAA;AAEtB,OAAO,EAKL,KAAK,GAAG,EAET,MAAM,UAAU,CAAA;AAKjB,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,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,CAelE;AAED;yGACyG;AACzG,wBAAgB,IAAI,IAAI,SAAS,CAMhC;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"}
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). Imports only `solid-js` + `@nifrajs/i18n`; no JSX (`createComponent`).
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 formatter = createMemo(() => createFormatter(props.locale, props.messages, {
18
- fallback: props.fallback,
19
- onMissing: props.onMissing,
20
- timeZone: props.timeZone,
21
- numberingSystem: props.numberingSystem,
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;;;;;;GAMG;AACH,OAAO,EACL,eAAe,GAMhB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAsC,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACnF,OAAO,EAEL,eAAe,EACf,aAAa,EACb,UAAU,EAEV,UAAU,GACX,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,MAAM,WAAW,GAAG,aAAa,EAAuB,CAAA;AASxD;;sDAEsD;AACtD,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE,CAChC,eAAe,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,EAAE;QAC5C,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,eAAe,EAAE,KAAK,CAAC,eAAe;KACvC,CAAC,CACH,CAAA;IACD,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;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"}
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"}
@@ -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;AAUjD,6EAA6E;AAC7E,eAAO,MAAM,YAAY,EAAE,aA2B1B,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
- return solidRenderToString(compose(chain, props));
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;QACzB,OAAO,mBAAmB,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;IACnD,CAAC;IACD,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO;QAClC,uFAAuF;QACvF,2FAA2F;QAC3F,6FAA6F;QAC7F,4FAA4F;QAC5F,oGAAoG;QACpG,MAAM,EAAE,GAAG,IAAI,eAAe,EAA0B,CAAA;QACxD,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;QACjC,MAAM,MAAM,GACV,OAAO,EAAE,KAAK,KAAK,SAAS;YAC1B,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC;YAC1B,CAAC,CAAC,mBAAmB,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAA;QACxD,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAA;QAC1B,OAAO,EAAE,CAAC,QAAQ,CAAA;IACpB,CAAC;IACD,aAAa;QACX,OAAO,cAAc,CAAA;IACvB,CAAC;CACF,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AAGpC;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CA0BjE"}
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 Bun.file(path).text();
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.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
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.0.0",
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.0.0",
125
- "@nifrajs/i18n": "5.0.0",
126
- "@nifrajs/image": "5.0.0",
127
- "@nifrajs/web": "5.0.0",
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). Imports only `solid-js` + `@nifrajs/i18n`; no JSX (`createComponent`).
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
- readonly locale: string
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 formatter = createMemo(() =>
41
- createFormatter(props.locale, props.messages, {
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
- return solidRenderToString(compose(chain, props))
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 { devServerCompile, normalizeFilePath, rewriteSsrImports } from "@nifrajs/web/plugins/kit"
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 Bun.file(path).text()
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.