@ecopages/react 0.2.0-alpha.5 → 0.2.0-alpha.51
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/README.md +152 -29
- package/package.json +24 -12
- package/src/eco-embed.d.ts +11 -0
- package/src/eco-embed.js +11 -0
- package/src/react-hmr-strategy.d.ts +65 -43
- package/src/react-hmr-strategy.js +298 -145
- package/src/react-renderer.d.ts +169 -42
- package/src/react-renderer.js +484 -164
- package/src/react.constants.d.ts +1 -0
- package/src/react.constants.js +4 -0
- package/src/react.plugin.d.ts +40 -111
- package/src/react.plugin.js +136 -61
- package/src/react.types.d.ts +88 -0
- package/src/react.types.js +0 -0
- package/src/router-adapter.d.ts +7 -14
- package/src/runtime/use-sync-external-store-with-selector.d.ts +3 -0
- package/src/runtime/use-sync-external-store-with-selector.js +56 -0
- package/src/services/react-bundle.service.d.ts +22 -35
- package/src/services/react-bundle.service.js +41 -105
- package/src/services/react-hmr-page-metadata-cache.d.ts +9 -0
- package/src/services/react-hmr-page-metadata-cache.js +18 -2
- package/src/services/react-hydration-asset.service.d.ts +28 -19
- package/src/services/react-hydration-asset.service.js +85 -66
- package/src/services/react-mdx-config-dependency.service.d.ts +36 -0
- package/src/services/react-mdx-config-dependency.service.js +122 -0
- package/src/services/react-page-module.service.d.ts +10 -2
- package/src/services/react-page-module.service.js +47 -39
- package/src/services/react-page-payload.service.d.ts +46 -0
- package/src/services/react-page-payload.service.js +67 -0
- package/src/services/react-runtime-bundle.service.d.ts +20 -13
- package/src/services/react-runtime-bundle.service.js +146 -179
- package/src/utils/client-graph-boundary-plugin.d.ts +1 -1
- package/src/utils/client-graph-boundary-plugin.js +80 -3
- package/src/utils/component-config-traversal.d.ts +36 -0
- package/src/utils/component-config-traversal.js +54 -0
- package/src/utils/declared-modules.d.ts +1 -1
- package/src/utils/declared-modules.js +7 -16
- package/src/utils/dynamic.test.browser.d.ts +1 -0
- package/src/utils/dynamic.test.browser.js +33 -0
- package/src/utils/hydration-scripts.d.ts +27 -6
- package/src/utils/hydration-scripts.js +177 -44
- package/src/utils/hydration-scripts.test.browser.d.ts +1 -0
- package/src/utils/hydration-scripts.test.browser.js +198 -0
- package/src/utils/react-dom-runtime-interop-plugin.d.ts +5 -0
- package/src/utils/react-dom-runtime-interop-plugin.js +38 -0
- package/src/utils/react-mdx-loader-plugin.d.ts +1 -1
- package/src/utils/react-mdx-loader-plugin.js +13 -5
- package/src/utils/react-runtime-alias-map.d.ts +8 -0
- package/src/utils/react-runtime-alias-map.js +90 -0
- package/CHANGELOG.md +0 -67
- package/src/react-hmr-strategy.ts +0 -455
- package/src/react-renderer.ts +0 -403
- package/src/react.plugin.ts +0 -241
- package/src/router-adapter.ts +0 -95
- package/src/services/react-bundle.service.ts +0 -217
- package/src/services/react-hmr-page-metadata-cache.ts +0 -24
- package/src/services/react-hydration-asset.service.ts +0 -260
- package/src/services/react-page-module.service.ts +0 -214
- package/src/services/react-runtime-bundle.service.ts +0 -271
- package/src/utils/client-graph-boundary-plugin.ts +0 -710
- package/src/utils/client-only.ts +0 -27
- package/src/utils/declared-modules.ts +0 -99
- package/src/utils/dynamic.ts +0 -27
- package/src/utils/hmr-scripts.ts +0 -47
- package/src/utils/html-boundary.ts +0 -66
- package/src/utils/hydration-scripts.ts +0 -338
- package/src/utils/reachability-analyzer.ts +0 -593
- package/src/utils/react-mdx-loader-plugin.ts +0 -40
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { createHydrationScript } from "./hydration-scripts.js";
|
|
3
|
+
const routerAdapter = {
|
|
4
|
+
name: "eco-router",
|
|
5
|
+
bundle: {
|
|
6
|
+
importPath: "/assets/router.js",
|
|
7
|
+
outputName: "router",
|
|
8
|
+
externals: []
|
|
9
|
+
},
|
|
10
|
+
components: {
|
|
11
|
+
router: "EcoRouter",
|
|
12
|
+
pageContent: "PageContent"
|
|
13
|
+
},
|
|
14
|
+
getRouterProps: (page, props) => `{ page: ${page}, pageProps: ${props} }`
|
|
15
|
+
};
|
|
16
|
+
function createModuleUrl(source) {
|
|
17
|
+
return `data:text/javascript;base64,${btoa(source)}`;
|
|
18
|
+
}
|
|
19
|
+
async function importModule(moduleUrl, scriptId) {
|
|
20
|
+
let marker;
|
|
21
|
+
if (scriptId) {
|
|
22
|
+
marker = document.createElement("script");
|
|
23
|
+
marker.setAttribute("data-eco-script-id", scriptId);
|
|
24
|
+
document.head.appendChild(marker);
|
|
25
|
+
}
|
|
26
|
+
await import(
|
|
27
|
+
/* @vite-ignore */
|
|
28
|
+
moduleUrl
|
|
29
|
+
);
|
|
30
|
+
marker?.remove();
|
|
31
|
+
}
|
|
32
|
+
function createRuntimeModules() {
|
|
33
|
+
const reactImportPath = createModuleUrl("export const createElement = (...args) => ({ args });");
|
|
34
|
+
const reactDomClientImportPath = createModuleUrl(`
|
|
35
|
+
export const hydrateRoot = (container, tree, options) => {
|
|
36
|
+
const runtime = window.__ECO_REACT_HYDRATION_TEST__;
|
|
37
|
+
runtime.hydrateCalls.push({
|
|
38
|
+
containerTag: container.tagName,
|
|
39
|
+
hasRecoverableErrorHandler: typeof options?.onRecoverableError === "function",
|
|
40
|
+
tree,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
render() {},
|
|
45
|
+
unmount() {
|
|
46
|
+
runtime.unmountCount += 1;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
`);
|
|
51
|
+
const importPath = createModuleUrl("export default function Page() { return null; }");
|
|
52
|
+
const routerImportPath = createModuleUrl(`
|
|
53
|
+
export function EcoRouter(props) {
|
|
54
|
+
return props;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function PageContent() {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
`);
|
|
61
|
+
return {
|
|
62
|
+
importPath,
|
|
63
|
+
reactImportPath,
|
|
64
|
+
reactDomClientImportPath,
|
|
65
|
+
routerImportPath
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
describe("createHydrationScript browser execution", () => {
|
|
69
|
+
afterEach(() => {
|
|
70
|
+
document.body.innerHTML = "";
|
|
71
|
+
const testWindow = window;
|
|
72
|
+
delete testWindow.__ECO_PAGES__;
|
|
73
|
+
delete testWindow.__ECO_REACT_HYDRATION_TEST__;
|
|
74
|
+
});
|
|
75
|
+
it("registers router ownership and cleanup when the browser hydration bootstrap runs", async () => {
|
|
76
|
+
const runtimeModules = createRuntimeModules();
|
|
77
|
+
const testWindow = window;
|
|
78
|
+
testWindow.__ECO_REACT_HYDRATION_TEST__ = {
|
|
79
|
+
hydrateCalls: [],
|
|
80
|
+
renderCalls: [],
|
|
81
|
+
claimedOwners: [],
|
|
82
|
+
releasedOwners: [],
|
|
83
|
+
registrations: [],
|
|
84
|
+
unmountCount: 0
|
|
85
|
+
};
|
|
86
|
+
testWindow.__ECO_PAGES__ = {
|
|
87
|
+
navigation: {
|
|
88
|
+
getOwnerState: () => ({
|
|
89
|
+
owner: "html",
|
|
90
|
+
canHandleSpaNavigation: false
|
|
91
|
+
}),
|
|
92
|
+
register: (registration) => {
|
|
93
|
+
testWindow.__ECO_REACT_HYDRATION_TEST__?.registrations.push(registration);
|
|
94
|
+
},
|
|
95
|
+
claimOwnership: (owner) => {
|
|
96
|
+
testWindow.__ECO_REACT_HYDRATION_TEST__?.claimedOwners.push(owner);
|
|
97
|
+
},
|
|
98
|
+
releaseOwnership: (owner) => {
|
|
99
|
+
testWindow.__ECO_REACT_HYDRATION_TEST__?.releasedOwners.push(owner);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
document.body.innerHTML = `<script id="__ECO_PAGE_DATA__" type="application/json">${JSON.stringify({
|
|
104
|
+
title: "Hello React",
|
|
105
|
+
locals: { theme: "dark" }
|
|
106
|
+
})}<\/script>`;
|
|
107
|
+
const script = createHydrationScript({
|
|
108
|
+
...runtimeModules,
|
|
109
|
+
scriptId: "ecopages-react-page",
|
|
110
|
+
isDevelopment: true,
|
|
111
|
+
isMdx: false,
|
|
112
|
+
router: routerAdapter
|
|
113
|
+
});
|
|
114
|
+
const moduleUrl = createModuleUrl(script);
|
|
115
|
+
await importModule(moduleUrl, "ecopages-react-page");
|
|
116
|
+
expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.hydrateCalls).toHaveLength(1);
|
|
117
|
+
expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.hydrateCalls[0]?.containerTag).toBe("BODY");
|
|
118
|
+
expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.hydrateCalls[0]?.hasRecoverableErrorHandler).toBe(true);
|
|
119
|
+
expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.claimedOwners).toEqual(["react-router"]);
|
|
120
|
+
expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.registrations).toHaveLength(1);
|
|
121
|
+
expect(typeof testWindow.__ECO_PAGES__?.react?.cleanupPageRoot).toBe("function");
|
|
122
|
+
expect(testWindow.__ECO_PAGES__?.page).toEqual({
|
|
123
|
+
module: moduleUrl,
|
|
124
|
+
props: {
|
|
125
|
+
title: "Hello React",
|
|
126
|
+
locals: { theme: "dark" }
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
await testWindow.__ECO_PAGES__?.react?.cleanupPageRoot?.();
|
|
130
|
+
expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.unmountCount).toBe(1);
|
|
131
|
+
expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.releasedOwners).toEqual(["react-router"]);
|
|
132
|
+
expect(testWindow.__ECO_PAGES__?.page).toBeUndefined();
|
|
133
|
+
expect(testWindow.__ECO_PAGES__?.react?.pageRoot).toBeNull();
|
|
134
|
+
});
|
|
135
|
+
it("reuses an existing router-owned page root during rerun bootstrap execution", async () => {
|
|
136
|
+
const runtimeModules = createRuntimeModules();
|
|
137
|
+
const testWindow = window;
|
|
138
|
+
testWindow.__ECO_REACT_HYDRATION_TEST__ = {
|
|
139
|
+
hydrateCalls: [],
|
|
140
|
+
renderCalls: [],
|
|
141
|
+
claimedOwners: [],
|
|
142
|
+
releasedOwners: [],
|
|
143
|
+
registrations: [],
|
|
144
|
+
unmountCount: 0
|
|
145
|
+
};
|
|
146
|
+
const existingRoot = {
|
|
147
|
+
render: (tree) => {
|
|
148
|
+
testWindow.__ECO_REACT_HYDRATION_TEST__?.renderCalls.push(tree);
|
|
149
|
+
},
|
|
150
|
+
unmount: () => {
|
|
151
|
+
testWindow.__ECO_REACT_HYDRATION_TEST__.unmountCount += 1;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
testWindow.__ECO_PAGES__ = {
|
|
155
|
+
navigation: {
|
|
156
|
+
getOwnerState: () => ({
|
|
157
|
+
owner: "react-router",
|
|
158
|
+
canHandleSpaNavigation: true
|
|
159
|
+
}),
|
|
160
|
+
register: (registration) => {
|
|
161
|
+
testWindow.__ECO_REACT_HYDRATION_TEST__?.registrations.push(registration);
|
|
162
|
+
},
|
|
163
|
+
claimOwnership: (owner) => {
|
|
164
|
+
testWindow.__ECO_REACT_HYDRATION_TEST__?.claimedOwners.push(owner);
|
|
165
|
+
},
|
|
166
|
+
releaseOwnership: (owner) => {
|
|
167
|
+
testWindow.__ECO_REACT_HYDRATION_TEST__?.releasedOwners.push(owner);
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
react: {
|
|
171
|
+
pageRoot: existingRoot
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
document.body.innerHTML = `<script id="__ECO_PAGE_DATA__" type="application/json">${JSON.stringify({
|
|
175
|
+
title: "Rerun"
|
|
176
|
+
})}<\/script>`;
|
|
177
|
+
const script = createHydrationScript({
|
|
178
|
+
...runtimeModules,
|
|
179
|
+
scriptId: "ecopages-react-page-rerun",
|
|
180
|
+
isDevelopment: true,
|
|
181
|
+
isMdx: false,
|
|
182
|
+
router: routerAdapter
|
|
183
|
+
});
|
|
184
|
+
const moduleUrl = createModuleUrl(script);
|
|
185
|
+
await importModule(moduleUrl, "ecopages-react-page-rerun");
|
|
186
|
+
expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.hydrateCalls).toHaveLength(0);
|
|
187
|
+
expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.renderCalls).toHaveLength(0);
|
|
188
|
+
expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.claimedOwners).toHaveLength(0);
|
|
189
|
+
expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.registrations).toHaveLength(0);
|
|
190
|
+
expect(testWindow.__ECO_PAGES__?.react?.pageRoot).toBe(existingRoot);
|
|
191
|
+
expect(testWindow.__ECO_PAGES__?.page).toEqual({
|
|
192
|
+
module: moduleUrl,
|
|
193
|
+
props: {
|
|
194
|
+
title: "Rerun"
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function escapeRegExp(value) {
|
|
4
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5
|
+
}
|
|
6
|
+
function createReactDomRuntimeInteropPlugin(options) {
|
|
7
|
+
const reactDomFileFilter = /[\\/]react-dom[\\/].*\.js$/;
|
|
8
|
+
const reactRequirePattern = /\brequire\((['"])react\1\)/g;
|
|
9
|
+
const reactSpecifier = options?.reactSpecifier ?? "react";
|
|
10
|
+
return {
|
|
11
|
+
name: options?.name ?? "react-dom-runtime-interop",
|
|
12
|
+
setup(build) {
|
|
13
|
+
if (reactSpecifier.startsWith("/")) {
|
|
14
|
+
build.onResolve({ filter: new RegExp(`^${escapeRegExp(reactSpecifier)}$`) }, (args) => ({
|
|
15
|
+
path: args.path,
|
|
16
|
+
external: true
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
build.onLoad({ filter: reactDomFileFilter }, (args) => {
|
|
20
|
+
const content = fs.readFileSync(args.path, "utf-8");
|
|
21
|
+
if (!reactRequirePattern.test(content)) {
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
reactRequirePattern.lastIndex = 0;
|
|
25
|
+
const rewritten = content.replace(reactRequirePattern, "__ecopages_react_runtime");
|
|
26
|
+
return {
|
|
27
|
+
contents: `import * as __ecopages_react_runtime from '${reactSpecifier}';
|
|
28
|
+
${rewritten}`,
|
|
29
|
+
loader: "js",
|
|
30
|
+
resolveDir: path.dirname(args.path)
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
createReactDomRuntimeInteropPlugin
|
|
38
|
+
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { EcoBuildPlugin } from '@ecopages/core/
|
|
1
|
+
import type { EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
|
|
2
2
|
import { type CompileOptions } from '@mdx-js/mdx';
|
|
3
3
|
export declare function createReactMdxLoaderPlugin(compilerOptions?: CompileOptions): EcoBuildPlugin;
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { compile } from "@mdx-js/mdx";
|
|
4
|
-
import
|
|
4
|
+
import sourceMap from "source-map";
|
|
5
5
|
import { VFile } from "vfile";
|
|
6
|
+
function resolveCompileFormat(filePath, compilerOptions) {
|
|
7
|
+
const configuredFormat = compilerOptions?.format;
|
|
8
|
+
if (configuredFormat && configuredFormat !== "detect") {
|
|
9
|
+
return configuredFormat;
|
|
10
|
+
}
|
|
11
|
+
return path.extname(filePath).toLowerCase() === ".md" ? "mdx" : configuredFormat;
|
|
12
|
+
}
|
|
6
13
|
function createReactMdxLoaderPlugin(compilerOptions) {
|
|
7
14
|
const mdxExtensions = compilerOptions?.mdxExtensions ?? [".mdx"];
|
|
8
|
-
const mdExtensions = compilerOptions?.mdExtensions ?? [
|
|
15
|
+
const mdExtensions = compilerOptions?.mdExtensions ?? [];
|
|
9
16
|
const allExtensions = [...mdxExtensions, ...mdExtensions];
|
|
10
17
|
const escapedExts = allExtensions.map((ext) => ext.replace(".", "\\."));
|
|
11
18
|
const filter = new RegExp(`(${escapedExts.join("|")})(\\?.*)?$`);
|
|
@@ -18,13 +25,14 @@ function createReactMdxLoaderPlugin(compilerOptions) {
|
|
|
18
25
|
const file = new VFile({ path: filePath, value: source });
|
|
19
26
|
const compiled = await compile(file, {
|
|
20
27
|
...compilerOptions,
|
|
21
|
-
|
|
28
|
+
format: resolveCompileFormat(filePath, compilerOptions),
|
|
29
|
+
SourceMapGenerator: sourceMap.SourceMapGenerator
|
|
22
30
|
});
|
|
23
|
-
const
|
|
31
|
+
const inlineSourceMap = compiled.map ? `
|
|
24
32
|
//# sourceMappingURL=data:application/json;base64,${Buffer.from(JSON.stringify(compiled.map)).toString("base64")}
|
|
25
33
|
` : "";
|
|
26
34
|
return {
|
|
27
|
-
contents: `${String(compiled.value)}${
|
|
35
|
+
contents: `${String(compiled.value)}${inlineSourceMap}`,
|
|
28
36
|
loader: compilerOptions?.jsx ? "jsx" : "js",
|
|
29
37
|
resolveDir: path.dirname(args.path)
|
|
30
38
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReactRouterAdapter } from '../router-adapter.js';
|
|
2
|
+
import type { ReactRuntimeImports } from '../services/react-runtime-bundle.service.js';
|
|
3
|
+
import { type BrowserRuntimeManifest } from '@ecopages/core/build/browser-runtime-manifest';
|
|
4
|
+
export declare const REACT_RUNTIME_SPECIFIERS: readonly ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime", "react-dom/client"];
|
|
5
|
+
export declare function buildReactRuntimeAliasMap(runtimeImports: ReactRuntimeImports): Record<string, string>;
|
|
6
|
+
export declare function buildReactRuntimeManifest(runtimeImports: ReactRuntimeImports): BrowserRuntimeManifest;
|
|
7
|
+
export declare function getReactRuntimeExternalSpecifiers(): string[];
|
|
8
|
+
export declare function getReactClientGraphAllowSpecifiers(runtimeSpecifiers: Iterable<string>, routerAdapter?: ReactRouterAdapter): string[];
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createBrowserRuntimeManifest,
|
|
3
|
+
getBrowserRuntimeSpecifierMap
|
|
4
|
+
} from "@ecopages/core/build/browser-runtime-manifest";
|
|
5
|
+
const REACT_RUNTIME_SPECIFIERS = [
|
|
6
|
+
"react",
|
|
7
|
+
"react-dom",
|
|
8
|
+
"react/jsx-runtime",
|
|
9
|
+
"react/jsx-dev-runtime",
|
|
10
|
+
"react-dom/client"
|
|
11
|
+
];
|
|
12
|
+
function buildReactRuntimeAliasMap(runtimeImports) {
|
|
13
|
+
return Object.fromEntries(getBrowserRuntimeSpecifierMap(buildReactRuntimeManifest(runtimeImports)));
|
|
14
|
+
}
|
|
15
|
+
function buildReactRuntimeManifest(runtimeImports) {
|
|
16
|
+
return createBrowserRuntimeManifest([
|
|
17
|
+
{
|
|
18
|
+
specifier: "react",
|
|
19
|
+
owner: "@ecopages/react",
|
|
20
|
+
importPath: "react",
|
|
21
|
+
publicPath: runtimeImports.react
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
specifier: "react/jsx-runtime",
|
|
25
|
+
owner: "@ecopages/react",
|
|
26
|
+
importPath: "react/jsx-runtime",
|
|
27
|
+
publicPath: runtimeImports.reactJsxRuntime
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
specifier: "react/jsx-dev-runtime",
|
|
31
|
+
owner: "@ecopages/react",
|
|
32
|
+
importPath: "react/jsx-dev-runtime",
|
|
33
|
+
publicPath: runtimeImports.reactJsxDevRuntime
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
specifier: "react-dom",
|
|
37
|
+
owner: "@ecopages/react",
|
|
38
|
+
importPath: "react-dom",
|
|
39
|
+
publicPath: runtimeImports.reactDom
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
specifier: "react-dom/client",
|
|
43
|
+
owner: "@ecopages/react",
|
|
44
|
+
importPath: "react-dom/client",
|
|
45
|
+
publicPath: runtimeImports.reactDomClient
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
specifier: "use-sync-external-store/shim",
|
|
49
|
+
owner: "@ecopages/react",
|
|
50
|
+
importPath: "use-sync-external-store/shim",
|
|
51
|
+
publicPath: runtimeImports.react
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
specifier: "use-sync-external-store/shim/index.js",
|
|
55
|
+
owner: "@ecopages/react",
|
|
56
|
+
importPath: "use-sync-external-store/shim/index.js",
|
|
57
|
+
publicPath: runtimeImports.react
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
specifier: "use-sync-external-store/shim/with-selector",
|
|
61
|
+
owner: "@ecopages/react",
|
|
62
|
+
importPath: "use-sync-external-store/shim/with-selector",
|
|
63
|
+
publicPath: runtimeImports.useSyncExternalStoreWithSelector
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
specifier: "use-sync-external-store/shim/with-selector.js",
|
|
67
|
+
owner: "@ecopages/react",
|
|
68
|
+
importPath: "use-sync-external-store/shim/with-selector.js",
|
|
69
|
+
publicPath: runtimeImports.useSyncExternalStoreWithSelector
|
|
70
|
+
}
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
function getReactRuntimeExternalSpecifiers() {
|
|
74
|
+
return [...REACT_RUNTIME_SPECIFIERS];
|
|
75
|
+
}
|
|
76
|
+
function getReactClientGraphAllowSpecifiers(runtimeSpecifiers, routerAdapter) {
|
|
77
|
+
return [
|
|
78
|
+
"@ecopages/core",
|
|
79
|
+
...REACT_RUNTIME_SPECIFIERS,
|
|
80
|
+
...routerAdapter ? [routerAdapter.bundle.importPath] : [],
|
|
81
|
+
...Array.from(runtimeSpecifiers)
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
REACT_RUNTIME_SPECIFIERS,
|
|
86
|
+
buildReactRuntimeAliasMap,
|
|
87
|
+
buildReactRuntimeManifest,
|
|
88
|
+
getReactClientGraphAllowSpecifiers,
|
|
89
|
+
getReactRuntimeExternalSpecifiers
|
|
90
|
+
};
|
package/CHANGELOG.md
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to `@ecopages/react` are documented here.
|
|
4
|
-
|
|
5
|
-
> **Note:** Changelog tracking begins at version `0.2.0`. Changes prior to this release are not recorded here but are available in the git history.
|
|
6
|
-
|
|
7
|
-
## [UNRELEASED] — TBD
|
|
8
|
-
|
|
9
|
-
### Features
|
|
10
|
-
|
|
11
|
-
#### Render Reachability Analysis
|
|
12
|
-
|
|
13
|
-
- **Client render graph (Phase 1)** — Introduces a static reachability analysis step that builds an explicit graph of which components are rendered client-side (`cdfbd69e`).
|
|
14
|
-
- **OXC-powered reachability analyzer** — `reachability-analyzer.ts` uses OXC to parse and walk component ASTs, building a `ClientRenderGraph` that maps exported components to their client-side reach (`5412df6b`).
|
|
15
|
-
- **Explicit client graph boundaries** — Components must now declare explicit boundaries; the analyser enforces these to prevent over-hydration (`2912d6bd`).
|
|
16
|
-
- **Declared modules utility** — `declared-modules.ts` tracks which modules are declared as client boundaries.
|
|
17
|
-
|
|
18
|
-
#### Service Architecture Refactor
|
|
19
|
-
|
|
20
|
-
- **`ReactRuntimeBundleService`** — Manages runtime assets and specifier mapping for the React integration (`cfd3cb05`).
|
|
21
|
-
- **`ReactHydrationAssetService`** — Creates and manages hydration assets for client-side rendering (`cfd3cb05`).
|
|
22
|
-
- **`ReactBundleService`** — Handles esbuild bundle configuration for React components (`cfd3cb05`).
|
|
23
|
-
- **`ReactPageModuleService`** — Loads and compiles MDX/TSX page modules, including config resolution (`cfd3cb05`).
|
|
24
|
-
- The integration no longer builds a monolithic renderer — each concern is handled by a focused service.
|
|
25
|
-
|
|
26
|
-
#### HMR Improvements
|
|
27
|
-
|
|
28
|
-
- **HMR page metadata caching** — Page metadata is now cached between HMR refreshes, preventing unnecessary re-fetches during Fast Refresh (`a663788c`).
|
|
29
|
-
- **Stale temp module race fix** — HMR no longer incorrectly reads a stale temporary module during rapid refresh cycles (`b2cf8466`).
|
|
30
|
-
- **Client graph HMR stability** — HMR reloads now correctly respect client graph boundaries to avoid partial hydration mismatches (`2912d6bd`).
|
|
31
|
-
|
|
32
|
-
#### HTML Boundary Utilities
|
|
33
|
-
|
|
34
|
-
- **`html-boundary.ts`** — New utility that wraps rendered output in explicit boundary markers for the cross-integration boundary rendering policy (`ec1e4d66`).
|
|
35
|
-
- **`hydration-scripts.ts`** — Expanded with new helpers for generating and injecting hydration entry scripts.
|
|
36
|
-
|
|
37
|
-
### Refactoring
|
|
38
|
-
|
|
39
|
-
- Aligned React renderer to full orchestration mode — removed legacy rendering path (`fc07bdb0`).
|
|
40
|
-
- Ambient module declarations cleaned up (`5f46ecc5`).
|
|
41
|
-
- Client graph boundaries and runtime dependency wiring corrected (`4b6cd32e`).
|
|
42
|
-
- Updated test suite for esbuild adapter and Node.js runtime compatibility (`31a44458`).
|
|
43
|
-
|
|
44
|
-
### Bug Fixes
|
|
45
|
-
|
|
46
|
-
- Inlined the React MDX loader so React apps no longer need to install `@ecopages/mdx` when enabling React MDX support (`unreleased`).
|
|
47
|
-
- Fixed stale temp module race during Fast Refresh cycles (`b2cf8466`).
|
|
48
|
-
- Fixed client graph boundary wiring for runtime dependencies (`4b6cd32e`).
|
|
49
|
-
- Fixed shared React barrel handling so client-reachable server-only re-exports now fail the build instead of being silently pruned (`unreleased`).
|
|
50
|
-
|
|
51
|
-
### Documentation
|
|
52
|
-
|
|
53
|
-
- Documented the React integration server/client graph contract for shared modules and barrel exports (`unreleased`).
|
|
54
|
-
|
|
55
|
-
### Tests
|
|
56
|
-
|
|
57
|
-
- Added `html-boundary.test.ts` covering boundary wrapping utilities.
|
|
58
|
-
- Added `hydration-scripts.test.ts` with hydration script generation coverage.
|
|
59
|
-
- Added `reachability-analyzer.test.ts` (187 lines) covering export declaration reachability.
|
|
60
|
-
- Updated integration tests for esbuild adapter compatibility (`31a44458`).
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## Migration Notes
|
|
65
|
-
|
|
66
|
-
- The React integration now requires explicit client boundary declarations. Components that should be hydrated client-side must be marked at a boundary entry point.
|
|
67
|
-
- The internal service layer (`ReactRuntimeBundleService`, `ReactBundleService`, etc.) is not part of the public API and may change between releases.
|