bunchee
Version:
zero config bundler for js/ts/jsx libraries
1,329 lines (1,305 loc) • 84.4 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 fastGlob = require('fast-glob');
var require$$0 = require('tty');
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;
}
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);
process.exit(1);
}
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) {
let result = path__default.default.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, '/');
}
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: ... }
* }
*/ function parseExports(pkg) {
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('.');
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 fastGlob.glob(entryFilesPatterns, {
cwd: dirPath,
ignore: [
PRIVATE_GLOB_PATTERN
]
});
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 fastGlob.glob(privatePattern, {
cwd: sourceFolderPath,
ignore: [
TESTS_GLOB_PATTERN
]
});
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: [
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 resolveTsConfig = memoize(resolveTsConfigHandler);
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')) {
envVars['process.env.NODE_ENV'] = JSON.stringify('production');
}
if (exportConditionNames.has('edge-light')) {
envVars['EdgeRuntime'] = JSON.stringify('edge-runtime');
}
return envVars;
}
const FILENAME_REGEX = /__filename/;
const DIRNAME_REGEX = /__dirname/;
// not char, or space before require(.resolve)?(
const GLOBAL_REQUIRE_REGEX = /(?:^|[^.\w'"`])\brequire(\.resolve)?\(\s*[\r\n]*(\w|['"`])/;
const PolyfillComment = '/** rollup-private-do-not-use-esm-shim-polyfill */';
const createESMShim = ({ filename, dirname, globalRequire })=>{
const useNodeUrl = filename || dirname;
const useNodePath = dirname;
const useNodeModule = globalRequire;
return `\
${PolyfillComment}
${useNodeUrl ? `import __node_cjsUrl from 'node:url'` : ''};
${useNodePath ? `import __node_cjsPath from 'node:path';` : ''}
${useNodeModule ? `import __node_cjsModule from 'node:module';` : ''}
${useNodeUrl ? 'const __filename = __node_cjsUrl.fileURLToPath(import.meta.url);' : ''}
${useNodePath ? 'const __dirname = __node_cjsPath.dirname(__filename);' : ''}
${useNodeModule ? 'const require = __node_cjsModule.createRequire(import.meta.url);' : ''}
`.trim() + '\n';
};
function esmShim() {
return {
name: 'esm-shim',
transform: {
order: 'post',
handler (code, id) {
const ext = path.extname(id);
if (!availableESExtensionsRegex.test(ext) || code.includes(PolyfillComment)) {
return null;
}
let hasFilename = false;
let hasDirname = false;
let hasGlobalRequire = false;
if (FILENAME_REGEX.test(code)) {
hasFilename = true;
}
if (DIRNAME_REGEX.test(code)) {
hasDirname = true;
}
if (GLOBAL_REQUIRE_REGEX.test(code)) {
hasGlobalRequire = true;
}
if (!hasFilename && !hasDirname && !hasGlobalRequire) {
return null;
}
const magicString = new MagicString__default.default(code);
let ast = null;
try {
// rollup 2 built-in parser doesn't have `allowShebang`, we need to use the sliced code here. Hence the `magicString.toString()`
ast = this.parse(magicString.toString(), {
allowReturnOutsideFunction: true
});
} catch (e) {
console.warn(e);
return null;
}
if (ast.type !== 'Program') {
return null;
}
let lastImportNode = null;
for (const node of ast.body){
if (node.type === 'ImportDeclaration') {
lastImportNode = node;
continue;
}
}
let end = 0;
if (lastImportNode) {
end = lastImportNode.end;
} else {
end = ast.body.length > 0 ? ast.body[0].end : 0;
}
magicString.appendRight(end, '\n' + createESMShim({
filename: hasFilename,
dirname: hasDirname,
globalRequire: hasGlobalRequire
}));
return {
code: magicString.toString(),
map: magicString.generateMap({
hires: true
})
};
}
}
};
}
const helpers = {
cssImport: {
// have to assign r.type = 'text/css' to make it work in Safari
global: `\
function __insertCSS(code) {
if (!code || typeof document == 'undefined') return
let head = document.head || document.getElementsByTagName('head')[0]
let style = document.createElement('style')
style.type = 'text/css'
head.appendChild(style)
;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
}
`,
create (code) {
return `__insertCSS(${JSON.stringify(code)});`;
}
}};
const cleanCssInstance = new CleanCSS__default.default({});
function minify(code) {
return cleanCssInstance.minify(code).styles;
}
function inlineCss(options) {
const cssIds = new Set();
var _options_exclude;
const filter = pluginutils.createFilter([
'**/*.css'
], (_options_exclude = options.exclude) != null ? _options_exclude : []);
// Follow up for rollup 4 for better support of assertion support https://github.com/rollup/rollup/issues/4818
return {
name: 'inline-css',
transform: {
order: 'post',
handler (code, id) {
if (!filter(id)) return;
if (options.skip) return '';
const cssCode = minify(code);
cssIds.add(id);
return {
code: helpers.cssImport.create(cssCode),
map: {
mappings: ''
}
};
}
},
renderChunk: {
order: 'pre',
handler (code) {
const dependenciesIds = this.getModuleIds();
let foundCss = false;
for (const depId of dependenciesIds){
if (depId && cssIds.has(depId)) {
foundCss = true;
break;
}
}
if (!foundCss) return;
return {
code: `${helpers.cssImport.global}\n${code}`,
map: {
mappings: ''
}
};
}
}
};
}
function rawContent({ exclude }) {
const filter = pluginutils.createFilter([
'**/*.data',
'**/*.txt'
], exclude);
return {
name: 'string',
resolveId (id, importer) {
// Handle ?raw query parameter
if (id.includes('?raw')) {
const cleanId = id.split('?')[0];
// Resolve the actual file path
if (importer) {
const path = require('path');
return path.resolve(path.dirname(importer), cleanId) + '?raw';
}
return cleanId + '?raw';
}
return null;
},
async load (id) {
// Handle ?raw query parameter - read the actual file without the query
if (id.includes('?raw')) {
const cleanId = id.split('?')[0];
try {
const content = await fsp.readFile(cleanId, 'utf-8');
// Normalize line endings only on Windows: convert \r\n to \n
return process.platform === 'win32' ? content.replace(/\r\n/g, '\n') : content;
} catch (error) {
this.error(`[bunchee] failed to read file: ${cleanId}`);
}
}
return null;
},
transform (code, id) {
// Check if the file has ?raw query parameter
const isRawQuery = id.includes('?raw');
const cleanId = isRawQuery ? id.split('?')[0] : id;
if (filter(cleanId) || isRawQuery) {
// Normalize line endings only on Windows for .txt and .data files
const normalizedCode = process.platform === 'win32' ? code.replace(/\r\n/g, '\n') : code;
return {
code: `const data = ${JSON.stringify(normalizedCode)};\nexport default data;`,
map: null
};
}
return null;
}
};
}
function hasNoSpecialCondition(conditionNames) {