@servicetitan/web-components 36.1.1-beta.1 → 36.2.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/globals.d.ts +3 -3
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +4 -19
- package/dist/globals.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/register-headless.d.ts.map +1 -1
- package/dist/register-headless.js +0 -1
- package/dist/register-headless.js.map +1 -1
- package/dist/register.d.ts.map +1 -1
- package/dist/register.js +36 -38
- package/dist/register.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +11 -3
- 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 +4 -10
- package/dist/utils/get-bundle-info.js.map +1 -1
- package/dist/utils/get-entry-points.d.ts +0 -1
- package/dist/utils/get-entry-points.d.ts.map +1 -1
- package/dist/utils/get-entry-points.js.map +1 -1
- package/dist/utils/get-ld-service-token.d.ts +3 -2
- package/dist/utils/get-ld-service-token.d.ts.map +1 -1
- package/dist/utils/get-ld-service-token.js +7 -2
- package/dist/utils/get-ld-service-token.js.map +1 -1
- package/package.json +12 -21
- package/src/__mocks__/test-component.tsx +2 -9
- package/src/__tests__/event-bus-types.test.ts +2 -1
- package/src/__tests__/register-anvil2.test.tsx +64 -11
- package/src/globals.ts +10 -30
- package/src/index.ts +0 -1
- package/src/register-headless.ts +0 -1
- package/src/register.tsx +42 -48
- package/src/render.ts +14 -3
- package/src/utils/get-bundle-info.ts +5 -11
- package/src/utils/get-entry-points.ts +0 -1
- package/src/utils/get-ld-service-token.ts +10 -2
- package/dist/with-css-injector.d.ts +0 -3
- package/dist/with-css-injector.d.ts.map +0 -1
- package/dist/with-css-injector.js +0 -52
- package/dist/with-css-injector.js.map +0 -1
- package/src/__mocks__/register-anvil2-harness.tsx +0 -49
- package/src/__tests__/register-anvil2-optional.test.tsx +0 -32
- package/src/__tests__/with-css-injector.test.tsx +0 -152
- package/src/with-css-injector.tsx +0 -45
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servicetitan/web-components",
|
|
3
|
-
"version": "36.
|
|
3
|
+
"version": "36.2.0",
|
|
4
4
|
"description": "Framework to build, register, and load Microfrontends into host applications",
|
|
5
5
|
"homepage": "https://docs.st.dev/docs/frontend/uikit/web-components",
|
|
6
6
|
"repository": {
|
|
@@ -10,34 +10,24 @@
|
|
|
10
10
|
},
|
|
11
11
|
"main": "./dist/index.js",
|
|
12
12
|
"typings": "./dist/index.d.ts",
|
|
13
|
-
"exports": {
|
|
14
|
-
".": {
|
|
15
|
-
"types": "./dist/index.d.ts",
|
|
16
|
-
"default": "./dist/index.js"
|
|
17
|
-
},
|
|
18
|
-
"./register-headless": {
|
|
19
|
-
"types": "./dist/register-headless.d.ts",
|
|
20
|
-
"default": "./dist/register-headless.js"
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
13
|
"sideEffects": false,
|
|
24
14
|
"files": [
|
|
25
15
|
"dist",
|
|
26
16
|
"src"
|
|
27
17
|
],
|
|
28
18
|
"dependencies": {
|
|
29
|
-
"@servicetitan/error-boundary": "36.
|
|
30
|
-
"@servicetitan/startup-utils": "36.
|
|
19
|
+
"@servicetitan/error-boundary": "36.2.0",
|
|
20
|
+
"@servicetitan/startup-utils": "36.2.0",
|
|
31
21
|
"lodash.get": "~4.4.2",
|
|
32
22
|
"mitt": "~3.0.1",
|
|
33
23
|
"semver": "~7.7.4"
|
|
34
24
|
},
|
|
35
25
|
"devDependencies": {
|
|
36
|
-
"@servicetitan/anvil2": "^
|
|
26
|
+
"@servicetitan/anvil2": "^3.0.4",
|
|
37
27
|
"@servicetitan/design-system": "~14.5.3",
|
|
38
|
-
"@servicetitan/launchdarkly-service": "36.
|
|
39
|
-
"@servicetitan/log-service": "36.
|
|
40
|
-
"@servicetitan/react-ioc": "36.
|
|
28
|
+
"@servicetitan/launchdarkly-service": "36.2.0",
|
|
29
|
+
"@servicetitan/log-service": "36.2.0",
|
|
30
|
+
"@servicetitan/react-ioc": "36.2.0",
|
|
41
31
|
"@testing-library/dom": "^10.4.1",
|
|
42
32
|
"@testing-library/jest-dom": "^6.9.1",
|
|
43
33
|
"@testing-library/react": "^16.3.2",
|
|
@@ -55,9 +45,9 @@
|
|
|
55
45
|
"peerDependencies": {
|
|
56
46
|
"@servicetitan/anvil2": ">=1.5.0",
|
|
57
47
|
"@servicetitan/design-system": ">=14.5.3",
|
|
58
|
-
"@servicetitan/launchdarkly-service": "36.
|
|
59
|
-
"@servicetitan/log-service": "36.
|
|
60
|
-
"@servicetitan/react-ioc": "36.
|
|
48
|
+
"@servicetitan/launchdarkly-service": "36.2.0",
|
|
49
|
+
"@servicetitan/log-service": "36.2.0",
|
|
50
|
+
"@servicetitan/react-ioc": "36.2.0",
|
|
61
51
|
"history": "~4.10.1",
|
|
62
52
|
"react": ">=17.0.2",
|
|
63
53
|
"react-dom": ">=17.0.2"
|
|
@@ -78,5 +68,6 @@
|
|
|
78
68
|
},
|
|
79
69
|
"cli": {
|
|
80
70
|
"webpack": false
|
|
81
|
-
}
|
|
71
|
+
},
|
|
72
|
+
"gitHead": "87254b2caf60b20eb46c33b40ffb4dd05ad3e0f7"
|
|
82
73
|
}
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import { TablePopupPropsContext } from '@servicetitan/design-system';
|
|
2
|
-
import {
|
|
2
|
+
import { FC, useContext } from 'react';
|
|
3
3
|
import { useMFEDataContext, useMFEMetadataContext } from '../contexts';
|
|
4
4
|
|
|
5
5
|
jest.mock('@progress/kendo-licensing', () => ({ validatePackage: jest.fn() }));
|
|
6
6
|
|
|
7
|
-
const FallbackContext = createContext(undefined);
|
|
8
|
-
|
|
9
7
|
export const MockComponent: FC<Record<string, any>> = props => {
|
|
10
8
|
const data = useMFEDataContext();
|
|
11
9
|
const metadata = useMFEMetadataContext();
|
|
12
|
-
|
|
13
|
-
* TablePopupPropsContext is undefined when a test mocks
|
|
14
|
-
* @servicetitan/design-system as an empty object. Fall back to
|
|
15
|
-
* a no-op context so the component renders without it.
|
|
16
|
-
*/
|
|
17
|
-
const popupProps = useContext(TablePopupPropsContext ?? FallbackContext);
|
|
10
|
+
const popupProps = useContext(TablePopupPropsContext);
|
|
18
11
|
const appendTo = popupProps?.({}).appendTo;
|
|
19
12
|
|
|
20
13
|
return (
|
|
@@ -27,7 +27,8 @@ describe(`[web-components] ${EventBus.name}`, () => {
|
|
|
27
27
|
describe('EventBus<T>', () => {
|
|
28
28
|
test('rejects invalid data type', () => {
|
|
29
29
|
// @ts-expect-error Types of property 'foo' are incompatible.
|
|
30
|
-
eventBus = EventBus<{ foo: () => void }>;
|
|
30
|
+
const eventBus = EventBus<{ foo: () => void }>;
|
|
31
|
+
expect(eventBus).toBeDefined();
|
|
31
32
|
});
|
|
32
33
|
});
|
|
33
34
|
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import '@testing-library/jest-dom';
|
|
2
|
-
import { within } from '@testing-library/react';
|
|
2
|
+
import { render, within } from '@testing-library/react';
|
|
3
3
|
import { Fragment } from 'react';
|
|
4
|
-
import { expectStylesInDom } from '../__mocks__';
|
|
5
|
-
import {
|
|
6
|
-
createRegisterHarness,
|
|
7
|
-
optionalLibraryWrappers,
|
|
8
|
-
} from '../__mocks__/register-anvil2-harness';
|
|
4
|
+
import { expectStylesInDom, MockComponent, registerComponent } from '../__mocks__';
|
|
9
5
|
|
|
10
6
|
function mockComponent(name: string) {
|
|
11
7
|
return {
|
|
@@ -41,7 +37,30 @@ jest.mock('@servicetitan/design-system', () => ({
|
|
|
41
37
|
}));
|
|
42
38
|
|
|
43
39
|
describe('[web-components] register', () => {
|
|
44
|
-
const
|
|
40
|
+
const componentName = 'mock-anvil2-app';
|
|
41
|
+
|
|
42
|
+
class Page {
|
|
43
|
+
static get componentShadowRoot() {
|
|
44
|
+
return document.querySelector(componentName)!.shadowRoot!;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static get portalShadowRoot() {
|
|
48
|
+
return document.querySelector('body')!.lastElementChild!.shadowRoot!;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
beforeAll(() => {
|
|
53
|
+
registerComponent(MockComponent, componentName, {
|
|
54
|
+
sharedDependenciesNames: ['@servicetitan/anvil2'],
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const subject = () => {
|
|
59
|
+
jest.spyOn(console, 'error').mockImplementation(jest.fn()); // suppress "ReactDOM.render deprecated" error
|
|
60
|
+
const Component: any = componentName;
|
|
61
|
+
const handleRef = (webComponent: any) => webComponent?.provide({ foo: 'bar' }); // triggers rendering
|
|
62
|
+
return render(<Component ref={handleRef} />);
|
|
63
|
+
};
|
|
45
64
|
|
|
46
65
|
describe('when Anvil2 bundle is present in the DOM', () => {
|
|
47
66
|
let cssLink: HTMLLinkElement;
|
|
@@ -71,10 +90,44 @@ describe('[web-components] register', () => {
|
|
|
71
90
|
});
|
|
72
91
|
});
|
|
73
92
|
|
|
74
|
-
|
|
75
|
-
|
|
93
|
+
function itWrapsComponentWithin(name: string, expected: boolean = true) {
|
|
94
|
+
test(`${expected ? 'wraps' : 'does not wrap'} component within ${name}`, () => {
|
|
95
|
+
subject();
|
|
96
|
+
|
|
97
|
+
const element = within(Page.componentShadowRoot as any).queryByText(`<${name}>`);
|
|
98
|
+
if (expected) {
|
|
99
|
+
expect(element).toBeInTheDocument();
|
|
100
|
+
} else {
|
|
101
|
+
expect(element).not.toBeInTheDocument();
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
['PortalProvider', 'DefaultPortalContext.Provider', 'TablePopupPropsContext.Provider'].forEach(
|
|
107
|
+
name => itWrapsComponentWithin(name)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
describe('when @servicetitan/anvil2 is not present', () => {
|
|
111
|
+
beforeAll(() => {
|
|
112
|
+
jest.resetModules();
|
|
113
|
+
jest.mock('@servicetitan/anvil2', () => {
|
|
114
|
+
throw new Error('Cannot find module');
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
itWrapsComponentWithin('PortalProvider', false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('when @servicetitan/design-system is not present', () => {
|
|
122
|
+
beforeAll(() => {
|
|
123
|
+
jest.resetModules();
|
|
124
|
+
jest.mock('@servicetitan/design-system', () => {
|
|
125
|
+
throw new Error('Cannot find module');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
76
128
|
|
|
77
|
-
|
|
78
|
-
|
|
129
|
+
['DefaultPortalContext.Provider', 'TablePopupPropsContext.Provider'].forEach(name =>
|
|
130
|
+
itWrapsComponentWithin(name, false)
|
|
131
|
+
);
|
|
79
132
|
});
|
|
80
133
|
});
|
package/src/globals.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
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
|
|
4
2
|
import { ExposedDependencies } from '@servicetitan/startup-utils';
|
|
5
3
|
import { ExposedInstanceDependencies } from './common';
|
|
6
4
|
|
|
@@ -10,13 +8,6 @@ declare global {
|
|
|
10
8
|
export const WEB_COMPONENT_NAME: string | undefined;
|
|
11
9
|
}
|
|
12
10
|
|
|
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
|
-
*/
|
|
20
11
|
function get<T>(fetch: () => T) {
|
|
21
12
|
try {
|
|
22
13
|
return fetch();
|
|
@@ -25,28 +16,17 @@ function get<T>(fetch: () => T) {
|
|
|
25
16
|
}
|
|
26
17
|
}
|
|
27
18
|
|
|
28
|
-
const _EXPOSED_DEPENDENCIES =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}) ??
|
|
32
|
-
get(() => {
|
|
33
|
-
return import.meta.env.EXPOSED_DEPENDENCIES;
|
|
34
|
-
});
|
|
19
|
+
const _EXPOSED_DEPENDENCIES = get(() => {
|
|
20
|
+
return EXPOSED_DEPENDENCIES;
|
|
21
|
+
});
|
|
35
22
|
|
|
36
|
-
const _WEB_COMPONENT_NAME =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
});
|
|
23
|
+
const _WEB_COMPONENT_NAME = get(() => {
|
|
24
|
+
return WEB_COMPONENT_NAME;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const _EXPOSED_INSTANCE_DEPENDENCIES = get(() => {
|
|
28
|
+
return EXPOSED_INSTANCE_DEPENDENCIES;
|
|
29
|
+
});
|
|
50
30
|
|
|
51
31
|
export {
|
|
52
32
|
_EXPOSED_DEPENDENCIES as EXPOSED_DEPENDENCIES,
|
package/src/index.ts
CHANGED
package/src/register-headless.ts
CHANGED
|
@@ -2,7 +2,6 @@ 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';
|
|
6
5
|
import { Serializable } from './types';
|
|
7
6
|
|
|
8
7
|
type HeadlessCallbackArgs<TMfeData, TEventBus extends EventBus = EventBus> = Pick<
|
package/src/register.tsx
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import * as Anvil2 from '@servicetitan/anvil2';
|
|
2
|
-
import * as Anvil1 from '@servicetitan/design-system';
|
|
3
1
|
import { type LDService } from '@servicetitan/launchdarkly-service';
|
|
4
2
|
import { Log } from '@servicetitan/log-service';
|
|
5
3
|
import { Provider } from '@servicetitan/react-ioc';
|
|
6
4
|
import { ExposedDependencies } from '@servicetitan/startup-utils';
|
|
7
|
-
import { ComponentType, FC,
|
|
5
|
+
import { ComponentType, FC, ReactElement, useEffect } from 'react';
|
|
8
6
|
import {
|
|
9
7
|
BASENAME_TOKEN,
|
|
10
8
|
Entries,
|
|
@@ -42,43 +40,6 @@ export interface RegisterOptions {
|
|
|
42
40
|
sharedDependenciesNames?: string[];
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
const Anvil1Providers: FC<{ root: ShadowRoot; portal: ShadowRoot } & PropsWithChildren> = ({
|
|
46
|
-
root,
|
|
47
|
-
portal,
|
|
48
|
-
children,
|
|
49
|
-
}) => {
|
|
50
|
-
const getTablePopupProps = useCallback(
|
|
51
|
-
(props: object) => ({
|
|
52
|
-
...props,
|
|
53
|
-
appendTo: root as unknown as HTMLElement,
|
|
54
|
-
}),
|
|
55
|
-
[root]
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
if (Anvil1.DefaultPortalContext === undefined || Anvil1.TablePopupPropsContext === undefined) {
|
|
59
|
-
return children;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<Anvil1.DefaultPortalContext.Provider value={portal as unknown as HTMLElement}>
|
|
64
|
-
<Anvil1.TablePopupPropsContext.Provider value={getTablePopupProps}>
|
|
65
|
-
{children}
|
|
66
|
-
</Anvil1.TablePopupPropsContext.Provider>
|
|
67
|
-
</Anvil1.DefaultPortalContext.Provider>
|
|
68
|
-
);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const Anvil2Providers: FC<{ portalAnvilContainer: HTMLDivElement } & PropsWithChildren> = ({
|
|
72
|
-
portalAnvilContainer,
|
|
73
|
-
children,
|
|
74
|
-
}) => {
|
|
75
|
-
if (Anvil2.PortalProvider === undefined) {
|
|
76
|
-
return children;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return <Anvil2.PortalProvider root={portalAnvilContainer}>{children}</Anvil2.PortalProvider>;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
43
|
export function register(Component: ComponentType, light: boolean, options?: RegisterOptions) {
|
|
83
44
|
class WebComponent extends HTMLElement implements IWebComponent {
|
|
84
45
|
private root?: ShadowRoot;
|
|
@@ -204,14 +165,47 @@ export function register(Component: ComponentType, light: boolean, options?: Reg
|
|
|
204
165
|
return elements;
|
|
205
166
|
};
|
|
206
167
|
|
|
207
|
-
private
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
168
|
+
private withAnvil1Providers(children: ReactElement): ReactElement {
|
|
169
|
+
try {
|
|
170
|
+
/**
|
|
171
|
+
* Note, the webpack.IgnorePlugin must be configured to ignore this dependency.
|
|
172
|
+
* @see {@link file://./../../startup/src/webpack/configs/plugins/ignore-plugin/ignore-plugin.ts}
|
|
173
|
+
*/
|
|
174
|
+
const { DefaultPortalContext, TablePopupPropsContext } =
|
|
175
|
+
require('@servicetitan/design-system') as typeof import('@servicetitan/design-system');
|
|
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));
|
|
215
209
|
}
|
|
216
210
|
|
|
217
211
|
private render = () => {
|
package/src/render.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
/* eslint-disable react/no-deprecated */
|
|
2
2
|
import { ReactElement } from 'react';
|
|
3
3
|
import ReactDOM from 'react-dom';
|
|
4
|
-
import
|
|
4
|
+
import type ReactDOMClient from 'react-dom/client';
|
|
5
|
+
|
|
6
|
+
let createRoot: typeof ReactDOMClient.createRoot;
|
|
7
|
+
try {
|
|
8
|
+
/**
|
|
9
|
+
* Note, the webpack.IgnorePlugin must be configured to ignore this dependency.
|
|
10
|
+
* @see {@link file://./../../startup/src/webpack/configs/plugins/ignore-plugin/ignore-plugin.ts}
|
|
11
|
+
*/
|
|
12
|
+
createRoot = require('react-dom/client').createRoot;
|
|
13
|
+
} catch {
|
|
14
|
+
// ignore
|
|
15
|
+
}
|
|
5
16
|
|
|
6
17
|
interface RenderOptions {
|
|
7
18
|
legacyRoot?: boolean;
|
|
@@ -17,11 +28,11 @@ export function render(
|
|
|
17
28
|
container: Element | DocumentFragment,
|
|
18
29
|
options?: RenderOptions
|
|
19
30
|
): RenderResult {
|
|
20
|
-
if (typeof
|
|
31
|
+
if (typeof createRoot !== 'function' || options?.legacyRoot) {
|
|
21
32
|
return renderLegacyRoot(element, container);
|
|
22
33
|
}
|
|
23
34
|
|
|
24
|
-
const root =
|
|
35
|
+
const root = createRoot(container);
|
|
25
36
|
root.render(element);
|
|
26
37
|
|
|
27
38
|
return {
|
|
@@ -78,13 +78,14 @@ export async function getBundleInfo({
|
|
|
78
78
|
resolvedBundleType = embed ? 'light' : 'full';
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
const
|
|
81
|
+
const withBaseUrl = (path: string) =>
|
|
82
|
+
`${exactPackageUrl}/dist/bundle/${resolvedBundleType}/${path}`;
|
|
82
83
|
|
|
83
84
|
let entrypoints: EntryPoints;
|
|
84
85
|
if (metadata.entrypoints?.[resolvedBundleType]) {
|
|
85
86
|
entrypoints = metadata.entrypoints[resolvedBundleType];
|
|
86
87
|
} else {
|
|
87
|
-
entrypoints = await getEntrypoints(
|
|
88
|
+
entrypoints = await getEntrypoints(withBaseUrl('entrypoints.json'), requestCache);
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
const disposer = () => {
|
|
@@ -96,16 +97,9 @@ export async function getBundleInfo({
|
|
|
96
97
|
}
|
|
97
98
|
};
|
|
98
99
|
|
|
99
|
-
let baseUrl = bundleBaseUrl;
|
|
100
|
-
if (entrypoints.port) {
|
|
101
|
-
const url = new URL(baseUrl);
|
|
102
|
-
url.port = String(entrypoints.port);
|
|
103
|
-
baseUrl = url.toString();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
100
|
const urls: EntryPoints = {
|
|
107
|
-
css: entrypoints.css.map(
|
|
108
|
-
js: entrypoints.js.map(
|
|
101
|
+
css: entrypoints.css.map(withBaseUrl),
|
|
102
|
+
js: entrypoints.js.map(withBaseUrl),
|
|
109
103
|
};
|
|
110
104
|
|
|
111
105
|
const elementName = resolvedBundleType === 'headless' ? `headless-${name}` : name;
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type LDService } from '@servicetitan/launchdarkly-service';
|
|
2
|
+
import { SymbolToken } from '@servicetitan/react-ioc';
|
|
3
|
+
|
|
4
|
+
let token: SymbolToken<LDService>;
|
|
5
|
+
try {
|
|
6
|
+
token = require('@servicetitan/launchdarkly-service').LD_SERVICE_TOKEN;
|
|
7
|
+
} catch {
|
|
8
|
+
// ignore
|
|
9
|
+
}
|
|
2
10
|
|
|
3
11
|
export function getLDServiceToken() {
|
|
4
|
-
return
|
|
12
|
+
return token;
|
|
5
13
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"with-css-injector.d.ts","sourceRoot":"","sources":["../src/with-css-injector.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAa,MAAM,OAAO,CAAC;AAIjD,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAwC/F"}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect } from 'react';
|
|
3
|
-
import { useMFEMetadataContext } from './contexts/mfe-metadata-context';
|
|
4
|
-
import { WEB_COMPONENT_NAME } from './globals';
|
|
5
|
-
export function withCssInjector(Component) {
|
|
6
|
-
return function CssInjectorWrapper(props) {
|
|
7
|
-
const { shadowRoot, portalShadowRoot } = useMFEMetadataContext();
|
|
8
|
-
useEffect(()=>{
|
|
9
|
-
var _window, ___mfeStyles__, _mfeStyles, _WEB_COMPONENT_NAME, _;
|
|
10
|
-
(___mfeStyles__ = (_window = window).__mfeStyles__) !== null && ___mfeStyles__ !== void 0 ? ___mfeStyles__ : _window.__mfeStyles__ = {};
|
|
11
|
-
const mfeStyles = window.__mfeStyles__;
|
|
12
|
-
(_ = (_mfeStyles = mfeStyles)[_WEB_COMPONENT_NAME = WEB_COMPONENT_NAME]) !== null && _ !== void 0 ? _ : _mfeStyles[_WEB_COMPONENT_NAME] = Object.assign(new EventTarget(), {
|
|
13
|
-
sheets: new Map()
|
|
14
|
-
});
|
|
15
|
-
const emitter = mfeStyles[WEB_COMPONENT_NAME];
|
|
16
|
-
/*
|
|
17
|
-
* In self-hosted standalone dev mode (set by index.html), also adopt sheets
|
|
18
|
-
* into document so host-only CSS vars and fonts propagate to the page.
|
|
19
|
-
*/ const roots = [
|
|
20
|
-
shadowRoot,
|
|
21
|
-
portalShadowRoot,
|
|
22
|
-
...window.__mfeSelfHosted__ ? [
|
|
23
|
-
document
|
|
24
|
-
] : []
|
|
25
|
-
];
|
|
26
|
-
function adoptSheet(sheet) {
|
|
27
|
-
for (const root of roots){
|
|
28
|
-
root.adoptedStyleSheets = [
|
|
29
|
-
...root.adoptedStyleSheets,
|
|
30
|
-
sheet
|
|
31
|
-
];
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
for (const sheet of emitter.sheets.values()){
|
|
35
|
-
adoptSheet(sheet);
|
|
36
|
-
}
|
|
37
|
-
const handleNewSheet = ({ detail: sheetId })=>{
|
|
38
|
-
adoptSheet(emitter.sheets.get(sheetId));
|
|
39
|
-
};
|
|
40
|
-
emitter.addEventListener('new-sheet', handleNewSheet);
|
|
41
|
-
return ()=>emitter.removeEventListener('new-sheet', handleNewSheet);
|
|
42
|
-
}, [
|
|
43
|
-
shadowRoot,
|
|
44
|
-
portalShadowRoot
|
|
45
|
-
]);
|
|
46
|
-
return /*#__PURE__*/ _jsx(Component, {
|
|
47
|
-
...props
|
|
48
|
-
});
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
//# sourceMappingURL=with-css-injector.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-css-injector.tsx"],"sourcesContent":["import { ComponentType, useEffect } from 'react';\nimport { useMFEMetadataContext } from './contexts/mfe-metadata-context';\nimport { WEB_COMPONENT_NAME } from './globals';\n\nexport function withCssInjector<P extends object>(Component: ComponentType<P>): ComponentType<P> {\n return function CssInjectorWrapper(props: P) {\n const { shadowRoot, portalShadowRoot } = useMFEMetadataContext();\n\n useEffect(() => {\n window.__mfeStyles__ ??= {};\n const mfeStyles = window.__mfeStyles__;\n mfeStyles[WEB_COMPONENT_NAME] ??= Object.assign(new EventTarget(), {\n sheets: new Map<string, CSSStyleSheet>(),\n });\n const emitter = mfeStyles[WEB_COMPONENT_NAME];\n /*\n * In self-hosted standalone dev mode (set by index.html), also adopt sheets\n * into document so host-only CSS vars and fonts propagate to the page.\n */\n const roots = [\n shadowRoot,\n portalShadowRoot,\n ...(window.__mfeSelfHosted__ ? [document] : []),\n ];\n function adoptSheet(sheet: CSSStyleSheet) {\n for (const root of roots) {\n root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];\n }\n }\n\n for (const sheet of emitter.sheets.values()) {\n adoptSheet(sheet);\n }\n\n const handleNewSheet = ({ detail: sheetId }: CustomEvent<string>) => {\n adoptSheet(emitter.sheets.get(sheetId)!);\n };\n\n emitter.addEventListener('new-sheet', handleNewSheet);\n return () => emitter.removeEventListener('new-sheet', handleNewSheet);\n }, [shadowRoot, portalShadowRoot]);\n\n return <Component {...props} />;\n };\n}\n"],"names":["useEffect","useMFEMetadataContext","WEB_COMPONENT_NAME","withCssInjector","Component","CssInjectorWrapper","props","shadowRoot","portalShadowRoot","window","mfeStyles","__mfeStyles__","Object","assign","EventTarget","sheets","Map","emitter","roots","__mfeSelfHosted__","document","adoptSheet","sheet","root","adoptedStyleSheets","values","handleNewSheet","detail","sheetId","get","addEventListener","removeEventListener"],"mappings":";AAAA,SAAwBA,SAAS,QAAQ,QAAQ;AACjD,SAASC,qBAAqB,QAAQ,kCAAkC;AACxE,SAASC,kBAAkB,QAAQ,YAAY;AAE/C,OAAO,SAASC,gBAAkCC,SAA2B;IACzE,OAAO,SAASC,mBAAmBC,KAAQ;QACvC,MAAM,EAAEC,UAAU,EAAEC,gBAAgB,EAAE,GAAGP;QAEzCD,UAAU;gBACNS,SAAAA,gBAEAC,YAAUR,qBAAVQ;aAFAD,iBAAAA,CAAAA,UAAAA,QAAOE,aAAa,cAApBF,4BAAAA,iBAAAA,QAAOE,aAAa,GAAK,CAAC;YAC1B,MAAMD,YAAYD,OAAOE,aAAa;aACtCD,IAAAA,CAAAA,aAAAA,UAAS,CAACR,sBAAAA,mBAAmB,cAA7BQ,eAAAA,IAAAA,UAAS,CAACR,oBAAmB,GAAKU,OAAOC,MAAM,CAAC,IAAIC,eAAe;gBAC/DC,QAAQ,IAAIC;YAChB;YACA,MAAMC,UAAUP,SAAS,CAACR,mBAAmB;YAC7C;;;aAGC,GACD,MAAMgB,QAAQ;gBACVX;gBACAC;mBACIC,OAAOU,iBAAiB,GAAG;oBAACC;iBAAS,GAAG,EAAE;aACjD;YACD,SAASC,WAAWC,KAAoB;gBACpC,KAAK,MAAMC,QAAQL,MAAO;oBACtBK,KAAKC,kBAAkB,GAAG;2BAAID,KAAKC,kBAAkB;wBAAEF;qBAAM;gBACjE;YACJ;YAEA,KAAK,MAAMA,SAASL,QAAQF,MAAM,CAACU,MAAM,GAAI;gBACzCJ,WAAWC;YACf;YAEA,MAAMI,iBAAiB,CAAC,EAAEC,QAAQC,OAAO,EAAuB;gBAC5DP,WAAWJ,QAAQF,MAAM,CAACc,GAAG,CAACD;YAClC;YAEAX,QAAQa,gBAAgB,CAAC,aAAaJ;YACtC,OAAO,IAAMT,QAAQc,mBAAmB,CAAC,aAAaL;QAC1D,GAAG;YAACnB;YAAYC;SAAiB;QAEjC,qBAAO,KAACJ;YAAW,GAAGE,KAAK;;IAC/B;AACJ"}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { render } from '@testing-library/react';
|
|
2
|
-
import { MockComponent, registerComponent } from './index';
|
|
3
|
-
|
|
4
|
-
/* Wrapper components injected by each optional library. */
|
|
5
|
-
export const optionalLibraryWrappers: Record<string, string[]> = {
|
|
6
|
-
'@servicetitan/anvil2': ['PortalProvider'],
|
|
7
|
-
'@servicetitan/design-system': [
|
|
8
|
-
'DefaultPortalContext.Provider',
|
|
9
|
-
'TablePopupPropsContext.Provider',
|
|
10
|
-
],
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export interface RegisterHarness {
|
|
14
|
-
Page: {
|
|
15
|
-
readonly componentShadowRoot: ShadowRoot;
|
|
16
|
-
readonly portalShadowRoot: ShadowRoot;
|
|
17
|
-
};
|
|
18
|
-
subject: () => ReturnType<typeof render>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function createRegisterHarness(
|
|
22
|
-
componentName: string,
|
|
23
|
-
options?: Parameters<typeof registerComponent>[2]
|
|
24
|
-
): RegisterHarness {
|
|
25
|
-
const Page = {
|
|
26
|
-
get componentShadowRoot() {
|
|
27
|
-
return document.querySelector(componentName)!.shadowRoot!;
|
|
28
|
-
},
|
|
29
|
-
get portalShadowRoot() {
|
|
30
|
-
return document.querySelector('body')!.lastElementChild!.shadowRoot!;
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
beforeAll(() => {
|
|
35
|
-
registerComponent(MockComponent, componentName, {
|
|
36
|
-
sharedDependenciesNames: ['@servicetitan/anvil2'],
|
|
37
|
-
...options,
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const subject = () => {
|
|
42
|
-
jest.spyOn(console, 'error').mockImplementation(jest.fn()); // suppress "ReactDOM.render deprecated" error
|
|
43
|
-
const Component: any = componentName;
|
|
44
|
-
const handleRef = (wc: any) => wc?.provide({ foo: 'bar' }); // triggers rendering
|
|
45
|
-
return render(<Component ref={handleRef} />);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
return { Page, subject };
|
|
49
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Split from register-anvil2.test.tsx because register.tsx changed from
|
|
3
|
-
* dynamic require() to static `import * as` for Vite/ESM compatibility.
|
|
4
|
-
* The previous approach used jest.resetModules() mid-file to re-mock
|
|
5
|
-
* modules between tests, but static imports are resolved once at module
|
|
6
|
-
* load time and aren't affected by resetModules. Separate files give
|
|
7
|
-
* each test suite its own jest.mock declarations.
|
|
8
|
-
*/
|
|
9
|
-
import '@testing-library/jest-dom';
|
|
10
|
-
import { within } from '@testing-library/react';
|
|
11
|
-
import {
|
|
12
|
-
createRegisterHarness,
|
|
13
|
-
optionalLibraryWrappers,
|
|
14
|
-
} from '../__mocks__/register-anvil2-harness';
|
|
15
|
-
|
|
16
|
-
jest.mock('@servicetitan/anvil2', () => ({}));
|
|
17
|
-
jest.mock('@servicetitan/design-system', () => ({}));
|
|
18
|
-
|
|
19
|
-
describe('[web-components] register', () => {
|
|
20
|
-
const { Page, subject } = createRegisterHarness('mock-no-optional-deps');
|
|
21
|
-
|
|
22
|
-
for (const [library, wrappers] of Object.entries(optionalLibraryWrappers)) {
|
|
23
|
-
describe(`when ${library} is not present`, () => {
|
|
24
|
-
test.each(wrappers)('does not wrap component within %s', name => {
|
|
25
|
-
subject();
|
|
26
|
-
|
|
27
|
-
const element = within(Page.componentShadowRoot as any).queryByText(`<${name}>`);
|
|
28
|
-
expect(element).not.toBeInTheDocument();
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
});
|