@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;
|
|
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
|
-
|
|
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.
|
|
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.
|
|
25
|
+
"@rmc-toolkit/core": "0.2.4",
|
|
21
26
|
"react": ">=18"
|
|
22
27
|
},
|
|
23
28
|
"dependencies": {
|
|
24
|
-
"@rmc-toolkit/core": "0.2.
|
|
29
|
+
"@rmc-toolkit/core": "0.2.4"
|
|
25
30
|
},
|
|
26
31
|
"scripts": {
|
|
27
32
|
"build": "tsc -b"
|