@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +23 -37
  2. package/README.md +143 -17
  3. package/package.json +3 -3
  4. package/src/react-hmr-strategy.d.ts +22 -19
  5. package/src/react-hmr-strategy.js +57 -109
  6. package/src/react-hmr-strategy.ts +76 -134
  7. package/src/react-renderer.d.ts +130 -11
  8. package/src/react-renderer.js +368 -64
  9. package/src/react-renderer.ts +490 -90
  10. package/src/react.plugin.d.ts +17 -5
  11. package/src/react.plugin.js +44 -13
  12. package/src/react.plugin.ts +49 -14
  13. package/src/router-adapter.d.ts +2 -2
  14. package/src/router-adapter.ts +2 -2
  15. package/src/services/react-bundle.service.d.ts +2 -25
  16. package/src/services/react-bundle.service.js +21 -91
  17. package/src/services/react-bundle.service.ts +22 -126
  18. package/src/services/react-hydration-asset.service.js +3 -3
  19. package/src/services/react-hydration-asset.service.ts +7 -4
  20. package/src/services/react-page-module.service.d.ts +3 -0
  21. package/src/services/react-page-module.service.js +20 -16
  22. package/src/services/react-page-module.service.ts +27 -17
  23. package/src/services/react-runtime-bundle.service.d.ts +12 -12
  24. package/src/services/react-runtime-bundle.service.js +98 -180
  25. package/src/services/react-runtime-bundle.service.ts +112 -211
  26. package/src/utils/client-graph-boundary-plugin.js +147 -9
  27. package/src/utils/client-graph-boundary-plugin.ts +252 -11
  28. package/src/utils/hydration-scripts.d.ts +18 -1
  29. package/src/utils/hydration-scripts.js +83 -32
  30. package/src/utils/hydration-scripts.ts +159 -38
  31. package/src/utils/reachability-analyzer.d.ts +12 -1
  32. package/src/utils/reachability-analyzer.js +101 -5
  33. package/src/utils/reachability-analyzer.ts +161 -8
  34. package/src/utils/react-dom-runtime-interop-plugin.d.ts +5 -0
  35. package/src/utils/react-dom-runtime-interop-plugin.js +29 -0
  36. package/src/utils/react-dom-runtime-interop-plugin.ts +33 -0
  37. package/src/utils/react-mdx-loader-plugin.js +13 -5
  38. package/src/utils/react-mdx-loader-plugin.ts +28 -5
  39. package/src/utils/react-runtime-specifier-map.d.ts +6 -0
  40. package/src/utils/react-runtime-specifier-map.js +37 -0
  41. package/src/utils/react-runtime-specifier-map.ts +45 -0
  42. package/src/utils/use-sync-external-store-shim-plugin.d.ts +5 -0
  43. package/src/utils/use-sync-external-store-shim-plugin.js +41 -0
  44. package/src/utils/use-sync-external-store-shim-plugin.ts +45 -0
@@ -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
- isFallbackRoots = true;
120
- for (const node of resolvedProgram.body) {
121
- if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration" || node.type === "ExportAllDeclaration") {
122
- potentialClientRoots.push(node);
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);
@@ -58,6 +58,16 @@ export type ReachabilityResult = {
58
58
  analyzed: boolean;
59
59
  };
60
60
 
61
+ /**
62
+ * Optional export filter supplied by the client graph boundary when a local
63
+ * module is imported through a narrower named-export surface.
64
+ *
65
+ * `'*'` means the whole module namespace is considered reachable, while a
66
+ * `Set` restricts analysis to the named exports that are actually requested by
67
+ * downstream client-reachable modules.
68
+ */
69
+ type ExplicitlyRequestedExports = Set<string> | '*';
70
+
61
71
  /**
62
72
  * Analyzes a module using Oxc AST and extracts a strict reachability graph
63
73
  * starting from client roots (`render`, `errorBoundary`, `loadingFallback` of `eco.page` or `eco.component`).
@@ -66,11 +76,14 @@ export type ReachabilityResult = {
66
76
  * @param filename - Absolute or relative path to the module file.
67
77
  * @param program - Optional pre-parsed Oxc program AST. When supplied, the
68
78
  * internal `parseSync` call is skipped entirely (avoids double-parsing).
79
+ * @param explicitlyRequestedExports - Optional named export filter propagated
80
+ * from a downstream importer when this module is only partially reachable.
69
81
  */
