@ecopages/react 0.2.0-alpha.4 → 0.2.0-alpha.40

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 (70) hide show
  1. package/README.md +161 -18
  2. package/package.json +16 -12
  3. package/src/eco-embed.d.ts +11 -0
  4. package/src/eco-embed.js +11 -0
  5. package/src/react-hmr-strategy.d.ts +42 -32
  6. package/src/react-hmr-strategy.js +103 -124
  7. package/src/react-renderer.d.ts +169 -42
  8. package/src/react-renderer.js +484 -164
  9. package/src/react.constants.d.ts +1 -0
  10. package/src/react.constants.js +4 -0
  11. package/src/react.plugin.d.ts +38 -111
  12. package/src/react.plugin.js +132 -61
  13. package/src/react.types.d.ts +88 -0
  14. package/src/react.types.js +0 -0
  15. package/src/router-adapter.d.ts +7 -14
  16. package/src/services/react-bundle.service.d.ts +15 -26
  17. package/src/services/react-bundle.service.js +45 -93
  18. package/src/services/react-hmr-page-metadata-cache.d.ts +9 -0
  19. package/src/services/react-hmr-page-metadata-cache.js +18 -2
  20. package/src/services/react-hydration-asset.service.d.ts +26 -19
  21. package/src/services/react-hydration-asset.service.js +72 -66
  22. package/src/services/react-mdx-config-dependency.service.d.ts +36 -0
  23. package/src/services/react-mdx-config-dependency.service.js +122 -0
  24. package/src/services/react-page-module.service.d.ts +10 -2
  25. package/src/services/react-page-module.service.js +47 -39
  26. package/src/services/react-page-payload.service.d.ts +46 -0
  27. package/src/services/react-page-payload.service.js +67 -0
  28. package/src/services/react-runtime-bundle.service.d.ts +15 -13
  29. package/src/services/react-runtime-bundle.service.js +103 -180
  30. package/src/utils/client-graph-boundary-plugin.d.ts +1 -1
  31. package/src/utils/client-graph-boundary-plugin.js +149 -11
  32. package/src/utils/component-config-traversal.d.ts +36 -0
  33. package/src/utils/component-config-traversal.js +54 -0
  34. package/src/utils/declared-modules.d.ts +1 -1
  35. package/src/utils/declared-modules.js +7 -16
  36. package/src/utils/dynamic.test.browser.d.ts +1 -0
  37. package/src/utils/dynamic.test.browser.js +33 -0
  38. package/src/utils/hydration-scripts.d.ts +25 -6
  39. package/src/utils/hydration-scripts.js +150 -44
  40. package/src/utils/hydration-scripts.test.browser.d.ts +1 -0
  41. package/src/utils/hydration-scripts.test.browser.js +198 -0
  42. package/src/utils/reachability-analyzer.d.ts +12 -1
  43. package/src/utils/reachability-analyzer.js +101 -5
  44. package/src/utils/react-dom-runtime-interop-plugin.d.ts +5 -0
  45. package/src/utils/react-dom-runtime-interop-plugin.js +29 -0
  46. package/src/utils/react-mdx-loader-plugin.d.ts +1 -1
  47. package/src/utils/react-mdx-loader-plugin.js +13 -5
  48. package/src/utils/react-runtime-alias-map.d.ts +6 -0
  49. package/src/utils/react-runtime-alias-map.js +33 -0
  50. package/src/utils/use-sync-external-store-shim-plugin.d.ts +5 -0
  51. package/src/utils/use-sync-external-store-shim-plugin.js +41 -0
  52. package/CHANGELOG.md +0 -62
  53. package/src/react-hmr-strategy.ts +0 -444
  54. package/src/react-renderer.ts +0 -403
  55. package/src/react.plugin.ts +0 -241
  56. package/src/router-adapter.ts +0 -95
  57. package/src/services/react-bundle.service.ts +0 -212
  58. package/src/services/react-hmr-page-metadata-cache.ts +0 -24
  59. package/src/services/react-hydration-asset.service.ts +0 -260
  60. package/src/services/react-page-module.service.ts +0 -214
  61. package/src/services/react-runtime-bundle.service.ts +0 -271
  62. package/src/utils/client-graph-boundary-plugin.ts +0 -590
  63. package/src/utils/client-only.ts +0 -27
  64. package/src/utils/declared-modules.ts +0 -99
  65. package/src/utils/dynamic.ts +0 -27
  66. package/src/utils/hmr-scripts.ts +0 -47
  67. package/src/utils/html-boundary.ts +0 -66
  68. package/src/utils/hydration-scripts.ts +0 -338
  69. package/src/utils/reachability-analyzer.ts +0 -440
  70. 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
