@furystack/shades-mfe 1.0.28 → 2.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.0.0] - 2026-02-09
4
+
5
+ ### ⬆️ Dependencies
6
+
7
+ - Updated peer dependency `@furystack/shades` to latest minor version
8
+ - Peer dependency on `@furystack/shades` bumped to new major version
9
+ - Updated `@furystack/shades` dependency
10
+
11
+ ### 💥 Breaking Changes
12
+
13
+ ### Requires `@furystack/shades` v3
14
+
15
+ This package now depends on the new major version of `@furystack/shades` which removed the `constructed` callback.
16
+
17
+ ### Migrated from `element` to `useRef` for Container Management
18
+
19
+ The `MicroFrontend` component no longer uses the `element` render option to access the host element. Instead, it uses `useRef` to create a container `<div>` inside the component, and the micro-frontend is mounted into that container.
20
+
21
+ **Impact:** The internal mounting behavior has changed — micro-frontends are now mounted inside a child `<div>` rather than directly into the host custom element. This should be transparent for most consumers, but any code that relies on the MFE being a direct child of the `shade-micro-frontend` element may need adjustment.
22
+
23
+ ### ♻️ Refactoring
24
+
25
+ - Migrated `MicroFrontend` component from async `constructed` callback to `useDisposable()` in `render`, using `[Symbol.asyncDispose]` for cleanup
26
+ - Replaced `element` parameter with `useRef('mfeContainer')` and a container `<div>` for MFE mounting
27
+ - MFE loader now defers initialization via `queueMicrotask` to ensure the ref is available after the first render
28
+ - Added prop propagation to inner MFE elements for Shade-based micro-frontends
29
+
30
+ ### 🧪 Tests
31
+
32
+ - Updated micro-frontend tests to work with microtask-based rendering
33
+ - Refactored `createShadesMicroFrontend` and `MicroFrontend` tests to use `usingAsync` for proper `Injector` disposal
34
+ - Updated tests to accommodate new rendering flow
35
+
3
36
  ## [1.0.28] - 2026-02-01
4
37
 
5
38
  ### ⬆️ Dependencies
@@ -1,6 +1,6 @@
1
1
  import { Injector } from '@furystack/inject';
2
2
  import { createComponent, Shade } from '@furystack/shades';
3
- import { sleepAsync } from '@furystack/utils';
3
+ import { sleepAsync, usingAsync } from '@furystack/utils';
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
5
  import { CreateMicroFrontendService } from './create-microfrontend-service.js';
6
6
  import { createShadesMicroFrontend } from './create-shades-micro-frontend.js';
@@ -21,22 +21,23 @@ describe('createShadesMicroFrontend', () => {
21
21
  expect(result.create).toBeTypeOf('function');
22
22
  expect(result.destroy).toBeUndefined();
23
23
  });
