@ebay/muse-lib-react 2.0.0 → 2.0.1

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/MUSE_README.md CHANGED
@@ -1,621 +1,370 @@
1
1
  # Plugin Integration Guide: @ebay/muse-lib-react
2
2
 
3
- **Generated**: 2026-03-28
3
+ **Generated**: 2026-05-21
4
4
  **Plugin Type**: lib
5
5
 
6
6
  ---
7
7
 
8
8
  ## 1. Plugin Purpose & Overview
9
9
 
10
- `@ebay/muse-lib-react` is the **foundational React library plugin** for MUSE applications. As a lib-type plugin with app entry capabilities, it serves three critical roles:
10
+ ### What This Plugin Does
11
11
 
12
- 1. **React Application Bootstrap** - Creates the React root element, initializes the application, and renders the main component tree with all providers (Redux, React Router, React Query, Nice Modal)
12
+ `@ebay/muse-lib-react` is the foundational React lib plugin for the MUSE micro-frontends framework. It bootstraps the React application (creates the React DOM root and renders the app), assembles the provider stack (Redux, React Query, React Router, NiceModal, SubApp context), builds the route tree from contributions across all plugins, and combines Redux reducers from all loaded plugins into a single store.
13
13
 
14
- 2. **Shared Module Provider** - Exports commonly-used React libraries as shared modules to prevent duplicate dependencies across plugins:
15
- - `react-loadable` - Code splitting and lazy loading
16
- - `lodash` - Utility functions
17
- - `react-use` - Collection of React hooks
18
- - `react-router-dom` v6 - Client-side routing
19
- - `@tanstack/react-query` v4 - Server state management
14
+ ### Key Features
20
15
 
21
- 3. **Extension Point Foundation** - Defines comprehensive extension points enabling other plugins to:
22
- - Customize routing and homepage
23
- - Extend Redux store with plugin-specific reducers
24
- - Modify the provider stack
25
- - Define main layout and root-level components
26
- - Hook into application lifecycle events
16
+ - **App bootstrapping** — Creates and renders the React root into `#muse-react-root` on demand, invokes lifecycle hooks before and after render.
17
+ - **Provider stack** — Builds the ordered React provider chain (React Query → Redux → SubApp Context → NiceModal → Router) and makes it extensible via extension points.
18
+ - **Route assembly** Collects route definitions from all loaded plugins, normalizes absolute/parent routes, resolves the homepage component, and renders with React Router v7.
19
+ - **Redux store** Combines built-in reducers with plugin-contributed reducers (both plugin-scoped and root-level).
20
+ - **Shared module exports** Bundles React ecosystem libraries (React Router, React Query, lodash, react-use, react-loadable) so all normal plugins can use them without duplicating them in their own bundles.
21
+ - **Utility primitives** Exports `extendArray`, `useExtPoint`, and `Nodes` to help other plugins build their own extensible arrays and UI node lists.
27
22
 
28
- ### Key Features
23
+ ### Plugin Type: lib
29
24
 
30
- - **Provider Stack Management**: Ordered provider chain (React Query Redux SubApp Context Nice Modal React Router) with extension points at every stage
31
- - **Flexible Routing**: Supports Browser, Hash, and Memory routers with React Router v6, backwards-compatible with v3-style route configuration
32
- - **Redux Integration**: Pre-configured Redux store with Thunk middleware, Redux Logger (dev), and DevTools support
33
- - **Sub-App Support**: Built-in iframe-based sub-application system for embedding external MUSE apps
34
- - **Lifecycle Hooks**: Extension points for initialization (`beforeRender`, `afterRender`, `onReady`)
25
+ As a `lib` plugin with `muse.isAppEntry: true`, this plugin loads before all normal plugins and serves two roles: it **provides shared runtime modules** (React, Redux, React Router, etc.) to every normal plugin via the MUSE build system's module federation, and it **acts as the application entry point** that renders the React tree. Normal plugins must never bundle these libraries themselves; they receive them from this plugin at runtime.
35
26
 
36
27
  ---
37
28
 
38
29
  ## 2. Extension Points Exposed
39
30
 
40
- This plugin exposes extension points that allow other plugins to customize and extend the React application. Extension points are organized into four categories:
31
+ This plugin exposes the following extension points that other plugins can implement to extend its functionality.
32
+
33
+ ### Summary
41
34
 
42
- ### 2.1 Application Lifecycle
35
+ - **Total Extension Points**: 15
36
+ - **Categories**: Root Lifecycle, Provider Stack, Routing, Redux, App Shell, Utility
43
37
 
44
- Extension points for hooking into application initialization:
38
+ ### Extension Point List
45
39
 
46
40
  #### `root.beforeRender`
47
- - **Type**: `Function`
48
- - **Purpose**: Execute logic before the React root element is created and rendered
49
- - **Use Case**: Early initialization tasks like setting up global state, registering event listeners, or configuring third-party libraries
50
- - **Invocation**: Called in `src/index.js:26` before `createRoot()`
51
- - **Context**: Runs after all plugins are loaded but before any React rendering
41
+
42
+ - **Purpose**: Execute initialization logic before the React root is created and rendered. Use this for side effects that must complete before any React component mounts.
43
+ - **When Invoked**: Synchronously before `createRoot().render()` is called.
44
+ - **Context Parameters**: none
45
+ - **Expected Return**: ignored
46
+ - **Use Case Example**: Injecting global CSS variables, initializing an analytics library, or pre-fetching critical configuration.
47
+ - **File Reference**: `src/index.jsx:26`
52
48
 
53
49
  #### `root.afterRender`
