@bleedingdev/modern-js-runtime-extensions 3.9.0-ultramodern.9 → 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/cjs/{reactContext.js → localisedUrls/config.js} +14 -10
- package/dist/cjs/localisedUrls/index.js +95 -0
- package/dist/cjs/localisedUrls/normalise.js +87 -0
- package/dist/cjs/localisedUrls/pathname.js +68 -0
- package/dist/cjs/localisedUrls/patterns.js +214 -0
- package/dist/cjs/localisedUrls/redirect.js +104 -0
- package/dist/cjs/localisedUrls/resolve.js +78 -0
- package/dist/cjs/localisedUrls/routeIdentity.js +92 -0
- package/dist/cjs/localisedUrls/routes.js +217 -0
- package/dist/cjs/localisedUrls/types.js +18 -0
- package/dist/cjs/rendererHead.js +18 -5
- package/dist/cjs/request-policy/policyCore.js +1 -1
- package/dist/cjs/request-policy/transport.js +2 -2
- package/dist/cjs/routerState.js +85 -77
- package/dist/cjs/routerStatePlugin.js +63 -9
- package/dist/cjs/server-config/serverSource.js +18 -0
- package/dist/esm/localisedUrls/config.mjs +11 -0
- package/dist/esm/localisedUrls/index.mjs +8 -0
- package/dist/esm/localisedUrls/normalise.mjs +37 -0
- package/dist/esm/localisedUrls/pathname.mjs +24 -0
- package/dist/esm/localisedUrls/patterns.mjs +152 -0
- package/dist/esm/localisedUrls/redirect.mjs +57 -0
- package/dist/esm/localisedUrls/resolve.mjs +37 -0
- package/dist/esm/localisedUrls/routeIdentity.mjs +54 -0
- package/dist/esm/localisedUrls/routes.mjs +176 -0
- package/dist/esm/localisedUrls/types.mjs +0 -0
- package/dist/esm/rendererHead.mjs +14 -5
- package/dist/esm/request-policy/policyCore.mjs +1 -1
- package/dist/esm/request-policy/transport.mjs +2 -2
- package/dist/esm/routerState.mjs +81 -73
- package/dist/esm/routerStatePlugin.mjs +62 -11
- package/dist/esm/server-config/serverSource.mjs +0 -0
- package/dist/esm-node/localisedUrls/config.mjs +12 -0
- package/dist/esm-node/localisedUrls/index.mjs +9 -0
- package/dist/esm-node/localisedUrls/normalise.mjs +38 -0
- package/dist/esm-node/localisedUrls/pathname.mjs +25 -0
- package/dist/esm-node/localisedUrls/patterns.mjs +153 -0
- package/dist/esm-node/localisedUrls/redirect.mjs +58 -0
- package/dist/esm-node/localisedUrls/resolve.mjs +38 -0
- package/dist/esm-node/localisedUrls/routeIdentity.mjs +55 -0
- package/dist/esm-node/localisedUrls/routes.mjs +177 -0
- package/dist/esm-node/localisedUrls/types.mjs +1 -0
- package/dist/esm-node/rendererHead.mjs +14 -5
- package/dist/esm-node/request-policy/policyCore.mjs +1 -1
- package/dist/esm-node/request-policy/transport.mjs +2 -2
- package/dist/esm-node/routerState.mjs +81 -73
- package/dist/esm-node/routerStatePlugin.mjs +62 -11
- package/dist/esm-node/server-config/serverSource.mjs +1 -0
- package/dist/types/helmetCompat.d.ts +1 -1
- package/dist/types/localisedUrls/config.d.ts +9 -0
- package/dist/types/localisedUrls/index.d.ts +10 -0
- package/dist/types/localisedUrls/normalise.d.ts +10 -0
- package/dist/types/localisedUrls/pathname.d.ts +4 -0
- package/dist/types/localisedUrls/patterns.d.ts +11 -0
- package/dist/types/localisedUrls/redirect.d.ts +19 -0
- package/dist/types/localisedUrls/resolve.d.ts +7 -0
- package/dist/types/localisedUrls/routeIdentity.d.ts +22 -0
- package/dist/types/localisedUrls/routes.d.ts +3 -0
- package/dist/types/localisedUrls/types.d.ts +14 -0
- package/dist/types/rendererHead.d.ts +5 -3
- package/dist/types/request-policy/browser.d.ts +5 -1
- package/dist/types/request-policy/node.d.ts +5 -1
- package/dist/types/request-policy/types.d.ts +1 -1
- package/dist/types/routeHydration.d.ts +2 -2
- package/dist/types/routerProvider.d.ts +1 -3
- package/dist/types/routerState.d.ts +11 -6
- package/dist/types/routerStatePlugin.d.ts +12 -0
- package/dist/types/routerStateTypes.d.ts +47 -17
- package/dist/types/server-config/index.d.ts +1 -0
- package/dist/types/server-config/serverSource.d.ts +16 -0
- package/package.json +43 -45
- package/src/helmetCompat.ts +1 -1
- package/src/localisedUrls/config.ts +18 -0
- package/src/localisedUrls/index.ts +25 -0
- package/src/localisedUrls/normalise.ts +62 -0
- package/src/localisedUrls/pathname.ts +60 -0
- package/src/localisedUrls/patterns.ts +283 -0
- package/src/localisedUrls/redirect.ts +110 -0
- package/src/localisedUrls/resolve.ts +106 -0
- package/src/localisedUrls/routeIdentity.ts +83 -0
- package/src/localisedUrls/routes.ts +451 -0
- package/src/localisedUrls/types.ts +18 -0
- package/src/rendererHead.ts +25 -8
- package/src/request-policy/policyCore.ts +1 -1
- package/src/request-policy/transport.ts +2 -2
- package/src/request-policy/types.ts +1 -1
- package/src/routeHydration.ts +2 -2
- package/src/routerProvider.ts +1 -1
- package/src/routerState.ts +127 -115
- package/src/routerStatePlugin.ts +106 -10
- package/src/routerStateTypes.ts +47 -17
- package/src/server-config/index.ts +1 -0
- package/src/server-config/serverSource.ts +23 -0
- package/dist/cjs/buildIdentity.js +0 -73
- package/dist/esm/buildIdentity.mjs +0 -35
- package/dist/esm/reactContext.mjs +0 -7
- package/dist/esm-node/buildIdentity.mjs +0 -36
- package/dist/esm-node/reactContext.mjs +0 -8
- package/dist/types/buildIdentity.d.ts +0 -37
- package/dist/types/reactContext.d.ts +0 -6
- package/rslib.config.mts +0 -19
- package/rstest.config.mts +0 -38
- package/src/buildIdentity.ts +0 -61
- package/src/reactContext.ts +0 -16
- package/tests/buildIdentity.test.ts +0 -51
- package/tests/contextExtensions.test.ts +0 -27
- package/tests/headRuntime.test.tsx +0 -229
- package/tests/rendererHead.test.ts +0 -187
- package/tests/request-policy/browser-policy.test.ts +0 -108
- package/tests/request-policy/client-isolation.test.ts +0 -109
- package/tests/request-policy/node-policy.test.ts +0 -447
- package/tests/request-policy/policyCore.test.ts +0 -129
- package/tests/request-policy/requestContext.test.ts +0 -66
- package/tests/request-policy/requestFactory.outbound-contract.test.ts +0 -290
- package/tests/request-policy/traceparent.test.ts +0 -52
- package/tests/request-policy/transport-retry.test.ts +0 -367
- package/tests/routeHydration.test.ts +0 -180
- package/tests/routerPrefetchPolicy.test.ts +0 -102
- package/tests/routerProvider.test.ts +0 -29
- package/tests/routerState.test.ts +0 -78
- package/tests/routerStatePlugin.test.ts +0 -63
- package/tests/runtimeContextProjection.test.ts +0 -83
- package/tests/safeFailure.test.ts +0 -136
- package/tests/shellStream.test.ts +0 -122
- package/tests/workspaceEvents.test.ts +0 -221
- package/tsconfig.json +0 -10
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import "node:module";
|
|
2
|
-
function readBuildValue(read, fallback) {
|
|
3
|
-
try {
|
|
4
|
-
return read();
|
|
5
|
-
} catch (error) {
|
|
6
|
-
if (null !== error && 'object' == typeof error && 'name' in error && 'ReferenceError' === error.name) return fallback;
|
|
7
|
-
throw error;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
function resolveUltramodernBuildArtifact(artifact, readers) {
|
|
11
|
-
const buildMarker = readers ? readBuildValue(readers.buildMarker, artifact.deliveryUnit.buildMarker) : artifact.deliveryUnit.buildMarker;
|
|
12
|
-
const sourceRevision = readers ? readBuildValue(readers.sourceRevision, artifact.deliveryUnit.sourceRevision) : artifact.deliveryUnit.sourceRevision;
|
|
13
|
-
const identity = {
|
|
14
|
-
build: buildMarker,
|
|
15
|
-
buildMarker,
|
|
16
|
-
sourceRevision
|
|
17
|
-
};
|
|
18
|
-
return {
|
|
19
|
-
...artifact,
|
|
20
|
-
deliveryUnit: {
|
|
21
|
-
...artifact.deliveryUnit,
|
|
22
|
-
...identity
|
|
23
|
-
},
|
|
24
|
-
surfaces: {
|
|
25
|
-
api: {
|
|
26
|
-
...artifact.surfaces.api,
|
|
27
|
-
...identity
|
|
28
|
-
},
|
|
29
|
-
ui: {
|
|
30
|
-
...artifact.surfaces.ui,
|
|
31
|
-
...identity
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
export { resolveUltramodernBuildArtifact };
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import "node:module";
|
|
2
|
-
function getRuntimeReactContext(kind, create) {
|
|
3
|
-
const key = Symbol.for(`@modern-js/runtime:react-context:v1:${kind}`);
|
|
4
|
-
const contexts = globalThis;
|
|
5
|
-
contexts[key] ??= create();
|
|
6
|
-
return contexts[key];
|
|
7
|
-
}
|
|
8
|
-
export { getRuntimeReactContext };
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
interface BuildIdentity {
|
|
2
|
-
readonly build: string;
|
|
3
|
-
readonly buildMarker: string;
|
|
4
|
-
readonly sourceRevision: string;
|
|
5
|
-
}
|
|
6
|
-
interface BuildArtifact {
|
|
7
|
-
readonly deliveryUnit: BuildIdentity;
|
|
8
|
-
readonly surfaces: {
|
|
9
|
-
readonly api: BuildIdentity;
|
|
10
|
-
readonly ui: BuildIdentity;
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
interface BuildIdentityReaders {
|
|
14
|
-
readonly buildMarker: () => string;
|
|
15
|
-
readonly sourceRevision: () => string;
|
|
16
|
-
}
|
|
17
|
-
/** Read compiler constants in the caller, even when this package is external. */
|
|
18
|
-
export declare function resolveUltramodernBuildArtifact<Artifact extends BuildArtifact>(artifact: Artifact, readers?: BuildIdentityReaders): Artifact & {
|
|
19
|
-
deliveryUnit: {
|
|
20
|
-
build: string;
|
|
21
|
-
buildMarker: string;
|
|
22
|
-
sourceRevision: string;
|
|
23
|
-
};
|
|
24
|
-
surfaces: {
|
|
25
|
-
api: {
|
|
26
|
-
build: string;
|
|
27
|
-
buildMarker: string;
|
|
28
|
-
sourceRevision: string;
|
|
29
|
-
};
|
|
30
|
-
ui: {
|
|
31
|
-
build: string;
|
|
32
|
-
buildMarker: string;
|
|
33
|
-
sourceRevision: string;
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
|
-
export {};
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Federation can bundle a runtime subpath separately from its shared root.
|
|
3
|
-
* Share React context identity across those copies, leaving request values to
|
|
4
|
-
* React's providers. Public and internal contexts retain separate identities.
|
|
5
|
-
*/
|
|
6
|
-
export declare function getRuntimeReactContext<T>(kind: 'public' | 'internal', create: () => T): T;
|
package/rslib.config.mts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { RSLIB_CODE_ENTRY_GLOB, rslibConfig } from '@modern-js/rslib';
|
|
2
|
-
import { defineConfig } from '@rslib/core';
|
|
3
|
-
|
|
4
|
-
export default defineConfig({
|
|
5
|
-
...rslibConfig,
|
|
6
|
-
lib: rslibConfig.lib.map(lib =>
|
|
7
|
-
lib.id === 'esm-web'
|
|
8
|
-
? {
|
|
9
|
-
...lib,
|
|
10
|
-
source: {
|
|
11
|
-
...lib.source,
|
|
12
|
-
entry: {
|
|
13
|
-
index: [RSLIB_CODE_ENTRY_GLOB, '!./src/request-policy/node.ts'],
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
}
|
|
17
|
-
: lib,
|
|
18
|
-
),
|
|
19
|
-
});
|
package/rstest.config.mts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { ProjectConfig } from '@rstest/core';
|
|
2
|
-
import { withTestPreset } from '@scripts/rstest-config';
|
|
3
|
-
|
|
4
|
-
const commonConfig: ProjectConfig = {
|
|
5
|
-
root: __dirname,
|
|
6
|
-
globals: true,
|
|
7
|
-
setupFiles: ['@scripts/rstest-config/setup.ts'],
|
|
8
|
-
tools: {
|
|
9
|
-
swc: {
|
|
10
|
-
jsc: {
|
|
11
|
-
transform: {
|
|
12
|
-
react: {
|
|
13
|
-
runtime: 'automatic',
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const browserPolicyProject = withTestPreset({
|
|
22
|
-
name: 'runtime-extensions-request-policy-browser',
|
|
23
|
-
testEnvironment: 'happy-dom',
|
|
24
|
-
extends: commonConfig,
|
|
25
|
-
include: ['tests/request-policy/browser-policy.test.ts'],
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
export default {
|
|
29
|
-
projects: [
|
|
30
|
-
withTestPreset({
|
|
31
|
-
name: 'runtime-extensions-node',
|
|
32
|
-
exclude: ['tests/request-policy/browser-policy.test.ts'],
|
|
33
|
-
testEnvironment: 'node',
|
|
34
|
-
extends: commonConfig,
|
|
35
|
-
}),
|
|
36
|
-
browserPolicyProject,
|
|
37
|
-
],
|
|
38
|
-
};
|
package/src/buildIdentity.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
interface BuildIdentity {
|
|
2
|
-
readonly build: string;
|
|
3
|
-
readonly buildMarker: string;
|
|
4
|
-
readonly sourceRevision: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
interface BuildArtifact {
|
|
8
|
-
readonly deliveryUnit: BuildIdentity;
|
|
9
|
-
readonly surfaces: {
|
|
10
|
-
readonly api: BuildIdentity;
|
|
11
|
-
readonly ui: BuildIdentity;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface BuildIdentityReaders {
|
|
16
|
-
readonly buildMarker: () => string;
|
|
17
|
-
readonly sourceRevision: () => string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function readBuildValue(read: () => string, fallback: string): string {
|
|
21
|
-
try {
|
|
22
|
-
return read();
|
|
23
|
-
} catch (error) {
|
|
24
|
-
// An uncompiled generated module has no injected constants. Check the
|
|
25
|
-
// error tag across realms, since source validators may evaluate in a VM.
|
|
26
|
-
if (
|
|
27
|
-
error !== null &&
|
|
28
|
-
typeof error === 'object' &&
|
|
29
|
-
'name' in error &&
|
|
30
|
-
error.name === 'ReferenceError'
|
|
31
|
-
) {
|
|
32
|
-
return fallback;
|
|
33
|
-
}
|
|
34
|
-
throw error;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Read compiler constants in the caller, even when this package is external. */
|
|
39
|
-
export function resolveUltramodernBuildArtifact<Artifact extends BuildArtifact>(
|
|
40
|
-
artifact: Artifact,
|
|
41
|
-
readers?: BuildIdentityReaders,
|
|
42
|
-
) {
|
|
43
|
-
const buildMarker = readers
|
|
44
|
-
? readBuildValue(readers.buildMarker, artifact.deliveryUnit.buildMarker)
|
|
45
|
-
: artifact.deliveryUnit.buildMarker;
|
|
46
|
-
const sourceRevision = readers
|
|
47
|
-
? readBuildValue(
|
|
48
|
-
readers.sourceRevision,
|
|
49
|
-
artifact.deliveryUnit.sourceRevision,
|
|
50
|
-
)
|
|
51
|
-
: artifact.deliveryUnit.sourceRevision;
|
|
52
|
-
const identity = { build: buildMarker, buildMarker, sourceRevision };
|
|
53
|
-
return {
|
|
54
|
-
...artifact,
|
|
55
|
-
deliveryUnit: { ...artifact.deliveryUnit, ...identity },
|
|
56
|
-
surfaces: {
|
|
57
|
-
api: { ...artifact.surfaces.api, ...identity },
|
|
58
|
-
ui: { ...artifact.surfaces.ui, ...identity },
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
}
|
package/src/reactContext.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Federation can bundle a runtime subpath separately from its shared root.
|
|
3
|
-
* Share React context identity across those copies, leaving request values to
|
|
4
|
-
* React's providers. Public and internal contexts retain separate identities.
|
|
5
|
-
*/
|
|
6
|
-
export function getRuntimeReactContext<T>(
|
|
7
|
-
kind: 'public' | 'internal',
|
|
8
|
-
create: () => T,
|
|
9
|
-
): T {
|
|
10
|
-
const key = Symbol.for(`@modern-js/runtime:react-context:v1:${kind}`);
|
|
11
|
-
const contexts = globalThis as typeof globalThis & {
|
|
12
|
-
[key: symbol]: unknown;
|
|
13
|
-
};
|
|
14
|
-
contexts[key] ??= create();
|
|
15
|
-
return contexts[key] as T;
|
|
16
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { resolveUltramodernBuildArtifact } from '../src/buildIdentity';
|
|
2
|
-
|
|
3
|
-
const artifact = {
|
|
4
|
-
deliveryUnit: {
|
|
5
|
-
build: 'original',
|
|
6
|
-
buildMarker: 'original',
|
|
7
|
-
sourceRevision: 'workspace',
|
|
8
|
-
unitId: 'shop/catalog',
|
|
9
|
-
},
|
|
10
|
-
surfaces: {
|
|
11
|
-
api: {
|
|
12
|
-
build: 'original',
|
|
13
|
-
buildMarker: 'original',
|
|
14
|
-
sourceRevision: 'workspace',
|
|
15
|
-
surface: 'api',
|
|
16
|
-
},
|
|
17
|
-
ui: {
|
|
18
|
-
build: 'original',
|
|
19
|
-
buildMarker: 'original',
|
|
20
|
-
sourceRevision: 'workspace',
|
|
21
|
-
surface: 'ui',
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
} as const;
|
|
25
|
-
|
|
26
|
-
const readers = {
|
|
27
|
-
buildMarker: () => 'compiled-build',
|
|
28
|
-
sourceRevision: () => 'compiled-revision',
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
it('falls back only for missing compiler references and preserves each supplied value', () => {
|
|
32
|
-
expect(resolveUltramodernBuildArtifact(artifact)).toEqual(artifact);
|
|
33
|
-
|
|
34
|
-
const result = resolveUltramodernBuildArtifact(artifact, {
|
|
35
|
-
buildMarker: readers.buildMarker,
|
|
36
|
-
sourceRevision: () => {
|
|
37
|
-
throw new ReferenceError('ULTRAMODERN_SOURCE_REVISION is not defined');
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
expect(result.deliveryUnit.buildMarker).toBe('compiled-build');
|
|
41
|
-
expect(result.deliveryUnit.sourceRevision).toBe('workspace');
|
|
42
|
-
const failure = new Error('reader failed');
|
|
43
|
-
expect(() =>
|
|
44
|
-
resolveUltramodernBuildArtifact(artifact, {
|
|
45
|
-
...readers,
|
|
46
|
-
buildMarker: () => {
|
|
47
|
-
throw failure;
|
|
48
|
-
},
|
|
49
|
-
}),
|
|
50
|
-
).toThrow(failure);
|
|
51
|
-
});
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { createRuntimeContextExtension } from '../src/contextExtensions';
|
|
2
|
-
|
|
3
|
-
describe('runtime context extensions', () => {
|
|
4
|
-
it('does not leak into string-key enumeration of the context', () => {
|
|
5
|
-
const extension = createRuntimeContextExtension<string>('test:hidden');
|
|
6
|
-
const context = {};
|
|
7
|
-
const keysBefore = Object.keys(context);
|
|
8
|
-
|
|
9
|
-
extension.set(context, 'secret');
|
|
10
|
-
|
|
11
|
-
expect(Object.keys(context)).toEqual(keysBefore);
|
|
12
|
-
expect(JSON.stringify(context)).not.toContain('secret');
|
|
13
|
-
for (const key in context) {
|
|
14
|
-
expect(typeof key).toBe('string');
|
|
15
|
-
expect((context as Record<string, unknown>)[key]).not.toBe('secret');
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('survives object spreads so SSR context copies keep their extensions', () => {
|
|
20
|
-
const extension = createRuntimeContextExtension<string>('test:spread');
|
|
21
|
-
const context = {};
|
|
22
|
-
extension.set(context, 'carried');
|
|
23
|
-
|
|
24
|
-
const copy = { ...context };
|
|
25
|
-
expect(extension.get(copy)).toBe('carried');
|
|
26
|
-
});
|
|
27
|
-
});
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { renderToString } from 'react-dom/server';
|
|
3
|
-
import { createHeadRuntime } from '../src/headRuntime';
|
|
4
|
-
import { ensureHelmetContext, getHelmetContext } from '../src/helmetContext';
|
|
5
|
-
import * as rendererHead from '../src/rendererHead';
|
|
6
|
-
|
|
7
|
-
const { Head: Helmet, wrapServerRoot } = createHeadRuntime();
|
|
8
|
-
const getHelmetData = (context: object) => getHelmetContext(context)?.helmet;
|
|
9
|
-
|
|
10
|
-
const createServerContext = () => ({ isBrowser: false }) as any;
|
|
11
|
-
|
|
12
|
-
const renderWithContext = (context: any, node: React.ReactNode) => {
|
|
13
|
-
rendererHead.beginHeadRender(context);
|
|
14
|
-
try {
|
|
15
|
-
const html = renderToString(
|
|
16
|
-
wrapServerRoot(node, {
|
|
17
|
-
runtimeContext: context,
|
|
18
|
-
helmetContext: ensureHelmetContext(context),
|
|
19
|
-
}),
|
|
20
|
-
);
|
|
21
|
-
return rendererHead.completeHeadRender(context, html);
|
|
22
|
-
} catch (error) {
|
|
23
|
-
rendererHead.abortHeadRender(context);
|
|
24
|
-
throw error;
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
describe('server Helmet collection', () => {
|
|
29
|
-
it('keeps independently registered adapters scoped to their own providers', () => {
|
|
30
|
-
const outer = createHeadRuntime();
|
|
31
|
-
const inner = createHeadRuntime();
|
|
32
|
-
const outerContext = {};
|
|
33
|
-
const innerContext = {};
|
|
34
|
-
|
|
35
|
-
renderToString(
|
|
36
|
-
outer.wrapServerRoot(
|
|
37
|
-
inner.wrapServerRoot(
|
|
38
|
-
<>
|
|
39
|
-
<outer.Head>
|
|
40
|
-
<title>outer application</title>
|
|
41
|
-
</outer.Head>
|
|
42
|
-
<inner.Head>
|
|
43
|
-
<title>inner application</title>
|
|
44
|
-
</inner.Head>
|
|
45
|
-
</>,
|
|
46
|
-
{
|
|
47
|
-
runtimeContext: innerContext,
|
|
48
|
-
helmetContext: ensureHelmetContext(innerContext),
|
|
49
|
-
},
|
|
50
|
-
),
|
|
51
|
-
{
|
|
52
|
-
runtimeContext: outerContext,
|
|
53
|
-
helmetContext: ensureHelmetContext(outerContext),
|
|
54
|
-
},
|
|
55
|
-
),
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
expect(getHelmetData(outerContext)?.title.toString()).toBe(
|
|
59
|
-
'<title data-rh="true">outer application</title>',
|
|
60
|
-
);
|
|
61
|
-
expect(getHelmetData(innerContext)?.title.toString()).toBe(
|
|
62
|
-
'<title data-rh="true">inner application</title>',
|
|
63
|
-
);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('excludes Helmet records from a discarded Suspense primary branch', () => {
|
|
67
|
-
const context = createServerContext();
|
|
68
|
-
const never = new Promise<never>(() => {});
|
|
69
|
-
|
|
70
|
-
const SuspendForever = (): null => {
|
|
71
|
-
throw never;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const html = renderWithContext(
|
|
75
|
-
context,
|
|
76
|
-
<>
|
|
77
|
-
<Helmet>
|
|
78
|
-
<meta name="outside" content="committed" />
|
|
79
|
-
</Helmet>
|
|
80
|
-
<React.Suspense
|
|
81
|
-
fallback={
|
|
82
|
-
<>
|
|
83
|
-
<Helmet>
|
|
84
|
-
<meta name="fallback" content="committed" />
|
|
85
|
-
</Helmet>
|
|
86
|
-
<span>fallback rendered</span>
|
|
87
|
-
</>
|
|
88
|
-
}
|
|
89
|
-
>
|
|
90
|
-
<Helmet>
|
|
91
|
-
<meta name="abandoned-unique" content="ghost" />
|
|
92
|
-
</Helmet>
|
|
93
|
-
<SuspendForever />
|
|
94
|
-
</React.Suspense>
|
|
95
|
-
</>,
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
expect(html).toContain('fallback rendered');
|
|
99
|
-
const meta = getHelmetData(context)!.meta.toString();
|
|
100
|
-
expect(meta).toContain('name="outside"');
|
|
101
|
-
expect(meta).toContain('name="fallback"');
|
|
102
|
-
expect(meta).not.toContain('name="abandoned-unique"');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('collects title/meta during SSR and renders nothing inline', () => {
|
|
106
|
-
const context = createServerContext();
|
|
107
|
-
const html = renderWithContext(
|
|
108
|
-
context,
|
|
109
|
-
<main>
|
|
110
|
-
<Helmet>
|
|
111
|
-
<title>Server Title</title>
|
|
112
|
-
<meta name="description" content="hello" />
|
|
113
|
-
</Helmet>
|
|
114
|
-
body
|
|
115
|
-
</main>,
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
expect(html).not.toContain('Server Title');
|
|
119
|
-
const helmet = getHelmetData(context)!;
|
|
120
|
-
expect(helmet.title.toString()).toBe(
|
|
121
|
-
'<title data-rh="true">Server Title</title>',
|
|
122
|
-
);
|
|
123
|
-
expect(helmet.meta.toString()).toBe(
|
|
124
|
-
'<meta data-rh="true" name="description" content="hello">',
|
|
125
|
-
);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('dedupes meta by primary attribute across nested Helmets, inner wins', () => {
|
|
129
|
-
const context = createServerContext();
|
|
130
|
-
renderWithContext(
|
|
131
|
-
context,
|
|
132
|
-
<>
|
|
133
|
-
<Helmet>
|
|
134
|
-
<meta name="description" content="outer" />
|
|
135
|
-
<meta name="keywords" content="modernjs" />
|
|
136
|
-
</Helmet>
|
|
137
|
-
<section>
|
|
138
|
-
<Helmet>
|
|
139
|
-
<meta name="description" content="inner" />
|
|
140
|
-
</Helmet>
|
|
141
|
-
</section>
|
|
142
|
-
</>,
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
const meta = getHelmetData(context)!.meta.toString();
|
|
146
|
-
expect(meta).toContain('content="inner"');
|
|
147
|
-
expect(meta).not.toContain('content="outer"');
|
|
148
|
-
expect(meta).toContain('content="modernjs"');
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('applies the innermost title and the innermost titleTemplate', () => {
|
|
152
|
-
const context = createServerContext();
|
|
153
|
-
renderWithContext(
|
|
154
|
-
context,
|
|
155
|
-
<>
|
|
156
|
-
<Helmet titleTemplate="%s | Site">
|
|
157
|
-
<title>Outer</title>
|
|
158
|
-
</Helmet>
|
|
159
|
-
<Helmet>
|
|
160
|
-
<title>Inner</title>
|
|
161
|
-
</Helmet>
|
|
162
|
-
</>,
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
expect(getHelmetData(context)!.title.toString()).toBe(
|
|
166
|
-
'<title data-rh="true">Inner | Site</title>',
|
|
167
|
-
);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('publishes prioritized SEO tags separately with component parity', () => {
|
|
171
|
-
const context = createServerContext();
|
|
172
|
-
renderWithContext(
|
|
173
|
-
context,
|
|
174
|
-
<Helmet prioritizeSeoTags htmlAttributes={{ class: 'app' } as any}>
|
|
175
|
-
<title>Priority Page</title>
|
|
176
|
-
<meta name="description" content="priority description" />
|
|
177
|
-
<meta name="keywords" content="ordinary keywords" />
|
|
178
|
-
<link rel="canonical" href="https://example.com/page" />
|
|
179
|
-
<link rel="stylesheet" href="/page.css" />
|
|
180
|
-
<script type="application/ld+json">{'{"name":"page"}'}</script>
|
|
181
|
-
<script src="/ordinary.js" />
|
|
182
|
-
</Helmet>,
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
const helmet = getHelmetData(context)!;
|
|
186
|
-
expect(helmet.priority.toString()).toContain('priority description');
|
|
187
|
-
expect(helmet.priority.toString()).toContain('https://example.com/page');
|
|
188
|
-
expect(helmet.priority.toString()).toContain('application/ld+json');
|
|
189
|
-
expect(helmet.meta.toString()).toContain('ordinary keywords');
|
|
190
|
-
expect(helmet.meta.toString()).not.toContain('priority description');
|
|
191
|
-
expect(helmet.link.toString()).toContain('/page.css');
|
|
192
|
-
expect(helmet.link.toString()).not.toContain('https://example.com/page');
|
|
193
|
-
expect(helmet.script.toString()).toContain('/ordinary.js');
|
|
194
|
-
expect(helmet.script.toString()).not.toContain('application/ld+json');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('is idempotent when React replays the tree (streaming SSR retry)', () => {
|
|
198
|
-
const context = createServerContext();
|
|
199
|
-
const app = (
|
|
200
|
-
<>
|
|
201
|
-
<Helmet titleTemplate="%s | Site">
|
|
202
|
-
<title>Page</title>
|
|
203
|
-
<meta name="description" content="stable" />
|
|
204
|
-
<html lang="en" />
|
|
205
|
-
</Helmet>
|
|
206
|
-
<Helmet>
|
|
207
|
-
<meta property="og:title" content="Page" />
|
|
208
|
-
</Helmet>
|
|
209
|
-
</>
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
renderWithContext(context, app);
|
|
213
|
-
const first = getHelmetData(context)!;
|
|
214
|
-
const firstSnapshot = {
|
|
215
|
-
title: first.title.toString(),
|
|
216
|
-
meta: first.meta.toString(),
|
|
217
|
-
htmlAttributes: first.htmlAttributes.toString(),
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
// Simulate a replayed render against the same per-request context.
|
|
221
|
-
renderWithContext(context, app);
|
|
222
|
-
const second = getHelmetData(context)!;
|
|
223
|
-
|
|
224
|
-
expect(second.title.toString()).toBe(firstSnapshot.title);
|
|
225
|
-
expect(second.meta.toString()).toBe(firstSnapshot.meta);
|
|
226
|
-
expect(second.htmlAttributes.toString()).toBe(firstSnapshot.htmlAttributes);
|
|
227
|
-
expect(second.meta.toString().match(/description/g)).toHaveLength(1);
|
|
228
|
-
});
|
|
229
|
-
});
|