24
- it('should create a child injector when create is called', () => {
24
+ it('should create a child injector when create is called', async () => {
25
25
  const TestComponent = Shade({
26
26
  shadowDomName: 'test-mfe-child-injector',
27
27
  render: ({ props }) => createComponent("div", null, props.value),
28
28
  });
29
- const parentInjector = new Injector();
30
- const createChildSpy = vi.spyOn(parentInjector, 'createChild');
31
- const mfeService = createShadesMicroFrontend(TestComponent);
32
- const rootElement = document.getElementById('root');
33
- mfeService.create({
34
- api: { value: 'test' },
35
- rootElement,
36
- injector: parentInjector,
37
- });
38
- expect(createChildSpy).toHaveBeenCalledWith({
39
- owner: createShadesMicroFrontend,
29
+ await usingAsync(new Injector(), async (parentInjector) => {
30
+ const createChildSpy = vi.spyOn(parentInjector, 'createChild');
31
+ const mfeService = createShadesMicroFrontend(TestComponent);
32
+ const rootElement = document.getElementById('root');
33
+ mfeService.create({
34
+ api: { value: 'test' },
35
+ rootElement,
36
+ injector: parentInjector,
37
+ });
38
+ expect(createChildSpy).toHaveBeenCalledWith({
39
+ owner: createShadesMicroFrontend,
40
+ });
40
41
  });
41
42
  });
42
43
  it('should render the component with the provided API props', async () => {
@@ -47,33 +48,35 @@ describe('createShadesMicroFrontend', () => {
47
48
  "Value: ",
48
49
  props.value),
49
50
  });
50
- const injector = new Injector();
51
- const rootElement = document.getElementById('root');
52
- const mfeService = createShadesMicroFrontend(TestComponent);
53
- mfeService.create({
54
- api: { value: testValue },
55
- rootElement,
56
- injector,
51
+ await usingAsync(new Injector(), async (injector) => {
52
+ const rootElement = document.getElementById('root');
53
+ const mfeService = createShadesMicroFrontend(TestComponent);
54
+ mfeService.create({
55
+ api: { value: testValue },
56
+ rootElement,
57
+ injector,
58
+ });
59
+ await sleepAsync(10);
60
+ expect(rootElement.innerHTML).toContain(`Value: ${testValue}`);
57
61
  });
58
- await sleepAsync(10);
59
- expect(rootElement.innerHTML).toContain(`Value: ${testValue}`);
60
62
  });
61
63
  it('should render the component into the provided root element', async () => {
62
64
  const TestComponent = Shade({
63
65
  shadowDomName: 'test-mfe-root-element',
64
66
  render: () => createComponent("span", null, "MFE Content"),
65
67
  });
66
- const injector = new Injector();
67
- const rootElement = document.getElementById('root');
68
- const mfeService = createShadesMicroFrontend(TestComponent);
69
- mfeService.create({
70
- api: {},
71
- rootElement,
72
- injector,
68
+ await usingAsync(new Injector(), async (injector) => {
69
+ const rootElement = document.getElementById('root');
70
+ const mfeService = createShadesMicroFrontend(TestComponent);
71
+ mfeService.create({
72
+ api: {},
73
+ rootElement,
74
+ injector,
75
+ });
76
+ await sleepAsync(10);
77
+ expect(rootElement.querySelector('test-mfe-root-element')).toBeTruthy();
78
+ expect(rootElement.innerHTML).toContain('MFE Content');
73
79
  });
74
- await sleepAsync(10);
75
- expect(rootElement.querySelector('test-mfe-root-element')).toBeTruthy();
76
- expect(rootElement.innerHTML).toContain('MFE Content');
77
80
  });
78
81
  it('should work with complex API types', async () => {
79
82
  const clickHandler = vi.fn();
@@ -84,24 +87,25 @@ describe('createShadesMicroFrontend', () => {
84
87
  createComponent("span", { "data-testid": "item-count" }, props.items.length),
85
88
  createComponent("button", { onclick: props.onClick }, "Click"))),
86
89
  });
87
- const injector = new Injector();
88
- const rootElement = document.getElementById('root');
89
- const mfeService = createShadesMicroFrontend(TestComponent);
90
- mfeService.create({
91
- api: {
92
- user: { id: '123', name: 'John Doe' },
93
- onClick: clickHandler,
94
- items: ['a', 'b', 'c'],
95
- },
96
- rootElement,
97
- injector,
90
+ await usingAsync(new Injector(), async (injector) => {
91
+ const rootElement = document.getElementById('root');
92
+ const mfeService = createShadesMicroFrontend(TestComponent);
93
+ mfeService.create({
94
+ api: {
95
+ user: { id: '123', name: 'John Doe' },
96
+ onClick: clickHandler,
97
+ items: ['a', 'b', 'c'],
98
+ },
99
+ rootElement,
100
+ injector,
101
+ });
102
+ await sleepAsync(10);
103
+ expect(rootElement.innerHTML).toContain('John Doe');
104
+ expect(rootElement.innerHTML).toContain('3');
105
+ const button = rootElement.querySelector('button');
106
+ button?.click();
107
+ expect(clickHandler).toHaveBeenCalled();
98
108
  });
99
- await sleepAsync(10);
100
- expect(rootElement.innerHTML).toContain('John Doe');
101
- expect(rootElement.innerHTML).toContain('3');
102
- const button = rootElement.querySelector('button');
103
- button?.click();
104
- expect(clickHandler).toHaveBeenCalled();
105
109
  });
106
110
  it('should use the child injector for the shade root', async () => {
107
111
  let capturedInjector;
@@ -112,18 +116,19 @@ describe('createShadesMicroFrontend', () => {
112
116
  return createComponent("div", null, "Test");
113
117
  },
114
118
  });
115
- const parentInjector = new Injector();
116
- const rootElement = document.getElementById('root');
117
- const mfeService = createShadesMicroFrontend(TestComponent);
118
- mfeService.create({
119
- api: {},
120
- rootElement,
121
- injector: parentInjector,
119
+ await usingAsync(new Injector(), async (parentInjector) => {
120
+ const rootElement = document.getElementById('root');
121
+ const mfeService = createShadesMicroFrontend(TestComponent);
122
+ mfeService.create({
123
+ api: {},
124
+ rootElement,
125
+ injector: parentInjector,
126
+ });
127
+ await sleepAsync(10);
128
+ expect(capturedInjector).toBeDefined();
129
+ // The component should receive a child injector, not the parent
130
+ expect(capturedInjector).not.toBe(parentInjector);
122
131
  });
123
- await sleepAsync(10);
124
- expect(capturedInjector).toBeDefined();
125
- // The component should receive a child injector, not the parent
126
- expect(capturedInjector).not.toBe(parentInjector);
127
132
  });
128
133
  });
129
134
  //# sourceMappingURL=create-shades-micro-frontend.spec.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-shades-micro-frontend.spec.js","sourceRoot":"","sources":["../src/create-shades-micro-frontend.spec.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"create-shades-micro-frontend.spec.js","sourceRoot":"","sources":["../src/create-shades-micro-frontend.spec.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"micro-frontend.d.ts","sourceRoot":"","sources":["../src/micro-frontend.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAA;AAEnF,KAAK,kBAAkB,CAAC,IAAI,IAAI;IAC9B,GAAG,EAAE,IAAI,CAAA;IACT,cAAc,EAAE,MAAM,OAAO,CAAC,0BAA0B,CAAC,IAAI,CAAC,CAAC,CAAA;IAC/D,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,OAAO,CAAA;CACpE,CAAA;AAED,eAAO,MAAM,aAAa,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,kBAAkB,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,OAwCzE,CAAA"}
1
+ {"version":3,"file":"micro-frontend.d.ts","sourceRoot":"","sources":["../src/micro-frontend.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAA;AAEnF,KAAK,kBAAkB,CAAC,IAAI,IAAI;IAC9B,GAAG,EAAE,IAAI,CAAA;IACT,cAAc,EAAE,MAAM,OAAO,CAAC,0BAA0B,CAAC,IAAI,CAAC,CAAC,CAAA;IAC/D,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,OAAO,CAAA;CACpE,CAAA;AAED,eAAO,MAAM,aAAa,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,kBAAkB,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,OA6DzE,CAAA"}
@@ -1,37 +1,46 @@
1
- import { Shade } from '@furystack/shades';
1
+ import { Shade, createComponent } from '@furystack/shades';
2
2
  export const MicroFrontend = Shade({
3
3
  shadowDomName: 'shade-micro-frontend',
4
- constructed: async ({ props, element, injector }) => {
5
- const tryCreateComponent = async () => {
6
- const creatorService = await props.loaderCallback();
7
- // TODO: Think about type checking
8
- // if (!(creatorService instanceof CreateMicroFrontendService)) {
9
- // throw new Error('Invalid creator service')
10
- // }
11
- const childInjector = injector.createChild({
12
- owner: creatorService,
4
+ render: ({ props, injector, useDisposable, useRef }) => {
5
+ const containerRef = useRef('mfeContainer');
6
+ useDisposable('mfe-loader', () => {
7
+ let destroyFn;
8
+ const tryCreateComponent = async () => {
9
+ const container = containerRef.current;
10
+ if (!container)
11
+ return;
12
+ const creatorService = await props.loaderCallback();
13
+ const childInjector = injector.createChild({
14
+ owner: creatorService,
15
+ });
16
+ container.innerHTML = '';
17
+ creatorService.create({
18
+ api: props.api,
19
+ rootElement: container,
20
+ injector: childInjector,
21
+ });
22
+ destroyFn = () => creatorService.destroy?.({ api: props.api, injector: childInjector });
23
+ };
24
+ queueMicrotask(() => {
25
+ tryCreateComponent().catch((error) => {
26
+ if (props.error && containerRef.current) {
27
+ containerRef.current.appendChild(props.error(error, async () => {
28
+ await tryCreateComponent();
29
+ }));
30
+ }
31
+ });
13
32
  });
14
- element.innerHTML = '';
15
- creatorService.create({
16
- api: props.api,
17
- rootElement: element,
18
- injector: childInjector,
19
- });
20
- return () => creatorService.destroy?.({ api: props.api, injector: childInjector });
21
- };
22
- try {
23
- return await tryCreateComponent();
24
- }
25
- catch (error) {
26
- if (props.error) {
27
- element.appendChild(props.error(error, async () => {
28
- await tryCreateComponent();
29
- }));
30
- }
33
+ return {
34
+ [Symbol.asyncDispose]: async () => destroyFn?.(),
35
+ };
36
+ });
37
+ // Propagate prop updates to the inner MFE element (for Shade-based MFEs)
38
+ const mfeElement = containerRef.current?.firstElementChild;
39
+ if (mfeElement?.updateComponent) {
40
+ mfeElement.props = props.api;
41
+ mfeElement.updateComponent();
31
42
  }
32
- },
33
- render: ({ props }) => {
34
- return props.loader || null;
43
+ return (createComponent("div", { ref: containerRef, style: { display: 'contents' } }, props.loader || null));
35
44
  },
36
45
  });
37
46
  //# sourceMappingURL=micro-frontend.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"micro-frontend.js","sourceRoot":"","sources":["../src/micro-frontend.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAUzC,MAAM,CAAC,MAAM,aAAa,GAA2D,KAAK,CAAC;IACzF,aAAa,EAAE,sBAAsB;IACrC,WAAW,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE;QAClD,MAAM,kBAAkB,GAAG,KAAK,IAAI,EAAE;YACpC,MAAM,cAAc,GAAG,MAAM,KAAK,CAAC,cAAc,EAAE,CAAA;YACnD,kCAAkC;YAClC,iEAAiE;YACjE,+CAA+C;YAC/C,IAAI;YAEJ,MAAM,aAAa,GAAG,QAAQ,CAAC,WAAW,CAAC;gBACzC,KAAK,EAAE,cAAc;aACtB,CAAC,CAAA;YAEF,OAAO,CAAC,SAAS,GAAG,EAAE,CAAA;YAEtB,cAAc,CAAC,MAAM,CAAC;gBACpB,GAAG,EAAE,KAAK,CAAC,GAAG;gBACd,WAAW,EAAE,OAAO;gBACpB,QAAQ,EAAE,aAAa;aACxB,CAAC,CAAA;YAEF,OAAO,GAAG,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,CAAA;QACpF,CAAC,CAAA;QAED,IAAI,CAAC;YACH,OAAO,MAAM,kBAAkB,EAAE,CAAA;QACnC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;gBAChB,OAAO,CAAC,WAAW,CACjB,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE;oBAC5B,MAAM,kBAAkB,EAAE,CAAA;gBAC5B,CAAC,CAAC,CACH,CAAA;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE;QACpB,OAAO,KAAK,CAAC,MAAM,IAAI,IAAI,CAAA;IAC7B,CAAC;CACF,CAAC,CAAA"}
1
+ {"version":3,"file":"micro-frontend.js","sourceRoot":"","sources":["../src/micro-frontend.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAU1D,MAAM,CAAC,MAAM,aAAa,GAA2D,KAAK,CAAC;IACzF,aAAa,EAAE,sBAAsB;IACrC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,EAAE;QACrD,MAAM,YAAY,GAAG,MAAM,CAAiB,cAAc,CAAC,CAAA;QAE3D,aAAa,CAAC,YAAY,EAAE,GAAG,EAAE;YAC/B,IAAI,SAAmC,CAAA;YAEvC,MAAM,kBAAkB,GAAG,KAAK,IAAI,EAAE;gBACpC,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAA;gBACtC,IAAI,CAAC,SAAS;oBAAE,OAAM;gBAEtB,MAAM,cAAc,GAAG,MAAM,KAAK,CAAC,cAAc,EAAE,CAAA;gBAEnD,MAAM,aAAa,GAAG,QAAQ,CAAC,WAAW,CAAC;oBACzC,KAAK,EAAE,cAAc;iBACtB,CAAC,CAAA;gBAEF,SAAS,CAAC,SAAS,GAAG,EAAE,CAAA;gBAExB,cAAc,CAAC,MAAM,CAAC;oBACpB,GAAG,EAAE,KAAK,CAAC,GAAG;oBACd,WAAW,EAAE,SAAS;oBACtB,QAAQ,EAAE,aAAa;iBACxB,CAAC,CAAA;gBAEF,SAAS,GAAG,GAAG,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,CAAA;YACzF,CAAC,CAAA;YAED,cAAc,CAAC,GAAG,EAAE;gBAClB,kBAAkB,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;oBAC5C,IAAI,KAAK,CAAC,KAAK,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;wBACxC,YAAY,CAAC,OAAO,CAAC,WAAW,CAC9B,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE;4BAC5B,MAAM,kBAAkB,EAAE,CAAA;wBAC5B,CAAC,CAAC,CACH,CAAA;oBACH,CAAC;gBACH,CAAC,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;YAEF,OAAO;gBACL,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC,SAAS,EAAE,EAAE;aACjD,CAAA;QACH,CAAC,CAAC,CAAA;QAEF,yEAAyE;QACzE,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,EAAE,iBAEjC,CAAA;QACR,IAAI,UAAU,EAAE,eAAe,EAAE,CAAC;YAChC,UAAU,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,CAAA;YAC5B,UAAU,CAAC,eAAe,EAAE,CAAA;QAC9B,CAAC;QAED,OAAO,CACL,yBAAK,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,IACnD,KAAK,CAAC,MAAM,IAAI,IAAI,CACjB,CACP,CAAA;IACH,CAAC;CACF,CAAC,CAAA"}
@@ -1,6 +1,6 @@
1
1
  import { Injector } from '@furystack/inject';
2
- import { sleepAsync } from '@furystack/utils';
3
- import { createComponent, initializeShadeRoot, Shade } from '@furystack/shades';
2
+ import { sleepAsync, usingAsync } from '@furystack/utils';
3
+ import { createComponent, flushUpdates, initializeShadeRoot, Shade } from '@furystack/shades';
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
5
  import { createShadesMicroFrontend } from './create-shades-micro-frontend.js';
6
6
  import { MicroFrontend } from './micro-frontend.js';
@@ -12,92 +12,100 @@ describe('<MicroFrontend /> component', () => {
12
12
  document.body.innerHTML = '';
13
13
  });
14
14
  it('Should render the MFE app', async () => {
15
- const injector = new Injector();
16
- const rootElement = document.getElementById('root');
17
- const MfeComponent = Shade({
18
- shadowDomName: 'mfe-test-example',
19
- render: ({ props }) => createComponent("div", null,
20
- "Loaded: ",
21
- props.value),
15
+ await usingAsync(new Injector(), async (injector) => {
16
+ const rootElement = document.getElementById('root');
17
+ const MfeComponent = Shade({
18
+ shadowDomName: 'mfe-test-example',
19
+ render: ({ props }) => createComponent("div", null,
20
+ "Loaded: ",
21
+ props.value),
22
+ });
23
+ const value = crypto.randomUUID();
24
+ initializeShadeRoot({
25
+ injector,
26
+ rootElement,
27
+ jsxElement: (createComponent(MicroFrontend, { api: { value }, loaderCallback: async () => {
28
+ return createShadesMicroFrontend(MfeComponent);
29
+ } })),
30
+ });
31
+ await flushUpdates();
32
+ await sleepAsync(1);
33
+ expect(document.body.innerHTML).toBe(`<div id="root"><shade-micro-frontend><div style="display: contents;"><mfe-test-example><div>Loaded: ${value}</div></mfe-test-example></div></shade-micro-frontend></div>`);
22
34
  });
23
- const value = crypto.randomUUID();
24
- initializeShadeRoot({
25
- injector,
26
- rootElement,
27
- jsxElement: (createComponent(MicroFrontend, { api: { value }, loaderCallback: async () => {
28
- return createShadesMicroFrontend(MfeComponent);
29
- } })),
30
- });
31
- await sleepAsync(1);
32
- expect(document.body.innerHTML).toBe(`<div id="root"><shade-micro-frontend><mfe-test-example><div>Loaded: ${value}</div></mfe-test-example></shade-micro-frontend></div>`);
33
35
  });
34
36
  it('Should render the MFE app with a loader', async () => {
35
- const injector = new Injector();
36
- const rootElement = document.getElementById('root');
37
- const MfeComponent = Shade({
38
- shadowDomName: 'mfe-test-example-w-loader',
39
- render: ({ props }) => createComponent("div", null,
40
- "Loaded: ",
41
- props.value),
42
- });
43
- const value = crypto.randomUUID();
44
- initializeShadeRoot({
45
- injector,
46
- rootElement,
47
- jsxElement: (createComponent(MicroFrontend, { loader: createComponent("div", null, "Loading..."), api: { value }, loaderCallback: async () => {
48
- await sleepAsync(10);
49
- return createShadesMicroFrontend(MfeComponent);
50
- } })),
37
+ await usingAsync(new Injector(), async (injector) => {
38
+ const rootElement = document.getElementById('root');
39
+ const MfeComponent = Shade({
40
+ shadowDomName: 'mfe-test-example-w-loader',
41
+ render: ({ props }) => createComponent("div", null,
42
+ "Loaded: ",
43
+ props.value),
44
+ });
45
+ const value = crypto.randomUUID();
46
+ initializeShadeRoot({
47
+ injector,
48
+ rootElement,
49
+ jsxElement: (createComponent(MicroFrontend, { loader: createComponent("div", null, "Loading..."), api: { value }, loaderCallback: async () => {
50
+ await sleepAsync(10);
51
+ return createShadesMicroFrontend(MfeComponent);
52
+ } })),
53
+ });
54
+ await flushUpdates();
55
+ expect(document.body.innerHTML).toBe('<div id="root"><shade-micro-frontend><div style="display: contents;"><div>Loading...</div></div></shade-micro-frontend></div>');
56
+ await sleepAsync(20);
57
+ expect(document.body.innerHTML).toBe(`<div id="root"><shade-micro-frontend><div style="display: contents;"><mfe-test-example-w-loader><div>Loaded: ${value}</div></mfe-test-example-w-loader></div></shade-micro-frontend></div>`);
51
58
  });
52
- expect(document.body.innerHTML).toBe('<div id="root"><shade-micro-frontend><div>Loading...</div></shade-micro-frontend></div>');
53
- await sleepAsync(20);
54
- expect(document.body.innerHTML).toBe(`<div id="root"><shade-micro-frontend><mfe-test-example-w-loader><div>Loaded: ${value}</div></mfe-test-example-w-loader></shade-micro-frontend></div>`);
55
59
  });
56
60
  it('Should render the MFE app with an error', async () => {
57
- const injector = new Injector();
58
- const rootElement = document.getElementById('root');
59
- initializeShadeRoot({
60
- injector,
61
- rootElement,
62
- jsxElement: (createComponent(MicroFrontend, { error: () => createComponent("div", null, "Error..."), api: {}, loaderCallback: async () => {
63
- throw Error(':(');
64
- } })),
61
+ await usingAsync(new Injector(), async (injector) => {
62
+ const rootElement = document.getElementById('root');
63
+ initializeShadeRoot({
64
+ injector,
65
+ rootElement,
66
+ jsxElement: (createComponent(MicroFrontend, { error: () => createComponent("div", null, "Error..."), api: {}, loaderCallback: async () => {
67
+ throw Error(':(');
68
+ } })),
69
+ });
70
+ await flushUpdates();
71
+ await sleepAsync(10);
72
+ expect(document.body.innerHTML).toBe(`<div id="root"><shade-micro-frontend><div style="display: contents;"><div>Error...</div></div></shade-micro-frontend></div>`);
65
73
  });
66
- await sleepAsync(10);
67
- expect(document.body.innerHTML).toBe(`<div id="root"><shade-micro-frontend><div>Error...</div></shade-micro-frontend></div>`);
68
74
  });
69
75
  it('Should execute the destroy callback', async () => {
70
- const injector = new Injector();
71
- const rootElement = document.getElementById('root');
72
- const value = crypto.randomUUID();
73
- const destroy = vi.fn();
74
- const Host = Shade({
75
- shadowDomName: 'mfe-test-example-w-destroy-host',
76
- render: ({ useState }) => {
77
- const [hasMfe, setHasMfe] = useState('hasMfe', true);
78
- return (createComponent(createComponent, null,
79
- createComponent("button", { onclick: () => {
80
- setHasMfe(false);
81
- } }, "Remove MFE"),
82
- hasMfe && (createComponent(MicroFrontend, { loader: createComponent("div", null, "Loading..."), api: { value }, loaderCallback: async () => {
83
- return {
84
- create: () => { },
85
- destroy,
86
- };
87
- } }))));
88
- },
89
- });
90
- initializeShadeRoot({
91
- injector,
92
- rootElement,
93
- jsxElement: createComponent(Host, null),
76
+ await usingAsync(new Injector(), async (injector) => {
77
+ const rootElement = document.getElementById('root');
78
+ const value = crypto.randomUUID();
79
+ const destroy = vi.fn();
80
+ const Host = Shade({
81
+ shadowDomName: 'mfe-test-example-w-destroy-host',
82
+ render: ({ useState }) => {
83
+ const [hasMfe, setHasMfe] = useState('hasMfe', true);
84
+ return (createComponent(createComponent, null,
85
+ createComponent("button", { onclick: () => {
86
+ setHasMfe(false);
87
+ } }, "Remove MFE"),
88
+ hasMfe && (createComponent(MicroFrontend, { loader: createComponent("div", null, "Loading..."), api: { value }, loaderCallback: async () => {
89
+ return {
90
+ create: () => { },
91
+ destroy,
92
+ };
93
+ } }))));
94
+ },
95
+ });
96
+ initializeShadeRoot({
97
+ injector,
98
+ rootElement,
99
+ jsxElement: createComponent(Host, null),
100
+ });
101
+ await flushUpdates();
102
+ await sleepAsync(10);
103
+ expect(document.body.innerHTML).toBe('<div id="root"><mfe-test-example-w-destroy-host><button>Remove MFE</button><shade-micro-frontend><div style="display: contents;"></div></shade-micro-frontend></mfe-test-example-w-destroy-host></div>');
104
+ document.querySelector('button').click();
105
+ await sleepAsync(10);
106
+ expect(destroy).toHaveBeenCalledWith({ api: { value }, injector: expect.any(Injector) });
107
+ expect(document.body.innerHTML).toBe(`<div id="root"><mfe-test-example-w-destroy-host><button>Remove MFE</button></mfe-test-example-w-destroy-host></div>`);
94
108
  });
95
- await sleepAsync(10);
96
- expect(document.body.innerHTML).toBe('<div id="root"><mfe-test-example-w-destroy-host><button>Remove MFE</button><shade-micro-frontend></shade-micro-frontend></mfe-test-example-w-destroy-host></div>');
97
- document.querySelector('button').click();
98
- await sleepAsync(10);
99
- expect(destroy).toHaveBeenCalledWith({ api: { value }, injector: expect.any(Injector) });
100
- expect(document.body.innerHTML).toBe(`<div id="root"><mfe-test-example-w-destroy-host><button>Remove MFE</button></mfe-test-example-w-destroy-host></div>`);
101
109
  });
102
110
  });
103
111
  //# sourceMappingURL=micro-frontend.spec.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"micro-frontend.spec.js","sourceRoot":"","sources":["../src/micro-frontend.spec.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"micro-frontend.spec.js","sourceRoot":"","sources":["../src/micro-frontend.spec.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@furystack/shades-mfe",
3
- "version": "1.0.28",
3
+ "version": "2.0.0",
4
4
  "description": "Micro-Frontend support for FuryStack Shades",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -37,8 +37,8 @@
37
37
  },
38
38
  "homepage": "https://github.com/furystack/furystack",
39
39
  "dependencies": {
40
- "@furystack/inject": "^12.0.28",
41
- "@furystack/shades": "^11.1.0"
40
+ "@furystack/inject": "^12.0.29",
41
+ "@furystack/shades": "^12.0.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^25.0.10",
@@ -1,6 +1,6 @@
1
1
  import { Injector } from '@furystack/inject'
2
2
  import { createComponent, Shade } from '@furystack/shades'
3
- import { sleepAsync } from '@furystack/utils'
3
+ import { sleepAsync, usingAsync } from '@furystack/utils'
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
5
  import { CreateMicroFrontendService } from './create-microfrontend-service.js'
6
6
  import { createShadesMicroFrontend } from './create-shades-micro-frontend.js'
@@ -27,26 +27,27 @@ describe('createShadesMicroFrontend', () => {
27
27
  expect(result.destroy).toBeUndefined()
28
28
  })
29
29
 
30
- it('should create a child injector when create is called', () => {
30
+ it('should create a child injector when create is called', async () => {
31
31
  const TestComponent = Shade<{ value: string }>({
32
32
  shadowDomName: 'test-mfe-child-injector',
33
33
  render: ({ props }) => <div>{props.value}</div>,
34
34
  })
35
35
 
36
- const parentInjector = new Injector()
37
- const createChildSpy = vi.spyOn(parentInjector, 'createChild')
36
+ await usingAsync(new Injector(), async (parentInjector) => {
37
+ const createChildSpy = vi.spyOn(parentInjector, 'createChild')
38
38
 
39
- const mfeService = createShadesMicroFrontend(TestComponent)
40
- const rootElement = document.getElementById('root') as HTMLDivElement
39
+ const mfeService = createShadesMicroFrontend(TestComponent)
40
+ const rootElement = document.getElementById('root') as HTMLDivElement
41
41
 
42
- mfeService.create({
43
- api: { value: 'test' },
44
- rootElement,
45
- injector: parentInjector,
46
- })
42
+ mfeService.create({
43
+ api: { value: 'test' },
44
+ rootElement,
45
+ injector: parentInjector,
46
+ })
47
47
 
48
- expect(createChildSpy).toHaveBeenCalledWith({
49
- owner: createShadesMicroFrontend,
48
+ expect(createChildSpy).toHaveBeenCalledWith({
49
+ owner: createShadesMicroFrontend,
50
+ })
50
51
  })
51
52
  })
52
53
 
@@ -58,19 +59,20 @@ describe('createShadesMicroFrontend', () => {
58
59
  render: ({ props }) => <div data-testid="content">Value: {props.value}</div>,
59
60
  })
60
61
 
61
- const injector = new Injector()
62
- const rootElement = document.getElementById('root') as HTMLDivElement
62
+ await usingAsync(new Injector(), async (injector) => {
63
+ const rootElement = document.getElementById('root') as HTMLDivElement
63
64
 
64
- const mfeService = createShadesMicroFrontend(TestComponent)
65
- mfeService.create({
66
- api: { value: testValue },
67
- rootElement,
68
- injector,
69
- })
65
+ const mfeService = createShadesMicroFrontend(TestComponent)
66
+ mfeService.create({
67
+ api: { value: testValue },
68
+ rootElement,
69
+ injector,
70
+ })
70
71
 
71
- await sleepAsync(10)
72
+ await sleepAsync(10)
72
73
 
73
- expect(rootElement.innerHTML).toContain(`Value: ${testValue}`)
74
+ expect(rootElement.innerHTML).toContain(`Value: ${testValue}`)
75
+ })
74
76
  })
75
77
 
76
78
  it('should render the component into the provided root element', async () => {
@@ -79,20 +81,21 @@ describe('createShadesMicroFrontend', () => {
79
81
  render: () => <span>MFE Content</span>,
80
82
  })
81
83
 
82
- const injector = new Injector()
83
- const rootElement = document.getElementById('root') as HTMLDivElement
84
+ await usingAsync(new Injector(), async (injector) => {
85
+ const rootElement = document.getElementById('root') as HTMLDivElement
84
86
 
85
- const mfeService = createShadesMicroFrontend(TestComponent)
86
- mfeService.create({
87
- api: {},
88
- rootElement,
89
- injector,
90
- })
87
+ const mfeService = createShadesMicroFrontend(TestComponent)
88
+ mfeService.create({
89
+ api: {},
90
+ rootElement,
91
+ injector,
92
+ })
91
93
 
92
- await sleepAsync(10)
94
+ await sleepAsync(10)
93
95
 
94
- expect(rootElement.querySelector('test-mfe-root-element')).toBeTruthy()
95
- expect(rootElement.innerHTML).toContain('MFE Content')
96
+ expect(rootElement.querySelector('test-mfe-root-element')).toBeTruthy()
97
+ expect(rootElement.innerHTML).toContain('MFE Content')
98
+ })
96
99
  })
97
100
 
98
101
  it('should work with complex API types', async () => {
@@ -115,29 +118,30 @@ describe('createShadesMicroFrontend', () => {
115
118
  ),
116
119
  })
117
120
 
118
- const injector = new Injector()
119
- const rootElement = document.getElementById('root') as HTMLDivElement
121
+ await usingAsync(new Injector(), async (injector) => {
122
+ const rootElement = document.getElementById('root') as HTMLDivElement
120
123
 
121
- const mfeService = createShadesMicroFrontend(TestComponent)
122
- mfeService.create({
123
- api: {
124
- user: { id: '123', name: 'John Doe' },
125
- onClick: clickHandler,
126
- items: ['a', 'b', 'c'],
127
- },
128
- rootElement,
129
- injector,
130
- })
124
+ const mfeService = createShadesMicroFrontend(TestComponent)
125
+ mfeService.create({
126
+ api: {
127
+ user: { id: '123', name: 'John Doe' },
128
+ onClick: clickHandler,
129
+ items: ['a', 'b', 'c'],
130
+ },
131
+ rootElement,
132
+ injector,
133
+ })
131
134
 
132
- await sleepAsync(10)
135
+ await sleepAsync(10)
133
136
 
134
- expect(rootElement.innerHTML).toContain('John Doe')
135
- expect(rootElement.innerHTML).toContain('3')
137
+ expect(rootElement.innerHTML).toContain('John Doe')
138
+ expect(rootElement.innerHTML).toContain('3')
136
139
 
137
- const button = rootElement.querySelector('button')
138
- button?.click()
140
+ const button = rootElement.querySelector('button')
141
+ button?.click()
139
142
 
140
- expect(clickHandler).toHaveBeenCalled()
143
+ expect(clickHandler).toHaveBeenCalled()
144
+ })
141
145
  })
142
146
 
143
147
  it('should use the child injector for the shade root', async () => {
@@ -151,20 +155,21 @@ describe('createShadesMicroFrontend', () => {
151
155
  },
152
156
  })
153
157
 
154
- const parentInjector = new Injector()
155
- const rootElement = document.getElementById('root') as HTMLDivElement
158
+ await usingAsync(new Injector(), async (parentInjector) => {
159
+ const rootElement = document.getElementById('root') as HTMLDivElement
156
160
 
157
- const mfeService = createShadesMicroFrontend(TestComponent)
158
- mfeService.create({
159
- api: {},
160
- rootElement,
161
- injector: parentInjector,
162
- })
161
+ const mfeService = createShadesMicroFrontend(TestComponent)
162
+ mfeService.create({
163
+ api: {},
164
+ rootElement,
165
+ injector: parentInjector,
166
+ })
163
167
 
164
- await sleepAsync(10)
168
+ await sleepAsync(10)
165
169
 
166
- expect(capturedInjector).toBeDefined()
167
- // The component should receive a child injector, not the parent
168
- expect(capturedInjector).not.toBe(parentInjector)
170
+ expect(capturedInjector).toBeDefined()
171
+ // The component should receive a child injector, not the parent
172
+ expect(capturedInjector).not.toBe(parentInjector)
173
+ })
169
174
  })
170
175
  })
@@ -1,7 +1,7 @@
1
1
  import { Injector } from '@furystack/inject'
2
- import { sleepAsync } from '@furystack/utils'
2
+ import { sleepAsync, usingAsync } from '@furystack/utils'
3
3
 
4
- import { createComponent, initializeShadeRoot, Shade } from '@furystack/shades'
4
+ import { createComponent, flushUpdates, initializeShadeRoot, Shade } from '@furystack/shades'
5
5
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
6
6
  import { createShadesMicroFrontend } from './create-shades-micro-frontend.js'
7
7
  import { MicroFrontend } from './micro-frontend.js'
@@ -15,145 +15,153 @@ describe('<MicroFrontend /> component', () => {
15
15
  })
16
16
 
17
17
  it('Should render the MFE app', async () => {
18
- const injector = new Injector()
19
- const rootElement = document.getElementById('root') as HTMLDivElement
20
-
21
- const MfeComponent = Shade<{ value: string }>({
22
- shadowDomName: 'mfe-test-example',
23
- render: ({ props }) => <div>Loaded: {props.value}</div>,
24
- })
25
-
26
- const value = crypto.randomUUID()
27
-
28
- initializeShadeRoot({
29
- injector,
30
- rootElement,
31
- jsxElement: (
32
- <MicroFrontend
33
- api={{ value }}
34
- loaderCallback={async () => {
35
- return createShadesMicroFrontend(MfeComponent)
36
- }}
37
- />
38
- ),
18
+ await usingAsync(new Injector(), async (injector) => {
19
+ const rootElement = document.getElementById('root') as HTMLDivElement
20
+
21
+ const MfeComponent = Shade<{ value: string }>({
22
+ shadowDomName: 'mfe-test-example',
23
+ render: ({ props }) => <div>Loaded: {props.value}</div>,
24
+ })
25
+
26
+ const value = crypto.randomUUID()
27
+
28
+ initializeShadeRoot({
29
+ injector,
30
+ rootElement,
31
+ jsxElement: (
32
+ <MicroFrontend
33
+ api={{ value }}
34
+ loaderCallback={async () => {
35
+ return createShadesMicroFrontend(MfeComponent)
36
+ }}
37
+ />
38
+ ),
39
+ })
40
+
41
+ await flushUpdates()
42
+ await sleepAsync(1)
43
+
44
+ expect(document.body.innerHTML).toBe(
45
+ `<div id="root"><shade-micro-frontend><div style="display: contents;"><mfe-test-example><div>Loaded: ${value}</div></mfe-test-example></div></shade-micro-frontend></div>`,
46
+ )
39
47
  })
40
-
41
- await sleepAsync(1)
42
-
43
- expect(document.body.innerHTML).toBe(
44
- `<div id="root"><shade-micro-frontend><mfe-test-example><div>Loaded: ${value}</div></mfe-test-example></shade-micro-frontend></div>`,
45
- )
46
48
  })
47
49
 
48
50
  it('Should render the MFE app with a loader', async () => {
49
- const injector = new Injector()
50
- const rootElement = document.getElementById('root') as HTMLDivElement
51
-
52
- const MfeComponent = Shade<{ value: string }>({
53
- shadowDomName: 'mfe-test-example-w-loader',
54
- render: ({ props }) => <div>Loaded: {props.value}</div>,
55
- })
56
-
57
- const value = crypto.randomUUID()
58
-
59
- initializeShadeRoot({
60
- injector,
61
- rootElement,
62
- jsxElement: (
63
- <MicroFrontend
64
- loader={<div>Loading...</div>}
65
- api={{ value }}
66
- loaderCallback={async () => {
67
- await sleepAsync(10)
68
- return createShadesMicroFrontend(MfeComponent)
69
- }}
70
- />
71
- ),
51
+ await usingAsync(new Injector(), async (injector) => {
52
+ const rootElement = document.getElementById('root') as HTMLDivElement
53
+
54
+ const MfeComponent = Shade<{ value: string }>({
55
+ shadowDomName: 'mfe-test-example-w-loader',
56
+ render: ({ props }) => <div>Loaded: {props.value}</div>,
57
+ })
58
+
59
+ const value = crypto.randomUUID()
60
+
61
+ initializeShadeRoot({
62
+ injector,
63
+ rootElement,
64
+ jsxElement: (
65
+ <MicroFrontend
66
+ loader={<div>Loading...</div>}
67
+ api={{ value }}
68
+ loaderCallback={async () => {
69
+ await sleepAsync(10)
70
+ return createShadesMicroFrontend(MfeComponent)
71
+ }}
72
+ />
73
+ ),
74
+ })
75
+ await flushUpdates()
76
+ expect(document.body.innerHTML).toBe(
77
+ '<div id="root"><shade-micro-frontend><div style="display: contents;"><div>Loading...</div></div></shade-micro-frontend></div>',
78
+ )
79
+ await sleepAsync(20)
80
+ expect(document.body.innerHTML).toBe(
81
+ `<div id="root"><shade-micro-frontend><div style="display: contents;"><mfe-test-example-w-loader><div>Loaded: ${value}</div></mfe-test-example-w-loader></div></shade-micro-frontend></div>`,
82
+ )
72
83
  })
73
- expect(document.body.innerHTML).toBe(
74
- '<div id="root"><shade-micro-frontend><div>Loading...</div></shade-micro-frontend></div>',
75
- )
76
- await sleepAsync(20)
77
- expect(document.body.innerHTML).toBe(
78
- `<div id="root"><shade-micro-frontend><mfe-test-example-w-loader><div>Loaded: ${value}</div></mfe-test-example-w-loader></shade-micro-frontend></div>`,
79
- )
80
84
  })
81
85
 
82
86
  it('Should render the MFE app with an error', async () => {
83
- const injector = new Injector()
84
- const rootElement = document.getElementById('root') as HTMLDivElement
85
-
86
- initializeShadeRoot({
87
- injector,
88
- rootElement,
89
- jsxElement: (
90
- <MicroFrontend
91
- error={() => <div>Error...</div>}
92
- api={{}}
93
- loaderCallback={async () => {
94
- throw Error(':(')
95
- }}
96
- />
97
- ),
87
+ await usingAsync(new Injector(), async (injector) => {
88
+ const rootElement = document.getElementById('root') as HTMLDivElement
89
+
90
+ initializeShadeRoot({
91
+ injector,
92
+ rootElement,
93
+ jsxElement: (
94
+ <MicroFrontend
95
+ error={() => <div>Error...</div>}
96
+ api={{}}
97
+ loaderCallback={async () => {
98
+ throw Error(':(')
99
+ }}
100
+ />
101
+ ),
102
+ })
103
+ await flushUpdates()
104
+ await sleepAsync(10)
105
+ expect(document.body.innerHTML).toBe(
106
+ `<div id="root"><shade-micro-frontend><div style="display: contents;"><div>Error...</div></div></shade-micro-frontend></div>`,
107
+ )
98
108
  })
99
- await sleepAsync(10)
100
- expect(document.body.innerHTML).toBe(
101
- `<div id="root"><shade-micro-frontend><div>Error...</div></shade-micro-frontend></div>`,
102
- )
103
109
  })
104
110
 
105
111
  it('Should execute the destroy callback', async () => {
106
- const injector = new Injector()
107
- const rootElement = document.getElementById('root') as HTMLDivElement
108
-
109
- const value = crypto.randomUUID()
110
-
111
- const destroy = vi.fn()
112
-
113
- const Host = Shade({
114
- shadowDomName: 'mfe-test-example-w-destroy-host',
115
- render: ({ useState }) => {
116
- const [hasMfe, setHasMfe] = useState('hasMfe', true)
117
- return (
118
- <>
119
- <button
120
- onclick={() => {
121
- setHasMfe(false)
122
- }}
123
- >
124
- Remove MFE
125
- </button>
126
- {hasMfe && (
127
- <MicroFrontend
128
- loader={<div>Loading...</div>}
129
- api={{ value }}
130
- loaderCallback={async () => {
131
- return {
132
- create: () => {},
133
- destroy,
134
- }
112
+ await usingAsync(new Injector(), async (injector) => {
113
+ const rootElement = document.getElementById('root') as HTMLDivElement
114
+
115
+ const value = crypto.randomUUID()
116
+
117
+ const destroy = vi.fn()
118
+
119
+ const Host = Shade({
120
+ shadowDomName: 'mfe-test-example-w-destroy-host',
121
+ render: ({ useState }) => {
122
+ const [hasMfe, setHasMfe] = useState('hasMfe', true)
123
+ return (
124
+ <>
125
+ <button
126
+ onclick={() => {
127
+ setHasMfe(false)
135
128
  }}
136
- />
137
- )}
138
- </>
139
- )
140
- },
141
- })
142
-
143
- initializeShadeRoot({
144
- injector,
145
- rootElement,
146
- jsxElement: <Host />,
129
+ >
130
+ Remove MFE
131
+ </button>
132
+ {hasMfe && (
133
+ <MicroFrontend
134
+ loader={<div>Loading...</div>}
135
+ api={{ value }}
136
+ loaderCallback={async () => {
137
+ return {
138
+ create: () => {},
139
+ destroy,
140
+ }
141
+ }}
142
+ />
143
+ )}
144
+ </>
145
+ )
146
+ },
147
+ })
148
+
149
+ initializeShadeRoot({
150
+ injector,
151
+ rootElement,
152
+ jsxElement: <Host />,
153
+ })
154
+ await flushUpdates()
155
+ await sleepAsync(10)
156
+ expect(document.body.innerHTML).toBe(
157
+ '<div id="root"><mfe-test-example-w-destroy-host><button>Remove MFE</button><shade-micro-frontend><div style="display: contents;"></div></shade-micro-frontend></mfe-test-example-w-destroy-host></div>',
158
+ )
159
+ document.querySelector('button')!.click()
160
+ await sleepAsync(10)
161
+ expect(destroy).toHaveBeenCalledWith({ api: { value }, injector: expect.any(Injector) as Injector })
162
+ expect(document.body.innerHTML).toBe(
163
+ `<div id="root"><mfe-test-example-w-destroy-host><button>Remove MFE</button></mfe-test-example-w-destroy-host></div>`,
164
+ )
147
165
  })
148
- await sleepAsync(10)
149
- expect(document.body.innerHTML).toBe(
150
- '<div id="root"><mfe-test-example-w-destroy-host><button>Remove MFE</button><shade-micro-frontend></shade-micro-frontend></mfe-test-example-w-destroy-host></div>',
151
- )
152
- document.querySelector('button')!.click()
153
- await sleepAsync(10)
154
- expect(destroy).toHaveBeenCalledWith({ api: { value }, injector: expect.any(Injector) as Injector })
155
- expect(document.body.innerHTML).toBe(
156
- `<div id="root"><mfe-test-example-w-destroy-host><button>Remove MFE</button></mfe-test-example-w-destroy-host></div>`,
157
- )
158
166
  })
159
167
  })
@@ -1,4 +1,4 @@
1
- import { Shade } from '@furystack/shades'
1
+ import { Shade, createComponent } from '@furystack/shades'
2
2
  import type { CreateMicroFrontendService } from './create-microfrontend-service.js'
3
3
 
4
4
  type MicroFrontendProps<TApi> = {
@@ -10,42 +10,63 @@ type MicroFrontendProps<TApi> = {
10
10
 
11
11
  export const MicroFrontend: <TApi>(props: MicroFrontendProps<TApi>) => JSX.Element = Shade({
12
12
  shadowDomName: 'shade-micro-frontend',
13
- constructed: async ({ props, element, injector }) => {
14
- const tryCreateComponent = async () => {
15
- const creatorService = await props.loaderCallback()
16
- // TODO: Think about type checking
17
- // if (!(creatorService instanceof CreateMicroFrontendService)) {
18
- // throw new Error('Invalid creator service')
19
- // }
20
-
21
- const childInjector = injector.createChild({
22
- owner: creatorService,
23
- })
13
+ render: ({ props, injector, useDisposable, useRef }) => {
14
+ const containerRef = useRef<HTMLDivElement>('mfeContainer')
24
15
 
25
- element.innerHTML = ''
16
+ useDisposable('mfe-loader', () => {
17
+ let destroyFn: (() => void) | undefined
26
18
 
27
- creatorService.create({
28
- api: props.api,
29
- rootElement: element,
30
- injector: childInjector,
31
- })
19
+ const tryCreateComponent = async () => {
20
+ const container = containerRef.current
21
+ if (!container) return
32
22
 
33
- return () => creatorService.destroy?.({ api: props.api, injector: childInjector })
34
- }
23
+ const creatorService = await props.loaderCallback()
24
+
25
+ const childInjector = injector.createChild({
26
+ owner: creatorService,
27
+ })
35
28
 
36
- try {
37
- return await tryCreateComponent()
38
- } catch (error) {
39
- if (props.error) {
40
- element.appendChild(
41
- props.error(error, async () => {
42
- await tryCreateComponent()
43
- }),
44
- )
29
+ container.innerHTML = ''
30
+
31
+ creatorService.create({
32
+ api: props.api,
33
+ rootElement: container,
34
+ injector: childInjector,
35
+ })
36
+
37
+ destroyFn = () => creatorService.destroy?.({ api: props.api, injector: childInjector })
45
38
  }
39
+
40
+ queueMicrotask(() => {
41
+ tryCreateComponent().catch((error: unknown) => {
42
+ if (props.error && containerRef.current) {
43
+ containerRef.current.appendChild(
44
+ props.error(error, async () => {
45
+ await tryCreateComponent()
46
+ }),
47
+ )
48
+ }
49
+ })
50
+ })
51
+
52
+ return {
53
+ [Symbol.asyncDispose]: async () => destroyFn?.(),
54
+ }
55
+ })
56
+
57
+ // Propagate prop updates to the inner MFE element (for Shade-based MFEs)
58
+ const mfeElement = containerRef.current?.firstElementChild as
59
+ | (HTMLElement & { props?: unknown; updateComponent?: () => void })
60
+ | null
61
+ if (mfeElement?.updateComponent) {
62
+ mfeElement.props = props.api
63
+ mfeElement.updateComponent()
46
64
  }
47
- },
48
- render: ({ props }) => {
49
- return props.loader || null
65
+
66
+ return (
67
+ <div ref={containerRef} style={{ display: 'contents' }}>
68
+ {props.loader || null}
69
+ </div>
70
+ )
50
71
  },
51
72
  })