@teambit/bundler 1.0.107 → 1.0.108

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,5 @@
1
+ import { ExecutionContext } from '@teambit/envs';
2
+
3
+ export type BrowserRuntime = {
4
+ entry: (context: ExecutionContext) => Promise<string[]>;
5
+ };
package/bundle.ts ADDED
@@ -0,0 +1,3 @@
1
+ export class Bundle {
2
+ constructor(readonly errors: Error) {}
3
+ }
@@ -0,0 +1,242 @@
1
+ import { Component } from '@teambit/component';
2
+ import { BuildContext } from '@teambit/builder';
3
+
4
+ export type LibraryOptions = {
5
+ /**
6
+ * Specify a name for the library
7
+ */
8
+ name: string;
9
+ // TODO: decide which exact types we want to support and their exact names
10
+ /**
11
+ * Configure how the library will be exposed
12
+ * could be values like: 'umd', 'umd2', 'amd', 'commonjs',
13
+ */
14
+ type?: string;
15
+ };
16
+
17
+ export type Entry = {
18
+ /**
19
+ * Specifies the name of each output file on disk
20
+ */
21
+ filename: string;
22
+ /**
23
+ * Module(s) that are loaded upon startup
24
+ */
25
+ import: string | string[];
26
+
27
+ /**
28
+ * Specify library options to bundle a library from current entry
29
+ */
30
+ library?: LibraryOptions;
31
+ };
32
+
33
+ export type EntryMap = {
34
+ [entryName: string]: Entry;
35
+ };
36
+
37
+ export type Target = {
38
+ /**
39
+ * entries of the target.
40
+ */
41
+ entries: string[] | EntryMap;
42
+
43
+ /**
44
+ * array of components included in the target.
45
+ */
46
+ components: Component[];
47
+
48
+ /**
49
+ * output path of the target
50
+ */
51
+ outputPath: string;
52
+
53
+ /**
54
+ * This option determines the name of each output bundle
55
+ */
56
+ filename?: string;
57
+
58
+ /**
59
+ * This option determines the name of non-initial chunk files
60
+ */
61
+ chunkFilename?: string;
62
+
63
+ /**
64
+ * Whether to run compression by the bundler
65
+ */
66
+ compress?: boolean;
67
+
68
+ /**
69
+ * List of peer dependencies
70
+ */
71
+ peers?: string[];
72
+
73
+ /**
74
+ * config for html generation
75
+ */
76
+ html?: HtmlConfig[];
77
+
78
+ /**
79
+ * module targets to expose.
80
+ */
81
+ modules?: ModuleTarget[];
82
+
83
+ /**
84
+ * Name for the runtime chunk
85
+ */
86
+ runtimeChunkName?: string;
87
+
88
+ /**
89
+ * Different configuration related to chunking
90
+ */
91
+ chunking?: Chunking;
92
+
93
+ /**
94
+ * A path for the host root dir
95
+ * Host root dir is usually the env root dir
96
+ * This can be used in different bundle options which run require.resolve
97
+ * for example when configuring webpack aliases or webpack expose loader on the peers deps
98
+ */
99
+ hostRootDir?: string;
100
+
101
+ /**
102
+ * Array of host dependencies, they are used later in case you use one of the following:
103
+ *
104
+ */
105
+ hostDependencies?: string[];
106
+
107
+ /**
108
+ * Make the hostDependencies externals. externals (from webpack docs):
109
+ * The externals configuration option provides a way of excluding dependencies from the output bundles.
110
+ * Instead, the created bundle relies on that dependency to be present in the consumer's (any end-user application) environment.
111
+ */
112
+ externalizeHostDependencies?: boolean;
113
+
114
+ /**
115
+ * Make aliases for the hostDependencies.
116
+ * the path of each one will be resolved by [hostRootDir, process.cwd(), __dirname]
117
+ * this will usually replace the instance of import one of the host dependencies by the instance of the env provided it
118
+ */
119
+ aliasHostDependencies?: boolean;
120
+ };
121
+
122
+ export type ModuleTarget = {
123
+ /**
124
+ * name of the module.
125
+ */
126
+ name: string;
127
+
128
+ /**
129
+ * module exposes.
130
+ */
131
+ exposes: {
132
+ [internalPath: string]: string;
133
+ };
134
+
135
+ shared: {
136
+ [key: string]: any;
137
+ };
138
+ };
139
+
140
+ export type HtmlConfig = {
141
+ /**
142
+ * The title to use for the generated HTML document
143
+ */
144
+ title: string;
145
+ /**
146
+ * The file to write the HTML to. Defaults to index.html
147
+ */
148
+ filename?: string;
149
+ /**
150
+ * Allows you to add only some chunks (e.g only the unit-test chunk)
151
+ */
152
+ chunks?: string[];
153
+ /**
154
+ * Load chunks according to their order in the `chunks` array
155
+ * @default auto
156
+ */
157
+ chunkOrder?: 'auto' | 'manual';
158
+ /**
159
+ * provide an inline template
160
+ */
161
+ templateContent: string;
162
+ /**
163
+ * Controls if and in what ways the output should be minified
164
+ */
165
+ minify?: boolean;
166
+
167
+ /**
168
+ * The favicon for the html page
169
+ */
170
+ favicon?: string;
171
+
172
+ // TODO: consider add chunksSortMode if there are more needs
173
+ };
174
+
175
+ export type Chunking = {
176
+ /**
177
+ * include all types of chunks (async / non-async) in splitting
178
+ */
179
+ splitChunks: boolean;
180
+ };
181
+
182
+ export type MetaData = {
183
+ /**
184
+ * Who initiate the bundling process
185
+ */
186
+ initiator?: string;
187
+ /**
188
+ * Env id (used usually to calculate the config)
189
+ */
190
+ envId?: string;
191
+
192
+ /**
193
+ * Whether the config is for an env template bundling
194
+ */
195
+ isEnvTemplate?: boolean;
196
+ };
197
+ export interface BundlerContext extends BuildContext {
198
+ /**
199
+ * targets for bundling.
200
+ */
201
+ targets: Target[];
202
+
203
+ /**
204
+ * determines whether it is a production build, default is `true`.
205
+ * in development, expect the bundler to favour debugging on the expanse of optimization.
206
+ */
207
+ development?: boolean;
208
+
209
+ /**
210
+ * public path output of the bundle.
211
+ */
212
+ publicPath?: string;
213
+
214
+ /**
215
+ * root path
216
+ */
217
+ rootPath?: string;
218
+
219
+ /**
220
+ * Whether to run compression by the bundler
221
+ */
222
+ compress?: boolean;
223
+
224
+ /**
225
+ * config for html generation for all targets
226
+ */
227
+ html?: HtmlConfig[];
228
+
229
+ /**
230
+ * modules for bundle to expose. used by module federation at webpack, or with different methods applied by various bundlers.
231
+ */
232
+ modules?: {
233
+ name: string;
234
+ fileName: string;
235
+ exposes: { [key: string]: string };
236
+ };
237
+
238
+ /**
239
+ * Additional info that can be used by the bundler for different stuff like logging info
240
+ */
241
+ metaData?: MetaData;
242
+ }
@@ -0,0 +1,5 @@
1
+ import { Aspect } from '@teambit/harmony';
2
+
3
+ export const BundlerAspect = Aspect.create({
4
+ id: 'teambit.compilation/bundler',
5
+ });
@@ -0,0 +1,159 @@
1
+ import PubsubAspect, { PubsubMain } from '@teambit/pubsub';
2
+ import { MainRuntime } from '@teambit/cli';
3
+ import { Component, ComponentAspect } from '@teambit/component';
4
+ import { DependencyResolverAspect, DependencyResolverMain } from '@teambit/dependency-resolver';
5
+ import { EnvsAspect, EnvsMain } from '@teambit/envs';
6
+ import { GraphqlAspect, GraphqlMain } from '@teambit/graphql';
7
+ import { Slot, SlotRegistry } from '@teambit/harmony';
8
+ import { BrowserRuntime } from './browser-runtime';
9
+ import { BundlerAspect } from './bundler.aspect';
10
+ import { ComponentServer } from './component-server';
11
+ import { BundlerContext } from './bundler-context';
12
+ import { devServerSchema } from './dev-server.graphql';
13
+ import { DevServerService } from './dev-server.service';
14
+ import { BundlerService } from './bundler.service';
15
+ import { DevServer } from './dev-server';
16
+
17
+ export type DevServerTransformer = (devServer: DevServer, { envId }: { envId: string }) => DevServer;
18
+
19
+ export type BrowserRuntimeSlot = SlotRegistry<BrowserRuntime>;
20
+ export type DevServerTransformerSlot = SlotRegistry<DevServerTransformer>;
21
+
22
+ export type BundlerConfig = {
23
+ dedicatedEnvDevServers: string[];
24
+ };
25
+
26
+ /**
27
+ * bundler extension.
28
+ */
29
+ export class BundlerMain {
30
+ constructor(
31
+ readonly config: BundlerConfig,
32
+ /**
33
+ * Pubsub extension.
34
+ */
35
+ private pubsub: PubsubMain,
36
+
37
+ /**
38
+ * environments extension.
39
+ */
40
+ private envs: EnvsMain,
41
+
42
+ /**
43
+ * dev server service.
44
+ */
45
+ private devService: DevServerService,
46
+
47
+ /**
48
+ * browser runtime slot.
49
+ */
50
+ private runtimeSlot: BrowserRuntimeSlot,
51
+
52
+ /**
53
+ * dev server transformer slot.
54
+ */
55
+ private devServerTransformerSlot: DevServerTransformerSlot
56
+ ) {}
57
+
58
+ /**
59
+ * load all given components in corresponding dev servers.
60
+ * @param components defaults to all components in the workspace.
61
+ */
62
+ async devServer(components: Component[]): Promise<ComponentServer[]> {
63
+ const envRuntime = await this.envs.createEnvironment(components);
64
+ // TODO: this must be refactored away from here. this logic should be in the Preview.
65
+ // @ts-ignore
66
+ const servers: ComponentServer[] = await envRuntime.runOnce<ComponentServer[]>(this.devService, {
67
+ dedicatedEnvDevServers: this.config.dedicatedEnvDevServers,
68
+ });
69
+ this._componentServers = servers;
70
+
71
+ this.indexByComponent();
72
+
73
+ return this._componentServers;
74
+ }
75
+
76
+ /**
77
+ * get a dev server instance containing a component.
78
+ * @param component
79
+ */
80
+ getComponentServer(component: Component): undefined | ComponentServer {
81
+ if (!this._componentServers) return undefined;
82
+ const envId = this.envs.getEnvId(component);
83
+ const server = this._componentServers.find(
84
+ (componentServer) =>
85
+ componentServer.context.relatedContexts.includes(envId) || componentServer.context.id === envId
86
+ );
87
+
88
+ return server;
89
+ }
90
+
91
+ /**
92
+ * compute entry files for bundling components in a given execution context.
93
+ */
94
+ async computeEntries(context: BundlerContext) {
95
+ const slotEntries = await Promise.all(
96
+ this.runtimeSlot.values().map(async (browserRuntime) => browserRuntime.entry(context))
97
+ );
98
+
99
+ const slotPaths = slotEntries.reduce((acc, current) => {
100
+ acc = acc.concat(current);
101
+ return acc;
102
+ });
103
+
104
+ return slotPaths;
105
+ }
106
+
107
+ /**
108
+ * register a new browser runtime environment.
109
+ * @param browserRuntime
110
+ */
111
+ registerTarget(browserRuntime: BrowserRuntime[]) {
112
+ browserRuntime.map((runtime) => {
113
+ return this.runtimeSlot.register(runtime);
114
+ });
115
+
116
+ return this;
117
+ }
118
+
119
+ /**
120
+ * register a new dev server transformer.
121
+ * @param devServerTransformer
122
+ */
123
+ registerDevServerTransformer(devServerTransformer: DevServerTransformer) {
124
+ this.devServerTransformerSlot.register(devServerTransformer);
125
+ return this;
126
+ }
127
+
128
+ /**
129
+ * component servers.
130
+ */
131
+ private _componentServers: null | ComponentServer[];
132
+
133
+ private indexByComponent() {}
134
+
135
+ static slots = [Slot.withType<BrowserRuntime>(), Slot.withType<DevServerTransformerSlot>()];
136
+
137
+ static runtime = MainRuntime;
138
+ static dependencies = [PubsubAspect, EnvsAspect, GraphqlAspect, DependencyResolverAspect, ComponentAspect];
139
+
140
+ static defaultConfig = {
141
+ dedicatedEnvDevServers: [],
142
+ };
143
+
144
+ static async provider(
145
+ [pubsub, envs, graphql, dependencyResolver]: [PubsubMain, EnvsMain, GraphqlMain, DependencyResolverMain],
146
+ config,
147
+ [runtimeSlot, devServerTransformerSlot]: [BrowserRuntimeSlot, DevServerTransformerSlot]
148
+ ) {
149
+ const devServerService = new DevServerService(pubsub, dependencyResolver, runtimeSlot, devServerTransformerSlot);
150
+ const bundler = new BundlerMain(config, pubsub, envs, devServerService, runtimeSlot, devServerTransformerSlot);
151
+ envs.registerService(devServerService, new BundlerService());
152
+
153
+ graphql.register(devServerSchema(bundler));
154
+
155
+ return bundler;
156
+ }
157
+ }
158
+
159
+ BundlerAspect.addRuntime(BundlerMain);
package/bundler.ts ADDED
@@ -0,0 +1,92 @@
1
+ import { Component } from '@teambit/component';
2
+
3
+ export interface DevServer {
4
+ start(): void;
5
+ }
6
+
7
+ export type Asset = {
8
+ /**
9
+ * name of the asset.
10
+ */
11
+ name: string;
12
+
13
+ /**
14
+ * size of the asset in bytes.
15
+ */
16
+ size: number;
17
+
18
+ /**
19
+ * size of the compressed asset in bytes.
20
+ */
21
+ compressedSize?: number;
22
+ };
23
+
24
+ export type ChunksAssetsMap = {
25
+ [assetName: string]: string[];
26
+ };
27
+
28
+ export type EntryAssets = {
29
+ assets: Asset[];
30
+ auxiliaryAssets: Asset[];
31
+ assetsSize: number;
32
+ compressedAssetsSize?: number;
33
+ auxiliaryAssetsSize: number;
34
+ compressedAuxiliaryAssetsSize: number;
35
+ };
36
+
37
+ export type EntriesAssetsMap = {
38
+ [entryId: string]: EntryAssets;
39
+ };
40
+
41
+ export type BundlerResult = {
42
+ /**
43
+ * list of generated assets.
44
+ */
45
+ assets: Asset[];
46
+
47
+ /**
48
+ * A map of assets names for each chunk
49
+ */
50
+ assetsByChunkName?: ChunksAssetsMap;
51
+
52
+ /**
53
+ * A map of assets for each entry point
54
+ */
55
+ entriesAssetsMap?: EntriesAssetsMap;
56
+
57
+ /**
58
+ * errors thrown during the bundling process.
59
+ */
60
+ errors: Error[];
61
+
62
+ /**
63
+ * warnings thrown during the bundling process.
64
+ */
65
+ warnings: string[];
66
+
67
+ /**
68
+ * components included in the bundling process.
69
+ */
70
+ components: Component[];
71
+
72
+ /**
73
+ * timestamp in milliseconds when the task started
74
+ */
75
+ startTime?: number;
76
+
77
+ /**
78
+ * timestamp in milliseconds when the task ended
79
+ */
80
+ endTime?: number;
81
+
82
+ /**
83
+ * out put path of the Bundler Result
84
+ */
85
+ outputPath?: string;
86
+ };
87
+
88
+ export interface Bundler {
89
+ run(): Promise<BundlerResult[]>;
90
+ }
91
+
92
+ export type BundlerMode = 'dev' | 'prod';
@@ -0,0 +1,94 @@
1
+ import { Component } from '@teambit/component';
2
+ import { ExecutionContext } from '@teambit/envs';
3
+ import { PubsubMain } from '@teambit/pubsub';
4
+
5
+ import { AddressInfo } from 'net';
6
+
7
+ import { DevServer } from './dev-server';
8
+ import { BindError } from './exceptions';
9
+ import { ComponentsServerStartedEvent } from './events';
10
+ import { BundlerAspect } from './bundler.aspect';
11
+ import { selectPort } from './select-port';
12
+
13
+ export class ComponentServer {
14
+ // why is this here
15
+ errors?: Error[];
16
+ constructor(
17
+ /**
18
+ * browser runtime slot
19
+ */
20
+ private pubsub: PubsubMain,
21
+
22
+ /**
23
+ * components contained in the existing component server.
24
+ */
25
+ readonly context: ExecutionContext,
26
+
27
+ /**
28
+ * port range of the component server.
29
+ */
30
+ readonly portRange: number[],
31
+
32
+ /**
33
+ * env dev server.
34
+ */
35
+ readonly devServer: DevServer
36
+ ) {}
37
+
38
+ hostname: string | undefined;
39
+
40
+ /**
41
+ * determine whether component server contains a component.
42
+ */
43
+ hasComponent(component: Component) {
44
+ return this.context.components.find((contextComponent) => contextComponent.equals(component));
45
+ }
46
+
47
+ get port() {
48
+ return this._port;
49
+ }
50
+
51
+ _port: number;
52
+ async listen() {
53
+ const port = await selectPort(this.portRange);
54
+ this._port = port;
55
+ const server = await this.devServer.listen(port);
56
+ const address = server.address();
57
+ const hostname = this.getHostname(address);
58
+ if (!address) throw new BindError();
59
+ this.hostname = hostname;
60
+
61
+ this.pubsub.pub(BundlerAspect.id, this.createComponentsServerStartedEvent(server, this.context, hostname, port));
62
+ }
63
+
64
+ private getHostname(address: string | AddressInfo | null) {
65
+ if (address === null) throw new BindError();
66
+ if (typeof address === 'string') return address;
67
+
68
+ let hostname = address.address;
69
+ if (hostname === '::') {
70
+ hostname = 'localhost';
71
+ }
72
+
73
+ return hostname;
74
+ }
75
+
76
+ private onChange() {}
77
+
78
+ private createComponentsServerStartedEvent: (
79
+ DevServer,
80
+ ExecutionContext,
81
+ string,
82
+ number
83
+ ) => ComponentsServerStartedEvent = (componentsServer, context, hostname, port) => {
84
+ return new ComponentsServerStartedEvent(Date.now(), componentsServer, context, hostname, port);
85
+ };
86
+
87
+ /**
88
+ * get the url of the component server.
89
+ */
90
+ get url() {
91
+ // tailing `/` is required!
92
+ return `/preview/${this.context.envRuntime.id}/`;
93
+ }
94
+ }
package/dedup-envs.ts ADDED
@@ -0,0 +1,84 @@
1
+ import { DependencyResolverMain } from '@teambit/dependency-resolver';
2
+ import type { ExecutionContext } from '@teambit/envs';
3
+
4
+ type GroupIdContextMap = Record<string, ExecutionContext[]>;
5
+
6
+ /**
7
+ * de-duping dev servers by the amount of type the dev server configuration was overridden by envs.
8
+ * This will split the dev server to groups of dev server that share the same webpack config, and same peer dependencies
9
+ * @param contexts
10
+ * @param dependencyResolver
11
+ * @param dedicatedEnvDevServers
12
+ */
13
+ export async function dedupEnvs(
14
+ contexts: ExecutionContext[],
15
+ dependencyResolver: DependencyResolverMain,
16
+ dedicatedEnvDevServers?: string[]
17
+ ) {
18
+ const idsGroups = groupByEnvId(contexts, dedicatedEnvDevServers);
19
+ const hasRootComponents = dependencyResolver.hasRootComponents();
20
+ // Do not split envs by peers if root components is enabled as it should be already handled by the package manager
21
+ // this will improve the performance of the dev server when root components is enabled
22
+ const finalGroups = hasRootComponents ? idsGroups : await splitByPeers(idsGroups, dependencyResolver);
23
+ return finalGroups;
24
+ }
25
+
26
+ function groupByEnvId(contexts: ExecutionContext[], dedicatedEnvDevServers?: string[]) {
27
+ const groupedEnvs: GroupIdContextMap = {};
28
+
29
+ contexts.forEach((context) => {
30
+ const envId = getEnvId(context, dedicatedEnvDevServers);
31
+ if (!envId) return;
32
+ if (!(envId in groupedEnvs)) groupedEnvs[envId] = [];
33
+
34
+ groupedEnvs[envId].push(context);
35
+ });
36
+
37
+ return groupedEnvs;
38
+ }
39
+
40
+ async function splitByPeers(idsGroups: GroupIdContextMap, dependencyResolver: DependencyResolverMain) {
41
+ const newGroupedEnvs: GroupIdContextMap = {};
42
+ const promises = Object.values(idsGroups).map(async (contexts) => {
43
+ const peersGroups = await groupByPeersHash(contexts, dependencyResolver);
44
+ Object.assign(newGroupedEnvs, peersGroups);
45
+ });
46
+ await Promise.all(promises);
47
+ return newGroupedEnvs;
48
+ }
49
+
50
+ function getEnvId(context: ExecutionContext, dedicatedServers?: string[]): string | undefined {
51
+ const id = context.id.split('@')[0];
52
+
53
+ if (dedicatedServers?.includes(id)) {
54
+ return context.id;
55
+ }
56
+
57
+ return context.env?.getDevEnvId(context);
58
+ }
59
+
60
+ async function groupByPeersHash(contexts: ExecutionContext[], dependencyResolver: DependencyResolverMain) {
61
+ const peerGroups: GroupIdContextMap = {};
62
+
63
+ await Promise.all(
64
+ contexts.map(async (context) => {
65
+ const env = context.env;
66
+ const policy = await dependencyResolver.getComponentEnvPolicyFromEnv(env, { envId: context.id });
67
+ const peersHash = policy.byLifecycleType('peer').hashNameVersion();
68
+ if (!peerGroups[peersHash]) {
69
+ peerGroups[peersHash] = [];
70
+ }
71
+ peerGroups[peersHash].push(context);
72
+ })
73
+ );
74
+ return indexPeerGroupsById(peerGroups);
75
+ }
76
+
77
+ function indexPeerGroupsById(peerGroups: GroupIdContextMap) {
78
+ const result: GroupIdContextMap = Object.values(peerGroups).reduce((acc, contexts) => {
79
+ const firstId = contexts[0].id;
80
+ acc[firstId] = contexts;
81
+ return acc;
82
+ }, {});
83
+ return result;
84
+ }