UNPKG

6.15 kBJavaScriptView Raw
1// @flow strict-local
2
3import type {Namer} from '@parcel/types';
4import type {Bundle as InternalBundle, ParcelOptions} from './types';
5import type ParcelConfig from './ParcelConfig';
6import type WorkerFarm from '@parcel/workers';
7import type AssetGraphBuilder from './AssetGraphBuilder';
8import type {AbortSignal} from 'abortcontroller-polyfill/dist/cjs-ponyfill';
9
10import assert from 'assert';
11import path from 'path';
12import nullthrows from 'nullthrows';
13import {PluginLogger} from '@parcel/logger';
14import ThrowableDiagnostic, {errorToDiagnostic} from '@parcel/diagnostic';
15import AssetGraph from './AssetGraph';
16import BundleGraph from './public/BundleGraph';
17import InternalBundleGraph, {removeAssetGroups} from './BundleGraph';
18import MutableBundleGraph from './public/MutableBundleGraph';
19import {Bundle} from './public/Bundle';
20import {report} from './ReporterRunner';
21import dumpGraphToGraphViz from './dumpGraphToGraphViz';
22import {normalizeSeparators, unique, md5FromObject} from '@parcel/utils';
23import PluginOptions from './public/PluginOptions';
24import applyRuntimes from './applyRuntimes';
25import {PARCEL_VERSION} from './constants';
26import {assertSignalNotAborted} from './utils';
27
28type Opts = {|
29 options: ParcelOptions,
30 config: ParcelConfig,
31 runtimesBuilder: AssetGraphBuilder,
32 workerFarm: WorkerFarm,
33|};
34
35export default class BundlerRunner {
36 options: ParcelOptions;
37 config: ParcelConfig;
38 pluginOptions: PluginOptions;
39 farm: WorkerFarm;
40 runtimesBuilder: AssetGraphBuilder;
41 isBundling: boolean = false;
42
43 constructor(opts: Opts) {
44 this.options = opts.options;
45 this.config = opts.config;
46 this.pluginOptions = new PluginOptions(this.options);
47 this.runtimesBuilder = opts.runtimesBuilder;
48 this.farm = opts.workerFarm;
49 }
50
51 async bundle(
52 graph: AssetGraph,
53 {signal}: {|signal: ?AbortSignal|},
54 ): Promise<InternalBundleGraph> {
55 report({
56 type: 'buildProgress',
57 phase: 'bundling',
58 });
59
60 let cacheKey;
61 if (!this.options.disableCache) {
62 cacheKey = await this.getCacheKey(graph);
63 let cachedBundleGraph = await this.options.cache.get(cacheKey);
64 assertSignalNotAborted(signal);
65
66 if (cachedBundleGraph) {
67 return cachedBundleGraph;
68 }
69 }
70
71 let bundleGraph = removeAssetGroups(graph);
72 // $FlowFixMe
73 let internalBundleGraph = new InternalBundleGraph({graph: bundleGraph});
74 await dumpGraphToGraphViz(bundleGraph, 'before_bundle');
75 let mutableBundleGraph = new MutableBundleGraph(
76 internalBundleGraph,
77 this.options,
78 );
79
80 let bundler = await this.config.getBundler();
81
82 try {
83 await bundler.bundle({
84 bundleGraph: mutableBundleGraph,
85 options: this.pluginOptions,
86 logger: new PluginLogger({origin: this.config.bundler}),
87 });
88 } catch (e) {
89 throw new ThrowableDiagnostic({
90 diagnostic: errorToDiagnostic(e, this.config.bundler),
91 });
92 }
93 assertSignalNotAborted(signal);
94
95 await dumpGraphToGraphViz(bundleGraph, 'after_bundle');
96 try {
97 await bundler.optimize({
98 bundleGraph: mutableBundleGraph,
99 options: this.pluginOptions,
100 logger: new PluginLogger({origin: this.config.bundler}),
101 });
102 } catch (e) {
103 throw new ThrowableDiagnostic({
104 diagnostic: errorToDiagnostic(e, this.config.bundler),
105 });
106 }
107 assertSignalNotAborted(signal);
108
109 await dumpGraphToGraphViz(bundleGraph, 'after_optimize');
110 await this.nameBundles(internalBundleGraph);
111
112 await applyRuntimes({
113 bundleGraph: internalBundleGraph,
114 runtimesBuilder: this.runtimesBuilder,
115 config: this.config,
116 options: this.options,
117 pluginOptions: this.pluginOptions,
118 });
119 assertSignalNotAborted(signal);
120 await dumpGraphToGraphViz(bundleGraph, 'after_runtimes');
121
122 if (cacheKey != null) {
123 await this.options.cache.set(cacheKey, internalBundleGraph);
124 }
125 assertSignalNotAborted(signal);
126
127 return internalBundleGraph;
128 }
129
130 async getCacheKey(assetGraph: AssetGraph) {
131 let bundler = this.config.bundler;
132 let {pkg} = await this.options.packageManager.resolve(
133 `${bundler}/package.json`,
134 `${this.config.filePath}/index`, // TODO: is this right?
135 );
136
137 let version = nullthrows(pkg).version;
138 return md5FromObject({
139 parcelVersion: PARCEL_VERSION,
140 bundler,
141 version,
142 hash: assetGraph.getHash(),
143 });
144 }
145
146 async nameBundles(bundleGraph: InternalBundleGraph): Promise<void> {
147 let namers = await this.config.getNamers();
148 let bundles = bundleGraph.getBundles();
149
150 await Promise.all(
151 bundles.map(bundle => this.nameBundle(namers, bundle, bundleGraph)),
152 );
153
154 let bundlePaths = bundles.map(b => b.filePath);
155 assert.deepEqual(
156 bundlePaths,
157 unique(bundlePaths),
158 'Bundles must have unique filePaths',
159 );
160 }
161
162 async nameBundle(
163 namers: Array<{|name: string, plugin: Namer|}>,
164 internalBundle: InternalBundle,
165 internalBundleGraph: InternalBundleGraph,
166 ): Promise<void> {
167 let bundle = new Bundle(internalBundle, internalBundleGraph, this.options);
168 let bundleGraph = new BundleGraph(internalBundleGraph, this.options);
169
170 for (let namer of namers) {
171 try {
172 let name = await namer.plugin.name({
173 bundle,
174 bundleGraph,
175 options: this.pluginOptions,
176 logger: new PluginLogger({origin: namer.name}),
177 });
178
179 if (name != null) {
180 if (path.extname(name).slice(1) !== bundle.type) {
181 throw new Error(
182 `Destination name ${name} extension does not match bundle type "${bundle.type}"`,
183 );
184 }
185
186 let target = nullthrows(internalBundle.target);
187 internalBundle.filePath = path.join(
188 target.distDir,
189 normalizeSeparators(name),
190 );
191 internalBundle.name = name;
192 return;
193 }
194 } catch (e) {
195 throw new ThrowableDiagnostic({
196 diagnostic: errorToDiagnostic(e, namer.name),
197 });
198 }
199 }
200
201 throw new Error('Unable to name bundle');
202 }
203}