@teambit/preview 1.0.142 → 1.0.144

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,1044 +0,0 @@
1
- import { ArtifactFactory, BuilderAspect } from '@teambit/builder';
2
- import type { BuilderMain } from '@teambit/builder';
3
- import { Asset, BundlerAspect, BundlerMain } from '@teambit/bundler';
4
- import { PubsubAspect, PubsubMain } from '@teambit/pubsub';
5
- import { MainRuntime } from '@teambit/cli';
6
- import {
7
- Component,
8
- ComponentAspect,
9
- ComponentMain,
10
- ComponentMap,
11
- ComponentID,
12
- ResolveAspectsOptions,
13
- } from '@teambit/component';
14
- import { EnvsAspect } from '@teambit/envs';
15
- import type { EnvsMain, ExecutionContext, PreviewEnv } from '@teambit/envs';
16
- import { Slot, SlotRegistry, Harmony } from '@teambit/harmony';
17
- import { UIAspect, UiMain, UIRoot } from '@teambit/ui';
18
- import { CacheAspect, CacheMain } from '@teambit/cache';
19
- import { CACHE_ROOT } from '@teambit/legacy/dist/constants';
20
- import { BitError } from '@teambit/bit-error';
21
- import objectHash from 'object-hash';
22
- import { uniq } from 'lodash';
23
- import { writeFileSync, existsSync, mkdirSync } from 'fs-extra';
24
- import { join } from 'path';
25
- import { PkgAspect, PkgMain } from '@teambit/pkg';
26
- import { AspectLoaderAspect, getAspectDir, getAspectDirFromBvm } from '@teambit/aspect-loader';
27
- import type { AspectDefinition, AspectLoaderMain } from '@teambit/aspect-loader';
28
- import WorkspaceAspect, { Workspace } from '@teambit/workspace';
29
- import { LoggerAspect, LoggerMain, Logger } from '@teambit/logger';
30
- import { DependencyResolverAspect } from '@teambit/dependency-resolver';
31
- import type { DependencyResolverMain } from '@teambit/dependency-resolver';
32
- import { ArtifactFiles } from '@teambit/legacy/dist/consumer/component/sources/artifact-files';
33
- import WatcherAspect, { WatcherMain } from '@teambit/watcher';
34
- import GraphqlAspect, { GraphqlMain } from '@teambit/graphql';
35
- import ScopeAspect, { ScopeMain } from '@teambit/scope';
36
- import { BundlingStrategyNotFound } from './exceptions';
37
- import { generateLink, MainModulesMap } from './generate-link';
38
- import { PreviewArtifact } from './preview-artifact';
39
- import { PreviewDefinition } from './preview-definition';
40
- import { PreviewAspect, PreviewRuntime } from './preview.aspect';
41
- import { PreviewRoute } from './preview.route';
42
- import { PreviewTask, PREVIEW_TASK_NAME } from './preview.task';
43
- import { BundlingStrategy } from './bundling-strategy';
44
- import {
45
- EnvBundlingStrategy,
46
- ComponentBundlingStrategy,
47
- COMPONENT_STRATEGY_ARTIFACT_NAME,
48
- COMPONENT_STRATEGY_SIZE_KEY_NAME,
49
- ENV_PREVIEW_STRATEGY_NAME,
50
- ENV_STRATEGY_ARTIFACT_NAME,
51
- COMPONENT_PREVIEW_STRATEGY_NAME,
52
- } from './strategies';
53
- import { ExecutionRef } from './execution-ref';
54
- import { PreviewStartPlugin } from './preview.start-plugin';
55
- import {
56
- EnvPreviewTemplateTask,
57
- GENERATE_ENV_TEMPLATE_TASK_NAME,
58
- getArtifactDef as getEnvTemplateArtifactDef,
59
- } from './env-preview-template.task';
60
- import { EnvTemplateRoute } from './env-template.route';
61
- import { ComponentPreviewRoute } from './component-preview.route';
62
- import { previewSchema } from './preview.graphql';
63
- import { PreviewAssetsRoute } from './preview-assets.route';
64
- import { PreviewService } from './preview.service';
65
- import { PUBLIC_DIR, RUNTIME_NAME, buildPreBundlePreview, generateBundlePreviewEntry } from './pre-bundle';
66
- import { BUNDLE_DIR, PreBundlePreviewTask } from './pre-bundle.task';
67
- import { createBundleHash, getBundlePath, readBundleHash } from './pre-bundle-utils';
68
-
69
- const noopResult = {
70
- results: [],
71
- toString: () => `updating link file`,
72
- };
73
-
74
- const DEFAULT_TEMP_DIR = join(CACHE_ROOT, PreviewAspect.id);
75
-
76
- export type PreviewDefinitionRegistry = SlotRegistry<PreviewDefinition>;
77
-
78
- export type PreviewStrategyName = 'env' | 'component';
79
-
80
- export type PreviewFiles = {
81
- files: string[];
82
- isBundledWithEnv: boolean;
83
- };
84
-
85
- export type ComponentPreviewSizedFile = Asset;
86
-
87
- export type ComponentPreviewSize = {
88
- files: ComponentPreviewSizedFile[];
89
- assets: ComponentPreviewSizedFile[];
90
- totalFiles: number;
91
- compressedTotalFiles?: number;
92
- totalAssets: number;
93
- compressedTotalAssets?: number;
94
- total: number;
95
- compressedTotal?: number;
96
- };
97
-
98
- export type ComponentPreviewMetaData = {
99
- size?: ComponentPreviewSize;
100
- };
101
-
102
- export type PreviewVariantConfig = {
103
- isScaling?: boolean;
104
- };
105
-
106
- /**
107
- * Preview data that stored on the component on load
108
- */
109
- export type PreviewComponentData = PreviewAnyComponentData & PreviewEnvComponentData;
110
-
111
- /**
112
- * Preview data that stored on the component on load for any component
113
- */
114
- export type PreviewAnyComponentData = {
115
- doesScaling?: boolean;
116
- /**
117
- * The strategy configured by the component's env
118
- */
119
- strategyName?: PreviewStrategyName;
120
- /**
121
- * Does the component has a bundle for the component itself (separated from the compositions/docs)
122
- * Calculated by the component's env
123
- */
124
- splitComponentBundle?: boolean;
125
-
126
- /**
127
- * don't allow other aspects implementing a preview definition to be included in your preview.
128
- */
129
- onlyOverview?: boolean;
130
-
131
- /**
132
- * use name query params to select a specific composition to render.
133
- */
134
- useNameParam?: boolean;
135
-
136
- /**
137
- * don't allow other aspects implementing a preview definition to be included in your preview.
138
- */
139
- skipIncludes?: boolean;
140
- };
141
-
142
- /**
143
- * Preview data that stored on the component on load if the component is an env
144
- */
145
- export type PreviewEnvComponentData = {
146
- isScaling?: boolean;
147
- supportsOnlyOverview?: boolean;
148
- supportsUseNameParam?: boolean;
149
- };
150
-
151
- export type PreviewConfig = {
152
- bundlingStrategy?: string;
153
- disabled: boolean;
154
- /**
155
- * limit concurrent components when running the bundling step for your bundler during generate components preview task.
156
- * this helps mitigate large memory consumption for the build pipeline. This may increase the overall time for the generate-preview task, but reduce memory footprint.
157
- * default - no limit.
158
- */
159
- maxChunkSize?: number;
160
- onlyOverview?: boolean;
161
- };
162
-
163
- export type EnvPreviewConfig = {
164
- strategyName?: PreviewStrategyName;
165
- splitComponentBundle?: boolean;
166
- };
167
-
168
- export type BundlingStrategySlot = SlotRegistry<BundlingStrategy>;
169
-
170
- export type GenerateLinkFn = (prefix: string, componentMap: ComponentMap<string[]>, defaultModule?: string) => string;
171
-
172
- export class PreviewMain {
173
- constructor(
174
- /**
175
- * harmony context.
176
- */
177
- private harmony: Harmony,
178
-
179
- /**
180
- * slot for preview definitions.
181
- */
182
- private previewSlot: PreviewDefinitionRegistry,
183
-
184
- private ui: UiMain,
185
-
186
- private cache: CacheMain,
187
-
188
- private envs: EnvsMain,
189
-
190
- private componentAspect: ComponentMain,
191
-
192
- private pkg: PkgMain,
193
-
194
- private aspectLoader: AspectLoaderMain,
195
-
196
- readonly config: PreviewConfig,
197
-
198
- private bundlingStrategySlot: BundlingStrategySlot,
199
-
200
- private builder: BuilderMain,
201
-
202
- private workspace: Workspace | undefined,
203
-
204
- private logger: Logger,
205
-
206
- private dependencyResolver: DependencyResolverMain
207
- ) {}
208
-
209
- get tempFolder(): string {
210
- return this.workspace?.getTempDir(PreviewAspect.id) || DEFAULT_TEMP_DIR;
211
- }
212
-
213
- getComponentBundleSize(component: Component): ComponentPreviewSize | undefined {
214
- const data = this.builder.getDataByAspect(component, PreviewAspect.id);
215
-
216
- if (!data) return undefined;
217
- return data[COMPONENT_STRATEGY_SIZE_KEY_NAME];
218
- }
219
-
220
- async getPreview(component: Component): Promise<PreviewArtifact | undefined> {
221
- const artifacts = await this.builder.getArtifactsVinylByAspectAndTaskName(
222
- component,
223
- PreviewAspect.id,
224
- PREVIEW_TASK_NAME
225
- );
226
- if (!artifacts) return undefined;
227
- return new PreviewArtifact(artifacts);
228
- }
229
-
230
- /**
231
- * Get a list of all the artifact files generated during the GeneratePreview task
232
- * @param component
233
- * @returns
234
- */
235
- async getPreviewFiles(component: Component): Promise<PreviewFiles | undefined> {
236
- const artifacts = await this.getPreview(component);
237
- const isBundledWithEnv = await this.isBundledWithEnv(component);
238
- if (!artifacts) return undefined;
239
- return {
240
- files: artifacts.getPaths(),
241
- isBundledWithEnv,
242
- };
243
- }
244
-
245
- /**
246
- * Get the preview config of the component.
247
- * (config that was set by variants or on bitmap)
248
- * @param component
249
- * @returns
250
- */
251
- getPreviewAspectConfig(component: Component): PreviewVariantConfig | undefined {
252
- return component.state.aspects.get(PreviewAspect.id)?.config;
253
- }
254
-
255
- /**
256
- * Get the preview data of the component.
257
- * (data that was calculated during the on load process)
258
- * @param component
259
- * @returns
260
- */
261
- getPreviewData(component: Component): PreviewComponentData | undefined {
262
- const previewData = component.state.aspects.get(PreviewAspect.id)?.data;
263
- return previewData;
264
- }
265
-
266
- /**
267
- * check if the current version of env component supports skipping other included previews
268
- * @param envComponent
269
- * @returns
270
- */
271
- doesEnvIncludesOnlyOverview(envComponent: Component): boolean {
272
- const previewData = this.getPreviewData(envComponent);
273
- return !!previewData?.supportsOnlyOverview;
274
- }
275
-
276
- /**
277
- * check if the current version of env component supports name query param
278
- * @param envComponent
279
- * @returns
280
- */
281
- doesEnvUseNameParam(envComponent: Component): boolean {
282
- const previewData = this.getPreviewData(envComponent);
283
- return !!previewData?.supportsUseNameParam;
284
- }
285
-
286
- private async calculateIncludeOnlyOverview(component: Component): Promise<boolean> {
287
- if (this.envs.isUsingCoreEnv(component)) {
288
- return true;
289
- }
290
- const envComponent = await this.envs.getEnvComponent(component);
291
- return this.doesEnvIncludesOnlyOverview(envComponent);
292
- }
293
-
294
- private async calculateUseNameParam(component: Component): Promise<boolean> {
295
- if (this.envs.isUsingCoreEnv(component)) {
296
- return true;
297
- }
298
- const envComponent = await this.envs.getEnvComponent(component);
299
- return this.doesEnvUseNameParam(envComponent);
300
- }
301
-
302
- /**
303
- * Calculate preview data on component load
304
- * @param component
305
- * @returns
306
- */
307
- async calcPreviewData(component: Component): Promise<PreviewComponentData> {
308
- const doesScaling = await this.calcDoesScalingForComponent(component);
309
- const dataFromEnv = await this.calcPreviewDataFromEnv(component);
310
- const envData = (await this.calculateDataForEnvComponent(component)) || {};
311
- const onlyOverview = await this.calculateIncludeOnlyOverview(component);
312
- const useNameParam = await this.calculateUseNameParam(component);
313
-
314
- const data: PreviewComponentData = {
315
- doesScaling,
316
- onlyOverview,
317
- useNameParam,
318
- ...dataFromEnv,
319
- ...envData,
320
- };
321
- return data;
322
- }
323
-
324
- /**
325
- * Calculate preview data on component that configured by its env
326
- * @param component
327
- * @returns
328
- */
329
- async calcPreviewDataFromEnv(
330
- component: Component
331
- ): Promise<Omit<PreviewAnyComponentData, 'doesScaling' | 'onlyOverview' | 'useNameParam'> | undefined> {
332
- // Prevent infinite loop that caused by the fact that the env of the aspect env or the env env is the same as the component
333
- // so we can't load it since during load we are trying to get env component and load it again
334
- if (
335
- component.id.toStringWithoutVersion() === 'teambit.harmony/aspect' ||
336
- component.id.toStringWithoutVersion() === 'teambit.envs/env'
337
- ) {
338
- return {
339
- strategyName: COMPONENT_PREVIEW_STRATEGY_NAME,
340
- splitComponentBundle: false,
341
- };
342
- }
343
-
344
- const env = this.envs.getEnv(component).env;
345
- const envPreviewConfig = this.getEnvPreviewConfig(env);
346
- const data = {
347
- strategyName: envPreviewConfig?.strategyName,
348
- splitComponentBundle: envPreviewConfig?.splitComponentBundle ?? false,
349
- };
350
- return data;
351
- }
352
-
353
- /**
354
- * calculate extra preview data for env components (on load)
355
- * @param envComponent
356
- * @returns
357
- */
358
- private async calculateDataForEnvComponent(envComponent: Component): Promise<PreviewEnvComponentData | undefined> {
359
- const isEnv = this.envs.isEnv(envComponent);
360
-
361
- // If the component is not an env, we don't want to store anything in the data
362
- if (!isEnv) return undefined;
363
-
364
- const previewAspectConfig = this.getPreviewAspectConfig(envComponent);
365
-
366
- const data = {
367
- // default to true if the env doesn't have a preview config
368
- isScaling: previewAspectConfig?.isScaling ?? true,
369
- supportsOnlyOverview: true,
370
- supportsUseNameParam: true,
371
- };
372
- return data;
373
- }
374
-
375
- /**
376
- * Check if the component preview bundle contain the env as part of the bundle or only the component code
377
- * (we used in the past to bundle them together, there might also be specific envs which still uses the env strategy)
378
- * This should be used only for calculating the value on load.
379
- * otherwise, use the isBundledWithEnv function
380
- * @param component
381
- * @returns
382
- */
383
- async calcIsBundledWithEnv(component: Component): Promise<boolean> {
384
- const envPreviewData = await this.calcPreviewDataFromEnv(component);
385
- return envPreviewData?.strategyName !== 'component';
386
- }
387
-
388
- /**
389
- * Check if the component preview bundle contain the env as part of the bundle or only the component code
390
- * (we used in the past to bundle them together, there might also be specific envs which still uses the env strategy)
391
- * @param component
392
- * @returns
393
- */
394
- async isBundledWithEnv(component: Component): Promise<boolean> {
395
- const data = await this.getPreviewData(component);
396
- // For components that tagged in the past we didn't store the data, so we calculate it the old way
397
- // We comparing the strategyName to undefined to cover a specific case when it doesn't exist at all.
398
- if (!data || data.strategyName === undefined) return this.isBundledWithEnvBackward(component);
399
- return data.strategyName === ENV_PREVIEW_STRATEGY_NAME;
400
- }
401
-
402
- /**
403
- * This is a legacy calculation for the isBundledWithEnv
404
- * This calc is based on the component artifacts which is very expensive operation as it requires to fetch and load the artifacts
405
- * See the new implementation in the isBundledWithEnv method
406
- * @param component
407
- * @returns
408
- */
409
- private async isBundledWithEnvBackward(component: Component): Promise<boolean> {
410
- const artifacts = await this.builder.getArtifactsVinylByAspectAndName(
411
- component,
412
- PreviewAspect.id,
413
- COMPONENT_STRATEGY_ARTIFACT_NAME
414
- );
415
- if (!artifacts || !artifacts.length) return true;
416
-
417
- return false;
418
- }
419
-
420
- // This used on component load to calc the final result of support is scaling for a given component
421
- // This calc based on the env, env data, env preview config and more
422
- // if you want to get the final result use the `doesScaling` method below
423
- // This should be used only for component load
424
- private async calcDoesScalingForComponent(component: Component): Promise<boolean> {
425
- const isBundledWithEnv = await this.calcIsBundledWithEnv(component);
426
- // if it's a core env and the env template is apart from the component it means the template bundle already contain the scaling functionality
427
- if (this.envs.isUsingCoreEnv(component)) {
428
- // If the component is new, no point to check the is bundle with env (there is no artifacts so it will for sure return false)
429
- // If it's new, and we are here, it means that we already use a version of the env that support scaling
430
- const isNew = await component.isNew();
431
- if (isNew) {
432
- return true;
433
- }
434
- return isBundledWithEnv === false;
435
- }
436
- // For envs that bundled with the env return true always
437
- if (isBundledWithEnv) {
438
- return true;
439
- }
440
- const envComponent = await this.envs.getEnvComponent(component);
441
- return this.isEnvSupportScaling(envComponent);
442
- }
443
-
444
- /**
445
- * can the current component preview scale in size for different preview sizes.
446
- * this calculation is based on the env of the component and if the env of the component support it.
447
- */
448
- async doesScaling(component: Component): Promise<boolean> {
449
- const inWorkspace = await this.workspace?.hasId(component.id);
450
- // Support case when we have the dev server for the env, in that case we calc the data of the env as we can't rely on the env data from the scope
451
- // since we bundle it for the dev server again
452
- if (inWorkspace) {
453
- // if it's a core env and the env template is apart from the component it means the template bundle already contain the scaling functionality
454
- if (this.envs.isUsingCoreEnv(component)) {
455
- const isBundledWithEnv = await this.isBundledWithEnv(component);
456
- // If the component is new, no point to check the is bundle with env (there is no artifacts so it will for sure return false)
457
- // If it's new, and we are here, it means that we already use a version of the env that support scaling
458
- const isNew = await component.isNew();
459
- if (isNew) {
460
- return true;
461
- }
462
- return isBundledWithEnv === false;
463
- }
464
- const envComponent = await this.envs.getEnvComponent(component);
465
- const envSupportScaling = await this.calculateIsEnvSupportScaling(envComponent);
466
- return envSupportScaling ?? true;
467
- }
468
- const previewData = this.getPreviewData(component);
469
- if (!previewData) return false;
470
- // Get the does scaling (the new calculation) or the old calc used in isScaling (between versions (about) 848 and 860)
471
- if (previewData.doesScaling !== undefined) return previewData.doesScaling;
472
- // in case this component were tagged with versions between 848 and 860 we need to use the old calculation
473
- // together with the env calculation
474
- // In that case it means the component already tagged, so we take the env calc from the env data and not re-calc it
475
- if (previewData.isScaling) {
476
- const envComponent = await this.envs.getEnvComponent(component);
477
- const envSupportScaling = this.isEnvSupportScaling(envComponent);
478
- return !!envSupportScaling;
479
- }
480
- return false;
481
- }
482
-
483
- /**
484
- * Check if the current version of the env support scaling
485
- * @param envComponent
486
- * @returns
487
- */
488
- isEnvSupportScaling(envComponent: Component): boolean {
489
- const previewData = this.getPreviewData(envComponent);
490
- return !!previewData?.isScaling;
491
- }
492
-
493
- async isSupportSkipIncludes(component: Component) {
494
- if (!this.config.onlyOverview) return false;
495
-
496
- const isCore = this.envs.isUsingCoreEnv(component);
497
- if (isCore) return false;
498
-
499
- const envComponent = await this.envs.getEnvComponent(component);
500
- const previewData = this.getPreviewData(envComponent);
501
- return !!previewData?.skipIncludes;
502
- }
503
-
504
- /**
505
- * check if the component preview should only include the overview (skipping rendering of the compostions and properties table)
506
- */
507
- async getOnlyOverview(component: Component): Promise<boolean> {
508
- if (!this.config.onlyOverview) return false;
509
- const previewData = this.getPreviewData(component);
510
- return previewData?.onlyOverview ?? false;
511
- }
512
-
513
- /**
514
- * check if the component preview should include the name query param
515
- */
516
- async getUseNameParam(component: Component): Promise<boolean> {
517
- const previewData = this.getPreviewData(component);
518
- return previewData?.useNameParam ?? false;
519
- }
520
-
521
- /**
522
- * This function is calculate the isScaling support flag for the component preview.
523
- * This is calculated only for the env component and not for the component itself.
524
- * It should be only used during the (env) component on load.
525
- * Once the component load, you should only use the `isEnvSupportScaling` to fetch it from the calculated data.
526
- * If you want to check if an env for a given component support scaling, use the `isScaling` function.
527
- * @param component
528
- * @returns
529
- */
530
- private async calculateIsEnvSupportScaling(envComponent: Component): Promise<boolean | undefined> {
531
- const isEnv = this.envs.isEnv(envComponent);
532
- // If the component is not an env, we don't want to store anything in the data
533
- if (!isEnv) return undefined;
534
- const previewAspectConfig = this.getPreviewAspectConfig(envComponent);
535
- // default to true if the env doesn't have a preview config
536
- return previewAspectConfig?.isScaling ?? true;
537
- }
538
-
539
- /**
540
- * Check if the component preview bundle contain the header inside of it (legacy)
541
- * today we are not including the header inside anymore
542
- * @param component
543
- * @returns
544
- */
545
- async isLegacyHeader(component: Component): Promise<boolean> {
546
- // these envs had header in their docs
547
- const ENV_WITH_LEGACY_DOCS = ['react', 'env', 'aspect', 'lit', 'html', 'node', 'mdx', 'react-native', 'readme'];
548
-
549
- const artifacts = await this.builder.getArtifactsVinylByAspectAndName(
550
- component,
551
- PreviewAspect.id,
552
- ENV_STRATEGY_ARTIFACT_NAME
553
- );
554
- const envType = this.envs.getEnvData(component).type;
555
- return !!artifacts && !!artifacts.length && ENV_WITH_LEGACY_DOCS.includes(envType || '');
556
- }
557
-
558
- /**
559
- * Getting the env template artifact
560
- * This should be called with the env itself or it will return undefined
561
- * If you want to get the env template from the env of the component,
562
- * use: getEnvTemplateFromComponentEnv below
563
- *
564
- * @param component
565
- * @returns
566
- */
567
- async getEnvTemplate(component: Component): Promise<PreviewArtifact | undefined> {
568
- const artifacts = await this.builder.getArtifactsVinylByAspectAndTaskName(
569
- component,
570
- PreviewAspect.id,
571
- GENERATE_ENV_TEMPLATE_TASK_NAME
572
- );
573
- if (!artifacts || !artifacts.length) return undefined;
574
-
575
- return new PreviewArtifact(artifacts);
576
- }
577
-
578
- /**
579
- * This is a special method to get a core env template
580
- * As the core envs doesn't exist in the scope we need to bring it from other place
581
- * We will bring it from the core env package files
582
- */
583
- private async getCoreEnvTemplate(envId: string): Promise<PreviewArtifact | undefined> {
584
- const coreEnvDir = getAspectDir(envId);
585
- // const finalDir = join(coreEnvDir, getEnvTemplateArtifactDirectory());
586
- const artifactDef = getEnvTemplateArtifactDef()[0];
587
- const artifactFactory = new ArtifactFactory();
588
-
589
- let rootDir = artifactFactory.getRootDir(coreEnvDir, artifactDef);
590
- if (!existsSync(rootDir)) {
591
- // fallback to the bvm folder
592
- const coreEnvDirFromBvm = getAspectDirFromBvm(envId);
593
- rootDir = artifactFactory.getRootDir(coreEnvDirFromBvm, artifactDef);
594
- }
595
- if (!existsSync(rootDir)) {
596
- return undefined;
597
- }
598
- const paths = artifactFactory.resolvePaths(rootDir, artifactDef);
599
- if (!paths || !paths.length) {
600
- return undefined;
601
- }
602
- const artifactFiles = new ArtifactFiles(paths);
603
-
604
- artifactFiles.populateVinylsFromPaths(rootDir);
605
- return new PreviewArtifact(artifactFiles.vinyls);
606
- }
607
-
608
- /**
609
- * This will fetch the component env, then will take the env template from the component env
610
- * @param component
611
- */
612
- async getEnvTemplateFromComponentEnv(component: Component): Promise<PreviewArtifact | undefined> {
613
- const envId = this.envs.getEnvId(component);
614
- return this.getEnvTemplateByEnvId(envId);
615
- }
616
-
617
- /**
618
- * This will fetch the component env, then will take the env template from the component env
619
- * @param component
620
- */
621
- async getEnvTemplateByEnvId(envId: string): Promise<PreviewArtifact | undefined> {
622
- // Special treatment for core envs
623
- if (this.aspectLoader.isCoreEnv(envId)) {
624
- return this.getCoreEnvTemplate(envId);
625
- }
626
- const host = this.componentAspect.getHost();
627
- const resolvedEnvId = await host.resolveComponentId(envId);
628
- const envComponent = await host.get(resolvedEnvId);
629
- if (!envComponent) {
630
- throw new BitError(`can't load env. env id is ${envId}`);
631
- }
632
- return this.getEnvTemplate(envComponent);
633
- }
634
-
635
- getDefs(): PreviewDefinition[] {
636
- return this.previewSlot.values();
637
- }
638
-
639
- private writeHash = new Map<string, string>();
640
- private timestamp = Date.now();
641
-
642
- /**
643
- * write a link to load custom modules dynamically.
644
- * @param prefix write
645
- * @param moduleMap map of components to module paths to require.
646
- * @param defaultModule
647
- * @param dirName
648
- */
649
- writeLink(
650
- prefix: string,
651
- moduleMap: ComponentMap<string[]>,
652
- mainModulesMap: MainModulesMap,
653
- dirName: string,
654
- isSplitComponentBundle: boolean
655
- ) {
656
- const contents = generateLink(prefix, moduleMap, mainModulesMap, isSplitComponentBundle);
657
- return this.writeLinkContents(contents, dirName, prefix);
658
- }
659
-
660
- writeLinkContents(contents: string, targetDir: string, prefix: string) {
661
- const hash = objectHash(contents);
662
- const targetPath = join(targetDir, `${prefix}-${this.timestamp}.js`);
663
-
664
- // write only if link has changed (prevents triggering fs watches)
665
- if (this.writeHash.get(targetPath) !== hash) {
666
- writeFileSync(targetPath, contents);
667
- this.writeHash.set(targetPath, hash);
668
- }
669
-
670
- return targetPath;
671
- }
672
-
673
- private executionRefs = new Map<string, ExecutionRef>();
674
-
675
- private async getPreviewTarget(
676
- /** execution context (of the specific env) */
677
- context: ExecutionContext
678
- ): Promise<string[]> {
679
- // store context for later link-file updates
680
- // also register related envs that this context is acting on their behalf
681
- [context.id, ...context.relatedContexts].forEach((ctxId) => {
682
- this.executionRefs.set(ctxId, new ExecutionRef(context));
683
- });
684
-
685
- const previewRuntime = await this.writePreviewEntry(context);
686
- const linkFiles = await this.updateLinkFiles(context.components, context);
687
-
688
- return [...linkFiles, previewRuntime];
689
- }
690
-
691
- private async writePreviewEntry(context: { components: Component[] }, aspectsIdsToNotFilterOut: string[] = []) {
692
- const { rebuild, skipUiBuild } = this.ui.runtimeOptions;
693
-
694
- const [name, uiRoot] = this.getUi();
695
- const cacheKey = `${uiRoot.path}|${RUNTIME_NAME}`;
696
- const currentBundleHash = await createBundleHash(uiRoot, RUNTIME_NAME);
697
- const preBundleHash = readBundleHash(PreviewAspect.id, BUNDLE_DIR, '');
698
- const workspaceBundleDir = join(uiRoot.path, PUBLIC_DIR);
699
- const lastBundleHash = await this.cache.get(cacheKey);
700
-
701
- let bundlePath = '';
702
-
703
- // ensure the pre-bundle is ready
704
- if (!rebuild && !existsSync(workspaceBundleDir) && (currentBundleHash === preBundleHash || skipUiBuild)) {
705
- // use pre-bundle
706
- bundlePath = getBundlePath(PreviewAspect.id, BUNDLE_DIR, '') as string;
707
- } else if (!rebuild && existsSync(workspaceBundleDir) && (currentBundleHash === lastBundleHash || skipUiBuild)) {
708
- // use workspace bundle
709
- bundlePath = workspaceBundleDir;
710
- } else {
711
- // do build
712
- const resolvedAspects = await this.resolveAspects(PreviewRuntime.name, undefined, uiRoot);
713
- const filteredAspects = this.filterAspectsByExecutionContext(resolvedAspects, context, aspectsIdsToNotFilterOut);
714
-
715
- await buildPreBundlePreview(filteredAspects);
716
- bundlePath = workspaceBundleDir;
717
- await this.cache.set(cacheKey, currentBundleHash);
718
- }
719
-
720
- // prepare the runtime entry
721
- const previewRuntime = await generateBundlePreviewEntry(name, bundlePath, this.harmony.config.toObject());
722
-
723
- return previewRuntime;
724
- }
725
-
726
- private updateLinkFiles(components: Component[] = [], context: ExecutionContext) {
727
- const previews = this.previewSlot.values();
728
- const paths = previews.map(async (previewDef) => {
729
- const defaultTemplatePath = await previewDef.renderTemplatePathByEnv?.(context.env);
730
- const visitedEnvs = new Set();
731
- const mainModulesMap: MainModulesMap = {
732
- // @ts-ignore
733
- default: defaultTemplatePath,
734
- [context.envDefinition.id]: defaultTemplatePath,
735
- };
736
-
737
- const map = await previewDef.getModuleMap(components);
738
- const isSplitComponentBundle = this.getEnvPreviewConfig().splitComponentBundle ?? false;
739
- const withPathsP = map.asyncMap(async (files, component) => {
740
- const envDef = this.envs.getEnv(component);
741
- const environment = envDef.env;
742
- const envId = envDef.id;
743
-
744
- if (!mainModulesMap[envId] && !visitedEnvs.has(envId)) {
745
- const modulePath = await previewDef.renderTemplatePathByEnv?.(envDef.env);
746
- if (modulePath) {
747
- mainModulesMap[envId] = modulePath;
748
- }
749
- visitedEnvs.add(envId);
750
- }
751
- const compilerInstance = environment.getCompiler?.();
752
- const modulePath =
753
- compilerInstance?.getPreviewComponentRootPath?.(component) || this.pkg.getRuntimeModulePath(component);
754
- return files.map((file) => {
755
- if (!this.workspace || !compilerInstance) {
756
- return file.path;
757
- }
758
- const distRelativePath = compilerInstance.getDistPathBySrcPath(file.relative);
759
- return join(this.workspace.path, modulePath, distRelativePath);
760
- });
761
- // return files.map((file) => file.path);
762
- });
763
- const withPaths = await withPathsP;
764
-
765
- const dirPath = join(this.tempFolder, context.id);
766
- if (!existsSync(dirPath)) mkdirSync(dirPath, { recursive: true });
767
-
768
- const link = this.writeLink(previewDef.prefix, withPaths, mainModulesMap, dirPath, isSplitComponentBundle);
769
- return link;
770
- });
771
-
772
- return Promise.all(paths);
773
- }
774
-
775
- async writePreviewRuntime(context: { components: Component[] }, aspectsIdsToNotFilterOut: string[] = []) {
776
- const [name, uiRoot] = this.getUi();
777
- const resolvedAspects = await this.resolveAspects(PreviewRuntime.name, undefined, uiRoot);
778
- const filteredAspects = this.filterAspectsByExecutionContext(resolvedAspects, context, aspectsIdsToNotFilterOut);
779
- const filePath = await this.ui.generateRoot(filteredAspects, name, 'preview', PreviewAspect.id);
780
- return filePath;
781
- }
782
-
783
- async resolveAspects(
784
- runtimeName?: string,
785
- componentIds?: ComponentID[],
786
- uiRoot?: UIRoot,
787
- opts?: ResolveAspectsOptions
788
- ): Promise<AspectDefinition[]> {
789
- const root = uiRoot || this.getUi()[1];
790
- runtimeName = runtimeName || MainRuntime.name;
791
- const resolvedAspects = await root.resolveAspects(runtimeName, componentIds, opts);
792
- return resolvedAspects;
793
- }
794
-
795
- private getUi() {
796
- const ui = this.ui.getUi();
797
- if (!ui) throw new Error('ui not found');
798
- return ui;
799
- }
800
-
801
- /**
802
- * Filter the aspects to have only aspects that are:
803
- * 1. core aspects
804
- * 2. configured on the host (workspace/scope)
805
- * 3. used by at least one component from the context
806
- * @param aspects
807
- * @param context
808
- */
809
- private filterAspectsByExecutionContext(
810
- aspects: AspectDefinition[],
811
- context: { components: Component[] },
812
- aspectsIdsToNotFilterOut: string[] = []
813
- ) {
814
- let allComponentContextAspects: string[] = [];
815
- allComponentContextAspects = context.components.reduce((acc, curr) => {
816
- return acc.concat(curr.state.aspects.ids);
817
- }, allComponentContextAspects);
818
- const hostAspects = Object.keys(this.harmony.config.toObject());
819
- const allAspectsToInclude = uniq(hostAspects.concat(allComponentContextAspects));
820
- const filtered = aspects.filter((aspect) => {
821
- if (!aspect.getId) {
822
- return false;
823
- }
824
- return (
825
- this.aspectLoader.isCoreAspect(aspect.getId) ||
826
- allAspectsToInclude.includes(aspect.getId) ||
827
- aspectsIdsToNotFilterOut.includes(aspect.getId)
828
- );
829
- });
830
-
831
- return filtered;
832
- }
833
-
834
- private getDefaultStrategies() {
835
- return [
836
- new EnvBundlingStrategy(this, this.pkg, this.dependencyResolver),
837
- new ComponentBundlingStrategy(this, this.pkg, this.dependencyResolver),
838
- ];
839
- }
840
-
841
- // TODO - executionContext should be responsible for updating components list, and emit 'update' events
842
- // instead we keep track of changes
843
- private handleComponentChange = async (c: Component, updater: (currentComponents: ExecutionRef) => void) => {
844
- const env = this.envs.getEnv(c);
845
- const envId = env.id.toString();
846
-
847
- const executionRef = this.executionRefs.get(envId);
848
- if (!executionRef) {
849
- this.logger.warn(
850
- `failed to update link file for component "${c.id.toString()}" - could not find execution context for ${envId}`
851
- );
852
- return noopResult;
853
- }
854
-
855
- // add / remove / etc
856
- updater(executionRef);
857
-
858
- await this.updateLinkFiles(executionRef.currentComponents, executionRef.executionCtx);
859
- return noopResult;
860
- };
861
-
862
- private handleComponentRemoval = (cId: ComponentID) => {
863
- let component: Component | undefined;
864
- this.executionRefs.forEach((components) => {
865
- const found = components.get(cId);
866
- if (found) component = found;
867
- });
868
- if (!component) return Promise.resolve(noopResult);
869
-
870
- return this.handleComponentChange(component, (currentComponents) => currentComponents.remove(cId));
871
- };
872
-
873
- getEnvPreviewConfig(env?: PreviewEnv): EnvPreviewConfig {
874
- const config = env?.getPreviewConfig && typeof env?.getPreviewConfig === 'function' ? env?.getPreviewConfig() : {};
875
-
876
- return config;
877
- }
878
-
879
- /**
880
- * return the configured bundling strategy.
881
- */
882
- getBundlingStrategy(env?: PreviewEnv): BundlingStrategy {
883
- const defaultStrategies = this.getDefaultStrategies();
884
- const envPreviewConfig = this.getEnvPreviewConfig(env);
885
- const strategyFromEnv = envPreviewConfig?.strategyName;
886
- const strategyName = strategyFromEnv || this.config.bundlingStrategy || 'env';
887
- const strategies = this.bundlingStrategySlot.values().concat(defaultStrategies);
888
- const selected = strategies.find((strategy) => {
889
- return strategy.name === strategyName;
890
- });
891
-
892
- if (!selected) throw new BundlingStrategyNotFound(strategyName);
893
-
894
- return selected;
895
- }
896
-
897
- /**
898
- * register a new bundling strategy. default available strategies are `env` and ``
899
- */
900
- registerBundlingStrategy(bundlingStrategy: BundlingStrategy) {
901
- this.bundlingStrategySlot.register(bundlingStrategy);
902
- return this;
903
- }
904
-
905
- /**
906
- * register a new preview definition.
907
- */
908
- registerDefinition(previewDef: PreviewDefinition) {
909
- this.previewSlot.register(previewDef);
910
- }
911
-
912
- static slots = [Slot.withType<PreviewDefinition>(), Slot.withType<BundlingStrategy>()];
913
-
914
- static runtime = MainRuntime;
915
- static dependencies = [
916
- BundlerAspect,
917
- BuilderAspect,
918
- ComponentAspect,
919
- UIAspect,
920
- CacheAspect,
921
- EnvsAspect,
922
- WorkspaceAspect,
923
- PkgAspect,
924
- PubsubAspect,
925
- AspectLoaderAspect,
926
- LoggerAspect,
927
- DependencyResolverAspect,
928
- GraphqlAspect,
929
- WatcherAspect,
930
- ScopeAspect,
931
- ];
932
-
933
- static defaultConfig = {
934
- disabled: false,
935
- onlyOverview: false,
936
- };
937
-
938
- static async provider(
939
- // eslint-disable-next-line max-len
940
- [
941
- bundler,
942
- builder,
943
- componentExtension,
944
- uiMain,
945
- cache,
946
- envs,
947
- workspace,
948
- pkg,
949
- pubsub,
950
- aspectLoader,
951
- loggerMain,
952
- dependencyResolver,
953
- graphql,
954
- watcher,
955
- scope,
956
- ]: [
957
- BundlerMain,
958
- BuilderMain,
959
- ComponentMain,
960
- UiMain,
961
- CacheMain,
962
- EnvsMain,
963
- Workspace | undefined,
964
- PkgMain,
965
- PubsubMain,
966
- AspectLoaderMain,
967
- LoggerMain,
968
- DependencyResolverMain,
969
- GraphqlMain,
970
- WatcherMain,
971
- ScopeMain
972
- ],
973
- config: PreviewConfig,
974
- [previewSlot, bundlingStrategySlot]: [PreviewDefinitionRegistry, BundlingStrategySlot],
975
- harmony: Harmony
976
- ) {
977
- const logger = loggerMain.createLogger(PreviewAspect.id);
978
- // app.registerApp(new PreviewApp());
979
- const preview = new PreviewMain(
980
- harmony,
981
- previewSlot,
982
- uiMain,
983
- cache,
984
- envs,
985
- componentExtension,
986
- pkg,
987
- aspectLoader,
988
- config,
989
- bundlingStrategySlot,
990
- builder,
991
- workspace,
992
- logger,
993
- dependencyResolver
994
- );
995
-
996
- if (workspace)
997
- uiMain.registerStartPlugin(new PreviewStartPlugin(workspace, bundler, uiMain, pubsub, logger, watcher));
998
-
999
- componentExtension.registerRoute([
1000
- new PreviewRoute(preview, logger),
1001
- new ComponentPreviewRoute(preview, logger),
1002
- // @ts-ignore
1003
- new EnvTemplateRoute(preview, logger),
1004
- new PreviewAssetsRoute(preview, logger),
1005
- ]);
1006
-
1007
- bundler.registerTarget([
1008
- {
1009
- entry: preview.getPreviewTarget.bind(preview),
1010
- },
1011
- ]);
1012
-
1013
- if (!config.disabled)
1014
- builder.registerBuildTasks([
1015
- new EnvPreviewTemplateTask(preview, envs, aspectLoader, dependencyResolver, logger),
1016
- new PreviewTask(bundler, preview, dependencyResolver, logger),
1017
- new PreBundlePreviewTask(uiMain, logger),
1018
- ]);
1019
-
1020
- if (workspace) {
1021
- workspace.registerOnComponentAdd((c) =>
1022
- preview.handleComponentChange(c, (currentComponents) => currentComponents.add(c))
1023
- );
1024
- workspace.registerOnComponentLoad(async (component) => {
1025
- return preview.calcPreviewData(component);
1026
- });
1027
- workspace.registerOnComponentChange((c) =>
1028
- preview.handleComponentChange(c, (currentComponents) => currentComponents.update(c))
1029
- );
1030
- workspace.registerOnComponentRemove((cId) => preview.handleComponentRemoval(cId));
1031
- }
1032
- if (scope) {
1033
- scope.registerOnCompAspectReCalc((c) => preview.calcPreviewData(c));
1034
- }
1035
-
1036
- envs.registerService(new PreviewService());
1037
-
1038
- graphql.register(previewSchema(preview));
1039
-
1040
- return preview;
1041
- }
1042
- }
1043
-
1044
- PreviewAspect.addRuntime(PreviewMain);