54
- - **Type**: `Function`
55
- - **Purpose**: Execute logic immediately after the React root element is rendered
56
- - **Use Case**: Post-render initialization like focusing elements, starting analytics, or triggering initial data fetches
57
- - **Invocation**: Called in `src/index.js:31` after `root.render(<Root />)`
58
- - **Context**: Runs before `onReady`, React tree is mounted but may not be fully painted
50
+
51
+ - **Purpose**: Execute logic immediately after `root.render()` is called (before React has painted).
52
+ - **When Invoked**: Synchronously after `root.render(<Root />)`.
53
+ - **Context Parameters**: none
54
+ - **Expected Return**: ignored
55
+ - **Use Case Example**: Starting a performance measurement timer or notifying a loading screen manager.
56
+ - **File Reference**: `src/index.jsx:31`
59
57
 
60
58
  #### `onReady`
61
- - **Type**: `Function`
62
- - **Purpose**: Execute logic after the application is fully mounted to the DOM
63
- - **Use Case**: Final initialization tasks that require the full component tree to be ready
64
- - **Invocation**: Called in `src/index.js:33` as the last step of `renderApp()`
65
- - **Context**: Complete application ready, all components mounted
66
59
 
67
- ### 2.2 Routing & Navigation
60
+ - **Purpose**: Execute logic after the application is fully mounted to the DOM.
61
+ - **When Invoked**: After `root.afterRender`, still synchronous on the same tick.
62
+ - **Context Parameters**: none
63
+ - **Expected Return**: ignored
64
+ - **Use Case Example**: Hiding a static HTML splash screen, signalling to an E2E test harness that the app is ready.
65
+ - **File Reference**: `src/index.jsx:33`
68
66
 
69
- Extension points for customizing application routes and routing behavior:
67
+ #### `root.renderChildren`
70
68
 