- 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);
@@ -0,0 +1,5 @@
1
+ import type { EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
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
+ };
@@ -1,3 +1,3 @@
1
- import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
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 { 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
  };
@@ -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,5 @@
1
+ import type { EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
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
+ };
package/CHANGELOG.md DELETED
@@ -1,62 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to `@ecopages/react` are documented here.
4
-
5
- > **Note:** Changelog tracking begins at version `0.2.0`. Changes prior to this release are not recorded here but are available in the git history.
6
-
7
- ## [UNRELEASED] — TBD
8
-
9
- ### Features
10
-
11
- #### Render Reachability Analysis
12
-
13
- - **Client render graph (Phase 1)** — Introduces a static reachability analysis step that builds an explicit graph of which components are rendered client-side (`cdfbd69e`).
14
- - **OXC-powered reachability analyzer** — `reachability-analyzer.ts` uses OXC to parse and walk component ASTs, building a `ClientRenderGraph` that maps exported components to their client-side reach (`5412df6b`).
15
- - **Explicit client graph boundaries** — Components must now declare explicit boundaries; the analyser enforces these to prevent over-hydration (`2912d6bd`).
16
- - **Declared modules utility** — `declared-modules.ts` tracks which modules are declared as client boundaries.
17
-
18
- #### Service Architecture Refactor
19
-
20
- - **`ReactRuntimeBundleService`** — Manages runtime assets and specifier mapping for the React integration (`cfd3cb05`).
21
- - **`ReactHydrationAssetService`** — Creates and manages hydration assets for client-side rendering (`cfd3cb05`).
22
- - **`ReactBundleService`** — Handles esbuild bundle configuration for React components (`cfd3cb05`).
23
- - **`ReactPageModuleService`** — Loads and compiles MDX/TSX page modules, including config resolution (`cfd3cb05`).
24
- - The integration no longer builds a monolithic renderer — each concern is handled by a focused service.
25
-
26
- #### HMR Improvements
27
-
28
- - **HMR page metadata caching** — Page metadata is now cached between HMR refreshes, preventing unnecessary re-fetches during Fast Refresh (`a663788c`).
29
- - **Stale temp module race fix** — HMR no longer incorrectly reads a stale temporary module during rapid refresh cycles (`b2cf8466`).
30
- - **Client graph HMR stability** — HMR reloads now correctly respect client graph boundaries to avoid partial hydration mismatches (`2912d6bd`).
31
-
32
- #### HTML Boundary Utilities
33
-
34
- - **`html-boundary.ts`** — New utility that wraps rendered output in explicit boundary markers for the cross-integration boundary rendering policy (`ec1e4d66`).
35
- - **`hydration-scripts.ts`** — Expanded with new helpers for generating and injecting hydration entry scripts.
36
-
37
- ### Refactoring
38
-
39
- - Aligned React renderer to full orchestration mode — removed legacy rendering path (`fc07bdb0`).
40
- - Ambient module declarations cleaned up (`5f46ecc5`).
41
- - Client graph boundaries and runtime dependency wiring corrected (`4b6cd32e`).
42
- - Updated test suite for esbuild adapter and Node.js runtime compatibility (`31a44458`).
43
-
44
- ### Bug Fixes
45
-
46
- - Inlined the React MDX loader so React apps no longer need to install `@ecopages/mdx` when enabling React MDX support (`unreleased`).
47
- - Fixed stale temp module race during Fast Refresh cycles (`b2cf8466`).
48
- - Fixed client graph boundary wiring for runtime dependencies (`4b6cd32e`).
49
-
50
- ### Tests
51
-
52
- - Added `html-boundary.test.ts` covering boundary wrapping utilities.
53
- - Added `hydration-scripts.test.ts` with hydration script generation coverage.
54
- - Added `reachability-analyzer.test.ts` (187 lines) covering export declaration reachability.
55
- - Updated integration tests for esbuild adapter compatibility (`31a44458`).
56
-
57
- ---
58
-
59
- ## Migration Notes
60
-
61
- - The React integration now requires explicit client boundary declarations. Components that should be hydrated client-side must be marked at a boundary entry point.
62
- - The internal service layer (`ReactRuntimeBundleService`, `ReactBundleService`, etc.) is not part of the public API and may change between releases.