@teambit/preview 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,14 @@
1
+ /* eslint-disable max-classes-per-file */
2
+ import { BitBaseEvent } from '@teambit/pubsub';
3
+
4
+ class ClickInsideAnIframeEventData {
5
+ constructor(private clickEvent: any) {}
6
+ }
7
+
8
+ export class ClickInsideAnIframeEvent extends BitBaseEvent<ClickInsideAnIframeEventData> {
9
+ static readonly TYPE = 'click-inside-an-iframe';
10
+
11
+ constructor(readonly timestamp = Date.now(), readonly clickEvent) {
12
+ super(ClickInsideAnIframeEvent.TYPE, '0.0.1', timestamp, new ClickInsideAnIframeEventData(clickEvent));
13
+ }
14
+ }
@@ -0,0 +1 @@
1
+ export * from './click-inside-an-iframe';
@@ -0,0 +1,5 @@
1
+ export class BundlingStrategyNotFound extends Error {
2
+ constructor(private strategyName: string) {
3
+ super(`bundling strategy with name ${strategyName} was not found`);
4
+ }
5
+ }
@@ -0,0 +1,3 @@
1
+ export { PreviewNotFound } from './preview-not-found';
2
+ export { PreviewArtifactNotFound } from './preview-artifact-not-found';
3
+ export { BundlingStrategyNotFound } from './bundling-strategy-not-found';
@@ -0,0 +1,8 @@
1
+ import { BitError } from '@teambit/bit-error';
2
+ import { ComponentID } from '@teambit/component';
3
+
4
+ export class PreviewArtifactNotFound extends BitError {
5
+ constructor(componentId: ComponentID) {
6
+ super(`preview for component ${componentId.toString()} was not found`);
7
+ }
8
+ }
@@ -0,0 +1,5 @@
1
+ export class PreviewNotFound extends Error {
2
+ constructor(name: string | null) {
3
+ super(`Preview for name: ${name} was not found`);
4
+ }
5
+ }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@teambit/preview",
3
- "version": "0.0.566",
3
+ "version": "0.0.570",
4
4
  "homepage": "https://bit.dev/teambit/preview/preview",
5
5
  "main": "dist/index.js",
6
6
  "componentId": {
7
7
  "scope": "teambit.preview",
8
8
  "name": "preview",
9
- "version": "0.0.566"
9
+ "version": "0.0.570"
10
10
  },
11
11
  "dependencies": {
12
12
  "fs-extra": "10.0.0",
@@ -17,26 +17,26 @@
17
17
  "mime": "2.5.2",
18
18
  "@babel/runtime": "7.12.18",
19
19
  "core-js": "^3.0.0",
20
- "@teambit/builder": "0.0.566",
21
- "@teambit/bundler": "0.0.566",
22
- "@teambit/component": "0.0.566",
23
- "@teambit/envs": "0.0.566",
24
- "@teambit/toolbox.path.to-windows-compatible-path": "0.0.467",
25
- "@teambit/aspect-loader": "0.0.566",
26
- "@teambit/cli": "0.0.393",
27
- "@teambit/logger": "0.0.478",
28
- "@teambit/pkg": "0.0.566",
29
- "@teambit/pubsub": "0.0.566",
30
- "@teambit/ui": "0.0.566",
31
- "@teambit/workspace": "0.0.566",
32
- "@teambit/component-id": "0.0.383",
33
- "@teambit/express": "0.0.482",
34
- "@teambit/ui-foundation.ui.pages.static-error": "0.0.54",
35
- "@teambit/compiler": "0.0.566",
36
- "@teambit/preview.cli.preview-server-status": "0.0.473",
37
- "@teambit/preview.cli.webpack-events-listener": "0.0.143",
38
- "@teambit/isolator": "0.0.566",
39
- "@teambit/bit-error": "0.0.379"
20
+ "@teambit/builder": "0.0.570",
21
+ "@teambit/bundler": "0.0.570",
22
+ "@teambit/component": "0.0.570",
23
+ "@teambit/envs": "0.0.570",
24
+ "@teambit/toolbox.path.to-windows-compatible-path": "0.0.469",
25
+ "@teambit/aspect-loader": "0.0.570",
26
+ "@teambit/cli": "0.0.395",
27
+ "@teambit/logger": "0.0.482",
28
+ "@teambit/pkg": "0.0.570",
29
+ "@teambit/pubsub": "0.0.570",
30
+ "@teambit/ui": "0.0.570",
31
+ "@teambit/workspace": "0.0.570",
32
+ "@teambit/component-id": "0.0.385",
33
+ "@teambit/express": "0.0.486",
34
+ "@teambit/ui-foundation.ui.pages.static-error": "0.0.56",
35
+ "@teambit/compiler": "0.0.570",
36
+ "@teambit/preview.cli.preview-server-status": "0.0.475",
37
+ "@teambit/preview.cli.webpack-events-listener": "0.0.145",
38
+ "@teambit/isolator": "0.0.570",
39
+ "@teambit/bit-error": "0.0.381"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/fs-extra": "9.0.7",
@@ -49,10 +49,10 @@
49
49
  "@types/jest": "^26.0.0",
50
50
  "@types/react-dom": "^17.0.5",
51
51
  "@types/node": "12.20.4",
52
- "@teambit/preview.aspect-docs.preview": "0.0.112"
52
+ "@teambit/preview.aspect-docs.preview": "0.0.116"
53
53
  },
