UNPKG

24.9 kBJavaScriptView Raw
1const fs = require('@parcel/fs');
2const Resolver = require('./Resolver');
3const Parser = require('./Parser');
4const WorkerFarm = require('@parcel/workers');
5const Path = require('path');
6const Bundle = require('./Bundle');
7const Watcher = require('@parcel/watcher');
8const FSCache = require('./FSCache');
9const HMRServer = require('./HMRServer');
10const Server = require('./Server');
11const {EventEmitter} = require('events');
12const logger = require('@parcel/logger');
13const PackagerRegistry = require('./packagers');
14const localRequire = require('./utils/localRequire');
15const config = require('./utils/config');
16const loadEnv = require('./utils/env');
17const PromiseQueue = require('./utils/PromiseQueue');
18const installPackage = require('./utils/installPackage');
19const bundleReport = require('./utils/bundleReport');
20const prettifyTime = require('./utils/prettifyTime');
21const getRootDir = require('./utils/getRootDir');
22const {glob, isGlob} = require('./utils/glob');
23
24/**
25 * The Bundler is the main entry point. It resolves and loads assets,
26 * creates the bundle tree, and manages the worker farm, cache, and file watcher.
27 */
28class Bundler extends EventEmitter {
29 constructor(entryFiles, options = {}) {
30 super();
31
32 entryFiles = this.normalizeEntries(entryFiles);
33 this.watchedGlobs = entryFiles.filter(entry => isGlob(entry));
34 this.entryFiles = this.findEntryFiles(entryFiles);
35 this.options = this.normalizeOptions(options);
36
37 this.resolver = new Resolver(this.options);
38 this.parser = new Parser(this.options);
39 this.packagers = new PackagerRegistry(this.options);
40 this.cache = this.options.cache ? new FSCache(this.options) : null;
41 this.delegate = options.delegate || {};
42 this.bundleLoaders = {};
43
44 this.addBundleLoader('wasm', {
45 browser: require.resolve('./builtins/loaders/browser/wasm-loader'),
46 node: require.resolve('./builtins/loaders/node/wasm-loader')
47 });
48 this.addBundleLoader('css', {
49 browser: require.resolve('./builtins/loaders/browser/css-loader'),
50 node: require.resolve('./builtins/loaders/node/css-loader')
51 });
52 this.addBundleLoader('js', {
53 browser: require.resolve('./builtins/loaders/browser/js-loader'),
54 node: require.resolve('./builtins/loaders/node/js-loader')
55 });
56 this.addBundleLoader('html', {
57 browser: require.resolve('./builtins/loaders/browser/html-loader'),
58 node: require.resolve('./builtins/loaders/node/html-loader')
59 });
60
61 this.pending = false;
62 this.loadedAssets = new Map();
63 this.watchedAssets = new Map();
64
65 this.farm = null;
66 this.watcher = null;
67 this.hmr = null;
68 this.bundleHashes = null;
69 this.error = null;
70 this.buildQueue = new PromiseQueue(this.processAsset.bind(this));
71 this.rebuildTimeout = null;
72
73 logger.setOptions(this.options);
74 }
75
76 normalizeEntries(entryFiles) {
77 // Support passing a single file
78 if (entryFiles && !Array.isArray(entryFiles)) {
79 entryFiles = [entryFiles];
80 }
81
82 // If no entry files provided, resolve the entry point from the current directory.
83 if (!entryFiles || entryFiles.length === 0) {
84 entryFiles = [process.cwd()];
85 }
86
87 return entryFiles;
88 }
89
90 findEntryFiles(entryFiles) {
91 // Match files as globs
92 return entryFiles
93 .reduce((p, m) => p.concat(glob.sync(m)), [])
94 .map(f => Path.resolve(f));
95 }
96
97 normalizeOptions(options) {
98 const isProduction =
99 options.production || process.env.NODE_ENV === 'production';
100 const publicURL = options.publicUrl || options.publicURL || '/';
101 const watch =
102 typeof options.watch === 'boolean' ? options.watch : !isProduction;
103 const target = options.target || 'browser';
104 const hmr =
105 target === 'node'
106 ? false
107 : typeof options.hmr === 'boolean'
108 ? options.hmr
109 : watch;
110 const scopeHoist =
111 options.scopeHoist !== undefined ? options.scopeHoist : false;
112 return {
113 production: isProduction,
114 outDir: Path.resolve(options.outDir || 'dist'),
115 outFile: options.outFile || '',
116 publicURL: publicURL,
117 watch: watch,
118 cache: typeof options.cache === 'boolean' ? options.cache : true,
119 cacheDir: Path.resolve(options.cacheDir || '.cache'),
120 killWorkers:
121 typeof options.killWorkers === 'boolean' ? options.killWorkers : true,
122 minify:
123 typeof options.minify === 'boolean' ? options.minify : isProduction,
124 target: target,
125 bundleNodeModules:
126 typeof options.bundleNodeModules === 'boolean'
127 ? options.bundleNodeModules
128 : target === 'browser',
129 hmr: hmr,
130 https: options.https || false,
131 logLevel: isNaN(options.logLevel) ? 3 : options.logLevel,
132 entryFiles: this.entryFiles,
133 hmrPort: options.hmrPort || 0,
134 rootDir: getRootDir(this.entryFiles),
135 sourceMaps:
136 (typeof options.sourceMaps === 'boolean' ? options.sourceMaps : true) &&
137 !scopeHoist,
138 hmrHostname:
139 options.hmrHostname ||
140 options.host ||
141 (options.target === 'electron' ? 'localhost' : ''),
142 detailedReport: options.detailedReport || false,
143 global: options.global,
144 autoinstall:
145 typeof options.autoInstall === 'boolean'
146 ? options.autoInstall
147 : process.env.PARCEL_AUTOINSTALL === 'false'
148 ? false
149 : !isProduction,
150 scopeHoist: scopeHoist,
151 contentHash:
152 typeof options.contentHash === 'boolean'
153 ? options.contentHash
154 : isProduction,
155 throwErrors:
156 typeof options.throwErrors === 'boolean' ? options.throwErrors : true
157 };
158 }
159
160 addAssetType(extension, path) {
161 if (typeof path !== 'string') {
162 throw new Error('Asset type should be a module path.');
163 }
164
165 if (this.farm) {
166 throw new Error('Asset types must be added before bundling.');
167 }
168
169 this.parser.registerExtension(extension, path);
170 }
171
172 addPackager(type, packager) {
173 if (this.farm) {
174 throw new Error('Packagers must be added before bundling.');
175 }
176
177 this.packagers.add(type, packager);
178 }
179
180 addBundleLoader(type, paths) {
181 if (typeof paths === 'string') {
182 paths = {node: paths, browser: paths};
183 } else if (typeof paths !== 'object') {
184 throw new Error('Bundle loaders should be an object.');
185 }
186
187 for (const target in paths) {
188 if (target !== 'node' && target !== 'browser') {
189 throw new Error(`Unknown bundle loader target "${target}".`);
190 }
191
192 if (typeof paths[target] !== 'string') {
193 throw new Error('Bundle loader should be a string.');
194 }
195 }
196
197 if (this.farm) {
198 throw new Error('Bundle loaders must be added before bundling.');
199 }
200
201 this.bundleLoaders[type] = paths;
202 }
203
204 async loadPlugins() {
205 let relative = Path.join(this.options.rootDir, 'index');
206 let pkg = await config.load(relative, ['package.json']);
207 if (!pkg) {
208 return;
209 }
210
211 let lastDep;
212 try {
213 let deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
214 for (let dep in deps) {
215 lastDep = dep;
216 const pattern = /^(@.*\/)?parcel-plugin-.+/;
217 if (pattern.test(dep)) {
218 let plugin = await localRequire(dep, relative);
219 await plugin(this);
220 }
221 }
222 } catch (err) {
223 logger.warn(
224 `Plugin ${lastDep} failed to initialize: ${err.stack ||
225 err.message ||
226 err}`
227 );
228 }
229 }
230
231 async bundle() {
232 // If another bundle is already pending, wait for that one to finish and retry.
233 if (this.pending) {
234 return new Promise((resolve, reject) => {
235 this.once('buildEnd', () => {
236 this.bundle().then(resolve, reject);
237 });
238 });
239 }
240
241 let isInitialBundle = !this.entryAssets;
242 let startTime = Date.now();
243 let initialised = !isInitialBundle;
244 this.pending = true;
245 this.error = null;
246
247 logger.clear();
248 logger.progress('Building...');
249
250 try {
251 // Start worker farm, watcher, etc. if needed
252 await this.start();
253
254 // Emit start event, after bundler is initialised
255 this.emit('buildStart', this.entryFiles);
256
257 // If this is the initial bundle, ensure the output directory exists, and resolve the main asset.
258 if (isInitialBundle) {
259 await fs.mkdirp(this.options.outDir);
260
261 this.entryAssets = new Set();
262 for (let entry of this.entryFiles) {
263 try {
264 let asset = await this.resolveAsset(entry);
265 this.buildQueue.add(asset);
266 this.entryAssets.add(asset);
267 } catch (err) {
268 throw new Error(
269 `Cannot resolve entry "${entry}" from "${this.options.rootDir}"`
270 );
271 }
272 }
273
274 if (this.entryAssets.size === 0) {
275 throw new Error('No entries found.');
276 }
277
278 initialised = true;
279 }
280
281 // Build the queued assets.
282 let loadedAssets = await this.buildQueue.run();
283
284 // The changed assets are any that don't have a parent bundle yet
285 // plus the ones that were in the build queue.
286 let changedAssets = [...this.findOrphanAssets(), ...loadedAssets];
287
288 // Invalidate bundles
289 for (let asset of this.loadedAssets.values()) {
290 asset.invalidateBundle();
291 }
292
293 logger.progress(`Producing bundles...`);
294
295 // Create a root bundle to hold all of the entry assets, and add them to the tree.
296 this.mainBundle = new Bundle();
297 for (let asset of this.entryAssets) {
298 this.createBundleTree(asset, this.mainBundle);
299 }
300
301 // If there is only one child bundle, replace the root with that bundle.
302 if (this.mainBundle.childBundles.size === 1) {
303 this.mainBundle = Array.from(this.mainBundle.childBundles)[0];
304 }
305
306 // Generate the final bundle names, and replace references in the built assets.
307 let numBundles = this.bundleNameMap ? this.bundleNameMap.size : 0;
308 this.bundleNameMap = this.mainBundle.getBundleNameMap(
309 this.options.contentHash
310 );
311
312 for (let asset of changedAssets) {
313 asset.replaceBundleNames(this.bundleNameMap);
314 }
315
316 // Emit an HMR update if this is not the initial bundle.
317 let bundlesChanged = numBundles !== this.bundleNameMap.size;
318 if (this.hmr && !isInitialBundle) {
319 this.hmr.emitUpdate(changedAssets, bundlesChanged);
320 }
321
322 logger.progress(`Packaging...`);
323
324 // Package everything up
325 this.bundleHashes = await this.mainBundle.package(
326 this,
327 bundlesChanged ? null : this.bundleHashes
328 );
329
330 // Unload any orphaned assets
331 this.unloadOrphanedAssets();
332
333 let buildTime = Date.now() - startTime;
334 let time = prettifyTime(buildTime);
335 logger.success(`Built in ${time}.`);
336 if (!this.watcher) {
337 bundleReport(this.mainBundle, this.options.detailedReport);
338 }
339
340 this.emit('bundled', this.mainBundle);
341 return this.mainBundle;
342 } catch (err) {
343 this.error = err;
344
345 logger.error(err);
346
347 this.emit('buildError', err);
348
349 if (this.hmr) {
350 this.hmr.emitError(err);
351 }
352
353 if (this.options.throwErrors && !this.hmr) {
354 throw err;
355 } else if (!this.options.watch || !initialised) {
356 await this.stop();
357 process.exit(1);
358 }
359 } finally {
360 this.pending = false;
361 this.emit('buildEnd');
362
363 // If not in watch mode, stop the worker farm so we don't keep the process running.
364 if (!this.watcher && this.options.killWorkers) {
365 await this.stop();
366 }
367 }
368 }
369
370 async start() {
371 if (this.farm) {
372 return;
373 }
374
375 await this.loadPlugins();
376
377 if (!this.options.env) {
378 await loadEnv(Path.join(this.options.rootDir, 'index'));
379 this.options.env = process.env;
380 }
381
382 this.options.extensions = Object.assign({}, this.parser.extensions);
383 this.options.bundleLoaders = this.bundleLoaders;
384
385 if (this.options.watch) {
386 this.watcher = new Watcher();
387 // Wait for ready event for reliable testing on watcher
388 if (process.env.NODE_ENV === 'test' && !this.watcher.ready) {
389 await new Promise(resolve => this.watcher.once('ready', resolve));
390 }
391 this.watchedGlobs.forEach(glob => {
392 this.watcher.add(glob);
393 });
394 this.watcher.on('add', this.onAdd.bind(this));
395 this.watcher.on('change', this.onChange.bind(this));
396 this.watcher.on('unlink', this.onUnlink.bind(this));
397 }
398
399 if (this.options.hmr) {
400 this.hmr = new HMRServer();
401 this.options.hmrPort = await this.hmr.start(this.options);
402 }
403
404 this.farm = await WorkerFarm.getShared(this.options, {
405 workerPath: require.resolve('./worker.js')
406 });
407 }
408
409 async stop() {
410 if (this.watcher) {
411 await this.watcher.stop();
412 }
413
414 if (this.hmr) {
415 this.hmr.stop();
416 }
417
418 // Watcher and hmr can cause workerfarm calls
419 // keep this as last to prevent unwanted errors
420 if (this.farm) {
421 await this.farm.end();
422 }
423 }
424
425 async getAsset(name, parent) {
426 let asset = await this.resolveAsset(name, parent);
427 this.buildQueue.add(asset);
428 await this.buildQueue.run();
429 return asset;
430 }
431
432 async resolveAsset(name, parent) {
433 let {path} = await this.resolver.resolve(name, parent);
434 return this.getLoadedAsset(path);
435 }
436
437 getLoadedAsset(path) {
438 if (this.loadedAssets.has(path)) {
439 return this.loadedAssets.get(path);
440 }
441
442 let asset = this.parser.getAsset(path, this.options);
443 this.loadedAssets.set(path, asset);
444
445 this.watch(path, asset);
446 return asset;
447 }
448
449 async watch(path, asset) {
450 if (!this.watcher) {
451 return;
452 }
453
454 path = await fs.realpath(path);
455
456 if (!this.watchedAssets.has(path)) {
457 this.watcher.watch(path);
458 this.watchedAssets.set(path, new Set());
459 }
460 this.watchedAssets.get(path).add(asset);
461 }
462
463 async unwatch(path, asset) {
464 path = await fs.realpath(path);
465 if (!this.watchedAssets.has(path)) {
466 return;
467 }
468
469 let watched = this.watchedAssets.get(path);
470 watched.delete(asset);
471
472 if (watched.size === 0) {
473 this.watchedAssets.delete(path);
474 this.watcher.unwatch(path);
475 }
476 }
477
478 async resolveDep(asset, dep, install = true) {
479 try {
480 if (dep.resolved) {
481 return this.getLoadedAsset(dep.resolved);
482 }
483
484 return await this.resolveAsset(dep.name, asset.name);
485 } catch (err) {
486 // If the dep is optional, return before we throw
487 if (dep.optional) {
488 return;
489 }
490
491 if (err.code === 'MODULE_NOT_FOUND') {
492 let isLocalFile = /^[/~.]/.test(dep.name);
493 let fromNodeModules = asset.name.includes(
494 `${Path.sep}node_modules${Path.sep}`
495 );
496
497 if (
498 !isLocalFile &&
499 !fromNodeModules &&
500 this.options.autoinstall &&
501 install
502 ) {
503 return this.installDep(asset, dep);
504 }
505
506 err.message = `Cannot resolve dependency '${dep.name}'`;
507 if (isLocalFile) {
508 const absPath = Path.resolve(Path.dirname(asset.name), dep.name);
509 err.message += ` at '${absPath}'`;
510 }
511
512 await this.throwDepError(asset, dep, err);
513 }
514
515 throw err;
516 }
517 }
518
519 async installDep(asset, dep) {
520 // Check if module exists, prevents useless installs
521 let resolved = await this.resolver.resolveModule(dep.name, asset.name);
522
523 // If the module resolved (i.e. wasn't a local file), but the module directory wasn't found, install it.
524 if (resolved.moduleName && !resolved.moduleDir) {
525 try {
526 await installPackage(resolved.moduleName, asset.name, {
527 saveDev: false
528 });
529 } catch (err) {
530 await this.throwDepError(asset, dep, err);
531 }
532 }
533
534 return this.resolveDep(asset, dep, false);
535 }
536
537 async throwDepError(asset, dep, err) {
538 // Generate a code frame where the dependency was used
539 if (dep.loc) {
540 await asset.loadIfNeeded();
541 err.loc = dep.loc;
542 err = asset.generateErrorMessage(err);
543 }
544
545 err.fileName = asset.name;
546 throw err;
547 }
548
549 async processAsset(asset, isRebuild) {
550 if (isRebuild) {
551 asset.invalidate();
552 if (this.cache) {
553 this.cache.invalidate(asset.name);
554 }
555 }
556
557 await this.loadAsset(asset);
558 }
559
560 async loadAsset(asset) {
561 if (asset.processed) {
562 return;
563 }
564
565 if (!this.error) {
566 logger.progress(`Building ${asset.basename}...`);
567 }
568
569 // Mark the asset processed so we don't load it twice
570 asset.processed = true;
571
572 // First try the cache, otherwise load and compile in the background
573 asset.startTime = Date.now();
574 let processed = this.cache && (await this.cache.read(asset.name));
575 let cacheMiss = false;
576 if (!processed || asset.shouldInvalidate(processed.cacheData)) {
577 processed = await this.farm.run(asset.name);
578 cacheMiss = true;
579 }
580
581 asset.endTime = Date.now();
582 asset.buildTime = asset.endTime - asset.startTime;
583 asset.id = processed.id;
584 asset.generated = processed.generated;
585 asset.sourceMaps = processed.sourceMaps;
586 asset.hash = processed.hash;
587 asset.cacheData = processed.cacheData;
588
589 // Call the delegate to get implicit dependencies
590 let dependencies = processed.dependencies;
591 if (this.delegate.getImplicitDependencies) {
592 let implicitDeps = await this.delegate.getImplicitDependencies(asset);
593 if (implicitDeps) {
594 dependencies = dependencies.concat(implicitDeps);
595 }
596 }
597
598 // Resolve and load asset dependencies
599 let assetDeps = await Promise.all(
600 dependencies.map(async dep => {
601 if (dep.includedInParent) {
602 // This dependency is already included in the parent's generated output,
603 // so no need to load it. We map the name back to the parent asset so
604 // that changing it triggers a recompile of the parent.
605 this.watch(dep.name, asset);
606 } else {
607 dep.parent = asset.name;
608 let assetDep = await this.resolveDep(asset, dep);
609 if (assetDep) {
610 await this.loadAsset(assetDep);
611 }
612
613 return assetDep;
614 }
615 })
616 );
617
618 // If there was a processing error, re-throw now that we've set up
619 // depdenency watchers. This keeps reloading working if there is an
620 // error in a dependency not directly handled by Parcel.
621 if (processed.error !== null) {
622 throw processed.error;
623 }
624
625 // Store resolved assets in their original order
626 dependencies.forEach((dep, i) => {
627 asset.dependencies.set(dep.name, dep);
628 let assetDep = assetDeps[i];
629 if (assetDep) {
630 asset.depAssets.set(dep, assetDep);
631 dep.resolved = assetDep.name;
632 }
633 });
634
635 logger.verbose(`Built ${asset.relativeName}...`);
636
637 if (this.cache && cacheMiss) {
638 this.cache.write(asset.name, processed);
639 }
640 }
641
642 createBundleTree(asset, bundle, dep, parentBundles = new Set()) {
643 if (dep) {
644 asset.parentDeps.add(dep);
645 }
646
647 if (asset.parentBundle && !bundle.isolated) {
648 // If the asset is already in a bundle, it is shared. Move it to the lowest common ancestor.
649 if (asset.parentBundle !== bundle) {
650 let commonBundle = bundle.findCommonAncestor(asset.parentBundle);
651
652 // If the common bundle's type matches the asset's, move the asset to the common bundle.
653 // Otherwise, proceed with adding the asset to the new bundle below.
654 if (asset.parentBundle.type === commonBundle.type) {
655 this.moveAssetToBundle(asset, commonBundle);
656 return;
657 }
658 } else {
659 return;
660 }
661
662 // Detect circular bundles
663 if (parentBundles.has(asset.parentBundle)) {
664 return;
665 }
666 }
667
668 // Skip this asset if it's already in the bundle.
669 // Happens when circular dependencies are placed in an isolated bundle (e.g. a worker).
670 if (bundle.isolated && bundle.assets.has(asset)) {
671 return;
672 }
673
674 let isEntryAsset =
675 asset.parentBundle && asset.parentBundle.entryAsset === asset;
676
677 // If the asset generated a representation for the parent bundle type, and this
678 // is not an async import, add it to the current bundle
679 if (bundle.type && asset.generated[bundle.type] != null && !dep.dynamic) {
680 bundle.addAsset(asset);
681 }
682
683 if ((dep && dep.dynamic) || !bundle.type) {
684 // If the asset is already the entry asset of a bundle, don't create a duplicate.
685 if (isEntryAsset) {
686 return;
687 }
688
689 // Create a new bundle for dynamic imports
690 bundle = bundle.createChildBundle(asset, dep);
691 } else if (
692 asset.type &&
693 !this.packagers.get(asset.type).shouldAddAsset(bundle, asset)
694 ) {
695 // If the asset is already the entry asset of a bundle, don't create a duplicate.
696 if (isEntryAsset) {
697 return;
698 }
699
700 // No packager is available for this asset type, or the packager doesn't support
701 // combining this asset into the bundle. Create a new bundle with only this asset.
702 bundle = bundle.createSiblingBundle(asset, dep);
703 } else {
704 // Add the asset to the common bundle of the asset's type
705 bundle.getSiblingBundle(asset.type).addAsset(asset);
706 }
707
708 // Add the asset to sibling bundles for each generated type
709 if (asset.type && asset.generated[asset.type]) {
710 for (let t in asset.generated) {
711 if (asset.generated[t]) {
712 bundle.getSiblingBundle(t).addAsset(asset);
713 }
714 }
715 }
716
717 asset.parentBundle = bundle;
718 parentBundles.add(bundle);
719
720 for (let [dep, assetDep] of asset.depAssets) {
721 this.createBundleTree(assetDep, bundle, dep, parentBundles);
722 }
723
724 parentBundles.delete(bundle);
725 return bundle;
726 }
727
728 moveAssetToBundle(asset, commonBundle) {
729 // Never move the entry asset of a bundle, as it was explicitly requested to be placed in a separate bundle.
730 if (
731 asset.parentBundle.entryAsset === asset ||
732 asset.parentBundle === commonBundle
733 ) {
734 return;
735 }
736
737 for (let bundle of Array.from(asset.bundles)) {
738 if (!bundle.isolated) {
739 bundle.removeAsset(asset);
740 }
741 commonBundle.getSiblingBundle(bundle.type).addAsset(asset);
742 }
743
744 let oldBundle = asset.parentBundle;
745 asset.parentBundle = commonBundle;
746
747 // Move all dependencies as well
748 for (let child of asset.depAssets.values()) {
749 if (child.parentBundle === oldBundle) {
750 this.moveAssetToBundle(child, commonBundle);
751 }
752 }
753 }
754
755 *findOrphanAssets() {
756 for (let asset of this.loadedAssets.values()) {
757 if (!asset.parentBundle) {
758 yield asset;
759 }
760 }
761 }
762
763 unloadOrphanedAssets() {
764 for (let asset of this.findOrphanAssets()) {
765 this.unloadAsset(asset);
766 }
767 }
768
769 unloadAsset(asset) {
770 this.loadedAssets.delete(asset.name);
771 if (this.watcher) {
772 this.unwatch(asset.name, asset);
773
774 // Unwatch all included dependencies that map to this asset
775 for (let dep of asset.dependencies.values()) {
776 if (dep.includedInParent) {
777 this.unwatch(dep.name, asset);
778 }
779 }
780 }
781 }
782
783 async onAdd(path) {
784 path = Path.join(process.cwd(), path);
785
786 let asset = this.parser.getAsset(path, this.options);
787 this.loadedAssets.set(path, asset);
788
789 this.entryAssets.add(asset);
790
791 await this.watch(path, asset);
792 this.onChange(path);
793 }
794
795 async onChange(path) {
796 // The path to the newly-added items are not absolute.
797 if (!Path.isAbsolute(path)) {
798 path = Path.resolve(process.cwd(), path);
799 }
800
801 let assets = this.watchedAssets.get(path);
802 if (!assets || !assets.size) {
803 return;
804 }
805
806 logger.clear();
807 logger.progress(`Building ${Path.basename(path)}...`);
808
809 // Add the asset to the rebuild queue, and reset the timeout.
810 for (let asset of assets) {
811 this.buildQueue.add(asset, true);
812 }
813
814 clearTimeout(this.rebuildTimeout);
815
816 this.rebuildTimeout = setTimeout(async () => {
817 await this.bundle();
818 }, 100);
819 }
820
821 async onUnlink(path) {
822 // The path to the newly-added items are not absolute.
823 if (!Path.isAbsolute(path)) {
824 path = Path.resolve(process.cwd(), path);
825 }
826
827 let asset = this.getLoadedAsset(path);
828 this.entryAssets.delete(asset);
829 this.unloadAsset(asset);
830
831 this.bundle();
832 }
833
834 middleware() {
835 this.bundle();
836 return Server.middleware(this);
837 }
838
839 async serve(port = 1234, https = false, host) {
840 this.server = await Server.serve(this, port, host, https);
841 try {
842 await this.bundle();
843 } catch (e) {
844 // ignore: server can still work with errored bundler
845 }
846 return this.server;
847 }
848}
849
850module.exports = Bundler;
851Bundler.Asset = require('./Asset');
852Bundler.Packager = require('./packagers/Packager');