@collagejs/core 0.5.0 β 0.7.0
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 +17 -19
- package/dist/MountedPiece.d.ts +1 -0
- package/dist/MountedPiece.js +89 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/noopPiece.d.ts +12 -0
- package/dist/noopPiece.js +16 -0
- package/dist/types.d.ts +81 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Micro library for framework-agnostic micro-frontends
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**π§ I'M CATCHING UP FAST. WORK IN PROGRESS... ETA: JULY, 2026 π§**
|
|
6
6
|
|
|
7
7
|
**If you're interested, star β the repository to get updates on the progress in your GH homepage.**
|
|
8
8
|
|
|
@@ -26,6 +26,7 @@ type UnmountFn = () => Promise<void>;
|
|
|
26
26
|
interface CorePiece<TProps, TCap> {
|
|
27
27
|
mount: (target: HTMLElement | ShadowRoot, props?: TProps) => Promise<UnmountFn>;
|
|
28
28
|
update?: (props: TProps) => Promise<void>;
|
|
29
|
+
relocate?: (source: AcceptableTarget, target: AcceptableTarget) => Promise<'supported' | 'done' | 'unsupported'>
|
|
29
30
|
readonly capabilities?: CorePieceCapabilities & TCap;
|
|
30
31
|
};
|
|
31
32
|
```
|
|
@@ -35,9 +36,10 @@ In short: Micro-frontend creators must simply provide a way to generate an obje
|
|
|
35
36
|
|
|
36
37
|
- `mount` mounts the micro-frontend user interface in the document
|
|
37
38
|
- `update` updates the properties given to the micro-frontend
|
|
39
|
+
- `relocate` migrates all root DOM elements in the current target to the new target without a mounting cycle
|
|
38
40
|
- `capabilities` is a feature introduced in v0.5.0 that allows core piece objects to declare its capabilities (see more in its own section)
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
Only the `mount` function is required and must return a cleanup function that, ideally, reverts the mounting process.
|
|
41
43
|
|
|
42
44
|
This object, once obtained by the consuming project/micro-frontend, is given to the `<Piece>` component. The implementation of this component is framework-specific. For example, the `<Piece>` component found in the `@collagejs/svelte` package is a **Svelte** component.
|
|
43
45
|
|
|
@@ -78,7 +80,8 @@ export function buildTestPiece<TProps extends Record<string, any> = Record<strin
|
|
|
78
80
|
callbacks?.update?.(props);
|
|
79
81
|
pre.textContent = JSON.stringify(props, null, 2);
|
|
80
82
|
return Promise.resolve();
|
|
81
|
-
}
|
|
83
|
+
},
|
|
84
|
+
relocate: () => Promise.resolve('supported'),
|
|
82
85
|
};
|
|
83
86
|
}
|
|
84
87
|
```
|
|
@@ -90,11 +93,10 @@ The key learnings here are:
|
|
|
90
93
|
2. Inside `mount()`, we do whatever we need to do to mount our user interface inside the target element.
|
|
91
94
|
3. We return a cleanup function that unmounts the user interface.
|
|
92
95
|
4. We optionally provide the `update()` method for property reactivity.
|
|
96
|
+
5. We optionally provide the `relocate()` method for runtime flexibility. Fun fact: *CollageJS* can mount in shadow DOM, and reactively changing this setting triggers relocation.
|
|
93
97
|
|
|
94
98
|
You can export from a single project as many of these factory functions as desired. You are not constrained to expose just one *CollageJS* piece per project. Export as many as needed/wanted.
|
|
95
99
|
|
|
96
|
-
This is how packages like `@collagejs/svelte` work: When mounting, it calls Svelte's `mount()` function with the given options (if any are given), and returns a cleanup function that calls `unmount()` on the component. Updating properties is as simple as using a reactive object, at which point Svelte itself takes over the reacting part.
|
|
97
|
-
|
|
98
100
|
## Router Needs
|
|
99
101
|
|
|
100
102
|
As you may (or may not) know, `single-spa` is a client-side router that defines a contract similar to `CorePiece` above. *CollageJS*, on the other hand, doesn't provide a router. Use whichever client-side router you wish in your micro-frontend projects.
|
|
@@ -108,7 +110,7 @@ There are 2 reasons:
|
|
|
108
110
|
|
|
109
111
|
Yes, it is very handy to mount/dismount MFE's as the location URL changes. This makes routing a popular choice, but not a mandatory one. MFE's can come and go for any reason, like button clicks, timers or anything a developer can imagine.
|
|
110
112
|
|
|
111
|
-
Maintenance is an issue. The `single-spa` core team has had hard times trying to accommodate everyone's needs, especially when dealing with clashing behavior between their router and some framework's router, because people create MFE's with their own router built-in. On top of this, `single-spa`'s layout web component, which is a "define your routes in markup" helper, lacks features that people want because they exist in other router engines.
|
|
113
|
+
Maintenance is an issue. The `single-spa` core team has had hard times trying to accommodate everyone's needs, especially when dealing with clashing behavior between their router and some framework's router, because people create MFE's with their own router built-in. On top of this, `single-spa`'s layout web component, which is a "define your routes in markup" helper (web component), lacks features that people want because they exist in other router engines.
|
|
112
114
|
|
|
113
115
|
So, the conclusion here is: *CollageJS* takes care of micro-frontends. Just that.
|
|
114
116
|
|
|
@@ -132,19 +134,19 @@ You can easily create a new *CollageJS* root project by telling GitHub to create
|
|
|
132
134
|
- How routers and routes are laid out in markup
|
|
133
135
|
- How fallback content works
|
|
134
136
|
|
|
135
|
-
> πͺ€**The Catch**: It is a Svelte project. Ideally, you should know [Svelte](https://svelte.dev) to take full advantage of it. With `single-spa`, you would have to learn how it worked, and how its layout web component worked. With this one, you should learn at least a little Svelte. Some learning curve on both sides. The good thing about this one is that Svelte knowledge is much more far-reaching. Svelte is very easy and fun. We promise.
|
|
137
|
+
> πͺ€**The Catch**: It is a Svelte project. Ideally, you should know [Svelte](https://svelte.dev) to take full advantage of it. With `single-spa`, you would have to learn how it worked, and how its layout web component worked. With this one, you should learn at least a little Svelte. Some learning curve exists on both sides. The good thing about this one is that Svelte knowledge is much more far-reaching. Svelte is very easy and fun. We promise.
|
|
136
138
|
|
|
137
139
|
## For the `single-spa` Savvy
|
|
138
140
|
|
|
139
|
-
If you know/are used to `single-spa`, you're almost up to speed with *CollageJS*. Basically, there's no concept of a "root config" project, but of course, there's always a "root" project. Use
|
|
141
|
+
If you know/are used to `single-spa`, you're almost up to speed with *CollageJS*. Basically, there's no concept of a "root config" project, but of course, there's always a "root" project. Use whichever framework/technology you want to produce it.
|
|
140
142
|
|
|
141
|
-
Then the micro-frontends: The concept doesn't exist. At this point (after creating a root UI project), you
|
|
143
|
+
Then the micro-frontends: The concept doesn't exist. At this point (after creating a root UI project), you just mount *CollageJS pieces* which are basically the equivalent of `single-spa` parcels. You don't get a router provided, you bring your own, or don't bring a router. No worries. Who says that a router is required? Not us. You can trigger "parcel" loading by any means at your disposal: Button clicks, timers, window events, and yes, also location URL changes (routing) if you want.
|
|
142
144
|
|
|
143
145
|
### Technical Differences
|
|
144
146
|
|
|
145
147
|
While `single-spa` asks you to shape your module exports in a particular way (the lifecycle functions), *CollageJS* imposes no such restriction. It is just not necessary. Just make sure you can get an object of type `CorePiece` to the `<Piece>` component of your preferred framework. Then use your framework's marvels to make the `<Piece>` component appear or disappear.
|
|
146
148
|
|
|
147
|
-
Yes, you'll still be working with import maps. They are super handy. We provide an enhanced (and simplified at the same time) version of `import-map-overrides` named `@collagejs/imo`. It only supports the `overridable-importmap` type (and therefore only native import maps for native ES modules), but carries support for our `@collagejs/vite-aim` plug-in that lets you statically import from micro-frontends. **That's right! We are free from dynamic `import()` calls!** We can statically import from micro-frontends. Furthermore, it has a more modern user interface:
|
|
149
|
+
Yes, you'll still be working with import maps. They are super handy. We provide an enhanced (and simplified at the same time) version of `import-map-overrides` named `@collagejs/imo`. It only supports the `overridable-importmap` type (and therefore only native import maps for native ES modules), but carries support for our `@collagejs/vite-aim` plug-in that lets you statically import from micro-frontends and auto-externalizes anything in the import map. **That's right! We are free from dynamic `import()` calls!** We can statically import from micro-frontends. Furthermore, it has a more modern user interface:
|
|
148
150
|
|
|
149
151
|

|
|
150
152
|
|
|
@@ -160,22 +162,18 @@ Gone. There's no equivalent in *CollageJS*, as experience with `single-spa` has
|
|
|
160
162
|
|
|
161
163
|
## I'm Curious about `capabilities`
|
|
162
164
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
This sounds great, but what if that breaks the MFE? So the safe path is not to take advantage of cool features. This conclusion made me sad as a developer. Therefore, enter `capabilties`.
|
|
166
|
-
|
|
167
|
-
The `capabilities` object simply states what the core piece object is able to withstand. The core library defines 2 capabilities, and devs can add user-defined ones to the object.
|
|
165
|
+
The `capabilities` property is a place where core piece developers can declare what their pieces are capable of. The *CollageJS* core library defines a small set of these, but developers are free to define their own. These are useful for custom mounting implementations in large projects, where additional impositions can be placed on developed pieces. Because core pieces are closed in nature, this is a simple way to inform consumers about the piece's capabilities.
|
|
168
166
|
|
|
169
|
-
|
|
167
|
+
> π΄ If you're a C/C++ developer that knows COM/ActiveX, this is kind of the equivalent of `IUnknown::QueryInterface()`, where interfaces are layers of additional capabilities.
|
|
170
168
|
|
|
171
|
-
|
|
169
|
+
Yes, you may also extend the core piece object and work with interfaces in TypeScript. Your choice.
|
|
172
170
|
|
|
173
171
|
## Packages
|
|
174
172
|
|
|
175
173
|
| Package | Status | Links | Description |
|
|
176
174
|
| - | - | - | - |
|
|
177
175
|
| `@collagejs/core` | βοΈ | (This repo) | Core functionality. Provides the general mounting and unmounting logic. |
|
|
178
|
-
| `@collagejs/vite-css` | βοΈ | [Repo](https://github.com/collagejs/vite) | Vite plug-in that offers a CSS-mounting algorithm that is fully compatible with Vite's CSS bundling, including split CSS. It also features FOUC prevention. |
|
|
176
|
+
| `@collagejs/vite-css` | βοΈ | [Repo](https://github.com/collagejs/vite) | Vite plug-in that offers a CSS-mounting algorithm that is fully compatible with Vite's CSS bundling, including split CSS. It also features FOUC prevention, shadow root mounting and relocation. |
|
|
179
177
|
| `@collagejs/vite-im` | βοΈ | [Repo](https://github.com/collagejs/vite) | Vite plug-in that injects an import map and optionally the `@collagejs/imo` package to define bare module identifiers for easy micro-frontend loading and debugging. |
|
|
180
178
|
| `@collagejs/vite-aim` | βοΈ | [Repo](https://github.com/collagejs/vite) | Vite-plugin that auto-externalizes the module identifiers found in the application's import map. It receives the import map live (and with overrides) from the client. This enables static imports (no more dynamic `import()` calls). |
|
|
181
179
|
| `@collagejs/imo` | βοΈ | [Repo](https://github.com/collagejs/imo) | Our version of `import-map-overrides` that does the usual overriding of import map entries, plus it transmits the final import map to Vite development servers found in it. |
|
|
@@ -190,4 +188,4 @@ With this reassurance at hand, framework adapters like `@collagejs/svelte` and o
|
|
|
190
188
|
| Repository | Description |
|
|
191
189
|
| - | - |
|
|
192
190
|
| [Root Template](https://github.com/collagejs/root-template) | Root template repository that can be used to create new repositories for *CollageJS* root projects, with client-side routing already configured. |
|
|
193
|
-
| (
|
|
191
|
+
| [Gallery](https://github.com/collagejs/gallery) | Repository of *CollageJS* pieces made in various front-end technologies for your reference and inspiration. |
|
package/dist/MountedPiece.d.ts
CHANGED
|
@@ -7,5 +7,6 @@ export declare class MountedPiece<TProps extends Record<string, any> = Record<st
|
|
|
7
7
|
[mountKey](target: AcceptableTarget, props?: TProps): Promise<void>;
|
|
8
8
|
unmount(): Promise<void>;
|
|
9
9
|
update(props: TProps): Promise<void>;
|
|
10
|
+
relocate(target: AcceptableTarget, newTarget: AcceptableTarget, customRelocate?: (source: AcceptableTarget, target: AcceptableTarget) => Promise<boolean>): Promise<boolean>;
|
|
10
11
|
get capabilities(): (CorePieceCapabilities & TCap) | undefined;
|
|
11
12
|
}
|
package/dist/MountedPiece.js
CHANGED
|
@@ -30,6 +30,66 @@ async function doUpdate(update, props) {
|
|
|
30
30
|
}
|
|
31
31
|
return await update(props);
|
|
32
32
|
}
|
|
33
|
+
function relocationResultValue(result) {
|
|
34
|
+
if (Array.isArray(result)) {
|
|
35
|
+
return result[0];
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
async function doRelocate(relocate, target, newTarget) {
|
|
40
|
+
const rollbackFns = new Stack();
|
|
41
|
+
let safeState = true;
|
|
42
|
+
const doRelocateInternal = async (rel) => {
|
|
43
|
+
const maybePushRollback = (result) => {
|
|
44
|
+
if (Array.isArray(result) && (result[0] === 'done' || result[0] === 'supported')) {
|
|
45
|
+
rollbackFns.push(result[1]);
|
|
46
|
+
}
|
|
47
|
+
else if (result === 'done') {
|
|
48
|
+
safeState = false;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
if (Array.isArray(rel)) {
|
|
52
|
+
let supported = false;
|
|
53
|
+
for (const fn of rel) {
|
|
54
|
+
const r = await doRelocateInternal(fn);
|
|
55
|
+
const rValue = relocationResultValue(r);
|
|
56
|
+
if (rValue === 'supported') {
|
|
57
|
+
supported = true;
|
|
58
|
+
}
|
|
59
|
+
else if (rValue === 'unsupported') {
|
|
60
|
+
if (safeState) {
|
|
61
|
+
while (rollbackFns.size) {
|
|
62
|
+
await rollbackFns.pop()?.();
|
|
63
|
+
}
|
|
64
|
+
return 'unsupported';
|
|
65
|
+
}
|
|
66
|
+
throw new Error("Relocation function returned 'unsupported' after another relocation function returned 'done' without a rollback. The piece's state is now inconsistent.");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return supported ? 'supported' : 'done';
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const r = await rel(target, newTarget);
|
|
73
|
+
maybePushRollback(r);
|
|
74
|
+
return relocationResultValue(r);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (safeState) {
|
|
78
|
+
while (rollbackFns.size) {
|
|
79
|
+
await rollbackFns.pop()?.();
|
|
80
|
+
}
|
|
81
|
+
console.warn("Relocation function failed. Piece relocation has been rolled back.", error);
|
|
82
|
+
return 'unsupported';
|
|
83
|
+
}
|
|
84
|
+
throw new Error("Relocation function failed after another relocation function returned 'done' without a rollback. The piece's state is now inconsistent.");
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const internalResult = await doRelocateInternal(relocate);
|
|
88
|
+
if (rollbackFns.size > 0 && relocationResultValue(internalResult) === 'supported' && safeState) {
|
|
89
|
+
return rollbackFns;
|
|
90
|
+
}
|
|
91
|
+
return internalResult;
|
|
92
|
+
}
|
|
33
93
|
export class MountedPiece {
|
|
34
94
|
#piece;
|
|
35
95
|
#id;
|
|
@@ -68,6 +128,35 @@ export class MountedPiece {
|
|
|
68
128
|
update(props) {
|
|
69
129
|
return doUpdate(this.#piece.update, props);
|
|
70
130
|
}
|
|
131
|
+
async relocate(target, newTarget, customRelocate) {
|
|
132
|
+
if (!this.#piece.relocate) {
|
|
133
|
+
return Promise.resolve(false);
|
|
134
|
+
}
|
|
135
|
+
const result = await doRelocate(this.#piece.relocate, target, newTarget);
|
|
136
|
+
if (result === 'done') {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
if (result === 'unsupported') {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
// At this point, custom relocation must finish the job.
|
|
143
|
+
if (!customRelocate) {
|
|
144
|
+
throw new Error("Relocation of this piece is 'supported', but no custom relocation function was provided.");
|
|
145
|
+
}
|
|
146
|
+
if (result === 'supported') {
|
|
147
|
+
return await customRelocate(target, newTarget);
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
return await customRelocate(target, newTarget);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
while (result.size) {
|
|
154
|
+
await result.pop()?.();
|
|
155
|
+
}
|
|
156
|
+
console.warn("Custom relocation function failed. Piece relocation has been rolled back.", error);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
71
160
|
get capabilities() {
|
|
72
161
|
return this.#piece.capabilities;
|
|
73
162
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,3 +3,5 @@ export { mountPiece } from './mountPiece.js';
|
|
|
3
3
|
export { mountPieceKey } from './common.js';
|
|
4
4
|
export { ensureGlobalCollageJs } from './global.js';
|
|
5
5
|
export { preventRemount } from './preventRemount.js';
|
|
6
|
+
export { noopPiece } from './noopPiece.js';
|
|
7
|
+
export { Stack } from './Stack.js';
|
package/dist/index.js
CHANGED
|
@@ -2,3 +2,5 @@ export { mountPiece } from './mountPiece.js';
|
|
|
2
2
|
export { mountPieceKey } from './common.js';
|
|
3
3
|
export { ensureGlobalCollageJs } from './global.js';
|
|
4
4
|
export { preventRemount } from './preventRemount.js';
|
|
5
|
+
export { noopPiece } from './noopPiece.js';
|
|
6
|
+
export { Stack } from './Stack.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CorePiece } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Utility function that provides a no-op `CorePiece` object. This is useful for testing or when a placeholder piece is
|
|
4
|
+
* needed.
|
|
5
|
+
*
|
|
6
|
+
* **β οΈIMPORTANT**: The returned object is a singleton. This is **not** a factory function that creates a new object
|
|
7
|
+
* each time it is called. If you need a new object, you should create your own.
|
|
8
|
+
* @template TProps The type of the properties for the `CorePiece`. Defaults to `Record<string, any>`.
|
|
9
|
+
* @template TCap The type of the capabilities for the `CorePiece`. Defaults to an empty object `{}`.
|
|
10
|
+
* @returns {CorePiece<TProps, TCap>} A no-op `CorePiece` object properly typed.
|
|
11
|
+
*/
|
|
12
|
+
export declare function noopPiece<TProps extends Record<string, any> = Record<string, any>, TCap extends Record<string, any> = {}>(): CorePiece<TProps, TCap>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const np = {
|
|
2
|
+
mount: () => Promise.resolve(() => Promise.resolve()),
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Utility function that provides a no-op `CorePiece` object. This is useful for testing or when a placeholder piece is
|
|
6
|
+
* needed.
|
|
7
|
+
*
|
|
8
|
+
* **β οΈIMPORTANT**: The returned object is a singleton. This is **not** a factory function that creates a new object
|
|
9
|
+
* each time it is called. If you need a new object, you should create your own.
|
|
10
|
+
* @template TProps The type of the properties for the `CorePiece`. Defaults to `Record<string, any>`.
|
|
11
|
+
* @template TCap The type of the capabilities for the `CorePiece`. Defaults to an empty object `{}`.
|
|
12
|
+
* @returns {CorePiece<TProps, TCap>} A no-op `CorePiece` object properly typed.
|
|
13
|
+
*/
|
|
14
|
+
export function noopPiece() {
|
|
15
|
+
return np;
|
|
16
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -20,6 +20,29 @@ export type UnmountFn = () => Promise<void>;
|
|
|
20
20
|
* @returns A promise to the cleanup function that unmounts the piece.
|
|
21
21
|
*/
|
|
22
22
|
export type MountFn<TProps extends Record<string, any> = Record<string, any>> = (target: AcceptableTarget, props?: MountProps<TProps>) => Promise<UnmountFn>;
|
|
23
|
+
/**
|
|
24
|
+
* Supported return values of `CorePiece.relocate` functions.
|
|
25
|
+
*/
|
|
26
|
+
export type RelocationResultValue = 'supported' | 'unsupported' | 'done';
|
|
27
|
+
/**
|
|
28
|
+
* Defines the signature of rollback functions that can be returned by `CorePiece.relocate` functions. These functions
|
|
29
|
+
* can be provided when returning `done` or `supported` from a relocation function, and will be called if the
|
|
30
|
+
* relocation chain fails.
|
|
31
|
+
* @returns A promise that resolves once the rollback process concludes.
|
|
32
|
+
*/
|
|
33
|
+
export type RelocationRollbackFn = () => Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Defines the possible return values of `CorePiece.relocate`.
|
|
36
|
+
*/
|
|
37
|
+
export type RelocationResult = RelocationResultValue | [Exclude<RelocationResultValue, 'unsupported'>, RelocationRollbackFn];
|
|
38
|
+
/**
|
|
39
|
+
* Type that defines the signature of the functions accepted in `CorePiece.relocate`.
|
|
40
|
+
* @param parent The current parent of the piece's root element(s).
|
|
41
|
+
* @param newParent The new parent where the piece's root element(s) will be relocated.
|
|
42
|
+
* @returns A promise that resolves to one of the acceptable values. See `RelocationResult` and related types for
|
|
43
|
+
* details.
|
|
44
|
+
*/
|
|
45
|
+
export type RelocateFn = (parent: AcceptableTarget, newParent: AcceptableTarget) => Promise<RelocationResult>;
|
|
23
46
|
/**
|
|
24
47
|
* Type that defines the signature of the functions accepted in `CorePiece.update`.
|
|
25
48
|
* @param props The new property values for the mounted piece.
|
|
@@ -33,7 +56,11 @@ export type Mount<TProps extends Record<string, any> = Record<string, any>> = Mo
|
|
|
33
56
|
/**
|
|
34
57
|
* Defines the accepted shapes for `CorePiece.update`.
|
|
35
58
|
*/
|
|
36
|
-
export type Update<TProps extends Record<string, any> = Record<string, any>> = UpdateFn<TProps> | UpdateFn<TProps>[] | Update[];
|
|
59
|
+
export type Update<TProps extends Record<string, any> = Record<string, any>> = UpdateFn<TProps> | UpdateFn<TProps>[] | Update<TProps>[];
|
|
60
|
+
/**
|
|
61
|
+
* Defines the accepted shapes for `CorePiece.relocate`.
|
|
62
|
+
*/
|
|
63
|
+
export type Relocate = RelocateFn | RelocateFn[] | Relocate[];
|
|
37
64
|
/**
|
|
38
65
|
* Defines the capabilities of a `CorePiece` object recognized by the core *CollageJS* library. These capabilities are
|
|
39
66
|
* used to determine how the core library should handle the piece's lifecycle, or whether a particular action or
|
|
@@ -50,18 +77,6 @@ export type CorePieceCapabilities = {
|
|
|
50
77
|
* **π‘TIP**: Official framework adapters provide this functionality.
|
|
51
78
|
*/
|
|
52
79
|
remountable?: boolean;
|
|
53
|
-
/**
|
|
54
|
-
* Indicates that the piece allows relocation of its HTML markup to a new parent without unmounting. For the best
|
|
55
|
-
* development experience, piece objects should always strive to be relocatable.
|
|
56
|
-
*
|
|
57
|
-
* If `false`, `Piece` components created with official framework adapters will most likely have to ask HMR to
|
|
58
|
-
* perform a full page reload whenever the developer changes shadow DOM options, like moving from an open to a
|
|
59
|
-
* closed shadow root.
|
|
60
|
-
*
|
|
61
|
-
* If `true` **and if the framework is capable** (i. e. Svelte), the piece can be relocated without unmounting, and
|
|
62
|
-
* HMR will be able to update the shadow root options without a full page reload.
|
|
63
|
-
*/
|
|
64
|
-
relocatable?: boolean;
|
|
65
80
|
};
|
|
66
81
|
/**
|
|
67
82
|
* Defines the contract that objects must follow in order to be mountable as *CollageJS* pieces (micro-frontends).
|
|
@@ -77,14 +92,58 @@ export interface CorePiece<TProps extends Record<string, any> = Record<string, a
|
|
|
77
92
|
* while mounted in the document, and all property values must have been passed during mounting.
|
|
78
93
|
*/
|
|
79
94
|
update?: Update<TProps>;
|
|
95
|
+
/**
|
|
96
|
+
* Either relocates the root element(s) of the piece to a new parent, or prepares the piece for relocation. This
|
|
97
|
+
* is optional. If not provided, the piece won't support relocation of its root element(s) to a new parent, and
|
|
98
|
+
* the piece will be unmounted and remounted instead in pertinent cases.
|
|
99
|
+
*
|
|
100
|
+
* ### Return Values
|
|
101
|
+
*
|
|
102
|
+
* + `done`: The relocation succeeded and was made entirely by the piece.
|
|
103
|
+
* + `unsupported`: The piece does not support relocation.
|
|
104
|
+
* + `supported`: The piece supports relocation, but the relocation must be performed by the caller.
|
|
105
|
+
*
|
|
106
|
+
* Additionally, the values `done` and `supported` can be returned as a tuple with a rollback function that will be
|
|
107
|
+
* called if the relocation chain fails after this function has returned `done` or `supported`.
|
|
108
|
+
*
|
|
109
|
+
* ### Rollback Functions
|
|
110
|
+
*
|
|
111
|
+
* If a relocation function returns a rollback function, it will be called if any of the following occurs:
|
|
112
|
+
*
|
|
113
|
+
* + Another relocation function returns `unsupported` after this one returned `done` or `supported`.
|
|
114
|
+
* + Another relocation function throws an error after this one returned `done` or `supported`.
|
|
115
|
+
*
|
|
116
|
+
* ### When Using Multiple Relocation Functions
|
|
117
|
+
*
|
|
118
|
+
* If multiple relocation functions are provided, they will be called in order:
|
|
119
|
+
*
|
|
120
|
+
* + If the first of them returns `unsupported`, the relocation process will stop.
|
|
121
|
+
* + If all of them return `done`, the relocation is considered successful.
|
|
122
|
+
* + If any of them returns `'supported'`, the remaining functions are still called, but the caller will
|
|
123
|
+
* run its relocation process after all functions have been called, even if they return `'done'`.
|
|
124
|
+
* + If any of them returns `unsupported` after one or more of them returned `'supported'` or `done` without a
|
|
125
|
+
* rollback function, the piece's state is considered inconsistent and an error will be thrown.
|
|
126
|
+
*/
|
|
127
|
+
relocate?: Relocate;
|
|
80
128
|
/**
|
|
81
129
|
* Declares the capabilities of the piece. This is optional. If not provided, the piece will be assumed to have no
|
|
82
|
-
* capabilities
|
|
83
|
-
*
|
|
130
|
+
* capabilities.
|
|
131
|
+
*
|
|
132
|
+
* ### Notable Exception
|
|
133
|
+
*
|
|
134
|
+
* The library defines the `remountable` capability, which is informative only. The core library cannot enforce
|
|
135
|
+
* this capability, so `CorePiece` developers should use the `preventRemount()` function (or equivalent) to throw an
|
|
136
|
+
* error if the piece is mounted more than once for pieces that cannot withstand multiple mountings.
|
|
137
|
+
*
|
|
138
|
+
* Official framework adapters query the value of `capabilities.remountable` and act accordingly to their best
|
|
139
|
+
* ability, and at least emit a warning if a non-remountable piece is mounted more than once.
|
|
84
140
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
141
|
+
* Only the author of a `CorePiece` object can guarantee that the piece is remountable. We encourage developers to
|
|
142
|
+
* be explicit about this capability when creating `CorePiece` objects.
|
|
143
|
+
*
|
|
144
|
+
* > βΉοΈ **NOTE:** Official framework adapters are free to choose which default value for `capabilities.remountable`
|
|
145
|
+
* > they will use while creating `CorePiece` objects when the property is not provided in order to maximize
|
|
146
|
+
* > framework feature usage (perhaps a framework can guarantee component state automatically, or perhaps it cannot).
|
|
88
147
|
*/
|
|
89
148
|
readonly capabilities?: CorePieceCapabilities & TCap;
|
|
90
149
|
}
|
|
@@ -100,6 +159,10 @@ export interface MountedPiece<TProps extends Record<string, any> = Record<string
|
|
|
100
159
|
* Function used to unmount the `CorePiece` object.
|
|
101
160
|
*/
|
|
102
161
|
unmount: UnmountFn;
|
|
162
|
+
/**
|
|
163
|
+
* Function used to relocate the `CorePiece` object to a new parent without unmounting.
|
|
164
|
+
*/
|
|
165
|
+
relocate: (source: AcceptableTarget, target: AcceptableTarget, customRelocate?: (source: AcceptableTarget, target: AcceptableTarget) => Promise<boolean>) => Promise<boolean>;
|
|
103
166
|
/**
|
|
104
167
|
* The version of the global `mountPiece` function that tracks mounted children so their unmounting is
|
|
105
168
|
* synchronized with this piece's unmount event.
|