UNPKG

rollup

Version:

Next-generation ES module bundler

1,188 lines (1,166 loc) 898 kB
/* @license Rollup.js v4.34.6 Fri, 07 Feb 2025 16:31:35 GMT - commit 4b8745922d37d8325197d5a6613ffbf231163c7d https://github.com/rollup/rollup Released under the MIT License. */ 'use strict'; const parseAst_js = require('./parseAst.js'); const process$1 = require('node:process'); const path = require('node:path'); const require$$0 = require('path'); const native_js = require('../native.js'); const node_perf_hooks = require('node:perf_hooks'); const promises = require('node:fs/promises'); var version = "4.34.6"; function ensureArray$1(items) { if (Array.isArray(items)) { return items.filter(Boolean); } if (items) { return [items]; } return []; } var BuildPhase; (function (BuildPhase) { BuildPhase[BuildPhase["LOAD_AND_PARSE"] = 0] = "LOAD_AND_PARSE"; BuildPhase[BuildPhase["ANALYSE"] = 1] = "ANALYSE"; BuildPhase[BuildPhase["GENERATE"] = 2] = "GENERATE"; })(BuildPhase || (BuildPhase = {})); let textEncoder; const getHash64 = input => native_js.xxhashBase64Url(ensureBuffer(input)); const getHash36 = input => native_js.xxhashBase36(ensureBuffer(input)); const getHash16 = input => native_js.xxhashBase16(ensureBuffer(input)); const hasherByType = { base36: getHash36, base64: getHash64, hex: getHash16 }; function ensureBuffer(input) { if (typeof input === 'string') { if (typeof Buffer === 'undefined') { textEncoder ??= new TextEncoder(); return textEncoder.encode(input); } return Buffer.from(input); } return input; } function getOrCreate(map, key, init) { const existing = map.get(key); if (existing !== undefined) { return existing; } const value = init(); map.set(key, value); return value; } function getNewSet() { return new Set(); } function getNewArray() { return []; } const chars$1 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$'; const base = 64; function toBase64(value) { let outString = ''; do { const currentDigit = value % base; value = (value / base) | 0; outString = chars$1[currentDigit] + outString; } while (value !== 0); return outString; } // Four random characters from the private use area to minimize risk of // conflicts const hashPlaceholderLeft = '!~{'; const hashPlaceholderRight = '}~'; const hashPlaceholderOverhead = hashPlaceholderLeft.length + hashPlaceholderRight.length; // This is the size of a 128-bits xxhash with base64url encoding const MAX_HASH_SIZE = 21; const DEFAULT_HASH_SIZE = 8; const getHashPlaceholderGenerator = () => { let nextIndex = 0; return (optionName, hashSize) => { if (hashSize > MAX_HASH_SIZE) { return parseAst_js.error(parseAst_js.logFailedValidation(`Hashes cannot be longer than ${MAX_HASH_SIZE} characters, received ${hashSize}. Check the "${optionName}" option.`)); } const placeholder = `${hashPlaceholderLeft}${toBase64(++nextIndex).padStart(hashSize - hashPlaceholderOverhead, '0')}${hashPlaceholderRight}`; if (placeholder.length > hashSize) { return parseAst_js.error(parseAst_js.logFailedValidation(`To generate hashes for this number of chunks (currently ${nextIndex}), you need a minimum hash size of ${placeholder.length}, received ${hashSize}. Check the "${optionName}" option.`)); } return placeholder; }; }; const REPLACER_REGEX = new RegExp(`${hashPlaceholderLeft}[0-9a-zA-Z_$]{1,${MAX_HASH_SIZE - hashPlaceholderOverhead}}${hashPlaceholderRight}`, 'g'); const replacePlaceholders = (code, hashesByPlaceholder) => code.replace(REPLACER_REGEX, placeholder => hashesByPlaceholder.get(placeholder) || placeholder); const replaceSinglePlaceholder = (code, placeholder, value) => code.replace(REPLACER_REGEX, match => (match === placeholder ? value : match)); const replacePlaceholdersWithDefaultAndGetContainedPlaceholders = (code, placeholders) => { const containedPlaceholders = new Set(); const transformedCode = code.replace(REPLACER_REGEX, placeholder => { if (placeholders.has(placeholder)) { containedPlaceholders.add(placeholder); return `${hashPlaceholderLeft}${'0'.repeat(placeholder.length - hashPlaceholderOverhead)}${hashPlaceholderRight}`; } return placeholder; }); return { containedPlaceholders, transformedCode }; }; const lowercaseBundleKeys = Symbol('bundleKeys'); const FILE_PLACEHOLDER = { type: 'placeholder' }; const getOutputBundle = (outputBundleBase) => { const reservedLowercaseBundleKeys = new Set(); return new Proxy(outputBundleBase, { deleteProperty(target, key) { if (typeof key === 'string') { reservedLowercaseBundleKeys.delete(key.toLowerCase()); } return Reflect.deleteProperty(target, key); }, get(target, key) { if (key === lowercaseBundleKeys) { return reservedLowercaseBundleKeys; } return Reflect.get(target, key); }, set(target, key, value) { if (typeof key === 'string') { reservedLowercaseBundleKeys.add(key.toLowerCase()); } return Reflect.set(target, key, value); } }); }; const removeUnreferencedAssets = (outputBundle) => { const unreferencedAssets = new Set(); const bundleEntries = Object.values(outputBundle); for (const asset of bundleEntries) { if (asset.type === 'asset' && asset.needsCodeReference) { unreferencedAssets.add(asset.fileName); } } for (const chunk of bundleEntries) { if (chunk.type === 'chunk') { for (const referencedFile of chunk.referencedFiles) { if (unreferencedAssets.has(referencedFile)) { unreferencedAssets.delete(referencedFile); } } } } for (const file of unreferencedAssets) { delete outputBundle[file]; } }; function renderNamePattern(pattern, patternName, replacements) { if (parseAst_js.isPathFragment(pattern)) return parseAst_js.error(parseAst_js.logFailedValidation(`Invalid pattern "${pattern}" for "${patternName}", patterns can be neither absolute nor relative paths. If you want your files to be stored in a subdirectory, write its name without a leading slash like this: subdirectory/pattern.`)); return pattern.replace(/\[(\w+)(:\d+)?]/g, (_match, type, size) => { if (!replacements.hasOwnProperty(type) || (size && type !== 'hash')) { return parseAst_js.error(parseAst_js.logFailedValidation(`"[${type}${size || ''}]" is not a valid placeholder in the "${patternName}" pattern.`)); } const replacement = replacements[type](size && Number.parseInt(size.slice(1))); if (parseAst_js.isPathFragment(replacement)) return parseAst_js.error(parseAst_js.logFailedValidation(`Invalid substitution "${replacement}" for placeholder "[${type}]" in "${patternName}" pattern, can be neither absolute nor relative path.`)); return replacement; }); } function makeUnique(name, { [lowercaseBundleKeys]: reservedLowercaseBundleKeys }) { if (!reservedLowercaseBundleKeys.has(name.toLowerCase())) return name; const extension = path.extname(name); name = name.slice(0, Math.max(0, name.length - extension.length)); let uniqueName, uniqueIndex = 1; while (reservedLowercaseBundleKeys.has((uniqueName = name + ++uniqueIndex + extension).toLowerCase())) ; return uniqueName; } function generateAssetFileName(name, names, source, originalFileName, originalFileNames, sourceHash, outputOptions, bundle, inputOptions) { const emittedName = outputOptions.sanitizeFileName(name || 'asset'); return makeUnique(renderNamePattern(typeof outputOptions.assetFileNames === 'function' ? outputOptions.assetFileNames({ // Additionally, this should be non-enumerable in the next major get name() { parseAst_js.warnDeprecation('Accessing the "name" property of emitted assets when generating the file name is deprecated. Use the "names" property instead.', parseAst_js.URL_GENERATEBUNDLE, false, inputOptions); return name; }, names, // Additionally, this should be non-enumerable in the next major get originalFileName() { parseAst_js.warnDeprecation('Accessing the "originalFileName" property of emitted assets when generating the file name is deprecated. Use the "originalFileNames" property instead.', parseAst_js.URL_GENERATEBUNDLE, false, inputOptions); return originalFileName; }, originalFileNames, source, type: 'asset' }) : outputOptions.assetFileNames, 'output.assetFileNames', { ext: () => path.extname(emittedName).slice(1), extname: () => path.extname(emittedName), hash: size => sourceHash.slice(0, Math.max(0, size || DEFAULT_HASH_SIZE)), name: () => emittedName.slice(0, Math.max(0, emittedName.length - path.extname(emittedName).length)) }), bundle); } function reserveFileNameInBundle(fileName, { bundle }, log) { if (bundle[lowercaseBundleKeys].has(fileName.toLowerCase())) { log(parseAst_js.LOGLEVEL_WARN, parseAst_js.logFileNameConflict(fileName)); } else { bundle[fileName] = FILE_PLACEHOLDER; } } const emittedFileTypes = new Set(['chunk', 'asset', 'prebuilt-chunk']); function hasValidType(emittedFile) { return Boolean(emittedFile && emittedFileTypes.has(emittedFile.type)); } function hasValidName(emittedFile) { const validatedName = emittedFile.fileName || emittedFile.name; return !validatedName || (typeof validatedName === 'string' && !parseAst_js.isPathFragment(validatedName)); } function getValidSource(source, emittedFile, fileReferenceId) { if (!(typeof source === 'string' || source instanceof Uint8Array)) { const assetName = emittedFile.fileName || emittedFile.name || fileReferenceId; return parseAst_js.error(parseAst_js.logFailedValidation(`Could not set source for ${typeof assetName === 'string' ? `asset "${assetName}"` : 'unnamed asset'}, asset source needs to be a string, Uint8Array or Buffer.`)); } return source; } function getAssetFileName(file, referenceId) { if (typeof file.fileName !== 'string') { return parseAst_js.error(parseAst_js.logAssetNotFinalisedForFileName(file.name || referenceId)); } return file.fileName; } function getChunkFileName(file, facadeChunkByModule) { if (file.fileName) { return file.fileName; } if (facadeChunkByModule) { return facadeChunkByModule.get(file.module).getFileName(); } return parseAst_js.error(parseAst_js.logChunkNotGeneratedForFileName(file.fileName || file.name)); } class FileEmitter { constructor(graph, options, baseFileEmitter) { this.graph = graph; this.options = options; this.facadeChunkByModule = null; this.nextIdBase = 1; this.output = null; this.outputFileEmitters = []; this.emitFile = (emittedFile) => { if (!hasValidType(emittedFile)) { return parseAst_js.error(parseAst_js.logFailedValidation(`Emitted files must be of type "asset", "chunk" or "prebuilt-chunk", received "${emittedFile && emittedFile.type}".`)); } if (emittedFile.type === 'prebuilt-chunk') { return this.emitPrebuiltChunk(emittedFile); } if (!hasValidName(emittedFile)) { return parseAst_js.error(parseAst_js.logFailedValidation(`The "fileName" or "name" properties of emitted chunks and assets must be strings that are neither absolute nor relative paths, received "${emittedFile.fileName || emittedFile.name}".`)); } if (emittedFile.type === 'chunk') { return this.emitChunk(emittedFile); } return this.emitAsset(emittedFile); }; this.finaliseAssets = () => { for (const [referenceId, emittedFile] of this.filesByReferenceId) { if (emittedFile.type === 'asset' && typeof emittedFile.fileName !== 'string') return parseAst_js.error(parseAst_js.logNoAssetSourceSet(emittedFile.name || referenceId)); } }; this.getFileName = (fileReferenceId) => { const emittedFile = this.filesByReferenceId.get(fileReferenceId); if (!emittedFile) return parseAst_js.error(parseAst_js.logFileReferenceIdNotFoundForFilename(fileReferenceId)); if (emittedFile.type === 'chunk') { return getChunkFileName(emittedFile, this.facadeChunkByModule); } if (emittedFile.type === 'prebuilt-chunk') { return emittedFile.fileName; } return getAssetFileName(emittedFile, fileReferenceId); }; this.setAssetSource = (referenceId, requestedSource) => { const consumedFile = this.filesByReferenceId.get(referenceId); if (!consumedFile) return parseAst_js.error(parseAst_js.logAssetReferenceIdNotFoundForSetSource(referenceId)); if (consumedFile.type !== 'asset') { return parseAst_js.error(parseAst_js.logFailedValidation(`Asset sources can only be set for emitted assets but "${referenceId}" is an emitted chunk.`)); } if (consumedFile.source !== undefined) { return parseAst_js.error(parseAst_js.logAssetSourceAlreadySet(consumedFile.name || referenceId)); } const source = getValidSource(requestedSource, consumedFile, referenceId); if (this.output) { this.finalizeAdditionalAsset(consumedFile, source, this.output); } else { consumedFile.source = source; for (const emitter of this.outputFileEmitters) { emitter.finalizeAdditionalAsset(consumedFile, source, emitter.output); } } }; this.setChunkInformation = (facadeChunkByModule) => { this.facadeChunkByModule = facadeChunkByModule; }; this.setOutputBundle = (bundle, outputOptions) => { const getHash = hasherByType[outputOptions.hashCharacters]; const output = (this.output = { bundle, fileNamesBySourceHash: new Map(), getHash, outputOptions }); for (const emittedFile of this.filesByReferenceId.values()) { if (emittedFile.fileName) { reserveFileNameInBundle(emittedFile.fileName, output, this.options.onLog); } } const consumedAssetsByHash = new Map(); for (const consumedFile of this.filesByReferenceId.values()) { if (consumedFile.type === 'asset' && consumedFile.source !== undefined) { if (consumedFile.fileName) { this.finalizeAdditionalAsset(consumedFile, consumedFile.source, output); } else { const sourceHash = getHash(consumedFile.source); getOrCreate(consumedAssetsByHash, sourceHash, () => []).push(consumedFile); } } else if (consumedFile.type === 'prebuilt-chunk') { this.output.bundle[consumedFile.fileName] = this.createPrebuiltChunk(consumedFile); } } for (const [sourceHash, consumedFiles] of consumedAssetsByHash) { this.finalizeAssetsWithSameSource(consumedFiles, sourceHash, output); } }; this.filesByReferenceId = baseFileEmitter ? new Map(baseFileEmitter.filesByReferenceId) : new Map(); baseFileEmitter?.addOutputFileEmitter(this); } addOutputFileEmitter(outputFileEmitter) { this.outputFileEmitters.push(outputFileEmitter); } assignReferenceId(file, idBase) { let referenceId = idBase; do { referenceId = getHash64(referenceId).slice(0, 8).replaceAll('-', '$'); } while (this.filesByReferenceId.has(referenceId) || this.outputFileEmitters.some(({ filesByReferenceId }) => filesByReferenceId.has(referenceId))); file.referenceId = referenceId; this.filesByReferenceId.set(referenceId, file); for (const { filesByReferenceId } of this.outputFileEmitters) { filesByReferenceId.set(referenceId, file); } return referenceId; } createPrebuiltChunk(prebuiltChunk) { return { code: prebuiltChunk.code, dynamicImports: [], exports: prebuiltChunk.exports || [], facadeModuleId: null, fileName: prebuiltChunk.fileName, implicitlyLoadedBefore: [], importedBindings: {}, imports: [], isDynamicEntry: false, isEntry: false, isImplicitEntry: false, map: prebuiltChunk.map || null, moduleIds: [], modules: {}, name: prebuiltChunk.fileName, preliminaryFileName: prebuiltChunk.fileName, referencedFiles: [], sourcemapFileName: prebuiltChunk.sourcemapFileName || null, type: 'chunk' }; } emitAsset(emittedAsset) { const source = emittedAsset.source === undefined ? undefined : getValidSource(emittedAsset.source, emittedAsset, null); const originalFileName = emittedAsset.originalFileName || null; if (typeof originalFileName === 'string') { this.graph.watchFiles[originalFileName] = true; } const consumedAsset = { fileName: emittedAsset.fileName, name: emittedAsset.name, needsCodeReference: !!emittedAsset.needsCodeReference, originalFileName, referenceId: '', source, type: 'asset' }; const referenceId = this.assignReferenceId(consumedAsset, emittedAsset.fileName || emittedAsset.name || String(this.nextIdBase++)); if (this.output) { this.emitAssetWithReferenceId(consumedAsset, this.output); } else { for (const fileEmitter of this.outputFileEmitters) { fileEmitter.emitAssetWithReferenceId(consumedAsset, fileEmitter.output); } } return referenceId; } emitAssetWithReferenceId(consumedAsset, output) { const { fileName, source } = consumedAsset; if (fileName) { reserveFileNameInBundle(fileName, output, this.options.onLog); } if (source !== undefined) { this.finalizeAdditionalAsset(consumedAsset, source, output); } } emitChunk(emittedChunk) { if (this.graph.phase > BuildPhase.LOAD_AND_PARSE) { return parseAst_js.error(parseAst_js.logInvalidRollupPhaseForChunkEmission()); } if (typeof emittedChunk.id !== 'string') { return parseAst_js.error(parseAst_js.logFailedValidation(`Emitted chunks need to have a valid string id, received "${emittedChunk.id}"`)); } const consumedChunk = { fileName: emittedChunk.fileName, module: null, name: emittedChunk.name || emittedChunk.id, referenceId: '', type: 'chunk' }; this.graph.moduleLoader .emitChunk(emittedChunk) .then(module => (consumedChunk.module = module)) .catch(() => { // Avoid unhandled Promise rejection as the error will be thrown later // once module loading has finished }); return this.assignReferenceId(consumedChunk, emittedChunk.id); } emitPrebuiltChunk(emitPrebuiltChunk) { if (typeof emitPrebuiltChunk.code !== 'string') { return parseAst_js.error(parseAst_js.logFailedValidation(`Emitted prebuilt chunks need to have a valid string code, received "${emitPrebuiltChunk.code}".`)); } if (typeof emitPrebuiltChunk.fileName !== 'string' || parseAst_js.isPathFragment(emitPrebuiltChunk.fileName)) { return parseAst_js.error(parseAst_js.logFailedValidation(`The "fileName" property of emitted prebuilt chunks must be strings that are neither absolute nor relative paths, received "${emitPrebuiltChunk.fileName}".`)); } const consumedPrebuiltChunk = { code: emitPrebuiltChunk.code, exports: emitPrebuiltChunk.exports, fileName: emitPrebuiltChunk.fileName, map: emitPrebuiltChunk.map, referenceId: '', type: 'prebuilt-chunk' }; const referenceId = this.assignReferenceId(consumedPrebuiltChunk, consumedPrebuiltChunk.fileName); if (this.output) { this.output.bundle[consumedPrebuiltChunk.fileName] = this.createPrebuiltChunk(consumedPrebuiltChunk); } return referenceId; } finalizeAdditionalAsset(consumedFile, source, { bundle, fileNamesBySourceHash, getHash, outputOptions }) { let { fileName, name, needsCodeReference, originalFileName, referenceId } = consumedFile; // Deduplicate assets if an explicit fileName is not provided if (!fileName) { const sourceHash = getHash(source); fileName = fileNamesBySourceHash.get(sourceHash); if (!fileName) { fileName = generateAssetFileName(name, name ? [name] : [], source, originalFileName, originalFileName ? [originalFileName] : [], sourceHash, outputOptions, bundle, this.options); fileNamesBySourceHash.set(sourceHash, fileName); } } // We must not modify the original assets to avoid interaction between outputs const assetWithFileName = { ...consumedFile, fileName, source }; this.filesByReferenceId.set(referenceId, assetWithFileName); const existingAsset = bundle[fileName]; if (existingAsset?.type === 'asset') { existingAsset.needsCodeReference &&= needsCodeReference; if (name) { existingAsset.names.push(name); } if (originalFileName) { existingAsset.originalFileNames.push(originalFileName); } } else { const { options } = this; bundle[fileName] = { fileName, get name() { // Additionally, this should be non-enumerable in the next major parseAst_js.warnDeprecation('Accessing the "name" property of emitted assets in the bundle is deprecated. Use the "names" property instead.', parseAst_js.URL_GENERATEBUNDLE, false, options); return name; }, names: name ? [name] : [], needsCodeReference, get originalFileName() { // Additionally, this should be non-enumerable in the next major parseAst_js.warnDeprecation('Accessing the "originalFileName" property of emitted assets in the bundle is deprecated. Use the "originalFileNames" property instead.', parseAst_js.URL_GENERATEBUNDLE, false, options); return originalFileName; }, originalFileNames: originalFileName ? [originalFileName] : [], source, type: 'asset' }; } } finalizeAssetsWithSameSource(consumedFiles, sourceHash, { bundle, fileNamesBySourceHash, outputOptions }) { const { names, originalFileNames } = getNamesFromAssets(consumedFiles); let fileName = ''; let usedConsumedFile; let needsCodeReference = true; for (const consumedFile of consumedFiles) { needsCodeReference &&= consumedFile.needsCodeReference; const assetFileName = generateAssetFileName(consumedFile.name, names, consumedFile.source, consumedFile.originalFileName, originalFileNames, sourceHash, outputOptions, bundle, this.options); if (!fileName || assetFileName.length < fileName.length || (assetFileName.length === fileName.length && assetFileName < fileName)) { fileName = assetFileName; usedConsumedFile = consumedFile; } } fileNamesBySourceHash.set(sourceHash, fileName); for (const consumedFile of consumedFiles) { // We must not modify the original assets to avoid interaction between outputs const assetWithFileName = { ...consumedFile, fileName }; this.filesByReferenceId.set(consumedFile.referenceId, assetWithFileName); } const { options } = this; bundle[fileName] = { fileName, get name() { // Additionally, this should be non-enumerable in the next major parseAst_js.warnDeprecation('Accessing the "name" property of emitted assets in the bundle is deprecated. Use the "names" property instead.', parseAst_js.URL_GENERATEBUNDLE, false, options); return usedConsumedFile.name; }, names, needsCodeReference, get originalFileName() { // Additionally, this should be non-enumerable in the next major parseAst_js.warnDeprecation('Accessing the "originalFileName" property of emitted assets in the bundle is deprecated. Use the "originalFileNames" property instead.', parseAst_js.URL_GENERATEBUNDLE, false, options); return usedConsumedFile.originalFileName; }, originalFileNames, source: usedConsumedFile.source, type: 'asset' }; } } function getNamesFromAssets(consumedFiles) { const names = []; const originalFileNames = []; for (const { name, originalFileName } of consumedFiles) { if (typeof name === 'string') { names.push(name); } if (originalFileName) { originalFileNames.push(originalFileName); } } originalFileNames.sort(); // Sort by length first and then alphabetically so that the order is stable // and the shortest names come first names.sort((a, b) => a.length - b.length || (a > b ? 1 : a === b ? 0 : -1)); return { names, originalFileNames }; } const doNothing = () => { }; async function asyncFlatten(array) { do { array = (await Promise.all(array)).flat(Infinity); } while (array.some((v) => v?.then)); return array; } const getOnLog = (config, logLevel, printLog = defaultPrintLog) => { const { onwarn, onLog } = config; const defaultOnLog = getDefaultOnLog(printLog, onwarn); if (onLog) { const minimalPriority = parseAst_js.logLevelPriority[logLevel]; return (level, log) => onLog(level, addLogToString(log), (level, handledLog) => { if (level === parseAst_js.LOGLEVEL_ERROR) { return parseAst_js.error(normalizeLog(handledLog)); } if (parseAst_js.logLevelPriority[level] >= minimalPriority) { defaultOnLog(level, normalizeLog(handledLog)); } }); } return defaultOnLog; }; const getDefaultOnLog = (printLog, onwarn) => onwarn ? (level, log) => { if (level === parseAst_js.LOGLEVEL_WARN) { onwarn(addLogToString(log), warning => printLog(parseAst_js.LOGLEVEL_WARN, normalizeLog(warning))); } else { printLog(level, log); } } : printLog; const addLogToString = (log) => { Object.defineProperty(log, 'toString', { value: () => log.message, writable: true }); return log; }; const normalizeLog = (log) => typeof log === 'string' ? { message: log } : typeof log === 'function' ? normalizeLog(log()) : log; const defaultPrintLog = (level, { message }) => { switch (level) { case parseAst_js.LOGLEVEL_WARN: { return console.warn(message); } case parseAst_js.LOGLEVEL_DEBUG: { return console.debug(message); } default: { return console.info(message); } } }; function warnUnknownOptions(passedOptions, validOptions, optionType, log, ignoredKeys = /$./) { const validOptionSet = new Set(validOptions); const unknownOptions = Object.keys(passedOptions).filter(key => !(validOptionSet.has(key) || ignoredKeys.test(key))); if (unknownOptions.length > 0) { log(parseAst_js.LOGLEVEL_WARN, parseAst_js.logUnknownOption(optionType, unknownOptions, [...validOptionSet].sort())); } } const treeshakePresets = { recommended: { annotations: true, correctVarValueBeforeDeclaration: false, manualPureFunctions: parseAst_js.EMPTY_ARRAY, moduleSideEffects: () => true, propertyReadSideEffects: true, tryCatchDeoptimization: true, unknownGlobalSideEffects: false }, safest: { annotations: true, correctVarValueBeforeDeclaration: true, manualPureFunctions: parseAst_js.EMPTY_ARRAY, moduleSideEffects: () => true, propertyReadSideEffects: true, tryCatchDeoptimization: true, unknownGlobalSideEffects: true }, smallest: { annotations: true, correctVarValueBeforeDeclaration: false, manualPureFunctions: parseAst_js.EMPTY_ARRAY, moduleSideEffects: () => false, propertyReadSideEffects: false, tryCatchDeoptimization: false, unknownGlobalSideEffects: false } }; const jsxPresets = { preserve: { factory: null, fragment: null, importSource: null, mode: 'preserve' }, 'preserve-react': { factory: 'React.createElement', fragment: 'React.Fragment', importSource: 'react', mode: 'preserve' }, react: { factory: 'React.createElement', fragment: 'React.Fragment', importSource: 'react', mode: 'classic' }, 'react-jsx': { factory: 'React.createElement', importSource: 'react', jsxImportSource: 'react/jsx-runtime', mode: 'automatic' } }; const generatedCodePresets = { es2015: { arrowFunctions: true, constBindings: true, objectShorthand: true, reservedNamesAsProps: true, symbols: true }, es5: { arrowFunctions: false, constBindings: false, objectShorthand: false, reservedNamesAsProps: true, symbols: false } }; const objectifyOption = (value) => value && typeof value === 'object' ? value : {}; const objectifyOptionWithPresets = (presets, optionName, urlSnippet, additionalValues) => (value) => { if (typeof value === 'string') { const preset = presets[value]; if (preset) { return preset; } parseAst_js.error(parseAst_js.logInvalidOption(optionName, urlSnippet, `valid values are ${additionalValues}${parseAst_js.printQuotedStringList(Object.keys(presets))}. You can also supply an object for more fine-grained control`, value)); } return objectifyOption(value); }; const getOptionWithPreset = (value, presets, optionName, urlSnippet, additionalValues) => { const presetName = value?.preset; if (presetName) { const preset = presets[presetName]; if (preset) { return { ...preset, ...value }; } else { parseAst_js.error(parseAst_js.logInvalidOption(`${optionName}.preset`, urlSnippet, `valid values are ${parseAst_js.printQuotedStringList(Object.keys(presets))}`, presetName)); } } return objectifyOptionWithPresets(presets, optionName, urlSnippet, additionalValues)(value); }; const normalizePluginOption = async (plugins) => (await asyncFlatten([plugins])).filter(Boolean); function getLogHandler(level, code, logger, pluginName, logLevel) { if (parseAst_js.logLevelPriority[level] < parseAst_js.logLevelPriority[logLevel]) { return doNothing; } return (log, pos) => { if (pos != null) { logger(parseAst_js.LOGLEVEL_WARN, parseAst_js.logInvalidLogPosition(pluginName)); } log = normalizeLog(log); if (log.code && !log.pluginCode) { log.pluginCode = log.code; } log.code = code; log.plugin = pluginName; logger(level, log); }; } const ANONYMOUS_PLUGIN_PREFIX = 'at position '; const ANONYMOUS_OUTPUT_PLUGIN_PREFIX = 'at output position '; function createPluginCache(cache) { return { delete(id) { return delete cache[id]; }, get(id) { const item = cache[id]; if (!item) return; item[0] = 0; return item[1]; }, has(id) { const item = cache[id]; if (!item) return false; item[0] = 0; return true; }, set(id, value) { cache[id] = [0, value]; } }; } function getTrackedPluginCache(pluginCache, onUse) { return { delete(id) { onUse(); return pluginCache.delete(id); }, get(id) { onUse(); return pluginCache.get(id); }, has(id) { onUse(); return pluginCache.has(id); }, set(id, value) { onUse(); return pluginCache.set(id, value); } }; } const NO_CACHE = { delete() { return false; }, get() { return undefined; }, has() { return false; }, set() { } }; function uncacheablePluginError(pluginName) { if (pluginName.startsWith(ANONYMOUS_PLUGIN_PREFIX) || pluginName.startsWith(ANONYMOUS_OUTPUT_PLUGIN_PREFIX)) { return parseAst_js.error(parseAst_js.logAnonymousPluginCache()); } return parseAst_js.error(parseAst_js.logDuplicatePluginName(pluginName)); } function getCacheForUncacheablePlugin(pluginName) { return { delete() { return uncacheablePluginError(pluginName); }, get() { return uncacheablePluginError(pluginName); }, has() { return uncacheablePluginError(pluginName); }, set() { return uncacheablePluginError(pluginName); } }; } function getPluginContext(plugin, pluginCache, graph, options, fileEmitter, existingPluginNames) { const { logLevel, onLog } = options; let cacheable = true; if (typeof plugin.cacheKey !== 'string') { if (plugin.name.startsWith(ANONYMOUS_PLUGIN_PREFIX) || plugin.name.startsWith(ANONYMOUS_OUTPUT_PLUGIN_PREFIX) || existingPluginNames.has(plugin.name)) { cacheable = false; } else { existingPluginNames.add(plugin.name); } } let cacheInstance; if (!pluginCache) { cacheInstance = NO_CACHE; } else if (cacheable) { const cacheKey = plugin.cacheKey || plugin.name; cacheInstance = createPluginCache(pluginCache[cacheKey] || (pluginCache[cacheKey] = Object.create(null))); } else { cacheInstance = getCacheForUncacheablePlugin(plugin.name); } return { addWatchFile(id) { graph.watchFiles[id] = true; }, cache: cacheInstance, debug: getLogHandler(parseAst_js.LOGLEVEL_DEBUG, 'PLUGIN_LOG', onLog, plugin.name, logLevel), emitFile: fileEmitter.emitFile.bind(fileEmitter), error(error_) { return parseAst_js.error(parseAst_js.logPluginError(normalizeLog(error_), plugin.name)); }, getFileName: fileEmitter.getFileName, getModuleIds: () => graph.modulesById.keys(), getModuleInfo: graph.getModuleInfo, getWatchFiles: () => Object.keys(graph.watchFiles), info: getLogHandler(parseAst_js.LOGLEVEL_INFO, 'PLUGIN_LOG', onLog, plugin.name, logLevel), load(resolvedId) { return graph.moduleLoader.preloadModule(resolvedId); }, meta: { rollupVersion: version, watchMode: graph.watchMode }, parse: parseAst_js.parseAst, resolve(source, importer, { attributes, custom, isEntry, skipSelf } = parseAst_js.BLANK) { skipSelf ??= true; return graph.moduleLoader.resolveId(source, importer, custom, isEntry, attributes || parseAst_js.EMPTY_OBJECT, skipSelf ? [{ importer, plugin, source }] : null); }, setAssetSource: fileEmitter.setAssetSource, warn: getLogHandler(parseAst_js.LOGLEVEL_WARN, 'PLUGIN_WARNING', onLog, plugin.name, logLevel) }; } // This will make sure no input hook is omitted const inputHookNames = { buildEnd: 1, buildStart: 1, closeBundle: 1, closeWatcher: 1, load: 1, moduleParsed: 1, onLog: 1, options: 1, resolveDynamicImport: 1, resolveId: 1, shouldTransformCachedModule: 1, transform: 1, watchChange: 1 }; const inputHooks = Object.keys(inputHookNames); class PluginDriver { constructor(graph, options, userPlugins, pluginCache, basePluginDriver) { this.graph = graph; this.options = options; this.pluginCache = pluginCache; this.sortedPlugins = new Map(); this.unfulfilledActions = new Set(); this.fileEmitter = new FileEmitter(graph, options, basePluginDriver && basePluginDriver.fileEmitter); this.emitFile = this.fileEmitter.emitFile.bind(this.fileEmitter); this.getFileName = this.fileEmitter.getFileName.bind(this.fileEmitter); this.finaliseAssets = this.fileEmitter.finaliseAssets.bind(this.fileEmitter); this.setChunkInformation = this.fileEmitter.setChunkInformation.bind(this.fileEmitter); this.setOutputBundle = this.fileEmitter.setOutputBundle.bind(this.fileEmitter); this.plugins = [...(basePluginDriver ? basePluginDriver.plugins : []), ...userPlugins]; const existingPluginNames = new Set(); this.pluginContexts = new Map(this.plugins.map(plugin => [ plugin, getPluginContext(plugin, pluginCache, graph, options, this.fileEmitter, existingPluginNames) ])); if (basePluginDriver) { for (const plugin of userPlugins) { for (const hook of inputHooks) { if (hook in plugin) { options.onLog(parseAst_js.LOGLEVEL_WARN, parseAst_js.logInputHookInOutputPlugin(plugin.name, hook)); } } } } } createOutputPluginDriver(plugins) { return new PluginDriver(this.graph, this.options, plugins, this.pluginCache, this); } getUnfulfilledHookActions() { return this.unfulfilledActions; } // chains, first non-null result stops and returns hookFirst(hookName, parameters, replaceContext, skipped) { return this.hookFirstAndGetPlugin(hookName, parameters, replaceContext, skipped).then(result => result && result[0]); } // chains, first non-null result stops and returns result and last plugin async hookFirstAndGetPlugin(hookName, parameters, replaceContext, skipped) { for (const plugin of this.getSortedPlugins(hookName)) { if (skipped?.has(plugin)) continue; const result = await this.runHook(hookName, parameters, plugin, replaceContext); if (result != null) return [result, plugin]; } return null; } // chains synchronously, first non-null result stops and returns hookFirstSync(hookName, parameters, replaceContext) { for (const plugin of this.getSortedPlugins(hookName)) { const result = this.runHookSync(hookName, parameters, plugin, replaceContext); if (result != null) return result; } return null; } // parallel, ignores returns async hookParallel(hookName, parameters, replaceContext) { const parallelPromises = []; for (const plugin of this.getSortedPlugins(hookName)) { if (plugin[hookName].sequential) { await Promise.all(parallelPromises); parallelPromises.length = 0; await this.runHook(hookName, parameters, plugin, replaceContext); } else { parallelPromises.push(this.runHook(hookName, parameters, plugin, replaceContext)); } } await Promise.all(parallelPromises); } // chains, reduces returned value, handling the reduced value as the first hook argument hookReduceArg0(hookName, [argument0, ...rest], reduce, replaceContext) { let promise = Promise.resolve(argument0); for (const plugin of this.getSortedPlugins(hookName)) { promise = promise.then(argument0 => this.runHook(hookName, [argument0, ...rest], plugin, replaceContext).then(result => reduce.call(this.pluginContexts.get(plugin), argument0, result, plugin))); } return promise; } // chains synchronously, reduces returned value, handling the reduced value as the first hook argument hookReduceArg0Sync(hookName, [argument0, ...rest], reduce, replaceContext) { for (const plugin of this.getSortedPlugins(hookName)) { const parameters = [argument0, ...rest]; const result = this.runHookSync(hookName, parameters, plugin, replaceContext); argument0 = reduce.call(this.pluginContexts.get(plugin), argument0, result, plugin); } return argument0; } // chains, reduces returned value to type string, handling the reduced value separately. permits hooks as values. async hookReduceValue(hookName, initialValue, parameters, reducer) { const results = []; const parallelResults = []; for (const plugin of this.getSortedPlugins(hookName, validateAddonPluginHandler)) { if (plugin[hookName].sequential) { results.push(...(await Promise.all(parallelResults))); parallelResults.length = 0; results.push(await this.runHook(hookName, parameters, plugin)); } else { parallelResults.push(this.runHook(hookName, parameters, plugin)); } } results.push(...(await Promise.all(parallelResults))); return results.reduce(reducer, await initialValue); } // chains synchronously, reduces returned value to type T, handling the reduced value separately. permits hooks as values. hookReduceValueSync(hookName, initialValue, parameters, reduce, replaceContext) { let accumulator = initialValue; for (const plugin of this.getSortedPlugins(hookName)) { const result = this.runHookSync(hookName, parameters, plugin, replaceContext); accumulator = reduce.call(this.pluginContexts.get(plugin), accumulator, result, plugin); } return accumulator; } // chains, ignores returns hookSeq(hookName, parameters, replaceContext) { let promise = Promise.resolve(); for (const plugin of this.getSortedPlugins(hookName)) { promise = promise.then(() => this.runHook(hookName, parameters, plugin, replaceContext)); } return promise.then(noReturn); } getSortedPlugins(hookName, validateHandler) { return getOrCreate(this.sortedPlugins, hookName, () => getSortedValidatedPlugins(hookName, this.plugins, validateHandler)); } // Implementation signature runHook(hookName, parameters, plugin, replaceContext) { // We always filter for plugins that support the hook before running it const hook = plugin[hookName]; const handler = typeof hook === 'object' ? hook.handler : hook; let context = this.pluginContexts.get(plugin); if (replaceContext) { context = replaceContext(context, plugin); } let action = null; return Promise.resolve() .then(() => { if (typeof handler !== 'function') { return handler; } // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type const hookResult = handler.apply(context, parameters); if (!hookResult?.then) { // short circuit for non-thenables and non-Promises return hookResult; } // Track pending hook actions to properly error out when // unfulfilled promises cause rollup to abruptly and confusingly // exit with a successful 0 return code but without producing any // output, errors or warnings. action = [plugin.name, hookName, parameters]; this.unfulfilledActions.add(action); // Although it would be more elegant to just return hookResult here // and put the .then() handler just above the .catch() handler below, // doing so would subtly change the defacto async event dispatch order // which at least one test and some plugins in the wild may depend on. return Promise.resolve(hookResult).then(result => { // action was fulfilled this.unfulfilledActions.delete(action); return result; }); }) .catch(error_ => { if (action !== null) { // action considered to be fulfilled since error being handled this.unfulfilledActions.delete(action); } return parseAst_js.error(parseAst_js.logPluginError(error_, plugin.name, { hook: hookName })); }); } /** * Run a sync plugin hook and return the result. * @param hookName Name of the plugin hook. Must be in `PluginHooks`. * @param args Arguments passed to the plugin hook. * @param plugin The acutal plugin * @param replaceContext When passed, the plugin context can be overridden. */ runHookSync(hookName, parameters, plugin, replaceContext) { const hook = plugin[hookName]; const handler = typeof hook === 'object' ? hook.handler : hook; let context = this.pluginContexts.get(plugin); if (replaceContext) { context = replaceContext(context, plugin); } try { // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type return handler.apply(context, parameters); } catch (error_) { return parseAst_js.error(parseAst_js.logPluginError(error_, plugin.name, { hook: hookName })); } } } function getSortedValidatedPlugins(hookName, plugins, validateHandler = validateFunctionPluginHandler) { const pre = []; const normal = []; const post = []; for (const plugin of plugins) { const hook = plugin[hookName]; if (hook) { if (typeof hook === 'object') { validateHandler(hook.handler, hookName, plugin); if (hook.order === 'pre') { pre.push(plugin); continue; } if (hook.order === 'post') { post.push(plugin); continue; } } else { validateHandler(hook, hookName, plugin); } normal.push(plugin); } } return [...pre, ...normal, ...post]; } function validateFunctionPluginHandler(handler, hookName, plugin) { if (typeof handler !== 'function') { parseAst_js.error(parseAst_js.logInvalidFunctionPluginHook(hookName, plugin.name)); } } function validateAddonPluginHandler(handler, hookName, plugin) { if (typeof handler !== 'string' && typeof handler !== 'function') { return parseAst_js.error(parseAst_js.logInvalidAddonPluginHook(hookName, plugin.name)); } } function noReturn() { } function getLogger(plugins, onLog, watchMode, logLevel) { plugins = getSortedValidatedPlugins('onLog', plugins); const minimalPriority = parseAst_js.logLevelPriority[logLevel]; const logger = (level, log, skipped = parseAst_js.EMPTY_SET) => { parseAst_js.augmentLogMessage(log); const logPriority = parseAst_js.logLevelPriority[level]; if (logPriority < minimalPriority) { return; } for (const plugin of plugins) { if (skipped.has(plugin)) continue; const { onLog: pluginOnLog } = plugin; const getLogHandler = (level) => { if (parseAst_js.logLevelPriority[level] < minimalPriority) { return doNothing; } return log => logger(level, normalizeLog(log), new Set(skipped).add(plugin)); }; const handler = 'handler' in pluginOnLog ? pluginOnLog.handler : pluginOnLog; if (handler.call({ debug: getLogHandler(parseAst_js.LOGLEVEL_DEBUG), error: (log) => parseAst_js.error(normalizeLog(log)), info: getLogHandler(parseAst_js.LOGLEVEL_INFO), meta: { rollupVersion: version, watchMode }, warn: