UNPKG

@parcel/core

Version:
539 lines (533 loc) • 22.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; function _assert() { const data = _interopRequireDefault(require("assert")); _assert = function () { return data; }; return data; } function _utils() { const data = require("@parcel/utils"); _utils = function () { return data; }; return data; } function _logger() { const data = require("@parcel/logger"); _logger = function () { return data; }; return data; } function _diagnostic() { const data = _interopRequireWildcard(require("@parcel/diagnostic")); _diagnostic = function () { return data; }; return data; } function _stream() { const data = require("stream"); _stream = function () { return data; }; return data; } function _nullthrows() { const data = _interopRequireDefault(require("nullthrows")); _nullthrows = function () { return data; }; return data; } function _path() { const data = _interopRequireDefault(require("path")); _path = function () { return data; }; return data; } function _url() { const data = _interopRequireDefault(require("url")); _url = function () { return data; }; return data; } function _rust() { const data = require("@parcel/rust"); _rust = function () { return data; }; return data; } var _Bundle = require("./public/Bundle"); var _BundleGraph = _interopRequireWildcard(require("./public/BundleGraph")); var _PluginOptions = _interopRequireDefault(require("./public/PluginOptions")); var _Config = _interopRequireDefault(require("./public/Config")); var _constants = require("./constants"); var _projectPath = require("./projectPath"); var _InternalConfig = require("./InternalConfig"); var _ConfigRequest = require("./requests/ConfigRequest"); var _DevDepRequest = require("./requests/DevDepRequest"); var _buildCache = require("./buildCache"); var _assetUtils = require("./assetUtils"); var _utils2 = require("./utils"); function _profiler() { const data = require("@parcel/profiler"); _profiler = function () { return data; }; return data; } function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const BOUNDARY_LENGTH = _constants.HASH_REF_PREFIX.length + 32 - 1; // Packager/optimizer configs are not bundle-specific, so we only need to // load them once per build. const pluginConfigs = (0, _buildCache.createBuildCache)(); class PackagerRunner { constructor({ config, options, report, previousDevDeps, previousInvalidations }) { this.config = config; this.options = options; this.report = report; this.previousDevDeps = previousDevDeps; this.devDepRequests = new Map(); this.previousInvalidations = previousInvalidations; this.invalidations = new Map(); this.pluginOptions = new _PluginOptions.default((0, _utils2.optionsProxy)(this.options, option => { let invalidation = { type: 'option', key: option }; this.invalidations.set((0, _assetUtils.getInvalidationId)(invalidation), invalidation); })); } async run(bundleGraph, bundle, invalidDevDeps) { (0, _DevDepRequest.invalidateDevDeps)(invalidDevDeps, this.options, this.config); let { configs, bundleConfigs } = await this.loadConfigs(bundleGraph, bundle); let bundleInfo = await this.getBundleInfoFromCache(bundleGraph, bundle, configs, bundleConfigs); if (bundleInfo.length === 0) { bundleInfo = await this.getBundleInfo(bundle, bundleGraph, configs, bundleConfigs); } let configRequests = (0, _ConfigRequest.getConfigRequests)([...configs.values(), ...bundleConfigs.values()]); let devDepRequests = (0, _DevDepRequest.getWorkerDevDepRequests)([...this.devDepRequests.values()]); return { bundleInfo, configRequests, devDepRequests, invalidations: [...this.invalidations.values()] }; } async loadConfigs(bundleGraph, bundle) { let configs = new Map(); let bundleConfigs = new Map(); await this.loadConfig(bundleGraph, bundle, configs, bundleConfigs); for (let inlineBundle of bundleGraph.getInlineBundles(bundle)) { await this.loadConfig(bundleGraph, inlineBundle, configs, bundleConfigs); } return { configs, bundleConfigs }; } async loadConfig(bundleGraph, bundle, configs, bundleConfigs) { let name = (0, _nullthrows().default)(bundle.name); let plugin = await this.config.getPackager(name, bundle.pipeline); await this.loadPluginConfig(bundleGraph, bundle, plugin, configs, bundleConfigs); let optimizers = await this.config.getOptimizers(name, bundle.pipeline); for (let optimizer of optimizers) { await this.loadPluginConfig(bundleGraph, bundle, optimizer, configs, bundleConfigs); } } async loadPluginConfig(bundleGraph, bundle, plugin, configs, bundleConfigs) { if (!configs.has(plugin.name)) { // Only load config for a plugin once per build. let existing = pluginConfigs.get(plugin.name); if (existing != null) { configs.set(plugin.name, existing); } else { if (plugin.plugin.loadConfig != null) { let config = (0, _InternalConfig.createConfig)({ plugin: plugin.name, searchPath: (0, _projectPath.toProjectPathUnsafe)('index') }); await (0, _ConfigRequest.loadPluginConfig)(plugin, config, this.options); for (let devDep of config.devDeps) { let devDepRequest = await (0, _DevDepRequest.createDevDependency)(devDep, this.previousDevDeps, this.options); let key = `${devDep.specifier}:${(0, _projectPath.fromProjectPath)(this.options.projectRoot, devDep.resolveFrom)}`; this.devDepRequests.set(key, devDepRequest); } pluginConfigs.set(plugin.name, config); configs.set(plugin.name, config); } } } let loadBundleConfig = plugin.plugin.loadBundleConfig; if (!bundleConfigs.has(plugin.name) && loadBundleConfig != null) { let config = (0, _InternalConfig.createConfig)({ plugin: plugin.name, searchPath: (0, _projectPath.joinProjectPath)(bundle.target.distDir, bundle.name ?? bundle.id) }); config.result = await loadBundleConfig({ bundle: _Bundle.NamedBundle.get(bundle, bundleGraph, this.options), bundleGraph: new _BundleGraph.default(bundleGraph, _Bundle.NamedBundle.get.bind(_Bundle.NamedBundle), this.options), config: new _Config.default(config, this.options), options: new _PluginOptions.default(this.options), logger: new (_logger().PluginLogger)({ origin: plugin.name }), tracer: new (_profiler().PluginTracer)({ origin: plugin.name, category: 'loadConfig' }) }); bundleConfigs.set(plugin.name, config); } } async getBundleInfoFromCache(bundleGraph, bundle, configs, bundleConfigs) { if (this.options.shouldDisableCache) { return []; } let cacheKey = await this.getCacheKey(bundle, bundleGraph, configs, bundleConfigs, this.previousInvalidations); let infoKey = PackagerRunner.getInfoKey(cacheKey); let res = await this.options.cache.get(infoKey); return res ?? []; } async getBundleInfo(bundle, bundleGraph, configs, bundleConfigs) { let results = await this.getBundleResult(bundle, bundleGraph, configs, bundleConfigs); // Recompute cache keys as they may have changed due to dev dependencies. let cacheKey = await this.getCacheKey(bundle, bundleGraph, configs, bundleConfigs, [...this.invalidations.values()]); return this.writeToCache(cacheKey, results); } async getInlineBundleContents(bundle, bundleGraph, configs, bundleConfigs) { let bundleInfo = await this.getBundleInfoFromCache(bundleGraph, bundle, configs, bundleConfigs); if (bundleInfo.length > 0) { return this.options.cache.getBlob(bundleInfo[0].cacheKeys.content); } let results = await this.getBundleResult(bundle, bundleGraph, configs, bundleConfigs); // Writing to the cache consumes the stream, but we need to also return it to the calling packager. // Buffer the stream into memory first. if (results[0].contents instanceof _stream().Readable) { results[0].contents = await (0, _utils().bufferStream)(results[0].contents); } // Recompute cache keys as they may have changed due to dev dependencies. let cacheKey = await this.getCacheKey(bundle, bundleGraph, configs, bundleConfigs, [...this.invalidations.values()]); await this.writeToCache(cacheKey, results); return results[0].contents; } async getBundleResult(bundle, bundleGraph, configs, bundleConfigs) { let packagedResults = await this.package(bundle, bundleGraph, configs, bundleConfigs); return Promise.all(packagedResults.map(async packaged => { let type = packaged.type ?? bundle.type; let res = await this.optimize(bundle, bundleGraph, type, packaged.contents, packaged.map, configs, bundleConfigs); let map = res.map != null ? await this.generateSourceMap(bundle, res.map) : null; return { type: res.type ?? type, contents: res.contents, map }; })); } getSourceMapReference(bundle, map) { if (map && bundle.env.sourceMap && bundle.bundleBehavior !== 'inline') { if (bundle.env.sourceMap && bundle.env.sourceMap.inline) { return this.generateSourceMap((0, _Bundle.bundleToInternalBundle)(bundle), map); } else { return _path().default.basename(bundle.name) + '.map'; } } else { return null; } } async package(internalBundle, bundleGraph, configs, bundleConfigs) { let bundle = _Bundle.NamedBundle.get(internalBundle, bundleGraph, this.options); this.report({ type: 'buildProgress', phase: 'packaging', bundle }); let packager = await this.config.getPackager(bundle.name, internalBundle.pipeline); let { name, resolveFrom, plugin } = packager; let measurement; try { var _configs$get, _bundleConfigs$get; measurement = _profiler().tracer.createMeasurement(name, 'packaging', bundle.name, { type: bundle.type }); let res = await plugin.package({ config: (_configs$get = configs.get(name)) === null || _configs$get === void 0 ? void 0 : _configs$get.result, bundleConfig: (_bundleConfigs$get = bundleConfigs.get(name)) === null || _bundleConfigs$get === void 0 ? void 0 : _bundleConfigs$get.result, bundle, bundleGraph: new _BundleGraph.default(bundleGraph, _Bundle.NamedBundle.get.bind(_Bundle.NamedBundle), this.options), getSourceMapReference: map => { return this.getSourceMapReference(bundle, map); }, options: this.pluginOptions, logger: new (_logger().PluginLogger)({ origin: name }), tracer: new (_profiler().PluginTracer)({ origin: name, category: 'package' }), getInlineBundleContents: async (bundle, bundleGraph) => { if (bundle.bundleBehavior !== 'inline') { throw new Error('Bundle is not inline and unable to retrieve contents'); } let contents = await this.getInlineBundleContents((0, _Bundle.bundleToInternalBundle)(bundle), // $FlowFixMe (0, _BundleGraph.bundleGraphToInternalBundleGraph)(bundleGraph), configs, bundleConfigs); return { contents }; } }); if (Array.isArray(res)) { return res; } return [res]; } catch (e) { throw new (_diagnostic().default)({ diagnostic: (0, _diagnostic().errorToDiagnostic)(e, { origin: name, filePath: _path().default.join(bundle.target.distDir, bundle.name) }) }); } finally { measurement && measurement.end(); // Add dev dependency for the packager. This must be done AFTER running it due to // the potential for lazy require() that aren't executed until the request runs. let devDepRequest = await (0, _DevDepRequest.createDevDependency)({ specifier: name, resolveFrom }, this.previousDevDeps, this.options); this.devDepRequests.set(`${name}:${(0, _projectPath.fromProjectPathRelative)(resolveFrom)}`, devDepRequest); } } async optimize(internalBundle, internalBundleGraph, type, contents, map, configs, bundleConfigs) { let bundle = _Bundle.NamedBundle.get(internalBundle, internalBundleGraph, this.options); let bundleGraph = new _BundleGraph.default(internalBundleGraph, _Bundle.NamedBundle.get.bind(_Bundle.NamedBundle), this.options); let name = bundle.name; if (type !== bundle.type) { name = name.slice(0, -_path().default.extname(name).length) + '.' + type; } let optimizers = await this.config.getOptimizers(name, internalBundle.pipeline); if (!optimizers.length) { return { type, contents, map }; } this.report({ type: 'buildProgress', phase: 'optimizing', bundle }); let optimized = { type, contents, map }; for (let optimizer of optimizers) { let measurement; try { var _configs$get2, _bundleConfigs$get2; measurement = _profiler().tracer.createMeasurement(optimizer.name, 'optimize', bundle.name); let next = await optimizer.plugin.optimize({ config: (_configs$get2 = configs.get(optimizer.name)) === null || _configs$get2 === void 0 ? void 0 : _configs$get2.result, bundleConfig: (_bundleConfigs$get2 = bundleConfigs.get(optimizer.name)) === null || _bundleConfigs$get2 === void 0 ? void 0 : _bundleConfigs$get2.result, bundle, bundleGraph, contents: optimized.contents, map: optimized.map, getSourceMapReference: map => { return this.getSourceMapReference(bundle, map); }, options: this.pluginOptions, logger: new (_logger().PluginLogger)({ origin: optimizer.name }), tracer: new (_profiler().PluginTracer)({ origin: optimizer.name, category: 'optimize' }) }); optimized.type = next.type ?? optimized.type; optimized.contents = next.contents; optimized.map = next.map; } catch (e) { throw new (_diagnostic().default)({ diagnostic: (0, _diagnostic().errorToDiagnostic)(e, { origin: optimizer.name, filePath: _path().default.join(bundle.target.distDir, bundle.name) }) }); } finally { measurement && measurement.end(); // Add dev dependency for the optimizer. This must be done AFTER running it due to // the potential for lazy require() that aren't executed until the request runs. let devDepRequest = await (0, _DevDepRequest.createDevDependency)({ specifier: optimizer.name, resolveFrom: optimizer.resolveFrom }, this.previousDevDeps, this.options); this.devDepRequests.set(`${optimizer.name}:${(0, _projectPath.fromProjectPathRelative)(optimizer.resolveFrom)}`, devDepRequest); } } return optimized; } async generateSourceMap(bundle, map) { // sourceRoot should be a relative path between outDir and rootDir for node.js targets let filePath = (0, _projectPath.joinProjectPath)(bundle.target.distDir, (0, _nullthrows().default)(bundle.name)); let fullPath = (0, _projectPath.fromProjectPath)(this.options.projectRoot, filePath); let sourceRoot = _path().default.relative(_path().default.dirname(fullPath), this.options.projectRoot); let inlineSources = false; if (bundle.target) { if (bundle.env.sourceMap && bundle.env.sourceMap.sourceRoot !== undefined) { sourceRoot = bundle.env.sourceMap.sourceRoot; } else if (this.options.serveOptions && bundle.target.env.context === 'browser') { sourceRoot = '/__parcel_source_root'; } if (bundle.env.sourceMap && bundle.env.sourceMap.inlineSources !== undefined) { inlineSources = bundle.env.sourceMap.inlineSources; } else if (bundle.target.env.context !== 'node') { // inlining should only happen in production for browser targets by default inlineSources = this.options.mode === 'production'; } } let isInlineMap = bundle.env.sourceMap && bundle.env.sourceMap.inline; let stringified = await map.stringify({ file: _path().default.basename(fullPath + '.map'), // $FlowFixMe fs: this.options.inputFS, rootDir: this.options.projectRoot, sourceRoot: !inlineSources ? _url().default.format(_url().default.parse(sourceRoot + '/')) : undefined, inlineSources, format: isInlineMap ? 'inline' : 'string' }); (0, _assert().default)(typeof stringified === 'string'); return stringified; } async getCacheKey(bundle, bundleGraph, configs, bundleConfigs, invalidations) { let configResults = {}; for (let [pluginName, config] of configs) { if (config) { configResults[pluginName] = await (0, _ConfigRequest.getConfigHash)(config, pluginName, this.options); } } let globalInfoResults = {}; for (let [pluginName, config] of bundleConfigs) { if (config) { globalInfoResults[pluginName] = await (0, _ConfigRequest.getConfigHash)(config, pluginName, this.options); } } let devDepHashes = await this.getDevDepHashes(bundle); for (let inlineBundle of bundleGraph.getInlineBundles(bundle)) { devDepHashes += await this.getDevDepHashes(inlineBundle); } let invalidationHash = await (0, _assetUtils.getInvalidationHash)(invalidations, this.options); return (0, _rust().hashString)(_constants.PARCEL_VERSION + devDepHashes + invalidationHash + bundle.target.publicUrl + bundleGraph.getHash(bundle) + JSON.stringify(configResults) + JSON.stringify(globalInfoResults) + this.options.mode + (this.options.shouldBuildLazily ? 'lazy' : 'eager')); } async getDevDepHashes(bundle) { var _this$devDepRequests$; let name = (0, _nullthrows().default)(bundle.name); let packager = await this.config.getPackager(name, bundle.pipeline); let optimizers = await this.config.getOptimizers(name, bundle.pipeline); let key = `${packager.name}:${(0, _projectPath.fromProjectPathRelative)(packager.resolveFrom)}`; let devDepHashes = ((_this$devDepRequests$ = this.devDepRequests.get(key)) === null || _this$devDepRequests$ === void 0 ? void 0 : _this$devDepRequests$.hash) ?? this.previousDevDeps.get(key) ?? ''; for (let { name, resolveFrom } of optimizers) { var _this$devDepRequests$2; let key = `${name}:${(0, _projectPath.fromProjectPathRelative)(resolveFrom)}`; devDepHashes += ((_this$devDepRequests$2 = this.devDepRequests.get(key)) === null || _this$devDepRequests$2 === void 0 ? void 0 : _this$devDepRequests$2.hash) ?? this.previousDevDeps.get(key) ?? ''; } return devDepHashes; } async writeToCache(cacheKey, results) { let info = await Promise.all(results.map(async ({ contents, map, type }, index) => { let size = 0; let hash; let hashReferences = []; let isLargeBlob = false; let cacheKeys = { content: PackagerRunner.getContentKey(cacheKey, index), map: PackagerRunner.getMapKey(cacheKey, index) }; // TODO: don't replace hash references in binary files?? if (contents instanceof _stream().Readable) { isLargeBlob = true; let boundaryStr = ''; let h = new (_rust().Hash)(); await this.options.cache.setStream(cacheKeys.content, (0, _utils().blobToStream)(contents).pipe(new (_utils().TapStream)(buf => { let str = boundaryStr + buf.toString(); hashReferences = hashReferences.concat(str.match(_constants.HASH_REF_REGEX) ?? []); size += buf.length; h.writeBuffer(buf); boundaryStr = str.slice(str.length - BOUNDARY_LENGTH); }))); hash = h.finish(); } else if (typeof contents === 'string') { let buffer = Buffer.from(contents); size = buffer.byteLength; hash = (0, _rust().hashBuffer)(buffer); hashReferences = contents.match(_constants.HASH_REF_REGEX) ?? []; await this.options.cache.setBlob(cacheKeys.content, buffer); } else { size = contents.length; hash = (0, _rust().hashBuffer)(contents); hashReferences = contents.toString().match(_constants.HASH_REF_REGEX) ?? []; await this.options.cache.setBlob(cacheKeys.content, contents); } if (map != null) { await this.options.cache.setBlob(cacheKeys.map, map); } let info = { type, size, hash, hashReferences, cacheKeys, isLargeBlob }; return info; })); await this.options.cache.set(PackagerRunner.getInfoKey(cacheKey), info); return info; } static getContentKey(cacheKey, index) { return (0, _rust().hashString)(`${cacheKey}:${index}:content`); } static getMapKey(cacheKey, index) { return (0, _rust().hashString)(`${cacheKey}:${index}:map`); } static getInfoKey(cacheKey) { return (0, _rust().hashString)(`${cacheKey}:info`); } } exports.default = PackagerRunner;