bunchee
Version:
zero config bundler for js/ts/jsx libraries
1,292 lines (1,271 loc) • 101 kB
JavaScript
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')) {