@bleedingdev/modern-js-runtime-renderer-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.
@@ -61,11 +61,12 @@ function createRendererHeadPlugin(options = {}) {
61
61
  const HeadRoot = (props)=>head.wrapClientRoot(Root ? external_react_default().createElement(Root, props) : props?.children ?? null);
62
62
  return HeadRoot;
63
63
  });
64
- const createState = (runtimeContext)=>{
64
+ const createState = ({ runtimeContext, monitors })=>{
65
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);
66
67
  const lifecycle = {
67
68
  beforeReact () {
68
- (0, runtime_extensions_namespaceObject.beginHeadRender)(runtimeContext);
69
+ (0, runtime_extensions_namespaceObject.beginHeadRender)(runtimeContext, reportLateHead);
69
70
  },
70
71
  completedBody (html, { phase }) {
71
72
  if ('complete' === phase) return (0, runtime_extensions_namespaceObject.completeHeadRender)(runtimeContext, html);
@@ -90,7 +91,7 @@ function createRendererHeadPlugin(options = {}) {
90
91
  };
91
92
  };
92
93
  api.extendStringSSRCollectors(({ render })=>{
93
- const state = createState(render.runtimeContext);
94
+ const state = createState(render);
94
95
  return {
95
96
  ...state.lifecycle,
96
97
  ...(0, external_assetPolicy_js_namespaceObject.createAssetPolicy)(render),
@@ -103,7 +104,7 @@ function createRendererHeadPlugin(options = {}) {
103
104
  const { runtimeContext, terminalMarker } = info;
104
105
  const processNodeStream = options.processNodeStream;
105
106
  if ('node' === info.platform && void 0 === processNodeStream) throw new Error('Node head rendering requires the Node runtime-renderer-extensions entry');
106
- const state = createState(runtimeContext);
107
+ const state = createState(info);
107
108
  return {
108
109
  ...state.lifecycle,
109
110
  ...(0, external_assetPolicy_js_namespaceObject.createAssetPolicy)(info),
@@ -16,11 +16,12 @@ function createRendererHeadPlugin(options = {}) {
16
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.runtimeContext);
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(runtimeContext);
62
+ const state = createState(info);
62
63
  return {
63
64
  ...state.lifecycle,
64
65
  ...createAssetPolicy(info),
@@ -17,11 +17,12 @@ function createRendererHeadPlugin(options = {}) {
17
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.runtimeContext);
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(runtimeContext);
63
+ const state = createState(info);
63
64
  return {
64
65
  ...state.lifecycle,
65
66
  ...createAssetPolicy(info),
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/BleedingDev/ultramodern.js#readme",
4
+ "homepage": "https://github.com/ultramodernjs/ultramodern.js#readme",
5
5
  "bugs": {
6
- "url": "https://github.com/BleedingDev/ultramodern.js/issues"
6
+ "url": "https://github.com/ultramodernjs/ultramodern.js/issues"
7
7
  },
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "git+https://github.com/BleedingDev/ultramodern.js.git",
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.0-ultramodern.9",
14
+ "version": "3.9.3-ultramodern.10",
15
15
  "engines": {
16
- "node": ">=26.7.0"
16
+ "node": ">=26.12.0"
17
17
  },
18
18
  "types": "./dist/types/runtimePlugin.d.ts",
19
19
  "main": "./dist/cjs/node.js",
@@ -36,34 +36,40 @@
36
36
  }
37
37
  },
38
38
  "dependencies": {
39
- "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.9",
40
- "@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.9"
39
+ "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.3-ultramodern.10",
40
+ "@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.3-ultramodern.10"
41
41
  },
42
42
  "devDependencies": {
43
- "@modern-js/render": "npm:@bleedingdev/modern-js-render@3.9.0-ultramodern.9",
44
- "@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.0-ultramodern.9",
45
- "@rslib/core": "1.0.0",
43
+ "@modern-js/render": "npm:@bleedingdev/modern-js-render@3.9.3-ultramodern.10",
44
+ "@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.3-ultramodern.10",
45
+ "@rslib/core": "1.0.3",
46
46
  "@scripts/rstest-config": "2.66.0",
47
- "@testing-library/dom": "^10.4.1",
47
+ "@testing-library/dom": "^10.4.2",
48
48
  "@testing-library/react": "^16.3.3",
49
- "@types/node": "^26.4.1",
50
- "@types/react": "^19.2.18",
51
- "@types/react-dom": "^19.2.7",
52
- "@typescript/native-preview": "7.0.0-dev.20260707.2",
49
+ "@types/node": "^26.6.5",
50
+ "@types/react": "^19.3.0",
51
+ "@types/react-dom": "^19.3.0",
53
52
  "esbuild": "^0.28.2",
54
- "react": "^19.2.8",
55
- "react-dom": "^19.2.8",
56
- "typescript": "^7.0.2"
53
+ "react": "^19.3.1",
54
+ "react-dom": "^19.3.1",
55
+ "typescript": "7.0.2"
57
56
  },
58
57
  "peerDependencies": {
59
- "react": "^19.2.8"
58
+ "react": "^19.3.1"
60
59
  },
61
60
  "sideEffects": false,
62
61
  "publishConfig": {
63
62
  "access": "public"
64
63
  },
64
+ "files": [
65
+ "dist",
66
+ "src"
67
+ ],
65
68
  "scripts": {
66
69
  "build": "rslib build",
67
70
  "test": "rstest"
71
+ },
72
+ "ultramodern": {
73
+ "sourceName": "@modern-js/runtime-renderer-extensions"
68
74
  }
69
75
  }
@@ -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';
@@ -51,11 +52,15 @@ export function createRendererHeadPlugin(
51
52
  return HeadRoot;
52
53
  });
53
54
 
54
- const createState = (runtimeContext: object) => {
55
+ const createState = ({ runtimeContext, monitors }: SSRRenderInfo) => {
55
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);
56
61
  const lifecycle: SSRRenderLifecycle = {
57
62
  beforeReact() {
58
- beginHeadRender(runtimeContext);
63
+ beginHeadRender(runtimeContext, reportLateHead);
59
64
  },
60
65
  completedBody(html, { phase }) {
61
66
  if (phase === 'complete') {
@@ -80,7 +85,7 @@ export function createRendererHeadPlugin(
80
85
  };
81
86
 
82
87
  api.extendStringSSRCollectors(({ render }): Collector => {
83
- const state = createState(render.runtimeContext);
88
+ const state = createState(render);
84
89
  return {
85
90
  ...state.lifecycle,
86
91
  ...createAssetPolicy(render),
@@ -97,7 +102,7 @@ export function createRendererHeadPlugin(
97
102
  'Node head rendering requires the Node runtime-renderer-extensions entry',
98
103
  );
99
104
  }
100
- const state = createState(runtimeContext);
105
+ const state = createState(info);
101
106
  return {
102
107
  ...state.lifecycle,
103
108
  ...createAssetPolicy(info),
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&quot; onload=&quot;alert(1)&amp;x=&lt;tag&gt;.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&quot;&amp;&lt;value&gt;" 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
- });