@bleedingdev/modern-js-renderer-octane 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/LICENSE +21 -0
- package/bin/octane-tsc.mjs +4 -0
- package/dist/cjs/bootstrap.js +68 -0
- package/dist/cjs/client.js +421 -0
- package/dist/cjs/entry-application.js +88 -0
- package/dist/cjs/entry-client.js +95 -0
- package/dist/cjs/entry-server.js +135 -0
- package/dist/cjs/federation-context.js +72 -0
- package/dist/cjs/federation.js +205 -0
- package/dist/cjs/i18n/I18nProvider.js +59 -0
- package/dist/cjs/i18n/LocalizedLink.js +80 -0
- package/dist/cjs/i18n/context.js +41 -0
- package/dist/cjs/i18n/index.js +83 -0
- package/dist/cjs/i18n/languageSync.js +99 -0
- package/dist/cjs/i18n/types.js +18 -0
- package/dist/cjs/i18n/useI18n.js +110 -0
- package/dist/cjs/i18n-external-snapshot.js +645 -0
- package/dist/cjs/index.js +46 -0
- package/dist/cjs/manifest.js +167 -0
- package/dist/cjs/plugin/compiler.js +122 -0
- package/dist/cjs/plugin/create.js +266 -0
- package/dist/cjs/plugin/index.js +151 -0
- package/dist/cjs/plugin/profile.js +91 -0
- package/dist/cjs/plugin/source.js +56 -0
- package/dist/cjs/router-client.js +139 -0
- package/dist/cjs/router-external-snapshot.js +291 -0
- package/dist/cjs/router-handler.js +171 -0
- package/dist/cjs/router-server-cleanup.js +46 -0
- package/dist/cjs/router-server-snapshot.js +405 -0
- package/dist/cjs/router-server.js +100 -0
- package/dist/cjs/router-snapshot.js +270 -0
- package/dist/cjs/router.js +218 -0
- package/dist/cjs/routes.js +190 -0
- package/dist/cjs/server.js +245 -0
- package/dist/cjs/typecheck.js +615 -0
- package/dist/cjs/worker-federation-context.js +63 -0
- package/dist/cjs/worker-federation-contexts.js +97 -0
- package/dist/cjs/worker-federation.js +97 -0
- package/dist/esm/bootstrap.mjs +24 -0
- package/dist/esm/client.mjs +377 -0
- package/dist/esm/entry-application.mjs +44 -0
- package/dist/esm/entry-client.mjs +57 -0
- package/dist/esm/entry-server.mjs +91 -0
- package/dist/esm/federation-context.mjs +25 -0
- package/dist/esm/federation.mjs +167 -0
- package/dist/esm/i18n/I18nProvider.mjs +21 -0
- package/dist/esm/i18n/LocalizedLink.mjs +42 -0
- package/dist/esm/i18n/context.mjs +3 -0
- package/dist/esm/i18n/index.mjs +8 -0
- package/dist/esm/i18n/languageSync.mjs +61 -0
- package/dist/esm/i18n/types.mjs +0 -0
- package/dist/esm/i18n/useI18n.mjs +72 -0
- package/dist/esm/i18n-external-snapshot.mjs +576 -0
- package/dist/esm/index.mjs +8 -0
- package/dist/esm/manifest.mjs +117 -0
- package/dist/esm/router-client.mjs +98 -0
- package/dist/esm/router-external-snapshot.mjs +244 -0
- package/dist/esm/router-handler.mjs +133 -0
- package/dist/esm/router-server-cleanup.mjs +8 -0
- package/dist/esm/router-server-snapshot.mjs +367 -0
- package/dist/esm/router-server.mjs +11 -0
- package/dist/esm/router-snapshot.mjs +199 -0
- package/dist/esm/router.mjs +94 -0
- package/dist/esm/routes.mjs +134 -0
- package/dist/esm/server.mjs +204 -0
- package/dist/esm/worker-federation-context.mjs +19 -0
- package/dist/esm/worker-federation-contexts.mjs +47 -0
- package/dist/esm/worker-federation.mjs +59 -0
- package/dist/esm-node/bootstrap.mjs +24 -0
- package/dist/esm-node/manifest.mjs +117 -0
- package/dist/esm-node/plugin/compiler.mjs +67 -0
- package/dist/esm-node/plugin/create.mjs +228 -0
- package/dist/esm-node/plugin/index.mjs +103 -0
- package/dist/esm-node/plugin/profile.mjs +53 -0
- package/dist/esm-node/plugin/source.mjs +7 -0
- package/dist/esm-node/typecheck.mjs +560 -0
- package/dist/types/bootstrap.d.ts +10 -0
- package/dist/types/client.d.ts +40 -0
- package/dist/types/entry-application.d.ts +19 -0
- package/dist/types/entry-client.d.ts +10 -0
- package/dist/types/entry-server.d.ts +14 -0
- package/dist/types/federation-context.d.ts +28 -0
- package/dist/types/federation.d.ts +20 -0
- package/dist/types/i18n/I18nProvider.d.ts +8 -0
- package/dist/types/i18n/LocalizedLink.d.ts +14 -0
- package/dist/types/i18n/context.d.ts +9 -0
- package/dist/types/i18n/index.d.ts +9 -0
- package/dist/types/i18n/languageSync.d.ts +26 -0
- package/dist/types/i18n/types.d.ts +34 -0
- package/dist/types/i18n/useI18n.d.ts +21 -0
- package/dist/types/i18n-external-snapshot.d.ts +43 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/manifest.d.ts +34 -0
- package/dist/types/plugin/compiler-manifest.d.cts +10 -0
- package/dist/types/plugin/compiler.d.ts +5 -0
- package/dist/types/plugin/create.d.ts +23 -0
- package/dist/types/plugin/index.d.ts +3 -0
- package/dist/types/plugin/profile.d.ts +2 -0
- package/dist/types/plugin/source.d.ts +1 -0
- package/dist/types/plugin/svg-component-template.d.cts +6 -0
- package/dist/types/router-client.d.ts +12 -0
- package/dist/types/router-external-snapshot.d.ts +10 -0
- package/dist/types/router-handler.d.ts +13 -0
- package/dist/types/router-server-cleanup.d.ts +3 -0
- package/dist/types/router-server-snapshot.d.ts +15 -0
- package/dist/types/router-server.d.ts +11 -0
- package/dist/types/router-snapshot.d.ts +14 -0
- package/dist/types/router.d.ts +30 -0
- package/dist/types/routes.d.ts +20 -0
- package/dist/types/server.d.ts +47 -0
- package/dist/types/typecheck.d.ts +31 -0
- package/dist/types/worker-federation-context.d.ts +34 -0
- package/dist/types/worker-federation-contexts.d.ts +25 -0
- package/dist/types/worker-federation.d.ts +20 -0
- package/package.json +278 -4
- package/src/bootstrap.ts +60 -0
- package/src/client.ts +568 -0
- package/src/entry-application.ts +76 -0
- package/src/entry-client.ts +90 -0
- package/src/entry-server.ts +153 -0
- package/src/federation-context.ts +53 -0
- package/src/federation.ts +307 -0
- package/src/i18n/I18nProvider.ts +47 -0
- package/src/i18n/LocalizedLink.ts +86 -0
- package/src/i18n/context.ts +12 -0
- package/src/i18n/index.ts +31 -0
- package/src/i18n/languageSync.ts +132 -0
- package/src/i18n/types.ts +36 -0
- package/src/i18n/useI18n.ts +119 -0
- package/src/i18n-external-snapshot.ts +803 -0
- package/src/index.ts +10 -0
- package/src/manifest.ts +253 -0
- package/src/plugin/compiler-manifest.cjs +204 -0
- package/src/plugin/compiler.ts +104 -0
- package/src/plugin/create.ts +273 -0
- package/src/plugin/index.ts +107 -0
- package/src/plugin/profile.ts +50 -0
- package/src/plugin/source.ts +16 -0
- package/src/plugin/svg-component-template.cjs +17 -0
- package/src/router-client.ts +144 -0
- package/src/router-external-snapshot.ts +363 -0
- package/src/router-handler.ts +185 -0
- package/src/router-server-cleanup.ts +11 -0
- package/src/router-server-snapshot.ts +564 -0
- package/src/router-server.ts +21 -0
- package/src/router-snapshot.ts +251 -0
- package/src/router.ts +192 -0
- package/src/routes.ts +274 -0
- package/src/server.ts +340 -0
- package/src/typecheck.ts +723 -0
- package/src/worker-federation-context.ts +51 -0
- package/src/worker-federation-contexts.ts +91 -0
- package/src/worker-federation.ts +120 -0
- package/README.md +0 -3
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { startNativeClientEntry } from "@modern-js/renderer-core/entry-client";
|
|
2
|
+
import { createNativeRouter } from "@modern-js/renderer-core/router";
|
|
3
|
+
import { hydrateOctaneApplication, mountOctaneApplication, readOctaneDocumentBootstrap } from "./client.mjs";
|
|
4
|
+
import { componentView, localizedRouterRoot, octaneRouterFactory } from "./entry-application.mjs";
|
|
5
|
+
import { OctaneRouterRoot, prepareOctaneRouterHydration } from "./router-client.mjs";
|
|
6
|
+
const LocalizedRouterRoot = localizedRouterRoot(OctaneRouterRoot);
|
|
7
|
+
function startNativeClient(options) {
|
|
8
|
+
const { nativeHydrationBuildId } = options;
|
|
9
|
+
startNativeClientEntry(options, {
|
|
10
|
+
readBootstrap: (document, identity)=>readOctaneDocumentBootstrap(document, identity, nativeHydrationBuildId),
|
|
11
|
+
createRouter: (application, routerOptions)=>createNativeRouter(application, routerOptions, octaneRouterFactory),
|
|
12
|
+
disposeRouter: (router)=>router.history.destroy(),
|
|
13
|
+
async prepareRouter (router, hydrating, signal) {
|
|
14
|
+
if (hydrating) await prepareOctaneRouterHydration(router, {
|
|
15
|
+
signal
|
|
16
|
+
});
|
|
17
|
+
else await router.load();
|
|
18
|
+
},
|
|
19
|
+
async start ({ root, identity, bootstrap, signal, load }) {
|
|
20
|
+
const application = {
|
|
21
|
+
container: root,
|
|
22
|
+
identity,
|
|
23
|
+
nativeHydrationBuildId,
|
|
24
|
+
federation: options.federation,
|
|
25
|
+
workerFederation: options.workerFederation,
|
|
26
|
+
documentId: bootstrap?.documentId,
|
|
27
|
+
signal,
|
|
28
|
+
load: async ()=>{
|
|
29
|
+
const view = await load();
|
|
30
|
+
if ('component' === view.kind) return {
|
|
31
|
+
default: componentView(view.component, view.i18n)
|
|
32
|
+
};
|
|
33
|
+
return view.i18n ? {
|
|
34
|
+
default: LocalizedRouterRoot,
|
|
35
|
+
props: {
|
|
36
|
+
router: view.router,
|
|
37
|
+
i18n: view.i18n
|
|
38
|
+
}
|
|
39
|
+
} : {
|
|
40
|
+
default: OctaneRouterRoot,
|
|
41
|
+
props: {
|
|
42
|
+
router: view.router
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const handle = bootstrap?.hydrating ? await hydrateOctaneApplication({
|
|
48
|
+
...application,
|
|
49
|
+
documentIdentity: bootstrap.identity,
|
|
50
|
+
documentId: bootstrap.documentId,
|
|
51
|
+
documentNativeHydrationBuildId: bootstrap.nativeHydrationBuildId
|
|
52
|
+
}) : await mountOctaneApplication(application);
|
|
53
|
+
return ()=>handle.dispose();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export { startNativeClient };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { collectDataHeaders, dataMetadataToDocumentPolicy, mergeDataResponseIntoResponse, mergeDataResponseMetadata, mergeHeaderFields } from "@modern-js/renderer-core/data";
|
|
2
|
+
import { createNativeServerEntry } from "@modern-js/renderer-core/entry-server";
|
|
3
|
+
import { createNativeRouter } from "@modern-js/renderer-core/router";
|
|
4
|
+
import { componentView, localizedRouterRoot, octaneRouterFactory } from "./entry-application.mjs";
|
|
5
|
+
import { validateOctaneModuleManifest } from "./manifest.mjs";
|
|
6
|
+
import { OctaneRouterServer, createOctaneRequestHandler, createSsrStreamResponse, getSsrStatus } from "./router-server.mjs";
|
|
7
|
+
import { renderOctaneApplication, renderOctaneCSRDocument } from "./server.mjs";
|
|
8
|
+
const OCTANE_DOCUMENT_FIELDS = new Set([
|
|
9
|
+
'content-language',
|
|
10
|
+
'content-location',
|
|
11
|
+
'content-disposition'
|
|
12
|
+
]);
|
|
13
|
+
function withOctaneDocumentFields(policy, responseHeaders) {
|
|
14
|
+
const fields = collectDataHeaders(responseHeaders).filter(([name])=>OCTANE_DOCUMENT_FIELDS.has(name.toLowerCase()));
|
|
15
|
+
return fields.length ? {
|
|
16
|
+
...policy,
|
|
17
|
+
headers: [
|
|
18
|
+
...policy.headers,
|
|
19
|
+
...fields
|
|
20
|
+
]
|
|
21
|
+
} : policy;
|
|
22
|
+
}
|
|
23
|
+
const LocalizedRouterServer = localizedRouterRoot(OctaneRouterServer);
|
|
24
|
+
function entry_server_createNativeServerEntry(options) {
|
|
25
|
+
return createNativeServerEntry(options, {
|
|
26
|
+
document: (document, nativeManifest, identity)=>({
|
|
27
|
+
...document,
|
|
28
|
+
nativeHydrationBuildId: validateOctaneModuleManifest(nativeManifest, identity).nativeHydrationBuildId
|
|
29
|
+
}),
|
|
30
|
+
run: (_session, callback)=>callback(),
|
|
31
|
+
respond: (_session, response)=>response,
|
|
32
|
+
createRouter: (application, routerOptions)=>createNativeRouter(application, routerOptions, octaneRouterFactory),
|
|
33
|
+
renderCSR: ({ session }, document)=>renderOctaneCSRDocument({
|
|
34
|
+
session,
|
|
35
|
+
document
|
|
36
|
+
}),
|
|
37
|
+
renderComponent: ({ session, workerFederation }, component, document, i18n)=>renderOctaneApplication({
|
|
38
|
+
session,
|
|
39
|
+
App: componentView(component, i18n),
|
|
40
|
+
document,
|
|
41
|
+
federation: options.federation,
|
|
42
|
+
workerFederation,
|
|
43
|
+
workerFederationLocale: options.workerFederationLocale
|
|
44
|
+
}),
|
|
45
|
+
async renderRoutes ({ request, context: { session, workerFederation }, router, outcomes, document, forbiddenValues, i18n }) {
|
|
46
|
+
const response = await createOctaneRequestHandler({
|
|
47
|
+
request,
|
|
48
|
+
session,
|
|
49
|
+
createRouter: ()=>router,
|
|
50
|
+
serialization: {
|
|
51
|
+
forbiddenValues
|
|
52
|
+
}
|
|
53
|
+
})(async ({ router, responseHeaders })=>{
|
|
54
|
+
const metadata = mergeDataResponseMetadata(outcomes, {
|
|
55
|
+
status: getSsrStatus(router)
|
|
56
|
+
});
|
|
57
|
+
const policy = dataMetadataToDocumentPolicy({
|
|
58
|
+
...metadata,
|
|
59
|
+
headers: mergeHeaderFields(collectDataHeaders(responseHeaders), metadata.headers)
|
|
60
|
+
});
|
|
61
|
+
session.resolveResponse(withOctaneDocumentFields(policy, responseHeaders));
|
|
62
|
+
const rendered = await renderOctaneApplication({
|
|
63
|
+
session,
|
|
64
|
+
...i18n ? {
|
|
65
|
+
App: LocalizedRouterServer,
|
|
66
|
+
props: {
|
|
67
|
+
router,
|
|
68
|
+
i18n
|
|
69
|
+
}
|
|
70
|
+
} : {
|
|
71
|
+
App: OctaneRouterServer,
|
|
72
|
+
props: {
|
|
73
|
+
router
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
document,
|
|
77
|
+
federation: options.federation,
|
|
78
|
+
workerFederation,
|
|
79
|
+
workerFederationLocale: options.workerFederationLocale,
|
|
80
|
+
router
|
|
81
|
+
});
|
|
82
|
+
return createSsrStreamResponse(router, rendered);
|
|
83
|
+
});
|
|
84
|
+
return session.committedPolicy ? response : mergeDataResponseIntoResponse(response, mergeDataResponseMetadata(outcomes, {
|
|
85
|
+
status: response.status
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export { createNativeWorkerFederationRuntime } from "./worker-federation.mjs";
|
|
91
|
+
export { entry_server_createNativeServerEntry as createNativeServerEntry, withOctaneDocumentFields };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getFederationHost } from "@modern-js/renderer-core/federation";
|
|
2
|
+
import { createContext, createElement } from "octane";
|
|
3
|
+
const FederationContext = createContext(null);
|
|
4
|
+
function createFederationScope(binding, server, nonce) {
|
|
5
|
+
return {
|
|
6
|
+
instance: getFederationHost(binding),
|
|
7
|
+
server,
|
|
8
|
+
...void 0 === nonce ? {} : {
|
|
9
|
+
nonce
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function FederationRoot(props) {
|
|
14
|
+
return createElement(FederationContext, {
|
|
15
|
+
value: props.scope,
|
|
16
|
+
children: props.children
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function FederationApplication(props) {
|
|
20
|
+
return createElement(FederationRoot, {
|
|
21
|
+
scope: props.scope,
|
|
22
|
+
children: createElement(props.application.default, props.application.props)
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export { FederationApplication, FederationContext, FederationRoot, createFederationScope };
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { loadFederatedModule } from "@modern-js/renderer-core/federation";
|
|
2
|
+
import { Suspense, createElement, decodeExternalSnapshot, externalSnapshotBoundary, hookSlots, lazy, preinit, publisherBoundary, serializeExternalSnapshotRequest, useContext, useEffect } from "octane";
|
|
3
|
+
import { FederationContext } from "./federation-context.mjs";
|
|
4
|
+
import { I18nContext } from "./i18n/context.mjs";
|
|
5
|
+
import { WorkerFederationContext } from "./worker-federation-context.mjs";
|
|
6
|
+
import { useOctaneWorkerContextKeys } from "./worker-federation-contexts.mjs";
|
|
7
|
+
function withTimeout(promise, timeout, label) {
|
|
8
|
+
if (void 0 === timeout) return promise;
|
|
9
|
+
return new Promise((resolve, reject)=>{
|
|
10
|
+
const timer = setTimeout(()=>reject(new Error(`Remote module ${label} did not load within ${timeout}ms`)), timeout);
|
|
11
|
+
timer.unref?.();
|
|
12
|
+
promise.then((value)=>{
|
|
13
|
+
clearTimeout(timer);
|
|
14
|
+
resolve(value);
|
|
15
|
+
}, (error)=>{
|
|
16
|
+
clearTimeout(timer);
|
|
17
|
+
reject(error);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
const publisherRegistrationEffectSlot = Symbol(hookSlots(1));
|
|
22
|
+
function federatedComponent(remote, options = {}) {
|
|
23
|
+
if ('string' == typeof remote ? !/^[^/]+\/.+/u.test(remote) : 'function' != typeof remote) throw new TypeError("federatedComponent requires a remote id such as 'remote/Widget' or a loader");
|
|
24
|
+
if (void 0 !== options.timeout && (!Number.isFinite(options.timeout) || options.timeout <= 0)) throw new TypeError('federatedComponent timeout must be a positive number of milliseconds');
|
|
25
|
+
const id = 'string' == typeof remote ? remote : void 0;
|
|
26
|
+
const standalone = {
|
|
27
|
+
instance: void 0,
|
|
28
|
+
server: "u" < typeof document
|
|
29
|
+
};
|
|
30
|
+
const components = new WeakMap();
|
|
31
|
+
const workerOwners = new WeakMap();
|
|
32
|
+
return (props)=>{
|
|
33
|
+
const context = useContext(FederationContext) ?? standalone;
|
|
34
|
+
const worker = useContext(WorkerFederationContext);
|
|
35
|
+
const language = worker ? useContext(I18nContext)?.language ?? worker.locale : void 0;
|
|
36
|
+
const contextKeys = worker ? useOctaneWorkerContextKeys(options.contextKeys) : void 0;
|
|
37
|
+
let owner = context;
|
|
38
|
+
if (worker) {
|
|
39
|
+
let scopes = workerOwners.get(context);
|
|
40
|
+
if (!scopes) workerOwners.set(context, scopes = new WeakMap());
|
|
41
|
+
let variants = scopes.get(worker);
|
|
42
|
+
if (!variants) scopes.set(worker, variants = new Map());
|
|
43
|
+
const key = JSON.stringify([
|
|
44
|
+
context.server ? language : void 0,
|
|
45
|
+
contextKeys
|
|
46
|
+
]);
|
|
47
|
+
let variant = variants.get(key);
|
|
48
|
+
if (!variant) variants.set(key, variant = {});
|
|
49
|
+
owner = variant;
|
|
50
|
+
}
|
|
51
|
+
let Remote = components.get(owner);
|
|
52
|
+
if (!Remote) {
|
|
53
|
+
Remote = lazy(async ()=>{
|
|
54
|
+
try {
|
|
55
|
+
if (context.server && !id) throw new Error('A federatedComponent loader renders in the browser only; use a remote id to server-render it');
|
|
56
|
+
if (worker) {
|
|
57
|
+
if (!id || !language) throw new Error('Worker federation requires a remote id and the owning entry locale');
|
|
58
|
+
const separator = id.indexOf('/');
|
|
59
|
+
const remoteName = id.slice(0, separator);
|
|
60
|
+
const expose = `./${id.slice(separator + 1)}`;
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
const signal = worker.signal ? AbortSignal.any([
|
|
63
|
+
worker.signal,
|
|
64
|
+
controller.signal
|
|
65
|
+
]) : controller.signal;
|
|
66
|
+
const timeout = options.timeout ?? (context.server ? 3000 : void 0);
|
|
67
|
+
let admitted;
|
|
68
|
+
try {
|
|
69
|
+
admitted = await withTimeout(worker.transport.admit({
|
|
70
|
+
remote: remoteName,
|
|
71
|
+
expose,
|
|
72
|
+
locale: language,
|
|
73
|
+
signal
|
|
74
|
+
}), timeout, id);
|
|
75
|
+
} finally{
|
|
76
|
+
controller.abort();
|
|
77
|
+
}
|
|
78
|
+
const BrowserRemote = lazy(async ()=>{
|
|
79
|
+
const { module, assets } = await loadFederatedModule(context.instance, id);
|
|
80
|
+
if ('function' != typeof module?.default) throw new TypeError('A federated Octane module must default-export a native component');
|
|
81
|
+
const component = module.default;
|
|
82
|
+
const Loaded = (props, scope, extra)=>{
|
|
83
|
+
for (const href of assets?.css ?? [])preinit(href, {
|
|
84
|
+
as: 'style',
|
|
85
|
+
precedence: 'federated',
|
|
86
|
+
...void 0 === context.nonce ? {} : {
|
|
87
|
+
nonce: context.nonce
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
return component(props, scope, extra);
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
default: Loaded
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
const boundary = externalSnapshotBoundary({
|
|
97
|
+
authority: admitted.coordinate.native,
|
|
98
|
+
component: BrowserRemote,
|
|
99
|
+
contextKeys,
|
|
100
|
+
timeoutMs: timeout,
|
|
101
|
+
async snapshot (request, signal) {
|
|
102
|
+
const response = await worker.transport.request({
|
|
103
|
+
remote: remoteName,
|
|
104
|
+
expose,
|
|
105
|
+
locale: language,
|
|
106
|
+
payload: serializeExternalSnapshotRequest(request),
|
|
107
|
+
signal
|
|
108
|
+
});
|
|
109
|
+
return decodeExternalSnapshot(response.payload);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
const Loaded = (props)=>{
|
|
113
|
+
for (const href of admitted.styles)preinit(href, {
|
|
114
|
+
as: 'style',
|
|
115
|
+
precedence: 'federated',
|
|
116
|
+
...void 0 === context.nonce ? {} : {
|
|
117
|
+
nonce: context.nonce
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return createElement(boundary, props);
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
default: Loaded
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const { module, assets } = await withTimeout(Promise.resolve().then(()=>id ? loadFederatedModule(context.instance, id) : remote().then((module)=>({
|
|
127
|
+
module,
|
|
128
|
+
assets: void 0
|
|
129
|
+
}))), options.timeout ?? (context.server ? 3000 : void 0), id ?? 'loader');
|
|
130
|
+
if ('function' != typeof module?.default) throw new TypeError('A federated Octane module must default-export a native component');
|
|
131
|
+
if (context.server && !assets) throw new Error(`Cannot server-render ${id}: the host has no browser assets for this remote`);
|
|
132
|
+
const component = publisherBoundary(module.default, {
|
|
133
|
+
publisherKey: id ?? 'ultramodern:browser-loader'
|
|
134
|
+
});
|
|
135
|
+
const Loaded = (props)=>{
|
|
136
|
+
useEffect(()=>{
|
|
137
|
+
if (context.server || !id || !context.instance) return;
|
|
138
|
+
const register = Reflect.get(context.instance, Symbol.for('ultramodern.federation.browser-dev-boundary.v1'));
|
|
139
|
+
if ('function' != typeof register) return;
|
|
140
|
+
return register(id, module.default, component);
|
|
141
|
+
}, [], publisherRegistrationEffectSlot);
|
|
142
|
+
for (const href of assets?.css ?? [])preinit(href, {
|
|
143
|
+
as: 'style',
|
|
144
|
+
precedence: 'federated',
|
|
145
|
+
...void 0 === context.nonce ? {} : {
|
|
146
|
+
nonce: context.nonce
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return createElement(component, props);
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
default: Loaded
|
|
153
|
+
};
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (!context.server) components.delete(owner);
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
components.set(owner, Remote);
|
|
160
|
+
}
|
|
161
|
+
return createElement(Suspense, {
|
|
162
|
+
fallback: options.fallback,
|
|
163
|
+
children: createElement(Remote, props)
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
export { federatedComponent };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createElement, hookSlots, useSyncExternalStore } from "octane";
|
|
2
|
+
import { I18nContext } from "./context.mjs";
|
|
3
|
+
const languageStoreSlot = Symbol(hookSlots(1));
|
|
4
|
+
function I18nProvider(props) {
|
|
5
|
+
const instance = props.instance;
|
|
6
|
+
const language = useSyncExternalStore((notify)=>{
|
|
7
|
+
instance.on?.('languageChanged', notify);
|
|
8
|
+
return ()=>instance.off?.('languageChanged', notify);
|
|
9
|
+
}, ()=>instance.language, ()=>instance.language, languageStoreSlot);
|
|
10
|
+
const value = {
|
|
11
|
+
instance: props.instance,
|
|
12
|
+
languages: props.languages,
|
|
13
|
+
localisedUrls: props.localisedUrls,
|
|
14
|
+
language
|
|
15
|
+
};
|
|
16
|
+
return createElement(I18nContext, {
|
|
17
|
+
value,
|
|
18
|
+
children: props.children
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export { I18nProvider };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { configuredLanguage, localizePath } from "@modern-js/i18n-runtime-extensions/paths";
|
|
2
|
+
import { createElement, hookSlots, useContext } from "octane";
|
|
3
|
+
import { Link, useLocation, useRouter } from "../router.mjs";
|
|
4
|
+
import { I18nContext } from "./context.mjs";
|
|
5
|
+
const targetLocationSlot = Symbol(hookSlots(1));
|
|
6
|
+
function LocalizedLink(props) {
|
|
7
|
+
const context = useContext(I18nContext);
|
|
8
|
+
if (!context) throw new Error('[renderer-octane] LocalizedLink was used outside of an I18nProvider.');
|
|
9
|
+
const router = useRouter();
|
|
10
|
+
const location = useLocation(void 0, targetLocationSlot);
|
|
11
|
+
const { language: requestedLanguage, to, children, ...options } = props;
|
|
12
|
+
const language = void 0 === requestedLanguage ? context.language : configuredLanguage(requestedLanguage, context.languages);
|
|
13
|
+
const externalTarget = to.startsWith('//') || URL.canParse(to);
|
|
14
|
+
const pathname = externalTarget ? to : localizePath(router.buildLocation({
|
|
15
|
+
to,
|
|
16
|
+
from: options.from,
|
|
17
|
+
params: options.params,
|
|
18
|
+
unsafeRelative: options.unsafeRelative,
|
|
19
|
+
_fromLocation: location
|
|
20
|
+
}).pathname, language, {
|
|
21
|
+
languages: [
|
|
22
|
+
...context.languages
|
|
23
|
+
],
|
|
24
|
+
localisedUrls: context.localisedUrls
|
|
25
|
+
});
|
|
26
|
+
return createElement(Link, {
|
|
27
|
+
...options,
|
|
28
|
+
to: router.options.rewrite ? to : pathname,
|
|
29
|
+
...router.options.rewrite && !externalTarget ? {
|
|
30
|
+
mask: {
|
|
31
|
+
to: pathname,
|
|
32
|
+
inheritSearch: true,
|
|
33
|
+
hash: options.hash,
|
|
34
|
+
state: options.state
|
|
35
|
+
}
|
|
36
|
+
} : {},
|
|
37
|
+
...language === context.language ? {} : {
|
|
38
|
+
hreflang: language
|
|
39
|
+
}
|
|
40
|
+
}, children);
|
|
41
|
+
}
|
|
42
|
+
export { LocalizedLink };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createRequestLanguageRedirect, detectRequestLanguage, languageFromPathname, resolveRequestLanguage } from "@modern-js/i18n-runtime-extensions/request-language";
|
|
2
|
+
export { I18N_SSR_HANDOFF_ELEMENT_ID, createI18nSsrHandoffInlineData, readI18nSsrHandoff } from "@modern-js/i18n-runtime-extensions/ssrLanguageHandoff";
|
|
3
|
+
export { createI18nUrlRewrite } from "@modern-js/i18n-runtime-extensions/urlRewrite";
|
|
4
|
+
export { I18nContext } from "./context.mjs";
|
|
5
|
+
export { I18nProvider } from "./I18nProvider.mjs";
|
|
6
|
+
export { LocalizedLink } from "./LocalizedLink.mjs";
|
|
7
|
+
export { useLatestLanguageSync } from "./languageSync.mjs";
|
|
8
|
+
export { useI18n } from "./useI18n.mjs";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createLatestLanguageSyncBinding } from "@modern-js/i18n-runtime-extensions/language-sync/controller";
|
|
2
|
+
import { hookSlots, useCallback, useEffect, useRef } from "octane";
|
|
3
|
+
const bindingSlot = Symbol(hookSlots(1));
|
|
4
|
+
const callbacksSlot = Symbol(hookSlots(1));
|
|
5
|
+
const activationSlot = Symbol(hookSlots(1));
|
|
6
|
+
const requestSlot = Symbol(hookSlots(1));
|
|
7
|
+
const retrySlot = Symbol(hookSlots(1));
|
|
8
|
+
const requestCallbackSlot = Symbol(hookSlots(1));
|
|
9
|
+
const useLatestLanguageSync = ({ changeLanguage, commitLanguage, desiredLanguage, enabled = true, policy, readLanguage, reportFailure, target })=>{
|
|
10
|
+
const bindingRef = useRef(null, bindingSlot);
|
|
11
|
+
if (null === bindingRef.current) bindingRef.current = createLatestLanguageSyncBinding(policy);
|
|
12
|
+
const binding = bindingRef.current;
|
|
13
|
+
useEffect(()=>{
|
|
14
|
+
binding.updateCallbacks({
|
|
15
|
+
changeLanguage,
|
|
16
|
+
commitLanguage,
|
|
17
|
+
readLanguage,
|
|
18
|
+
reportFailure
|
|
19
|
+
});
|
|
20
|
+
}, void 0, callbacksSlot);
|
|
21
|
+
useEffect(()=>{
|
|
22
|
+
if (!enabled || !target) return;
|
|
23
|
+
binding.activate(target);
|
|
24
|
+
return ()=>binding.deactivate();
|
|
25
|
+
}, [
|
|
26
|
+
binding,
|
|
27
|
+
enabled,
|
|
28
|
+
target
|
|
29
|
+
], activationSlot);
|
|
30
|
+
useEffect(()=>{
|
|
31
|
+
if (!enabled || !target || !desiredLanguage) return void binding.clearRequest();
|
|
32
|
+
binding.request(desiredLanguage);
|
|
33
|
+
}, [
|
|
34
|
+
binding,
|
|
35
|
+
desiredLanguage,
|
|
36
|
+
enabled,
|
|
37
|
+
target
|
|
38
|
+
], requestSlot);
|
|
39
|
+
useEffect(()=>{
|
|
40
|
+
if (!enabled || !target || !desiredLanguage || "u" < typeof window) return;
|
|
41
|
+
const retryCurrentIntent = ()=>binding.request(desiredLanguage);
|
|
42
|
+
const retryVisibleIntent = ()=>{
|
|
43
|
+
if ('visible' === document.visibilityState) retryCurrentIntent();
|
|
44
|
+
};
|
|
45
|
+
window.addEventListener('online', retryCurrentIntent);
|
|
46
|
+
document.addEventListener('visibilitychange', retryVisibleIntent);
|
|
47
|
+
return ()=>{
|
|
48
|
+
window.removeEventListener('online', retryCurrentIntent);
|
|
49
|
+
document.removeEventListener('visibilitychange', retryVisibleIntent);
|
|
50
|
+
};
|
|
51
|
+
}, [
|
|
52
|
+
binding,
|
|
53
|
+
desiredLanguage,
|
|
54
|
+
enabled,
|
|
55
|
+
target
|
|
56
|
+
], retrySlot);
|
|
57
|
+
return useCallback((language)=>binding.request(language), [
|
|
58
|
+
binding
|
|
59
|
+
], requestCallbackSlot);
|
|
60
|
+
};
|
|
61
|
+
export { useLatestLanguageSync };
|
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { configuredLanguage, localizePath } from "@modern-js/i18n-runtime-extensions/paths";
|
|
2
|
+
import { languageFromPathname } from "@modern-js/i18n-runtime-extensions/request-language";
|
|
3
|
+
import { useContext } from "octane";
|
|
4
|
+
import { useRouter } from "../router.mjs";
|
|
5
|
+
import { I18nContext } from "./context.mjs";
|
|
6
|
+
const languageSwitches = new WeakMap();
|
|
7
|
+
function useI18n() {
|
|
8
|
+
const context = useContext(I18nContext);
|
|
9
|
+
if (!context) throw new Error('[renderer-octane] useI18n() was called outside of an I18nProvider.');
|
|
10
|
+
const { instance, languages, localisedUrls, language } = context;
|
|
11
|
+
const t = (key, options)=>instance.t(key, options);
|
|
12
|
+
const router = useRouter({
|
|
13
|
+
warn: false
|
|
14
|
+
});
|
|
15
|
+
const changeLanguage = (requested)=>{
|
|
16
|
+
let nextLanguage;
|
|
17
|
+
try {
|
|
18
|
+
nextLanguage = configuredLanguage(requested, languages);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
return Promise.reject(error);
|
|
21
|
+
}
|
|
22
|
+
const state = languageSwitches.get(instance) ?? {
|
|
23
|
+
latest: 0,
|
|
24
|
+
queue: Promise.resolve()
|
|
25
|
+
};
|
|
26
|
+
languageSwitches.set(instance, state);
|
|
27
|
+
const generation = ++state.latest;
|
|
28
|
+
const run = state.queue.then(async ()=>{
|
|
29
|
+
if (state.latest !== generation) return;
|
|
30
|
+
const committed = instance.language;
|
|
31
|
+
const publicLanguage = ()=>router ? languageFromPathname(new URL(router.state.location.publicHref, 'http://localhost').pathname, languages, router.options.basepath) : void 0;
|
|
32
|
+
const reconcile = async ()=>{
|
|
33
|
+
const language = publicLanguage() ?? committed;
|
|
34
|
+
if (instance.language !== language) await instance.changeLanguage?.(language);
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
await instance.changeLanguage?.(nextLanguage);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
await reconcile();
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
if (!router) return;
|
|
43
|
+
const href = localizePath(router.state.location.href, nextLanguage, {
|
|
44
|
+
languages: [
|
|
45
|
+
...languages
|
|
46
|
+
],
|
|
47
|
+
localisedUrls
|
|
48
|
+
});
|
|
49
|
+
try {
|
|
50
|
+
await router.navigate({
|
|
51
|
+
to: '.',
|
|
52
|
+
href,
|
|
53
|
+
replace: true
|
|
54
|
+
});
|
|
55
|
+
} catch (error) {
|
|
56
|
+
await reconcile();
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
if (publicLanguage() !== nextLanguage) await reconcile();
|
|
60
|
+
});
|
|
61
|
+
state.queue = run.catch(()=>{});
|
|
62
|
+
return run;
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
t,
|
|
66
|
+
language,
|
|
67
|
+
languages,
|
|
68
|
+
instance,
|
|
69
|
+
changeLanguage
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export { useI18n };
|