UNPKG

@parcel/core

Version:
473 lines (466 loc) • 16.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.create = create; exports.default = createParcelConfigRequest; exports.getCachedParcelConfig = getCachedParcelConfig; exports.getResolveFrom = getResolveFrom; exports.loadParcelConfig = loadParcelConfig; exports.mergeConfigs = mergeConfigs; exports.mergeMaps = mergeMaps; exports.mergePipelines = mergePipelines; exports.parseAndProcessConfig = parseAndProcessConfig; exports.processConfig = processConfig; exports.processConfigChain = processConfigChain; exports.resolveExtends = resolveExtends; exports.resolveParcelConfig = resolveParcelConfig; exports.validateConfigFile = validateConfigFile; exports.validateNotEmpty = validateNotEmpty; function _utils() { const data = require("@parcel/utils"); _utils = function () { return data; }; return data; } function _diagnostic() { const data = _interopRequireWildcard(require("@parcel/diagnostic")); _diagnostic = function () { return data; }; return data; } function _json() { const data = require("json5"); _json = function () { return data; }; return data; } function _path() { const data = _interopRequireDefault(require("path")); _path = function () { return data; }; return data; } function _assert() { const data = _interopRequireDefault(require("assert")); _assert = function () { return data; }; return data; } var _ParcelConfig = _interopRequireDefault(require("../ParcelConfig.schema")); var _utils2 = require("../utils"); var _ParcelConfig2 = _interopRequireDefault(require("../ParcelConfig")); var _buildCache = require("../buildCache"); var _projectPath = require("../projectPath"); var _RequestTracker = require("../RequestTracker"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 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; } const type = 'parcel_config_request'; function createParcelConfigRequest() { return { id: type, type: _RequestTracker.requestTypes[type], async run({ api, options }) { let { config, extendedFiles, usedDefault } = await loadParcelConfig((0, _utils2.optionsProxy)(options, api.invalidateOnOptionChange)); api.invalidateOnFileUpdate(config.filePath); api.invalidateOnFileDelete(config.filePath); for (let filePath of extendedFiles) { let pp = (0, _projectPath.toProjectPath)(options.projectRoot, filePath); api.invalidateOnFileUpdate(pp); api.invalidateOnFileDelete(pp); } if (usedDefault) { let resolveFrom = getResolveFrom(options.inputFS, options.projectRoot); api.invalidateOnFileCreate({ fileName: '.parcelrc', aboveFilePath: (0, _projectPath.toProjectPath)(options.projectRoot, resolveFrom) }); } let cachePath = (0, _utils().hashObject)(config); await options.cache.set(cachePath, config); let result = { config, cachePath }; // TODO: don't store config twice (once in the graph and once in a separate cache entry) api.storeResult(result); return result; }, input: null }; } const parcelConfigCache = (0, _buildCache.createBuildCache)(); function getCachedParcelConfig(result, options) { let { config: processedConfig, cachePath } = result; let config = parcelConfigCache.get(cachePath); if (config) { return config; } config = new _ParcelConfig2.default(processedConfig, options); parcelConfigCache.set(cachePath, config); return config; } async function loadParcelConfig(options) { let parcelConfig = await resolveParcelConfig(options); if (!parcelConfig) { throw new Error('Could not find a .parcelrc'); } return parcelConfig; } async function resolveParcelConfig(options) { let resolveFrom = getResolveFrom(options.inputFS, options.projectRoot); let configPath = options.config != null ? (await options.packageManager.resolve(options.config, resolveFrom)).resolved : await (0, _utils().resolveConfig)(options.inputFS, resolveFrom, ['.parcelrc'], options.projectRoot); let usedDefault = false; if (configPath == null && options.defaultConfig != null) { usedDefault = true; configPath = (await options.packageManager.resolve(options.defaultConfig, resolveFrom)).resolved; } if (configPath == null) { return null; } let contents; try { contents = await options.inputFS.readFile(configPath, 'utf8'); } catch (e) { throw new (_diagnostic().default)({ diagnostic: { message: (0, _diagnostic().md)`Could not find parcel config at ${_path().default.relative(options.projectRoot, configPath)}`, origin: '@parcel/core' } }); } let { config, extendedFiles } = await parseAndProcessConfig(configPath, contents, options); if (options.additionalReporters.length > 0) { config.reporters = [...options.additionalReporters.map(({ packageName, resolveFrom }) => ({ packageName, resolveFrom })), ...(config.reporters ?? [])]; } return { config, extendedFiles, usedDefault }; } function create(config, options) { return processConfigChain(config, config.filePath, options); } // eslint-disable-next-line require-await async function parseAndProcessConfig(configPath, contents, options) { let config; try { config = (0, _json().parse)(contents); } catch (e) { let pos = { line: e.lineNumber, column: e.columnNumber }; throw new (_diagnostic().default)({ diagnostic: { message: `Failed to parse .parcelrc`, origin: '@parcel/core', codeFrames: [{ filePath: configPath, language: 'json5', code: contents, codeHighlights: [{ start: pos, end: pos, message: (0, _diagnostic().escapeMarkdown)(e.message) }] }] } }); } return processConfigChain(config, configPath, options); } function processPipeline(options, pipeline, keyPath, filePath) { if (pipeline) { return pipeline.map((pkg, i) => { // $FlowFixMe if (pkg === '...') return pkg; return { packageName: pkg, resolveFrom: (0, _projectPath.toProjectPath)(options.projectRoot, filePath), keyPath: `${keyPath}/${i}` }; }); } } const RESERVED_PIPELINES = new Set(['node:', 'npm:', 'http:', 'https:', 'data:', 'tel:', 'mailto:']); async function processMap( // $FlowFixMe map, keyPath, filePath, options // $FlowFixMe ) { if (!map) return undefined; // $FlowFixMe let res = {}; for (let k in map) { let i = k.indexOf(':'); if (i > 0 && RESERVED_PIPELINES.has(k.slice(0, i + 1))) { let code = await options.inputFS.readFile(filePath, 'utf8'); throw new (_diagnostic().default)({ diagnostic: { message: `Named pipeline '${k.slice(0, i + 1)}' is reserved.`, origin: '@parcel/core', codeFrames: [{ filePath: filePath, language: 'json5', code, codeHighlights: (0, _diagnostic().generateJSONCodeHighlights)(code, [{ key: `${keyPath}/${k}`, type: 'key' }]) }], documentationURL: 'https://parceljs.org/features/dependency-resolution/#url-schemes' } }); } if (typeof map[k] === 'string') { res[k] = { packageName: map[k], resolveFrom: (0, _projectPath.toProjectPath)(options.projectRoot, filePath), keyPath: `${keyPath}/${k}` }; } else { res[k] = processPipeline(options, map[k], `${keyPath}/${k}`, filePath); } } return res; } async function processConfig(configFile, options) { return { filePath: (0, _projectPath.toProjectPath)(options.projectRoot, configFile.filePath), ...(configFile.resolveFrom != null ? { resolveFrom: (0, _projectPath.toProjectPath)(options.projectRoot, configFile.resolveFrom) } : { /*::...null*/ }), resolvers: processPipeline(options, configFile.resolvers, '/resolvers', configFile.filePath), transformers: await processMap(configFile.transformers, '/transformers', configFile.filePath, options), bundler: configFile.bundler != null ? { packageName: configFile.bundler, resolveFrom: (0, _projectPath.toProjectPath)(options.projectRoot, configFile.filePath), keyPath: '/bundler' } : undefined, namers: processPipeline(options, configFile.namers, '/namers', configFile.filePath), runtimes: processPipeline(options, configFile.runtimes, '/runtimes', configFile.filePath), packagers: await processMap(configFile.packagers, '/packagers', configFile.filePath, options), optimizers: await processMap(configFile.optimizers, '/optimizers', configFile.filePath, options), compressors: await processMap(configFile.compressors, '/compressors', configFile.filePath, options), reporters: processPipeline(options, configFile.reporters, '/reporters', configFile.filePath), validators: await processMap(configFile.validators, '/validators', configFile.filePath, options) }; } async function processConfigChain(configFile, filePath, options) { // Validate config... let relativePath = _path().default.relative(options.inputFS.cwd(), filePath); validateConfigFile(configFile, relativePath); // Process config... let config = await processConfig({ filePath, ...configFile }, options); let extendedFiles = []; if (configFile.extends != null) { let exts = Array.isArray(configFile.extends) ? configFile.extends : [configFile.extends]; let errors = []; if (exts.length !== 0) { let extStartConfig; let i = 0; for (let ext of exts) { try { let key = Array.isArray(configFile.extends) ? `/extends/${i}` : '/extends'; let resolved = await resolveExtends(ext, filePath, key, options); extendedFiles.push(resolved); let { extendedFiles: moreExtendedFiles, config: nextConfig } = await processExtendedConfig(filePath, key, ext, resolved, options); extendedFiles = extendedFiles.concat(moreExtendedFiles); extStartConfig = extStartConfig ? mergeConfigs(extStartConfig, nextConfig) : nextConfig; } catch (err) { errors.push(err); } i++; } // Merge with the inline config last if (extStartConfig) { config = mergeConfigs(extStartConfig, config); } } if (errors.length > 0) { throw new (_diagnostic().default)({ diagnostic: errors.flatMap(e => e.diagnostics ?? (0, _diagnostic().errorToDiagnostic)(e)) }); } } return { config, extendedFiles }; } async function resolveExtends(ext, configPath, extendsKey, options) { if (ext.startsWith('.')) { return _path().default.resolve(_path().default.dirname(configPath), ext); } else { try { let { resolved } = await options.packageManager.resolve(ext, configPath); return options.inputFS.realpath(resolved); } catch (err) { let parentContents = await options.inputFS.readFile(configPath, 'utf8'); let alternatives = await (0, _utils().findAlternativeNodeModules)(options.inputFS, ext, _path().default.dirname(configPath)); throw new (_diagnostic().default)({ diagnostic: { message: `Cannot find extended parcel config`, origin: '@parcel/core', codeFrames: [{ filePath: configPath, language: 'json5', code: parentContents, codeHighlights: (0, _diagnostic().generateJSONCodeHighlights)(parentContents, [{ key: extendsKey, type: 'value', message: (0, _diagnostic().md)`Cannot find module "${ext}"${alternatives[0] ? `, did you mean "${alternatives[0]}"?` : ''}` }]) }] } }); } } } async function processExtendedConfig(configPath, extendsKey, extendsSpecifier, resolvedExtendedConfigPath, options) { let contents; try { contents = await options.inputFS.readFile(resolvedExtendedConfigPath, 'utf8'); } catch (e) { let parentContents = await options.inputFS.readFile(configPath, 'utf8'); let alternatives = await (0, _utils().findAlternativeFiles)(options.inputFS, extendsSpecifier, _path().default.dirname(resolvedExtendedConfigPath), options.projectRoot); throw new (_diagnostic().default)({ diagnostic: { message: 'Cannot find extended parcel config', origin: '@parcel/core', codeFrames: [{ filePath: configPath, language: 'json5', code: parentContents, codeHighlights: (0, _diagnostic().generateJSONCodeHighlights)(parentContents, [{ key: extendsKey, type: 'value', message: (0, _diagnostic().md)`"${extendsSpecifier}" does not exist${alternatives[0] ? `, did you mean "${alternatives[0]}"?` : ''}` }]) }] } }); } return parseAndProcessConfig(resolvedExtendedConfigPath, contents, options); } function validateConfigFile(config, relativePath) { try { validateNotEmpty(config, relativePath); } catch (e) { throw new (_diagnostic().default)({ diagnostic: { message: e.message, origin: '@parcel/core' } }); } _utils().validateSchema.diagnostic(_ParcelConfig.default, { data: config, filePath: relativePath }, '@parcel/core', 'Invalid Parcel Config'); } function validateNotEmpty(config, relativePath) { _assert().default.notDeepStrictEqual(config, {}, `${relativePath} can't be empty`); } function mergeConfigs(base, ext) { return { filePath: ext.filePath, resolvers: assertPurePipeline(mergePipelines(base.resolvers, ext.resolvers)), transformers: mergeMaps(base.transformers, ext.transformers, mergePipelines), validators: mergeMaps(base.validators, ext.validators, mergePipelines), bundler: ext.bundler || base.bundler, namers: assertPurePipeline(mergePipelines(base.namers, ext.namers)), runtimes: assertPurePipeline(mergePipelines(base.runtimes, ext.runtimes)), packagers: mergeMaps(base.packagers, ext.packagers), optimizers: mergeMaps(base.optimizers, ext.optimizers, mergePipelines), compressors: mergeMaps(base.compressors, ext.compressors, mergePipelines), reporters: assertPurePipeline(mergePipelines(base.reporters, ext.reporters)) }; } function getResolveFrom(fs, projectRoot) { let cwd = fs.cwd(); let dir = (0, _utils().isDirectoryInside)(cwd, projectRoot) ? cwd : projectRoot; return _path().default.join(dir, 'index'); } function assertPurePipeline(pipeline) { return pipeline.map(s => { (0, _assert().default)(typeof s !== 'string'); return s; }); } function mergePipelines(base, ext) { if (ext == null) { return base ?? []; } if (ext.filter(v => v === '...').length > 1) { throw new Error('Only one spread element can be included in a config pipeline'); } // Merge the base pipeline if a rest element is defined let spreadIndex = ext.indexOf('...'); if (spreadIndex >= 0) { return [...ext.slice(0, spreadIndex), ...(base ?? []), ...ext.slice(spreadIndex + 1)]; } else { return ext; } } function mergeMaps(base, ext, merger) { if (!ext || Object.keys(ext).length === 0) { return base || {}; } if (!base) { return ext; } let res = {}; // Add the extension options first so they have higher precedence in the output glob map for (let k in ext) { //$FlowFixMe Flow doesn't correctly infer the type. See https://github.com/facebook/flow/issues/1736. let key = k; res[key] = merger && base[key] != null ? merger(base[key], ext[key]) : ext[key]; } // Add base options that aren't defined in the extension for (let k in base) { // $FlowFixMe let key = k; if (res[key] == null) { res[key] = base[key]; } } return res; }