71
- #### `route`
72
- - **Type**: `MuseRoute | MuseRoute[]`
73
- - **Purpose**: Register route definitions for plugin-specific pages
74
- - **Use Case**: Add new pages/routes to the application
75
- - **Collection**: Routes collected in `src/common/routeConfig.js:87` via `plugin.invoke('!route')`
76
- - **Route Structure**:
77
- - `path`: URL path (string or array of strings)
78
- - `component`: React component to render
79
- - `childRoutes`: Nested routes
80
- - `parent`: ID of parent route (moves route to parent's `childRoutes`)
81
- - `isIndex`: Mark as index route (duplicated to path='')
82
- - `id`: Unique identifier for route (enables `parent` references)
83
- - **Special Behavior**:
84
- - Routes with `path` starting with `/` are moved to top level
85
- - Routes with `parent` property are relocated to parent's `childRoutes`
86
- - Array paths are expanded to multiple route objects
87
- - **File Reference**: `src/common/routeConfig.js:32-132`
69
+ - **Purpose**: Wrap the rendered route tree in additional React components. Each implementer receives the current children and must return the (potentially wrapped) children.
70
+ - **When Invoked**: During `Root` render, before the provider chain is built.
71
+ - **Context Parameters**: `children` `React.ReactNode` the current route element tree
72
+ - **Expected Return**: `React.ReactNode` the (wrapped or unchanged) children
73
+ - **Use Case Example**: Wrapping the entire app in an error boundary, a theme context, or a drag-and-drop context.
74
+ - **File Reference**: `src/Root.jsx:104`
88
75
 
89
76
  #### `routerProps`
90
- - **Type**: `Record<string, any>`
91
- - **Purpose**: Merge additional props into the React Router `RouterProvider` component
92
- - **Use Case**: Customize router behavior (e.g., future flags, error handlers)
93
- - **Collection**: First contribution used via `plugin.invoke('!routerProps')[0]` in `src/Root.js:140`
94
- - **Note**: Props are merged with defaults (`basename`, `router`, `navigator`)
95
- - **File Reference**: `src/Root.js:140-177`
96
77
 
97
- ### 2.3 Provider Stack Customization
98
-
99
- Extension points for modifying the provider chain that wraps the application. Providers execute in order from outer to inner (lower order = outer wrapper).
78
+ - **Purpose**: Merge additional props directly into the `RouterProvider` component.
79
+ - **When Invoked**: During `Root` render, when the Router is configured.
80
+ - **Context Parameters**: none
81
+ - **Expected Return**: `Record<string, any>` — props merged into `RouterProvider`. Only the first contribution is used.
82
+ - **Use Case Example**: Providing a custom `future` flag object for React Router v7 feature flags.
83
+ - **File Reference**: `src/Root.jsx:140`
100
84
 
101
85
  #### `root.preProcessProviders`
102
- - **Type**: `(context: { providers: ProviderType[] }) => void`
103
- - **Purpose**: Modify or remove built-in providers before custom providers are collected
104
- - **Use Case**: Disable default providers (e.g., remove Redux if using alternative state management)
105
- - **Invocation Order**: 1st - called by `extendArray()` in `src/Root.js:181`
106
- - **Context**: `{ providers }` array of default providers (React Query, Redux, SubApp Context, Nice Modal, React Router)
107
- - **File Reference**: `src/utils.js:17`, `src/Root.js:144-181`
86
+
87
+ - **Purpose**: Inspect or mutate the initial providers array before additional providers from `root.getProviders` are added. Use this to remove or reorder built-in providers.
88
+ - **When Invoked**: During `Root` render, via `extendArray` before provider collection.
89
+ - **Context Parameters**: `{ providers: ProviderType[] }` — the current mutable array of built-in providers
90
+ - **Expected Return**: `string | boolean` return value is used by `jsPlugin.sort` ordering
91
+ - **Use Case Example**: Removing the built-in Redux `Provider` to replace it with a custom store setup.
92
+ - **File Reference**: `src/Root.jsx:181`, `src/utils.js:17`
108
93
 
109
94
  #### `root.getProviders`
110
- - **Type**: `(context: { providers: ProviderType[] }) => ProviderType | ProviderType[] | void`
111
- - **Purpose**: Contribute additional providers to wrap the application
112
- - **Use Case**: Add custom providers (e.g., theme provider, i18n provider, custom context)
113
- - **Invocation Order**: 2nd - called by `extendArray()` in `src/Root.js:181`
114
- - **Return**: Single provider, array of providers, or nothing
115
- - **Provider Structure**:
116
- - `order`: Rendering order (lower = outer, default sort)
117
- - `key`: Unique identifier
118
- - `provider`: React component (must accept `children` prop)
119
- - `props`: Props object for provider
120
- - `renderProvider`: Custom render function `(children) => ReactNode` (alternative to `provider`)
121
- - **File Reference**: `src/utils.js:18`, `src/Root.js:144-192`
95
+
96
+ - **Purpose**: Contribute one or more additional React context providers to be inserted into the provider stack at a specific order position.
97
+ - **When Invoked**: During `Root` render, via `extendArray` during provider collection.
98
+ - **Context Parameters**: `{ providers: ProviderType[] }` — the current providers array (after pre-processing)
99
+ - **Expected Return**: `ProviderType | ProviderType[] | void` provider descriptor(s) to add
100
+ - **Use Case Example**: Adding an Apollo Client provider at `order: 25` (between Redux and SubApp context).
101
+ - **File Reference**: `src/Root.jsx:181`, `src/utils.js:18`
122
102
 
123
103
  #### `root.processProviders`
124
- - **Type**: `(context: { providers: ProviderType[] }) => void`
125
- - **Purpose**: Modify the collected providers array after all contributions
126
- - **Use Case**: Reorder providers, modify provider props, or conditionally add/remove providers
127
- - **Invocation Order**: 3rd - called by `extendArray()` in `src/Root.js:181`
128
- - **Context**: `{ providers }` includes both built-in and contributed providers
129
- - **File Reference**: `src/utils.js:20`, `src/Root.js:181`
104
+
105
+ - **Purpose**: Post-process the combined providers array after all `getProviders` contributions have been collected.
106
+ - **When Invoked**: During `Root` render, via `extendArray` after collection.
107
+ - **Context Parameters**: `{ providers: ProviderType[] }` the full combined providers array
108
+ - **Expected Return**: `string | void` used for sort ordering
109
+ - **Use Case Example**: Validating that required providers are present and throwing a descriptive error if not.
110
+ - **File Reference**: `src/Root.jsx:181`, `src/utils.js:20`
130
111
 
131
112
  #### `root.postProcessProviders`
132
- - **Type**: `(context: { providers: ProviderType[] }) => void`
133
- - **Purpose**: Final opportunity to modify providers before rendering
134
- - **Use Case**: Last-minute adjustments or validations
135
- - **Invocation Order**: 4th (final) - called by `extendArray()` in `src/Root.js:181`
136
- - **Note**: After this, providers are sorted by `order` and rendered
137
- - **File Reference**: `src/utils.js:21`, `src/Root.js:181`
138
113
 
139
- #### `root.renderChildren`
140
- - **Type**: `(children: ReactNode) => ReactNode`
141
- - **Purpose**: Wrap the root element with custom components
142
- - **Use Case**: Alternative to `getProviders` for wrapping the app (less recommended)
143
- - **Collection**: All contributions applied sequentially in `src/Root.js:105-110`
144
- - **Note**: Prefer `getProviders` for adding providers; this is for non-provider wrappers
145
- - **File Reference**: `src/Root.js:104-111`
114
+ - **Purpose**: Final hook to inspect or modify the providers array after all processing is complete.
115
+ - **When Invoked**: During `Root` render, via `extendArray` as the last step.
116
+ - **Context Parameters**: `{ providers: ProviderType[] }` the finalized providers array
117
+ - **Expected Return**: `void`
118
+ - **Use Case Example**: Logging the final provider stack in development for debugging.
119
+ - **File Reference**: `src/Root.jsx:181`, `src/utils.js:21`
146
120
 
147
- ### 2.4 Layout & UI
121
+ #### `route`
148
122
 
149
- Extension points for customizing the application's visual structure:
123
+ - **Purpose**: Register one or more route definitions into the application's route tree.
124
+ - **When Invoked**: On every render of the `Root` component, when `routeConfig()` rebuilds the route tree.
125
+ - **Context Parameters**: none
126
+ - **Expected Return**: `MuseRoute | MuseRoute[]` — one or more route objects. See `MuseRoute` in `src/muse.d.ts:89`.
127
+ - **Use Case Example**: Adding `/settings`, `/reports`, or any feature page routes.
128
+ - **File Reference**: `src/common/routeConfig.jsx:87`
150
129
 
151
130
  #### `home.homepage`
152
- - **Type**: `ComponentType<Object>`
153
- - **Purpose**: Provide a custom homepage component for the root route `/`
154
- - **Use Case**: Replace default "Welcome to Muse" page with application-specific homepage
155
- - **Resolution**: Single contribution used; if multiple, checks `window.MUSE_GLOBAL.homepage` for preference
156
- - **Collection**: Via `plugin.getPlugins('home.homepage')` in `src/common/routeConfig.js:96-111`
157
- - **Default**: If not provided, uses built-in `Homepage` component (`src/features/home/Homepage.js`)
158
- - **File Reference**: `src/common/routeConfig.js:94-116`
131
+
132
+ - **Purpose**: Replace the default homepage component rendered at the root path `/`.
133
+ - **When Invoked**: During route assembly in `routeConfig()`.
134
+ - **Context Parameters**: none
135
+ - **Expected Return**: `ComponentType` the React component to render at `/`. Only one plugin should contribute this.
136
+ - **Use Case Example**: A dashboard plugin providing a `DashboardHomepage` component.
137
+ - **File Reference**: `src/common/routeConfig.jsx:96`
159
138
 
160
139
  #### `home.mainLayout`
161
- - **Type**: `ComponentType<{ children: ReactNode }>`
162
- - **Purpose**: Provide a main layout component that wraps all routes
163
- - **Use Case**: Define application shell (header, sidebar, footer) that persists across pages
164
- - **Collection**: Via `plugin.invoke('!home.mainLayout')` in `src/features/home/App.js:10`
165
- - **Constraint**: Only one layout allowed; multiple contributions will show error message
166
- - **Note**: When using custom layout, undeploy `@ebay/muse-layout-antd` if present
167
- - **File Reference**: `src/features/home/App.js:9-22`
168
140
 
169
- #### `rootComponent`
170
- - **Type**: `ComponentType<Object>`
171
- - **Purpose**: Render initialization components at app root (should return `null`, no UI)
172
- - **Use Case**: Global initialization logic that needs component lifecycle (e.g., modal managers, global listeners)
173
- - **Collection**: All contributions rendered in `src/features/home/App.js:26-29`
174
- - **Important**: Components should not render visible UI, only invisible initialization/context
175
- - **File Reference**: `src/features/home/App.js:6-30`
141
+ - **Purpose**: Provide the main layout shell component that wraps all page content. Only one plugin should contribute this — multiple contributions render an error message.
142
+ - **When Invoked**: On every render of the `App` shell component.
143
+ - **Context Parameters**: none
144
+ - **Expected Return**: `ComponentType` a layout component that accepts `children`. Only the first contribution is used.
145
+ - **Use Case Example**: `muse-layout-antd` provides a sidebar/header layout via this extension point.
146
+ - **File Reference**: `src/features/home/App.jsx:10`
176
147
 
177
- ### 2.5 State Management
148
+ #### `rootComponent`
178
149
 
179
- Extension points for extending the Redux store:
150
+ - **Purpose**: Mount an invisible initialization React component into the app root. The component should return `null` — it exists only for side effects (subscriptions, global listeners, etc.).
151
+ - **When Invoked**: On every render of the `App` shell component.
152
+ - **Context Parameters**: none
153
+ - **Expected Return**: `ComponentType` — a component that renders `null`. All contributing plugins' components are rendered simultaneously.
154
+ - **Use Case Example**: A plugin that listens for global WebSocket messages and dispatches Redux actions.
155
+ - **File Reference**: `src/features/home/App.jsx:7`
180
156
 
181
157
  #### `reducer`
182
- - **Type**: `Reducer<any, AnyAction>`
183
- - **Purpose**: Contribute a plugin-level reducer to the Redux store
184
- - **Use Case**: Add plugin-specific state management to Redux
185
- - **Storage Key**: Auto-generated as `camelCase('plugin-' + pluginName)`
186
- - **Example**: Plugin `@ebay/my-plugin` → state key `pluginEbayMyPlugin`
187
- - **Collection**: Via `plugin.getPlugins('reducer')` in `src/common/rootReducer.js:24-31`
188
- - **File Reference**: `src/common/rootReducer.js:23-31`
189
-
190
- #### `reducers`
191
- - **Type**: `Record<string, Reducer<any, AnyAction>>`
192
- - **Purpose**: Contribute root-level reducers with custom state keys
193
- - **Use Case**: Add multiple reducers or control exact state key names
194
- - **Example**: `{ myData: myDataReducer, config: configReducer }` → `state.myData`, `state.config`
195
- - **Collection**: Via `plugin.getPlugins('reducers')` in `src/common/rootReducer.js:33-37`
196
- - **Note**: Provides more control than `reducer` extension point
197
- - **File Reference**: `src/common/rootReducer.js:33-39`
198
-
199
- ---
200
-
201
- ## 3. Extension Points Contributed
202
158
 
203
- This plugin does not contribute to extension points from other plugins. As a foundational lib plugin, it defines the extension point architecture but does not extend external plugins.
159
+ - **Purpose**: Contribute a plugin-scoped Redux reducer. The reducer is automatically mounted under the key `plugin-<pluginName>` (camelCased) in the store.
160
+ - **When Invoked**: When the Redux store is created (once at startup).
161
+ - **Context Parameters**: none
162
+ - **Expected Return**: `Reducer<any, AnyAction>` — a Redux reducer function
163
+ - **Use Case Example**: A plugin contributing its own Redux slice under `pluginMyPlugin` in the store.
164
+ - **File Reference**: `src/common/rootReducer.js:24`
204
165
 
205
- ---
166
+ #### `reducers`
206
167
 
207
- ## 4. Exported Functionality
168
+ - **Purpose**: Contribute one or more root-level Redux reducers with custom store branch keys (unlike `reducer` which auto-generates the key).
169
+ - **When Invoked**: When the Redux store is created (once at startup).
170
+ - **Context Parameters**: none
171
+ - **Expected Return**: `Record<string, Reducer<any, AnyAction>>` — a map of store key → reducer
172
+ - **Use Case Example**: A plugin that needs its state at `store.auth` or `store.featureFlags` instead of an auto-generated key.
173
+ - **File Reference**: `src/common/rootReducer.js:33`
208
174
 
209
- As a lib-type plugin, `@ebay/muse-lib-react` exports shared modules and utilities for use by other plugins.
175
+ ### Usage Example
210
176
 
211
- ### 4.1 Shared Modules (Module Federation)
177
+ ```javascript
178
+ // Extension points use nested object properties — NOT string paths
179
+ plugin.register({
180
+ name: 'my-plugin',
212
181
 
213
- These modules are exported via the default export in `src/index.js:41` and made available to other plugins through MUSE's module sharing system (webpack/vite plugin externalization):
182
+ // Lifecycle hooks
183
+ onReady: () => {
184
+ console.log('App is ready');
185
+ },
214
186
 
215
- #### `Loadable`
216
- - **Source**: `react-loadable` package
217
- - **Purpose**: Code splitting and lazy loading of React components
218
- - **Use Case**: Improve bundle size and load time by dynamically importing components
219
- - **File Reference**: `src/index.js:7,41`
187
+ // Route contribution
188
+ route: [
189
+ { path: 'settings', component: SettingsPage },
190
+ { path: 'reports', component: ReportsPage },
191
+ ],
220
192
 
221
- #### `_` (lodash)
222
- - **Source**: `lodash` package
223
- - **Purpose**: Utility functions for arrays, objects, strings, etc.
224
- - **Use Case**: Common data manipulation without importing lodash separately
225
- - **File Reference**: `src/index.js:8,41`
193
+ // Redux reducer
194
+ reducer: myPluginReducer,
226
195
 
227
- #### `reactUse`
228
- - **Source**: `react-use` package
229
- - **Purpose**: Collection of essential React hooks
230
- - **Use Case**: Access common hooks (useMount, useToggle, etc.) without separate package
231
- - **File Reference**: `src/index.js:9,41`
196
+ // Provider injection
197
+ root: {
198
+ getProviders: ({ providers }) => ({
199
+ order: 25,
200
+ key: 'my-context',
201
+ provider: MyContextProvider,
202
+ props: { value: myContextValue },
203
+ }),
204
+ beforeRender: () => {
205
+ initAnalytics();
206
+ },
207
+ },
232
208
 
233
- #### `reactRouterDom`
234
- - **Source**: `react-router-dom` v6
235
- - **Purpose**: React Router v6 routing library
236
- - **Use Case**: Navigation components (`Link`, `Navigate`) and hooks (`useNavigate`, `useParams`)
237
- - **File Reference**: `src/index.js:11,41`
209
+ // Layout/homepage
210
+ home: {
211
+ homepage: MyHomepageComponent,
212
+ mainLayout: MyLayoutComponent, // WARNING: only one plugin should provide this
213
+ },
238
214
 
239
- #### `reactQuery`
240
- - **Source**: `@tanstack/react-query` v4
241
- - **Purpose**: Server state management (data fetching, caching, synchronization)
242
- - **Use Case**: Manage API calls and server state with hooks (`useQuery`, `useMutation`)
243
- - **File Reference**: `src/index.js:12,41`
215
+ // Invisible init component
216
+ rootComponent: MyGlobalListenerComponent,
217
+ });
218
+ ```
244
219
 
245
- ### 4.2 Utility Components
220
+ ---
246
221
 
247
- These components can be imported from the plugin's `build/` directory by other plugins:
222
+ ## 3. Extension Points Contributed
248
223
 
249
- #### `Nodes`
250
- - **Source**: `src/features/common/Nodes.js`
251
- - **Type**: `ComponentType<{ items: any[], extName: string, extBase: string, extArgs: any }>`
252
- - **Purpose**: Render a list of components with extension point integration
253
- - **Use Case**: Create extendable UI component lists (e.g., toolbar buttons, menu items)
254
- - **Behavior**: Uses `extendArray()` to allow plugins to contribute additional nodes
255
- - **Exported**: `src/features/common/index.js:3`
256
- - **File Reference**: `src/features/common/Nodes.js:1-21`
257
-
258
- #### `useExtPoint`
259
- - **Source**: `src/features/common/useExtPoint.js`
260
- - **Type**: `(extPointName: string, extArgs?: any) => { extNode: ReactNode, values: any[] }`
261
- - **Purpose**: React hook for consuming extension points within components
262
- - **Use Case**: Render extension point contributions as React elements and collect callback values
263
- - **Returns**:
264
- - `extNode`: React fragment containing all extension point components
265
- - `values`: Array of values passed to callbacks
266
- - **Exported**: `src/features/common/index.js:4`
267
- - **File Reference**: `src/features/common/useExtPoint.js:1-29`
268
-
269
- #### `ErrorBoundary`
270
- - **Source**: `src/features/common/ErrorBoundary.js`
271
- - **Type**: `ComponentType<{ children: ReactNode }>`
272
- - **Purpose**: React error boundary component
273
- - **Use Case**: Catch and handle React errors gracefully
274
- - **Exported**: `src/features/common/index.js:2`
275
-
276
- #### `PageNotFound`
277
- - **Source**: `src/features/common/PageNotFound.js`
278
- - **Type**: `ComponentType<Object>`
279
- - **Purpose**: Default 404 page component
280
- - **Use Case**: Fallback for unmatched routes
281
- - **Exported**: `src/features/common/index.js:1`
282
-
283
- ### 4.3 Sub-App Utilities
284
-
285
- Components and utilities for the sub-application feature:
286
-
287
- #### `SubAppContainer`
288
- - **Source**: `src/features/sub-app/SubAppContainer.js`
289
- - **Type**: `ComponentType<{ subApp: SubAppConfig, subApps: SubAppConfig[] }>`
290
- - **Purpose**: Renders an external MUSE app in an iframe
291
- - **Use Case**: Embed other MUSE applications within the main app
292
- - **Exported**: `src/features/sub-app/index.js:1`
293
-
294
- #### `FixedSubAppContainer`
295
- - **Source**: `src/features/sub-app/FixedSubAppContainer.js`
296
- - **Type**: `ComponentType<Object>`
297
- - **Purpose**: Fixed-position sub-app container
298
- - **Use Case**: Persistent sub-app that doesn't follow routing
299
- - **Exported**: `src/features/sub-app/index.js:2`
300
-
301
- #### `SubAppContext`
302
- - **Source**: `src/features/sub-app/SubAppContext.js`
303
- - **Type**: `React.Context<any>`
304
- - **Purpose**: React context for parent-child sub-app communication
305
- - **Use Case**: Share data between parent app and embedded sub-apps
306
- - **Exported**: `src/features/sub-app/index.js:4`
307
-
308
- #### `LoadingSkeleton`
309
- - **Source**: `src/features/sub-app/LoadingSkeleton.js`
310
- - **Type**: `ComponentType<Object>`
311
- - **Purpose**: Loading placeholder for sub-apps
312
- - **Use Case**: Display loading state while sub-app loads
313
- - **Exported**: `src/features/sub-app/index.js:3`
314
-
315
- #### `C2SProxyFailed`
316
- - **Source**: `src/features/sub-app/C2SProxyFailed.js`
317
- - **Type**: `ComponentType<Object>`
318
- - **Purpose**: Error component for sub-app proxy failures
319
- - **Use Case**: Display when C2S proxy configuration fails
320
- - **Exported**: `src/features/sub-app/index.js:5`
321
-
322
- #### `useSetSubAppState`
323
- - **Source**: `src/features/sub-app/redux/setSubAppState.js`
324
- - **Type**: `() => (state: any) => void`
325
- - **Purpose**: Hook for setting sub-app state from Redux
326
- - **Use Case**: Update sub-app context from parent app
327
- - **Exported**: `src/features/sub-app/redux/hooks.js:1`
328
-
329
- ### 4.4 Common Utilities
330
-
331
- #### `extendArray`
332
- - **Source**: `src/utils.js:15-24`
333
- - **Type**: `(arr: any[], extName: string, extBase: string, ...args: any[]) => any[]`
334
- - **Purpose**: Makes an array extensible via js-plugin extension points
335
- - **Use Case**: Internal utility for provider/route extension, can be used by other plugins for custom extension points
336
- - **Behavior**: Invokes `preProcess`, `get`, `process`, `postProcess` extension points, flattens contributions, sorts by order
337
- - **Example**:
338
- ```javascript
339
- const items = [];
340
- extendArray(items, 'items', 'myPlugin.customExt', { context: 'data' });
341
- // Invokes: myPlugin.customExt.preProcessItems
342
- // myPlugin.customExt.getItems
343
- // myPlugin.customExt.processItems
344
- // myPlugin.customExt.postProcessItems
345
- ```
346
- - **Exported**: `src/utils.js:26`
347
- - **File Reference**: `src/utils.js:15-24`
348
-
349
- ### 4.5 Redux Store Access
350
-
351
- #### `store`
352
- - **Source**: `src/common/store.js`
353
- - **Type**: Redux Store wrapper
354
- - **Purpose**: Access to the global Redux store
355
- - **Use Case**: Dispatch actions or access state outside React components
356
- - **Methods**:
357
- - `getStore()`: Returns the Redux store instance
358
- - `getState()`: Returns current state
359
- - `dispatch(action)`: Dispatches an action
360
- - `subscribe(listener)`: Subscribes to store changes
361
- - `replaceReducer(reducer)`: Hot-swaps reducers
362
- - **File Reference**: `src/common/store.js:1-21`
363
-
364
- #### `history`
365
- - **Source**: `src/common/history.js`
366
- - **Type**: History object (from `history` package)
367
- - **Purpose**: Programmatic navigation outside React components
368
- - **Use Case**: Navigate to routes from Redux actions or non-React code
369
- - **File Reference**: `src/common/history.js`
224
+ This plugin does not extend other plugins via extension points. It is the foundational bootstrap plugin — it only **exposes** extension points for others to implement.
370
225
 
371
226
  ---
372
227
 
373
- ## 5. Integration Examples
228
+ ## 4. Exported Functionality
374
229
 
375
- **CRITICAL**: Extension points are **nested object properties**, NOT string paths!
230
+ This plugin exports the following functionality for use by other plugins.
376
231
 
377
- ### Example 1: Adding a Custom Route
232
+ **Access via**: Shared modules are available automatically in normal plugins via the MUSE build system. Direct exports are accessible via `plugin.getPlugin('@ebay/muse-lib-react').exports` or direct source imports.
378
233
 
379
- ```javascript
380
- // ✅ CORRECT - nested object properties
381
- plugin.register({
382
- name: 'my-plugin',
383
- route: {
384
- path: '/my-page',
385
- component: MyPageComponent,
386
- childRoutes: [
387
- {
388
- path: 'details/:id',
389
- component: DetailsComponent
390
- }
391
- ]
392
- }
393
- });
234
+ ### Shared Modules (Module Federation)
394
235
 
395
- // INCORRECT - DO NOT use string paths
396
- plugin.register({
397
- name: 'my-plugin',
398
- 'route': { ... } // Works, but 'route' doesn't need quotes
399
- });
400
- ```
236
+ As a `lib` plugin, `@ebay/muse-lib-react` makes the following libraries available as shared singletons to all normal plugins at runtime. Normal plugins must **not** bundle these themselves — they receive them from this plugin via the MUSE vite/webpack build plugin.
401
237
 
402
- ### Example 2: Contributing Multiple Routes
238
+ The shared module set is determined by the transitive imports of `src/index.jsx`. The following are explicitly re-exported:
403
239
 
404
- ```javascript
405
- plugin.register({
406
- name: 'my-plugin',
407
- route: [
408
- { path: '/page1', component: Page1 },
409
- { path: '/page2', component: Page2 },
410
- { path: '/page3', component: Page3, parent: 'some-route-id' }
411
- ]
412
- });
413
- ```
240
+ | Shared Module | Package | Version | Export Key |
241
+ |---|---|---|---|
242
+ | `Loadable` | `react-loadable` | 5.5.0 | `Loadable` |
243
+ | `_` (lodash) | `lodash` | 4.18.1 | `_` |
244
+ | `reactUse` | `react-use` | 17.6.0 | `reactUse` |
245
+ | `reactRouterDom` | `react-router-dom` | 7.15.0 | `reactRouterDom` |
246
+ | `reactQuery` | `@tanstack/react-query` | 5.100.9 | `reactQuery` |
414
247
 
415
- ### Example 3: Customizing Homepage
248
+ Additionally, all transitive dependencies (including `react`, `react-dom`, `redux`, `react-redux`, `@ebay/nice-modal-react`, `history`) are shared automatically by the MUSE build system.
416
249
 
417
- ```javascript
418
- import MyHomepage from './MyHomepage';
250
+ **File Reference**: `src/index.jsx:41`
419
251
 
420
- plugin.register({
421
- name: 'my-plugin',
422
- home: {
423
- homepage: MyHomepage
424
- }
425
- });
426
- ```
252
+ ### Utilities
427
253
 
428
- ### Example 4: Adding Custom Layout
254
+ #### `extendArray(arr, extName, extBase, ...args)`
429
255
 
430
- ```javascript
431
- import MyLayout from './MyLayout';
256
+ Makes any array extensible by js-plugin extension points. Invokes four lifecycle hooks in order — `preProcess<Name>`, `get<Name>`, `process<Name>`, `postProcess<Name>` — then sorts the array by `order`.
432
257
 
433
- plugin.register({
434
- name: 'my-plugin',
435
- home: {
436
- mainLayout: MyLayout // Should accept children prop
437
- }
438
- });
439
- ```
258
+ - **Parameters**: `arr` (mutable array), `extName` (string, capitalized to form hook names), `extBase` (string prefix for hook names), `...args` (passed through to each hook invocation)
259
+ - **Returns**: the mutated `arr`
260
+ - **File Reference**: `src/utils.js:15`
440
261
 
441
- ### Example 5: Adding Redux Reducer (Plugin-Level)
262
+ ### React Hooks
442
263
 
443
- ```javascript
444
- import myReducer from './redux/reducer';
264
+ #### `useExtPoint(extPointName, extArgs)`
445
265
 
446
- plugin.register({
447
- name: '@ebay/my-plugin',
448
- reducer: myReducer
449
- });
266
+ A React hook that invokes any extension point and renders all contributed components into a JSX fragment. Returns `{ extNode, values }` where `extNode` is the rendered fragment of all contributions and `values` is state updated when a contribution calls its `callback` prop.
450
267
 
451
- // Redux state will be accessible at:
452
- // state.pluginEbayMyPlugin
453
- ```
268
+ - **Parameters**: `extPointName` (string), `extArgs` (object passed as props to each contributed component)
269
+ - **Returns**: `{ extNode: ReactNode, values: any[] }`
270
+ - **File Reference**: `src/features/common/useExtPoint.jsx:7`
454
271
 
455
- ### Example 6: Adding Redux Reducers (Root-Level)
272
+ ### React Components
456
273
 
457
- ```javascript
458
- import userReducer from './redux/userReducer';
459
- import configReducer from './redux/configReducer';
274
+ #### `Nodes`
460
275
 
461
- plugin.register({
462
- name: 'my-plugin',
463
- reducers: {
464
- user: userReducer,
465
- appConfig: <USER_NAME>
466
- }
467
- });
276
+ Renders an extensible list of nodes where each node can be a render function, raw content, or component. Internally uses `extendArray` to allow other plugins to inject items.
468
277
 
469
- // Redux state accessible at:
470
- // state.user
471
- // state.appConfig
472
- ```
278
+ - **Props**: `items` (array), `extName` (string), `extBase` (string), `extArgs` (object)
279
+ - **File Reference**: `src/features/common/Nodes.jsx:8`
473
280
 
474
- ### Example 7: Adding a Custom Provider
281
+ ### Using Shared Modules
475
282
 
476
- ```javascript
477
- import { ThemeProvider } from 'my-theme-library';
478
-
479
- plugin.register({
480
- name: 'my-plugin',
481
- root: {
482
- getProviders: () => ({
483
- order: 15, // Between React Query (10) and Redux (20)
484
- key: 'theme-provider',
485
- provider: ThemeProvider,
486
- props: { theme: myTheme }
487
- })
488
- }
489
- });
490
- ```
491
-
492
- ### Example 8: Modifying Existing Providers
283
+ In normal plugins built with `@ebay/muse-vite-plugin` or `@ebay/muse-webpack-plugin`, these modules are automatically externalized and resolved from `@ebay/muse-lib-react` at runtime:
493
284
 
494
285
  ```javascript
495
- plugin.register({
496
- name: 'my-plugin',
497
- root: {
498
- preProcessProviders: ({ providers }) => {
499
- // Remove Redux provider
500
- const reduxIndex = providers.findIndex(p => p.key === 'redux-provider');
501
- if (reduxIndex >= 0) {
502
- providers.splice(reduxIndex, 1);
503
- }
504
- }
505
- }
506
- });
286
+ // In a normal plugin — these are resolved from muse-lib-react at runtime,
287
+ // NOT bundled into the plugin's own bundle.
288
+ import { useNavigate, Link } from 'react-router-dom';
289
+ import { useQuery } from '@tanstack/react-query';
290
+ import _ from 'lodash';
507
291
  ```
508
292
 
509
- ### Example 9: Lifecycle Hooks
510
-
511
- ```javascript
512
- plugin.register({
513
- name: 'my-plugin',
514
- root: {
515
- beforeRender: () => {
516
- console.log('App is about to render');
517
- // Initialize analytics, set up listeners, etc.
518
- },
519
- afterRender: () => {
520
- console.log('App has rendered');
521
- // Post-render tasks
522
- }
523
- },
524
- onReady: () => {
525
- console.log('App is fully ready');
526
- // Final initialization
527
- }
528
- });
529
- ```
293
+ **Note**: Exports create tight coupling. Prefer extension points for loose coupling when possible.
530
294
 
531
- ### Example 10: Using Shared Modules
295
+ ---
532
296
 
533
- ```javascript
534
- // In your plugin code
535
- const { exports } = plugin.getPlugin('@ebay/muse-lib-react');
536
- const { _, reactRouterDom, reactQuery } = exports;
297
+ ## 5. Consumed Exports (Runtime Dependencies)
537
298
 
538
- // Use lodash
539
- const uniqueItems = _.uniq(items);
299
+ This plugin does not consume exports from other plugins. All inter-plugin collaboration is done through extension points (loose coupling).
540
300
 
541
- // Use React Router
542
- const { useNavigate, Link } = reactRouterDom;
301
+ ---
543
302
 
544
- // Use React Query
545
- const { useQuery, useMutation } = reactQuery;
546
- ```
303
+ ## 6. Integration Examples
547
304
 
548
- ### Example 11: Using Utility Components
305
+ ### Extending This Plugin
549
306
 
550
307
  ```javascript
551
- import { Nodes, useExtPoint } from '@ebay/muse-lib-react/build/features/common';
552
-
553
- // Using Nodes component
554
- function MyToolbar() {
555
- const items = [
556
- { key: 'btn1', component: Button1, props: { label: 'Click' } }
557
- ];
558
-
559
- return (
560
- <Nodes
561
- items={items}
562
- extName="buttons"
563
- extBase="myPlugin.toolbar"
564
- />
565
- );
566
- }
308
+ // Extension points use nested object properties — NOT string paths
309
+ plugin.register({
310
+ name: 'my-plugin',
567
311
 
568
- // Using useExtPoint hook
569
- function MyComponent() {
570
- const { extNode, values } = useExtPoint('myPlugin.customPoint', { data: 123 });
312
+ // Add application routes
313
+ route: [
314
+ { path: 'my-feature', component: MyFeaturePage },
315
+ ],
571
316
 
572
- return <div>{extNode}</div>;
573
- }
574
- ```
317
+ // Redux integration
318
+ reducer: myFeatureReducer,
575
319
 
576
- ### Example 12: Root Component for Initialization
320
+ // Root lifecycle
321
+ onReady: () => {
322
+ console.log('App mounted');
323
+ },
577
324
 
578
- ```javascript
579
- function MyInitComponent() {
580
- useEffect(() => {
581
- // Initialize something globally
582
- window.myGlobalState = { ... };
583
- }, []);
325
+ // Provider injection
326
+ root: {
327
+ getProviders: ({ providers }) => ({
328
+ order: 25,
329
+ key: 'my-context',
330
+ provider: MyContextProvider,
331
+ props: { store: myStore },
332
+ }),
333
+ },
584
334
 
585
- return null; // IMPORTANT: No UI rendering
586
- }
335
+ // Layout/homepage
336
+ home: {
337
+ homepage: MyHomepageComponent,
338
+ mainLayout: MyLayoutComponent, // WARNING: only one plugin should provide this
339
+ },
587
340
 
588
- plugin.register({
589
- name: 'my-plugin',
590
- rootComponent: MyInitComponent
341
+ // Invisible init component
342
+ rootComponent: MyGlobalListenerComponent,
591
343
  });
592
344
  ```
593
345
 
594
- ### Example 13: Custom Router Props
346
+ ### Building Your Own Extensible Array
595
347
 
596
348
  ```javascript
597
- plugin.register({
598
- name: 'my-plugin',
599
- routerProps: {
600
- future: {
601
- v7_startTransition: true
602
- }
603
- }
604
- });
349
+ import { extendArray } from '@ebay/muse-lib-react/src/utils';
350
+
351
+ // In your component:
352
+ const menuItems = [...defaultItems];
353
+ extendArray(menuItems, 'menuItems', 'myPlugin', { context: someContext });
354
+ // Now other plugins can implement:
355
+ // myPlugin.preProcessMenuItems
356
+ // myPlugin.getMenuItems (return additional items)
357
+ // myPlugin.processMenuItems
358
+ // myPlugin.postProcessMenuItems
605
359
  ```
606
360
 
607
- ---
608
-
609
- ## Integration Checklist
361
+ ### Using the useExtPoint Hook
610
362
 
611
- When integrating with `@ebay/muse-lib-react`:
363
+ ```javascript
364
+ import useExtPoint from '@ebay/muse-lib-react/src/features/common/useExtPoint';
612
365
 
613
- - [ ] Ensure plugin type is compatible (normal or lib plugins can extend this)
614
- - [ ] Use nested object properties for extension points, not string paths
615
- - [ ] Only one plugin should provide `home.homepage` and `home.mainLayout`
616
- - [ ] Provider `order` values: lower = outer wrapper (10, 20, 30...)
617
- - [ ] Redux reducer keys via `reducer` are auto-generated; use `reducers` for custom keys
618
- - [ ] `rootComponent` must return `null` (no UI)
619
- - [ ] Routes with absolute paths (`/`) are moved to top level
620
- - [ ] Shared modules accessed via `plugin.getPlugin('@ebay/muse-lib-react').exports`
621
- - [ ] Sub-app configuration goes in `window.MUSE_GLOBAL.plugins` under this plugin's `subApps` array
366
+ function MyToolbar() {
367
+ const { extNode } = useExtPoint('myPlugin.toolbarItems', { context: 'main' });
368
+ return <div className="toolbar">{extNode}</div>;
369
+ }
370
+ ```