UNPKG

bunchee

Version:

zero config bundler for js/ts/jsx libraries

1,292 lines (1,271 loc) 101 kB
Object.defineProperty(exports, '__esModule', { value: true }); var fsp = require('fs/promises'); var fs = require('fs'); var path = require('path'); require('pretty-bytes'); var require$$0 = require('tty'); var tinyglobby = require('tinyglobby'); var module$1 = require('module'); var rollup = require('rollup'); var pluginWasm = require('@rollup/plugin-wasm'); var rollupPluginSwc3 = require('rollup-plugin-swc3'); var commonjs = require('@rollup/plugin-commonjs'); var json = require('@rollup/plugin-json'); var pluginNodeResolve = require('@rollup/plugin-node-resolve'); var replace = require('@rollup/plugin-replace'); var preserveDirectives = require('rollup-preserve-directives'); var MagicString = require('magic-string'); var CleanCSS = require('clean-css'); var pluginutils = require('@rollup/pluginutils'); var child_process = require('child_process'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var fsp__default = /*#__PURE__*/_interopDefault(fsp); var fs__default = /*#__PURE__*/_interopDefault(fs); var path__default = /*#__PURE__*/_interopDefault(path); var require$$0__default = /*#__PURE__*/_interopDefault(require$$0); var commonjs__default = /*#__PURE__*/_interopDefault(commonjs); var json__default = /*#__PURE__*/_interopDefault(json); var replace__default = /*#__PURE__*/_interopDefault(replace); var preserveDirectives__default = /*#__PURE__*/_interopDefault(preserveDirectives); var MagicString__default = /*#__PURE__*/_interopDefault(MagicString); var CleanCSS__default = /*#__PURE__*/_interopDefault(CleanCSS); function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var picocolors = {exports: {}}; var hasRequiredPicocolors; function requirePicocolors () { if (hasRequiredPicocolors) return picocolors.exports; hasRequiredPicocolors = 1; let tty = require$$0__default.default; let isColorSupported = !("NO_COLOR" in process.env || process.argv.includes("--no-color")) && ("FORCE_COLOR" in process.env || process.argv.includes("--color") || process.platform === "win32" || tty.isatty(1) && process.env.TERM !== "dumb" || "CI" in process.env); let formatter = (open, close, replace = open)=>(input)=>{ let string = "" + input; let index = string.indexOf(close, open.length); return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close; }; let replaceClose = (string, close, replace, index)=>{ let start = string.substring(0, index) + replace; let end = string.substring(index + close.length); let nextIndex = end.indexOf(close); return ~nextIndex ? start + replaceClose(end, close, replace, nextIndex) : start + end; }; let createColors = (enabled = isColorSupported)=>({ isColorSupported: enabled, reset: enabled ? (s)=>`\x1b[0m${s}\x1b[0m` : String, bold: enabled ? formatter("\x1b[1m", "\x1b[22m", "\x1b[22m\x1b[1m") : String, dim: enabled ? formatter("\x1b[2m", "\x1b[22m", "\x1b[22m\x1b[2m") : String, italic: enabled ? formatter("\x1b[3m", "\x1b[23m") : String, underline: enabled ? formatter("\x1b[4m", "\x1b[24m") : String, inverse: enabled ? formatter("\x1b[7m", "\x1b[27m") : String, hidden: enabled ? formatter("\x1b[8m", "\x1b[28m") : String, strikethrough: enabled ? formatter("\x1b[9m", "\x1b[29m") : String, black: enabled ? formatter("\x1b[30m", "\x1b[39m") : String, red: enabled ? formatter("\x1b[31m", "\x1b[39m") : String, green: enabled ? formatter("\x1b[32m", "\x1b[39m") : String, yellow: enabled ? formatter("\x1b[33m", "\x1b[39m") : String, blue: enabled ? formatter("\x1b[34m", "\x1b[39m") : String, magenta: enabled ? formatter("\x1b[35m", "\x1b[39m") : String, cyan: enabled ? formatter("\x1b[36m", "\x1b[39m") : String, white: enabled ? formatter("\x1b[37m", "\x1b[39m") : String, gray: enabled ? formatter("\x1b[90m", "\x1b[39m") : String, bgBlack: enabled ? formatter("\x1b[40m", "\x1b[49m") : String, bgRed: enabled ? formatter("\x1b[41m", "\x1b[49m") : String, bgGreen: enabled ? formatter("\x1b[42m", "\x1b[49m") : String, bgYellow: enabled ? formatter("\x1b[43m", "\x1b[49m") : String, bgBlue: enabled ? formatter("\x1b[44m", "\x1b[49m") : String, bgMagenta: enabled ? formatter("\x1b[45m", "\x1b[49m") : String, bgCyan: enabled ? formatter("\x1b[46m", "\x1b[49m") : String, bgWhite: enabled ? formatter("\x1b[47m", "\x1b[49m") : String }); picocolors.exports = createColors(); picocolors.exports.createColors = createColors; return picocolors.exports; } var picocolorsExports = /*@__PURE__*/ requirePicocolors(); var pc = /*@__PURE__*/getDefaultExportFromCjs(picocolorsExports); const defaultColorFn = (text)=>text; function color(prefixColor) { return pc.isColorSupported ? pc[prefixColor] : defaultColorFn; } // Store original console methods ({ log: console.log.bind(console), warn: console.warn.bind(console), error: console.error.bind(console), info: console.info.bind(console) }); const logger = { log (...arg) { console.log(...arg); }, warn (...arg) { console.warn(color('yellow')('!'), ...arg); }, error (...arg) { console.error(color('red')('⨯'), ...arg); }, info (...arg) { console.log(color('green')('✓'), ...arg); } }; const availableExtensions = new Set([ 'js', 'cjs', 'mjs', 'jsx', 'ts', 'tsx', 'cts', 'mts' ]); const nodeResolveExtensions = [ '.mjs', '.cjs', '.js', '.json', '.node', '.jsx' ]; // You can find the list of runtime keys here: // https://runtime-keys.proposal.wintercg.org/ const runtimeExportConventions = new Set([ 'electron', 'react-server', 'react-native', 'edge-light', 'node', 'deno', 'bun', 'workerd', // Browser only 'browser' ]); const runtimeExportConventionsFallback = new Map([ // ESM only runtime [ 'workerd', [ 'import', 'default' ] ], [ 'edge-light', [ 'import', 'default' ] ], // Fallback to default when unsure [ 'electron', [ 'default' ] ], [ 'react-server', [ 'default' ] ], [ 'react-native', [ 'default' ] ], [ 'node', [ 'default' ] ], [ 'deno', [ 'default' ] ], [ 'bun', [ 'default' ] ], [ 'development', [ 'default' ] ], [ 'production', [ 'default' ] ], [ 'browser', [ 'import', 'require', 'default' ] ] ]); const optimizeConventions = new Set([ 'development', 'production' ]); const specialExportConventions = new Set([ ...runtimeExportConventions, ...optimizeConventions ]); const availableESExtensionsRegex = /\.(m|c)?[jt]sx?$/; const SRC = 'src'; const dtsExtensionsMap = { js: 'd.ts', cjs: 'd.cts', mjs: 'd.mts' }; const disabledWarnings = new Set([ 'EMPTY_BUNDLE', 'MIXED_EXPORTS', 'PREFER_NAMED_EXPORTS', 'UNRESOLVED_IMPORT', 'THIS_IS_UNDEFINED', 'INVALID_ANNOTATION', 'UNUSED_EXTERNAL_IMPORT' ]); const tsExtensions = new Set([ 'ts', 'tsx', 'cts', 'mts' ]); const DEFAULT_TS_CONFIG = { compilerOptions: { target: 'ES2022', module: 'ESNext', moduleResolution: 'bundler' }, include: [ 'src' ] }; const BINARY_TAG = '$binary'; const PRIVATE_GLOB_PATTERN = '**/_*/**'; const TESTS_GLOB_PATTERN = '**/{__tests__/**,__mocks__/**,*.{test,spec}.*}'; function posixRelativify(path) { return path.startsWith('.') ? path : `./${path}`; } // Example: ./src/util/foo.development.ts -> foo.development // Example: ./src/util/foo.react-server.ts -> foo.react-server const baseNameWithoutExtension = (filePath)=>{ return path__default.default.basename(filePath, path__default.default.extname(filePath)); }; function validateEntryFiles(entryFiles) { const fileBasePaths = new Set(); const duplicatePaths = new Set(); for (const filePath of entryFiles){ // Check if there are multiple files with the same base name const filePathWithoutExt = filePath.slice(0, -path__default.default.extname(filePath).length).replace(/\\/g, '/'); const segments = filePathWithoutExt.split('/'); let lastSegment = segments[segments.length - 1]; while(lastSegment && (lastSegment === 'index' || lastSegment === '')){ segments.pop(); lastSegment = segments[segments.length - 1]; } const fileBasePath = segments.join('/'); if (fileBasePaths.has(fileBasePath)) { duplicatePaths.add(// Add a dot if the base name is empty, 'foo' -> './foo', '' -> '.' './' + filePath.replace(/\\/g, '/')); } fileBasePaths.add(fileBasePath); } if (duplicatePaths.size > 0) { throw new Error(`Conflicted entry files found for entries: ${[ ...duplicatePaths ].join(', ')}`); } } function exit(err) { logger.error(err); throw new Error(err) ; } async function getPackageMeta(cwd) { const pkgFilePath = path__default.default.resolve(cwd, 'package.json'); let targetPackageJson = {}; try { targetPackageJson = JSON.parse(await fsp__default.default.readFile(pkgFilePath, { encoding: 'utf-8' })); } catch (_) {} return targetPackageJson; } function isTypescriptFile(filename) { const ext = path__default.default.extname(filename).slice(1); return tsExtensions.has(ext); } function fileExists(filePath) { return fs__default.default.existsSync(filePath); } const isNotNull = (n)=>Boolean(n); function resolveSourceFile(cwd, filename) { return path__default.default.resolve(cwd, SRC, filename); } function findSourceEntryFile(cwd, exportPath, exportTypeSuffix, ext) { const filename = resolveSourceFile(cwd, `${exportPath}${exportTypeSuffix ? `.${exportTypeSuffix}` : ''}.${ext}`); if (fileExists(filename)) { return filename; } const subFolderIndexFilename = resolveSourceFile(cwd, `${exportPath}/index${exportTypeSuffix ? `.${exportTypeSuffix}` : ''}.${ext}`); try { if (fileExists(subFolderIndexFilename)) { return subFolderIndexFilename; } } catch {} return undefined; } // Map '.' -> './index.[ext]' // Map './lite' -> './lite.[ext]' // Return undefined if no match or if it's package.json exports async function getSourcePathFromExportPath(cwd, exportPath, exportType) { for (const ext of availableExtensions){ // ignore package.json if (exportPath === '/package.json') return; if (exportPath === '.') exportPath = './index'; // Find convention-based source file for specific export types // $binary represents `pkg.bin` if (runtimeExportConventions.has(exportType) && exportType !== BINARY_TAG) { const filename = await findSourceEntryFile(cwd, exportPath, exportType, ext); if (filename) return filename; } const [, optimizeType] = exportType.split('.'); if (optimizeConventions.has(optimizeType)) { const filename = await findSourceEntryFile(cwd, exportPath, optimizeType, ext); if (filename) return filename; } const filename = await findSourceEntryFile(cwd, exportPath, null, ext); if (filename) return filename; } return; } // TODO: add unit test // Unlike path.basename, forcedly removing extension function filePathWithoutExtension(filePath) { if (!filePath) return ''; const lastDotIndex = filePath.lastIndexOf('.'); const lastSlashIndex = filePath.lastIndexOf('/'); if (lastDotIndex !== -1 && lastDotIndex > lastSlashIndex) { return filePath.slice(0, filePath.indexOf('.', lastSlashIndex + 1)); } return filePath; } const hasCjsExtension = (filename)=>path__default.default.extname(filename) === '.cjs'; const getMainFieldExportType = (pkg)=>{ const isEsmPkg = isESModulePackage(pkg.type); const mainExportType = isEsmPkg && pkg.main ? hasCjsExtension(pkg.main) ? 'require' : 'import' : 'require'; return mainExportType; }; const isTestFile = (filename)=>/\.(test|spec)$/.test(baseNameWithoutExtension(filename)); function joinRelativePath(...segments) { // Normalize to forward slashes for cross-platform compatibility // Export paths in package.json always use POSIX-style paths let result = path__default.default.posix.join(...segments); // If the first segment starts with '.', ensure the result does too. if (segments[0] === '.' && !result.startsWith('.')) { result = './' + result; } return result; } function isESModulePackage(packageType) { return packageType === 'module'; } async function removeDir(dirPath) { try { const dirStat = await fsp__default.default.stat(dirPath); if (dirStat.isDirectory()) { await fsp__default.default.rm(dirPath, { recursive: true, force: true }); } } catch (err) { if (err.code !== 'ENOENT') { throw err; } } } const removedDirs = new Set(); async function removeOutputDir(output, cwd) { const dir = output.dir; if (dir && // not equal to cwd dir !== cwd && // not equal to src/ dir dir !== path__default.default.resolve(cwd, SRC) && !removedDirs.has(dir)) { await removeDir(dir); removedDirs.add(dir); } } function isBinExportPath(exportPath) { return exportPath === BINARY_TAG || exportPath.startsWith(BINARY_TAG + '/'); } // shared.ts -> ./shared // shared.<export condition>.ts -> ./shared.<export condition> // index.ts -> ./index // index.development.ts -> ./index.development // foo/index.ts -> ./foo function sourceFilenameToExportFullPath(filename) { const ext = path__default.default.extname(filename); const exportPath = filename.slice(0, -ext.length); return posixRelativify(exportPath); } function normalizePath(filePath) { return filePath.replace(/\\/g, '/'); } /** * Check if an export key contains a wildcard pattern */ function hasWildcardPattern(exportKey) { return exportKey.includes('*'); } /** * Replace wildcard in output path with matched subpath * Example: "./dist/features/*.js" with "foo" -> "./dist/features/foo.js" */ function substituteWildcardInPath(outputPath, matchedSubpath) { return outputPath.replace(/\*/g, matchedSubpath); } /** * Expand a wildcard export pattern by finding matching source files * Returns a map of concrete export paths to their matched subpaths * Example: "./features/*" with files ["foo.ts", "bar.ts"] in src/features/ * -> { "./features/foo": "foo", "./features/bar": "bar" } */ async function expandWildcardPattern(wildcardPattern, cwd) { const expanded = new Map(); const sourceDir = path__default.default.join(cwd, SRC); if (!fileExists(sourceDir)) { return expanded; } // Convert wildcard pattern to glob pattern // "./features/*" -> "features/*" const cleanPattern = wildcardPattern.replace(/^\.\//, ''); // Extract the base path before the wildcard // "features/*" -> "features" const basePathParts = cleanPattern.split('*'); const basePath = basePathParts[0].replace(/\/$/, ''); // Build glob pattern to match files // "features/*" -> "features/*.{js,ts,tsx,...}" const extPattern = `{${[ ...availableExtensions ].join(',')}}`; const globPatterns = [ `${cleanPattern}.${extPattern}`, `${cleanPattern}/index.${extPattern}` ]; let matches = []; try { matches = await tinyglobby.glob(globPatterns, { cwd: sourceDir, ignore: [ PRIVATE_GLOB_PATTERN, TESTS_GLOB_PATTERN ], expandDirectories: false }); } catch (error) { logger.warn(`Failed to expand wildcard pattern ${wildcardPattern}: ${error}`); return expanded; } for (const match of matches){ // Extract the matched subpath // "features/foo.ts" -> "foo" // "features/bar/index.ts" -> "bar" const relativePath = normalizePath(match); const ext = path__default.default.extname(relativePath); const withoutExt = relativePath.slice(0, -ext.length); // Remove the base path to get just the matched part // "features/foo" -> "foo" (when basePath is "features") let matchedPart = withoutExt; if (basePath && matchedPart.startsWith(basePath + '/')) { matchedPart = matchedPart.slice(basePath.length + 1); } else if (basePath && matchedPart === basePath) { continue; } // Handle index files let matchedSubpath; if (matchedPart.endsWith('/index')) { matchedSubpath = matchedPart.slice(0, -6); // Remove "/index" // If there's still a path, take the last segment const lastSlash = matchedSubpath.lastIndexOf('/'); matchedSubpath = lastSlash >= 0 ? matchedSubpath.slice(lastSlash + 1) : matchedSubpath; } else { // Take the first segment (what matches the *) const firstSlash = matchedPart.indexOf('/'); matchedSubpath = firstSlash >= 0 ? matchedPart.slice(0, firstSlash) : matchedPart; } // Build the concrete export path // "./features/*" + "foo" -> "./features/foo" const concreteExportPath = basePath ? `./${basePath}/${matchedSubpath}` : `./${matchedSubpath}`; expanded.set(concreteExportPath, matchedSubpath); } return expanded; } /** * Process export value for wildcard patterns, substituting wildcards in output paths */ async function processWildcardExportValue(exportValue, originalExportKey, currentPath, exportTypes, exportToDist, matchedSubpath) { // End of searching, export value is file path. // <export key>: <export value> (string) if (typeof exportValue === 'string') { const composedTypes = new Set(exportTypes); const exportType = originalExportKey.startsWith('.') ? 'default' : originalExportKey; composedTypes.add(exportType); const exportInfo = exportToDist.get(mapExportFullPath(currentPath)); const exportCondition = Array.from(composedTypes).join('.'); // Substitute wildcard in output path const substitutedPath = substituteWildcardInPath(exportValue, matchedSubpath); if (!exportInfo) { const outputConditionPair = [ substitutedPath, exportCondition ]; addToExportDistMap(exportToDist, currentPath, [ outputConditionPair ]); } else { exportInfo.push([ substitutedPath, exportCondition ]); } return; } const exportKeys = Object.keys(exportValue); for (const exportKey of exportKeys){ // Clone the set to avoid modifying the parent set const childExports = new Set(exportTypes); // Normalize child export value to a map const childExportValue = exportValue[exportKey]; // Substitute wildcard in nested string values let processedChildValue = childExportValue; if (typeof childExportValue === 'string') { processedChildValue = substituteWildcardInPath(childExportValue, matchedSubpath); } else if (typeof childExportValue === 'object' && childExportValue !== null) { // Recursively process nested objects const processed = {}; for (const [key, value] of Object.entries(childExportValue)){ if (typeof value === 'string') { processed[key] = substituteWildcardInPath(value, matchedSubpath); } else if (value !== null && value !== undefined) { processed[key] = value; } } processedChildValue = processed; } // Visit export path: ./subpath, ./subpath2, ... if (exportKey.startsWith('.')) { const childPath = joinRelativePath(currentPath, exportKey); await processWildcardExportValue(processedChildValue, exportKey, childPath, childExports, exportToDist, matchedSubpath); } else { // Visit export type: import, require, ... childExports.add(exportKey); await processWildcardExportValue(processedChildValue, exportKey, currentPath, childExports, exportToDist, matchedSubpath); } } } function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exportToDist) { // End of searching, export value is file path. // <export key>: <export value> (string) if (typeof exportValue === 'string') { const composedTypes = new Set(exportTypes); const exportType = exportKey.startsWith('.') ? 'default' : exportKey; composedTypes.add(exportType); const exportInfo = exportToDist.get(mapExportFullPath(currentPath)); const exportCondition = Array.from(composedTypes).join('.'); if (!exportInfo) { const outputConditionPair = [ exportValue, exportCondition ]; addToExportDistMap(exportToDist, currentPath, [ outputConditionPair ]); } else { exportInfo.push([ exportValue, exportCondition ]); } return; } const exportKeys = Object.keys(exportValue); for (const exportKey of exportKeys){ // Clone the set to avoid modifying the parent set const childExports = new Set(exportTypes); // Normalize child export value to a map const childExportValue = exportValue[exportKey]; // Visit export path: ./subpath, ./subpath2, ... if (exportKey.startsWith('.')) { const childPath = joinRelativePath(currentPath, exportKey); collectExportPath(childExportValue, exportKey, childPath, childExports, exportToDist); } else { // Visit export type: import, require, ... childExports.add(exportKey); collectExportPath(childExportValue, exportKey, currentPath, childExports, exportToDist); } } } const mapExportFullPath = (exportPath)=>exportPath === '.' ? './index' : exportPath; function addToExportDistMap(exportToDist, exportPath, outputConditionPairs) { const fullPath = mapExportFullPath(exportPath); const existingExportInfo = exportToDist.get(fullPath); if (!existingExportInfo) { exportToDist.set(fullPath, outputConditionPairs); } else { existingExportInfo.push(...outputConditionPairs); } } /** * parseExports - parse package.exports field and other fields like main,module to a map * * map from export path to output path and export conditions * * exportToDist: { * './index': { development: ..., default: ... } * './index.react-server': { development: ..., default: ... } * } */ async function parseExports(pkg, cwd) { var _pkg_exports; const exportsField = (_pkg_exports = pkg.exports) != null ? _pkg_exports : {}; var _pkg_bin; const bins = (_pkg_bin = pkg.bin) != null ? _pkg_bin : {}; const exportToDist = new Map(); const isEsmPkg = isESModulePackage(pkg.type); const defaultCondition = isEsmPkg ? 'import' : 'require'; let currentPath = '.'; if (typeof exportsField === 'string') { const outputConditionPair = [ exportsField, defaultCondition ]; addToExportDistMap(exportToDist, currentPath, [ outputConditionPair ]); } else { // keys means unknown if they're relative path or export type const exportConditionKeys = Object.keys(exportsField); for (const exportKey of exportConditionKeys){ const exportValue = exportsField[exportKey]; const exportTypes = new Set(); const isExportPath = exportKey.startsWith('.'); // Handle wildcard patterns if (isExportPath && hasWildcardPattern(exportKey) && cwd) { // Expand wildcard pattern to concrete exports const expanded = await expandWildcardPattern(exportKey, cwd); for (const [concreteExportPath, matchedSubpath] of expanded){ const childPath = joinRelativePath(currentPath, concreteExportPath); // Process the export value and substitute wildcards in output paths await processWildcardExportValue(exportValue, exportKey, childPath, exportTypes, exportToDist, matchedSubpath); } continue; } const childPath = isExportPath ? joinRelativePath(currentPath, exportKey) : currentPath; if (!isExportPath) { exportTypes.add(exportKey); } collectExportPath(exportValue, exportKey, childPath, exportTypes, exportToDist); } } if (typeof bins === 'string') { const outputConditionPair = [ bins, defaultCondition ]; addToExportDistMap(exportToDist, BINARY_TAG, [ outputConditionPair ]); } else { for (const binName of Object.keys(bins)){ const binDistPath = bins[binName]; const exportType = getExportTypeFromFile(binDistPath, pkg.type); const exportPath = path.posix.join(BINARY_TAG, binName); const outputConditionPair = [ binDistPath, exportType ]; addToExportDistMap(exportToDist, exportPath, [ outputConditionPair ]); } } // Handle package.json global exports fields if (pkg.main || pkg.module || pkg.types) { const mainExportPath = pkg.main; const moduleExportPath = pkg.module; const typesEntryPath = pkg.types; addToExportDistMap(exportToDist, './index', [ Boolean(mainExportPath) && [ mainExportPath, getMainFieldExportType(pkg) ], Boolean(moduleExportPath) && [ moduleExportPath, 'module' ], Boolean(typesEntryPath) && [ typesEntryPath, 'types' ] ].filter(Boolean)); } return exportToDist; } function isEsmExportName(name, ext) { return [ 'import', 'module', 'module-sync' ].includes(name) || ext === 'mjs'; } function isCjsExportName(pkg, exportCondition, ext) { const isESModule = isESModulePackage(pkg.type); const isCjsCondition = [ 'require', 'main' ].includes(exportCondition); const isNotEsmExportName = !isEsmExportName(exportCondition, ext); return !isESModule && isNotEsmExportName && (ext !== 'mjs' || isCjsCondition) || ext === 'cjs'; } function getFileExportType(composedTypes) { return composedTypes.split('.').pop(); } function getExportsDistFilesOfCondition(pkg, parsedExportCondition, cwd, dts) { const dist = []; const exportConditionNames = Object.keys(parsedExportCondition.export); const uniqueFiles = new Set(); for (const exportCondition of exportConditionNames){ const exportType = getFileExportType(exportCondition); // Filter out non-types field when generating types jobs if (dts && exportType !== 'types') { continue; } // Filter out types field when generating asset jobs if (!dts && exportType === 'types') { continue; } const filePath = parsedExportCondition.export[exportCondition]; const ext = path.extname(filePath).slice(1); const relativePath = parsedExportCondition.export[exportCondition]; const distFile = path.resolve(cwd, relativePath); const format = isCjsExportName(pkg, exportCondition, ext) ? 'cjs' : 'esm'; if (uniqueFiles.has(distFile)) { continue; } uniqueFiles.add(distFile); dist.push({ format, file: distFile, exportCondition }); } return dist; } function getExportFileTypePath(absoluteJsBundlePath) { const dirName = path.dirname(absoluteJsBundlePath); const baseName = baseNameWithoutExtension(absoluteJsBundlePath); const ext = path.extname(absoluteJsBundlePath).slice(1); const typeExtension = dtsExtensionsMap[ext]; return normalizePath(path.join(dirName, baseName + '.' + typeExtension)); } function getExportTypeFromFile(filename, pkgType) { const isESModule = isESModulePackage(pkgType); const isCjsExt = filename.endsWith('.cjs'); const isEsmExt = filename.endsWith('.mjs'); const exportType = isEsmExt ? 'import' : isCjsExt ? 'require' : isESModule ? 'import' : 'require'; return exportType; } async function collectEntriesFromParsedExports(cwd, parsedExportsInfo, pkg, sourceFile) { const entries = {}; if (sourceFile) { const defaultExport = parsedExportsInfo.get('./index')[0]; entries['./index'] = { source: sourceFile, name: '.', export: { default: defaultExport[0] } }; } // Find source files const { bins, exportsEntries } = await collectSourceEntriesFromExportPaths(path__default.default.join(cwd, SRC), parsedExportsInfo, pkg); // A mapping between each export path and its related special export conditions, // excluding the 'default' export condition. // { './index' => Set('development', 'edge-light') } const pathSpecialConditionsMap = {}; for (const [exportPath] of exportsEntries){ const normalizedExportPath = stripSpecialCondition(exportPath); if (!pathSpecialConditionsMap[normalizedExportPath]) { pathSpecialConditionsMap[normalizedExportPath] = new Set(); } const exportType = getExportTypeFromExportPath(exportPath); if (exportType !== 'default') { pathSpecialConditionsMap[normalizedExportPath].add(exportType); } } // Traverse source files and try to match the entries // Find exports from parsed exports info // entryExportPath can be: './index', './index.development', './shared.edge-light', etc. for (const [entryExportPath, sourceFilesMap] of exportsEntries){ const normalizedExportPath = stripSpecialCondition(entryExportPath); const entryExportPathType = getExportTypeFromExportPath(entryExportPath); const outputExports = parsedExportsInfo.get(normalizedExportPath); if (!outputExports) { continue; } for (const [outputPath, outputComposedExportType] of outputExports){ // export type can be: default, development, react-server, etc. const matchedExportType = getSpecialExportTypeFromComposedExportPath(outputComposedExportType); const specialSet = pathSpecialConditionsMap[normalizedExportPath]; const hasSpecialEntry = specialSet.has(matchedExportType); const sourceFile = sourceFilesMap[matchedExportType] || sourceFilesMap.default; if (!sourceFile) { continue; } if (!entries[entryExportPath]) { // Create a new entry entries[entryExportPath] = { source: sourceFile, name: normalizedExportPath, export: {} }; } else if (matchedExportType === entryExportPathType) { entries[entryExportPath].source = sourceFile; } // output exports match if (matchedExportType === entryExportPathType || !hasSpecialEntry && matchedExportType !== 'default') { // When we dealing with special export conditions, we need to make sure // the outputs won't override the default export output paths. // e.g. We have './index' -> { default: 'index.js', development: 'index.development.js' }; // When we generate './index.react-server' -> { 'react-server': 'index.react-server.js' }, // Normalize the entryExportPath to './index' first and check if it already exists with output paths. const normalizedEntryExportPath = stripSpecialCondition(entryExportPath); if (// The entry already exists, e.g. normalize './index.react-server' to './index' entries[normalizedEntryExportPath] && // Is special export condition entryExportPathType !== 'default' && // The extracted special condition is not the current loop one. entryExportPathType !== matchedExportType) { continue; } const exportMap = entries[entryExportPath].export; exportMap[outputComposedExportType] = outputPath; } } } // Handling binaries for (const [exportPath, sourceFile] of bins){ const outputExports = parsedExportsInfo.get(exportPath); if (!outputExports) { continue; } for (const [outputPath, exportType] of outputExports){ entries[exportPath] = { source: sourceFile, name: exportPath, export: { [exportType]: outputPath } }; } } return entries; } // ./index -> default // ./index.development -> development // ./index.react-server -> react-server function getExportTypeFromExportPath(exportPath) { // Skip the first two segments: `.` and `index` const exportTypes = exportPath.split('.').slice(2); return getExportTypeFromExportTypesArray(exportTypes); } function getSpecialExportTypeFromComposedExportPath(composedExportType) { const exportTypes = composedExportType.split('.'); for (const exportType of exportTypes){ if (specialExportConventions.has(exportType)) { return exportType; } } return 'default'; } function getSpecialExportTypeFromSourcePath(sourcePath) { const fileBaseName = baseNameWithoutExtension(sourcePath); return getSpecialExportTypeFromComposedExportPath(fileBaseName); } function getExportTypeFromExportTypesArray(types) { let exportType = 'default'; new Set(types).forEach((value)=>{ if (specialExportConventions.has(value)) { exportType = value; } else if (value === 'import' || value === 'require' || value === 'types') { exportType = value; } }); return exportType; } function getSpecialExportTypeFromConditionNames(conditionNames) { let exportType = 'default'; conditionNames.forEach((value)=>{ if (specialExportConventions.has(value)) { exportType = value; } }); return exportType; } // ./index -> . // ./index.development -> . // ./index.react-server -> . // ./shared -> ./shared // ./shared.development -> ./shared // $binary -> $binary // $binary/index -> $binary // $binary/foo -> $binary/foo function normalizeExportPath(exportPath) { if (exportPath.startsWith(BINARY_TAG)) { if (exportPath === `${BINARY_TAG}/index`) { exportPath = BINARY_TAG; } return exportPath; } const baseName = exportPath.split('.').slice(0, 2).join('.'); if (baseName === './index') { return '.'; } return baseName; } // ./index.react-server -> ./index function stripSpecialCondition(exportPath) { return exportPath.split('.').slice(0, 2).join('.'); } async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpath, bins, exportsEntries) { const isBinaryPath = isBinExportPath(originalSubpath); const subpath = originalSubpath.replace(BINARY_TAG, 'bin'); const absoluteDirPath = path__default.default.join(sourceFolderPath, subpath); const dirName = path__default.default.dirname(subpath) // Get directory name regardless of file/directory ; const baseName = path__default.default.basename(subpath) // Get base name regardless of file/directory ; const dirPath = path__default.default.join(sourceFolderPath, dirName); // Match <name>{,/index}.{<ext>,<runtime>.<ext>} const entryFilesPatterns = [ `${baseName}.{${[ ...availableExtensions ].join(',')}}`, `${baseName}.{${[ ...runtimeExportConventions ].join(',')}}.{${[ ...availableExtensions ].join(',')}}`, `${baseName}/index.{${[ ...availableExtensions ].join(',')}}`, `${baseName}/index.{${[ ...runtimeExportConventions ].join(',')}}.{${[ ...availableExtensions ].join(',')}}` ]; const entryFiles = await tinyglobby.glob(entryFilesPatterns, { cwd: dirPath, ignore: [ PRIVATE_GLOB_PATTERN ], expandDirectories: false }); validateEntryFiles(entryFiles); for (const file of entryFiles){ const ext = path__default.default.extname(file).slice(1); if (!availableExtensions.has(ext) || isTestFile(file)) continue; const sourceFileAbsolutePath = path__default.default.join(dirPath, file); const exportPath = posixRelativify(fs.existsSync(absoluteDirPath) && (await fsp__default.default.stat(absoluteDirPath)).isDirectory() ? subpath : originalSubpath); if (isBinaryPath) { bins.set(normalizeExportPath(originalSubpath), sourceFileAbsolutePath); } else { const parts = path__default.default.basename(file).split('.'); const exportType = parts.length > 2 ? parts[1] : getExportTypeFromExportPath(exportPath); const specialExportPath = exportType !== 'index' && parts.length > 2 ? exportPath + '.' + exportType : exportPath // Adjust for direct file matches ; const sourceFilesMap = exportsEntries.get(specialExportPath) || {}; sourceFilesMap[exportType] = sourceFileAbsolutePath; if (specialExportConventions.has(exportType)) { const fallbackExportPath = sourceFilenameToExportFullPath(originalSubpath); const fallbackSourceFilesMap = exportsEntries.get(fallbackExportPath) || {}; Object.assign(sourceFilesMap, fallbackSourceFilesMap); } exportsEntries.set(specialExportPath, sourceFilesMap); } } } /** * exportsEntries { * "./index" => { * "development" => source" * "react-server" => "source" * }, * "./index.react-server" => { * "development" => source" * "react-server" => "source" * } * } */ async function collectSourceEntriesFromExportPaths(sourceFolderPath, parsedExportsInfo, pkg) { const bins = new Map(); const exportsEntries = new Map(); for (const [exportPath, exportInfo] of parsedExportsInfo.entries()){ const specialConditions = new Set(); for (const [_, composedExportType] of exportInfo){ const specialExportType = getSpecialExportTypeFromComposedExportPath(composedExportType); if (specialExportType !== 'default') { specialConditions.add(specialExportType); } } await collectSourceEntriesByExportPath(sourceFolderPath, exportPath, bins, exportsEntries); for (const specialCondition of specialConditions){ await collectSourceEntriesByExportPath(sourceFolderPath, exportPath + '.' + specialCondition, bins, exportsEntries); } } // Search private shared module files which are not in the parsedExportsInfo, but start with _. // Leading underscore: e.g. _utils.ts, _utils/index.ts // Segment contains leading underscore: e.g. a/_b/_c.ts, a/b/_c/index.ts // Contains special suffix: e.g. _utils.development.ts, _utils/index.development.js const suffixPattern = [ ...runtimeExportConventions ].join(','); const extPattern = [ ...availableExtensions ].join(','); const privatePattern = `**/_*{,/*}{,{.${suffixPattern}}}.{${extPattern}}`; const privateFiles = await tinyglobby.glob(privatePattern, { cwd: sourceFolderPath, ignore: [ TESTS_GLOB_PATTERN ], expandDirectories: false }); for (const file of privateFiles){ const sourceFileAbsolutePath = path__default.default.join(sourceFolderPath, file); const exportPath = sourceFilenameToExportFullPath(file); const isEsmPkg = isESModulePackage(pkg.type); const specialExportType = getSpecialExportTypeFromSourcePath(file); const normalizedExportPath = stripSpecialCondition(exportPath); const isSpecialExport = specialExportType !== 'default'; // export type: default => '' // export type: development => '.development' const condPart = isSpecialExport ? specialExportType + '.' : ''; // Map private shared files to the dist directory // e.g. ./_utils.ts -> ./dist/_utils.js // TODO: improve the logic to only generate the required files, not all possible files const isTs = isTypescriptFile(file); const typesInfos = [ [ posixRelativify(path.posix.join('./dist', exportPath + (isEsmPkg ? '.d.ts' : '.d.mts'))), condPart + 'import.types' ], [ posixRelativify(path.posix.join('./dist', exportPath + (isEsmPkg ? '.d.cts' : '.d.ts'))), condPart + 'require.types' ] ]; const privateExportInfo = [ ...isTs ? typesInfos : [], [ posixRelativify(path.posix.join('./dist', exportPath + (isEsmPkg ? '.js' : '.mjs'))), condPart + 'import.default' ], [ posixRelativify(path.posix.join('./dist', exportPath + (isEsmPkg ? '.cjs' : '.js'))), condPart + 'require.default' ] ]; const exportsInfo = parsedExportsInfo.get(normalizedExportPath); if (!exportsInfo) { // Add private shared files to parsedExportsInfo parsedExportsInfo.set(normalizedExportPath, privateExportInfo); } else { // Merge private shared files to the existing exportsInfo exportsInfo.push(...privateExportInfo); } // Insert private shared modules into the entries const entry = exportsEntries.get(exportPath); if (!entry) { exportsEntries.set(exportPath, { [specialExportType]: sourceFileAbsolutePath }); } else { entry[specialExportType] = sourceFileAbsolutePath; } } return { bins, exportsEntries }; } // Example: @foo/bar -> bar const removeScope = (exportPath)=>exportPath.replace(/^@[^/]+\//, ''); function createOutputState({ entries }) { const sizeStats = new Map(); const uniqFiles = new Set(); function addSize({ fileName, size, sourceFileName, exportPath }) { if (!sizeStats.has(exportPath)) { sizeStats.set(exportPath, []); } const distFilesStats = sizeStats.get(exportPath); if (!uniqFiles.has(fileName)) { uniqFiles.add(fileName); if (distFilesStats) { distFilesStats.push([ fileName, sourceFileName, size ]); } } } const reversedMapping = new Map(); Object.entries(entries).forEach(([resolvedExportName, entry])=>{ reversedMapping.set(entry.source, resolvedExportName); }); return { plugin: (cwd)=>{ return { name: 'collect-sizes', writeBundle (options, bundle) { const dir = options.dir || path__default.default.dirname(options.file); Object.entries(bundle).forEach(([fileName, chunk])=>{ const filePath = path__default.default.join(dir, fileName); if (chunk.type !== 'chunk') { return; } if (!chunk.isEntry) { return; } const size = chunk.code.length; const sourceFileName = chunk.facadeModuleId || ''; const exportPath = removeScope(reversedMapping.get(sourceFileName) || '.'); addSize({ fileName: normalizePath(path__default.default.relative(cwd, filePath)), size, sourceFileName: normalizePath(sourceFileName), exportPath }); }); } }; }, getSizeStats () { return sizeStats; } }; } const createMemoize = (fn, cacheKey, cacheArg)=>{ const cache = cacheArg || new Map(); return (...args)=>{ const key = cacheKey ? typeof cacheKey === 'function' ? cacheKey(...args) : cacheKey : JSON.stringify({ args }); const existing = cache.get(key); if (existing !== undefined) { return existing; } const result = fn(...args); cache.set(key, result); return result; }; }; const memoizeByKey = (fn)=>{ const cache = new Map(); return (cacheKey)=>createMemoize(fn, cacheKey, cache); }; const memoize = (fn)=>createMemoize(fn); let hasLoggedTsWarning = false; function resolveTypescript(cwd) { let ts; const m = new module$1.Module('', undefined); m.paths = module$1.Module._nodeModulePaths(cwd); try { // Bun does not yet support the `Module` class properly. if (typeof (m == null ? void 0 : m.require) === 'undefined') { const tsPath = require.resolve('typescript', { paths: module$1.Module._nodeModulePaths(cwd) }); ts = require(tsPath); } else { ts = m.require('typescript'); } } catch (e) { console.error(e); if (!hasLoggedTsWarning) { hasLoggedTsWarning = true; exit('Could not load TypeScript compiler. Try to install `typescript` as dev dependency'); } } return ts; } const resolveTsConfigPath = memoize((cwd, tsconfigFileName = 'tsconfig.json')=>{ let tsConfigPath; tsConfigPath = path.resolve(cwd, tsconfigFileName); return fileExists(tsConfigPath) ? tsConfigPath : undefined; }); function resolveTsConfigHandler(cwd, tsConfigPath) { let tsCompilerOptions = {}; if (tsConfigPath) { // Use the original ts handler to avoid memory leak const ts = resolveTypescript(cwd); const basePath = tsConfigPath ? path.dirname(tsConfigPath) : cwd; const tsconfigJSON = ts.readConfigFile(tsConfigPath, ts.sys.readFile).config; tsCompilerOptions = ts.parseJsonConfigFileContent(tsconfigJSON, ts.sys, basePath).options; } else { return null; } return { tsCompilerOptions, tsConfigPath }; } const resolveTsConfigCache = new Map(); function resolveTsConfig(cwd, tsConfigPath) { const cacheKey = `${cwd}:${tsConfigPath || ''}`; if (resolveTsConfigCache.has(cacheKey)) { return resolveTsConfigCache.get(cacheKey); } const result = resolveTsConfigHandler(cwd, tsConfigPath); resolveTsConfigCache.set(cacheKey, result); return result; } async function convertCompilerOptions(cwd, json) { // Use the original ts handler to avoid memory leak const ts = resolveTypescript(cwd); return ts.convertCompilerOptionsFromJson(json, './'); } async function writeDefaultTsconfig(tsConfigPath) { await fs.promises.writeFile(tsConfigPath, JSON.stringify(DEFAULT_TS_CONFIG, null, 2), 'utf-8'); logger.log(`Detected using TypeScript but tsconfig.json is missing, created a ${pc.blue('tsconfig.json')} for you.`); } /** * @return {Record<string, string>} env { 'process.env.<key>': '<value>' } */ function getDefinedInlineVariables(envs, parsedExportCondition) { const envVars = envs.reduce((acc, key)=>{ const value = process.env[key]; if (typeof value !== 'undefined') { acc['process.env.' + key] = JSON.stringify(value); } return acc; }, {}); const exportConditionNames = Object.keys(parsedExportCondition.export).reduce((acc, key)=>{ // key could be 'require' or 'import.development' etc. const exportTypes = key.split('.'); for (const exportType of exportTypes){ acc.add(exportType); } return acc; }, new Set()); // For development and production convention, we override the NODE_ENV value if (exportConditionNames.has('development')) { envVars['process.env.NODE_ENV'] = JSON.stringify('development'); } else if (exportConditionNames.has('production')) {