@ecopages/react 0.2.0-alpha.2 → 0.2.0-alpha.20
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 +19 -39
- package/README.md +160 -18
- package/package.json +6 -6
- package/src/react-hmr-strategy.d.ts +26 -21
- package/src/react-hmr-strategy.js +91 -110
- package/src/react-renderer.d.ts +165 -41
- package/src/react-renderer.js +451 -158
- package/src/react.constants.d.ts +1 -0
- package/src/react.constants.js +4 -0
- package/src/react.plugin.d.ts +37 -108
- package/src/react.plugin.js +125 -54
- package/src/react.types.d.ts +88 -0
- package/src/react.types.js +0 -0
- package/src/router-adapter.d.ts +2 -2
- package/src/services/react-bundle.service.d.ts +4 -25
- package/src/services/react-bundle.service.js +39 -91
- 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 +7 -6
- package/src/services/react-hydration-asset.service.js +29 -17
- 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 +8 -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 +14 -12
- package/src/services/react-runtime-bundle.service.js +103 -180
- 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 +19 -4
- package/src/utils/hydration-scripts.js +102 -39
- package/src/utils/hydration-scripts.test.browser.d.ts +1 -0
- package/src/utils/hydration-scripts.test.browser.js +126 -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.js +13 -5
- package/src/utils/react-runtime-specifier-map.d.ts +6 -0
- package/src/utils/react-runtime-specifier-map.js +37 -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
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
3
|
import { rapidhash } from "@ecopages/core/hash";
|
|
4
|
-
import {
|
|
4
|
+
import { build } from "@ecopages/core/build/build-adapter";
|
|
5
5
|
import { fileSystem } from "@ecopages/file-system";
|
|
6
|
+
import { someInConfigTree } from "../utils/component-config-traversal.js";
|
|
6
7
|
import { collectDeclaredModulesInConfig } from "../utils/declared-modules.js";
|
|
7
8
|
class ReactPageModuleService {
|
|
9
|
+
config;
|
|
8
10
|
constructor(config) {
|
|
9
11
|
this.config = config;
|
|
10
12
|
}
|
|
@@ -22,7 +24,7 @@ class ReactPageModuleService {
|
|
|
22
24
|
* @param filePath - Absolute path to the MDX file
|
|
23
25
|
* @returns The imported module
|
|
24
26
|
*/
|
|
25
|
-
async importMdxPageFile(filePath) {
|
|
27
|
+
async importMdxPageFile(filePath, options) {
|
|
26
28
|
const { createReactMdxLoaderPlugin } = await import("../utils/react-mdx-loader-plugin.js");
|
|
27
29
|
const mdxPlugin = createReactMdxLoaderPlugin(
|
|
28
30
|
this.config.mdxCompilerOptions ?? {
|
|
@@ -31,24 +33,28 @@ class ReactPageModuleService {
|
|
|
31
33
|
development: process?.env?.NODE_ENV === "development"
|
|
32
34
|
}
|
|
33
35
|
);
|
|
34
|
-
const outdir = path.join(this.config.
|
|
36
|
+
const outdir = path.join(this.config.workDir, ".server-modules-react-mdx");
|
|
35
37
|
const fileBaseName = path.basename(filePath, path.extname(filePath));
|
|
36
38
|
const fileHash = fileSystem.hash(filePath);
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
const cacheScopeSuffix = options?.cacheScope ? `-${sanitizeCacheScope(options.cacheScope)}` : "";
|
|
40
|
+
const cacheBuster = options?.bypassCache || process?.env?.NODE_ENV === "development" ? `-${Date.now()}` : "";
|
|
41
|
+
const outputFileName = `${fileBaseName}-${fileHash}${cacheScopeSuffix}${cacheBuster}.js`;
|
|
42
|
+
const buildResult = await build(
|
|
43
|
+
{
|
|
44
|
+
entrypoints: [filePath],
|
|
45
|
+
root: this.config.rootDir,
|
|
46
|
+
outdir,
|
|
47
|
+
target: "es2022",
|
|
48
|
+
format: "esm",
|
|
49
|
+
sourcemap: "none",
|
|
50
|
+
splitting: false,
|
|
51
|
+
minify: false,
|
|
52
|
+
treeshaking: false,
|
|
53
|
+
naming: outputFileName,
|
|
54
|
+
plugins: [mdxPlugin]
|
|
55
|
+
},
|
|
56
|
+
this.config.buildExecutor
|
|
57
|
+
);
|
|
52
58
|
if (!buildResult.success) {
|
|
53
59
|
const details = buildResult.logs.map((log) => log.message).join(" | ");
|
|
54
60
|
throw new Error(`Failed to compile MDX page module: ${details}`);
|
|
@@ -58,7 +64,17 @@ class ReactPageModuleService {
|
|
|
58
64
|
if (!compiledOutput) {
|
|
59
65
|
throw new Error(`No compiled MDX output generated for page: ${filePath}`);
|
|
60
66
|
}
|
|
61
|
-
|
|
67
|
+
const compiledOutputUrl = pathToFileURL(compiledOutput);
|
|
68
|
+
if (process?.env?.NODE_ENV === "development" || options?.cacheScope) {
|
|
69
|
+
compiledOutputUrl.searchParams.set(
|
|
70
|
+
"update",
|
|
71
|
+
[fileHash, options?.cacheScope ? sanitizeCacheScope(options.cacheScope) : void 0].filter((value) => value !== void 0).join("-")
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return await import(
|
|
75
|
+
/* @vite-ignore */
|
|
76
|
+
compiledOutputUrl.href
|
|
77
|
+
);
|
|
62
78
|
}
|
|
63
79
|
/**
|
|
64
80
|
* Ensures that an EcoComponentConfig has proper `__eco` metadata attached.
|
|
@@ -91,7 +107,7 @@ class ReactPageModuleService {
|
|
|
91
107
|
if (fileSystem.exists(resolvedDependency)) {
|
|
92
108
|
return {
|
|
93
109
|
...config,
|
|
94
|
-
__eco: buildEcoMeta(
|
|
110
|
+
__eco: buildEcoMeta(path.join(candidateDir, path.basename(pagePath)))
|
|
95
111
|
};
|
|
96
112
|
}
|
|
97
113
|
}
|
|
@@ -105,23 +121,11 @@ class ReactPageModuleService {
|
|
|
105
121
|
* Recursively checks whether a component config tree declares any browser modules.
|
|
106
122
|
* Used to determine if a page needs hydration.
|
|
107
123
|
*/
|
|
108
|
-
hasModulesInConfig(config
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (config.dependencies?.modules?.some((entry) => entry.trim().length > 0)) {
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
if (config.layout?.config && this.hasModulesInConfig(config.layout.config, visited)) {
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
for (const component of config.dependencies?.components ?? []) {
|
|
120
|
-
if (this.hasModulesInConfig(component.config, visited)) {
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return false;
|
|
124
|
+
hasModulesInConfig(config) {
|
|
125
|
+
return someInConfigTree(
|
|
126
|
+
config,
|
|
127
|
+
(node) => node.dependencies?.modules?.some((entry) => entry.trim().length > 0) ?? false
|
|
128
|
+
);
|
|
125
129
|
}
|
|
126
130
|
/**
|
|
127
131
|
* Determines whether a page needs client-side hydration.
|
|
@@ -150,6 +154,9 @@ class ReactPageModuleService {
|
|
|
150
154
|
return Array.from(new Set(declarations));
|
|
151
155
|
}
|
|
152
156
|
}
|
|
157
|
+
function sanitizeCacheScope(cacheScope) {
|
|
158
|
+
return cacheScope.replace(/[^a-zA-Z0-9_-]+/g, "-");
|
|
159
|
+
}
|
|
153
160
|
export {
|
|
154
161
|
ReactPageModuleService
|
|
155
162
|
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { HtmlTemplateProps, IntegrationRendererRenderOptions, RequestLocals } from '@ecopages/core';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
type PagePayloadOptions = {
|
|
4
|
+
pageProps?: HtmlTemplateProps['pageProps'];
|
|
5
|
+
params: IntegrationRendererRenderOptions<ReactNode>['params'];
|
|
6
|
+
query: IntegrationRendererRenderOptions<ReactNode>['query'];
|
|
7
|
+
safeLocals?: RequestLocals;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Builds the serialized page payload React exposes to document shells and the browser.
|
|
11
|
+
*
|
|
12
|
+
* This keeps hydration payload shaping away from the renderer so the renderer can
|
|
13
|
+
* stay focused on component orchestration instead of data serialization rules.
|
|
14
|
+
*/
|
|
15
|
+
export declare class ReactPagePayloadService {
|
|
16
|
+
/**
|
|
17
|
+
* Creates the canonical page-props payload used by router hydration.
|
|
18
|
+
*
|
|
19
|
+
* React pages embedded in a non-React HTML shell still need to expose the same
|
|
20
|
+
* page-data contract as fully React-owned documents so navigation and hydration
|
|
21
|
+
* can read one shared document payload consistently.
|
|
22
|
+
*/
|
|
23
|
+
buildRouterPageDataScript(pageProps: HtmlTemplateProps['pageProps'] | undefined): string;
|
|
24
|
+
/**
|
|
25
|
+
* Builds the serialized page-props payload embedded into the final HTML.
|
|
26
|
+
*
|
|
27
|
+
* The document payload is intentionally narrower than the full server render
|
|
28
|
+
* input: only routing data, public page props, and explicitly allowed locals are
|
|
29
|
+
* exposed to the browser.
|
|
30
|
+
*/
|
|
31
|
+
buildSerializedPageProps(options: PagePayloadOptions): HtmlTemplateProps['pageProps'];
|
|
32
|
+
/**
|
|
33
|
+
* Safely extracts the declared subset of locals for client-side hydration.
|
|
34
|
+
*
|
|
35
|
+
* On dynamic pages with `cache: 'dynamic'`, middleware populates `locals` with
|
|
36
|
+
* request-scoped data (e.g., session). Only keys explicitly declared via
|
|
37
|
+
* `Page.requires` are serialized to the client so sensitive request-only data
|
|
38
|
+
* is not leaked into hydration payloads by default.
|
|
39
|
+
*
|
|
40
|
+
* On static pages, `locals` is a Proxy that throws `LocalsAccessError` on access
|
|
41
|
+
* to prevent accidental use. This method safely detects that case and returns
|
|
42
|
+
* `undefined` instead of throwing.
|
|
43
|
+
*/
|
|
44
|
+
getSerializableLocals(locals: RequestLocals | undefined, requiredLocals?: string | readonly string[]): RequestLocals | undefined;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { LocalsAccessError } from "@ecopages/core/errors/locals-access-error";
|
|
2
|
+
class ReactPagePayloadService {
|
|
3
|
+
/**
|
|
4
|
+
* Creates the canonical page-props payload used by router hydration.
|
|
5
|
+
*
|
|
6
|
+
* React pages embedded in a non-React HTML shell still need to expose the same
|
|
7
|
+
* page-data contract as fully React-owned documents so navigation and hydration
|
|
8
|
+
* can read one shared document payload consistently.
|
|
9
|
+
*/
|
|
10
|
+
buildRouterPageDataScript(pageProps) {
|
|
11
|
+
const safeJson = JSON.stringify(pageProps || {}).replace(/</g, "\\u003c");
|
|
12
|
+
return `<script id="__ECO_PAGE_DATA__" type="application/json">${safeJson}<\/script>`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Builds the serialized page-props payload embedded into the final HTML.
|
|
16
|
+
*
|
|
17
|
+
* The document payload is intentionally narrower than the full server render
|
|
18
|
+
* input: only routing data, public page props, and explicitly allowed locals are
|
|
19
|
+
* exposed to the browser.
|
|
20
|
+
*/
|
|
21
|
+
buildSerializedPageProps(options) {
|
|
22
|
+
return {
|
|
23
|
+
...options.pageProps,
|
|
24
|
+
params: options.params,
|
|
25
|
+
query: options.query,
|
|
26
|
+
...options.safeLocals && { locals: options.safeLocals }
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Safely extracts the declared subset of locals for client-side hydration.
|
|
31
|
+
*
|
|
32
|
+
* On dynamic pages with `cache: 'dynamic'`, middleware populates `locals` with
|
|
33
|
+
* request-scoped data (e.g., session). Only keys explicitly declared via
|
|
34
|
+
* `Page.requires` are serialized to the client so sensitive request-only data
|
|
35
|
+
* is not leaked into hydration payloads by default.
|
|
36
|
+
*
|
|
37
|
+
* On static pages, `locals` is a Proxy that throws `LocalsAccessError` on access
|
|
38
|
+
* to prevent accidental use. This method safely detects that case and returns
|
|
39
|
+
* `undefined` instead of throwing.
|
|
40
|
+
*/
|
|
41
|
+
getSerializableLocals(locals, requiredLocals) {
|
|
42
|
+
try {
|
|
43
|
+
if (!locals) {
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
const requiredKeys = requiredLocals ? Array.isArray(requiredLocals) ? requiredLocals : [requiredLocals] : [];
|
|
47
|
+
if (requiredKeys.length === 0) {
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
const serializedLocals = Object.fromEntries(
|
|
51
|
+
requiredKeys.filter((key) => Object.prototype.hasOwnProperty.call(locals, key)).map((key) => [key, locals[key]])
|
|
52
|
+
);
|
|
53
|
+
if (Object.keys(serializedLocals).length > 0) {
|
|
54
|
+
return serializedLocals;
|
|
55
|
+
}
|
|
56
|
+
return void 0;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error instanceof LocalsAccessError) {
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
ReactPagePayloadService
|
|
67
|
+
};
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* Runtime bundle service for React integration.
|
|
3
3
|
*
|
|
4
4
|
* Owns creation of the browser runtime assets for React and React DOM,
|
|
5
|
-
* including
|
|
6
|
-
* interop rewriting.
|
|
5
|
+
* including shared runtime entry generation and specifier mapping.
|
|
7
6
|
*
|
|
8
7
|
* @module
|
|
9
8
|
*/
|
|
@@ -20,19 +19,22 @@ export type ReactRuntimeImports = {
|
|
|
20
19
|
};
|
|
21
20
|
export interface ReactRuntimeBundleServiceConfig {
|
|
22
21
|
routerAdapter?: ReactRouterAdapter;
|
|
22
|
+
rootDir?: string;
|
|
23
23
|
}
|
|
24
|
+
type RuntimeMode = 'development' | 'production';
|
|
24
25
|
export declare class ReactRuntimeBundleService {
|
|
25
26
|
private readonly config;
|
|
26
27
|
constructor(config: ReactRuntimeBundleServiceConfig);
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
setRootDir(rootDir: string | undefined): void;
|
|
29
|
+
private get isDevelopment();
|
|
30
|
+
private getCurrentRuntimeMode;
|
|
31
|
+
private createRuntimeDefines;
|
|
32
|
+
private getReactVendorFileName;
|
|
33
|
+
private getReactDomVendorFileName;
|
|
34
|
+
private getRouterVendorFileName;
|
|
35
|
+
getRuntimeImports(mode?: RuntimeMode): ReactRuntimeImports;
|
|
36
|
+
getSpecifierMap(mode?: RuntimeMode): Record<string, string>;
|
|
29
37
|
getDependencies(): AssetDefinition[];
|
|
30
|
-
createRuntimeAliasPlugin(): EcoBuildPlugin;
|
|
31
|
-
private buildImportMapSourceUrl;
|
|
32
|
-
private createRuntimeSpecifierAliasPlugin;
|
|
33
|
-
private createReactDomRuntimeInteropPlugin;
|
|
34
|
-
private getRuntimeArtifactsDir;
|
|
35
|
-
private createRuntimeEntry;
|
|
36
|
-
private getModuleExportNames;
|
|
37
|
-
private isValidExportName;
|
|
38
|
+
createRuntimeAliasPlugin(mode?: RuntimeMode): EcoBuildPlugin;
|
|
38
39
|
}
|
|
40
|
+
export {};
|
|
@@ -1,205 +1,128 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { createRuntimeSpecifierAliasPlugin } from "@ecopages/core/build/runtime-specifier-alias-plugin";
|
|
2
|
+
import {
|
|
3
|
+
buildBrowserRuntimeAssetUrl,
|
|
4
|
+
createBrowserRuntimeModuleAsset,
|
|
5
|
+
createBrowserRuntimeScriptAsset
|
|
6
|
+
} from "@ecopages/core/services/asset-processing-service";
|
|
7
|
+
import { createReactDomRuntimeInteropPlugin } from "../utils/react-dom-runtime-interop-plugin.js";
|
|
8
|
+
import { buildReactRuntimeSpecifierMap } from "../utils/react-runtime-specifier-map.js";
|
|
5
9
|
class ReactRuntimeBundleService {
|
|
10
|
+
config;
|
|
6
11
|
constructor(config) {
|
|
7
12
|
this.config = config;
|
|
8
13
|
}
|
|
9
|
-
|
|
14
|
+
setRootDir(rootDir) {
|
|
15
|
+
this.config.rootDir = rootDir;
|
|
16
|
+
}
|
|
17
|
+
get isDevelopment() {
|
|
18
|
+
return process.env.NODE_ENV === "development";
|
|
19
|
+
}
|
|
20
|
+
getCurrentRuntimeMode() {
|
|
21
|
+
return this.isDevelopment ? "development" : "production";
|
|
22
|
+
}
|
|
23
|
+
createRuntimeDefines(mode) {
|
|
24
|
+
const nodeEnv = JSON.stringify(mode);
|
|
25
|
+
return {
|
|
26
|
+
"process.env.NODE_ENV": nodeEnv,
|
|
27
|
+
"import.meta.env.NODE_ENV": nodeEnv
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
getReactVendorFileName(mode) {
|
|
31
|
+
return mode === "development" ? "react.development.js" : "react.js";
|
|
32
|
+
}
|
|
33
|
+
getReactDomVendorFileName(mode) {
|
|
34
|
+
return mode === "development" ? "react-dom.development.js" : "react-dom.js";
|
|
35
|
+
}
|
|
36
|
+
getRouterVendorFileName(mode) {
|
|
37
|
+
if (!this.config.routerAdapter) {
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
return mode === "development" ? `${this.config.routerAdapter.bundle.outputName}.development.js` : `${this.config.routerAdapter.bundle.outputName}.js`;
|
|
41
|
+
}
|
|
42
|
+
getRuntimeImports(mode = this.getCurrentRuntimeMode()) {
|
|
43
|
+
const reactVendorFileName = this.getReactVendorFileName(mode);
|
|
44
|
+
const reactDomVendorFileName = this.getReactDomVendorFileName(mode);
|
|
10
45
|
const runtimeImports = {
|
|
11
|
-
react:
|
|
12
|
-
reactDomClient:
|
|
13
|
-
reactJsxRuntime:
|
|
14
|
-
reactJsxDevRuntime:
|
|
15
|
-
reactDom:
|
|
46
|
+
react: buildBrowserRuntimeAssetUrl(reactVendorFileName),
|
|
47
|
+
reactDomClient: buildBrowserRuntimeAssetUrl(reactDomVendorFileName),
|
|
48
|
+
reactJsxRuntime: buildBrowserRuntimeAssetUrl(reactVendorFileName),
|
|
49
|
+
reactJsxDevRuntime: buildBrowserRuntimeAssetUrl(reactVendorFileName),
|
|
50
|
+
reactDom: buildBrowserRuntimeAssetUrl(reactDomVendorFileName)
|
|
16
51
|
};
|
|
17
52
|
if (this.config.routerAdapter) {
|
|
18
|
-
runtimeImports.router = this.
|
|
53
|
+
runtimeImports.router = buildBrowserRuntimeAssetUrl(this.getRouterVendorFileName(mode));
|
|
19
54
|
}
|
|
20
55
|
return runtimeImports;
|
|
21
56
|
}
|
|
22
|
-
getSpecifierMap() {
|
|
23
|
-
|
|
24
|
-
const map = {
|
|
25
|
-
react: runtimeImports.react,
|
|
26
|
-
"react/jsx-runtime": runtimeImports.reactJsxRuntime,
|
|
27
|
-
"react/jsx-dev-runtime": runtimeImports.reactJsxDevRuntime,
|
|
28
|
-
"react-dom": runtimeImports.reactDom,
|
|
29
|
-
"react-dom/client": runtimeImports.reactDomClient
|
|
30
|
-
};
|
|
31
|
-
if (this.config.routerAdapter && runtimeImports.router) {
|
|
32
|
-
map[this.config.routerAdapter.importMapKey] = runtimeImports.router;
|
|
33
|
-
}
|
|
34
|
-
return map;
|
|
57
|
+
getSpecifierMap(mode = this.getCurrentRuntimeMode()) {
|
|
58
|
+
return buildReactRuntimeSpecifierMap(this.getRuntimeImports(mode), this.config.routerAdapter);
|
|
35
59
|
}
|
|
36
60
|
getDependencies() {
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const reactEntry = this.createRuntimeEntry(
|
|
44
|
-
[
|
|
45
|
-
{ specifier: "react", defaultExport: true },
|
|
46
|
-
{ specifier: "react/jsx-runtime" },
|
|
47
|
-
{ specifier: "react/jsx-dev-runtime" }
|
|
48
|
-
],
|
|
49
|
-
"react-entry.mjs"
|
|
50
|
-
);
|
|
51
|
-
const reactDomEntry = this.createRuntimeEntry(
|
|
52
|
-
[{ specifier: "react-dom", defaultExport: true }, { specifier: "react-dom/client" }],
|
|
53
|
-
"react-dom-entry.mjs"
|
|
54
|
-
);
|
|
55
|
-
const dependencies = [
|
|
56
|
-
AssetFactory.createNodeModuleScript({
|
|
57
|
-
position: "head",
|
|
58
|
-
importPath: reactEntry,
|
|
59
|
-
name: "react",
|
|
60
|
-
excludeFromHtml: true,
|
|
61
|
-
bundleOptions: { naming: "react.js" },
|
|
62
|
-
attributes: runtimeAttrs
|
|
63
|
-
}),
|
|
64
|
-
AssetFactory.createNodeModuleScript({
|
|
65
|
-
position: "head",
|
|
66
|
-
importPath: reactDomEntry,
|
|
67
|
-
name: "react-dom",
|
|
68
|
-
excludeFromHtml: true,
|
|
69
|
-
bundleOptions: {
|
|
70
|
-
naming: "react-dom.js",
|
|
71
|
-
plugins: [reactRuntimeAliasPlugin, reactDomRuntimeInteropPlugin]
|
|
61
|
+
const reactDomRuntimeInteropPlugin = createReactDomRuntimeInteropPlugin();
|
|
62
|
+
const dependencies = [];
|
|
63
|
+
for (const mode of ["production", "development"]) {
|
|
64
|
+
const reactRuntimeAliasPlugin = createRuntimeSpecifierAliasPlugin(
|
|
65
|
+
{
|
|
66
|
+
react: buildBrowserRuntimeAssetUrl(this.getReactVendorFileName(mode))
|
|
72
67
|
},
|
|
73
|
-
|
|
74
|
-
})
|
|
75
|
-
];
|
|
76
|
-
if (this.config.routerAdapter) {
|
|
77
|
-
const runtimeAliasPlugin = this.createRuntimeAliasPlugin();
|
|
78
|
-
const mappedSpecifiers = new Set(Object.keys(this.getSpecifierMap()));
|
|
79
|
-
const unresolvedExternals = this.config.routerAdapter.bundle.externals.filter(
|
|
80
|
-
(external) => !mappedSpecifiers.has(external)
|
|
68
|
+
{ name: `react-plugin-runtime-specifier-alias-${mode}` }
|
|
81
69
|
);
|
|
70
|
+
const reactDomBundlePlugins = [reactRuntimeAliasPlugin, reactDomRuntimeInteropPlugin].filter(
|
|
71
|
+
(plugin) => plugin !== null
|
|
72
|
+
);
|
|
73
|
+
const runtimeAliasPlugin = this.createRuntimeAliasPlugin(mode);
|
|
74
|
+
const mappedSpecifiers = new Set(Object.keys(this.getSpecifierMap(mode)));
|
|
82
75
|
dependencies.push(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
76
|
+
createBrowserRuntimeModuleAsset({
|
|
77
|
+
modules: [
|
|
78
|
+
{ specifier: "react", defaultExport: true },
|
|
79
|
+
{ specifier: "react/jsx-runtime" },
|
|
80
|
+
{ specifier: "react/jsx-dev-runtime" }
|
|
81
|
+
],
|
|
82
|
+
name: "react",
|
|
83
|
+
fileName: this.getReactVendorFileName(mode),
|
|
84
|
+
cacheDirName: `ecopages-react-runtime-${mode}`,
|
|
85
|
+
rootDir: this.config.rootDir,
|
|
88
86
|
bundleOptions: {
|
|
89
|
-
|
|
90
|
-
external: unresolvedExternals,
|
|
91
|
-
plugins: [runtimeAliasPlugin]
|
|
92
|
-
},
|
|
93
|
-
attributes: runtimeAttrs
|
|
94
|
-
})
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
return dependencies;
|
|
98
|
-
}
|
|
99
|
-
createRuntimeAliasPlugin() {
|
|
100
|
-
const specifierMap = this.getSpecifierMap();
|
|
101
|
-
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
102
|
-
const filter = new RegExp(
|
|
103
|
-
`^(${Object.keys(specifierMap).map((key) => escapeRegExp(key)).join("|")})$`
|
|
104
|
-
);
|
|
105
|
-
return {
|
|
106
|
-
name: "react-plugin-runtime-alias",
|
|
107
|
-
setup(build) {
|
|
108
|
-
build.onResolve({ filter }, (args) => {
|
|
109
|
-
const mappedPath = specifierMap[args.path];
|
|
110
|
-
if (!mappedPath) {
|
|
111
|
-
return void 0;
|
|
87
|
+
define: this.createRuntimeDefines(mode)
|
|
112
88
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
createRuntimeSpecifierAliasPlugin(specifierMap, external = true) {
|
|
125
|
-
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
126
|
-
const filter = new RegExp(
|
|
127
|
-
`^(${Object.keys(specifierMap).map((key) => escapeRegExp(key)).join("|")})$`
|
|
128
|
-
);
|
|
129
|
-
return {
|
|
130
|
-
name: "react-plugin-runtime-specifier-alias",
|
|
131
|
-
setup(build) {
|
|
132
|
-
build.onResolve({ filter }, (args) => {
|
|
133
|
-
const mappedPath = specifierMap[args.path];
|
|
134
|
-
if (!mappedPath) {
|
|
135
|
-
return void 0;
|
|
136
|
-
}
|
|
137
|
-
return {
|
|
138
|
-
path: mappedPath,
|
|
139
|
-
external
|
|
140
|
-
};
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
createReactDomRuntimeInteropPlugin() {
|
|
146
|
-
const reactDomFileFilter = /[\\/]react-dom[\\/].*\.js$/;
|
|
147
|
-
const reactRequirePattern = /\brequire\((['"])react\1\)/g;
|
|
148
|
-
return {
|
|
149
|
-
name: "react-dom-runtime-interop",
|
|
150
|
-
setup(build) {
|
|
151
|
-
build.onLoad({ filter: reactDomFileFilter }, (args) => {
|
|
152
|
-
const content = fs.readFileSync(args.path, "utf-8");
|
|
153
|
-
if (!reactRequirePattern.test(content)) {
|
|
154
|
-
return void 0;
|
|
89
|
+
}),
|
|
90
|
+
createBrowserRuntimeModuleAsset({
|
|
91
|
+
modules: [{ specifier: "react-dom", defaultExport: true }, { specifier: "react-dom/client" }],
|
|
92
|
+
name: "react-dom",
|
|
93
|
+
fileName: this.getReactDomVendorFileName(mode),
|
|
94
|
+
cacheDirName: `ecopages-react-runtime-${mode}`,
|
|
95
|
+
rootDir: this.config.rootDir,
|
|
96
|
+
bundleOptions: {
|
|
97
|
+
define: this.createRuntimeDefines(mode),
|
|
98
|
+
plugins: reactDomBundlePlugins
|
|
155
99
|
}
|
|
156
|
-
|
|
157
|
-
const rewritten = content.replace(reactRequirePattern, "__ecopages_react_runtime");
|
|
158
|
-
return {
|
|
159
|
-
contents: `import * as __ecopages_react_runtime from 'react';
|
|
160
|
-
${rewritten}`,
|
|
161
|
-
loader: "js",
|
|
162
|
-
resolveDir: path.dirname(args.path)
|
|
163
|
-
};
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
getRuntimeArtifactsDir() {
|
|
169
|
-
const tmpDir = path.join(process.cwd(), "node_modules", ".cache", "ecopages-react-runtime");
|
|
170
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
171
|
-
return tmpDir;
|
|
172
|
-
}
|
|
173
|
-
createRuntimeEntry(modules, fileName) {
|
|
174
|
-
const tmpDir = this.getRuntimeArtifactsDir();
|
|
175
|
-
const requireFromRoot = createRequire(path.join(process.cwd(), "package.json"));
|
|
176
|
-
const seenExports = /* @__PURE__ */ new Set();
|
|
177
|
-
const statements = [];
|
|
178
|
-
for (const module of modules) {
|
|
179
|
-
if (module.defaultExport) {
|
|
180
|
-
statements.push(`import __ecopages_default_export__ from '${module.specifier}';`);
|
|
181
|
-
statements.push("export default __ecopages_default_export__;");
|
|
182
|
-
}
|
|
183
|
-
const exportNames = this.getModuleExportNames(module.specifier, requireFromRoot).filter(
|
|
184
|
-
(name) => !seenExports.has(name)
|
|
100
|
+
})
|
|
185
101
|
);
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
102
|
+
if (this.config.routerAdapter) {
|
|
103
|
+
const unresolvedExternals = this.config.routerAdapter.bundle.externals.filter(
|
|
104
|
+
(external) => !mappedSpecifiers.has(external)
|
|
105
|
+
);
|
|
106
|
+
dependencies.push(
|
|
107
|
+
createBrowserRuntimeScriptAsset({
|
|
108
|
+
importPath: this.config.routerAdapter.bundle.importPath,
|
|
109
|
+
name: this.config.routerAdapter.bundle.outputName,
|
|
110
|
+
fileName: this.getRouterVendorFileName(mode),
|
|
111
|
+
bundleOptions: {
|
|
112
|
+
define: this.createRuntimeDefines(mode),
|
|
113
|
+
external: unresolvedExternals,
|
|
114
|
+
plugins: [runtimeAliasPlugin]
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
);
|
|
191
118
|
}
|
|
192
119
|
}
|
|
193
|
-
|
|
194
|
-
fs.writeFileSync(filePath, statements.join("\n"), "utf-8");
|
|
195
|
-
return filePath;
|
|
196
|
-
}
|
|
197
|
-
getModuleExportNames(specifier, requireFromRoot) {
|
|
198
|
-
const moduleExports = requireFromRoot(specifier);
|
|
199
|
-
return Object.keys(moduleExports).filter((name) => name !== "__esModule" && name !== "default").filter((name) => this.isValidExportName(name)).sort();
|
|
120
|
+
return dependencies;
|
|
200
121
|
}
|
|
201
|
-
|
|
202
|
-
return
|
|
122
|
+
createRuntimeAliasPlugin(mode = this.getCurrentRuntimeMode()) {
|
|
123
|
+
return createRuntimeSpecifierAliasPlugin(this.getSpecifierMap(mode), {
|
|
124
|
+
name: `react-plugin-runtime-alias-${mode}`
|
|
125
|
+
});
|
|
203
126
|
}
|
|
204
127
|
}
|
|
205
128
|
export {
|