@ecopages/react 0.2.0-alpha.5 → 0.2.0-alpha.51
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/README.md +152 -29
- package/package.json +24 -12
- package/src/eco-embed.d.ts +11 -0
- package/src/eco-embed.js +11 -0
- package/src/react-hmr-strategy.d.ts +65 -43
- package/src/react-hmr-strategy.js +298 -145
- package/src/react-renderer.d.ts +169 -42
- package/src/react-renderer.js +484 -164
- package/src/react.constants.d.ts +1 -0
- package/src/react.constants.js +4 -0
- package/src/react.plugin.d.ts +40 -111
- package/src/react.plugin.js +136 -61
- package/src/react.types.d.ts +88 -0
- package/src/react.types.js +0 -0
- package/src/router-adapter.d.ts +7 -14
- package/src/runtime/use-sync-external-store-with-selector.d.ts +3 -0
- package/src/runtime/use-sync-external-store-with-selector.js +56 -0
- package/src/services/react-bundle.service.d.ts +22 -35
- package/src/services/react-bundle.service.js +41 -105
- 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 +28 -19
- package/src/services/react-hydration-asset.service.js +85 -66
- package/src/services/react-mdx-config-dependency.service.d.ts +36 -0
- package/src/services/react-mdx-config-dependency.service.js +122 -0
- package/src/services/react-page-module.service.d.ts +10 -2
- package/src/services/react-page-module.service.js +47 -39
- 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 +20 -13
- package/src/services/react-runtime-bundle.service.js +146 -179
- package/src/utils/client-graph-boundary-plugin.d.ts +1 -1
- package/src/utils/client-graph-boundary-plugin.js +80 -3
- 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 +27 -6
- package/src/utils/hydration-scripts.js +177 -44
- package/src/utils/hydration-scripts.test.browser.d.ts +1 -0
- package/src/utils/hydration-scripts.test.browser.js +198 -0
- package/src/utils/react-dom-runtime-interop-plugin.d.ts +5 -0
- package/src/utils/react-dom-runtime-interop-plugin.js +38 -0
- package/src/utils/react-mdx-loader-plugin.d.ts +1 -1
- package/src/utils/react-mdx-loader-plugin.js +13 -5
- package/src/utils/react-runtime-alias-map.d.ts +8 -0
- package/src/utils/react-runtime-alias-map.js +90 -0
- package/CHANGELOG.md +0 -67
- package/src/react-hmr-strategy.ts +0 -455
- 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 -217
- 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 -710
- 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 -593
- package/src/utils/react-mdx-loader-plugin.ts +0 -40
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const REACT_PLUGIN_NAME = "react";
|
package/src/react.plugin.d.ts
CHANGED
|
@@ -1,94 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* @module
|
|
4
|
-
*/
|
|
5
|
-
import { IntegrationPlugin } from '@ecopages/core/plugins/integration-plugin';
|
|
6
|
-
import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
|
|
1
|
+
import { IntegrationPlugin, type EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
|
|
2
|
+
import type { BrowserRuntimeManifest } from '@ecopages/core/build/browser-runtime-manifest';
|
|
7
3
|
import type { HmrStrategy } from '@ecopages/core/hmr/hmr-strategy';
|
|
8
|
-
import type { IHmrManager } from '@ecopages/core/internal-types';
|
|
9
|
-
import type { AssetDefinition } from '@ecopages/core/services/asset-processing-service';
|
|
10
|
-
import type { CompileOptions } from '@mdx-js/mdx';
|
|
11
4
|
import type React from 'react';
|
|
12
5
|
import { ReactRenderer } from './react-renderer.js';
|
|
13
|
-
import type {
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* MDX configuration options for the React plugin
|
|
17
|
-
*/
|
|
18
|
-
export type ReactMdxOptions = {
|
|
19
|
-
/**
|
|
20
|
-
* Whether to enable MDX support.
|
|
21
|
-
* @default false
|
|
22
|
-
*/
|
|
23
|
-
enabled: boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Compiler options for MDX.
|
|
26
|
-
* @default undefined
|
|
27
|
-
*/
|
|
28
|
-
compilerOptions?: Omit<CompileOptions, 'jsxImportSource' | 'jsxRuntime'>;
|
|
29
|
-
/**
|
|
30
|
-
* Remark plugins.
|
|
31
|
-
* @default undefined
|
|
32
|
-
*/
|
|
33
|
-
remarkPlugins?: CompileOptions['remarkPlugins'];
|
|
34
|
-
/**
|
|
35
|
-
* Rehype plugins.
|
|
36
|
-
* @default undefined
|
|
37
|
-
*/
|
|
38
|
-
rehypePlugins?: CompileOptions['rehypePlugins'];
|
|
39
|
-
/**
|
|
40
|
-
* Recma plugins.
|
|
41
|
-
* @default undefined
|
|
42
|
-
*/
|
|
43
|
-
recmaPlugins?: CompileOptions['recmaPlugins'];
|
|
44
|
-
/**
|
|
45
|
-
* Custom extensions to be treated as MDX files.
|
|
46
|
-
* @default ['.mdx']
|
|
47
|
-
*/
|
|
48
|
-
extensions?: string[];
|
|
49
|
-
};
|
|
50
|
-
/**
|
|
51
|
-
* Options for the React plugin
|
|
52
|
-
*/
|
|
53
|
-
export type ReactPluginOptions = {
|
|
54
|
-
extensions?: string[];
|
|
55
|
-
dependencies?: AssetDefinition[];
|
|
56
|
-
/**
|
|
57
|
-
* Enables explicit client graph mode for React page entries.
|
|
58
|
-
*
|
|
59
|
-
* When enabled, React page-entry bundling relies on explicit dependency declarations
|
|
60
|
-
* and skips AST-based `middleware`/`requires` stripping in the React path.
|
|
61
|
-
* @default false
|
|
62
|
-
*/
|
|
63
|
-
explicitGraph?: boolean;
|
|
64
|
-
/**
|
|
65
|
-
* Router adapter for SPA navigation.
|
|
66
|
-
* When provided, pages with layouts will be wrapped in the router for client-side navigation.
|
|
67
|
-
* @example
|
|
68
|
-
* ```ts
|
|
69
|
-
* import { ecoRouter } from '@ecopages/react-router';
|
|
70
|
-
* reactPlugin({ router: ecoRouter() })
|
|
71
|
-
* ```
|
|
72
|
-
*/
|
|
73
|
-
router?: ReactRouterAdapter;
|
|
74
|
-
/**
|
|
75
|
-
* MDX configuration for handling .mdx files within the React plugin.
|
|
76
|
-
* When enabled, MDX files are treated as React pages with full router support.
|
|
77
|
-
* @example
|
|
78
|
-
* ```ts
|
|
79
|
-
* reactPlugin({
|
|
80
|
-
* router: ecoRouter(),
|
|
81
|
-
* mdx: {
|
|
82
|
-
* enabled: true,
|
|
83
|
-
* extensions: ['.mdx', '.md'],
|
|
84
|
-
* remarkPlugins: [remarkGfm],
|
|
85
|
-
* rehypePlugins: [[rehypePrettyCode, { theme: '...' }]],
|
|
86
|
-
* }
|
|
87
|
-
* })
|
|
88
|
-
* ```
|
|
89
|
-
*/
|
|
90
|
-
mdx?: ReactMdxOptions;
|
|
91
|
-
};
|
|
6
|
+
import type { ReactPluginOptions } from './react.types.js';
|
|
7
|
+
export type { ReactMdxOptions, ReactPluginOptions, ReactRendererConfig } from './react.types.js';
|
|
92
8
|
/**
|
|
93
9
|
* The name of the React plugin
|
|
94
10
|
*/
|
|
@@ -97,21 +13,49 @@ export declare const PLUGIN_NAME = "react";
|
|
|
97
13
|
* The React plugin class
|
|
98
14
|
* This plugin provides support for React components in Ecopages
|
|
99
15
|
*/
|
|
100
|
-
export declare class ReactPlugin extends IntegrationPlugin<React.
|
|
16
|
+
export declare class ReactPlugin extends IntegrationPlugin<React.ReactNode> {
|
|
101
17
|
renderer: typeof ReactRenderer;
|
|
102
|
-
|
|
103
|
-
private mdxEnabled;
|
|
104
|
-
private mdxCompilerOptions?;
|
|
105
|
-
private mdxExtensions;
|
|
18
|
+
private readonly routerAdapter;
|
|
19
|
+
private readonly mdxEnabled;
|
|
20
|
+
private readonly mdxCompilerOptions?;
|
|
21
|
+
private readonly mdxExtensions;
|
|
106
22
|
private mdxLoaderPlugin;
|
|
107
|
-
private runtimeBundleService;
|
|
23
|
+
private readonly runtimeBundleService;
|
|
108
24
|
private readonly hmrPageMetadataCache;
|
|
25
|
+
private runtimeDependenciesInitialized;
|
|
109
26
|
/**
|
|
110
27
|
* Indicates whether React explicit graph mode is enabled for renderer/HMR behavior.
|
|
111
28
|
*/
|
|
112
|
-
private explicitGraphEnabled;
|
|
113
|
-
|
|
29
|
+
private readonly explicitGraphEnabled;
|
|
30
|
+
private readonly rendererConfig;
|
|
31
|
+
constructor(options?: ReactPluginOptions);
|
|
32
|
+
/**
|
|
33
|
+
* Creates a React renderer with instance-owned runtime configuration.
|
|
34
|
+
*
|
|
35
|
+
* React renderers depend on plugin-owned router, MDX, and HMR metadata state.
|
|
36
|
+
* Keeping that state on the instance avoids cross-plugin static mutation while
|
|
37
|
+
* preserving the same runtime services the base initializer wires up.
|
|
38
|
+
*/
|
|
39
|
+
initializeRenderer(options?: {
|
|
40
|
+
rendererModules?: unknown;
|
|
41
|
+
}): ReactRenderer;
|
|
42
|
+
private ensureRuntimeDependencies;
|
|
114
43
|
get plugins(): EcoBuildPlugin[];
|
|
44
|
+
get browserRuntimeManifest(): BrowserRuntimeManifest;
|
|
45
|
+
/**
|
|
46
|
+
* Ensures the optional React MDX loader exists before either config-time
|
|
47
|
+
* manifest sealing or runtime setup needs it.
|
|
48
|
+
*/
|
|
49
|
+
private ensureMdxLoaderPlugin;
|
|
50
|
+
/**
|
|
51
|
+
* Prepares React's build-facing loader contributions before config build seals
|
|
52
|
+
* the app manifest.
|
|
53
|
+
*/
|
|
54
|
+
prepareBuildContributions(): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Performs runtime-only React setup after build contributions are already
|
|
57
|
+
* materialized.
|
|
58
|
+
*/
|
|
115
59
|
setup(): Promise<void>;
|
|
116
60
|
/**
|
|
117
61
|
* Provides React-specific HMR strategy with Fast Refresh support.
|
|
@@ -123,21 +67,6 @@ export declare class ReactPlugin extends IntegrationPlugin<React.JSX.Element> {
|
|
|
123
67
|
* @returns ReactHmrStrategy instance for handling React component updates
|
|
124
68
|
*/
|
|
125
69
|
getHmrStrategy(): HmrStrategy | undefined;
|
|
126
|
-
/**
|
|
127
|
-
* Override to register React-specific specifier mappings for HMR.
|
|
128
|
-
*/
|
|
129
|
-
setHmrManager(hmrManager: IHmrManager): void;
|
|
130
|
-
/**
|
|
131
|
-
* Declares React's boundary deferral rule for cross-integration rendering.
|
|
132
|
-
*
|
|
133
|
-
* React defers when a render pass owned by another integration enters a React
|
|
134
|
-
* component boundary. That boundary is then resolved later through the marker
|
|
135
|
-
* graph stage using the React renderer.
|
|
136
|
-
*
|
|
137
|
-
* @param input Boundary metadata for the active render pass.
|
|
138
|
-
* @returns `true` when the boundary should be deferred into the marker pass.
|
|
139
|
-
*/
|
|
140
|
-
shouldDeferComponentBoundary(input: ComponentBoundaryPolicyInput): boolean;
|
|
141
70
|
}
|
|
142
71
|
/**
|
|
143
72
|
* Factory function to create a React plugin instance
|
package/src/react.plugin.js
CHANGED
|
@@ -1,11 +1,68 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
IntegrationPlugin
|
|
3
|
+
} from "@ecopages/core/plugins/integration-plugin";
|
|
2
4
|
import { Logger } from "@ecopages/logger";
|
|
5
|
+
import { REACT_PLUGIN_NAME } from "./react.constants.js";
|
|
3
6
|
import { ReactRenderer } from "./react-renderer.js";
|
|
4
7
|
import { ReactHmrStrategy } from "./react-hmr-strategy.js";
|
|
5
8
|
import { ReactRuntimeBundleService } from "./services/react-runtime-bundle.service.js";
|
|
6
9
|
import { ReactHmrPageMetadataCache } from "./services/react-hmr-page-metadata-cache.js";
|
|
10
|
+
import { createReactMdxLoaderPlugin } from "./utils/react-mdx-loader-plugin.js";
|
|
7
11
|
const appLogger = new Logger("[ReactPlugin]");
|
|
8
|
-
const PLUGIN_NAME =
|
|
12
|
+
const PLUGIN_NAME = REACT_PLUGIN_NAME;
|
|
13
|
+
const mergePluginLists = (...lists) => {
|
|
14
|
+
const merged = lists.flatMap((list) => list ? [...list] : []);
|
|
15
|
+
return merged.length > 0 ? merged : void 0;
|
|
16
|
+
};
|
|
17
|
+
const appendMdxExtensions = (target, mdxExtensions) => {
|
|
18
|
+
for (const extension of mdxExtensions) {
|
|
19
|
+
if (!target.includes(extension)) {
|
|
20
|
+
target.push(extension);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const resolveReactMdxCompilerOptions = (mdxOptions) => {
|
|
25
|
+
const { compilerOptions, remarkPlugins, rehypePlugins, recmaPlugins } = mdxOptions;
|
|
26
|
+
const resolved = {
|
|
27
|
+
...compilerOptions,
|
|
28
|
+
jsxImportSource: "react",
|
|
29
|
+
jsxRuntime: "automatic",
|
|
30
|
+
development: process.env.NODE_ENV === "development"
|
|
31
|
+
};
|
|
32
|
+
const mergedRemark = mergePluginLists(compilerOptions?.remarkPlugins, remarkPlugins);
|
|
33
|
+
const mergedRehype = mergePluginLists(compilerOptions?.rehypePlugins, rehypePlugins);
|
|
34
|
+
const mergedRecma = mergePluginLists(compilerOptions?.recmaPlugins, recmaPlugins);
|
|
35
|
+
if (mergedRemark) resolved.remarkPlugins = mergedRemark;
|
|
36
|
+
if (mergedRehype) resolved.rehypePlugins = mergedRehype;
|
|
37
|
+
if (mergedRecma) resolved.recmaPlugins = mergedRecma;
|
|
38
|
+
return resolved;
|
|
39
|
+
};
|
|
40
|
+
const resolveReactPluginOptions = (options) => {
|
|
41
|
+
const { extensions: userExtensions, router, mdx, explicitGraph, dependencies, ...baseConfig } = options ?? {};
|
|
42
|
+
const extensions = [...userExtensions ?? [".tsx"]];
|
|
43
|
+
const mdxEnabled = mdx?.enabled ?? false;
|
|
44
|
+
const mdxExtensions = mdx?.extensions ?? [".mdx"];
|
|
45
|
+
if (mdxEnabled) {
|
|
46
|
+
appendMdxExtensions(extensions, mdxExtensions);
|
|
47
|
+
} else if (mdx?.extensions?.length) {
|
|
48
|
+
appLogger.warn(
|
|
49
|
+
"MDX extensions provided but MDX is disabled. MDX files will not be processed. Set mdx.enabled to true to enable MDX support."
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
const rendererConfig = {
|
|
53
|
+
routerAdapter: router,
|
|
54
|
+
mdxCompilerOptions: mdxEnabled && mdx ? resolveReactMdxCompilerOptions(mdx) : void 0,
|
|
55
|
+
mdxExtensions,
|
|
56
|
+
hmrPageMetadataCache: new ReactHmrPageMetadataCache(),
|
|
57
|
+
explicitGraphEnabled: explicitGraph ?? false
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
...baseConfig,
|
|
61
|
+
extensions,
|
|
62
|
+
integrationDependencies: dependencies,
|
|
63
|
+
rendererConfig
|
|
64
|
+
};
|
|
65
|
+
};
|
|
9
66
|
class ReactPlugin extends IntegrationPlugin {
|
|
10
67
|
renderer = ReactRenderer;
|
|
11
68
|
routerAdapter;
|
|
@@ -14,52 +71,63 @@ class ReactPlugin extends IntegrationPlugin {
|
|
|
14
71
|
mdxExtensions;
|
|
15
72
|
mdxLoaderPlugin;
|
|
16
73
|
runtimeBundleService;
|
|
17
|
-
hmrPageMetadataCache
|
|
74
|
+
hmrPageMetadataCache;
|
|
75
|
+
runtimeDependenciesInitialized = false;
|
|
18
76
|
/**
|
|
19
77
|
* Indicates whether React explicit graph mode is enabled for renderer/HMR behavior.
|
|
20
78
|
*/
|
|
21
79
|
explicitGraphEnabled;
|
|
80
|
+
rendererConfig;
|
|
22
81
|
constructor(options) {
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
if (options?.mdx?.enabled) {
|
|
26
|
-
extensions.push(...mdxExtensions);
|
|
27
|
-
} else if (options?.mdx?.extensions?.length) {
|
|
28
|
-
appLogger.warn(
|
|
29
|
-
"MDX extensions provided but MDX is disabled. MDX files will not be processed. Set mdx.enabled to true to enable MDX support."
|
|
30
|
-
);
|
|
31
|
-
}
|
|
82
|
+
const config = resolveReactPluginOptions(options);
|
|
83
|
+
const { extensions, rendererConfig, integrationDependencies, ...baseConfig } = config;
|
|
32
84
|
super({
|
|
33
85
|
name: PLUGIN_NAME,
|
|
34
86
|
extensions,
|
|
35
|
-
|
|
87
|
+
jsxImportSource: "react",
|
|
88
|
+
integrationDependencies,
|
|
89
|
+
...baseConfig
|
|
36
90
|
});
|
|
37
|
-
this.
|
|
38
|
-
this.
|
|
91
|
+
this.routerAdapter = rendererConfig.routerAdapter;
|
|
92
|
+
this.mdxCompilerOptions = rendererConfig.mdxCompilerOptions;
|
|
93
|
+
this.mdxEnabled = Boolean(rendererConfig.mdxCompilerOptions);
|
|
94
|
+
this.mdxExtensions = rendererConfig.mdxExtensions ?? [".mdx"];
|
|
95
|
+
this.hmrPageMetadataCache = rendererConfig.hmrPageMetadataCache ?? new ReactHmrPageMetadataCache();
|
|
96
|
+
this.explicitGraphEnabled = rendererConfig.explicitGraphEnabled ?? false;
|
|
97
|
+
this.rendererConfig = {
|
|
98
|
+
...rendererConfig,
|
|
99
|
+
mdxExtensions: this.mdxExtensions,
|
|
100
|
+
hmrPageMetadataCache: this.hmrPageMetadataCache,
|
|
101
|
+
explicitGraphEnabled: this.explicitGraphEnabled
|
|
102
|
+
};
|
|
39
103
|
if (this.mdxEnabled) {
|
|
40
|
-
const { compilerOptions, remarkPlugins, rehypePlugins, recmaPlugins } = options?.mdx || {};
|
|
41
|
-
this.mdxCompilerOptions = {
|
|
42
|
-
...compilerOptions,
|
|
43
|
-
remarkPlugins: [...compilerOptions?.remarkPlugins || [], ...remarkPlugins || []],
|
|
44
|
-
rehypePlugins: [...compilerOptions?.rehypePlugins || [], ...rehypePlugins || []],
|
|
45
|
-
recmaPlugins: [...compilerOptions?.recmaPlugins || [], ...recmaPlugins || []],
|
|
46
|
-
jsxImportSource: "react",
|
|
47
|
-
jsxRuntime: "automatic",
|
|
48
|
-
development: process.env.NODE_ENV === "development"
|
|
49
|
-
};
|
|
50
104
|
appLogger.debug("MDX mode enabled with React jsx runtime");
|
|
51
105
|
}
|
|
52
|
-
this.routerAdapter = options?.router;
|
|
53
106
|
this.runtimeBundleService = new ReactRuntimeBundleService({
|
|
54
107
|
routerAdapter: this.routerAdapter
|
|
55
108
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Creates a React renderer with instance-owned runtime configuration.
|
|
112
|
+
*
|
|
113
|
+
* React renderers depend on plugin-owned router, MDX, and HMR metadata state.
|
|
114
|
+
* Keeping that state on the instance avoids cross-plugin static mutation while
|
|
115
|
+
* preserving the same runtime services the base initializer wires up.
|
|
116
|
+
*/
|
|
117
|
+
initializeRenderer(options) {
|
|
118
|
+
const renderer = new this.renderer({
|
|
119
|
+
...this.createRendererOptions(options),
|
|
120
|
+
reactConfig: this.rendererConfig
|
|
121
|
+
});
|
|
122
|
+
return this.attachRendererRuntimeServices(renderer);
|
|
123
|
+
}
|
|
124
|
+
ensureRuntimeDependencies() {
|
|
125
|
+
if (this.runtimeDependenciesInitialized) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.runtimeBundleService.setRootDir(this.appConfig?.rootDir);
|
|
62
129
|
this.integrationDependencies.unshift(...this.runtimeBundleService.getDependencies());
|
|
130
|
+
this.runtimeDependenciesInitialized = true;
|
|
63
131
|
}
|
|
64
132
|
get plugins() {
|
|
65
133
|
if (this.mdxLoaderPlugin) {
|
|
@@ -67,11 +135,35 @@ class ReactPlugin extends IntegrationPlugin {
|
|
|
67
135
|
}
|
|
68
136
|
return [];
|
|
69
137
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
138
|
+
get browserRuntimeManifest() {
|
|
139
|
+
this.ensureRuntimeDependencies();
|
|
140
|
+
return this.runtimeBundleService.getRuntimeManifest();
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Ensures the optional React MDX loader exists before either config-time
|
|
144
|
+
* manifest sealing or runtime setup needs it.
|
|
145
|
+
*/
|
|
146
|
+
async ensureMdxLoaderPlugin() {
|
|
147
|
+
if (!this.mdxEnabled || !this.mdxCompilerOptions || this.mdxLoaderPlugin) {
|
|
148
|
+
return;
|
|
74
149
|
}
|
|
150
|
+
this.mdxLoaderPlugin = createReactMdxLoaderPlugin(this.mdxCompilerOptions);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Prepares React's build-facing loader contributions before config build seals
|
|
154
|
+
* the app manifest.
|
|
155
|
+
*/
|
|
156
|
+
async prepareBuildContributions() {
|
|
157
|
+
this.ensureRuntimeDependencies();
|
|
158
|
+
await this.ensureMdxLoaderPlugin();
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Performs runtime-only React setup after build contributions are already
|
|
162
|
+
* materialized.
|
|
163
|
+
*/
|
|
164
|
+
async setup() {
|
|
165
|
+
this.ensureRuntimeDependencies();
|
|
166
|
+
await this.ensureMdxLoaderPlugin();
|
|
75
167
|
await super.setup();
|
|
76
168
|
}
|
|
77
169
|
/**
|
|
@@ -88,32 +180,15 @@ class ReactPlugin extends IntegrationPlugin {
|
|
|
88
180
|
return void 0;
|
|
89
181
|
}
|
|
90
182
|
const context = this.hmrManager.getDefaultContext();
|
|
91
|
-
return new ReactHmrStrategy(
|
|
183
|
+
return new ReactHmrStrategy({
|
|
92
184
|
context,
|
|
93
|
-
this.hmrPageMetadataCache,
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
*/
|
|
101
|
-
setHmrManager(hmrManager) {
|
|
102
|
-
super.setHmrManager(hmrManager);
|
|
103
|
-
hmrManager.registerSpecifierMap(this.runtimeBundleService.getSpecifierMap());
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Declares React's boundary deferral rule for cross-integration rendering.
|
|
107
|
-
*
|
|
108
|
-
* React defers when a render pass owned by another integration enters a React
|
|
109
|
-
* component boundary. That boundary is then resolved later through the marker
|
|
110
|
-
* graph stage using the React renderer.
|
|
111
|
-
*
|
|
112
|
-
* @param input Boundary metadata for the active render pass.
|
|
113
|
-
* @returns `true` when the boundary should be deferred into the marker pass.
|
|
114
|
-
*/
|
|
115
|
-
shouldDeferComponentBoundary(input) {
|
|
116
|
-
return input.targetIntegration === this.name && input.currentIntegration !== this.name;
|
|
185
|
+
pageMetadataCache: this.hmrPageMetadataCache,
|
|
186
|
+
runtimeManifest: this.runtimeBundleService.getRuntimeManifest("development"),
|
|
187
|
+
mdxCompilerOptions: this.mdxCompilerOptions,
|
|
188
|
+
ownedTemplateExtensions: this.extensions,
|
|
189
|
+
allTemplateExtensions: this.appConfig.templatesExt,
|
|
190
|
+
explicitGraphEnabled: this.explicitGraphEnabled
|
|
191
|
+
});
|
|
117
192
|
}
|
|
118
193
|
}
|
|
119
194
|
function reactPlugin(options) {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { AssetDefinition } from '@ecopages/core/services/asset-processing-service';
|
|
2
|
+
import type { CompileOptions } from '@mdx-js/mdx';
|
|
3
|
+
import type { ReactRouterAdapter } from './router-adapter.js';
|
|
4
|
+
import type { ReactHmrPageMetadataCache } from './services/react-hmr-page-metadata-cache.js';
|
|
5
|
+
/**
|
|
6
|
+
* MDX configuration options for the React plugin.
|
|
7
|
+
*/
|
|
8
|
+
export type ReactMdxOptions = {
|
|
9
|
+
/**
|
|
10
|
+
* Whether to enable MDX support.
|
|
11
|
+
* @default false
|
|
12
|
+
*/
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Compiler options for MDX.
|
|
16
|
+
* @default undefined
|
|
17
|
+
*/
|
|
18
|
+
compilerOptions?: Omit<CompileOptions, 'jsxImportSource' | 'jsxRuntime'>;
|
|
19
|
+
/**
|
|
20
|
+
* Remark plugins.
|
|
21
|
+
* @default undefined
|
|
22
|
+
*/
|
|
23
|
+
remarkPlugins?: CompileOptions['remarkPlugins'];
|
|
24
|
+
/**
|
|
25
|
+
* Rehype plugins.
|
|
26
|
+
* @default undefined
|
|
27
|
+
*/
|
|
28
|
+
rehypePlugins?: CompileOptions['rehypePlugins'];
|
|
29
|
+
/**
|
|
30
|
+
* Recma plugins.
|
|
31
|
+
* @default undefined
|
|
32
|
+
*/
|
|
33
|
+
recmaPlugins?: CompileOptions['recmaPlugins'];
|
|
34
|
+
/**
|
|
35
|
+
* Custom extensions to be treated as MDX files.
|
|
36
|
+
* @default ['.mdx']
|
|
37
|
+
*/
|
|
38
|
+
extensions?: string[];
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Options for the React plugin.
|
|
42
|
+
*/
|
|
43
|
+
export type ReactPluginOptions = {
|
|
44
|
+
extensions?: string[];
|
|
45
|
+
dependencies?: AssetDefinition[];
|
|
46
|
+
/**
|
|
47
|
+
* Enables explicit client graph mode for React page entries.
|
|
48
|
+
*
|
|
49
|
+
* When enabled, React page-entry bundling relies on explicit dependency declarations
|
|
50
|
+
* and skips AST-based `middleware`/`requires` stripping in the React path.
|
|
51
|
+
* @default false
|
|
52
|
+
*/
|
|
53
|
+
explicitGraph?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Router adapter for SPA navigation.
|
|
56
|
+
* When provided, pages with layouts will be wrapped in the router for client-side navigation.
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* import { ecoRouter } from '@ecopages/react-router';
|
|
60
|
+
* reactPlugin({ router: ecoRouter() })
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
router?: ReactRouterAdapter;
|
|
64
|
+
/**
|
|
65
|
+
* MDX configuration for handling .mdx files within the React plugin.
|
|
66
|
+
* When enabled, MDX files are treated as React pages with full router support.
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* reactPlugin({
|
|
70
|
+
* router: ecoRouter(),
|
|
71
|
+
* mdx: {
|
|
72
|
+
* enabled: true,
|
|
73
|
+
* extensions: ['.mdx', '.md'],
|
|
74
|
+
* remarkPlugins: [remarkGfm],
|
|
75
|
+
* rehypePlugins: [[rehypePrettyCode, { theme: '...' }]],
|
|
76
|
+
* }
|
|
77
|
+
* })
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
mdx?: ReactMdxOptions;
|
|
81
|
+
};
|
|
82
|
+
export type ReactRendererConfig = {
|
|
83
|
+
routerAdapter?: ReactRouterAdapter;
|
|
84
|
+
mdxCompilerOptions?: CompileOptions;
|
|
85
|
+
mdxExtensions?: string[];
|
|
86
|
+
hmrPageMetadataCache?: ReactHmrPageMetadataCache;
|
|
87
|
+
explicitGraphEnabled?: boolean;
|
|
88
|
+
};
|
|
File without changes
|
package/src/router-adapter.d.ts
CHANGED
|
@@ -12,11 +12,10 @@
|
|
|
12
12
|
* const myRouter: ReactRouterAdapter = {
|
|
13
13
|
* name: 'my-router',
|
|
14
14
|
* bundle: {
|
|
15
|
-
* importPath: '@my/router/browser
|
|
15
|
+
* importPath: '@my/router/browser',
|
|
16
16
|
* outputName: 'my-router',
|
|
17
17
|
* externals: ['react', 'react-dom'],
|
|
18
18
|
* },
|
|
19
|
-
* importMapKey: '@my/router',
|
|
20
19
|
* components: {
|
|
21
20
|
* router: 'MyRouter',
|
|
22
21
|
* pageContent: 'PageOutlet',
|
|
@@ -36,39 +35,33 @@ export interface ReactRouterAdapter {
|
|
|
36
35
|
bundle: {
|
|
37
36
|
/**
|
|
38
37
|
* Node module import path for the browser-compatible entry.
|
|
39
|
-
* @example '@ecopages/react-router/browser
|
|
38
|
+
* @example '@ecopages/react-router/browser'
|
|
40
39
|
*/
|
|
41
40
|
importPath: string;
|
|
42
41
|
/**
|
|
43
42
|
* Output filename (without extension).
|
|
44
|
-
* @example '
|
|
43
|
+
* @example 'my-router'
|
|
45
44
|
*/
|
|
46
45
|
outputName: string;
|
|
47
46
|
/**
|
|
48
47
|
* Packages to externalize when bundling.
|
|
49
|
-
* These should
|
|
50
|
-
* @example ['react', 'react-dom'
|
|
48
|
+
* These should stay as bare runtime dependencies for the router bundle.
|
|
49
|
+
* @example ['react', 'react-dom']
|
|
51
50
|
*/
|
|
52
51
|
externals: string[];
|
|
53
52
|
};
|
|
54
|
-
/**
|
|
55
|
-
* Bare specifier for the import map entry.
|
|
56
|
-
* This is what the hydration script will import from.
|
|
57
|
-
* @example '@ecopages/react-router'
|
|
58
|
-
*/
|
|
59
|
-
importMapKey: string;
|
|
60
53
|
/**
|
|
61
54
|
* Component names to import from the router package.
|
|
62
55
|
*/
|
|
63
56
|
components: {
|
|
64
57
|
/**
|
|
65
58
|
* The router component that wraps the layout.
|
|
66
|
-
* @example '
|
|
59
|
+
* @example 'MyRouter'
|
|
67
60
|
*/
|
|
68
61
|
router: string;
|
|
69
62
|
/**
|
|
70
63
|
* The component that renders the current page content.
|
|
71
|
-
* @example '
|
|
64
|
+
* @example 'PageOutlet'
|
|
72
65
|
*/
|
|
73
66
|
pageContent: string;
|
|
74
67
|
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
type Subscribe = (onStoreChange: () => void) => () => void;
|
|
2
|
+
export declare function useSyncExternalStoreWithSelector<Snapshot, Selection>(subscribe: Subscribe, getSnapshot: () => Snapshot, getServerSnapshot: (() => Snapshot) | undefined, selector: (snapshot: Snapshot) => Selection, isEqual?: (left: Selection, right: Selection) => boolean): Selection;
|
|
3
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useDebugValue, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
2
|
+
const objectIs = Object.is;
|
|
3
|
+
function useSyncExternalStoreWithSelector(subscribe, getSnapshot, getServerSnapshot, selector, isEqual) {
|
|
4
|
+
const instRef = useRef({ hasValue: false, value: void 0 });
|
|
5
|
+
const memoizedSelectionRef = useMemo(() => {
|
|
6
|
+
let hasMemo = false;
|
|
7
|
+
let memoizedSnapshot;
|
|
8
|
+
let memoizedSelection;
|
|
9
|
+
const maybeGetServerSnapshot = getServerSnapshot === void 0 ? null : getServerSnapshot;
|
|
10
|
+
const memoizedSelector = (nextSnapshot) => {
|
|
11
|
+
if (!hasMemo) {
|
|
12
|
+
hasMemo = true;
|
|
13
|
+
memoizedSnapshot = nextSnapshot;
|
|
14
|
+
const nextSelection2 = selector(nextSnapshot);
|
|
15
|
+
const inst = instRef.current;
|
|
16
|
+
if (isEqual !== void 0 && inst.hasValue) {
|
|
17
|
+
const currentSelection2 = inst.value;
|
|
18
|
+
if (isEqual(currentSelection2, nextSelection2)) {
|
|
19
|
+
memoizedSelection = currentSelection2;
|
|
20
|
+
return currentSelection2;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
memoizedSelection = nextSelection2;
|
|
24
|
+
return nextSelection2;
|
|
25
|
+
}
|
|
26
|
+
const currentSelection = memoizedSelection;
|
|
27
|
+
if (objectIs(memoizedSnapshot, nextSnapshot)) {
|
|
28
|
+
return currentSelection;
|
|
29
|
+
}
|
|
30
|
+
const nextSelection = selector(nextSnapshot);
|
|
31
|
+
if (isEqual !== void 0 && isEqual(currentSelection, nextSelection)) {
|
|
32
|
+
memoizedSnapshot = nextSnapshot;
|
|
33
|
+
return currentSelection;
|
|
34
|
+
}
|
|
35
|
+
memoizedSnapshot = nextSnapshot;
|
|
36
|
+
memoizedSelection = nextSelection;
|
|
37
|
+
return nextSelection;
|
|
38
|
+
};
|
|
39
|
+
return [
|
|
40
|
+
() => memoizedSelector(getSnapshot()),
|
|
41
|
+
maybeGetServerSnapshot === null ? void 0 : () => memoizedSelector(maybeGetServerSnapshot())
|
|
42
|
+
];
|
|
43
|
+
}, [getSnapshot, getServerSnapshot, selector, isEqual]);
|
|
44
|
+
const value = useSyncExternalStore(subscribe, memoizedSelectionRef[0], memoizedSelectionRef[1]);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
instRef.current = {
|
|
47
|
+
hasValue: true,
|
|
48
|
+
value
|
|
49
|
+
};
|
|
50
|
+
}, [value]);
|
|
51
|
+
useDebugValue(value);
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
useSyncExternalStoreWithSelector
|
|
56
|
+
};
|