@rmc-toolkit/react 0.2.0 → 0.2.4

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 ADDED
@@ -0,0 +1,86 @@
1
+ # @rmc-toolkit/react
2
+
3
+ React adapter for [Runtime Module Composition](https://runtime-module-composition.dev). Two factories, for two different slice conventions — pick the one matching how your slices are written, not both for the same slice. Both take your app's own already-resolved `React` instance rather than importing one themselves, so this package never bundles a second, conflicting copy of React.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @rmc-toolkit/react @rmc-toolkit/core react
9
+ ```
10
+
11
+ ## Quick example
12
+
13
+ **Slices share the `mount()`/`unmount()` convention:** use `createReactAdapter`, which wraps the resolve/import/mount lifecycle in a `useRuntimeHost` hook:
14
+
15
+ ```ts
16
+ // src/rmc-adapter.ts
17
+ import React from "react";
18
+ import { createReactAdapter } from "@rmc-toolkit/react";
19
+
20
+ export const { useRuntimeHost } = createReactAdapter(React);
21
+ ```
22
+
23
+ ```tsx
24
+ // App.tsx
25
+ import { useLocation } from "react-router-dom";
26
+ import { useRuntimeHost } from "./rmc-adapter";
27
+ import { manifest } from "./runtime-composition.manifest";
28
+
29
+ function App() {
30
+ const location = useLocation();
31
+ const { ref, status } = useRuntimeHost<HTMLElement>(location.pathname, { manifest });
32
+
33
+ return (
34
+ <div className="app-shell">
35
+ <SiteHeader loading={status.type === "loading"} />
36
+ <main ref={ref} />
37
+ <SiteFooter />
38
+ </div>
39
+ );
40
+ }
41
+ ```
42
+
43
+ **Slices are plain default-exported components (not `mount()`/`unmount()`):** use `createDynamicModuleBoundary` instead:
44
+
45
+ ```tsx
46
+ // src/rmc-adapter.ts
47
+ import React from "react";
48
+ import { createDynamicModuleBoundary } from "@rmc-toolkit/react";
49
+
50
+ export const { DynamicModuleBoundary } = createDynamicModuleBoundary(React);
51
+ ```
52
+
53
+ ```tsx
54
+ // RouteSlot.tsx
55
+ import { resolveRoute } from "@rmc-toolkit/core";
56
+ import { DynamicModuleBoundary } from "./rmc-adapter";
57
+ import { manifest } from "./runtime-composition.manifest";
58
+
59
+ export function RouteSlot() {
60
+ const match = resolveRoute(manifest, window.location.pathname);
61
+ if (!match) return null;
62
+
63
+ return (
64
+ <DynamicModuleBoundary
65
+ specifier={match.specifier}
66
+ context={{ route: match, manifest }}
67
+ fallback={<div>Loading...</div>}
68
+ errorFallback={<div>Unable to load this section.</div>}
69
+ />
70
+ );
71
+ }
72
+ ```
73
+
74
+ ## What's in here
75
+
76
+ - `createReactAdapter(React)` → `{ useRuntimeHost }` — a hook wrapping `createRuntimeHostObservable`'s resolve/import/mount/unmount lifecycle, with loading/error status.
77
+ - `createDynamicModuleBoundary(React)` → `{ DynamicModuleBoundary }` — a `Suspense` + error-boundary component around `React.lazy()`-loading a slice's default-exported component.
78
+
79
+ Full signatures and behavior for both: [API Reference](https://runtime-module-composition.dev/api-reference/#react-adapter-rmc-toolkitreact).
80
+
81
+ ## Documentation
82
+
83
+ - [Getting Started](https://runtime-module-composition.dev/getting-started/) — install and wire up a host + slice end to end
84
+ - [API Reference](https://runtime-module-composition.dev/api-reference/#react-adapter-rmc-toolkitreact)
85
+ - [Technical Implementation](https://runtime-module-composition.dev/technical-implementation/) — the architecture and failure modes behind the pattern
86
+ - [Multi-Framework Demo](https://runtime-module-composition.dev/demo/) — a full, runnable reference implementation, including a React slice
@@ -1 +1 @@
1
- {"version":3,"file":"create-dynamic-module-boundary.d.ts","sourceRoot":"","sources":["../src/create-dynamic-module-boundary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,cAAc,MAAM,OAAO,CAAC;AAC7C,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,oBAAoB,EAC1B,MAAM,mBAAmB,CAAC;AAM3B,MAAM,MAAM,0BAA0B,GAAG;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,QAAQ,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC;IACpC,aAAa,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC;IACzC,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B,CAAC;AAYF,eAAO,MAAM,2BAA2B,GACtC,OAAO,OAAO,cAAc,KAC3B;IACD,qBAAqB,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,cAAc,CAAC,YAAY,CAAC;CAsE3F,CAAC"}
1
+ {"version":3,"file":"create-dynamic-module-boundary.d.ts","sourceRoot":"","sources":["../src/create-dynamic-module-boundary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,cAAc,MAAM,OAAO,CAAC;AAC7C,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,oBAAoB,EAC1B,MAAM,mBAAmB,CAAC;AAM3B,MAAM,MAAM,0BAA0B,GAAG;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,QAAQ,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC;IACpC,aAAa,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC;IACzC,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B,CAAC;AAYF,eAAO,MAAM,2BAA2B,GACtC,OAAO,OAAO,cAAc,KAC3B;IACD,qBAAqB,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,cAAc,CAAC,YAAY,CAAC;CAiF3F,CAAC"}
@@ -30,10 +30,13 @@ export const createDynamicModuleBoundary = (React) => {
30
30
  }
31
31
  }
32
32
  const DynamicModuleBoundary = ({ specifier, context, fallback = null, errorFallback = null, importer = defaultImporter, }) => {
33
- const LazyModule = React.lazy(async () => {
33
+ // Memoized by specifier/importer: React.lazy() must return the same
34
+ // component type across renders, or React treats it as a brand-new
35
+ // component and remounts (re-suspending) on every parent re-render.
36
+ const LazyModule = React.useMemo(() => React.lazy(async () => {
34
37
  const loadedModule = (await importer(specifier));
35
38
  return loadedModule;
36
- });
39
+ }), [specifier, importer]);
37
40
  const moduleProps = context === undefined
38
41
  ? {}
39
42
  : { context };
@@ -50,6 +53,10 @@ export const createDynamicModuleBoundary = (React) => {
50
53
  // required -- it is the same substitution JSX itself performs, not an
51
54
  // accidental extra prop.
52
55
  return React.createElement(React.Suspense, { fallback }, React.createElement(ErrorBoundary, {
56
+ // Keyed by specifier so switching modules mounts a fresh
57
+ // ErrorBoundary instance instead of reusing one still holding a
58
+ // previous module's error state.
59
+ key: specifier,
53
60
  fallback: errorFallback,
54
61
  children: React.createElement(LazyModule, moduleProps),
55
62
  }));
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@rmc-toolkit/react",
3
- "version": "0.2.0",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/runtime-module-composition/rmc-toolkit",
8
+ "directory": "packages/react"
9
+ },
5
10
  "main": "./dist/index.js",
6
11
  "types": "./dist/index.d.ts",
7
12
  "exports": {
@@ -17,11 +22,11 @@
17
22
  "access": "public"
18
23
  },
19
24
  "peerDependencies": {
20
- "@rmc-toolkit/core": "0.2.0",
25
+ "@rmc-toolkit/core": "0.2.4",
21
26
  "react": ">=18"
22
27
  },
23
28
  "dependencies": {
24
- "@rmc-toolkit/core": "0.2.0"
29
+ "@rmc-toolkit/core": "0.2.4"
25
30
  },
26
31
  "scripts": {
27
32
  "build": "tsc -b"