@bleedingdev/modern-js-renderer-solid 0.0.0-stage → 3.9.3-ultramodern.10
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/esm/actions.js +282 -0
- package/dist/esm/application-router.js +30 -0
- package/dist/esm/client.js +49 -0
- package/dist/esm/entry-application.js +60 -0
- package/dist/esm/entry-client.js +24 -0
- package/dist/esm/federation-context.js +25 -0
- package/dist/esm/federation-ssr.js +92 -0
- package/dist/esm/federation.js +87 -0
- package/dist/esm/i18n/I18nProvider.js +33 -0
- package/dist/esm/i18n/LocalizedLink.js +52 -0
- package/dist/esm/i18n/context.js +3 -0
- package/dist/esm/i18n/index.js +8 -0
- package/dist/esm/i18n/languageSync.js +50 -0
- package/dist/esm/i18n/types.js +0 -0
- package/dist/esm/i18n/useI18n.js +74 -0
- package/dist/esm/index.js +58 -0
- package/dist/esm/manifest.js +150 -0
- package/dist/esm/route-completion.js +151 -0
- package/dist/esm/route-data-error.js +108 -0
- package/dist/esm/router-binding/Asset.js +202 -0
- package/dist/esm/router-binding/CatchBoundary.js +94 -0
- package/dist/esm/router-binding/ClientOnly.js +26 -0
- package/dist/esm/router-binding/HeadContent.js +8 -0
- package/dist/esm/router-binding/LICENSE +21 -0
- package/dist/esm/router-binding/Match.js +319 -0
- package/dist/esm/router-binding/Matches.js +124 -0
- package/dist/esm/router-binding/PROVENANCE.json +94 -0
- package/dist/esm/router-binding/RouterProvider.js +52 -0
- package/dist/esm/router-binding/SafeFragment.js +5 -0
- package/dist/esm/router-binding/ScriptOnce.js +15 -0
- package/dist/esm/router-binding/Scripts.js +30 -0
- package/dist/esm/router-binding/ScrollRestoration.js +16 -0
- package/dist/esm/router-binding/Transitioner.js +109 -0
- package/dist/esm/router-binding/awaited.js +48 -0
- package/dist/esm/router-binding/fileRoute.js +83 -0
- package/dist/esm/router-binding/flightData.js +23 -0
- package/dist/esm/router-binding/headContentUtils.js +168 -0
- package/dist/esm/router-binding/history.js +0 -0
- package/dist/esm/router-binding/index.js +35 -0
- package/dist/esm/router-binding/lazyRouteComponent.js +30 -0
- package/dist/esm/router-binding/link.js +387 -0
- package/dist/esm/router-binding/matchContext.js +7 -0
- package/dist/esm/router-binding/nonRouteComponentContext.js +13 -0
- package/dist/esm/router-binding/not-found.js +35 -0
- package/dist/esm/router-binding/publicMatchData.js +447 -0
- package/dist/esm/router-binding/registryTransfer.js +223 -0
- package/dist/esm/router-binding/renderRouteNotFound.js +23 -0
- package/dist/esm/router-binding/route.js +143 -0
- package/dist/esm/router-binding/router.js +14 -0
- package/dist/esm/router-binding/routerContext.js +3 -0
- package/dist/esm/router-binding/routerOptions.js +0 -0
- package/dist/esm/router-binding/routerStores.js +50 -0
- package/dist/esm/router-binding/scroll-restoration.js +13 -0
- package/dist/esm/router-binding/typePrimitives.js +0 -0
- package/dist/esm/router-binding/useBlocker.js +152 -0
- package/dist/esm/router-binding/useCanGoBack.js +7 -0
- package/dist/esm/router-binding/useLoaderData.js +8 -0
- package/dist/esm/router-binding/useLoaderDeps.js +8 -0
- package/dist/esm/router-binding/useLocation.js +14 -0
- package/dist/esm/router-binding/useMatch.js +28 -0
- package/dist/esm/router-binding/useNavigate.js +25 -0
- package/dist/esm/router-binding/useParams.js +11 -0
- package/dist/esm/router-binding/useRouteContext.js +8 -0
- package/dist/esm/router-binding/useRouter.js +10 -0
- package/dist/esm/router-binding/useRouterState.js +19 -0
- package/dist/esm/router-binding/useSearch.js +11 -0
- package/dist/esm/router-binding/utils.js +23 -0
- package/dist/esm/router.js +187 -0
- package/dist/esm-node/actions.js +263 -0
- package/dist/esm-node/application-router.js +30 -0
- package/dist/esm-node/client.js +49 -0
- package/dist/esm-node/entry-application.js +110 -0
- package/dist/esm-node/entry-client.js +24 -0
- package/dist/esm-node/entry-server.js +47 -0
- package/dist/esm-node/federation-context.js +25 -0
- package/dist/esm-node/federation-ssr.js +92 -0
- package/dist/esm-node/federation.js +87 -0
- package/dist/esm-node/i18n/I18nProvider.js +42 -0
- package/dist/esm-node/i18n/LocalizedLink.js +88 -0
- package/dist/esm-node/i18n/context.js +3 -0
- package/dist/esm-node/i18n/index.js +8 -0
- package/dist/esm-node/i18n/languageSync.js +50 -0
- package/dist/esm-node/i18n/types.js +1 -0
- package/dist/esm-node/i18n/useI18n.js +74 -0
- package/dist/esm-node/index.js +58 -0
- package/dist/esm-node/manifest.js +150 -0
- package/dist/esm-node/native-promise-serialization.js +113 -0
- package/dist/esm-node/plugin/compiler.js +209 -0
- package/dist/esm-node/plugin/create.js +236 -0
- package/dist/esm-node/plugin/index.js +70 -0
- package/dist/esm-node/plugin/profile.js +51 -0
- package/dist/esm-node/route-completion.js +161 -0
- package/dist/esm-node/route-data-error.js +108 -0
- package/dist/esm-node/router-binding/Asset.js +172 -0
- package/dist/esm-node/router-binding/CatchBoundary.js +136 -0
- package/dist/esm-node/router-binding/ClientOnly.js +46 -0
- package/dist/esm-node/router-binding/HeadContent.js +8 -0
- package/dist/esm-node/router-binding/LICENSE +21 -0
- package/dist/esm-node/router-binding/Match.js +660 -0
- package/dist/esm-node/router-binding/Matches.js +161 -0
- package/dist/esm-node/router-binding/PROVENANCE.json +94 -0
- package/dist/esm-node/router-binding/RouterProvider.js +88 -0
- package/dist/esm-node/router-binding/SafeFragment.js +5 -0
- package/dist/esm-node/router-binding/ScriptOnce.js +16 -0
- package/dist/esm-node/router-binding/Scripts.js +49 -0
- package/dist/esm-node/router-binding/ScrollRestoration.js +16 -0
- package/dist/esm-node/router-binding/Transitioner.js +109 -0
- package/dist/esm-node/router-binding/awaited.js +75 -0
- package/dist/esm-node/router-binding/fileRoute.js +83 -0
- package/dist/esm-node/router-binding/flightData.js +23 -0
- package/dist/esm-node/router-binding/headContentUtils.js +168 -0
- package/dist/esm-node/router-binding/history.js +1 -0
- package/dist/esm-node/router-binding/index.js +35 -0
- package/dist/esm-node/router-binding/lazyRouteComponent.js +30 -0
- package/dist/esm-node/router-binding/link.js +399 -0
- package/dist/esm-node/router-binding/matchContext.js +7 -0
- package/dist/esm-node/router-binding/nonRouteComponentContext.js +22 -0
- package/dist/esm-node/router-binding/not-found.js +49 -0
- package/dist/esm-node/router-binding/publicMatchData.js +447 -0
- package/dist/esm-node/router-binding/registryTransfer.js +223 -0
- package/dist/esm-node/router-binding/renderRouteNotFound.js +22 -0
- package/dist/esm-node/router-binding/route.js +162 -0
- package/dist/esm-node/router-binding/router.js +14 -0
- package/dist/esm-node/router-binding/routerContext.js +3 -0
- package/dist/esm-node/router-binding/routerOptions.js +1 -0
- package/dist/esm-node/router-binding/routerStores.js +50 -0
- package/dist/esm-node/router-binding/scroll-restoration.js +12 -0
- package/dist/esm-node/router-binding/typePrimitives.js +1 -0
- package/dist/esm-node/router-binding/useBlocker.js +152 -0
- package/dist/esm-node/router-binding/useCanGoBack.js +7 -0
- package/dist/esm-node/router-binding/useLoaderData.js +8 -0
- package/dist/esm-node/router-binding/useLoaderDeps.js +8 -0
- package/dist/esm-node/router-binding/useLocation.js +14 -0
- package/dist/esm-node/router-binding/useMatch.js +28 -0
- package/dist/esm-node/router-binding/useNavigate.js +25 -0
- package/dist/esm-node/router-binding/useParams.js +11 -0
- package/dist/esm-node/router-binding/useRouteContext.js +8 -0
- package/dist/esm-node/router-binding/useRouter.js +10 -0
- package/dist/esm-node/router-binding/useRouterState.js +19 -0
- package/dist/esm-node/router-binding/useSearch.js +11 -0
- package/dist/esm-node/router-binding/utils.js +23 -0
- package/dist/esm-node/router.js +187 -0
- package/dist/esm-node/server.js +396 -0
- package/dist/types/actions.d.ts +45 -0
- package/dist/types/application-router.d.ts +5 -0
- package/dist/types/client.d.ts +23 -0
- package/dist/types/entry-application.d.ts +10 -0
- package/dist/types/entry-client.d.ts +5 -0
- package/dist/types/entry-server.d.ts +5 -0
- package/dist/types/federation-context.d.ts +16 -0
- package/dist/types/federation-ssr.d.ts +19 -0
- package/dist/types/federation.d.ts +30 -0
- package/dist/types/i18n/I18nProvider.d.ts +21 -0
- package/dist/types/i18n/LocalizedLink.d.ts +15 -0
- package/dist/types/i18n/context.d.ts +11 -0
- package/dist/types/i18n/index.d.ts +9 -0
- package/dist/types/i18n/languageSync.d.ts +31 -0
- package/dist/types/i18n/types.d.ts +33 -0
- package/dist/types/i18n/useI18n.d.ts +24 -0
- package/dist/types/index.d.ts +41 -0
- package/dist/types/manifest.d.ts +55 -0
- package/dist/types/native-promise-serialization.d.ts +9 -0
- package/dist/types/plugin/compiler.d.ts +6 -0
- package/dist/types/plugin/create.d.ts +21 -0
- package/dist/types/plugin/index.d.ts +3 -0
- package/dist/types/plugin/lazy-modules.d.cts +15 -0
- package/dist/types/plugin/preserve-import-loader.d.cts +3 -0
- package/dist/types/plugin/profile.d.ts +2 -0
- package/dist/types/plugin/solid-loader.d.cts +2 -0
- package/dist/types/plugin/svg-component-template.d.cts +6 -0
- package/dist/types/route-completion.d.ts +21 -0
- package/dist/types/route-data-error.d.ts +17 -0
- package/dist/types/router-binding/Asset.d.ts +3 -0
- package/dist/types/router-binding/CatchBoundary.d.ts +13 -0
- package/dist/types/router-binding/ClientOnly.d.ts +31 -0
- package/dist/types/router-binding/HeadContent.d.ts +13 -0
- package/dist/types/router-binding/Match.d.ts +6 -0
- package/dist/types/router-binding/Matches.d.ts +26 -0
- package/dist/types/router-binding/RouterProvider.d.ts +11 -0
- package/dist/types/router-binding/SafeFragment.d.ts +2 -0
- package/dist/types/router-binding/ScriptOnce.d.ts +6 -0
- package/dist/types/router-binding/Scripts.d.ts +2 -0
- package/dist/types/router-binding/ScrollRestoration.d.ts +14 -0
- package/dist/types/router-binding/Transitioner.d.ts +2 -0
- package/dist/types/router-binding/awaited.d.ts +11 -0
- package/dist/types/router-binding/fileRoute.d.ts +54 -0
- package/dist/types/router-binding/flightData.d.ts +38 -0
- package/dist/types/router-binding/headContentUtils.d.ts +15 -0
- package/dist/types/router-binding/history.d.ts +8 -0
- package/dist/types/router-binding/index.d.ts +46 -0
- package/dist/types/router-binding/lazyRouteComponent.d.ts +2 -0
- package/dist/types/router-binding/link.d.ts +43 -0
- package/dist/types/router-binding/matchContext.d.ts +7 -0
- package/dist/types/router-binding/nonRouteComponentContext.d.ts +5 -0
- package/dist/types/router-binding/not-found.d.ts +11 -0
- package/dist/types/router-binding/publicMatchData.d.ts +19 -0
- package/dist/types/router-binding/registryTransfer.d.ts +49 -0
- package/dist/types/router-binding/renderRouteNotFound.d.ts +11 -0
- package/dist/types/router-binding/route.d.ts +105 -0
- package/dist/types/router-binding/router.d.ts +70 -0
- package/dist/types/router-binding/routerContext.d.ts +3 -0
- package/dist/types/router-binding/routerOptions.d.ts +6 -0
- package/dist/types/router-binding/routerStores.d.ts +2 -0
- package/dist/types/router-binding/scroll-restoration.d.ts +2 -0
- package/dist/types/router-binding/typePrimitives.d.ts +10 -0
- package/dist/types/router-binding/useBlocker.d.ts +70 -0
- package/dist/types/router-binding/useCanGoBack.d.ts +2 -0
- package/dist/types/router-binding/useLoaderData.d.ts +8 -0
- package/dist/types/router-binding/useLoaderDeps.d.ts +8 -0
- package/dist/types/router-binding/useLocation.d.ts +7 -0
- package/dist/types/router-binding/useMatch.d.ts +10 -0
- package/dist/types/router-binding/useNavigate.d.ts +5 -0
- package/dist/types/router-binding/useParams.d.ts +9 -0
- package/dist/types/router-binding/useRouteContext.d.ts +4 -0
- package/dist/types/router-binding/useRouter.d.ts +4 -0
- package/dist/types/router-binding/useRouterState.d.ts +8 -0
- package/dist/types/router-binding/useSearch.d.ts +9 -0
- package/dist/types/router-binding/utils.d.ts +25 -0
- package/dist/types/router.d.ts +31 -0
- package/dist/types/server.d.ts +47 -0
- package/package.json +130 -4
- package/src/actions.tsx +441 -0
- package/src/application-router.tsx +47 -0
- package/src/client.ts +106 -0
- package/src/entry-application.tsx +73 -0
- package/src/entry-client.tsx +49 -0
- package/src/entry-server.tsx +85 -0
- package/src/federation-context.ts +47 -0
- package/src/federation-ssr.ts +136 -0
- package/src/federation.tsx +197 -0
- package/src/i18n/I18nProvider.tsx +54 -0
- package/src/i18n/LocalizedLink.tsx +83 -0
- package/src/i18n/context.ts +13 -0
- package/src/i18n/index.ts +31 -0
- package/src/i18n/languageSync.ts +113 -0
- package/src/i18n/types.ts +35 -0
- package/src/i18n/useI18n.ts +121 -0
- package/src/index.ts +41 -0
- package/src/manifest.ts +337 -0
- package/src/native-promise-serialization.ts +167 -0
- package/src/plugin/compiler.ts +320 -0
- package/src/plugin/create.ts +271 -0
- package/src/plugin/index.ts +69 -0
- package/src/plugin/lazy-modules.cjs +146 -0
- package/src/plugin/preserve-import-loader.cjs +46 -0
- package/src/plugin/profile.ts +42 -0
- package/src/plugin/solid-loader.cjs +83 -0
- package/src/plugin/svg-component-template.cjs +19 -0
- package/src/route-completion.tsx +226 -0
- package/src/route-data-error.ts +175 -0
- package/src/router-binding/Asset.tsx +284 -0
- package/src/router-binding/CatchBoundary.tsx +130 -0
- package/src/router-binding/ClientOnly.tsx +76 -0
- package/src/router-binding/HeadContent.tsx +23 -0
- package/src/router-binding/LICENSE +21 -0
- package/src/router-binding/Match.tsx +397 -0
- package/src/router-binding/Matches.tsx +242 -0
- package/src/router-binding/PROVENANCE.json +94 -0
- package/src/router-binding/RouterProvider.tsx +128 -0
- package/src/router-binding/SafeFragment.tsx +5 -0
- package/src/router-binding/ScriptOnce.tsx +23 -0
- package/src/router-binding/Scripts.tsx +44 -0
- package/src/router-binding/ScrollRestoration.tsx +49 -0
- package/src/router-binding/Transitioner.tsx +177 -0
- package/src/router-binding/awaited.tsx +71 -0
- package/src/router-binding/fileRoute.ts +268 -0
- package/src/router-binding/flightData.ts +90 -0
- package/src/router-binding/headContentUtils.tsx +292 -0
- package/src/router-binding/history.ts +9 -0
- package/src/router-binding/index.tsx +315 -0
- package/src/router-binding/lazyRouteComponent.tsx +70 -0
- package/src/router-binding/link.tsx +865 -0
- package/src/router-binding/matchContext.tsx +15 -0
- package/src/router-binding/nonRouteComponentContext.tsx +23 -0
- package/src/router-binding/not-found.tsx +65 -0
- package/src/router-binding/publicMatchData.ts +674 -0
- package/src/router-binding/registryTransfer.ts +400 -0
- package/src/router-binding/renderRouteNotFound.tsx +53 -0
- package/src/router-binding/route.tsx +665 -0
- package/src/router-binding/router.ts +123 -0
- package/src/router-binding/routerContext.tsx +6 -0
- package/src/router-binding/routerOptions.ts +26 -0
- package/src/router-binding/routerStores.ts +85 -0
- package/src/router-binding/scroll-restoration.tsx +15 -0
- package/src/router-binding/typePrimitives.ts +74 -0
- package/src/router-binding/useBlocker.tsx +359 -0
- package/src/router-binding/useCanGoBack.ts +9 -0
- package/src/router-binding/useLoaderData.tsx +51 -0
- package/src/router-binding/useLoaderDeps.tsx +46 -0
- package/src/router-binding/useLocation.tsx +43 -0
- package/src/router-binding/useMatch.tsx +111 -0
- package/src/router-binding/useNavigate.tsx +45 -0
- package/src/router-binding/useParams.tsx +70 -0
- package/src/router-binding/useRouteContext.ts +30 -0
- package/src/router-binding/useRouter.tsx +17 -0
- package/src/router-binding/useRouterState.tsx +50 -0
- package/src/router-binding/useSearch.tsx +70 -0
- package/src/router-binding/utils.ts +59 -0
- package/src/router.ts +376 -0
- package/src/server.ts +645 -0
- package/README.md +0 -3
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { LocalisedUrlsOption } from '@modern-js/i18n-runtime-extensions/paths';
|
|
2
|
+
import {
|
|
3
|
+
type NativeClientEntryOptions,
|
|
4
|
+
type NativeI18nInstance,
|
|
5
|
+
startNativeClientEntry,
|
|
6
|
+
} from '@modern-js/renderer-core/entry-client';
|
|
7
|
+
import { createNativeRouter } from '@modern-js/renderer-core/router';
|
|
8
|
+
import {
|
|
9
|
+
hydrateApplication,
|
|
10
|
+
mountApplication,
|
|
11
|
+
readSolidDocumentBootstrap,
|
|
12
|
+
} from './client';
|
|
13
|
+
import {
|
|
14
|
+
componentView,
|
|
15
|
+
routerView,
|
|
16
|
+
solidRouterFactory,
|
|
17
|
+
} from './entry-application';
|
|
18
|
+
|
|
19
|
+
export type SolidClientEntryOptions = NativeClientEntryOptions<
|
|
20
|
+
NativeI18nInstance,
|
|
21
|
+
LocalisedUrlsOption
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
/** Start a generated Solid browser entry: mount, or hydrate the server document. */
|
|
25
|
+
export function startNativeClient(options: SolidClientEntryOptions): void {
|
|
26
|
+
startNativeClientEntry(options, {
|
|
27
|
+
readBootstrap: readSolidDocumentBootstrap,
|
|
28
|
+
createRouter: (application, routerOptions) =>
|
|
29
|
+
createNativeRouter(application, routerOptions, solidRouterFactory),
|
|
30
|
+
disposeRouter: router => router.history.destroy(),
|
|
31
|
+
async prepareRouter(router, hydrating) {
|
|
32
|
+
// Hydration adopts the server's matches; a fresh mount loads its own.
|
|
33
|
+
if (!hydrating) await router.load();
|
|
34
|
+
},
|
|
35
|
+
async start({ root, bootstrap, load }) {
|
|
36
|
+
const view = await load();
|
|
37
|
+
return (bootstrap?.hydrating ? hydrateApplication : mountApplication)(
|
|
38
|
+
view.kind === 'component'
|
|
39
|
+
? componentView(view.component, view.i18n)
|
|
40
|
+
: routerView(view.router, view.i18n),
|
|
41
|
+
root,
|
|
42
|
+
{
|
|
43
|
+
...(bootstrap ? { renderId: bootstrap.documentId } : {}),
|
|
44
|
+
federation: options.federation,
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { LocalisedUrlsOption } from '@modern-js/i18n-runtime-extensions/paths';
|
|
2
|
+
import {
|
|
3
|
+
dataMetadataToDocumentPolicy,
|
|
4
|
+
mergeDataResponseIntoResponse,
|
|
5
|
+
mergeDataResponseMetadata,
|
|
6
|
+
} from '@modern-js/renderer-core/data';
|
|
7
|
+
import {
|
|
8
|
+
createNativeServerEntry as createServerEntry,
|
|
9
|
+
type NativeI18nInstance,
|
|
10
|
+
type NativeServerEntry,
|
|
11
|
+
type NativeServerEntryOptions,
|
|
12
|
+
} from '@modern-js/renderer-core/entry-server';
|
|
13
|
+
import { createNativeRouter } from '@modern-js/renderer-core/router';
|
|
14
|
+
import {
|
|
15
|
+
componentView,
|
|
16
|
+
routerView,
|
|
17
|
+
solidRouterFactory,
|
|
18
|
+
} from './entry-application';
|
|
19
|
+
import { validateSolidModuleManifest } from './manifest';
|
|
20
|
+
import { getApplicationStatus, resolveApplicationRedirect } from './router';
|
|
21
|
+
import { prepareRouterMatchTransfer } from './router-binding/index';
|
|
22
|
+
import {
|
|
23
|
+
renderCSRDocument,
|
|
24
|
+
renderDocumentApplication,
|
|
25
|
+
respondApplicationResponse,
|
|
26
|
+
runApplicationRequest,
|
|
27
|
+
} from './server';
|
|
28
|
+
|
|
29
|
+
export type SolidServerEntryOptions = NativeServerEntryOptions<
|
|
30
|
+
NativeI18nInstance,
|
|
31
|
+
LocalisedUrlsOption
|
|
32
|
+
>;
|
|
33
|
+
|
|
34
|
+
/** The native transport handlers of a generated Solid server entry. */
|
|
35
|
+
export function createNativeServerEntry(
|
|
36
|
+
options: SolidServerEntryOptions,
|
|
37
|
+
): NativeServerEntry {
|
|
38
|
+
return createServerEntry(options, {
|
|
39
|
+
document: (document, nativeManifest, identity) => ({
|
|
40
|
+
...document,
|
|
41
|
+
renderId: document.documentId,
|
|
42
|
+
manifest: validateSolidModuleManifest(nativeManifest, identity).modules,
|
|
43
|
+
}),
|
|
44
|
+
run: (session, callback) => runApplicationRequest(session, callback),
|
|
45
|
+
respond: respondApplicationResponse,
|
|
46
|
+
createRouter: (application, routerOptions) =>
|
|
47
|
+
createNativeRouter(application, routerOptions, solidRouterFactory),
|
|
48
|
+
renderCSR: ({ session }, document) =>
|
|
49
|
+
renderCSRDocument({ session, document }),
|
|
50
|
+
renderComponent: ({ session }, component, document, i18n) =>
|
|
51
|
+
renderDocumentApplication({
|
|
52
|
+
session,
|
|
53
|
+
view: componentView(component, i18n),
|
|
54
|
+
document,
|
|
55
|
+
federation: options.federation,
|
|
56
|
+
}),
|
|
57
|
+
async renderRoutes({
|
|
58
|
+
context: { session },
|
|
59
|
+
router,
|
|
60
|
+
outcomes,
|
|
61
|
+
document,
|
|
62
|
+
forbiddenValues,
|
|
63
|
+
i18n,
|
|
64
|
+
}) {
|
|
65
|
+
await router.load();
|
|
66
|
+
prepareRouterMatchTransfer(router, session, forbiddenValues);
|
|
67
|
+
const metadata = mergeDataResponseMetadata(outcomes, {
|
|
68
|
+
status: getApplicationStatus(router),
|
|
69
|
+
});
|
|
70
|
+
const redirect = resolveApplicationRedirect(router);
|
|
71
|
+
if (redirect)
|
|
72
|
+
return respondApplicationResponse(
|
|
73
|
+
session,
|
|
74
|
+
mergeDataResponseIntoResponse(redirect, metadata),
|
|
75
|
+
);
|
|
76
|
+
session.resolveResponse(dataMetadataToDocumentPolicy(metadata));
|
|
77
|
+
return renderDocumentApplication({
|
|
78
|
+
session,
|
|
79
|
+
view: routerView(router, i18n),
|
|
80
|
+
document,
|
|
81
|
+
federation: options.federation,
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type FederationInstance,
|
|
3
|
+
getFederationHost,
|
|
4
|
+
type NativeFederationBinding,
|
|
5
|
+
} from '@modern-js/renderer-core/federation';
|
|
6
|
+
import type { JSX } from '@solidjs/web';
|
|
7
|
+
import { createComponent, createContext } from 'solid-js';
|
|
8
|
+
|
|
9
|
+
/** One application owns its host runtime; each response owns its render scope. */
|
|
10
|
+
export interface SolidFederationScope {
|
|
11
|
+
readonly instance: FederationInstance | undefined;
|
|
12
|
+
/** Server scopes: the host's client module that hydrates a remote. */
|
|
13
|
+
readonly hydrationModule?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Outside a generated entry, remote ids have no host; custom loaders work. */
|
|
17
|
+
export const FederationContext = createContext<SolidFederationScope>(
|
|
18
|
+
Object.freeze({ instance: undefined }),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export function createFederationScope(
|
|
22
|
+
binding: NativeFederationBinding | undefined,
|
|
23
|
+
): SolidFederationScope | undefined {
|
|
24
|
+
if (binding === undefined) return undefined;
|
|
25
|
+
const instance = getFederationHost(binding);
|
|
26
|
+
return binding.hydrationModule === undefined
|
|
27
|
+
? { instance }
|
|
28
|
+
: { instance, hydrationModule: binding.hydrationModule };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Provide the scope to an application view. The server and the browser wrap
|
|
33
|
+
* the same view, so both render the provider at the same hydration position.
|
|
34
|
+
*/
|
|
35
|
+
export function provideFederation(
|
|
36
|
+
scope: SolidFederationScope | undefined,
|
|
37
|
+
view: () => JSX.Element,
|
|
38
|
+
): () => JSX.Element {
|
|
39
|
+
if (!scope) return view;
|
|
40
|
+
return () =>
|
|
41
|
+
createComponent(FederationContext, {
|
|
42
|
+
value: scope,
|
|
43
|
+
get children() {
|
|
44
|
+
return view();
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { FederatedAssets } from '@modern-js/renderer-core/federation';
|
|
2
|
+
import type { SolidFederationScope } from './federation-context';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The server half of same-renderer Module Federation. federatedComponent()
|
|
6
|
+
* names each remote it server-rendered by an asset key, and the response's
|
|
7
|
+
* document resolves that key through the same host's federation scope.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Asset keys for server-rendered remotes, e.g. `ultramodern-federation:remote/Widget`. */
|
|
11
|
+
export const FEDERATED_ASSET_PREFIX = 'ultramodern-federation:';
|
|
12
|
+
|
|
13
|
+
interface CapturedServerAssets {
|
|
14
|
+
readonly hydration: string;
|
|
15
|
+
readonly assets: FederatedAssets;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const capturedAssets = new WeakMap<
|
|
19
|
+
SolidFederationScope,
|
|
20
|
+
Map<string, CapturedServerAssets>
|
|
21
|
+
>();
|
|
22
|
+
|
|
23
|
+
/** Retain the assets admitted with this module until its response resolves them. */
|
|
24
|
+
export function captureFederatedServerAssets(
|
|
25
|
+
scope: SolidFederationScope,
|
|
26
|
+
id: string,
|
|
27
|
+
assets: FederatedAssets | undefined,
|
|
28
|
+
): string | undefined {
|
|
29
|
+
const instance = scope.instance;
|
|
30
|
+
if (!assets || !scope.hydrationModule || !instance?.name) return undefined;
|
|
31
|
+
// The browser module finds this host by name among the realm's instances.
|
|
32
|
+
const hydration = `${scope.hydrationModule}?id=${encodeURIComponent(id)}&host=${encodeURIComponent(instance.name)}`;
|
|
33
|
+
let records = capturedAssets.get(scope);
|
|
34
|
+
if (!records) {
|
|
35
|
+
records = new Map();
|
|
36
|
+
capturedAssets.set(scope, records);
|
|
37
|
+
}
|
|
38
|
+
const baseKey = federatedAssetKey(id);
|
|
39
|
+
let key = baseKey;
|
|
40
|
+
// Two loads of one remote may straddle a development revision in one response.
|
|
41
|
+
// Give each loaded module its own key rather than replacing earlier assets.
|
|
42
|
+
for (let revision = 1; records.has(key); revision += 1)
|
|
43
|
+
key = `${baseKey}:${revision}`;
|
|
44
|
+
records.set(key, { hydration, assets });
|
|
45
|
+
return key;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function federatedAssetKey(id: string): string {
|
|
49
|
+
return `${FEDERATED_ASSET_PREFIX}${id}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface ResolvedDocumentAssets {
|
|
53
|
+
js: string[];
|
|
54
|
+
css: string[];
|
|
55
|
+
preloads?: Record<string, unknown>[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface StaticAssetChunk {
|
|
59
|
+
file: string;
|
|
60
|
+
css?: string[];
|
|
61
|
+
imports?: string[];
|
|
62
|
+
preloads?: Record<string, unknown>[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Solid's own asset path join: absolute URLs pass through. */
|
|
66
|
+
function joinAssetPath(base: unknown, file: string): string {
|
|
67
|
+
if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//iu.test(file)) return file;
|
|
68
|
+
let prefix = typeof base === 'string' && base ? base : '/';
|
|
69
|
+
if (!prefix.endsWith('/')) prefix += '/';
|
|
70
|
+
return prefix + (file.startsWith('/') ? file.slice(1) : file);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Solid's static manifest walk, so a resolver can extend a static manifest. */
|
|
74
|
+
function resolveStaticAssets(
|
|
75
|
+
manifest: Record<string, unknown>,
|
|
76
|
+
key: string,
|
|
77
|
+
): ResolvedDocumentAssets | null {
|
|
78
|
+
if (key === '_base' || !Object.hasOwn(manifest, key)) return null;
|
|
79
|
+
const base = manifest._base;
|
|
80
|
+
const js: string[] = [];
|
|
81
|
+
const css: string[] = [];
|
|
82
|
+
let preloads: Record<string, unknown>[] | undefined;
|
|
83
|
+
const visited = new Set<string>();
|
|
84
|
+
const walk = (name: string) => {
|
|
85
|
+
if (visited.has(name) || name === '_base') return;
|
|
86
|
+
visited.add(name);
|
|
87
|
+
const chunk = manifest[name] as StaticAssetChunk | undefined;
|
|
88
|
+
if (!chunk) return;
|
|
89
|
+
js.push(joinAssetPath(base, chunk.file));
|
|
90
|
+
for (const file of chunk.css ?? []) css.push(joinAssetPath(base, file));
|
|
91
|
+
for (const link of chunk.preloads ?? []) {
|
|
92
|
+
const href = typeof link.href === 'string' && link.href;
|
|
93
|
+
if (!href && typeof link.imagesrcset !== 'string') continue;
|
|
94
|
+
preloads ??= [];
|
|
95
|
+
if (href) preloads.push({ ...link, href: joinAssetPath(base, href) });
|
|
96
|
+
else {
|
|
97
|
+
const { href: _ignored, ...rest } = link;
|
|
98
|
+
preloads.push(rest);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const dependency of chunk.imports ?? []) walk(dependency);
|
|
102
|
+
};
|
|
103
|
+
walk(key);
|
|
104
|
+
return preloads ? { js, css, preloads } : { js, css };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Extend a static Solid asset manifest with the remotes this response
|
|
109
|
+
* server-rendered through its host. The first module is the host's hydration
|
|
110
|
+
* module for that remote, which Solid imports before hydrating the boundary
|
|
111
|
+
* that rendered it.
|
|
112
|
+
*/
|
|
113
|
+
export function withFederatedAssets<Manifest>(
|
|
114
|
+
manifest: Manifest,
|
|
115
|
+
scope: SolidFederationScope | undefined,
|
|
116
|
+
): Manifest {
|
|
117
|
+
if (
|
|
118
|
+
!scope?.hydrationModule ||
|
|
119
|
+
manifest === null ||
|
|
120
|
+
typeof manifest !== 'object' ||
|
|
121
|
+
typeof (manifest as { resolve?: unknown }).resolve === 'function'
|
|
122
|
+
)
|
|
123
|
+
return manifest;
|
|
124
|
+
const modules = manifest as Record<string, unknown>;
|
|
125
|
+
const resolve = (key: string): ResolvedDocumentAssets | null => {
|
|
126
|
+
if (!key.startsWith(FEDERATED_ASSET_PREFIX))
|
|
127
|
+
return resolveStaticAssets(modules, key);
|
|
128
|
+
const remote = capturedAssets.get(scope)?.get(key);
|
|
129
|
+
if (!remote) return null;
|
|
130
|
+
return {
|
|
131
|
+
js: [joinAssetPath(modules._base, remote.hydration), ...remote.assets.js],
|
|
132
|
+
css: [...remote.assets.css],
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
return { resolve, resolveSync: resolve } as Manifest;
|
|
136
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { loadFederatedModule } from '@modern-js/renderer-core/federation';
|
|
2
|
+
import { isServer, type JSX } from '@solidjs/web';
|
|
3
|
+
import * as solid from 'solid-js';
|
|
4
|
+
import {
|
|
5
|
+
type Component,
|
|
6
|
+
createComponent,
|
|
7
|
+
createSignal,
|
|
8
|
+
Errored,
|
|
9
|
+
isHydrating,
|
|
10
|
+
Loading,
|
|
11
|
+
lazy,
|
|
12
|
+
onCleanup,
|
|
13
|
+
useContext,
|
|
14
|
+
} from 'solid-js';
|
|
15
|
+
import {
|
|
16
|
+
FederationContext,
|
|
17
|
+
type SolidFederationScope,
|
|
18
|
+
} from './federation-context';
|
|
19
|
+
import { captureFederatedServerAssets } from './federation-ssr';
|
|
20
|
+
|
|
21
|
+
/** The module shape a same-renderer Module Federation remote exposes. */
|
|
22
|
+
export interface FederatedModule<P extends object> {
|
|
23
|
+
readonly default: Component<P>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface FederatedComponentOptions {
|
|
27
|
+
/** Rendered while the remote loads, and on the server when it cannot load. */
|
|
28
|
+
readonly fallback?: () => JSX.Element;
|
|
29
|
+
/**
|
|
30
|
+
* Milliseconds a remote load may take before it fails. Defaults to 3000 on
|
|
31
|
+
* the server, so an unavailable remote cannot hold the document; the
|
|
32
|
+
* browser waits without a limit unless one is given.
|
|
33
|
+
*/
|
|
34
|
+
readonly timeout?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const DEFAULT_SERVER_TIMEOUT = 3000;
|
|
38
|
+
|
|
39
|
+
function withTimeout<T>(
|
|
40
|
+
promise: Promise<T>,
|
|
41
|
+
timeout: number | undefined,
|
|
42
|
+
label: string,
|
|
43
|
+
): Promise<T> {
|
|
44
|
+
if (timeout === undefined) return promise;
|
|
45
|
+
return new Promise<T>((resolve, reject) => {
|
|
46
|
+
const timer = setTimeout(
|
|
47
|
+
() =>
|
|
48
|
+
reject(
|
|
49
|
+
new Error(`Remote module ${label} did not load within ${timeout}ms`),
|
|
50
|
+
),
|
|
51
|
+
timeout,
|
|
52
|
+
);
|
|
53
|
+
// A pending remote must not keep a server process alive.
|
|
54
|
+
(timer as { unref?: () => void }).unref?.();
|
|
55
|
+
promise.then(
|
|
56
|
+
value => {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
resolve(value);
|
|
59
|
+
},
|
|
60
|
+
error => {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
reject(error);
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Run once the document has hydrated. A retry inside a hydration pass would
|
|
70
|
+
* look for server data the failed render never produced.
|
|
71
|
+
*/
|
|
72
|
+
function afterHydration(callback: () => void): void {
|
|
73
|
+
// The hydration runtime installs this hook on Solid's shared config.
|
|
74
|
+
const { sharedConfig } = solid as unknown as {
|
|
75
|
+
sharedConfig?: { onHydrationEnd?: (callback: () => void) => void };
|
|
76
|
+
};
|
|
77
|
+
const onHydrationEnd = sharedConfig?.onHydrationEnd;
|
|
78
|
+
if (onHydrationEnd) onHydrationEnd(callback);
|
|
79
|
+
else queueMicrotask(callback);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Render a Solid component exposed by a Module Federation remote, by its
|
|
84
|
+
* `remote/Expose` id or a custom loader.
|
|
85
|
+
*
|
|
86
|
+
* A remote id loads through the federation instance of the application that
|
|
87
|
+
* renders it, so co-located hosts keep their own remotes. On the server it
|
|
88
|
+
* renders into the document with the remote's stylesheets and module
|
|
89
|
+
* preloads; the browser loads the same remote before hydrating it.
|
|
90
|
+
* A remote that fails or exceeds its timeout on the server renders the
|
|
91
|
+
* fallback, and the browser retries it after hydration. Custom loaders render
|
|
92
|
+
* in the browser only. Browser load and renderer-admission failures throw to
|
|
93
|
+
* the nearest `Errored` boundary.
|
|
94
|
+
*/
|
|
95
|
+
export function federatedComponent<P extends object = Record<string, never>>(
|
|
96
|
+
remote: string | (() => Promise<FederatedModule<P>>),
|
|
97
|
+
options: FederatedComponentOptions = {},
|
|
98
|
+
): Component<P> {
|
|
99
|
+
if (
|
|
100
|
+
typeof remote === 'string'
|
|
101
|
+
? !/^[^/]+\/.+/u.test(remote)
|
|
102
|
+
: typeof remote !== 'function'
|
|
103
|
+
)
|
|
104
|
+
throw new TypeError(
|
|
105
|
+
"federatedComponent requires a remote id such as 'remote/Widget' or a loader",
|
|
106
|
+
);
|
|
107
|
+
if (
|
|
108
|
+
options.timeout !== undefined &&
|
|
109
|
+
(!Number.isFinite(options.timeout) || options.timeout <= 0)
|
|
110
|
+
)
|
|
111
|
+
throw new TypeError(
|
|
112
|
+
'federatedComponent timeout must be a positive number of milliseconds',
|
|
113
|
+
);
|
|
114
|
+
const id = typeof remote === 'string' ? remote : undefined;
|
|
115
|
+
const timeout =
|
|
116
|
+
options.timeout ?? (isServer ? DEFAULT_SERVER_TIMEOUT : undefined);
|
|
117
|
+
const load = (scope: SolidFederationScope): Component<P> =>
|
|
118
|
+
lazy(async () => {
|
|
119
|
+
if (isServer && !id)
|
|
120
|
+
throw new Error(
|
|
121
|
+
'A federatedComponent loader renders in the browser only; use a remote id to server-render it',
|
|
122
|
+
);
|
|
123
|
+
const { module, assets } = await withTimeout(
|
|
124
|
+
Promise.resolve().then(() =>
|
|
125
|
+
id
|
|
126
|
+
? loadFederatedModule<FederatedModule<P>>(scope.instance, id)
|
|
127
|
+
: (remote as () => Promise<FederatedModule<P>>)().then(module => ({
|
|
128
|
+
module,
|
|
129
|
+
assets: undefined,
|
|
130
|
+
})),
|
|
131
|
+
),
|
|
132
|
+
timeout,
|
|
133
|
+
id ?? 'loader',
|
|
134
|
+
);
|
|
135
|
+
const component = module?.default;
|
|
136
|
+
if (typeof component !== 'function')
|
|
137
|
+
throw new TypeError(
|
|
138
|
+
'A federated Solid module must default-export a component',
|
|
139
|
+
);
|
|
140
|
+
if (!isServer || !id) return { default: component };
|
|
141
|
+
const assetKey = captureFederatedServerAssets(scope, id, assets);
|
|
142
|
+
if (!assetKey)
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Cannot server-render ${id}: the host has no server federation state for its browser assets`,
|
|
145
|
+
);
|
|
146
|
+
// Solid resolves a loaded module's $$moduleUrl through the response's
|
|
147
|
+
// asset resolver: stylesheets, preloads and the hydration module.
|
|
148
|
+
return { default: component, $$moduleUrl: assetKey };
|
|
149
|
+
}) as Component<P>;
|
|
150
|
+
// Solid's lazy caches its module. Scope the cache to one application or
|
|
151
|
+
// response, so two hosts rendering this component never share a remote.
|
|
152
|
+
const remotes = new WeakMap<SolidFederationScope, Component<P>>();
|
|
153
|
+
const fallback = (): JSX.Element => options.fallback?.();
|
|
154
|
+
|
|
155
|
+
return (props: P): JSX.Element => {
|
|
156
|
+
const scope = useContext(FederationContext);
|
|
157
|
+
const Remote = remotes.get(scope) ?? load(scope);
|
|
158
|
+
remotes.set(scope, Remote);
|
|
159
|
+
const remoteView = () => createComponent(Remote, props);
|
|
160
|
+
// After a server failure, the browser renders the remote afresh: like a
|
|
161
|
+
// client-only mount, its failures reach the application's boundaries.
|
|
162
|
+
const [retried, retry] = createSignal(false);
|
|
163
|
+
const recover = (error: () => unknown): JSX.Element => {
|
|
164
|
+
// A server failure renders the fallback and serializes the failure;
|
|
165
|
+
// hydration claims that fallback, then the browser retries the remote.
|
|
166
|
+
if (isServer) return fallback();
|
|
167
|
+
if (isHydrating()) {
|
|
168
|
+
let active = true;
|
|
169
|
+
onCleanup(() => {
|
|
170
|
+
active = false;
|
|
171
|
+
});
|
|
172
|
+
afterHydration(() => {
|
|
173
|
+
if (active) retry(true);
|
|
174
|
+
});
|
|
175
|
+
return fallback();
|
|
176
|
+
}
|
|
177
|
+
throw error();
|
|
178
|
+
};
|
|
179
|
+
// Loading owns the pending load. On the first render Errored sits inside
|
|
180
|
+
// it, so a load that fails after suspending still reaches it on the server.
|
|
181
|
+
return createComponent(Loading, {
|
|
182
|
+
get fallback() {
|
|
183
|
+
return fallback();
|
|
184
|
+
},
|
|
185
|
+
get children() {
|
|
186
|
+
return retried()
|
|
187
|
+
? remoteView()
|
|
188
|
+
: createComponent(Errored, {
|
|
189
|
+
fallback: recover,
|
|
190
|
+
get children() {
|
|
191
|
+
return remoteView();
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { JSX } from '@solidjs/web';
|
|
2
|
+
import * as Solid from 'solid-js';
|
|
3
|
+
import { I18nContext } from './context';
|
|
4
|
+
import type { I18nContextValue } from './types';
|
|
5
|
+
|
|
6
|
+
export interface I18nProviderProps extends Omit<I18nContextValue, 'language'> {
|
|
7
|
+
children: JSX.Element;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Provides a per-request i18next-shaped instance to the Solid tree. The
|
|
12
|
+
* instance must come from the caller (created with `cloneInstance` per
|
|
13
|
+
* request on the server, and the same instance reused on hydration) — this
|
|
14
|
+
* provider holds no module-level state of its own beyond the one reactive
|
|
15
|
+
* `language` signal it derives from `instance` and shares with every
|
|
16
|
+
* consumer through context (see `I18nContextValue.language`'s doc for why
|
|
17
|
+
* that single shared subscription matters, instead of each consumer
|
|
18
|
+
* subscribing to `languageChanged` independently).
|
|
19
|
+
*
|
|
20
|
+
* In Solid 2 a `Context` doubles as its own provider component: `value` is
|
|
21
|
+
* read reactively off `props` here, so an ancestor re-rendering this
|
|
22
|
+
* provider with a new `instance`/`languages`/`localisedUrls` propagates
|
|
23
|
+
* without needing a `.Provider` subcomponent.
|
|
24
|
+
*/
|
|
25
|
+
export function I18nProvider(props: I18nProviderProps): JSX.Element {
|
|
26
|
+
const [language, setLanguage] = Solid.createSignal(props.instance.language);
|
|
27
|
+
|
|
28
|
+
Solid.createEffect(
|
|
29
|
+
() => props.instance,
|
|
30
|
+
instance => {
|
|
31
|
+
setLanguage(instance.language);
|
|
32
|
+
const handleLanguageChanged = (lng: string) => setLanguage(lng);
|
|
33
|
+
instance.on?.('languageChanged', handleLanguageChanged);
|
|
34
|
+
return () => {
|
|
35
|
+
instance.off?.('languageChanged', handleLanguageChanged);
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const value: I18nContextValue = {
|
|
41
|
+
get instance() {
|
|
42
|
+
return props.instance;
|
|
43
|
+
},
|
|
44
|
+
get languages() {
|
|
45
|
+
return props.languages;
|
|
46
|
+
},
|
|
47
|
+
get localisedUrls() {
|
|
48
|
+
return props.localisedUrls;
|
|
49
|
+
},
|
|
50
|
+
language,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return <I18nContext value={value}>{props.children}</I18nContext>;
|
|
54
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
configuredLanguage,
|
|
3
|
+
localizePath,
|
|
4
|
+
} from '@modern-js/i18n-runtime-extensions/paths';
|
|
5
|
+
import type { JSX } from '@solidjs/web';
|
|
6
|
+
import * as Solid from 'solid-js';
|
|
7
|
+
import {
|
|
8
|
+
type AnyRouter,
|
|
9
|
+
Link,
|
|
10
|
+
type LinkComponentProps,
|
|
11
|
+
useLocation,
|
|
12
|
+
useRouter,
|
|
13
|
+
} from '../router';
|
|
14
|
+
import { I18nContext } from './context';
|
|
15
|
+
|
|
16
|
+
export interface LocalizedLinkProps
|
|
17
|
+
extends Omit<
|
|
18
|
+
LinkComponentProps<'a', AnyRouter, string, string>,
|
|
19
|
+
'to' | 'mask' | 'href' | 'publicHref' | '_fromLocation'
|
|
20
|
+
> {
|
|
21
|
+
/** A canonical, language-agnostic target, e.g. `/products/red-shoe`. */
|
|
22
|
+
to: string;
|
|
23
|
+
/** Localize for a specific language instead of the current one. */
|
|
24
|
+
language?: string;
|
|
25
|
+
[key: `data-${string}`]: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The native router owns navigation, preloading and active state. A route mask
|
|
30
|
+
* publishes the selected language URL while keeping the canonical route;
|
|
31
|
+
* the entry's router subscription synchronizes the i18next instance.
|
|
32
|
+
*/
|
|
33
|
+
export function LocalizedLink(props: LocalizedLinkProps): JSX.Element {
|
|
34
|
+
const context = Solid.useContext(I18nContext);
|
|
35
|
+
const router = useRouter();
|
|
36
|
+
const location = useLocation();
|
|
37
|
+
const options = Solid.omit(props, 'to', 'language', 'children');
|
|
38
|
+
const targetLanguage = () =>
|
|
39
|
+
props.language === undefined
|
|
40
|
+
? context.language()
|
|
41
|
+
: configuredLanguage(props.language, context.languages);
|
|
42
|
+
const externalTarget = () =>
|
|
43
|
+
props.to.startsWith('//') || URL.canParse(props.to);
|
|
44
|
+
const canonicalTarget = () => ({
|
|
45
|
+
to: props.to,
|
|
46
|
+
from: props.from,
|
|
47
|
+
params: props.params,
|
|
48
|
+
unsafeRelative: props.unsafeRelative,
|
|
49
|
+
});
|
|
50
|
+
const pathname = Solid.createMemo(() => {
|
|
51
|
+
if (externalTarget()) return props.to;
|
|
52
|
+
const target = router.buildLocation({
|
|
53
|
+
...canonicalTarget(),
|
|
54
|
+
_fromLocation: location(),
|
|
55
|
+
});
|
|
56
|
+
return localizePath(target.pathname, targetLanguage(), {
|
|
57
|
+
languages: [...context.languages],
|
|
58
|
+
localisedUrls: context.localisedUrls,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Link<AnyRouter, string, string, string, string>
|
|
64
|
+
{...options}
|
|
65
|
+
to={router.options.rewrite ? props.to : pathname()}
|
|
66
|
+
mask={
|
|
67
|
+
router.options.rewrite && !externalTarget()
|
|
68
|
+
? {
|
|
69
|
+
to: pathname(),
|
|
70
|
+
inheritSearch: true,
|
|
71
|
+
hash: props.hash,
|
|
72
|
+
state: props.state,
|
|
73
|
+
}
|
|
74
|
+
: undefined
|
|
75
|
+
}
|
|
76
|
+
hreflang={
|
|
77
|
+
targetLanguage() === context.language() ? undefined : targetLanguage()
|
|
78
|
+
}
|
|
79
|
+
>
|
|
80
|
+
{props.children}
|
|
81
|
+
</Link>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as Solid from 'solid-js';
|
|
2
|
+
import type { I18nContextValue } from './types';
|
|
3
|
+
|
|
4
|
+
export type { I18nContextValue, I18nInstanceLike } from './types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Default-less form (Solid 2): `useContext` on this throws
|
|
8
|
+
* `ContextNotFoundError` when no `I18nProvider` is mounted, instead of
|
|
9
|
+
* silently reading a shared fallback instance across requests — exactly the
|
|
10
|
+
* failure mode a per-request i18n instance needs. The context object is
|
|
11
|
+
* itself the provider component: render `<I18nContext value={...}>`.
|
|
12
|
+
*/
|
|
13
|
+
export const I18nContext = Solid.createContext<I18nContextValue>();
|