@teambit/preview 0.0.555 → 0.0.559

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.
@@ -1,346 +0,0 @@
1
- import { BuilderAspect, BuilderMain } from '@teambit/builder';
2
- import { BundlerAspect, BundlerMain } from '@teambit/bundler';
3
- import { PubsubAspect, PubsubMain } from '@teambit/pubsub';
4
- import { MainRuntime } from '@teambit/cli';
5
- import { Component, ComponentAspect, ComponentMain, ComponentMap, ComponentID } from '@teambit/component';
6
- import { EnvsAspect, EnvsMain, ExecutionContext } from '@teambit/envs';
7
- import { Slot, SlotRegistry, Harmony } from '@teambit/harmony';
8
- import { UIAspect, UiMain } from '@teambit/ui';
9
- import { CACHE_ROOT } from '@teambit/legacy/dist/constants';
10
- import objectHash from 'object-hash';
11
- import { uniq } from 'lodash';
12
- import { writeFileSync, existsSync, mkdirSync } from 'fs-extra';
13
- import { join } from 'path';
14
- import { PkgAspect, PkgMain } from '@teambit/pkg';
15
- import { AspectDefinition, AspectLoaderMain, AspectLoaderAspect } from '@teambit/aspect-loader';
16
- import WorkspaceAspect, { Workspace } from '@teambit/workspace';
17
- import { LoggerAspect, LoggerMain, Logger } from '@teambit/logger';
18
- import { BundlingStrategyNotFound } from './exceptions';
19
- import { generateLink } from './generate-link';
20
- import { PreviewArtifact } from './preview-artifact';
21
- import { PreviewDefinition } from './preview-definition';
22
- import { PreviewAspect, PreviewRuntime } from './preview.aspect';
23
- import { PreviewRoute } from './preview.route';
24
- import { PreviewTask } from './preview.task';
25
- import { BundlingStrategy } from './bundling-strategy';
26
- import { EnvBundlingStrategy, ComponentBundlingStrategy } from './strategies';
27
- import { ExecutionRef } from './execution-ref';
28
- import { PreviewStartPlugin } from './preview.start-plugin';
29
-
30
- const noopResult = {
31
- results: [],
32
- toString: () => `updating link file`,
33
- };
34
-
35
- const DEFAULT_TEMP_DIR = join(CACHE_ROOT, PreviewAspect.id);
36
-
37
- export type PreviewDefinitionRegistry = SlotRegistry<PreviewDefinition>;
38
-
39
- export type PreviewConfig = {
40
- bundlingStrategy: string;
41
- disabled: boolean;
42
- };
43
-
44
- export type BundlingStrategySlot = SlotRegistry<BundlingStrategy>;
45
-
46
- export class PreviewMain {
47
- constructor(
48
- /**
49
- * harmony context.
50
- */
51
- private harmony: Harmony,
52
-
53
- /**
54
- * slot for preview definitions.
55
- */
56
- private previewSlot: PreviewDefinitionRegistry,
57
-
58
- private ui: UiMain,
59
-
60
- private envs: EnvsMain,
61
-
62
- private pkg: PkgMain,
63
-
64
- private aspectLoader: AspectLoaderMain,
65
-
66
- readonly config: PreviewConfig,
67
-
68
- private bundlingStrategySlot: BundlingStrategySlot,
69
-
70
- private builder: BuilderMain,
71
-
72
- private workspace: Workspace | undefined,
73
-
74
- private logger: Logger
75
- ) {}
76
-
77
- get tempFolder(): string {
78
- return this.workspace?.getTempDir(PreviewAspect.id) || DEFAULT_TEMP_DIR;
79
- }
80
-
81
- async getPreview(component: Component): Promise<PreviewArtifact | undefined> {
82
- const artifacts = await this.builder.getArtifactsVinylByExtension(component, PreviewAspect.id);
83
- if (!artifacts.length) return undefined;
84
-
85
- return new PreviewArtifact(artifacts);
86
- }
87
-
88
- getDefs(): PreviewDefinition[] {
89
- return this.previewSlot.values();
90
- }
91
-
92
- private writeHash = new Map<string, string>();
93
- private timestamp = Date.now();
94
-
95
- /**
96
- * write a link to load custom modules dynamically.
97
- * @param prefix write
98
- * @param moduleMap map of components to module paths to require.
99
- * @param defaultModule
100
- * @param dirName
101
- */
102
- writeLink(prefix: string, moduleMap: ComponentMap<string[]>, defaultModule: string | undefined, dirName: string) {
103
- const contents = generateLink(prefix, moduleMap, defaultModule);
104
- const hash = objectHash(contents);
105
- const targetPath = join(dirName, `__${prefix}-${this.timestamp}.js`);
106
-
107
- // write only if link has changed (prevents triggering fs watches)
108
- if (this.writeHash.get(targetPath) !== hash) {
109
- writeFileSync(targetPath, contents);
110
- this.writeHash.set(targetPath, hash);
111
- }
112
-
113
- return targetPath;
114
- }
115
-
116
- private executionRefs = new Map<string, ExecutionRef>();
117
-
118
- private async getPreviewTarget(
119
- /** execution context (of the specific env) */
120
- context: ExecutionContext
121
- ): Promise<string[]> {
122
- // store context for later link-file updates
123
- // also register related envs that this context is acting on their behalf
124
- [context.id, ...context.relatedContexts].forEach((ctxId) => {
125
- this.executionRefs.set(ctxId, new ExecutionRef(context));
126
- });
127
-
128
- const previewRuntime = await this.writePreviewRuntime(context);
129
- const linkFiles = await this.updateLinkFiles(context.components, context);
130
-
131
- return [...linkFiles, previewRuntime];
132
- }
133
-
134
- private updateLinkFiles(components: Component[] = [], context: ExecutionContext) {
135
- const previews = this.previewSlot.values();
136
- const paths = previews.map(async (previewDef) => {
137
- const templatePath = await previewDef.renderTemplatePath?.(context);
138
-
139
- const map = await previewDef.getModuleMap(components);
140
- const environment = context.envRuntime.env;
141
- const compilerInstance = environment.getCompiler?.();
142
- const withPaths = map.map<string[]>((files, component) => {
143
- const modulePath =
144
- compilerInstance?.getPreviewComponentRootPath?.(component) || this.pkg.getModulePath(component);
145
- return files.map((file) => {
146
- if (!this.workspace || !compilerInstance) {
147
- return file.path;
148
- }
149
- const distRelativePath = compilerInstance.getDistPathBySrcPath(file.relative);
150
- return join(this.workspace.path, modulePath, distRelativePath);
151
- });
152
- // return files.map((file) => file.path);
153
- });
154
-
155
- const dirPath = join(this.tempFolder, context.id);
156
- if (!existsSync(dirPath)) mkdirSync(dirPath, { recursive: true });
157
-
158
- const link = this.writeLink(previewDef.prefix, withPaths, templatePath, dirPath);
159
- return link;
160
- });
161
-
162
- return Promise.all(paths);
163
- }
164
-
165
- async writePreviewRuntime(context: { components: Component[] }) {
166
- const ui = this.ui.getUi();
167
- if (!ui) throw new Error('ui not found');
168
- const [name, uiRoot] = ui;
169
- const resolvedAspects = await uiRoot.resolveAspects(PreviewRuntime.name);
170
- const filteredAspects = this.filterAspectsByExecutionContext(resolvedAspects, context);
171
- const filePath = await this.ui.generateRoot(filteredAspects, name, 'preview', PreviewAspect.id);
172
- return filePath;
173
- }
174
-
175
- /**
176
- * Filter the aspects to have only aspects that are:
177
- * 1. core aspects
178
- * 2. configured on the host (workspace/scope)
179
- * 3. used by at least one component from the context
180
- * @param aspects
181
- * @param context
182
- */
183
- private filterAspectsByExecutionContext(aspects: AspectDefinition[], context: { components: Component[] }) {
184
- let allComponentContextAspects: string[] = [];
185
- allComponentContextAspects = context.components.reduce((acc, curr) => {
186
- return acc.concat(curr.state.aspects.ids);
187
- }, allComponentContextAspects);
188
- const hostAspects = Object.keys(this.harmony.config.toObject());
189
- const allAspectsToInclude = uniq(hostAspects.concat(allComponentContextAspects));
190
- const filtered = aspects.filter((aspect) => {
191
- if (!aspect.getId) {
192
- return false;
193
- }
194
- return this.aspectLoader.isCoreAspect(aspect.getId) || allAspectsToInclude.includes(aspect.getId);
195
- });
196
- return filtered;
197
- }
198
-
199
- private getDefaultStrategies() {
200
- return [new EnvBundlingStrategy(this), new ComponentBundlingStrategy()];
201
- }
202
-
203
- // TODO - executionContext should be responsible for updating components list, and emit 'update' events
204
- // instead we keep track of changes
205
- private handleComponentChange = async (c: Component, updater: (currentComponents: ExecutionRef) => void) => {
206
- const env = this.envs.getEnv(c);
207
- const envId = env.id.toString();
208
-
209
- const executionRef = this.executionRefs.get(envId);
210
- if (!executionRef) {
211
- this.logger.warn(
212
- `failed to update link file for component "${c.id.toString()}" - could not find execution context for ${envId}`
213
- );
214
- return noopResult;
215
- }
216
-
217
- // add / remove / etc
218
- updater(executionRef);
219
-
220
- await this.updateLinkFiles(executionRef.currentComponents, executionRef.executionCtx);
221
-
222
- return noopResult;
223
- };
224
-
225
- private handleComponentRemoval = (cId: ComponentID) => {
226
- let component: Component | undefined;
227
- this.executionRefs.forEach((components) => {
228
- const found = components.get(cId);
229
- if (found) component = found;
230
- });
231
- if (!component) return Promise.resolve(noopResult);
232
-
233
- return this.handleComponentChange(component, (currentComponents) => currentComponents.remove(cId));
234
- };
235
-
236
- /**
237
- * return the configured bundling strategy.
238
- */
239
- getBundlingStrategy(): BundlingStrategy {
240
- const defaultStrategies = this.getDefaultStrategies();
241
- const strategyName = this.config.bundlingStrategy;
242
- const strategies = this.bundlingStrategySlot.values().concat(defaultStrategies);
243
- const selected = strategies.find((strategy) => {
244
- return strategy.name === strategyName;
245
- });
246
-
247
- if (!selected) throw new BundlingStrategyNotFound(strategyName);
248
-
249
- return selected;
250
- }
251
-
252
- /**
253
- * register a new bundling strategy. default available strategies are `env` and ``
254
- */
255
- registerBundlingStrategy(bundlingStrategy: BundlingStrategy) {
256
- this.bundlingStrategySlot.register(bundlingStrategy);
257
- return this;
258
- }
259
-
260
- /**
261
- * register a new preview definition.
262
- */
263
- registerDefinition(previewDef: PreviewDefinition) {
264
- this.previewSlot.register(previewDef);
265
- }
266
-
267
- static slots = [Slot.withType<PreviewDefinition>(), Slot.withType<BundlingStrategy>()];
268
-
269
- static runtime = MainRuntime;
270
- static dependencies = [
271
- BundlerAspect,
272
- BuilderAspect,
273
- ComponentAspect,
274
- UIAspect,
275
- EnvsAspect,
276
- WorkspaceAspect,
277
- PkgAspect,
278
- PubsubAspect,
279
- AspectLoaderAspect,
280
- LoggerAspect,
281
- ];
282
-
283
- static defaultConfig = {
284
- bundlingStrategy: 'env',
285
- disabled: false,
286
- };
287
-
288
- static async provider(
289
- [bundler, builder, componentExtension, uiMain, envs, workspace, pkg, pubsub, aspectLoader, loggerMain]: [
290
- BundlerMain,
291
- BuilderMain,
292
- ComponentMain,
293
- UiMain,
294
- EnvsMain,
295
- Workspace | undefined,
296
- PkgMain,
297
- PubsubMain,
298
- AspectLoaderMain,
299
- LoggerMain
300
- ],
301
- config: PreviewConfig,
302
- [previewSlot, bundlingStrategySlot]: [PreviewDefinitionRegistry, BundlingStrategySlot],
303
- harmony: Harmony
304
- ) {
305
- const logger = loggerMain.createLogger(PreviewAspect.id);
306
-
307
- const preview = new PreviewMain(
308
- harmony,
309
- previewSlot,
310
- uiMain,
311
- envs,
312
- pkg,
313
- aspectLoader,
314
- config,
315
- bundlingStrategySlot,
316
- builder,
317
- workspace,
318
- logger
319
- );
320
-
321
- if (workspace) uiMain.registerStartPlugin(new PreviewStartPlugin(workspace, bundler, uiMain, pubsub, logger));
322
-
323
- componentExtension.registerRoute([new PreviewRoute(preview, logger)]);
324
- bundler.registerTarget([
325
- {
326
- entry: preview.getPreviewTarget.bind(preview),
327
- },
328
- ]);
329
-
330
- if (!config.disabled) builder.registerBuildTasks([new PreviewTask(bundler, preview)]);
331
-
332
- if (workspace) {
333
- workspace.registerOnComponentAdd((c) =>
334
- preview.handleComponentChange(c, (currentComponents) => currentComponents.add(c))
335
- );
336
- workspace.registerOnComponentChange((c) =>
337
- preview.handleComponentChange(c, (currentComponents) => currentComponents.update(c))
338
- );
339
- workspace.registerOnComponentRemove((cId) => preview.handleComponentRemoval(cId));
340
- }
341
-
342
- return preview;
343
- }
344
- }
345
-
346
- PreviewAspect.addRuntime(PreviewMain);
@@ -1,148 +0,0 @@
1
- import PubsubAspect, { PubsubPreview } from '@teambit/pubsub';
2
- import { Slot, SlotRegistry } from '@teambit/harmony';
3
- import { ComponentID } from '@teambit/component-id';
4
-
5
- import { PreviewNotFound } from './exceptions';
6
- import { PreviewType } from './preview-type';
7
- import { PreviewAspect, PreviewRuntime } from './preview.aspect';
8
- import { ClickInsideAnIframeEvent } from './events';
9
- import { PreviewModule } from './types/preview-module';
10
- import { RenderingContext } from './rendering-context';
11
-
12
- export type PreviewSlot = SlotRegistry<PreviewType>;
13
-
14
- const PREVIEW_MODULES: Record<string, PreviewModule> = {};
15
-
16
- export type RenderingContextProvider = () => { [key: string]: any };
17
- export type RenderingContextSlot = SlotRegistry<RenderingContextProvider>;
18
-
19
- export class PreviewPreview {
20
- constructor(
21
- /**
22
- * register to pubsub
23
- */
24
- private pubsub: PubsubPreview,
25
-
26
- /**
27
- * preview slot.
28
- */
29
- private previewSlot: PreviewSlot,
30
-
31
- private renderingContextSlot: RenderingContextSlot
32
- ) {
33
- this.registerClickPubSub();
34
- }
35
-
36
- private registerClickPubSub() {
37
- window.addEventListener('click', (e) => {
38
- const timestamp = Date.now();
39
- const clickEvent = Object.assign({}, e);
40
- this.pubsub.pub(PreviewAspect.id, new ClickInsideAnIframeEvent(timestamp, clickEvent));
41
- });
42
- }
43
-
44
- /**
45
- * render the preview.
46
- */
47
- render = () => {
48
- const { previewName, componentId } = this.getLocation();
49
- const name = previewName || this.getDefault();
50
-
51
- const preview = this.getPreview(name);
52
- if (!preview || !componentId) {
53
- throw new PreviewNotFound(previewName);
54
- }
55
- const includes = (preview.include || [])
56
- .map((prevName) => {
57
- const includedPreview = this.getPreview(prevName);
58
- if (!includedPreview) return undefined;
59
-
60
- return includedPreview.selectPreviewModel?.(componentId.fullName, PREVIEW_MODULES[prevName]);
61
- })
62
- .filter((module) => !!module);
63
-
64
- return preview.render(componentId.fullName, PREVIEW_MODULES[name], includes, this.getRenderingContext());
65
- };
66
-
67
- /**
68
- * register a new preview.
69
- */
70
- registerPreview(preview: PreviewType) {
71
- this.previewSlot.register(preview);
72
- return this;
73
- }
74
-
75
- /**
76
- * get the preview rendering context.
77
- */
78
- getRenderingContext() {
79
- return new RenderingContext(this.renderingContextSlot);
80
- }
81
-
82
- /**
83
- * allows aspects to add rendering contexts.
84
- * render context is available through all preview definitions.
85
- */
86
- registerRenderContext(renderContext: RenderingContextProvider) {
87
- this.renderingContextSlot.register(renderContext);
88
- return this;
89
- }
90
-
91
- getDefault() {
92
- const previews = this.previewSlot.values();
93
- const defaultOne = previews.find((previewCandidate) => previewCandidate.default);
94
-
95
- return defaultOne?.name || previews[0].name;
96
- }
97
-
98
- private getPreview(previewName: string): undefined | PreviewType {
99
- const previews = this.previewSlot.values();
100
- const preview = previews.find((previewCandidate) => previewCandidate.name === previewName);
101
-
102
- return preview;
103
- }
104
-
105
- private getParam(query: string, param: string) {
106
- const params = new URLSearchParams(query);
107
- return params.get(param);
108
- }
109
-
110
- private getLocation() {
111
- const withoutHash = window.location.hash.substring(1);
112
- const [before, after] = withoutHash.split('?');
113
-
114
- return {
115
- previewName: this.getParam(after, 'preview'),
116
- componentId: ComponentID.tryFromString(before),
117
- };
118
- }
119
-
120
- static runtime = PreviewRuntime;
121
-
122
- static dependencies = [PubsubAspect];
123
-
124
- static slots = [Slot.withType<PreviewType>(), Slot.withType<RenderingContextProvider>()];
125
-
126
- static async provider(
127
- [pubsub]: [PubsubPreview],
128
- config,
129
- [previewSlot, renderingContextSlot]: [PreviewSlot, RenderingContextSlot]
130
- ) {
131
- const preview = new PreviewPreview(pubsub, previewSlot, renderingContextSlot);
132
-
133
- window.addEventListener('hashchange', () => {
134
- preview.render();
135
- });
136
-
137
- return preview;
138
- }
139
- }
140
-
141
- export function linkModules(previewName: string, defaultModule: any, componentMap: { [key: string]: any }) {
142
- PREVIEW_MODULES[previewName] = {
143
- mainModule: defaultModule,
144
- componentMap,
145
- };
146
- }
147
-
148
- PreviewAspect.addRuntime(PreviewPreview);
package/preview.route.ts DELETED
@@ -1,56 +0,0 @@
1
- import { Request, Response, Route } from '@teambit/express';
2
- import mime from 'mime';
3
- import type { Component } from '@teambit/component';
4
- import { noPreview, serverError } from '@teambit/ui-foundation.ui.pages.static-error';
5
- import type { Logger } from '@teambit/logger';
6
-
7
- import { PreviewMain } from './preview.main.runtime';
8
- import { PreviewArtifact } from './preview-artifact';
9
-
10
- type UrlParams = {
11
- /** `/preview/:previewPath(*)` */
12
- previewPath?: string;
13
- };
14
-
15
- export class PreviewRoute implements Route {
16
- constructor(
17
- /**
18
- * preview extension.
19
- */
20
- private preview: PreviewMain,
21
- private logger: Logger
22
- ) {}
23
-
24
- route = `/preview/:previewPath(*)`;
25
- method = 'get';
26
-
27
- middlewares = [
28
- async (req: Request<UrlParams>, res: Response) => {
29
- try {
30
- // @ts-ignore TODO: @guy please fix.
31
- const component = req.component as Component | undefined;
32
- if (!component) return res.status(404).send(noPreview());
33
-
34
- let artifact: PreviewArtifact | undefined;
35
- // TODO - prevent error `getVinylsAndImportIfMissing is not a function` #4680
36
- try {
37
- artifact = await this.preview.getPreview(component);
38
- } catch (e: any) {
39
- return res.status(404).send(noPreview());
40
- }
41
- // TODO: please fix file path concatenation here.
42
- const file = artifact?.getFile(`public/${req.params.previewPath || 'index.html'}`);
43
- if (!file) return res.status(404).send(noPreview());
44
-
45
- const contents = file.contents;
46
- const str = `${file.cwd}/${file.path}`;
47
- const contentType = mime.getType(str);
48
- if (contentType) res.set('Content-Type', contentType);
49
- return res.send(contents);
50
- } catch (e: any) {
51
- this.logger.error('failed getting preview', e);
52
- return res.status(500).send(serverError());
53
- }
54
- },
55
- ];
56
- }
@@ -1,121 +0,0 @@
1
- import React, { useState, useEffect, Dispatch, SetStateAction } from 'react';
2
- import { flatten } from 'lodash';
3
- import { PreviewServerStatus } from '@teambit/preview.cli.preview-server-status';
4
- import { BundlerMain, ComponentServer } from '@teambit/bundler';
5
- import { PubsubMain } from '@teambit/pubsub';
6
- import { ProxyEntry, StartPlugin, StartPluginOptions, UiMain } from '@teambit/ui';
7
- import { Workspace, CheckTypes } from '@teambit/workspace';
8
- import { SubscribeToWebpackEvents, CompilationResult } from '@teambit/preview.cli.webpack-events-listener';
9
- import { CompilationInitiator } from '@teambit/compiler';
10
- import { Logger } from '@teambit/logger';
11
-
12
- type CompilationServers = Record<string, CompilationResult>;
13
- type ServersSetter = Dispatch<SetStateAction<CompilationServers>>;
14
-
15
- export class PreviewStartPlugin implements StartPlugin {
16
- constructor(
17
- private workspace: Workspace,
18
- private bundler: BundlerMain,
19
- private ui: UiMain,
20
- private pubsub: PubsubMain,
21
- private logger: Logger
22
- ) {}
23
-
24
- previewServers: ComponentServer[] = [];
25
-
26
- async initiate(options: StartPluginOptions) {
27
- this.listenToDevServers();
28
-
29
- const components = await this.workspace.byPattern(options.pattern || '');
30
- // TODO: logic for creating preview servers must be refactored to this aspect from the DevServer aspect.
31
- const previewServers = await this.bundler.devServer(components);
32
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
33
- previewServers.forEach((server) => server.listen());
34
- // DON'T add wait! this promise never resolve so it's stop all the start process!
35
- this.workspace.watcher
36
- .watchAll({
37
- spawnTSServer: true,
38
- checkTypes: CheckTypes.None,
39
- preCompile: false,
40
- msgs: {
41
- onAll: () => {},
42
- onStart: () => {},
43
- onReady: () => {},
44
- onChange: () => {},
45
- onAdd: () => {},
46
- onError: () => {},
47
- onUnlink: () => {},
48
- },
49
- initiator: CompilationInitiator.Start,
50
- })
51
- .catch((err) => {
52
- const msg = `watcher found an error`;
53
- this.logger.error(msg, err);
54
- this.logger.console(`${msg}, ${err.message}`);
55
- });
56
- this.previewServers = this.previewServers.concat(previewServers);
57
- }
58
-
59
- getProxy(): ProxyEntry[] {
60
- const proxyConfigs = this.previewServers.map<ProxyEntry[]>((server) => {
61
- return [
62
- {
63
- context: [`/preview/${server.context.envRuntime.id}`],
64
- target: `http://localhost:${server.port}`,
65
- },
66
- {
67
- context: [`/_hmr/${server.context.envRuntime.id}`],
68
- target: `http://localhost:${server.port}`,
69
- ws: true,
70
- },
71
- ];
72
- });
73
-
74
- return flatten(proxyConfigs);
75
- }
76
-
77
- // TODO: this should be a part of the devServer
78
- private listenToDevServers() {
79
- // keep state changes immutable!
80
- SubscribeToWebpackEvents(this.pubsub, {
81
- onStart: (id) => {
82
- this.updateServers((state) => ({
83
- ...state,
84
- [id]: { compiling: true },
85
- }));
86
- },
87
- onDone: (id, results) => {
88
- this.updateServers((state) => ({
89
- ...state,
90
- [id]: results,
91
- }));
92
- },
93
- });
94
- }
95
-
96
- private setReady: () => void;
97
- private readyPromise = new Promise<void>((resolve) => (this.setReady = resolve));
98
- get whenReady(): Promise<void> {
99
- return this.readyPromise;
100
- }
101
-
102
- private initialState: CompilationServers = {};
103
- // implements react-like setter (value or updater)
104
- private updateServers: ServersSetter = (servers) => {
105
- this.initialState = typeof servers === 'function' ? servers(this.initialState) : servers;
106
- return servers;
107
- };
108
-
109
- render = () => {
110
- const [servers, setServers] = useState<CompilationServers>(this.initialState);
111
- this.updateServers = setServers;
112
- this.initialState = {};
113
-
114
- useEffect(() => {
115
- const noneAreCompiling = Object.values(servers).every((x) => !x.compiling);
116
- if (noneAreCompiling) this.setReady();
117
- }, [servers]);
118
-
119
- return <PreviewServerStatus previewServers={this.previewServers} serverStats={servers} />;
120
- };
121
- }