@ecopages/react 0.2.0-alpha.4 → 0.2.0-alpha.7
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 +23 -37
- package/README.md +143 -17
- package/package.json +3 -3
- package/src/react-hmr-strategy.d.ts +22 -19
- package/src/react-hmr-strategy.js +57 -109
- package/src/react-hmr-strategy.ts +76 -134
- package/src/react-renderer.d.ts +130 -11
- package/src/react-renderer.js +368 -64
- package/src/react-renderer.ts +490 -90
- package/src/react.plugin.d.ts +17 -5
- package/src/react.plugin.js +44 -13
- package/src/react.plugin.ts +49 -14
- package/src/router-adapter.d.ts +2 -2
- package/src/router-adapter.ts +2 -2
- package/src/services/react-bundle.service.d.ts +2 -25
- package/src/services/react-bundle.service.js +21 -91
- package/src/services/react-bundle.service.ts +22 -126
- package/src/services/react-hydration-asset.service.js +3 -3
- package/src/services/react-hydration-asset.service.ts +7 -4
- package/src/services/react-page-module.service.d.ts +3 -0
- package/src/services/react-page-module.service.js +20 -16
- package/src/services/react-page-module.service.ts +27 -17
- package/src/services/react-runtime-bundle.service.d.ts +12 -12
- package/src/services/react-runtime-bundle.service.js +98 -180
- package/src/services/react-runtime-bundle.service.ts +112 -211
- package/src/utils/client-graph-boundary-plugin.js +147 -9
- package/src/utils/client-graph-boundary-plugin.ts +252 -11
- package/src/utils/hydration-scripts.d.ts +18 -1
- package/src/utils/hydration-scripts.js +83 -32
- package/src/utils/hydration-scripts.ts +159 -38
- package/src/utils/reachability-analyzer.d.ts +12 -1
- package/src/utils/reachability-analyzer.js +101 -5
- package/src/utils/reachability-analyzer.ts +161 -8
- 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-dom-runtime-interop-plugin.ts +33 -0
- package/src/utils/react-mdx-loader-plugin.js +13 -5
- package/src/utils/react-mdx-loader-plugin.ts +28 -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/react-runtime-specifier-map.ts +45 -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/utils/use-sync-external-store-shim-plugin.ts +45 -0
|
@@ -1,205 +1,123 @@
|
|
|
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
|
+
get isDevelopment() {
|
|
15
|
+
return process.env.NODE_ENV === "development";
|
|
16
|
+
}
|
|
17
|
+
getCurrentRuntimeMode() {
|
|
18
|
+
return this.isDevelopment ? "development" : "production";
|
|
19
|
+
}
|
|
20
|
+
createRuntimeDefines(mode) {
|
|
21
|
+
const nodeEnv = JSON.stringify(mode);
|
|
22
|
+
return {
|
|
23
|
+
"process.env.NODE_ENV": nodeEnv,
|
|
24
|
+
"import.meta.env.NODE_ENV": nodeEnv
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
getReactVendorFileName(mode) {
|
|
28
|
+
return mode === "development" ? "react.development.js" : "react.js";
|
|
29
|
+
}
|
|
30
|
+
getReactDomVendorFileName(mode) {
|
|
31
|
+
return mode === "development" ? "react-dom.development.js" : "react-dom.js";
|
|
32
|
+
}
|
|
33
|
+
getRouterVendorFileName(mode) {
|
|
34
|
+
if (!this.config.routerAdapter) {
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
return mode === "development" ? `${this.config.routerAdapter.bundle.outputName}.development.js` : `${this.config.routerAdapter.bundle.outputName}.js`;
|
|
38
|
+
}
|
|
39
|
+
getRuntimeImports(mode = this.getCurrentRuntimeMode()) {
|
|
40
|
+
const reactVendorFileName = this.getReactVendorFileName(mode);
|
|
41
|
+
const reactDomVendorFileName = this.getReactDomVendorFileName(mode);
|
|
10
42
|
const runtimeImports = {
|
|
11
|
-
react:
|
|
12
|
-
reactDomClient:
|
|
13
|
-
reactJsxRuntime:
|
|
14
|
-
reactJsxDevRuntime:
|
|
15
|
-
reactDom:
|
|
43
|
+
react: buildBrowserRuntimeAssetUrl(reactVendorFileName),
|
|
44
|
+
reactDomClient: buildBrowserRuntimeAssetUrl(reactDomVendorFileName),
|
|
45
|
+
reactJsxRuntime: buildBrowserRuntimeAssetUrl(reactVendorFileName),
|
|
46
|
+
reactJsxDevRuntime: buildBrowserRuntimeAssetUrl(reactVendorFileName),
|
|
47
|
+
reactDom: buildBrowserRuntimeAssetUrl(reactDomVendorFileName)
|
|
16
48
|
};
|
|
17
49
|
if (this.config.routerAdapter) {
|
|
18
|
-
runtimeImports.router = this.
|
|
50
|
+
runtimeImports.router = buildBrowserRuntimeAssetUrl(this.getRouterVendorFileName(mode));
|
|
19
51
|
}
|
|
20
52
|
return runtimeImports;
|
|
21
53
|
}
|
|
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;
|
|
54
|
+
getSpecifierMap(mode = this.getCurrentRuntimeMode()) {
|
|
55
|
+
return buildReactRuntimeSpecifierMap(this.getRuntimeImports(mode), this.config.routerAdapter);
|
|
35
56
|
}
|
|
36
57
|
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]
|
|
58
|
+
const reactDomRuntimeInteropPlugin = createReactDomRuntimeInteropPlugin();
|
|
59
|
+
const dependencies = [];
|
|
60
|
+
for (const mode of ["production", "development"]) {
|
|
61
|
+
const reactRuntimeAliasPlugin = createRuntimeSpecifierAliasPlugin(
|
|
62
|
+
{
|
|
63
|
+
react: buildBrowserRuntimeAssetUrl(this.getReactVendorFileName(mode))
|
|
72
64
|
},
|
|
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)
|
|
65
|
+
{ name: `react-plugin-runtime-specifier-alias-${mode}` }
|
|
81
66
|
);
|
|
67
|
+
const reactDomBundlePlugins = [reactRuntimeAliasPlugin, reactDomRuntimeInteropPlugin].filter(
|
|
68
|
+
(plugin) => plugin !== null
|
|
69
|
+
);
|
|
70
|
+
const runtimeAliasPlugin = this.createRuntimeAliasPlugin(mode);
|
|
71
|
+
const mappedSpecifiers = new Set(Object.keys(this.getSpecifierMap(mode)));
|
|
82
72
|
dependencies.push(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
73
|
+
createBrowserRuntimeModuleAsset({
|
|
74
|
+
modules: [
|
|
75
|
+
{ specifier: "react", defaultExport: true },
|
|
76
|
+
{ specifier: "react/jsx-runtime" },
|
|
77
|
+
{ specifier: "react/jsx-dev-runtime" }
|
|
78
|
+
],
|
|
79
|
+
name: "react",
|
|
80
|
+
fileName: this.getReactVendorFileName(mode),
|
|
81
|
+
cacheDirName: `ecopages-react-runtime-${mode}`,
|
|
88
82
|
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;
|
|
83
|
+
define: this.createRuntimeDefines(mode)
|
|
112
84
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return `/${AssetFactory.RESOLVED_ASSETS_VENDORS_DIR}/${fileName}`;
|
|
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;
|
|
85
|
+
}),
|
|
86
|
+
createBrowserRuntimeModuleAsset({
|
|
87
|
+
modules: [{ specifier: "react-dom", defaultExport: true }, { specifier: "react-dom/client" }],
|
|
88
|
+
name: "react-dom",
|
|
89
|
+
fileName: this.getReactDomVendorFileName(mode),
|
|
90
|
+
cacheDirName: `ecopages-react-runtime-${mode}`,
|
|
91
|
+
bundleOptions: {
|
|
92
|
+
define: this.createRuntimeDefines(mode),
|
|
93
|
+
plugins: reactDomBundlePlugins
|
|
155
94
|
}
|
|
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)
|
|
95
|
+
})
|
|
185
96
|
);
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
97
|
+
if (this.config.routerAdapter) {
|
|
98
|
+
const unresolvedExternals = this.config.routerAdapter.bundle.externals.filter(
|
|
99
|
+
(external) => !mappedSpecifiers.has(external)
|
|
100
|
+
);
|
|
101
|
+
dependencies.push(
|
|
102
|
+
createBrowserRuntimeScriptAsset({
|
|
103
|
+
importPath: this.config.routerAdapter.bundle.importPath,
|
|
104
|
+
name: this.config.routerAdapter.bundle.outputName,
|
|
105
|
+
fileName: this.getRouterVendorFileName(mode),
|
|
106
|
+
bundleOptions: {
|
|
107
|
+
define: this.createRuntimeDefines(mode),
|
|
108
|
+
external: unresolvedExternals,
|
|
109
|
+
plugins: [runtimeAliasPlugin]
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
);
|
|
191
113
|
}
|
|
192
114
|
}
|
|
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();
|
|
115
|
+
return dependencies;
|
|
200
116
|
}
|
|
201
|
-
|
|
202
|
-
return
|
|
117
|
+
createRuntimeAliasPlugin(mode = this.getCurrentRuntimeMode()) {
|
|
118
|
+
return createRuntimeSpecifierAliasPlugin(this.getSpecifierMap(mode), {
|
|
119
|
+
name: `react-plugin-runtime-alias-${mode}`
|
|
120
|
+
});
|
|
203
121
|
}
|
|
204
122
|
}
|
|
205
123
|
export {
|
|
@@ -2,23 +2,22 @@
|
|
|
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
|
*/
|
|
10
9
|
|
|
11
|
-
import fs from 'node:fs';
|
|
12
|
-
import path from 'node:path';
|
|
13
|
-
import { createRequire } from 'node:module';
|
|
14
10
|
import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
|
|
15
|
-
import {
|
|
11
|
+
import { createRuntimeSpecifierAliasPlugin } from '@ecopages/core/build/runtime-specifier-alias-plugin';
|
|
12
|
+
import {
|
|
13
|
+
buildBrowserRuntimeAssetUrl,
|
|
14
|
+
createBrowserRuntimeModuleAsset,
|
|
15
|
+
createBrowserRuntimeScriptAsset,
|
|
16
|
+
type AssetDefinition,
|
|
17
|
+
} from '@ecopages/core/services/asset-processing-service';
|
|
16
18
|
import type { ReactRouterAdapter } from '../router-adapter.ts';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
specifier: string;
|
|
20
|
-
defaultExport?: boolean;
|
|
21
|
-
};
|
|
19
|
+
import { createReactDomRuntimeInteropPlugin } from '../utils/react-dom-runtime-interop-plugin.ts';
|
|
20
|
+
import { buildReactRuntimeSpecifierMap } from '../utils/react-runtime-specifier-map.ts';
|
|
22
21
|
|
|
23
22
|
export type ReactRuntimeImports = {
|
|
24
23
|
react: string;
|
|
@@ -33,239 +32,141 @@ export interface ReactRuntimeBundleServiceConfig {
|
|
|
33
32
|
routerAdapter?: ReactRouterAdapter;
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
constructor(private readonly config: ReactRuntimeBundleServiceConfig) {}
|
|
38
|
-
|
|
39
|
-
getRuntimeImports(): ReactRuntimeImports {
|
|
40
|
-
const runtimeImports: ReactRuntimeImports = {
|
|
41
|
-
react: this.buildImportMapSourceUrl('react.js'),
|
|
42
|
-
reactDomClient: this.buildImportMapSourceUrl('react-dom.js'),
|
|
43
|
-
reactJsxRuntime: this.buildImportMapSourceUrl('react.js'),
|
|
44
|
-
reactJsxDevRuntime: this.buildImportMapSourceUrl('react.js'),
|
|
45
|
-
reactDom: this.buildImportMapSourceUrl('react-dom.js'),
|
|
46
|
-
};
|
|
35
|
+
type RuntimeMode = 'development' | 'production';
|
|
47
36
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
37
|
+
export class ReactRuntimeBundleService {
|
|
38
|
+
private readonly config: ReactRuntimeBundleServiceConfig;
|
|
51
39
|
|
|
52
|
-
|
|
40
|
+
constructor(config: ReactRuntimeBundleServiceConfig) {
|
|
41
|
+
this.config = config;
|
|
53
42
|
}
|
|
54
43
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const map: Record<string, string> = {
|
|
58
|
-
react: runtimeImports.react,
|
|
59
|
-
'react/jsx-runtime': runtimeImports.reactJsxRuntime,
|
|
60
|
-
'react/jsx-dev-runtime': runtimeImports.reactJsxDevRuntime,
|
|
61
|
-
'react-dom': runtimeImports.reactDom,
|
|
62
|
-
'react-dom/client': runtimeImports.reactDomClient,
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
if (this.config.routerAdapter && runtimeImports.router) {
|
|
66
|
-
map[this.config.routerAdapter.importMapKey] = runtimeImports.router;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return map;
|
|
44
|
+
private get isDevelopment(): boolean {
|
|
45
|
+
return process.env.NODE_ENV === 'development';
|
|
70
46
|
}
|
|
71
47
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const runtimeImports = this.getRuntimeImports();
|
|
75
|
-
const reactRuntimeAliasPlugin = this.createRuntimeSpecifierAliasPlugin({
|
|
76
|
-
react: runtimeImports.react,
|
|
77
|
-
});
|
|
78
|
-
const reactDomRuntimeInteropPlugin = this.createReactDomRuntimeInteropPlugin();
|
|
79
|
-
|
|
80
|
-
const reactEntry = this.createRuntimeEntry(
|
|
81
|
-
[
|
|
82
|
-
{ specifier: 'react', defaultExport: true },
|
|
83
|
-
{ specifier: 'react/jsx-runtime' },
|
|
84
|
-
{ specifier: 'react/jsx-dev-runtime' },
|
|
85
|
-
],
|
|
86
|
-
'react-entry.mjs',
|
|
87
|
-
);
|
|
88
|
-
const reactDomEntry = this.createRuntimeEntry(
|
|
89
|
-
[{ specifier: 'react-dom', defaultExport: true }, { specifier: 'react-dom/client' }],
|
|
90
|
-
'react-dom-entry.mjs',
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
const dependencies: AssetDefinition[] = [
|
|
94
|
-
AssetFactory.createNodeModuleScript({
|
|
95
|
-
position: 'head',
|
|
96
|
-
importPath: reactEntry,
|
|
97
|
-
name: 'react',
|
|
98
|
-
excludeFromHtml: true,
|
|
99
|
-
bundleOptions: { naming: 'react.js' },
|
|
100
|
-
attributes: runtimeAttrs,
|
|
101
|
-
}),
|
|
102
|
-
AssetFactory.createNodeModuleScript({
|
|
103
|
-
position: 'head',
|
|
104
|
-
importPath: reactDomEntry,
|
|
105
|
-
name: 'react-dom',
|
|
106
|
-
excludeFromHtml: true,
|
|
107
|
-
bundleOptions: {
|
|
108
|
-
naming: 'react-dom.js',
|
|
109
|
-
plugins: [reactRuntimeAliasPlugin, reactDomRuntimeInteropPlugin],
|
|
110
|
-
},
|
|
111
|
-
attributes: runtimeAttrs,
|
|
112
|
-
}),
|
|
113
|
-
];
|
|
114
|
-
|
|
115
|
-
if (this.config.routerAdapter) {
|
|
116
|
-
const runtimeAliasPlugin = this.createRuntimeAliasPlugin();
|
|
117
|
-
const mappedSpecifiers = new Set(Object.keys(this.getSpecifierMap()));
|
|
118
|
-
const unresolvedExternals = this.config.routerAdapter.bundle.externals.filter(
|
|
119
|
-
(external) => !mappedSpecifiers.has(external),
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
dependencies.push(
|
|
123
|
-
AssetFactory.createNodeModuleScript({
|
|
124
|
-
position: 'head',
|
|
125
|
-
importPath: this.config.routerAdapter.bundle.importPath,
|
|
126
|
-
name: this.config.routerAdapter.bundle.outputName,
|
|
127
|
-
excludeFromHtml: true,
|
|
128
|
-
bundleOptions: {
|
|
129
|
-
naming: `${this.config.routerAdapter.bundle.outputName}.js`,
|
|
130
|
-
external: unresolvedExternals,
|
|
131
|
-
plugins: [runtimeAliasPlugin],
|
|
132
|
-
},
|
|
133
|
-
attributes: runtimeAttrs,
|
|
134
|
-
}),
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return dependencies;
|
|
48
|
+
private getCurrentRuntimeMode(): RuntimeMode {
|
|
49
|
+
return this.isDevelopment ? 'development' : 'production';
|
|
139
50
|
}
|
|
140
51
|
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
const escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
144
|
-
const filter = new RegExp(
|
|
145
|
-
`^(${Object.keys(specifierMap)
|
|
146
|
-
.map((key) => escapeRegExp(key))
|
|
147
|
-
.join('|')})$`,
|
|
148
|
-
);
|
|
52
|
+
private createRuntimeDefines(mode: RuntimeMode): Record<string, string> {
|
|
53
|
+
const nodeEnv = JSON.stringify(mode);
|
|
149
54
|
|
|
150
55
|
return {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
build.onResolve({ filter }, (args) => {
|
|
154
|
-
const mappedPath = specifierMap[args.path];
|
|
155
|
-
if (!mappedPath) {
|
|
156
|
-
return undefined;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
path: mappedPath,
|
|
161
|
-
external: true,
|
|
162
|
-
};
|
|
163
|
-
});
|
|
164
|
-
},
|
|
56
|
+
'process.env.NODE_ENV': nodeEnv,
|
|
57
|
+
'import.meta.env.NODE_ENV': nodeEnv,
|
|
165
58
|
};
|
|
166
59
|
}
|
|
167
60
|
|
|
168
|
-
private
|
|
169
|
-
return
|
|
61
|
+
private getReactVendorFileName(mode: RuntimeMode): string {
|
|
62
|
+
return mode === 'development' ? 'react.development.js' : 'react.js';
|
|
170
63
|
}
|
|
171
64
|
|
|
172
|
-
private
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
`^(${Object.keys(specifierMap)
|
|
176
|
-
.map((key) => escapeRegExp(key))
|
|
177
|
-
.join('|')})$`,
|
|
178
|
-
);
|
|
65
|
+
private getReactDomVendorFileName(mode: RuntimeMode): string {
|
|
66
|
+
return mode === 'development' ? 'react-dom.development.js' : 'react-dom.js';
|
|
67
|
+
}
|
|
179
68
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const mappedPath = specifierMap[args.path];
|
|
185
|
-
if (!mappedPath) {
|
|
186
|
-
return undefined;
|
|
187
|
-
}
|
|
69
|
+
private getRouterVendorFileName(mode: RuntimeMode): string {
|
|
70
|
+
if (!this.config.routerAdapter) {
|
|
71
|
+
return '';
|
|
72
|
+
}
|
|
188
73
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
};
|
|
193
|
-
});
|
|
194
|
-
},
|
|
195
|
-
};
|
|
74
|
+
return mode === 'development'
|
|
75
|
+
? `${this.config.routerAdapter.bundle.outputName}.development.js`
|
|
76
|
+
: `${this.config.routerAdapter.bundle.outputName}.js`;
|
|
196
77
|
}
|
|
197
78
|
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
return undefined;
|
|
209
|
-
}
|
|
79
|
+
getRuntimeImports(mode = this.getCurrentRuntimeMode()): ReactRuntimeImports {
|
|
80
|
+
const reactVendorFileName = this.getReactVendorFileName(mode);
|
|
81
|
+
const reactDomVendorFileName = this.getReactDomVendorFileName(mode);
|
|
82
|
+
const runtimeImports: ReactRuntimeImports = {
|
|
83
|
+
react: buildBrowserRuntimeAssetUrl(reactVendorFileName),
|
|
84
|
+
reactDomClient: buildBrowserRuntimeAssetUrl(reactDomVendorFileName),
|
|
85
|
+
reactJsxRuntime: buildBrowserRuntimeAssetUrl(reactVendorFileName),
|
|
86
|
+
reactJsxDevRuntime: buildBrowserRuntimeAssetUrl(reactVendorFileName),
|
|
87
|
+
reactDom: buildBrowserRuntimeAssetUrl(reactDomVendorFileName),
|
|
88
|
+
};
|
|
210
89
|
|
|
211
|
-
|
|
212
|
-
|
|
90
|
+
if (this.config.routerAdapter) {
|
|
91
|
+
runtimeImports.router = buildBrowserRuntimeAssetUrl(this.getRouterVendorFileName(mode));
|
|
92
|
+
}
|
|
213
93
|
|
|
214
|
-
|
|
215
|
-
contents: `import * as __ecopages_react_runtime from 'react';\n${rewritten}`,
|
|
216
|
-
loader: 'js',
|
|
217
|
-
resolveDir: path.dirname(args.path),
|
|
218
|
-
};
|
|
219
|
-
});
|
|
220
|
-
},
|
|
221
|
-
};
|
|
94
|
+
return runtimeImports;
|
|
222
95
|
}
|
|
223
96
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
227
|
-
return tmpDir;
|
|
97
|
+
getSpecifierMap(mode = this.getCurrentRuntimeMode()): Record<string, string> {
|
|
98
|
+
return buildReactRuntimeSpecifierMap(this.getRuntimeImports(mode), this.config.routerAdapter);
|
|
228
99
|
}
|
|
229
100
|
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
const seenExports = new Set<string>();
|
|
234
|
-
const statements: string[] = [];
|
|
101
|
+
getDependencies(): AssetDefinition[] {
|
|
102
|
+
const reactDomRuntimeInteropPlugin = createReactDomRuntimeInteropPlugin();
|
|
103
|
+
const dependencies: AssetDefinition[] = [];
|
|
235
104
|
|
|
236
|
-
for (const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
105
|
+
for (const mode of ['production', 'development'] as const) {
|
|
106
|
+
const reactRuntimeAliasPlugin = createRuntimeSpecifierAliasPlugin(
|
|
107
|
+
{
|
|
108
|
+
react: buildBrowserRuntimeAssetUrl(this.getReactVendorFileName(mode)),
|
|
109
|
+
},
|
|
110
|
+
{ name: `react-plugin-runtime-specifier-alias-${mode}` },
|
|
111
|
+
);
|
|
112
|
+
const reactDomBundlePlugins = [reactRuntimeAliasPlugin, reactDomRuntimeInteropPlugin].filter(
|
|
113
|
+
(plugin): plugin is EcoBuildPlugin => plugin !== null,
|
|
114
|
+
);
|
|
115
|
+
const runtimeAliasPlugin = this.createRuntimeAliasPlugin(mode);
|
|
116
|
+
const mappedSpecifiers = new Set(Object.keys(this.getSpecifierMap(mode)));
|
|
241
117
|
|
|
242
|
-
|
|
243
|
-
(
|
|
118
|
+
dependencies.push(
|
|
119
|
+
createBrowserRuntimeModuleAsset({
|
|
120
|
+
modules: [
|
|
121
|
+
{ specifier: 'react', defaultExport: true },
|
|
122
|
+
{ specifier: 'react/jsx-runtime' },
|
|
123
|
+
{ specifier: 'react/jsx-dev-runtime' },
|
|
124
|
+
],
|
|
125
|
+
name: 'react',
|
|
126
|
+
fileName: this.getReactVendorFileName(mode),
|
|
127
|
+
cacheDirName: `ecopages-react-runtime-${mode}`,
|
|
128
|
+
bundleOptions: {
|
|
129
|
+
define: this.createRuntimeDefines(mode),
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
132
|
+
createBrowserRuntimeModuleAsset({
|
|
133
|
+
modules: [{ specifier: 'react-dom', defaultExport: true }, { specifier: 'react-dom/client' }],
|
|
134
|
+
name: 'react-dom',
|
|
135
|
+
fileName: this.getReactDomVendorFileName(mode),
|
|
136
|
+
cacheDirName: `ecopages-react-runtime-${mode}`,
|
|
137
|
+
bundleOptions: {
|
|
138
|
+
define: this.createRuntimeDefines(mode),
|
|
139
|
+
plugins: reactDomBundlePlugins,
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
244
142
|
);
|
|
245
143
|
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
144
|
+
if (this.config.routerAdapter) {
|
|
145
|
+
const unresolvedExternals = this.config.routerAdapter.bundle.externals.filter(
|
|
146
|
+
(external) => !mappedSpecifiers.has(external),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
dependencies.push(
|
|
150
|
+
createBrowserRuntimeScriptAsset({
|
|
151
|
+
importPath: this.config.routerAdapter.bundle.importPath,
|
|
152
|
+
name: this.config.routerAdapter.bundle.outputName,
|
|
153
|
+
fileName: this.getRouterVendorFileName(mode),
|
|
154
|
+
bundleOptions: {
|
|
155
|
+
define: this.createRuntimeDefines(mode),
|
|
156
|
+
external: unresolvedExternals,
|
|
157
|
+
plugins: [runtimeAliasPlugin],
|
|
158
|
+
},
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
251
161
|
}
|
|
252
162
|
}
|
|
253
163
|
|
|
254
|
-
|
|
255
|
-
fs.writeFileSync(filePath, statements.join('\n'), 'utf-8');
|
|
256
|
-
return filePath;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
private getModuleExportNames(specifier: string, requireFromRoot: ReturnType<typeof createRequire>): string[] {
|
|
260
|
-
const moduleExports = requireFromRoot(specifier);
|
|
261
|
-
|
|
262
|
-
return Object.keys(moduleExports)
|
|
263
|
-
.filter((name) => name !== '__esModule' && name !== 'default')
|
|
264
|
-
.filter((name) => this.isValidExportName(name))
|
|
265
|
-
.sort();
|
|
164
|
+
return dependencies;
|
|
266
165
|
}
|
|
267
166
|
|
|
268
|
-
|
|
269
|
-
return
|
|
167
|
+
createRuntimeAliasPlugin(mode = this.getCurrentRuntimeMode()): EcoBuildPlugin {
|
|
168
|
+
return createRuntimeSpecifierAliasPlugin(this.getSpecifierMap(mode), {
|
|
169
|
+
name: `react-plugin-runtime-alias-${mode}`,
|
|
170
|
+
})!;
|
|
270
171
|
}
|
|
271
172
|
}
|