70
82
  export function analyzeReachability(
71
83
  source: string,
72
84
  filename: string,
73
85
  program?: ReturnType<typeof parseSync>['program'],
86
+ explicitlyRequestedExports?: ExplicitlyRequestedExports,
74
87
  ): ReachabilityResult {
75
88
  /**
76
89
  * AST Resolution
@@ -242,6 +255,57 @@ export function analyzeReachability(
242
255
  }
243
256
  }
244
257
 
258
+ /**
259
+ * Resolves the externally visible export name from an export specifier.
260
+ *
261
+ * @param specifier - Oxc export specifier node.
262
+ * @returns The exported binding name when available.
263
+ */
264
+ function getExportedName(specifier: any): string | undefined {
265
+ if (specifier?.exported?.type === 'Identifier') return specifier.exported.name;
266
+ if (typeof specifier?.exported?.value === 'string') return specifier.exported.value;
267
+ if (specifier?.local?.type === 'Identifier') return specifier.local.name;
268
+ if (typeof specifier?.local?.value === 'string') return specifier.local.value;
269
+ return undefined;
270
+ }
271
+
272
+ /**
273
+ * Resolves the imported binding name represented by a re-export specifier.
274
+ *
275
+ * @param specifier - Oxc export specifier node.
276
+ * @returns The source-module binding name that should be marked reachable.
277
+ */
278
+ function getReexportedImportName(specifier: any): string | undefined {
279
+ if (specifier?.local?.type === 'Identifier') return specifier.local.name;
280
+ if (typeof specifier?.local?.value === 'string') return specifier.local.value;
281
+ if (specifier?.imported?.type === 'Identifier') return specifier.imported.name;
282
+ if (typeof specifier?.imported?.value === 'string') return specifier.imported.value;
283
+ return getExportedName(specifier);
284
+ }
285
+
286
+ /**
287
+ * Resolves the local identifier used by a local export list entry.
288
+ *
289
+ * @param specifier - Oxc export specifier node.
290
+ * @returns The local symbol name referenced by the export list.
291
+ */
292
+ function getLocalExportName(specifier: any): string | undefined {
293
+ if (specifier?.local?.type === 'Identifier') return specifier.local.name;
294
+ if (typeof specifier?.local?.value === 'string') return specifier.local.value;
295
+ return undefined;
296
+ }
297
+
298
+ /**
299
+ * Checks whether a named export is part of the explicitly requested subset.
300
+ *
301
+ * @param name - Export name to test.
302
+ * @returns True when the export should seed or continue traversal.
303
+ */
304
+ function isExplicitlyRequestedExport(name: string): boolean {
305
+ if (explicitlyRequestedExports === '*') return true;
306
+ return explicitlyRequestedExports?.has(name) ?? false;
307
+ }
308
+
245
309
  /**
246
310
  * Client root resolution (fallback mode)
247
311
  *
@@ -255,14 +319,73 @@ export function analyzeReachability(
255
319
  */
256
320
  let isFallbackRoots = false;
257
321
  if (potentialClientRoots.length === 0) {
258
- isFallbackRoots = true;
259
- for (const node of resolvedProgram.body) {
260
- if (
261
- (node as { type: string }).type === 'ExportNamedDeclaration' ||
262
- (node as { type: string }).type === 'ExportDefaultDeclaration' ||
263
- (node as { type: string }).type === 'ExportAllDeclaration'
264
- ) {
265
- potentialClientRoots.push(node);
322
+ if (explicitlyRequestedExports) {
323
+ for (const node of resolvedProgram.body) {
324
+ if ((node as { type: string }).type === 'ExportNamedDeclaration') {
325
+ const exportNode = node as any;
326
+ if (exportNode.source && exportNode.specifiers?.length) {
327
+ const hasRequestedReexport = exportNode.specifiers.some((specifier: any) => {
328
+ const exportedName = getExportedName(specifier);
329
+ return exportedName ? isExplicitlyRequestedExport(exportedName) : false;
330
+ });
331
+ if (hasRequestedReexport) {
332
+ potentialClientRoots.push(node);
333
+ }
334
+ continue;
335
+ }
336
+
337
+ if (
338
+ exportNode.declaration?.type === 'FunctionDeclaration' ||
339
+ exportNode.declaration?.type === 'ClassDeclaration'
340
+ ) {
341
+ const declarationName = exportNode.declaration.id?.name;
342
+ if (declarationName && isExplicitlyRequestedExport(declarationName)) {
343
+ potentialClientRoots.push(node);
344
+ }
345
+ continue;
346
+ }
347
+
348
+ if (exportNode.declaration?.type === 'VariableDeclaration') {
349
+ const hasRequestedDeclaration = exportNode.declaration.declarations.some(
350
+ (declaration: any) =>
351
+ declaration.id?.type === 'Identifier' &&
352
+ isExplicitlyRequestedExport(declaration.id.name),
353
+ );
354
+ if (hasRequestedDeclaration) {
355
+ potentialClientRoots.push(node);
356
+ }
357
+ continue;
358
+ }
359
+
360
+ if (exportNode.specifiers?.length) {
361
+ const hasRequestedSpecifier = exportNode.specifiers.some((specifier: any) => {
362
+ const exportedName = getExportedName(specifier);
363
+ return exportedName ? isExplicitlyRequestedExport(exportedName) : false;
364
+ });
365
+ if (hasRequestedSpecifier) {
366
+ potentialClientRoots.push(node);
367
+ }
368
+ }
369
+ } else if ((node as { type: string }).type === 'ExportDefaultDeclaration') {
370
+ if (isExplicitlyRequestedExport('default')) {
371
+ potentialClientRoots.push(node);
372
+ }
373
+ } else if ((node as { type: string }).type === 'ExportAllDeclaration') {
374
+ if (explicitlyRequestedExports === '*') {
375
+ potentialClientRoots.push(node);
376
+ }
377
+ }
378
+ }
379
+ } else {
380
+ isFallbackRoots = true;
381
+ for (const node of resolvedProgram.body) {
382
+ if (
383
+ (node as { type: string }).type === 'ExportNamedDeclaration' ||
384
+ (node as { type: string }).type === 'ExportDefaultDeclaration' ||
385
+ (node as { type: string }).type === 'ExportAllDeclaration'
386
+ ) {
387
+ potentialClientRoots.push(node);
388
+ }
266
389
  }
267
390
  }
268
391
  }
@@ -353,6 +476,36 @@ export function analyzeReachability(
353
476
  return;
354
477
  }
355
478
 
479
+ if (node.type === 'ExportNamedDeclaration' && typeof node.source?.value === 'string') {
480
+ for (const specifier of node.specifiers ?? []) {
481
+ const importedName = getReexportedImportName(specifier);
482
+ if (importedName) {
483
+ markImportReachable(node.source.value as string, importedName);
484
+ }
485
+ }
486
+ return;
487
+ }
488
+
489
+ if (
490
+ node.type === 'ExportNamedDeclaration' &&
491
+ !node.source &&
492
+ explicitlyRequestedExports &&
493
+ node.specifiers?.length
494
+ ) {
495
+ for (const specifier of node.specifiers) {
496
+ const exportedName = getExportedName(specifier);
497
+ if (!exportedName || !isExplicitlyRequestedExport(exportedName)) {
498
+ continue;
499
+ }
500
+
501
+ const localName = getLocalExportName(specifier);
502
+ if (localName && !currentScope.has(localName)) {
503
+ checkIdentifier(localName);
504
+ }
505
+ }
506
+ return;
507
+ }
508
+
356
509
  if (node.type === 'Identifier' || (node.type === 'JSXIdentifier' && /^[A-Z]/.test(node.name))) {
357
510
  if (!currentScope.has(node.name)) {
358
511
  checkIdentifier(node.name);
@@ -0,0 +1,5 @@
1
+ import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
2
+ export declare function createReactDomRuntimeInteropPlugin(options?: {
3
+ name?: string;
4
+ reactSpecifier?: string;
5
+ }): EcoBuildPlugin;
@@ -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
+ };
@@ -0,0 +1,33 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
4
+
5
+ export function createReactDomRuntimeInteropPlugin(options?: {
6
+ name?: string;
7
+ reactSpecifier?: string;
8
+ }): EcoBuildPlugin {
9
+ const reactDomFileFilter = /[\\/]react-dom[\\/].*\.js$/;
10
+ const reactRequirePattern = /\brequire\((['"])react\1\)/g;
11
+ const reactSpecifier = options?.reactSpecifier ?? 'react';
12
+
13
+ return {
14
+ name: options?.name ?? 'react-dom-runtime-interop',
15
+ setup(build) {
16
+ build.onLoad({ filter: reactDomFileFilter }, (args) => {
17
+ const content = fs.readFileSync(args.path, 'utf-8');
18
+ if (!reactRequirePattern.test(content)) {
19
+ return undefined;
20
+ }
21
+
22
+ reactRequirePattern.lastIndex = 0;
23
+ const rewritten = content.replace(reactRequirePattern, '__ecopages_react_runtime');
24
+
25
+ return {
26
+ contents: `import * as __ecopages_react_runtime from '${reactSpecifier}';\n${rewritten}`,
27
+ loader: 'js',
28
+ resolveDir: path.dirname(args.path),
29
+ };
30
+ });
31
+ },
32
+ };
33
+ }
@@ -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 { SourceMapGenerator } from "source-map";
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 ?? [".md"];
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
- SourceMapGenerator
28
+ format: resolveCompileFormat(filePath, compilerOptions),
29
+ SourceMapGenerator: sourceMap.SourceMapGenerator
22
30
  });
23
- const sourceMap = compiled.map ? `
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)}${sourceMap}`,
35
+ contents: `${String(compiled.value)}${inlineSourceMap}`,
28
36
  loader: compilerOptions?.jsx ? "jsx" : "js",
29
37
  resolveDir: path.dirname(args.path)
30
38
  };
@@ -2,12 +2,34 @@ import { readFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
4
4
  import { type CompileOptions, compile } from '@mdx-js/mdx';
5
- import { SourceMapGenerator } from 'source-map';
5
+ import sourceMap from 'source-map';
6
6
  import { VFile } from 'vfile';
7
7
 
8
+ /**
9
+ * Resolves the MDX parser mode for a React-backed MDX source file.
10
+ *
11
+ * When `.md` files are explicitly opted into the React MDX loader they still
12
+ * need to compile as MDX, not plain markdown. Otherwise top-level ESM exports
13
+ * like `config` are treated as literal text and the route cannot execute as a
14
+ * page module.
15
+ *
16
+ * @param filePath Absolute or relative source file path.
17
+ * @param compilerOptions User-provided MDX compiler options.
18
+ * @returns The compile format that should be passed to `@mdx-js/mdx`.
19
+ */
20
+ function resolveCompileFormat(filePath: string, compilerOptions?: CompileOptions): CompileOptions['format'] {
21
+ const configuredFormat = compilerOptions?.format;
22
+
23
+ if (configuredFormat && configuredFormat !== 'detect') {
24
+ return configuredFormat;
25
+ }
26
+
27
+ return path.extname(filePath).toLowerCase() === '.md' ? 'mdx' : configuredFormat;
28
+ }
29
+
8
30
  export function createReactMdxLoaderPlugin(compilerOptions?: CompileOptions): EcoBuildPlugin {
9
31
  const mdxExtensions = compilerOptions?.mdxExtensions ?? ['.mdx'];
10
- const mdExtensions = compilerOptions?.mdExtensions ?? ['.md'];
32
+ const mdExtensions = compilerOptions?.mdExtensions ?? [];
11
33
  const allExtensions = [...mdxExtensions, ...mdExtensions];
12
34
  const escapedExts = allExtensions.map((ext) => ext.replace('.', '\\.'));
13
35
  const filter = new RegExp(`(${escapedExts.join('|')})(\\?.*)?$`);
@@ -22,15 +44,16 @@ export function createReactMdxLoaderPlugin(compilerOptions?: CompileOptions): Ec
22
44
 
23
45
  const compiled = await compile(file, {
24
46
  ...compilerOptions,
25
- SourceMapGenerator,
47
+ format: resolveCompileFormat(filePath, compilerOptions),
48
+ SourceMapGenerator: sourceMap.SourceMapGenerator,
26
49
  });
27
50
 
28
- const sourceMap = compiled.map
51
+ const inlineSourceMap = compiled.map
29
52
  ? `\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(JSON.stringify(compiled.map)).toString('base64')}\n`
30
53
  : '';
31
54
 
32
55
  return {
33
- contents: `${String(compiled.value)}${sourceMap}`,
56
+ contents: `${String(compiled.value)}${inlineSourceMap}`,
34
57
  loader: compilerOptions?.jsx ? 'jsx' : 'js',
35
58
  resolveDir: path.dirname(args.path),
36
59
  };
@@ -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 buildReactRuntimeSpecifierMap(runtimeImports: ReactRuntimeImports, routerAdapter?: ReactRouterAdapter): Record<string, string>;
5
+ export declare function getReactRuntimeExternalSpecifiers(): string[];
6
+ export declare function getReactClientGraphAllowSpecifiers(runtimeSpecifiers: Iterable<string>, routerAdapter?: ReactRouterAdapter): string[];
@@ -0,0 +1,37 @@
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 buildReactRuntimeSpecifierMap(runtimeImports, routerAdapter) {
9
+ const map = {
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
+ if (routerAdapter && runtimeImports.router) {
17
+ map[routerAdapter.importMapKey] = runtimeImports.router;
18
+ }
19
+ return map;
20
+ }
21
+ function getReactRuntimeExternalSpecifiers() {
22
+ return [...REACT_RUNTIME_SPECIFIERS];
23
+ }
24
+ function getReactClientGraphAllowSpecifiers(runtimeSpecifiers, routerAdapter) {
25
+ return [
26
+ "@ecopages/core",
27
+ ...REACT_RUNTIME_SPECIFIERS,
28
+ ...routerAdapter ? [routerAdapter.importMapKey] : [],
29
+ ...Array.from(runtimeSpecifiers)
30
+ ];
31
+ }
32
+ export {
33
+ REACT_RUNTIME_SPECIFIERS,
34
+ buildReactRuntimeSpecifierMap,
35
+ getReactClientGraphAllowSpecifiers,
36
+ getReactRuntimeExternalSpecifiers
37
+ };
@@ -0,0 +1,45 @@
1
+ import type { ReactRouterAdapter } from '../router-adapter.ts';
2
+ import type { ReactRuntimeImports } from '../services/react-runtime-bundle.service.ts';
3
+
4
+ export const REACT_RUNTIME_SPECIFIERS = [
5
+ 'react',
6
+ 'react-dom',
7
+ 'react/jsx-runtime',
8
+ 'react/jsx-dev-runtime',
9
+ 'react-dom/client',
10
+ ] as const;
11
+
12
+ export function buildReactRuntimeSpecifierMap(
13
+ runtimeImports: ReactRuntimeImports,
14
+ routerAdapter?: ReactRouterAdapter,
15
+ ): Record<string, string> {
16
+ const map: Record<string, string> = {
17
+ react: runtimeImports.react,
18
+ 'react/jsx-runtime': runtimeImports.reactJsxRuntime,
19
+ 'react/jsx-dev-runtime': runtimeImports.reactJsxDevRuntime,
20
+ 'react-dom': runtimeImports.reactDom,
21
+ 'react-dom/client': runtimeImports.reactDomClient,
22
+ };
23
+
24
+ if (routerAdapter && runtimeImports.router) {
25
+ map[routerAdapter.importMapKey] = runtimeImports.router;
26
+ }
27
+
28
+ return map;
29
+ }
30
+
31
+ export function getReactRuntimeExternalSpecifiers(): string[] {
32
+ return [...REACT_RUNTIME_SPECIFIERS];
33
+ }
34
+
35
+ export function getReactClientGraphAllowSpecifiers(
36
+ runtimeSpecifiers: Iterable<string>,
37
+ routerAdapter?: ReactRouterAdapter,
38
+ ): string[] {
39
+ return [
40
+ '@ecopages/core',
41
+ ...REACT_RUNTIME_SPECIFIERS,
42
+ ...(routerAdapter ? [routerAdapter.importMapKey] : []),
43
+ ...Array.from(runtimeSpecifiers),
44
+ ];
45
+ }
@@ -0,0 +1,5 @@
1
+ import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
2
+ export declare function createUseSyncExternalStoreShimPlugin(options?: {
3
+ name?: string;
4
+ namespace?: string;
5
+ }): EcoBuildPlugin;
@@ -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
+ };