UNPKG

parcel-bundler

Version:

<p align="center"> <a href="https://parceljs.org/" target="_blank"> <img alt="Parcel" src="https://user-images.githubusercontent.com/19409/31321658-f6aed0f2-ac3d-11e7-8100-1587e676e0ec.png" width="749"> </a> </p>

673 lines (556 loc) 20.1 kB
'use strict'; function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } const fs = require('./utils/fs'); const Resolver = require('./Resolver'); const Parser = require('./Parser'); const WorkerFarm = require('./WorkerFarm'); const Path = require('path'); const Bundle = require('./Bundle'); var _require = require('chokidar'); const FSWatcher = _require.FSWatcher; const FSCache = require('./FSCache'); const HMRServer = require('./HMRServer'); const Server = require('./Server'); var _require2 = require('events'); const EventEmitter = _require2.EventEmitter; const Logger = require('./Logger'); const PackagerRegistry = require('./packagers'); const localRequire = require('./utils/localRequire'); const config = require('./utils/config'); /** * The Bundler is the main entry point. It resolves and loads assets, * creates the bundle tree, and manages the worker farm, cache, and file watcher. */ class Bundler extends EventEmitter { constructor(main, options = {}) { super(); this.mainFile = Path.resolve(main || ''); this.options = this.normalizeOptions(options); this.resolver = new Resolver(this.options); this.parser = new Parser(this.options); this.packagers = new PackagerRegistry(); this.cache = this.options.cache ? new FSCache(this.options) : null; this.logger = new Logger(this.options); this.delegate = options.delegate || {}; this.pending = false; this.loadedAssets = new Map(); this.farm = null; this.watcher = null; this.hmr = null; this.bundleHashes = null; this.errored = false; this.buildQueue = new Set(); this.rebuildTimeout = null; } normalizeOptions(options) { const isProduction = options.production || process.env.NODE_ENV === 'production'; const publicURL = options.publicUrl || options.publicURL || '/' + Path.basename(options.outDir || 'dist'); const watch = typeof options.watch === 'boolean' ? options.watch : !isProduction; return { outDir: Path.resolve(options.outDir || 'dist'), publicURL: publicURL, watch: watch, cache: typeof options.cache === 'boolean' ? options.cache : true, killWorkers: typeof options.killWorkers === 'boolean' ? options.killWorkers : true, minify: typeof options.minify === 'boolean' ? options.minify : isProduction, hmr: typeof options.hmr === 'boolean' ? options.hmr : watch, logLevel: typeof options.logLevel === 'number' ? options.logLevel : 3, mainFile: this.mainFile }; } addAssetType(extension, path) { if (typeof path !== 'string') { throw new Error('Asset type should be a module path.'); } if (this.farm) { throw new Error('Asset types must be added before bundling.'); } this.parser.registerExtension(extension, path); } addPackager(type, packager) { if (this.farm) { throw new Error('Packagers must be added before bundling.'); } this.packagers.add(type, packager); } loadPlugins() { var _this = this; return _asyncToGenerator(function* () { let pkg = yield config.load(_this.mainFile, ['package.json']); if (!pkg) { return; } try { let deps = Object.assign({}, pkg.dependencies, pkg.devDependencies); for (let dep in deps) { if (dep.startsWith('parcel-plugin-')) { let plugin = yield localRequire(dep, _this.mainFile); plugin(_this); } } } catch (err) { _this.logger.warn(err); } })(); } bundle() { var _this2 = this; return _asyncToGenerator(function* () { // If another bundle is already pending, wait for that one to finish and retry. if (_this2.pending) { return new Promise(function (resolve, reject) { _this2.once('buildEnd', function () { _this2.bundle().then(resolve, reject); }); }); } let isInitialBundle = !_this2.mainAsset; let startTime = Date.now(); _this2.pending = true; _this2.errored = false; _this2.logger.clear(); _this2.logger.status('⏳', 'Building...'); try { // Start worker farm, watcher, etc. if needed yield _this2.start(); // If this is the initial bundle, ensure the output directory exists, and resolve the main asset. if (isInitialBundle) { yield fs.mkdirp(_this2.options.outDir); _this2.mainAsset = yield _this2.resolveAsset(_this2.mainFile); _this2.buildQueue.add(_this2.mainAsset); } // Build the queued assets, and produce a bundle tree. let bundle = yield _this2.buildQueuedAssets(isInitialBundle); let buildTime = Date.now() - startTime; let time = buildTime < 1000 ? `${buildTime}ms` : `${(buildTime / 1000).toFixed(2)}s`; _this2.logger.status('✨', `Built in ${time}.`, 'green'); return bundle; } catch (err) { _this2.errored = true; _this2.logger.error(err); if (_this2.hmr) { _this2.hmr.emitError(err); } if (process.env.NODE_ENV === 'production') { process.exitCode = 1; } } finally { _this2.pending = false; _this2.emit('buildEnd'); // If not in watch mode, stop the worker farm so we don't keep the process running. if (!_this2.watcher && _this2.options.killWorkers) { _this2.stop(); } } })(); } start() { var _this3 = this; return _asyncToGenerator(function* () { if (_this3.farm) { return; } yield _this3.loadPlugins(); _this3.options.extensions = Object.assign({}, _this3.parser.extensions); _this3.farm = WorkerFarm.getShared(_this3.options); if (_this3.options.watch) { // FS events on macOS are flakey in the tests, which write lots of files very quickly // See https://github.com/paulmillr/chokidar/issues/612 _this3.watcher = new FSWatcher({ useFsEvents: process.env.NODE_ENV !== 'test' }); _this3.watcher.on('change', _this3.onChange.bind(_this3)); } if (_this3.options.hmr) { _this3.hmr = new HMRServer(); _this3.options.hmrPort = yield _this3.hmr.start(); } })(); } stop() { if (this.farm) { this.farm.end(); } if (this.watcher) { this.watcher.close(); } if (this.hmr) { this.hmr.stop(); } } buildQueuedAssets(isInitialBundle = false) { var _this4 = this; return _asyncToGenerator(function* () { // Consume the rebuild queue until it is empty. let loadedAssets = new Set(); while (_this4.buildQueue.size > 0) { let promises = []; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = _this4.buildQueue[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { let asset = _step.value; // Invalidate the asset, unless this is the initial bundle if (!isInitialBundle) { asset.invalidate(); if (_this4.cache) { _this4.cache.invalidate(asset.name); } } promises.push(_this4.loadAsset(asset)); loadedAssets.add(asset); } // Wait for all assets to load. If there are more added while // these are processing, they'll be loaded in the next batch. } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } yield Promise.all(promises); } // Emit an HMR update for any new assets (that don't have a parent bundle yet) // plus the asset that actually changed. if (_this4.hmr && !isInitialBundle) { _this4.hmr.emitUpdate([..._this4.findOrphanAssets(), ...loadedAssets]); } // Invalidate bundles var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = _this4.loadedAssets.values()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { let asset = _step2.value; asset.invalidateBundle(); } // Create a new bundle tree and package everything up. } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } let bundle = _this4.createBundleTree(_this4.mainAsset); _this4.bundleHashes = yield bundle.package(_this4, _this4.bundleHashes); // Unload any orphaned assets _this4.unloadOrphanedAssets(); _this4.emit('bundled', bundle); return bundle; })(); } resolveAsset(name, parent) { var _this5 = this; return _asyncToGenerator(function* () { var _ref = yield _this5.resolver.resolve(name, parent); let path = _ref.path, pkg = _ref.pkg; if (_this5.loadedAssets.has(path)) { return _this5.loadedAssets.get(path); } let asset = _this5.parser.getAsset(path, pkg, _this5.options); _this5.loadedAssets.set(path, asset); if (_this5.watcher) { _this5.watcher.add(path); } return asset; })(); } resolveDep(asset, dep) { var _this6 = this; return _asyncToGenerator(function* () { try { return yield _this6.resolveAsset(dep.name, asset.name); } catch (err) { let thrown = err; if (thrown.message.indexOf(`Cannot find module '${dep.name}'`) === 0) { thrown.message = `Cannot resolve dependency '${dep.name}'`; // Add absolute path to the error message if the dependency specifies a relative path if (dep.name.startsWith('.')) { const absPath = Path.resolve(Path.dirname(asset.name), dep.name); err.message += ` at '${absPath}'`; } // Generate a code frame where the dependency was used if (dep.loc) { yield asset.loadIfNeeded(); thrown.loc = dep.loc; thrown = asset.generateErrorMessage(thrown); } thrown.fileName = asset.name; } throw thrown; } })(); } loadAsset(asset) { var _this7 = this; return _asyncToGenerator(function* () { if (asset.processed) { _this7.buildQueue.delete(asset); return; } if (!_this7.errored) { _this7.logger.status('⏳', `Building ${asset.basename}...`); } // Mark the asset processed so we don't load it twice asset.processed = true; // First try the cache, otherwise load and compile in the background let processed = _this7.cache && (yield _this7.cache.read(asset.name)); if (!processed) { processed = yield _this7.farm.run(asset.name, asset.package, _this7.options); if (_this7.cache) { _this7.cache.write(asset.name, processed); } } asset.generated = processed.generated; asset.hash = processed.hash; // Call the delegate to get implicit dependencies let dependencies = processed.dependencies; if (_this7.delegate.getImplicitDependencies) { let implicitDeps = yield _this7.delegate.getImplicitDependencies(asset); if (implicitDeps) { dependencies = dependencies.concat(implicitDeps); } } // Resolve and load asset dependencies let assetDeps = yield Promise.all(dependencies.map((() => { var _ref2 = _asyncToGenerator(function* (dep) { let assetDep = yield _this7.resolveDep(asset, dep); if (!dep.includedInParent) { yield _this7.loadAsset(assetDep); } return assetDep; }); return function (_x) { return _ref2.apply(this, arguments); }; })())); // Store resolved assets in their original order dependencies.forEach(function (dep, i) { let assetDep = assetDeps[i]; if (dep.includedInParent) { // This dependency is already included in the parent's generated output, // so no need to load it. We map the name back to the parent asset so // that changing it triggers a recompile of the parent. _this7.loadedAssets.set(dep.name, asset); } else { asset.dependencies.set(dep.name, dep); asset.depAssets.set(dep.name, assetDep); } }); _this7.buildQueue.delete(asset); })(); } createBundleTree(asset, dep, bundle) { if (dep) { asset.parentDeps.add(dep); } if (asset.parentBundle) { // If the asset is already in a bundle, it is shared. Move it to the lowest common ancestor. if (asset.parentBundle !== bundle) { let commonBundle = bundle.findCommonAncestor(asset.parentBundle); if (asset.parentBundle !== commonBundle && asset.parentBundle.type === commonBundle.type) { this.moveAssetToBundle(asset, commonBundle); } } return; } // Create the root bundle if it doesn't exist if (!bundle) { bundle = new Bundle(asset.type, Path.join(this.options.outDir, asset.generateBundleName())); bundle.entryAsset = asset; } // Create a new bundle for dynamic imports if (dep && dep.dynamic) { bundle = bundle.createChildBundle(asset.type, Path.join(this.options.outDir, asset.generateBundleName())); bundle.entryAsset = asset; } // Add the asset to the bundle of the asset's type bundle.getSiblingBundle(asset.type).addAsset(asset); // If the asset generated a representation for the parent bundle type, also add it there if (asset.generated[bundle.type] != null) { bundle.addAsset(asset); } asset.parentBundle = bundle; var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = asset.dependencies.values()[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { let dep = _step3.value; let assetDep = asset.depAssets.get(dep.name); this.createBundleTree(assetDep, dep, bundle); } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } return bundle; } moveAssetToBundle(asset, commonBundle) { var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = Array.from(asset.bundles)[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { let bundle = _step4.value; bundle.removeAsset(asset); commonBundle.getSiblingBundle(bundle.type).addAsset(asset); } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } let oldBundle = asset.parentBundle; asset.parentBundle = commonBundle; // Move all dependencies as well var _iteratorNormalCompletion5 = true; var _didIteratorError5 = false; var _iteratorError5 = undefined; try { for (var _iterator5 = asset.depAssets.values()[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { let child = _step5.value; if (child.parentBundle === oldBundle) { this.moveAssetToBundle(child, commonBundle); } } } catch (err) { _didIteratorError5 = true; _iteratorError5 = err; } finally { try { if (!_iteratorNormalCompletion5 && _iterator5.return) { _iterator5.return(); } } finally { if (_didIteratorError5) { throw _iteratorError5; } } } } *findOrphanAssets() { var _iteratorNormalCompletion6 = true; var _didIteratorError6 = false; var _iteratorError6 = undefined; try { for (var _iterator6 = this.loadedAssets.values()[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { let asset = _step6.value; if (!asset.parentBundle) { yield asset; } } } catch (err) { _didIteratorError6 = true; _iteratorError6 = err; } finally { try { if (!_iteratorNormalCompletion6 && _iterator6.return) { _iterator6.return(); } } finally { if (_didIteratorError6) { throw _iteratorError6; } } } } unloadOrphanedAssets() { var _iteratorNormalCompletion7 = true; var _didIteratorError7 = false; var _iteratorError7 = undefined; try { for (var _iterator7 = this.findOrphanAssets()[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) { let asset = _step7.value; this.unloadAsset(asset); } } catch (err) { _didIteratorError7 = true; _iteratorError7 = err; } finally { try { if (!_iteratorNormalCompletion7 && _iterator7.return) { _iterator7.return(); } } finally { if (_didIteratorError7) { throw _iteratorError7; } } } } unloadAsset(asset) { this.loadedAssets.delete(asset.name); if (this.watcher) { this.watcher.unwatch(asset.name); } } onChange(path) { var _this8 = this; return _asyncToGenerator(function* () { let asset = _this8.loadedAssets.get(path); if (!asset) { return; } _this8.logger.clear(); _this8.logger.status('⏳', `Building ${asset.basename}...`); // Add the asset to the rebuild queue, and reset the timeout. _this8.buildQueue.add(asset); clearTimeout(_this8.rebuildTimeout); _this8.rebuildTimeout = setTimeout(_asyncToGenerator(function* () { yield _this8.bundle(); }), 100); })(); } middleware() { return Server.middleware(this); } serve(port = 1234) { var _this9 = this; return _asyncToGenerator(function* () { let server = yield Server.serve(_this9, port); _this9.bundle(); return server; })(); } } module.exports = Bundler; Bundler.Asset = require('./Asset'); Bundler.Packager = require('./packagers/Packager');