54
54
  "peerDependencies": {
55
- "@teambit/legacy": "1.0.179",
55
+ "@teambit/legacy": "1.0.181",
56
56
  "react-dom": "^16.8.0 || ^17.0.0",
57
57
  "react": "^16.8.0 || ^17.0.0"
58
58
  },
@@ -80,27 +80,12 @@
80
80
  "react": "-"
81
81
  },
82
82
  "peerDependencies": {
83
- "@teambit/legacy": "1.0.179",
83
+ "@teambit/legacy": "1.0.181",
84
84
  "react-dom": "^16.8.0 || ^17.0.0",
85
85
  "react": "^16.8.0 || ^17.0.0"
86
86
  }
87
87
  }
88
88
  },
89
- "files": [
90
- "dist",
91
- "!dist/tsconfig.tsbuildinfo",
92
- "**/*.md",
93
- "**/*.mdx",
94
- "**/*.js",
95
- "**/*.json",
96
- "**/*.sass",
97
- "**/*.scss",
98
- "**/*.less",
99
- "**/*.css",
100
- "**/*.css",
101
- "**/*.jpeg",
102
- "**/*.gif"
103
- ],
104
89
  "private": false,
105
90
  "engines": {
106
91
  "node": ">=12.22.0"
@@ -0,0 +1,346 @@
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);
@@ -0,0 +1,148 @@
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);
@@ -0,0 +1,121 @@
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
+ }
@@ -0,0 +1,34 @@
1
+ import { BuildContext } from '@teambit/builder';
2
+ import { Target, BundlerResult, BundlerContext } from '@teambit/bundler';
3
+ import { BundlingStrategy } from '../bundling-strategy';
4
+ import { PreviewDefinition } from '../preview-definition';
5
+ import { PreviewTask } from '../preview.task';
6
+
7
+ export class ComponentBundlingStrategy implements BundlingStrategy {
8
+ name = 'component';
9
+
10
+ computeTargets(context: BuildContext, previewDefs: PreviewDefinition[], previewTask: PreviewTask): Promise<Target[]> {
11
+ return Promise.all(
12
+ context.capsuleNetwork.graphCapsules.map(async (capsule) => {
13
+ return {
14
+ entries: await previewTask.computePaths(capsule, previewDefs, context),
15
+ components: [capsule.component],
16
+ outputPath: capsule.path,
17
+ };
18
+ })
19
+ );
20
+ }
21
+
22
+ async computeResults(context: BundlerContext, results: BundlerResult[], previewTask: PreviewTask) {
23
+ return {
24
+ componentsResults: results.map((result) => {
25
+ return {
26
+ errors: result.errors,
27
+ component: result.components[0],
28
+ warning: result.warnings,
29
+ };
30
+ }),
31
+ artifacts: [{ name: 'preview', globPatterns: [previewTask.getPreviewDirectory(context)] }],
32
+ };
33
+ }
34
+ }
@@ -0,0 +1,118 @@
1
+ import { join, resolve } from 'path';
2
+ import { existsSync, mkdirpSync } from 'fs-extra';
3
+ import { flatten } from 'lodash';
4
+ import { ComponentMap } from '@teambit/component';
5
+ import { Compiler } from '@teambit/compiler';
6
+ import { AbstractVinyl } from '@teambit/legacy/dist/consumer/component/sources';
7
+ import { Capsule } from '@teambit/isolator';
8
+ import { BuildContext, ComponentResult } from '@teambit/builder';
9
+ import { BundlerResult, BundlerContext } from '@teambit/bundler';
10
+ import { BundlingStrategy } from '../bundling-strategy';
11
+ import { PreviewDefinition } from '../preview-definition';
12
+ import { PreviewMain } from '../preview.main.runtime';
13
+
14
+ /**
15
+ * bundles all components in a given env into the same bundle.
16
+ */
17
+ export class EnvBundlingStrategy implements BundlingStrategy {
18
+ name = 'env';
19
+
20
+ constructor(private preview: PreviewMain) {}
21
+
22
+ async computeTargets(context: BuildContext, previewDefs: PreviewDefinition[]) {
23
+ const outputPath = this.getOutputPath(context);
24
+ if (!existsSync(outputPath)) mkdirpSync(outputPath);
25
+
26
+ return [
27
+ {
28
+ entries: await this.computePaths(outputPath, previewDefs, context),
29
+ components: context.components,
30
+ outputPath,
31
+ },
32
+ ];
33
+ }
34
+
35
+ async computeResults(context: BundlerContext, results: BundlerResult[]) {
36
+ const result = results[0];
37
+
38
+ const componentsResults: ComponentResult[] = result.components.map((component) => {
39
+ return {
40
+ component,
41
+ errors: result.errors.map((err) => (typeof err === 'string' ? err : err.message)),
42
+ warning: result.warnings,
43
+ startTime: result.startTime,
44
+ endTime: result.endTime,
45
+ };
46
+ });
47
+
48
+ const artifacts = this.getArtifactDef(context);
49
+
50
+ return {
51
+ componentsResults,
52
+ artifacts,
53
+ };
54
+ }
55
+
56
+ private getArtifactDef(context: BuildContext) {
57
+ // eslint-disable-next-line @typescript-eslint/prefer-as-const
58
+ const env: 'env' = 'env';
59
+ const rootDir = this.getDirName(context);
60
+
61
+ return [
62
+ {
63
+ name: 'preview',
64
+ globPatterns: ['public/**'],
65
+ rootDir,
66
+ context: env,
67
+ },
68
+ ];
69
+ }
70
+
71
+ getDirName(context: BuildContext) {
72
+ const envName = context.id.replace('/', '__');
73
+ return `${envName}-preview`;
74
+ }
75
+
76
+ private getOutputPath(context: BuildContext) {
77
+ return resolve(`${context.capsuleNetwork.capsulesRootDir}/${this.getDirName(context)}`);
78
+ }
79
+
80
+ private getPaths(context: BuildContext, files: AbstractVinyl[], capsule: Capsule) {
81
+ const compiler: Compiler = context.env.getCompiler();
82
+ return files.map((file) => join(capsule.path, compiler.getDistPathBySrcPath(file.relative)));
83
+ }
84
+
85
+ private async computePaths(outputPath: string, defs: PreviewDefinition[], context: BuildContext): Promise<string[]> {
86
+ const previewMain = await this.preview.writePreviewRuntime(context);
87
+ const moduleMapsPromise = defs.map(async (previewDef) => {
88
+ const moduleMap = await previewDef.getModuleMap(context.components);
89
+
90
+ const paths = ComponentMap.as(context.components, (component) => {
91
+ const capsule = context.capsuleNetwork.graphCapsules.getCapsule(component.id);
92
+ const maybeFiles = moduleMap.get(component);
93
+ if (!maybeFiles || !capsule) return [];
94
+ const [, files] = maybeFiles;
95
+ const compiledPaths = this.getPaths(context, files, capsule);
96
+ return compiledPaths;
97
+ });
98
+
99
+ const template = previewDef.renderTemplatePath ? await previewDef.renderTemplatePath(context) : 'undefined';
100
+
101
+ const link = this.preview.writeLink(
102
+ previewDef.prefix,
103
+ paths,
104
+ previewDef.renderTemplatePath ? await previewDef.renderTemplatePath(context) : undefined,
105
+ outputPath
106
+ );
107
+
108
+ const files = flatten(paths.toArray().map(([, file]) => file)).concat([link]);
109
+
110
+ if (template) return files.concat([template]);
111
+ return files;
112
+ });
113
+
114
+ const moduleMaps = await Promise.all(moduleMapsPromise);
115
+
116
+ return flatten(moduleMaps.concat([previewMain]));
117
+ }
118
+ }
@@ -0,0 +1,2 @@
1
+ export { EnvBundlingStrategy } from './env-strategy';
2
+ export { ComponentBundlingStrategy } from './component-strategy';
@@ -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,15 @@
1
+ /**
2
+ * A full index of the preview data
3
+ */
4
+ export type PreviewModule<T = any> = {
5
+ /** Dictionary mapping components to their module files. */
6
+ componentMap: Record<string, ModuleFile<T>[]>;
7
+
8
+ /** The 'main file' for this Preview type */
9
+ mainModule: {
10
+ default: (...args: any[]) => void;
11
+ };
12
+ };
13
+
14
+ /** single preview module, e.g. compositions file */
15
+ export type ModuleFile<T = any> = Record<string, T>;
@@ -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
+ }