@bleedingdev/modern-js-runtime-renderer-extensions 3.9.0-ultramodern.8 → 3.9.3-ultramodern.1
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/runtimePlugin.js +19 -6
- package/dist/esm/runtimePlugin.mjs +17 -7
- package/dist/esm-node/runtimePlugin.mjs +17 -7
- package/dist/types/runtimePlugin.d.ts +6 -0
- package/package.json +24 -19
- package/src/runtimePlugin.ts +34 -7
- package/rslib.config.mts +0 -19
- package/rstest.config.mts +0 -29
- package/tests/federatedCss.test.ts +0 -49
- package/tests/headRuntime.test.tsx +0 -163
- package/tests/nativeAssetPolicy.test.tsx +0 -377
- package/tests/nativeStreamHead.test.tsx +0 -77
- package/tests/nativeStringHead.test.tsx +0 -128
- package/tests/nativeTemplatePolicy.test.tsx +0 -366
- package/tests/scriptOrder.security.test.ts +0 -42
- package/tsconfig.json +0 -10
|
@@ -37,6 +37,7 @@ var __webpack_require__ = {};
|
|
|
37
37
|
var __webpack_exports__ = {};
|
|
38
38
|
__webpack_require__.r(__webpack_exports__);
|
|
39
39
|
__webpack_require__.d(__webpack_exports__, {
|
|
40
|
+
RENDERER_HEAD_PRE_PLUGINS: ()=>RENDERER_HEAD_PRE_PLUGINS,
|
|
40
41
|
createRendererHeadPlugin: ()=>createRendererHeadPlugin,
|
|
41
42
|
default: ()=>runtimePlugin,
|
|
42
43
|
rendererHeadPlugin: ()=>rendererHeadPlugin
|
|
@@ -57,14 +58,15 @@ function createRendererHeadPlugin(options = {}) {
|
|
|
57
58
|
api.resolveComponent((component, { name })=>'head.Helmet' === name ? head.Head : component);
|
|
58
59
|
api.transformRuntimeContext(context_projection_namespaceObject.projectRuntimeContext);
|
|
59
60
|
api.wrapRoot((Root)=>{
|
|
60
|
-
const HeadRoot = (props)=>head.wrapClientRoot(external_react_default().createElement(Root, props));
|
|
61
|
+
const HeadRoot = (props)=>head.wrapClientRoot(Root ? external_react_default().createElement(Root, props) : props?.children ?? null);
|
|
61
62
|
return HeadRoot;
|
|
62
63
|
});
|
|
63
|
-
const createState = (runtimeContext)=>{
|
|
64
|
+
const createState = ({ runtimeContext, monitors })=>{
|
|
64
65
|
const helmetContext = (0, helmet_context_namespaceObject.ensureHelmetContext)(runtimeContext);
|
|
66
|
+
const reportLateHead = 'production' === process.env.NODE_ENV ? (message)=>monitors.warn(message) : (message)=>monitors.error(message);
|
|
65
67
|
const lifecycle = {
|
|
66
68
|
beforeReact () {
|
|
67
|
-
(0, runtime_extensions_namespaceObject.beginHeadRender)(runtimeContext);
|
|
69
|
+
(0, runtime_extensions_namespaceObject.beginHeadRender)(runtimeContext, reportLateHead);
|
|
68
70
|
},
|
|
69
71
|
completedBody (html, { phase }) {
|
|
70
72
|
if ('complete' === phase) return (0, runtime_extensions_namespaceObject.completeHeadRender)(runtimeContext, html);
|
|
@@ -89,7 +91,7 @@ function createRendererHeadPlugin(options = {}) {
|
|
|
89
91
|
};
|
|
90
92
|
};
|
|
91
93
|
api.extendStringSSRCollectors(({ render })=>{
|
|
92
|
-
const state = createState(render
|
|
94
|
+
const state = createState(render);
|
|
93
95
|
return {
|
|
94
96
|
...state.lifecycle,
|
|
95
97
|
...(0, external_assetPolicy_js_namespaceObject.createAssetPolicy)(render),
|
|
@@ -102,7 +104,7 @@ function createRendererHeadPlugin(options = {}) {
|
|
|
102
104
|
const { runtimeContext, terminalMarker } = info;
|
|
103
105
|
const processNodeStream = options.processNodeStream;
|
|
104
106
|
if ('node' === info.platform && void 0 === processNodeStream) throw new Error('Node head rendering requires the Node runtime-renderer-extensions entry');
|
|
105
|
-
const state = createState(
|
|
107
|
+
const state = createState(info);
|
|
106
108
|
return {
|
|
107
109
|
...state.lifecycle,
|
|
108
110
|
...(0, external_assetPolicy_js_namespaceObject.createAssetPolicy)(info),
|
|
@@ -116,12 +118,23 @@ function createRendererHeadPlugin(options = {}) {
|
|
|
116
118
|
}
|
|
117
119
|
};
|
|
118
120
|
}
|
|
119
|
-
const
|
|
121
|
+
const RENDERER_HEAD_PRE_PLUGINS = [
|
|
122
|
+
'@modern-js/plugin-router',
|
|
123
|
+
'@modern-js/plugin-tanstack',
|
|
124
|
+
'@modern-js/plugin-state',
|
|
125
|
+
'@modern-js/plugin-i18n'
|
|
126
|
+
];
|
|
127
|
+
const rendererHeadPlugin = ()=>({
|
|
128
|
+
...createRendererHeadPlugin(),
|
|
129
|
+
pre: RENDERER_HEAD_PRE_PLUGINS
|
|
130
|
+
});
|
|
120
131
|
const runtimePlugin = rendererHeadPlugin;
|
|
132
|
+
exports.RENDERER_HEAD_PRE_PLUGINS = __webpack_exports__.RENDERER_HEAD_PRE_PLUGINS;
|
|
121
133
|
exports.createRendererHeadPlugin = __webpack_exports__.createRendererHeadPlugin;
|
|
122
134
|
exports["default"] = __webpack_exports__["default"];
|
|
123
135
|
exports.rendererHeadPlugin = __webpack_exports__.rendererHeadPlugin;
|
|
124
136
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
137
|
+
"RENDERER_HEAD_PRE_PLUGINS",
|
|
125
138
|
"createRendererHeadPlugin",
|
|
126
139
|
"default",
|
|
127
140
|
"rendererHeadPlugin"
|
|
@@ -13,14 +13,15 @@ function createRendererHeadPlugin(options = {}) {
|
|
|
13
13
|
api.resolveComponent((component, { name })=>'head.Helmet' === name ? head.Head : component);
|
|
14
14
|
api.transformRuntimeContext(projectRuntimeContext);
|
|
15
15
|
api.wrapRoot((Root)=>{
|
|
16
|
-
const HeadRoot = (props)=>head.wrapClientRoot(react.createElement(Root, props));
|
|
16
|
+
const HeadRoot = (props)=>head.wrapClientRoot(Root ? react.createElement(Root, props) : props?.children ?? null);
|
|
17
17
|
return HeadRoot;
|
|
18
18
|
});
|
|
19
|
-
const createState = (runtimeContext)=>{
|
|
19
|
+
const createState = ({ runtimeContext, monitors })=>{
|
|
20
20
|
const helmetContext = ensureHelmetContext(runtimeContext);
|
|
21
|
+
const reportLateHead = 'production' === process.env.NODE_ENV ? (message)=>monitors.warn(message) : (message)=>monitors.error(message);
|
|
21
22
|
const lifecycle = {
|
|
22
23
|
beforeReact () {
|
|
23
|
-
beginHeadRender(runtimeContext);
|
|
24
|
+
beginHeadRender(runtimeContext, reportLateHead);
|
|
24
25
|
},
|
|
25
26
|
completedBody (html, { phase }) {
|
|
26
27
|
if ('complete' === phase) return completeHeadRender(runtimeContext, html);
|
|
@@ -45,7 +46,7 @@ function createRendererHeadPlugin(options = {}) {
|
|
|
45
46
|
};
|
|
46
47
|
};
|
|
47
48
|
api.extendStringSSRCollectors(({ render })=>{
|
|
48
|
-
const state = createState(render
|
|
49
|
+
const state = createState(render);
|
|
49
50
|
return {
|
|
50
51
|
...state.lifecycle,
|
|
51
52
|
...createAssetPolicy(render),
|
|
@@ -58,7 +59,7 @@ function createRendererHeadPlugin(options = {}) {
|
|
|
58
59
|
const { runtimeContext, terminalMarker } = info;
|
|
59
60
|
const processNodeStream = options.processNodeStream;
|
|
60
61
|
if ('node' === info.platform && void 0 === processNodeStream) throw new Error('Node head rendering requires the Node runtime-renderer-extensions entry');
|
|
61
|
-
const state = createState(
|
|
62
|
+
const state = createState(info);
|
|
62
63
|
return {
|
|
63
64
|
...state.lifecycle,
|
|
64
65
|
...createAssetPolicy(info),
|
|
@@ -72,7 +73,16 @@ function createRendererHeadPlugin(options = {}) {
|
|
|
72
73
|
}
|
|
73
74
|
};
|
|
74
75
|
}
|
|
75
|
-
const
|
|
76
|
+
const RENDERER_HEAD_PRE_PLUGINS = [
|
|
77
|
+
'@modern-js/plugin-router',
|
|
78
|
+
'@modern-js/plugin-tanstack',
|
|
79
|
+
'@modern-js/plugin-state',
|
|
80
|
+
'@modern-js/plugin-i18n'
|
|
81
|
+
];
|
|
82
|
+
const rendererHeadPlugin = ()=>({
|
|
83
|
+
...createRendererHeadPlugin(),
|
|
84
|
+
pre: RENDERER_HEAD_PRE_PLUGINS
|
|
85
|
+
});
|
|
76
86
|
const runtimePlugin = rendererHeadPlugin;
|
|
77
87
|
export default runtimePlugin;
|
|
78
|
-
export { createRendererHeadPlugin, rendererHeadPlugin };
|
|
88
|
+
export { RENDERER_HEAD_PRE_PLUGINS, createRendererHeadPlugin, rendererHeadPlugin };
|
|
@@ -14,14 +14,15 @@ function createRendererHeadPlugin(options = {}) {
|
|
|
14
14
|
api.resolveComponent((component, { name })=>'head.Helmet' === name ? head.Head : component);
|
|
15
15
|
api.transformRuntimeContext(projectRuntimeContext);
|
|
16
16
|
api.wrapRoot((Root)=>{
|
|
17
|
-
const HeadRoot = (props)=>head.wrapClientRoot(react.createElement(Root, props));
|
|
17
|
+
const HeadRoot = (props)=>head.wrapClientRoot(Root ? react.createElement(Root, props) : props?.children ?? null);
|
|
18
18
|
return HeadRoot;
|
|
19
19
|
});
|
|
20
|
-
const createState = (runtimeContext)=>{
|
|
20
|
+
const createState = ({ runtimeContext, monitors })=>{
|
|
21
21
|
const helmetContext = ensureHelmetContext(runtimeContext);
|
|
22
|
+
const reportLateHead = 'production' === process.env.NODE_ENV ? (message)=>monitors.warn(message) : (message)=>monitors.error(message);
|
|
22
23
|
const lifecycle = {
|
|
23
24
|
beforeReact () {
|
|
24
|
-
beginHeadRender(runtimeContext);
|
|
25
|
+
beginHeadRender(runtimeContext, reportLateHead);
|
|
25
26
|
},
|
|
26
27
|
completedBody (html, { phase }) {
|
|
27
28
|
if ('complete' === phase) return completeHeadRender(runtimeContext, html);
|
|
@@ -46,7 +47,7 @@ function createRendererHeadPlugin(options = {}) {
|
|
|
46
47
|
};
|
|
47
48
|
};
|
|
48
49
|
api.extendStringSSRCollectors(({ render })=>{
|
|
49
|
-
const state = createState(render
|
|
50
|
+
const state = createState(render);
|
|
50
51
|
return {
|
|
51
52
|
...state.lifecycle,
|
|
52
53
|
...createAssetPolicy(render),
|
|
@@ -59,7 +60,7 @@ function createRendererHeadPlugin(options = {}) {
|
|
|
59
60
|
const { runtimeContext, terminalMarker } = info;
|
|
60
61
|
const processNodeStream = options.processNodeStream;
|
|
61
62
|
if ('node' === info.platform && void 0 === processNodeStream) throw new Error('Node head rendering requires the Node runtime-renderer-extensions entry');
|
|
62
|
-
const state = createState(
|
|
63
|
+
const state = createState(info);
|
|
63
64
|
return {
|
|
64
65
|
...state.lifecycle,
|
|
65
66
|
...createAssetPolicy(info),
|
|
@@ -73,7 +74,16 @@ function createRendererHeadPlugin(options = {}) {
|
|
|
73
74
|
}
|
|
74
75
|
};
|
|
75
76
|
}
|
|
76
|
-
const
|
|
77
|
+
const RENDERER_HEAD_PRE_PLUGINS = [
|
|
78
|
+
'@modern-js/plugin-router',
|
|
79
|
+
'@modern-js/plugin-tanstack',
|
|
80
|
+
'@modern-js/plugin-state',
|
|
81
|
+
'@modern-js/plugin-i18n'
|
|
82
|
+
];
|
|
83
|
+
const rendererHeadPlugin = ()=>({
|
|
84
|
+
...createRendererHeadPlugin(),
|
|
85
|
+
pre: RENDERER_HEAD_PRE_PLUGINS
|
|
86
|
+
});
|
|
77
87
|
const runtimePlugin = rendererHeadPlugin;
|
|
78
88
|
export default runtimePlugin;
|
|
79
|
-
export { createRendererHeadPlugin, rendererHeadPlugin };
|
|
89
|
+
export { RENDERER_HEAD_PRE_PLUGINS, createRendererHeadPlugin, rendererHeadPlugin };
|
|
@@ -3,5 +3,11 @@ export type RendererHeadPluginOptions = {
|
|
|
3
3
|
processNodeStream?: (source: NodeJS.ReadWriteStream, context: object, terminalMarker: string) => NodeJS.ReadWriteStream;
|
|
4
4
|
};
|
|
5
5
|
export declare function createRendererHeadPlugin(options?: RendererHeadPluginOptions): RuntimePlugin<{}>;
|
|
6
|
+
/**
|
|
7
|
+
* The plugins whose root this one wraps. Declaring them keeps the head
|
|
8
|
+
* provider outside their trees no matter what order the CLI emitted the
|
|
9
|
+
* runtime descriptors in.
|
|
10
|
+
*/
|
|
11
|
+
export declare const RENDERER_HEAD_PRE_PLUGINS: string[];
|
|
6
12
|
export declare const rendererHeadPlugin: () => RuntimePlugin<{}>;
|
|
7
13
|
export default rendererHeadPlugin;
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bleedingdev/modern-js-runtime-renderer-extensions",
|
|
3
3
|
"description": "UltraModern.js head rendering plugin registered through native runtime hooks.",
|
|
4
|
-
"homepage": "https://github.com/
|
|
4
|
+
"homepage": "https://github.com/ultramodernjs/ultramodern.js#readme",
|
|
5
5
|
"bugs": {
|
|
6
|
-
"url": "https://github.com/
|
|
6
|
+
"url": "https://github.com/ultramodernjs/ultramodern.js/issues"
|
|
7
7
|
},
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/
|
|
10
|
+
"url": "git+https://github.com/ultramodernjs/ultramodern.js.git",
|
|
11
11
|
"directory": "packages/runtime/renderer-extensions"
|
|
12
12
|
},
|
|
13
13
|
"license": "MIT",
|
|
14
|
-
"version": "3.9.
|
|
14
|
+
"version": "3.9.3-ultramodern.1",
|
|
15
15
|
"engines": {
|
|
16
|
-
"node": ">=26.
|
|
16
|
+
"node": ">=26.10.0"
|
|
17
17
|
},
|
|
18
18
|
"types": "./dist/types/runtimePlugin.d.ts",
|
|
19
19
|
"main": "./dist/cjs/node.js",
|
|
@@ -36,25 +36,23 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.
|
|
40
|
-
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.
|
|
39
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.3-ultramodern.1",
|
|
40
|
+
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.3-ultramodern.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@modern-js/render": "npm:@bleedingdev/modern-js-render@3.9.
|
|
44
|
-
"@modern-js/
|
|
45
|
-
"@
|
|
46
|
-
"@rslib/core": "1.0.0",
|
|
43
|
+
"@modern-js/render": "npm:@bleedingdev/modern-js-render@3.9.3-ultramodern.1",
|
|
44
|
+
"@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.3-ultramodern.1",
|
|
45
|
+
"@rslib/core": "1.0.3",
|
|
47
46
|
"@scripts/rstest-config": "2.66.0",
|
|
48
|
-
"@testing-library/dom": "^10.4.
|
|
47
|
+
"@testing-library/dom": "^10.4.2",
|
|
49
48
|
"@testing-library/react": "^16.3.3",
|
|
50
|
-
"@types/node": "^26.
|
|
51
|
-
"@types/react": "^19.
|
|
52
|
-
"@types/react-dom": "^19.
|
|
53
|
-
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
49
|
+
"@types/node": "^26.6.3",
|
|
50
|
+
"@types/react": "^19.3.0",
|
|
51
|
+
"@types/react-dom": "^19.3.0",
|
|
54
52
|
"esbuild": "^0.28.2",
|
|
55
|
-
"react": "^19.
|
|
56
|
-
"react-dom": "^19.
|
|
57
|
-
"typescript": "
|
|
53
|
+
"react": "^19.3.0",
|
|
54
|
+
"react-dom": "^19.3.0",
|
|
55
|
+
"typescript": "7.0.2"
|
|
58
56
|
},
|
|
59
57
|
"peerDependencies": {
|
|
60
58
|
"react": "^19.2.8"
|
|
@@ -63,8 +61,15 @@
|
|
|
63
61
|
"publishConfig": {
|
|
64
62
|
"access": "public"
|
|
65
63
|
},
|
|
64
|
+
"files": [
|
|
65
|
+
"dist",
|
|
66
|
+
"src"
|
|
67
|
+
],
|
|
66
68
|
"scripts": {
|
|
67
69
|
"build": "rslib build",
|
|
68
70
|
"test": "rstest"
|
|
71
|
+
},
|
|
72
|
+
"ultramodern": {
|
|
73
|
+
"sourceName": "@modern-js/runtime-renderer-extensions"
|
|
69
74
|
}
|
|
70
75
|
}
|
package/src/runtimePlugin.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
Collector,
|
|
3
3
|
RuntimePlugin,
|
|
4
|
+
SSRRenderInfo,
|
|
4
5
|
SSRRenderLifecycle,
|
|
5
6
|
StreamSSRExtender,
|
|
6
7
|
} from '@modern-js/plugin/runtime';
|
|
@@ -39,15 +40,27 @@ export function createRendererHeadPlugin(
|
|
|
39
40
|
api.transformRuntimeContext(projectRuntimeContext);
|
|
40
41
|
api.wrapRoot(Root => {
|
|
41
42
|
const HeadRoot = (props: React.ComponentProps<typeof Root>) =>
|
|
42
|
-
|
|
43
|
+
// A root is only defined once the plugin that owns it (the router,
|
|
44
|
+
// for a file-system routes entry) has run. This plugin must keep
|
|
45
|
+
// working when it is registered before that one: it then wraps the
|
|
46
|
+
// children the later plugin hands it instead of a missing root.
|
|
47
|
+
head.wrapClientRoot(
|
|
48
|
+
Root
|
|
49
|
+
? React.createElement(Root, props)
|
|
50
|
+
: ((props as { children?: React.ReactNode })?.children ?? null),
|
|
51
|
+
);
|
|
43
52
|
return HeadRoot;
|
|
44
53
|
});
|
|
45
54
|
|
|
46
|
-
const createState = (runtimeContext:
|
|
55
|
+
const createState = ({ runtimeContext, monitors }: SSRRenderInfo) => {
|
|
47
56
|
const helmetContext = ensureHelmetContext(runtimeContext);
|
|
57
|
+
const reportLateHead =
|
|
58
|
+
process.env.NODE_ENV === 'production'
|
|
59
|
+
? (message: string) => monitors.warn(message)
|
|
60
|
+
: (message: string) => monitors.error(message);
|
|
48
61
|
const lifecycle: SSRRenderLifecycle = {
|
|
49
62
|
beforeReact() {
|
|
50
|
-
beginHeadRender(runtimeContext);
|
|
63
|
+
beginHeadRender(runtimeContext, reportLateHead);
|
|
51
64
|
},
|
|
52
65
|
completedBody(html, { phase }) {
|
|
53
66
|
if (phase === 'complete') {
|
|
@@ -72,7 +85,7 @@ export function createRendererHeadPlugin(
|
|
|
72
85
|
};
|
|
73
86
|
|
|
74
87
|
api.extendStringSSRCollectors(({ render }): Collector => {
|
|
75
|
-
const state = createState(render
|
|
88
|
+
const state = createState(render);
|
|
76
89
|
return {
|
|
77
90
|
...state.lifecycle,
|
|
78
91
|
...createAssetPolicy(render),
|
|
@@ -89,7 +102,7 @@ export function createRendererHeadPlugin(
|
|
|
89
102
|
'Node head rendering requires the Node runtime-renderer-extensions entry',
|
|
90
103
|
);
|
|
91
104
|
}
|
|
92
|
-
const state = createState(
|
|
105
|
+
const state = createState(info);
|
|
93
106
|
return {
|
|
94
107
|
...state.lifecycle,
|
|
95
108
|
...createAssetPolicy(info),
|
|
@@ -113,7 +126,21 @@ export function createRendererHeadPlugin(
|
|
|
113
126
|
};
|
|
114
127
|
}
|
|
115
128
|
|
|
116
|
-
|
|
117
|
-
|
|
129
|
+
/**
|
|
130
|
+
* The plugins whose root this one wraps. Declaring them keeps the head
|
|
131
|
+
* provider outside their trees no matter what order the CLI emitted the
|
|
132
|
+
* runtime descriptors in.
|
|
133
|
+
*/
|
|
134
|
+
export const RENDERER_HEAD_PRE_PLUGINS = [
|
|
135
|
+
'@modern-js/plugin-router',
|
|
136
|
+
'@modern-js/plugin-tanstack',
|
|
137
|
+
'@modern-js/plugin-state',
|
|
138
|
+
'@modern-js/plugin-i18n',
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
export const rendererHeadPlugin = (): RuntimePlugin<{}> => ({
|
|
142
|
+
...createRendererHeadPlugin(),
|
|
143
|
+
pre: RENDERER_HEAD_PRE_PLUGINS,
|
|
144
|
+
});
|
|
118
145
|
|
|
119
146
|
export default rendererHeadPlugin;
|
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/node.ts'],
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
}
|
|
17
|
-
: lib,
|
|
18
|
-
),
|
|
19
|
-
});
|
package/rstest.config.mts
DELETED
|
@@ -1,29 +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
|
-
export default {
|
|
22
|
-
projects: [
|
|
23
|
-
withTestPreset({
|
|
24
|
-
name: 'renderer-extensions-node',
|
|
25
|
-
testEnvironment: 'node',
|
|
26
|
-
extends: commonConfig,
|
|
27
|
-
}),
|
|
28
|
-
],
|
|
29
|
-
};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
attributesToString,
|
|
3
|
-
hasStylesheetLink,
|
|
4
|
-
} from '../../plugin-runtime/src/core/server/utils';
|
|
5
|
-
import { createFederatedCssLinks as createLinks } from '../src/federatedCss';
|
|
6
|
-
|
|
7
|
-
const createFederatedCssLinks = (
|
|
8
|
-
assets: Parameters<typeof createLinks>[0],
|
|
9
|
-
options: Parameters<typeof createLinks>[1],
|
|
10
|
-
) => createLinks(assets, options, { attributesToString, hasStylesheetLink });
|
|
11
|
-
|
|
12
|
-
describe('createFederatedCssLinks', () => {
|
|
13
|
-
it('does not dedupe a stylesheet against a longer href substring', () => {
|
|
14
|
-
const html = createFederatedCssLinks(['/remote/theme.css'], {
|
|
15
|
-
template: '<link href="/remote/theme.css?v=next" rel="stylesheet" />',
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
expect(html).toBe('<link href="/remote/theme.css" rel="stylesheet" />');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('escapes federated css asset urls before interpolating href attributes', () => {
|
|
22
|
-
const html = createFederatedCssLinks(
|
|
23
|
-
['/remote/theme" onload="alert(1)&x=<tag>.css'],
|
|
24
|
-
{
|
|
25
|
-
template: '',
|
|
26
|
-
},
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
expect(html).toBe(
|
|
30
|
-
'<link href="/remote/theme" onload="alert(1)&x=<tag>.css" rel="stylesheet" />',
|
|
31
|
-
);
|
|
32
|
-
expect(html).not.toContain('onload="alert(1)');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('escapes extra attributes and prevents duplicate renderer-owned attributes', () => {
|
|
36
|
-
const html = createFederatedCssLinks(['/remote/theme.css'], {
|
|
37
|
-
template: '',
|
|
38
|
-
attributes: {
|
|
39
|
-
nonce: 'nonce"&<value>',
|
|
40
|
-
href: 'https://attacker.example/override.css',
|
|
41
|
-
rel: 'preload',
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
expect(html).toBe(
|
|
46
|
-
'<link nonce="nonce"&<value>" href="/remote/theme.css" rel="stylesheet" />',
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { getHelmetContext } from '@modern-js/runtime-extensions/helmet-context';
|
|
2
|
-
import React, { useContext } from 'react';
|
|
3
|
-
import { renderToReadableStream, renderToString } from 'react-dom/server';
|
|
4
|
-
import {
|
|
5
|
-
getGlobalEnableRsc,
|
|
6
|
-
getGlobalInternalRuntimeContext,
|
|
7
|
-
getInitialContext,
|
|
8
|
-
InternalRuntimeContext,
|
|
9
|
-
setGlobalContext,
|
|
10
|
-
setGlobalInternalRuntimeContext,
|
|
11
|
-
} from '../../plugin-runtime/src/core/context';
|
|
12
|
-
import { registerPlugin } from '../../plugin-runtime/src/core/plugin';
|
|
13
|
-
import {
|
|
14
|
-
wrapRuntimeContextProvider as nativeWrapRuntimeContextProvider,
|
|
15
|
-
wrapRuntimeComponentResolver,
|
|
16
|
-
} from '../../plugin-runtime/src/core/react/wrapper';
|
|
17
|
-
import { Helmet } from '../../plugin-runtime/src/exports/head';
|
|
18
|
-
import rendererHeadPlugin from '../src/runtimePlugin';
|
|
19
|
-
|
|
20
|
-
const previousRuntime = getGlobalInternalRuntimeContext();
|
|
21
|
-
const previousRsc = getGlobalEnableRsc();
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
setGlobalContext({ enableRsc: false });
|
|
24
|
-
registerPlugin([rendererHeadPlugin()]);
|
|
25
|
-
});
|
|
26
|
-
afterEach(() => {
|
|
27
|
-
setGlobalInternalRuntimeContext(previousRuntime);
|
|
28
|
-
setGlobalContext({ enableRsc: previousRsc });
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Exercise the unchanged native provider/head entry points through registered
|
|
32
|
-
// collector providers. Transaction/lifecycle rendering is covered separately.
|
|
33
|
-
function wrapRuntimeContextProvider(
|
|
34
|
-
app: React.ReactElement,
|
|
35
|
-
context: ReturnType<typeof getInitialContext>,
|
|
36
|
-
) {
|
|
37
|
-
const hooks = getGlobalInternalRuntimeContext().hooks;
|
|
38
|
-
const App = hooks.wrapRoot.call(() => app);
|
|
39
|
-
let root = nativeWrapRuntimeContextProvider(<App />, context);
|
|
40
|
-
if (getGlobalEnableRsc() && !context.isBrowser) {
|
|
41
|
-
root = wrapRuntimeComponentResolver(root);
|
|
42
|
-
}
|
|
43
|
-
const collectors = hooks.extendStringSSRCollectors.call({
|
|
44
|
-
chunkSet: { renderLevel: 0, ssrScripts: '', jsChunk: '', cssChunk: '' },
|
|
45
|
-
render: {
|
|
46
|
-
runtimeContext: context,
|
|
47
|
-
request: new Request('https://example.test/tractors'),
|
|
48
|
-
platform: 'node',
|
|
49
|
-
mode: 'string',
|
|
50
|
-
isRsc: getGlobalEnableRsc() === true,
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
for (const collector of collectors) root = collector.collect?.(root) ?? root;
|
|
54
|
-
return root;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
describe('registered native runtime/head compatibility', () => {
|
|
58
|
-
it('keeps overlapping requests on their original head contexts after projection', async () => {
|
|
59
|
-
const projectedContexts: object[] = [];
|
|
60
|
-
getGlobalInternalRuntimeContext().pluginAPI?.transformRuntimeContext(
|
|
61
|
-
(projection: any) => {
|
|
62
|
-
const internalContext = { ...projection.internalContext };
|
|
63
|
-
delete internalContext[
|
|
64
|
-
Symbol.for('@modern-js/runtime:context-extensions')
|
|
65
|
-
];
|
|
66
|
-
projectedContexts.push(internalContext);
|
|
67
|
-
return { ...projection, internalContext };
|
|
68
|
-
},
|
|
69
|
-
);
|
|
70
|
-
const SuspendedHead = ({
|
|
71
|
-
ready,
|
|
72
|
-
title,
|
|
73
|
-
}: {
|
|
74
|
-
ready: Promise<void>;
|
|
75
|
-
title: string;
|
|
76
|
-
}) => {
|
|
77
|
-
React.use(ready);
|
|
78
|
-
return (
|
|
79
|
-
<Helmet>
|
|
80
|
-
<title>{title}</title>
|
|
81
|
-
</Helmet>
|
|
82
|
-
);
|
|
83
|
-
};
|
|
84
|
-
const requests = ['first request', 'second request'].map(title => {
|
|
85
|
-
let release = () => {};
|
|
86
|
-
const ready = new Promise<void>(resolve => {
|
|
87
|
-
release = resolve;
|
|
88
|
-
});
|
|
89
|
-
const context = getInitialContext(false);
|
|
90
|
-
return { title, ready, release, context };
|
|
91
|
-
});
|
|
92
|
-
const streams = await Promise.all(
|
|
93
|
-
requests.map(({ title, ready, context }) =>
|
|
94
|
-
renderToReadableStream(
|
|
95
|
-
wrapRuntimeContextProvider(
|
|
96
|
-
<React.Suspense fallback="pending">
|
|
97
|
-
<SuspendedHead ready={ready} title={title} />
|
|
98
|
-
</React.Suspense>,
|
|
99
|
-
context,
|
|
100
|
-
),
|
|
101
|
-
),
|
|
102
|
-
),
|
|
103
|
-
);
|
|
104
|
-
requests[1].release();
|
|
105
|
-
requests[0].release();
|
|
106
|
-
await Promise.all(
|
|
107
|
-
streams.map(async stream => {
|
|
108
|
-
await stream.allReady;
|
|
109
|
-
return new Response(stream).text();
|
|
110
|
-
}),
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
for (const { context, title } of requests) {
|
|
114
|
-
expect(getHelmetContext(context)?.helmet?.title.toString()).toBe(
|
|
115
|
-
`<title data-rh="true">${title}</title>`,
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
expect(projectedContexts).toHaveLength(2);
|
|
119
|
-
for (const context of projectedContexts) {
|
|
120
|
-
expect(getHelmetContext(context)).toBeUndefined();
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('keeps response functions outside the RSC internal context', () => {
|
|
125
|
-
let internalValue: ReturnType<typeof getInitialContext> | undefined;
|
|
126
|
-
|
|
127
|
-
const Probe = () => {
|
|
128
|
-
internalValue = useContext(InternalRuntimeContext);
|
|
129
|
-
return null;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const response = {
|
|
133
|
-
setHeader: rstest.fn(),
|
|
134
|
-
status: rstest.fn(),
|
|
135
|
-
locals: { tenant: 'tractor-store' },
|
|
136
|
-
};
|
|
137
|
-
const context = getInitialContext(false);
|
|
138
|
-
context.ssrContext = {
|
|
139
|
-
request: {
|
|
140
|
-
params: { category: 'compact' },
|
|
141
|
-
pathname: '/tractors',
|
|
142
|
-
query: { sort: 'price' },
|
|
143
|
-
headers: { accept: 'text/html' },
|
|
144
|
-
host: 'example.test',
|
|
145
|
-
url: 'https://example.test/tractors?sort=price',
|
|
146
|
-
},
|
|
147
|
-
response,
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
setGlobalContext({ enableRsc: true });
|
|
151
|
-
try {
|
|
152
|
-
renderToString(wrapRuntimeContextProvider(<Probe />, context));
|
|
153
|
-
} finally {
|
|
154
|
-
setGlobalContext({ enableRsc: false });
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
expect(
|
|
158
|
-
Object.values(internalValue?.requestContext.response ?? {}).some(
|
|
159
|
-
value => typeof value === 'function',
|
|
160
|
-
),
|
|
161
|
-
).toBe(false);
|
|
162
|
-
});
|
|
163
|
-
});
|