@teambit/ui 0.0.566 → 0.0.570

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.
@@ -0,0 +1,86 @@
1
+ import type { IncomingHttpHeaders } from 'http';
2
+ import type { Request } from 'express';
3
+
4
+ export type ParsedQuery = { [key: string]: undefined | string | string[] | ParsedQuery | ParsedQuery[] };
5
+
6
+ export type BrowserData = {
7
+ connection: {
8
+ secure: boolean;
9
+ headers: IncomingHttpHeaders;
10
+ body: any;
11
+ };
12
+ /**
13
+ * isomorphic location object, resembling the browser's window.location
14
+ */
15
+ location: {
16
+ /** hostname + port
17
+ * @example localhost:3000
18
+ */
19
+ host: string;
20
+ /**
21
+ * @example localhost
22
+ */
23
+ hostname: string;
24
+ /** full url
25
+ * @example http://localhost:3000/components?q=button
26
+ */
27
+ href: string;
28
+ /** full url without query
29
+ * @example http://localhost:3000/components
30
+ */
31
+ origin: string;
32
+ /**
33
+ * @example /component
34
+ */
35
+ pathname: string;
36
+ /**
37
+ * @example 3000
38
+ */
39
+ port: number;
40
+ /**
41
+ * @example http
42
+ */
43
+ protocol: string;
44
+ /**
45
+ * parsed search params
46
+ * @example { one: 1, two: [2,3]}
47
+ */
48
+ query: ParsedQuery;
49
+ /**
50
+ * full resource path, including query, without hostname
51
+ * @example /components?q=button
52
+ */
53
+ url: string;
54
+ };
55
+ cookie?: string;
56
+ };
57
+
58
+ /**
59
+ * extract relevant information from Express request.
60
+ */
61
+ export function requestToObj(req: Request, port: number) {
62
+ // apparently port is not readily available in request.
63
+
64
+ const browser: BrowserData = {
65
+ connection: {
66
+ secure: req.secure,
67
+ headers: req.headers,
68
+ body: req.body,
69
+ },
70
+ // trying to match to browser.location
71
+ location: {
72
+ host: `${req.hostname}:${port}`,
73
+ hostname: req.hostname,
74
+ href: `${req.protocol}://${req.hostname}:${port}${req.url}`,
75
+ origin: `${req.protocol}://${req.hostname}:${port}`,
76
+ pathname: req.path,
77
+ port,
78
+ protocol: `${req.protocol}:`,
79
+ query: req.query,
80
+ url: req.url,
81
+ },
82
+ cookie: req.header('Cookie'),
83
+ };
84
+
85
+ return browser;
86
+ }
@@ -0,0 +1,10 @@
1
+ import type { Request, Response } from 'express';
2
+
3
+ /**
4
+ * Represents the server configuration for the current request
5
+ */
6
+ export type RequestServer = {
7
+ port: number;
8
+ request: Request;
9
+ response: Response;
10
+ };
@@ -0,0 +1,9 @@
1
+ import type { Assets } from '@teambit/ui-foundation.ui.rendering.html';
2
+ import { BrowserData } from './request-browser';
3
+ import { RequestServer } from './request-server';
4
+
5
+ export type SsrContent = {
6
+ assets?: Assets;
7
+ browser?: BrowserData;
8
+ server?: RequestServer;
9
+ };
package/start.cmd.tsx ADDED
@@ -0,0 +1,93 @@
1
+ import React from 'react';
2
+ import open from 'open';
3
+ import { Command, CommandOptions } from '@teambit/cli';
4
+ import { Logger } from '@teambit/logger';
5
+ import { UIServerConsole } from '@teambit/ui-foundation.cli.ui-server-console';
6
+ import type { UiMain } from './ui.main.runtime';
7
+
8
+ type StartArgs = [uiName: string, userPattern: string];
9
+ type StartFlags = {
10
+ dev: boolean;
11
+ port: string;
12
+ rebuild: boolean;
13
+ verbose: boolean;
14
+ noBrowser: boolean;
15
+ skipCompilation: boolean;
16
+ };
17
+
18
+ export class StartCmd implements Command {
19
+ name = 'start [type] [pattern]';
20
+ description = 'Start a dev environment for a workspace or a specific component';
21
+ alias = 'c';
22
+ group = 'development';
23
+ shortDescription = '';
24
+ options = [
25
+ ['d', 'dev', 'start UI server in dev mode.'],
26
+ ['p', 'port [number]', 'port of the UI server.'],
27
+ ['r', 'rebuild', 'rebuild the UI'],
28
+ ['v', 'verbose', 'showing verbose output for inspection and prints stack trace'],
29
+ ['', 'no-browser', 'do not automatically open browser when ready'],
30
+ ['', 'skip-compilation', 'skip the auto-compilation before starting the web-server'],
31
+ ] as CommandOptions;
32
+
33
+ constructor(
34
+ /**
35
+ * access to the extension instance.
36
+ */
37
+ private ui: UiMain,
38
+
39
+ private logger: Logger
40
+ ) {}
41
+
42
+ // async report([uiRootName, userPattern]: StartArgs, { dev, port, rebuild, verbose }: StartFlags): Promise<string> {
43
+ // this.logger.off();
44
+ // const pattern = userPattern && userPattern.toString();
45
+
46
+ // const uiServer = await this.ui.createRuntime({
47
+ // uiRootName,
48
+ // pattern,
49
+ // dev,
50
+ // port: port ? parseInt(port) : undefined,
51
+ // rebuild,
52
+ // verbose,
53
+ // });
54
+
55
+ // return `Bit server has started on port ${uiServer.port}`;
56
+ // }
57
+
58
+ async render(
59
+ [uiRootName, userPattern]: StartArgs,
60
+ { dev, port, rebuild, verbose, noBrowser, skipCompilation }: StartFlags
61
+ ): Promise<React.ReactElement> {
62
+ this.logger.off();
63
+ const appName = this.ui.getUiName(uiRootName);
64
+ await this.ui.invokePreStart({ skipCompilation });
65
+ const uiServer = this.ui.createRuntime({
66
+ uiRootName,
67
+ pattern: userPattern,
68
+ dev,
69
+ port: +port,
70
+ rebuild,
71
+ verbose,
72
+ });
73
+
74
+ if (!noBrowser) {
75
+ uiServer
76
+ .then(async (server) => {
77
+ if (!server.buildOptions?.launchBrowserOnStart) return undefined;
78
+
79
+ await server.whenReady;
80
+
81
+ return open(this.ui.publicUrl || server.fullUrl);
82
+ })
83
+ .catch((error) => this.logger.error(error));
84
+ }
85
+
86
+ // DO NOT CHANGE THIS - this meant to be an async hook.
87
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
88
+ this.ui.invokeOnStart();
89
+ this.ui.clearConsole();
90
+
91
+ return <UIServerConsole appName={appName} futureUiServer={uiServer} url={this.ui.publicUrl} />;
92
+ }
93
+ }
@@ -0,0 +1,29 @@
1
+ declare module '*.png' {
2
+ const value: any;
3
+ export = value;
4
+ }
5
+ declare module '*.svg' {
6
+ import type { FunctionComponent, SVGProps } from 'react';
7
+
8
+ export const ReactComponent: FunctionComponent<SVGProps<SVGSVGElement> & { title?: string }>;
9
+ const src: string;
10
+ export default src;
11
+ }
12
+
13
+ // @TODO Gilad
14
+ declare module '*.jpg' {
15
+ const value: any;
16
+ export = value;
17
+ }
18
+ declare module '*.jpeg' {
19
+ const value: any;
20
+ export = value;
21
+ }
22
+ declare module '*.gif' {
23
+ const value: any;
24
+ export = value;
25
+ }
26
+ declare module '*.bmp' {
27
+ const value: any;
28
+ export = value;
29
+ }
@@ -0,0 +1,42 @@
1
+ declare module '*.module.css' {
2
+ const classes: { readonly [key: string]: string };
3
+ export default classes;
4
+ }
5
+ declare module '*.module.scss' {
6
+ const classes: { readonly [key: string]: string };
7
+ export default classes;
8
+ }
9
+ declare module '*.module.sass' {
10
+ const classes: { readonly [key: string]: string };
11
+ export default classes;
12
+ }
13
+
14
+ declare module '*.module.less' {
15
+ const classes: { readonly [key: string]: string };
16
+ export default classes;
17
+ }
18
+
19
+ declare module '*.less' {
20
+ const classes: { readonly [key: string]: string };
21
+ export default classes;
22
+ }
23
+
24
+ declare module '*.css' {
25
+ const classes: { readonly [key: string]: string };
26
+ export default classes;
27
+ }
28
+
29
+ declare module '*.sass' {
30
+ const classes: { readonly [key: string]: string };
31
+ export default classes;
32
+ }
33
+
34
+ declare module '*.scss' {
35
+ const classes: { readonly [key: string]: string };
36
+ export default classes;
37
+ }
38
+
39
+ declare module '*.mdx' {
40
+ const component: any;
41
+ export default component;
42
+ }
@@ -0,0 +1,28 @@
1
+ import React, { ReactNode } from 'react';
2
+ import { Theme } from '@teambit/base-ui.theme.theme-provider';
3
+ import { IconFont } from '@teambit/design.theme.icons-font';
4
+ import { LoaderRibbon } from '@teambit/base-ui.loaders.loader-ribbon';
5
+ import { Roboto } from '@teambit/base-ui.theme.fonts.roboto';
6
+ import { TooltipMountPoint } from '@teambit/design.ui.tooltip';
7
+
8
+ import { LoaderContext, useLoaderApi } from '@teambit/ui-foundation.ui.global-loader';
9
+ import styles from './client-context.module.scss';
10
+
11
+ export function ClientContext({ children }: { children: ReactNode }) {
12
+ const [loaderApi, isLoading] = useLoaderApi();
13
+
14
+ return (
15
+ <React.StrictMode>
16
+ {/* TODO - try moving LoaderContext to contextSlot, and LoaderRibbon to hudSlot */}
17
+ <LoaderContext.Provider value={loaderApi}>
18
+ <IconFont query="q76y7n" />
19
+ <Theme>
20
+ <Roboto />
21
+ <LoaderRibbon active={isLoading} className={styles.loader} />
22
+ {children}
23
+ <TooltipMountPoint />
24
+ </Theme>
25
+ </LoaderContext.Provider>
26
+ </React.StrictMode>
27
+ );
28
+ }
@@ -0,0 +1,27 @@
1
+ import { Command } from '@teambit/cli';
2
+ import { UnknownBuildError } from './exceptions';
3
+
4
+ import { UiMain } from './ui.main.runtime';
5
+
6
+ export class UIBuildCmd implements Command {
7
+ name = 'ui-build [type]';
8
+ description = 'build production assets for deployment.';
9
+ alias = 'c';
10
+ group = 'development';
11
+ shortDescription = '';
12
+ options = [];
13
+
14
+ constructor(
15
+ /**
16
+ * access to the extension instance.
17
+ */
18
+ private ui: UiMain
19
+ ) {}
20
+
21
+ async report([type]: [string]): Promise<string> {
22
+ // teambit.workspace/variants should be the one to take care of component patterns.
23
+ const stats = await this.ui.build(type);
24
+ if (!stats) throw new UnknownBuildError();
25
+ return stats.toString();
26
+ }
27
+ }
package/ui-root.tsx ADDED
@@ -0,0 +1,59 @@
1
+ import { AspectDefinition } from '@teambit/aspect-loader';
2
+ import { ComponentDir } from '@teambit/bundler';
3
+ import { Component } from '@teambit/component';
4
+ import { ProxyConfigArrayItem } from 'webpack-dev-server';
5
+
6
+ // TODO: remove this extends "ComponentDir", this should be part of the workspace alone since scope
7
+ // would never have componentDir and as it has nothing to do with `UIRoot`.
8
+ export interface UIRoot extends ComponentDir {
9
+ /**
10
+ * unique name of the ui.
11
+ */
12
+ name: string;
13
+
14
+ /**
15
+ * path of the ui root.
16
+ */
17
+ path: string;
18
+
19
+ /**
20
+ * name of the UI root config file.
21
+ */
22
+ configFile: string;
23
+
24
+ buildOptions?: {
25
+ ssr?: boolean;
26
+ launchBrowserOnStart?: boolean;
27
+ };
28
+
29
+ /**
30
+ * resolve all aspects in the UI root.
31
+ */
32
+ resolveAspects(runtimeName: string): Promise<AspectDefinition[]>;
33
+
34
+ /**
35
+ * resolve components from a given pattern.
36
+ */
37
+ resolvePattern?(pattern: string): Promise<Component[]>;
38
+
39
+ /**
40
+ * listener for when the dev server starts. can be used for running the watcher.
41
+ */
42
+ postStart?(options: PostStartOptions): Promise<void>;
43
+
44
+ /**
45
+ * determine whether UI should get a priority.
46
+ */
47
+ priority?: boolean;
48
+ }
49
+
50
+ export type ProxyEntry = ProxyConfigArrayItem & {
51
+ context: string[]; // limit type to simplify our code. (not required)
52
+ };
53
+
54
+ export type PostStartOptions = {
55
+ /**
56
+ * pattern for selecting components in the container.
57
+ */
58
+ pattern?: string;
59
+ };
package/ui.cli.rt.tsx ADDED
@@ -0,0 +1,10 @@
1
+ // import { CLIExtension } from '../cli';
2
+ // import { StartCmd } from './start.cmd';
3
+ // import { Workspace } from '../workspace';
4
+ // import { GraphQLExtension } from '../graphql';
5
+
6
+ // export default ([cli, envs, workspace, graphql]: [CLIExtension, Environments, Workspace, GraphQLExtension]) => {
7
+ // // const ui = new UIExtension(envs, graphql);
8
+ // cli.register(new StartCmd(ui, workspace));
9
+ // // return ui;
10
+ // }
@@ -0,0 +1,277 @@
1
+ import type { GraphqlUI } from '@teambit/graphql';
2
+ import { GraphqlAspect } from '@teambit/graphql';
3
+ import { Slot, SlotRegistry } from '@teambit/harmony';
4
+ import type { ReactRouterUI } from '@teambit/react-router';
5
+ import { ReactRouterAspect } from '@teambit/react-router';
6
+ import { Html, MountPoint, mountPointId, ssrCleanup, Assets } from '@teambit/ui-foundation.ui.rendering.html';
7
+
8
+ import { merge } from 'webpack-merge';
9
+ import React, { ReactNode, ComponentType } from 'react';
10
+ import ReactDOM from 'react-dom';
11
+ import ReactDOMServer from 'react-dom/server';
12
+ import compact from 'lodash.compact';
13
+
14
+ import { Compose, Wrapper } from './compose';
15
+ import { UIRootFactory } from './ui-root.ui';
16
+ import { UIAspect, UIRuntime } from './ui.aspect';
17
+ import { ClientContext } from './ui/client-context';
18
+ import type { SsrContent } from './ssr/ssr-content';
19
+ import type { RequestServer } from './ssr/request-server';
20
+ import type { BrowserData } from './ssr/request-browser';
21
+ import { RenderLifecycle } from './render-lifecycle';
22
+
23
+ export type ContextProps<T = any> = { renderCtx?: T; children: ReactNode };
24
+
25
+ type HudSlot = SlotRegistry<ReactNode>;
26
+ type renderLifecycleSlot = SlotRegistry<RenderLifecycle>;
27
+ type UIRootRegistry = SlotRegistry<UIRootFactory>;
28
+
29
+ /**
30
+ * extension
31
+ */
32
+ export class UiUI {
33
+ constructor(
34
+ /**
35
+ * react-router extension.
36
+ */
37
+ private router: ReactRouterUI,
38
+ /**
39
+ * ui root registry.
40
+ */
41
+ private uiRootSlot: UIRootRegistry,
42
+ /** slot for overlay ui elements */
43
+ private hudSlot: HudSlot,
44
+ /** hooks into the ssr render process */
45
+ private lifecycleSlot: renderLifecycleSlot
46
+ ) {}
47
+
48
+ /** render and rehydrate client-side */
49
+ async render(rootExtension: string): Promise<void> {
50
+ const rootFactory = this.getRoot(rootExtension);
51
+ if (!rootFactory) throw new Error(`root: ${rootExtension} was not found`);
52
+ const uiRoot = rootFactory();
53
+ const initialLocation = `${window.location.pathname}${window.location.search}${window.location.hash}`;
54
+ const routes = this.router.renderRoutes(uiRoot.routes, { initialLocation });
55
+ const hudItems = this.hudSlot.values();
56
+ const lifecycleHooks = this.lifecycleSlot.toArray();
57
+
58
+ // TODO - extract the logic from here down as reusable ssr machine
59
+ const deserializedState = await this.deserialize(lifecycleHooks);
60
+ let renderContexts = await this.triggerBrowserInit(lifecycleHooks, deserializedState);
61
+ const reactContexts = this.getReactContexts(lifecycleHooks, renderContexts);
62
+
63
+ const app = (
64
+ <Compose components={reactContexts}>
65
+ <ClientContext>
66
+ {hudItems}
67
+ {routes}
68
+ </ClientContext>
69
+ </Compose>
70
+ );
71
+
72
+ renderContexts = await this.triggerBeforeHydrateHook(renderContexts, lifecycleHooks, app);
73
+
74
+ const mountPoint = document.getElementById(mountPointId);
75
+ // .render() already runs `.hydrate()` behind the scenes.
76
+ // in the future, we may want to replace it with .hydrate()
77
+ ReactDOM.render(app, mountPoint);
78
+
79
+ await this.triggerHydrateHook(renderContexts, lifecycleHooks, mountPoint);
80
+
81
+ // remove ssr only styles
82
+ ssrCleanup();
83
+ }
84
+
85
+ /** render dehydrated server-side */
86
+ async renderSsr(rootExtension: string, { assets, browser, server }: SsrContent = {}): Promise<string> {
87
+ const rootFactory = this.getRoot(rootExtension);
88
+ if (!rootFactory) throw new Error(`root: ${rootExtension} was not found`);
89
+
90
+ const uiRoot = rootFactory();
91
+ const routes = this.router.renderRoutes(uiRoot.routes, { initialLocation: browser?.location.url });
92
+ const hudItems = this.hudSlot.values();
93
+
94
+ // create array once to keep consistent indexes
95
+ const lifecycleHooks = this.lifecycleSlot.toArray();
96
+
97
+ // TODO - extract the logic from here down as reusable ssr machine
98
+ // (1) init
99
+ let renderContexts = await this.triggerServerInit(lifecycleHooks, browser, server);
100
+ const reactContexts = this.getReactContexts(lifecycleHooks, renderContexts);
101
+
102
+ // (2) make (virtual) dom
103
+ const app = (
104
+ <MountPoint>
105
+ <Compose components={reactContexts}>
106
+ <ClientContext>
107
+ {hudItems}
108
+ {routes}
109
+ </ClientContext>
110
+ </Compose>
111
+ </MountPoint>
112
+ );
113
+
114
+ // (3) render
115
+ renderContexts = await this.onBeforeRender(renderContexts, lifecycleHooks, app);
116
+
117
+ const renderedApp = ReactDOMServer.renderToString(app);
118
+
119
+ // (3) render html-template
120
+ const realtimeAssets = await this.serialize(lifecycleHooks, renderContexts, app);
121
+ // @ts-ignore // TODO upgrade 'webpack-merge'
122
+ const totalAssets = merge(assets, realtimeAssets) as Assets;
123
+
124
+ const html = <Html assets={totalAssets} withDevTools fullHeight ssr />;
125
+ const renderedHtml = `<!DOCTYPE html>${ReactDOMServer.renderToStaticMarkup(html)}`;
126
+ const fullHtml = Html.fillContent(renderedHtml, renderedApp);
127
+
128
+ // (4) serve
129
+ return fullHtml;
130
+ }
131
+
132
+ /** adds elements to the Heads Up Display */
133
+ registerHudItem = (element: ReactNode) => {
134
+ this.hudSlot.register(element);
135
+ };
136
+
137
+ /**
138
+ * adds global context at the ui root
139
+ * @deprecated replace with `.registerRenderHooks({ reactContext })`.
140
+ */
141
+ registerContext<T>(context: ComponentType<ContextProps<T>>) {
142
+ this.lifecycleSlot.register({
143
+ reactContext: context,
144
+ });
145
+ }
146
+
147
+ registerRoot(uiRoot: UIRootFactory) {
148
+ return this.uiRootSlot.register(uiRoot);
149
+ }
150
+
151
+ registerRenderHooks<T, Y>(hooks: RenderLifecycle<T, Y>) {
152
+ return this.lifecycleSlot.register(hooks);
153
+ }
154
+
155
+ private triggerBrowserInit(lifecycleHooks: [string, RenderLifecycle<any, any>][], deserializedState: any[]) {
156
+ return Promise.all(lifecycleHooks.map(([, hooks], idx) => hooks.browserInit?.(deserializedState[idx])));
157
+ }
158
+
159
+ private triggerServerInit(
160
+ lifecycleHooks: [string, RenderLifecycle<any, any>][],
161
+ browser?: BrowserData,
162
+ server?: RequestServer
163
+ ) {
164
+ return Promise.all(lifecycleHooks.map(([, hooks]) => hooks.serverInit?.({ browser, server })));
165
+ }
166
+
167
+ private getReactContexts(lifecycleHooks: [string, RenderLifecycle<any>][], renderContexts: any[]): Wrapper[] {
168
+ return compact(
169
+ lifecycleHooks.map(([, hooks], idx) => {
170
+ const renderCtx = renderContexts[idx];
171
+ const props = { renderCtx };
172
+ return hooks.reactContext ? [hooks.reactContext, props] : undefined;
173
+ })
174
+ );
175
+ }
176
+
177
+ private async onBeforeRender(
178
+ renderContexts: any[],
179
+ lifecycleHooks: [string, RenderLifecycle<any>][],
180
+ app: JSX.Element
181
+ ) {
182
+ await Promise.all(
183
+ lifecycleHooks.map(async ([, hooks], idx) => {
184
+ const ctx = renderContexts[idx];
185
+ const nextCtx = await hooks.onBeforeRender?.(ctx, app);
186
+ return nextCtx || ctx;
187
+ })
188
+ );
189
+ return renderContexts;
190
+ }
191
+
192
+ private triggerBeforeHydrateHook(
193
+ renderContexts: any[],
194
+ lifecycleHooks: [string, RenderLifecycle<any>][],
195
+ app: JSX.Element
196
+ ) {
197
+ return Promise.all(
198
+ lifecycleHooks.map(async ([, hooks], idx) => {
199
+ const ctx = renderContexts[idx];
200
+ const nextCtx = await hooks.onBeforeHydrate?.(ctx, app);
201
+ return nextCtx || ctx;
202
+ })
203
+ );
204
+ }
205
+
206
+ private async triggerHydrateHook(
207
+ renderContexts: any[],
208
+ lifecycleHooks: [string, RenderLifecycle<any, any>][],
209
+ mountPoint: HTMLElement | null
210
+ ) {
211
+ await Promise.all(lifecycleHooks.map(([, hooks], idx) => hooks.onHydrate?.(renderContexts[idx], mountPoint)));
212
+ }
213
+
214
+ private async serialize(
215
+ lifecycleHooks: [string, RenderLifecycle][],
216
+ renderContexts: any[],
217
+ app: ReactNode
218
+ ): Promise<Assets> {
219
+ const json = {};
220
+
221
+ await Promise.all(
222
+ lifecycleHooks.map(async ([key, hooks], idx) => {
223
+ const renderCtx = renderContexts[idx];
224
+ const result = await hooks.serialize?.(renderCtx, app);
225
+
226
+ if (!result) return;
227
+ if (result.json) json[key] = result.json;
228
+ })
229
+ );
230
+
231
+ // more assets will be available in the future
232
+ return { json };
233
+ }
234
+
235
+ private async deserialize(lifecycleHooks: [string, RenderLifecycle][]) {
236
+ const rawAssets = Html.popAssets();
237
+
238
+ const deserialized = await Promise.all(
239
+ lifecycleHooks.map(async ([key, hooks]) => {
240
+ try {
241
+ const raw = rawAssets.get(key);
242
+ return hooks.deserialize?.(raw);
243
+ } catch (e) {
244
+ // eslint-disable-next-line no-console
245
+ console.error(`failed deserializing server state for aspect ${key}`, e);
246
+ return undefined;
247
+ }
248
+ })
249
+ );
250
+
251
+ return deserialized;
252
+ }
253
+
254
+ private getRoot(rootExtension: string) {
255
+ return this.uiRootSlot.get(rootExtension);
256
+ }
257
+
258
+ static slots = [Slot.withType<UIRootFactory>(), Slot.withType<ReactNode>(), Slot.withType<RenderLifecycle>()];
259
+
260
+ static dependencies = [GraphqlAspect, ReactRouterAspect];
261
+
262
+ static runtime = UIRuntime;
263
+
264
+ static async provider(
265
+ [GraphqlUi, router]: [GraphqlUI, ReactRouterUI],
266
+ config,
267
+ [uiRootSlot, hudSlot, renderLifecycleSlot]: [UIRootRegistry, HudSlot, renderLifecycleSlot]
268
+ ) {
269
+ const uiUi = new UiUI(router, uiRootSlot, hudSlot, renderLifecycleSlot);
270
+
271
+ uiUi.registerRenderHooks(GraphqlUi.renderHooks);
272
+
273
+ return uiUi;
274
+ }
275
+ }
276
+
277
+ UIAspect.addRuntime(UiUI);