@ecopages/react 0.2.0-alpha.3 → 0.2.0-alpha.30
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/CHANGELOG.md +27 -39
- package/README.md +161 -18
- package/package.json +6 -6
- package/src/react-hmr-strategy.d.ts +42 -32
- package/src/react-hmr-strategy.js +99 -123
- package/src/react-renderer.d.ts +168 -41
- package/src/react-renderer.js +466 -163
- package/src/react.constants.d.ts +1 -0
- package/src/react.constants.js +4 -0
- package/src/react.plugin.d.ts +38 -111
- package/src/react.plugin.js +132 -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/services/react-bundle.service.d.ts +15 -26
- package/src/services/react-bundle.service.js +44 -92
- 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 +17 -18
- package/src/services/react-hydration-asset.service.js +59 -65
- 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 +44 -37
- 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 +15 -13
- package/src/services/react-runtime-bundle.service.js +103 -180
- package/src/utils/client-graph-boundary-plugin.d.ts +1 -1
- package/src/utils/client-graph-boundary-plugin.js +149 -11
- 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 +25 -6
- package/src/utils/hydration-scripts.js +150 -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/reachability-analyzer.d.ts +12 -1
- package/src/utils/reachability-analyzer.js +101 -5
- package/src/utils/react-dom-runtime-interop-plugin.d.ts +5 -0
- package/src/utils/react-dom-runtime-interop-plugin.js +29 -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 +6 -0
- package/src/utils/react-runtime-alias-map.js +33 -0
- package/src/utils/use-sync-external-store-shim-plugin.d.ts +5 -0
- package/src/utils/use-sync-external-store-shim-plugin.js +41 -0
- package/src/react-hmr-strategy.ts +0 -444
- 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 -212
- 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 -590
- 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 -440
- package/src/utils/react-mdx-loader-plugin.ts +0 -40
|
@@ -42,6 +42,15 @@ export type ReachabilityResult = {
|
|
|
42
42
|
*/
|
|
43
43
|
analyzed: boolean;
|
|
44
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* Optional export filter supplied by the client graph boundary when a local
|
|
47
|
+
* module is imported through a narrower named-export surface.
|
|
48
|
+
*
|
|
49
|
+
* `'*'` means the whole module namespace is considered reachable, while a
|
|
50
|
+
* `Set` restricts analysis to the named exports that are actually requested by
|
|
51
|
+
* downstream client-reachable modules.
|
|
52
|
+
*/
|
|
53
|
+
type ExplicitlyRequestedExports = Set<string> | '*';
|
|
45
54
|
/**
|
|
46
55
|
* Analyzes a module using Oxc AST and extracts a strict reachability graph
|
|
47
56
|
* starting from client roots (`render`, `errorBoundary`, `loadingFallback` of `eco.page` or `eco.component`).
|
|
@@ -50,6 +59,8 @@ export type ReachabilityResult = {
|
|
|
50
59
|
* @param filename - Absolute or relative path to the module file.
|
|
51
60
|
* @param program - Optional pre-parsed Oxc program AST. When supplied, the
|
|
52
61
|
* internal `parseSync` call is skipped entirely (avoids double-parsing).
|
|
62
|
+
* @param explicitlyRequestedExports - Optional named export filter propagated
|
|
63
|
+
* from a downstream importer when this module is only partially reachable.
|
|
53
64
|
*/
|
|
54
|
-
export declare function analyzeReachability(source: string, filename: string, program?: ReturnType<typeof parseSync>['program']): ReachabilityResult;
|
|
65
|
+
export declare function analyzeReachability(source: string, filename: string, program?: ReturnType<typeof parseSync>['program'], explicitlyRequestedExports?: ExplicitlyRequestedExports): ReachabilityResult;
|
|
55
66
|
export {};
|
|
@@ -7,7 +7,7 @@ function parserLanguageForFile(filename) {
|
|
|
7
7
|
if (extension === ".jsx") return "jsx";
|
|
8
8
|
return "js";
|
|
9
9
|
}
|
|
10
|
-
function analyzeReachability(source, filename, program) {
|
|
10
|
+
function analyzeReachability(source, filename, program, explicitlyRequestedExports) {
|
|
11
11
|
let resolvedProgram;
|
|
12
12
|
if (program) {
|
|
13
13
|
resolvedProgram = program;
|
|
@@ -114,12 +114,86 @@ function analyzeReachability(source, filename, program) {
|
|
|
114
114
|
potentialClientRoots.push(node);
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
+
function getExportedName(specifier) {
|
|
118
|
+
if (specifier?.exported?.type === "Identifier") return specifier.exported.name;
|
|
119
|
+
if (typeof specifier?.exported?.value === "string") return specifier.exported.value;
|
|
120
|
+
if (specifier?.local?.type === "Identifier") return specifier.local.name;
|
|
121
|
+
if (typeof specifier?.local?.value === "string") return specifier.local.value;
|
|
122
|
+
return void 0;
|
|
123
|
+
}
|
|
124
|
+
function getReexportedImportName(specifier) {
|
|
125
|
+
if (specifier?.local?.type === "Identifier") return specifier.local.name;
|
|
126
|
+
if (typeof specifier?.local?.value === "string") return specifier.local.value;
|
|
127
|
+
if (specifier?.imported?.type === "Identifier") return specifier.imported.name;
|
|
128
|
+
if (typeof specifier?.imported?.value === "string") return specifier.imported.value;
|
|
129
|
+
return getExportedName(specifier);
|
|
130
|
+
}
|
|
131
|
+
function getLocalExportName(specifier) {
|
|
132
|
+
if (specifier?.local?.type === "Identifier") return specifier.local.name;
|
|
133
|
+
if (typeof specifier?.local?.value === "string") return specifier.local.value;
|
|
134
|
+
return void 0;
|
|
135
|
+
}
|
|
136
|
+
function isExplicitlyRequestedExport(name) {
|
|
137
|
+
if (explicitlyRequestedExports === "*") return true;
|
|
138
|
+
return explicitlyRequestedExports?.has(name) ?? false;
|
|
139
|
+
}
|
|
117
140
|
let isFallbackRoots = false;
|
|
118
141
|
if (potentialClientRoots.length === 0) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
142
|
+
if (explicitlyRequestedExports) {
|
|
143
|
+
for (const node of resolvedProgram.body) {
|
|
144
|
+
if (node.type === "ExportNamedDeclaration") {
|
|
145
|
+
const exportNode = node;
|
|
146
|
+
if (exportNode.source && exportNode.specifiers?.length) {
|
|
147
|
+
const hasRequestedReexport = exportNode.specifiers.some((specifier) => {
|
|
148
|
+
const exportedName = getExportedName(specifier);
|
|
149
|
+
return exportedName ? isExplicitlyRequestedExport(exportedName) : false;
|
|
150
|
+
});
|
|
151
|
+
if (hasRequestedReexport) {
|
|
152
|
+
potentialClientRoots.push(node);
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (exportNode.declaration?.type === "FunctionDeclaration" || exportNode.declaration?.type === "ClassDeclaration") {
|
|
157
|
+
const declarationName = exportNode.declaration.id?.name;
|
|
158
|
+
if (declarationName && isExplicitlyRequestedExport(declarationName)) {
|
|
159
|
+
potentialClientRoots.push(node);
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (exportNode.declaration?.type === "VariableDeclaration") {
|
|
164
|
+
const hasRequestedDeclaration = exportNode.declaration.declarations.some(
|
|
165
|
+
(declaration) => declaration.id?.type === "Identifier" && isExplicitlyRequestedExport(declaration.id.name)
|
|
166
|
+
);
|
|
167
|
+
if (hasRequestedDeclaration) {
|
|
168
|
+
potentialClientRoots.push(node);
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (exportNode.specifiers?.length) {
|
|
173
|
+
const hasRequestedSpecifier = exportNode.specifiers.some((specifier) => {
|
|
174
|
+
const exportedName = getExportedName(specifier);
|
|
175
|
+
return exportedName ? isExplicitlyRequestedExport(exportedName) : false;
|
|
176
|
+
});
|
|
177
|
+
if (hasRequestedSpecifier) {
|
|
178
|
+
potentialClientRoots.push(node);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} else if (node.type === "ExportDefaultDeclaration") {
|
|
182
|
+
if (isExplicitlyRequestedExport("default")) {
|
|
183
|
+
potentialClientRoots.push(node);
|
|
184
|
+
}
|
|
185
|
+
} else if (node.type === "ExportAllDeclaration") {
|
|
186
|
+
if (explicitlyRequestedExports === "*") {
|
|
187
|
+
potentialClientRoots.push(node);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
isFallbackRoots = true;
|
|
193
|
+
for (const node of resolvedProgram.body) {
|
|
194
|
+
if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration" || node.type === "ExportAllDeclaration") {
|
|
195
|
+
potentialClientRoots.push(node);
|
|
196
|
+
}
|
|
123
197
|
}
|
|
124
198
|
}
|
|
125
199
|
}
|
|
@@ -167,6 +241,28 @@ function analyzeReachability(source, filename, program) {
|
|
|
167
241
|
markImportReachable(node.source.value, "*");
|
|
168
242
|
return;
|
|
169
243
|
}
|
|
244
|
+
if (node.type === "ExportNamedDeclaration" && typeof node.source?.value === "string") {
|
|
245
|
+
for (const specifier of node.specifiers ?? []) {
|
|
246
|
+
const importedName = getReexportedImportName(specifier);
|
|
247
|
+
if (importedName) {
|
|
248
|
+
markImportReachable(node.source.value, importedName);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (node.type === "ExportNamedDeclaration" && !node.source && explicitlyRequestedExports && node.specifiers?.length) {
|
|
254
|
+
for (const specifier of node.specifiers) {
|
|
255
|
+
const exportedName = getExportedName(specifier);
|
|
256
|
+
if (!exportedName || !isExplicitlyRequestedExport(exportedName)) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const localName = getLocalExportName(specifier);
|
|
260
|
+
if (localName && !currentScope.has(localName)) {
|
|
261
|
+
checkIdentifier(localName);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
170
266
|
if (node.type === "Identifier" || node.type === "JSXIdentifier" && /^[A-Z]/.test(node.name)) {
|
|
171
267
|
if (!currentScope.has(node.name)) {
|
|
172
268
|
checkIdentifier(node.name);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function createReactDomRuntimeInteropPlugin(options) {
|
|
4
|
+
const reactDomFileFilter = /[\\/]react-dom[\\/].*\.js$/;
|
|
5
|
+
const reactRequirePattern = /\brequire\((['"])react\1\)/g;
|
|
6
|
+
const reactSpecifier = options?.reactSpecifier ?? "react";
|
|
7
|
+
return {
|
|
8
|
+
name: options?.name ?? "react-dom-runtime-interop",
|
|
9
|
+
setup(build) {
|
|
10
|
+
build.onLoad({ filter: reactDomFileFilter }, (args) => {
|
|
11
|
+
const content = fs.readFileSync(args.path, "utf-8");
|
|
12
|
+
if (!reactRequirePattern.test(content)) {
|
|
13
|
+
return void 0;
|
|
14
|
+
}
|
|
15
|
+
reactRequirePattern.lastIndex = 0;
|
|
16
|
+
const rewritten = content.replace(reactRequirePattern, "__ecopages_react_runtime");
|
|
17
|
+
return {
|
|
18
|
+
contents: `import * as __ecopages_react_runtime from '${reactSpecifier}';
|
|
19
|
+
${rewritten}`,
|
|
20
|
+
loader: "js",
|
|
21
|
+
resolveDir: path.dirname(args.path)
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
createReactDomRuntimeInteropPlugin
|
|
29
|
+
};
|
|
@@ -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,6 @@
|
|
|
1
|
+
import type { ReactRouterAdapter } from '../router-adapter.js';
|
|
2
|
+
import type { ReactRuntimeImports } from '../services/react-runtime-bundle.service.js';
|
|
3
|
+
export declare const REACT_RUNTIME_SPECIFIERS: readonly ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime", "react-dom/client"];
|
|
4
|
+
export declare function buildReactRuntimeAliasMap(runtimeImports: ReactRuntimeImports): Record<string, string>;
|
|
5
|
+
export declare function getReactRuntimeExternalSpecifiers(): string[];
|
|
6
|
+
export declare function getReactClientGraphAllowSpecifiers(runtimeSpecifiers: Iterable<string>, routerAdapter?: ReactRouterAdapter): string[];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const REACT_RUNTIME_SPECIFIERS = [
|
|
2
|
+
"react",
|
|
3
|
+
"react-dom",
|
|
4
|
+
"react/jsx-runtime",
|
|
5
|
+
"react/jsx-dev-runtime",
|
|
6
|
+
"react-dom/client"
|
|
7
|
+
];
|
|
8
|
+
function buildReactRuntimeAliasMap(runtimeImports) {
|
|
9
|
+
return {
|
|
10
|
+
react: runtimeImports.react,
|
|
11
|
+
"react/jsx-runtime": runtimeImports.reactJsxRuntime,
|
|
12
|
+
"react/jsx-dev-runtime": runtimeImports.reactJsxDevRuntime,
|
|
13
|
+
"react-dom": runtimeImports.reactDom,
|
|
14
|
+
"react-dom/client": runtimeImports.reactDomClient
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function getReactRuntimeExternalSpecifiers() {
|
|
18
|
+
return [...REACT_RUNTIME_SPECIFIERS];
|
|
19
|
+
}
|
|
20
|
+
function getReactClientGraphAllowSpecifiers(runtimeSpecifiers, routerAdapter) {
|
|
21
|
+
return [
|
|
22
|
+
"@ecopages/core",
|
|
23
|
+
...REACT_RUNTIME_SPECIFIERS,
|
|
24
|
+
...routerAdapter ? [routerAdapter.bundle.importPath] : [],
|
|
25
|
+
...Array.from(runtimeSpecifiers)
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
REACT_RUNTIME_SPECIFIERS,
|
|
30
|
+
buildReactRuntimeAliasMap,
|
|
31
|
+
getReactClientGraphAllowSpecifiers,
|
|
32
|
+
getReactRuntimeExternalSpecifiers
|
|
33
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
function createUseSyncExternalStoreShimPlugin(options) {
|
|
2
|
+
const namespace = options?.namespace ?? "ecopages-react-use-sync-external-store-shim";
|
|
3
|
+
return {
|
|
4
|
+
name: options?.name ?? "react-use-sync-external-store-shim",
|
|
5
|
+
setup(build) {
|
|
6
|
+
build.onResolve({ filter: /^use-sync-external-store\/shim(?:\/index\.js)?$/ }, () => ({
|
|
7
|
+
path: "use-sync-external-store/shim",
|
|
8
|
+
namespace
|
|
9
|
+
}));
|
|
10
|
+
build.onLoad({ filter: /^use-sync-external-store\/shim$/, namespace }, () => ({
|
|
11
|
+
contents: "export { useSyncExternalStore } from 'react';",
|
|
12
|
+
loader: "js"
|
|
13
|
+
}));
|
|
14
|
+
build.onLoad({ filter: /[\\/]use-sync-external-store[\\/]shim[\\/]index\.js$/ }, () => ({
|
|
15
|
+
contents: "export { useSyncExternalStore } from 'react';",
|
|
16
|
+
loader: "js"
|
|
17
|
+
}));
|
|
18
|
+
build.onLoad(
|
|
19
|
+
{
|
|
20
|
+
filter: /[\\/]use-sync-external-store[\\/]cjs[\\/]use-sync-external-store-shim\.development\.js$/
|
|
21
|
+
},
|
|
22
|
+
() => ({
|
|
23
|
+
contents: "export { useSyncExternalStore } from 'react';",
|
|
24
|
+
loader: "js"
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
build.onLoad(
|
|
28
|
+
{
|
|
29
|
+
filter: /[\\/]use-sync-external-store[\\/]cjs[\\/]use-sync-external-store-shim\.production\.js$/
|
|
30
|
+
},
|
|
31
|
+
() => ({
|
|
32
|
+
contents: "export { useSyncExternalStore } from 'react';",
|
|
33
|
+
loader: "js"
|
|
34
|
+
})
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
createUseSyncExternalStoreShimPlugin
|
|
41
|
+
};
|