@servicetitan/web-components 36.3.0 → 36.4.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/dist/css-injector/index.d.ts +3 -0
- package/dist/css-injector/index.d.ts.map +1 -0
- package/dist/css-injector/index.js +4 -0
- package/dist/css-injector/index.js.map +1 -0
- package/dist/css-injector/mfe-style-registry.d.ts +14 -0
- package/dist/css-injector/mfe-style-registry.d.ts.map +1 -0
- package/dist/css-injector/mfe-style-registry.js +63 -0
- package/dist/css-injector/mfe-style-registry.js.map +1 -0
- package/dist/css-injector/with-css-injector.d.ts +6 -0
- package/dist/css-injector/with-css-injector.d.ts.map +1 -0
- package/dist/css-injector/with-css-injector.js +38 -0
- package/dist/css-injector/with-css-injector.js.map +1 -0
- package/dist/globals.d.ts +3 -3
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +19 -4
- package/dist/globals.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/register-headless.d.ts.map +1 -1
- package/dist/register-headless.js +1 -0
- package/dist/register-headless.js.map +1 -1
- package/dist/register.d.ts.map +1 -1
- package/dist/register.js +43 -37
- package/dist/register.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +2 -2
- package/dist/render.js.map +1 -1
- package/dist/utils/get-bundle-info.d.ts.map +1 -1
- package/dist/utils/get-bundle-info.js +11 -4
- package/dist/utils/get-bundle-info.js.map +1 -1
- package/dist/utils/get-entry-points.d.ts +1 -0
- package/dist/utils/get-entry-points.d.ts.map +1 -1
- package/dist/utils/get-entry-points.js.map +1 -1
- package/package.json +23 -12
- package/src/__mocks__/register-anvil2-harness.tsx +49 -0
- package/src/__mocks__/test-component.tsx +9 -2
- package/src/__tests__/loader.test.tsx +32 -0
- package/src/__tests__/register-anvil2.test.tsx +21 -56
- package/src/css-injector/__tests__/mfe-style-registry.test.ts +163 -0
- package/src/css-injector/__tests__/with-css-injector.test.tsx +128 -0
- package/src/css-injector/index.ts +2 -0
- package/src/css-injector/mfe-style-registry.ts +48 -0
- package/src/css-injector/with-css-injector.tsx +40 -0
- package/src/globals.ts +30 -10
- package/src/index.ts +1 -0
- package/src/register-headless.ts +1 -0
- package/src/register.tsx +54 -43
- package/src/render.ts +2 -3
- package/src/utils/get-bundle-info.ts +12 -5
- package/src/utils/get-entry-points.ts +1 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { FC } from 'react';
|
|
3
|
+
import { useMFEMetadataContext } from '../../contexts/mfe-metadata-context';
|
|
4
|
+
import { MfeStyleRegistry } from '../mfe-style-registry';
|
|
5
|
+
import { withCssInjector } from '../with-css-injector';
|
|
6
|
+
|
|
7
|
+
jest.mock('../../contexts/mfe-metadata-context');
|
|
8
|
+
jest.mock('../../globals', () => ({
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
10
|
+
WEB_COMPONENT_NAME: 'test-mfe',
|
|
11
|
+
}));
|
|
12
|
+
jest.mock('../mfe-style-registry');
|
|
13
|
+
|
|
14
|
+
describe(`[web-components] ${withCssInjector.name}`, () => {
|
|
15
|
+
const innerTestId = 'inner';
|
|
16
|
+
const innerLabel = 'foo';
|
|
17
|
+
const InnerComponent: FC<{ label: string }> = ({ label }) => (
|
|
18
|
+
<div data-testid={innerTestId}>{label}</div>
|
|
19
|
+
);
|
|
20
|
+
const WrappedComponent = withCssInjector(InnerComponent);
|
|
21
|
+
|
|
22
|
+
let registry: jest.Mocked<MfeStyleRegistry>;
|
|
23
|
+
let shadowRoot: ShadowRoot;
|
|
24
|
+
let portalShadowRoot: ShadowRoot;
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
jest.clearAllMocks();
|
|
28
|
+
delete globalThis.__mfeSelfHosted__;
|
|
29
|
+
registry = {
|
|
30
|
+
observeRoot: jest.fn(),
|
|
31
|
+
unobserveRoot: jest.fn(),
|
|
32
|
+
} as unknown as jest.Mocked<MfeStyleRegistry>;
|
|
33
|
+
jest.mocked(MfeStyleRegistry.for).mockReturnValue(registry);
|
|
34
|
+
shadowRoot = document.createElement('div').attachShadow({ mode: 'open' });
|
|
35
|
+
portalShadowRoot = document.createElement('span').attachShadow({ mode: 'open' });
|
|
36
|
+
jest.mocked(useMFEMetadataContext).mockImplementation(() => ({
|
|
37
|
+
shadowRoot,
|
|
38
|
+
portalShadowRoot,
|
|
39
|
+
}));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const subject = () => render(<WrappedComponent label={innerLabel} />);
|
|
43
|
+
|
|
44
|
+
test('renders the wrapped component with its props', () => {
|
|
45
|
+
const { getByTestId } = subject();
|
|
46
|
+
|
|
47
|
+
expect(getByTestId(innerTestId).textContent).toBe(innerLabel);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('retrieves the registry for the component', () => {
|
|
51
|
+
subject();
|
|
52
|
+
|
|
53
|
+
expect(MfeStyleRegistry.for).toHaveBeenCalledWith('test-mfe');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('observes shadow root', () => {
|
|
57
|
+
subject();
|
|
58
|
+
|
|
59
|
+
expect(registry.observeRoot).toHaveBeenCalledWith(shadowRoot);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('observes portal shadow root', () => {
|
|
63
|
+
subject();
|
|
64
|
+
|
|
65
|
+
expect(registry.observeRoot).toHaveBeenCalledWith(portalShadowRoot);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('when self-hosted', () => {
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
globalThis.__mfeSelfHosted__ = true;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('observes document', () => {
|
|
74
|
+
subject();
|
|
75
|
+
|
|
76
|
+
expect(registry.observeRoot).toHaveBeenCalledWith(document);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('unobserves document on unmount', () => {
|
|
80
|
+
const { unmount } = subject();
|
|
81
|
+
|
|
82
|
+
unmount();
|
|
83
|
+
|
|
84
|
+
expect(registry.unobserveRoot).toHaveBeenCalledWith(document);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('when unmounted', () => {
|
|
89
|
+
test('unobserves shadow root', () => {
|
|
90
|
+
const { unmount } = subject();
|
|
91
|
+
|
|
92
|
+
unmount();
|
|
93
|
+
|
|
94
|
+
expect(registry.unobserveRoot).toHaveBeenCalledWith(shadowRoot);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('unobserves portal shadow root', () => {
|
|
98
|
+
const { unmount } = subject();
|
|
99
|
+
|
|
100
|
+
unmount();
|
|
101
|
+
|
|
102
|
+
expect(registry.unobserveRoot).toHaveBeenCalledWith(portalShadowRoot);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('in production', () => {
|
|
107
|
+
const originalEnv = process.env.NODE_ENV;
|
|
108
|
+
|
|
109
|
+
beforeEach(() => {
|
|
110
|
+
process.env.NODE_ENV = 'production';
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
afterEach(() => {
|
|
114
|
+
process.env.NODE_ENV = originalEnv;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('returns Component directly', () => {
|
|
118
|
+
expect.assertions(1);
|
|
119
|
+
|
|
120
|
+
jest.isolateModules(() => {
|
|
121
|
+
const { withCssInjector: freshWithCssInjector } = require('../with-css-injector');
|
|
122
|
+
const Dummy: FC = () => <div />;
|
|
123
|
+
|
|
124
|
+
expect(freshWithCssInjector(Dummy)).toBe(Dummy);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
3
|
+
var __mfeStyles__: Record<string, MfeStyleRegistry> | undefined;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
type StyleRoot = ShadowRoot | Document;
|
|
7
|
+
|
|
8
|
+
export class MfeStyleRegistry {
|
|
9
|
+
readonly sheets = new Map<string, CSSStyleSheet>();
|
|
10
|
+
readonly roots = new Set<StyleRoot>();
|
|
11
|
+
|
|
12
|
+
// Per-component state stored on globalThis.__mfeStyles__ so it survives Vite HMR module reloads.
|
|
13
|
+
static for(componentName: string): MfeStyleRegistry {
|
|
14
|
+
globalThis.__mfeStyles__ ??= {};
|
|
15
|
+
globalThis.__mfeStyles__[componentName] ??= new MfeStyleRegistry();
|
|
16
|
+
return globalThis.__mfeStyles__[componentName];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
injectSheet(sheetId: string, css: string) {
|
|
20
|
+
let sheet = this.sheets.get(sheetId);
|
|
21
|
+
if (sheet) {
|
|
22
|
+
sheet.replaceSync(css);
|
|
23
|
+
} else {
|
|
24
|
+
sheet = new CSSStyleSheet();
|
|
25
|
+
sheet.replaceSync(css);
|
|
26
|
+
this.sheets.set(sheetId, sheet);
|
|
27
|
+
for (const root of this.roots) {
|
|
28
|
+
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
observeRoot(root: StyleRoot) {
|
|
34
|
+
if (this.roots.has(root)) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
this.roots.add(root);
|
|
38
|
+
root.adoptedStyleSheets ??= [];
|
|
39
|
+
root.adoptedStyleSheets = [...root.adoptedStyleSheets, ...this.sheets.values()];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
unobserveRoot(root: StyleRoot) {
|
|
43
|
+
this.roots.delete(root);
|
|
44
|
+
const sheets = new Set(this.sheets.values());
|
|
45
|
+
root.adoptedStyleSheets ??= [];
|
|
46
|
+
root.adoptedStyleSheets = root.adoptedStyleSheets.filter(s => !sheets.has(s));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ComponentType, useLayoutEffect } from 'react';
|
|
2
|
+
import { useMFEMetadataContext } from '../contexts/mfe-metadata-context';
|
|
3
|
+
import { WEB_COMPONENT_NAME } from '../globals';
|
|
4
|
+
import { MfeStyleRegistry } from './mfe-style-registry';
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
8
|
+
var __mfeSelfHosted__: boolean | undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* HOC wrapping the MFE's root component in register(). Adopts sheets from the registry into
|
|
13
|
+
* the MFE's shadow roots. Only active in development; eliminated by Vite in production builds.
|
|
14
|
+
*/
|
|
15
|
+
export function withCssInjector<P extends object>(Component: ComponentType<P>): ComponentType<P> {
|
|
16
|
+
if (process.env.NODE_ENV === 'production') {
|
|
17
|
+
return Component;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return function CssInjectorWrapper(props: P) {
|
|
21
|
+
const { shadowRoot, portalShadowRoot } = useMFEMetadataContext();
|
|
22
|
+
|
|
23
|
+
useLayoutEffect(() => {
|
|
24
|
+
const registry = MfeStyleRegistry.for(WEB_COMPONENT_NAME);
|
|
25
|
+
const roots = [
|
|
26
|
+
shadowRoot,
|
|
27
|
+
portalShadowRoot,
|
|
28
|
+
...(globalThis.__mfeSelfHosted__ ? [document] : []),
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
roots.forEach(root => registry.observeRoot(root));
|
|
32
|
+
|
|
33
|
+
return () => {
|
|
34
|
+
roots.forEach(root => registry.unobserveRoot(root));
|
|
35
|
+
};
|
|
36
|
+
}, [shadowRoot, portalShadowRoot]);
|
|
37
|
+
|
|
38
|
+
return <Component {...props} />;
|
|
39
|
+
};
|
|
40
|
+
}
|
package/src/globals.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/naming-convention */
|
|
2
|
+
// eslint-disable-next-line spaced-comment
|
|
3
|
+
/// <reference types="vite/client" /> — provides types for import.meta.env
|
|
2
4
|
import { ExposedDependencies } from '@servicetitan/startup-utils';
|
|
3
5
|
import { ExposedInstanceDependencies } from './common';
|
|
4
6
|
|
|
@@ -8,6 +10,13 @@ declare global {
|
|
|
8
10
|
export const WEB_COMPONENT_NAME: string | undefined;
|
|
9
11
|
}
|
|
10
12
|
|
|
13
|
+
/*
|
|
14
|
+
* Safely access a value that may not exist. Returns undefined instead of
|
|
15
|
+
* throwing a ReferenceError. Used below to try global constants first
|
|
16
|
+
* (set by webpack DefinePlugin), then fall back to import.meta.env
|
|
17
|
+
* (set by Vite's define config). One of these will always be defined
|
|
18
|
+
* depending on which bundler built the code.
|
|
19
|
+
*/
|
|
11
20
|
function get<T>(fetch: () => T) {
|
|
12
21
|
try {
|
|
13
22
|
return fetch();
|
|
@@ -16,17 +25,28 @@ function get<T>(fetch: () => T) {
|
|
|
16
25
|
}
|
|
17
26
|
}
|
|
18
27
|
|
|
19
|
-
const _EXPOSED_DEPENDENCIES =
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
const _EXPOSED_DEPENDENCIES =
|
|
29
|
+
get(() => {
|
|
30
|
+
return EXPOSED_DEPENDENCIES;
|
|
31
|
+
}) ??
|
|
32
|
+
get(() => {
|
|
33
|
+
return import.meta.env.EXPOSED_DEPENDENCIES;
|
|
34
|
+
});
|
|
22
35
|
|
|
23
|
-
const _WEB_COMPONENT_NAME =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
});
|
|
36
|
+
const _WEB_COMPONENT_NAME =
|
|
37
|
+
get(() => {
|
|
38
|
+
return WEB_COMPONENT_NAME;
|
|
39
|
+
}) ??
|
|
40
|
+
get(() => {
|
|
41
|
+
return import.meta.env.WEB_COMPONENT_NAME;
|
|
42
|
+
});
|
|
43
|
+
const _EXPOSED_INSTANCE_DEPENDENCIES =
|
|
44
|
+
get(() => {
|
|
45
|
+
return EXPOSED_INSTANCE_DEPENDENCIES;
|
|
46
|
+
}) ??
|
|
47
|
+
get(() => {
|
|
48
|
+
return import.meta.env.EXPOSED_INSTANCE_DEPENDENCIES;
|
|
49
|
+
});
|
|
30
50
|
|
|
31
51
|
export {
|
|
32
52
|
_EXPOSED_DEPENDENCIES as EXPOSED_DEPENDENCIES,
|
package/src/index.ts
CHANGED
package/src/register-headless.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { LDService } from '@servicetitan/launchdarkly-service';
|
|
|
2
2
|
import type { Log } from '@servicetitan/log-service';
|
|
3
3
|
import type { Entries, IWebComponent } from './common';
|
|
4
4
|
import type { EventBus } from './event-bus';
|
|
5
|
+
import { WEB_COMPONENT_NAME } from './globals';
|
|
5
6
|
import { Serializable } from './types';
|
|
6
7
|
|
|
7
8
|
type HeadlessCallbackArgs<TMfeData, TEventBus extends EventBus = EventBus> = Pick<
|
package/src/register.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { type LDService } from '@servicetitan/launchdarkly-service';
|
|
|
2
2
|
import { Log } from '@servicetitan/log-service';
|
|
3
3
|
import { Provider } from '@servicetitan/react-ioc';
|
|
4
4
|
import { ExposedDependencies } from '@servicetitan/startup-utils';
|
|
5
|
-
import { ComponentType, FC, ReactElement, useEffect } from 'react';
|
|
5
|
+
import { ComponentType, FC, PropsWithChildren, ReactElement, useCallback, useEffect } from 'react';
|
|
6
6
|
import {
|
|
7
7
|
BASENAME_TOKEN,
|
|
8
8
|
Entries,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from './common';
|
|
16
16
|
import { MFEDataContext } from './contexts/mfe-data-context';
|
|
17
17
|
import { MFEMetadataContext } from './contexts/mfe-metadata-context';
|
|
18
|
+
import { withCssInjector } from './css-injector';
|
|
18
19
|
import { EVENT_BUS_TOKEN, EventBus } from './event-bus';
|
|
19
20
|
import { WEB_COMPONENT_NAME } from './globals';
|
|
20
21
|
import { HistoryManager } from './history-manager';
|
|
@@ -40,7 +41,50 @@ export interface RegisterOptions {
|
|
|
40
41
|
sharedDependenciesNames?: string[];
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
const Anvil1Providers: FC<{ root: ShadowRoot; portal: ShadowRoot } & PropsWithChildren> = ({
|
|
45
|
+
root,
|
|
46
|
+
portal,
|
|
47
|
+
children,
|
|
48
|
+
}) => {
|
|
49
|
+
const getTablePopupProps = useCallback(
|
|
50
|
+
(props: object) => ({
|
|
51
|
+
...props,
|
|
52
|
+
appendTo: root as unknown as HTMLElement,
|
|
53
|
+
}),
|
|
54
|
+
[root]
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const { DefaultPortalContext, TablePopupPropsContext } =
|
|
59
|
+
require('@servicetitan/design-system') as typeof import('@servicetitan/design-system');
|
|
60
|
+
return (
|
|
61
|
+
<DefaultPortalContext.Provider value={portal as unknown as HTMLElement}>
|
|
62
|
+
<TablePopupPropsContext.Provider value={getTablePopupProps}>
|
|
63
|
+
{children}
|
|
64
|
+
</TablePopupPropsContext.Provider>
|
|
65
|
+
</DefaultPortalContext.Provider>
|
|
66
|
+
);
|
|
67
|
+
} catch {
|
|
68
|
+
return children;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const Anvil2Providers: FC<{ portalAnvilContainer: HTMLDivElement } & PropsWithChildren> = ({
|
|
73
|
+
portalAnvilContainer,
|
|
74
|
+
children,
|
|
75
|
+
}) => {
|
|
76
|
+
try {
|
|
77
|
+
const { PortalProvider } =
|
|
78
|
+
require('@servicetitan/anvil2') as typeof import('@servicetitan/anvil2');
|
|
79
|
+
return <PortalProvider root={portalAnvilContainer}>{children}</PortalProvider>;
|
|
80
|
+
} catch {
|
|
81
|
+
return children;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
43
85
|
export function register(Component: ComponentType, light: boolean, options?: RegisterOptions) {
|
|
86
|
+
const WrappedComponent = withCssInjector(Component);
|
|
87
|
+
|
|
44
88
|
class WebComponent extends HTMLElement implements IWebComponent {
|
|
45
89
|
private root?: ShadowRoot;
|
|
46
90
|
private portal?: ShadowRoot;
|
|
@@ -165,47 +209,14 @@ export function register(Component: ComponentType, light: boolean, options?: Reg
|
|
|
165
209
|
return elements;
|
|
166
210
|
};
|
|
167
211
|
|
|
168
|
-
private
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return (
|
|
177
|
-
<DefaultPortalContext.Provider value={this.portal as unknown as HTMLElement}>
|
|
178
|
-
<TablePopupPropsContext.Provider
|
|
179
|
-
value={props => ({
|
|
180
|
-
...props,
|
|
181
|
-
appendTo: this.root as unknown as HTMLElement,
|
|
182
|
-
})}
|
|
183
|
-
>
|
|
184
|
-
{children}
|
|
185
|
-
</TablePopupPropsContext.Provider>
|
|
186
|
-
</DefaultPortalContext.Provider>
|
|
187
|
-
);
|
|
188
|
-
} catch {
|
|
189
|
-
return children;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
private withAnvil2Providers(children: ReactElement): ReactElement {
|
|
194
|
-
try {
|
|
195
|
-
/**
|
|
196
|
-
* Note, the webpack.IgnorePlugin must be configured to ignore this dependency.
|
|
197
|
-
* @see {@link file://./../../startup/src/webpack/configs/plugins/ignore-plugin/ignore-plugin.ts}
|
|
198
|
-
*/
|
|
199
|
-
const { PortalProvider } =
|
|
200
|
-
require('@servicetitan/anvil2') as typeof import('@servicetitan/anvil2');
|
|
201
|
-
return <PortalProvider root={this.portalAnvilContainer}>{children}</PortalProvider>;
|
|
202
|
-
} catch {
|
|
203
|
-
return children;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
private withProviders(children: ReactElement): ReactElement {
|
|
208
|
-
return this.withAnvil2Providers(this.withAnvil1Providers(children));
|
|
212
|
+
private withProviders(children: ReactElement) {
|
|
213
|
+
return (
|
|
214
|
+
<Anvil2Providers portalAnvilContainer={this.portalAnvilContainer!}>
|
|
215
|
+
<Anvil1Providers root={this.root!} portal={this.portal!}>
|
|
216
|
+
{children}
|
|
217
|
+
</Anvil1Providers>
|
|
218
|
+
</Anvil2Providers>
|
|
219
|
+
);
|
|
209
220
|
}
|
|
210
221
|
|
|
211
222
|
private render = () => {
|
|
@@ -264,7 +275,7 @@ export function register(Component: ComponentType, light: boolean, options?: Reg
|
|
|
264
275
|
>
|
|
265
276
|
<MFEDataContext.Provider value={{ ...mfeData }}>
|
|
266
277
|
<MFEMetadataContext.Provider value={metadata}>
|
|
267
|
-
{this.withProviders(<
|
|
278
|
+
{this.withProviders(<WrappedComponent {...mfeData} />)}
|
|
268
279
|
{this.onReady && <RenderCallback callback={this.onReady} />}
|
|
269
280
|
</MFEMetadataContext.Provider>
|
|
270
281
|
</MFEDataContext.Provider>
|
package/src/render.ts
CHANGED
|
@@ -6,14 +6,13 @@ import type ReactDOMClient from 'react-dom/client';
|
|
|
6
6
|
let createRoot: typeof ReactDOMClient.createRoot;
|
|
7
7
|
try {
|
|
8
8
|
/**
|
|
9
|
-
* Note,
|
|
10
|
-
* @see {@link file://./../../startup/src/
|
|
9
|
+
* Note, bundlers must be configured to ignore this dependency.
|
|
10
|
+
* @see {@link file://./../../startup/src/core/check-resource/is-managed-react-dom-client-dependency.ts}
|
|
11
11
|
*/
|
|
12
12
|
createRoot = require('react-dom/client').createRoot;
|
|
13
13
|
} catch {
|
|
14
14
|
// ignore
|
|
15
15
|
}
|
|
16
|
-
|
|
17
16
|
interface RenderOptions {
|
|
18
17
|
legacyRoot?: boolean;
|
|
19
18
|
}
|
|
@@ -78,14 +78,13 @@ export async function getBundleInfo({
|
|
|
78
78
|
resolvedBundleType = embed ? 'light' : 'full';
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
const
|
|
82
|
-
`${exactPackageUrl}/dist/bundle/${resolvedBundleType}/${path}`;
|
|
81
|
+
const bundleBaseUrl = `${exactPackageUrl}/dist/bundle/${resolvedBundleType}`;
|
|
83
82
|
|
|
84
83
|
let entrypoints: EntryPoints;
|
|
85
84
|
if (metadata.entrypoints?.[resolvedBundleType]) {
|
|
86
85
|
entrypoints = metadata.entrypoints[resolvedBundleType];
|
|
87
86
|
} else {
|
|
88
|
-
entrypoints = await getEntrypoints(
|
|
87
|
+
entrypoints = await getEntrypoints(`${bundleBaseUrl}/entrypoints.json`, requestCache);
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
const disposer = () => {
|
|
@@ -97,9 +96,17 @@ export async function getBundleInfo({
|
|
|
97
96
|
}
|
|
98
97
|
};
|
|
99
98
|
|
|
99
|
+
let baseUrl = bundleBaseUrl;
|
|
100
|
+
// Per-MFE dev-server port only happens during local development
|
|
101
|
+
if (process.env.NODE_ENV !== 'production' && entrypoints.port) {
|
|
102
|
+
const url = new URL(baseUrl);
|
|
103
|
+
url.port = String(entrypoints.port);
|
|
104
|
+
baseUrl = url.toString();
|
|
105
|
+
}
|
|
106
|
+
|
|
100
107
|
const urls: EntryPoints = {
|
|
101
|
-
css: entrypoints.css.map(
|
|
102
|
-
js: entrypoints.js.map(
|
|
108
|
+
css: entrypoints.css.map(path => `${baseUrl}/${path}`),
|
|
109
|
+
js: entrypoints.js.map(path => `${baseUrl}/${path}`),
|
|
103
110
|
};
|
|
104
111
|
|
|
105
112
|
const elementName = resolvedBundleType === 'headless' ? `headless-${name}` : name;
|