bunchee
Version:
zero config bundler for js/ts/jsx libraries
1,258 lines (1,232 loc) • 53.7 kB
JavaScript
#!/usr/bin/env node
var path = require('path');
var yargs = require('yargs');
var helpers = require('yargs/helpers');
var perf_hooks = require('perf_hooks');
var fs = require('fs');
var fsp = require('fs/promises');
var require$$0 = require('tty');
var picomatch = require('picomatch');
var index_js = require('../index.js');
require('module');
var fastGlob = require('fast-glob');
var prettyBytes = require('pretty-bytes');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var path__default = /*#__PURE__*/_interopDefault(path);
var yargs__default = /*#__PURE__*/_interopDefault(yargs);
var fs__default = /*#__PURE__*/_interopDefault(fs);
var fsp__default = /*#__PURE__*/_interopDefault(fsp);
var require$$0__default = /*#__PURE__*/_interopDefault(require$$0);
var picomatch__default = /*#__PURE__*/_interopDefault(picomatch);
var prettyBytes__default = /*#__PURE__*/_interopDefault(prettyBytes);
const availableExtensions = new Set([
'js',
'cjs',
'mjs',
'jsx',
'ts',
'tsx',
'cts',
'mts'
]);
// 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 optimizeConventions = new Set([
'development',
'production'
]);
const specialExportConventions = new Set([
...runtimeExportConventions,
...optimizeConventions
]);
const SRC = 'src';
const DIST = 'dist';
const dtsExtensionsMap = {
js: 'd.ts',
cjs: 'd.cts',
mjs: 'd.mts'
};
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 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);
}
};
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);
}
function hasPackageJson(cwd) {
return fileExists(path__default.default.resolve(cwd, 'package.json'));
}
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 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';
}
function isBinExportPath(exportPath) {
return exportPath === BINARY_TAG || exportPath.startsWith(BINARY_TAG + '/');
}
function isTypeFile(filename) {
return filename.endsWith('.d.ts') || filename.endsWith('.d.mts') || filename.endsWith('.d.cts');
}
// 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);
}
// If the file is matching the private module convention file export path.
// './lib/_foo' -> true
// './_util/index' -> true
// './lib/_foo/bar' -> true
// './foo' -> false
function isPrivateExportPath(exportPath) {
return /\/_/.test(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 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;
}
const matchFile = (matchingPattern, filePath)=>{
return matchingPattern.some((pattern)=>{
// pattern is always posix
const normalizedPattern = path.posix.normalize(pattern);
const expandedPattern = normalizedPattern.endsWith('/') ? `${normalizedPattern}**` : `${normalizedPattern}/**`;
const matcher = picomatch__default.default(expandedPattern);
const normalizedFilePath = path.posix.normalize(filePath);
return matcher(normalizedFilePath);
});
};
function validateTypesFieldCondition(pair) {
const [outputPath, composedExportType] = pair;
const exportTypes = new Set(composedExportType.split('.'));
if (!exportTypes.has('types') && isTypeFile(outputPath)) {
return true;
}
return false;
}
function validateFilesField(packageJson) {
const state = {
missingFiles: []
};
const filesField = packageJson.files || [
'*'
];
const exportsField = packageJson.exports || {};
const resolveExportsPaths = (exports)=>{
const paths = [];
if (typeof exports === 'string') {
paths.push(exports);
} else if (typeof exports === 'object') {
for(const key in exports){
paths.push(...resolveExportsPaths(exports[key]));
}
}
return paths;
};
const exportedPaths = resolveExportsPaths(exportsField).map((p)=>normalizePath(path__default.default.normalize(p)));
const commonFields = [
'main',
'module',
'types',
'module-sync'
];
for (const field of commonFields){
if (field in packageJson) {
exportedPaths.push(packageJson[field]);
}
}
state.missingFiles = exportedPaths.filter((exportPath)=>{
// Special case for package.json
if (exportPath === 'package.json') {
return false;
}
return !matchFile(filesField, exportPath);
});
return state;
}
function lint$1(pkg) {
const { name, main, exports } = pkg;
const isESM = isESModulePackage(pkg.type);
const parsedExports = parseExports(pkg);
if (!name) {
logger.warn('Missing package name');
}
const exportsState = {
badMainExtension: false,
badMainExport: false,
invalidExportsFieldType: false,
badCjsRequireExport: {
value: false,
paths: []
},
badCjsImportExport: {
value: false,
paths: []
},
badEsmRequireExport: {
value: false,
paths: []
},
badEsmImportExport: {
value: false,
paths: []
},
badTypesExport: []
};
// Validate ESM package
if (isESM) {
if (exports) {
if (typeof exports === 'string') {
if (hasCjsExtension(exports)) {
exportsState.badMainExport = true;
}
} else if (typeof exports !== 'object') {
exportsState.invalidExportsFieldType = true;
} else {
parsedExports.forEach((outputPairs)=>{
for (const outputPair of outputPairs){
const [outputPath, composedExportType] = outputPair;
if (validateTypesFieldCondition([
outputPath,
composedExportType
])) {
exportsState.badTypesExport.push([
outputPath,
composedExportType
]);
}
const exportTypes = new Set(composedExportType.split('.'));
let requirePath = '';
let importPath = '';
if (exportTypes.has('require')) {
requirePath = outputPath;
}
if (exportTypes.has('import')) {
importPath = outputPath;
}
const requireExt = requirePath && path__default.default.extname(requirePath);
const importExt = importPath && path__default.default.extname(importPath);
if (requireExt === '.mjs' || requireExt === '.js') {
exportsState.badEsmRequireExport.value = true;
exportsState.badEsmRequireExport.paths.push(requirePath);
}
if (importExt === '.cjs') {
exportsState.badEsmImportExport.value = true;
exportsState.badEsmImportExport.paths.push(importPath);
}
}
});
}
}
} else {
// Validate CJS package
if (main && path__default.default.extname(main) === '.mjs') {
exportsState.badMainExtension = true;
}
if (exports) {
if (typeof exports === 'string') {
if (path__default.default.extname(exports) === '.mjs') {
exportsState.badMainExport = true;
}
} else if (typeof exports !== 'object') {
exportsState.invalidExportsFieldType = true;
} else {
parsedExports.forEach((outputPairs)=>{
for (const outputPair of outputPairs){
const [outputPath, composedExportType] = outputPair;
if (validateTypesFieldCondition([
outputPath,
composedExportType
])) {
exportsState.badTypesExport.push([
outputPath,
composedExportType
]);
}
const exportTypes = new Set(composedExportType.split('.'));
let requirePath = '';
let importPath = '';
if (exportTypes.has('require')) {
requirePath = outputPath;
}
if (exportTypes.has('import')) {
importPath = outputPath;
}
const requireExt = requirePath && path__default.default.extname(requirePath);
const importExt = importPath && path__default.default.extname(importPath);
if (requireExt === '.mjs') {
exportsState.badCjsRequireExport.value = true;
exportsState.badCjsRequireExport.paths.push(requirePath);
}
if (importExt === '.js' || importExt === '.cjs') {
exportsState.badCjsImportExport.value = true;
exportsState.badCjsImportExport.paths.push(importPath);
}
}
});
}
}
}
const fieldState = validateFilesField(pkg);
const warningsCount = exportsState.badTypesExport.length + fieldState.missingFiles.length;
if (warningsCount) {
logger.warn(`Lint: ${warningsCount} issues found.`);
}
if (fieldState.missingFiles.length) {
logger.warn('Missing files in package.json');
fieldState.missingFiles.forEach((p)=>{
logger.warn(` ${p}`);
});
}
if (exportsState.badMainExtension) {
logger.warn('Cannot export `main` field with .mjs extension in CJS package, only .js extension is allowed');
}
if (exportsState.badMainExport) {
if (isESM) {
logger.warn('Cannot export `exports` field with .cjs extension in ESM package, only .mjs and .js extensions are allowed');
} else {
logger.warn('Cannot export `exports` field with .mjs extension in CJS package, only .js and .cjs extensions are allowed');
}
}
if (exportsState.invalidExportsFieldType) {
logger.warn('Invalid exports field type, only object or string is allowed');
}
if (exportsState.badCjsRequireExport.value) {
logger.warn('Cannot export `require` field with .mjs extension in CJS package, only .cjs and .js extensions are allowed');
exportsState.badCjsRequireExport.paths.forEach((p)=>{
logger.warn(` ${p}`);
});
}
if (exportsState.badCjsImportExport.value) {
logger.warn('Cannot export `import` field with .js or .cjs extension in CJS package, only .mjs extensions are allowed');
exportsState.badCjsImportExport.paths.forEach((p)=>{
logger.warn(` ${p}`);
});
}
if (exportsState.badEsmRequireExport.value) {
logger.warn('Cannot export `require` field with .js or .mjs extension in ESM package, only .cjs extensions are allowed');
exportsState.badEsmRequireExport.paths.forEach((p)=>{
logger.warn(` ${p}`);
});
}
if (exportsState.badEsmImportExport.value) {
logger.warn('Cannot export `import` field with .cjs extension in ESM package, only .js and .mjs extensions are allowed');
exportsState.badEsmImportExport.paths.forEach((p)=>{
logger.warn(` ${p}`);
});
}
if (exportsState.badTypesExport.length) {
exportsState.badTypesExport.forEach(([outputPath, composedExportType])=>{
logger.error(`Bad export types field with ${composedExportType} in ${outputPath}, use "types" export condition for it`);
});
}
}
var version = "6.6.0";
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.`);
}
// ./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 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;
}
// ./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;
}
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);
}
}
}
// For `prepare` command
async function collectSourceEntries(sourceFolderPath) {
const bins = new Map();
const exportsEntries = new Map();
if (!fs.existsSync(sourceFolderPath)) {
return {
bins,
exportsEntries
};
}
// Match with global patterns
// bin/**/*.<ext>, bin/**/index.<ext>
const binPattern = `bin/**/*.{${[
...availableExtensions
].join(',')}}`;
const srcPattern = `**/*.{${[
...availableExtensions
].join(',')}}`;
const binMatches = await fastGlob.glob(binPattern, {
cwd: sourceFolderPath,
ignore: [
PRIVATE_GLOB_PATTERN,
TESTS_GLOB_PATTERN
]
});
const srcMatches = await fastGlob.glob(srcPattern, {
cwd: sourceFolderPath,
ignore: [
PRIVATE_GLOB_PATTERN,
TESTS_GLOB_PATTERN
]
});
for (const file of binMatches){
// convert relative path to export path
const exportPath = sourceFilenameToExportFullPath(normalizePath(file));
const binExportPath = exportPath.replace(/^\.[\//]bin/, BINARY_TAG);
await collectSourceEntriesByExportPath(sourceFolderPath, binExportPath, bins, exportsEntries);
}
for (const file of srcMatches){
const binExportPath = normalizePath(file).replace(/^bin/, BINARY_TAG)// Remove index.<ext> to [^index].<ext> to build the export path
.replace(/(\/index)?\.[^/]+$/, '');
await collectSourceEntriesByExportPath(sourceFolderPath, binExportPath, bins, exportsEntries);
}
return {
bins,
exportsEntries
};
}
// Output with posix style in package.json
function getDistPath(...subPaths) {
return posixRelativify(path.posix.join(DIST, ...subPaths));
}
function stripeBinaryTag(exportName) {
// Add \ to decode leading $
return exportName.replace(/\$binary\//, '');
}
const normalizeBaseNameToExportName = (name)=>{
const baseName = stripeBinaryTag(name);
return /^\.\/index(\.|$)/.test(baseName) ? '.' : posixRelativify(baseName);
};
function createExportCondition(exportName, sourceFile, moduleType) {
const isTsSourceFile = isTypescriptFile(sourceFile);
let cjsExtension = 'js';
let esmExtension = 'mjs';
if (moduleType === 'module') {
cjsExtension = 'cjs';
esmExtension = 'js';
}
if (exportName === '.') {
exportName = 'index';
}
if (isTsSourceFile) {
return {
import: {
types: getDistPath('es', `${exportName}.${dtsExtensionsMap[esmExtension]}`),
default: getDistPath('es', `${exportName}.${esmExtension}`)
},
require: {
types: getDistPath('cjs', `${exportName}.${dtsExtensionsMap[cjsExtension]}`),
default: getDistPath('cjs', `${exportName}.${cjsExtension}`)
}
};
}
return {
import: getDistPath(`${exportName}.mjs`),
require: getDistPath(`${exportName}.${cjsExtension}`)
};
}
function createExportConditionPair(exportName, sourceFile, moduleType) {
// <exportName>.<specialCondition>
let specialCondition;
const specialConditionName = getSpecialExportTypeFromComposedExportPath(exportName);
const normalizedExportPath = normalizeExportPath(exportName);
if (specialConditionName !== 'default') {
// e.g.
// ./index.develop -> index
// ./foo.react-server -> foo
const fileBaseName = exportName.split('.').slice(0, 2).join('.').replace('./', '');
specialCondition = {
[specialConditionName]: getDistPath('es', `${fileBaseName}-${specialConditionName}.mjs`)
};
return [
normalizedExportPath,
specialCondition
];
}
const exportCond = createExportCondition(exportName, sourceFile, moduleType);
return [
normalizedExportPath,
exportCond
];
}
async function prepare(cwd) {
const sourceFolder = path__default.default.resolve(cwd, SRC);
if (!fs__default.default.existsSync(sourceFolder)) {
logger.error(`Source folder ${sourceFolder} does not exist. Cannot proceed to configure \`exports\` field.`);
process.exit(1);
}
let hasPackageJson = false;
const pkgJsonPath = path__default.default.join(cwd, 'package.json');
let pkgJson = {};
if (fs__default.default.existsSync(pkgJsonPath)) {
hasPackageJson = true;
const pkgJsonString = await fsp__default.default.readFile(pkgJsonPath, 'utf-8');
pkgJson = JSON.parse(pkgJsonString);
}
// configure `files` field with `dist`
const files = pkgJson.files || [];
if (!files.includes(DIST)) {
files.push(DIST);
}
pkgJson.files = files;
let isUsingTs = false;
// Collect bins and exports entries
const { bins, exportsEntries } = await collectSourceEntries(sourceFolder);
const tsConfigPath = path__default.default.join(cwd, 'tsconfig.json');
const exportsSourceFiles = [
...exportsEntries.values()
].reduce((acc, sourceFiles)=>{
Object.values(sourceFiles).forEach((sourceFile)=>acc.add(sourceFile));
return acc;
}, new Set());
const allSourceFiles = [
...exportsSourceFiles,
...bins.values()
].map((absoluteFilePath)=>absoluteFilePath);
const hasTypeScriptFiles = allSourceFiles.some((filename)=>isTypescriptFile(filename));
if (hasTypeScriptFiles) {
isUsingTs = true;
if (!fs__default.default.existsSync(tsConfigPath)) {
await writeDefaultTsconfig(tsConfigPath);
}
}
// Configure as ESM package by default if there's no package.json
if (!hasPackageJson) {
pkgJson.type = 'module';
}
if (bins.size > 0) {
logger.log('Discovered binaries entries:');
const maxLengthOfBinName = Math.max(...Array.from(bins.keys()).map((binName)=>normalizeBaseNameToExportName(binName).length));
for (const [binName, binFile] of bins.entries()){
const spaces = ' '.repeat(Math.max(maxLengthOfBinName - normalizeBaseNameToExportName(binName).length, 0));
logger.log(` ${normalizeBaseNameToExportName(binName)}${spaces}: ${path__default.default.basename(binFile)}`);
}
if (bins.size === 1 && bins.has(BINARY_TAG)) {
pkgJson.bin = getDistPath('bin', 'index.js');
} else {
pkgJson.bin = {};
for (const [binOriginName] of bins.entries()){
const binName = stripeBinaryTag(binOriginName);
pkgJson.bin[binName === '.' ? pkgJson.name : binName] = getDistPath('bin', binName + '.js');
}
}
}
if (exportsEntries.size > 0) {
logger.log('Discovered exports entries:');
const maxLengthOfExportName = Math.max(...Array.from(exportsEntries.keys()).map((exportName)=>normalizeBaseNameToExportName(exportName).length));
for (const [exportName, sourceFilesMap] of exportsEntries.entries()){
const spaces = ' '.repeat(Math.max(maxLengthOfExportName - normalizeBaseNameToExportName(exportName).length, 0));
for (const exportFile of Object.values(sourceFilesMap)){
logger.log(` ${normalizeBaseNameToExportName(exportName)}${spaces}: ${path__default.default.basename(exportFile)}`);
}
}
const pkgExports = {};
for (const [exportName, sourceFilesMap] of exportsEntries.entries()){
for (const sourceFile of Object.values(sourceFilesMap)){
const [normalizedExportPath, conditions] = createExportConditionPair(exportName, sourceFile, pkgJson.type);
pkgExports[normalizedExportPath] = {
...conditions,
...pkgExports[normalizedExportPath]
};
}
}
// Configure node10 module resolution
if (exportsEntries.has('./index')) {
const isESM = pkgJson.type === 'module';
const mainExport = pkgExports['.'];
const mainCondition = isESM ? 'import' : 'require';
pkgJson.main = isUsingTs ? mainExport[mainCondition].default : mainExport[mainCondition];
pkgJson.module = isUsingTs ? mainExport.import.default : mainExport.import;
if (isUsingTs) {
pkgJson.types = mainExport[mainCondition].types;
}
}
// Assign the properties by order: files, main, module, types, exports
if (Object.keys(pkgExports).length > 0) {
if (!pkgJson.exports) {
pkgJson.exports = pkgExports;
} else {
// Update existing exports
Object.keys(pkgExports).forEach((exportName)=>{
pkgJson.exports[exportName] = {
// Apply the new export conditions
...pkgJson.exports[exportName],
// Keep the existing export conditions
...pkgJson.exports[exportName]
};
});
}
}
}
await fsp__default.default.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + '\n');
logger.info('Configured `exports` in package.json');
}
function normalizeExportName(exportName) {
const isBinary = isBinExportPath(exportName);
let result = exportName;
if (isBinary) {
result = (exportName.replace(new RegExp(`^\\${BINARY_TAG}\\/?`), '') || '.') + ' (bin)';
} else {
const normalizedExportPath = normalizeExportPath(exportName);
const specialConditionName = getSpecialExportTypeFromComposedExportPath(exportName);
result = normalizedExportPath + (specialConditionName !== 'default' ? ` (${specialConditionName})` : '');
}
return result;
}
function logOutputState(stats) {
if (stats.size === 0) {
logger.warn('No build info can be logged');
return;
}
const allFileNameLengths = Array.from(stats.values()).flat(1).map(([filename])=>filename.length);
const maxFilenameLength = Math.max(...allFileNameLengths);
const statsArray = [
...stats.entries()
]// filter out empty file states.
// e.g. if you're building a file that does not listed in the exports, or there's no exports condition.
.filter(([, fileStates])=>fileStates.length > 0).sort(([a], [b])=>{
const comp = normalizeExportPath(a).length - normalizeExportPath(b).length;
return comp === 0 ? a.localeCompare(b) : comp;
});
const maxLengthOfExportName = Math.max(...statsArray.map(([exportName])=>normalizeExportName(exportName).length));
console.log(pc.underline('Exports'), ' '.repeat(Math.max(maxLengthOfExportName - 'Exports'.length, 0)), pc.underline('File'), ' '.repeat(Math.max(maxFilenameLength - 'File'.length, 0)), pc.underline('Size'));
statsArray.forEach(([exportName, filesList])=>{
// sort by file type, first js files then types, js/mjs/cjs are prioritized than .d.ts/.d.mts/.d.cts
filesList.sort(([a], [b])=>{
const aIsType = isTypeFile(a);
const bIsType = isTypeFile(b);
if (aIsType && bIsType) {
return 0;
}
if (aIsType) {
return 1;
}
if (bIsType) {
return -1;
}
return 0;
}).forEach((item, index)=>{
const [filename, , size] = item;
const normalizedExportName = normalizeExportName(exportName);
const exportNameWithPadding = index === 0 ? // For other formats, just show the padding spaces.
normalizedExportName : ' '.repeat(normalizedExportName.length);
const filenamePadding = ' '.repeat(Math.max(maxLengthOfExportName, 'Exports'.length) - normalizedExportName.length);
const isType = isTypeFile(filename);
const prettiedSize = prettyBytes__default.default(size);
const sizePadding = ' '.repeat(Math.max(maxFilenameLength, 'File'.length) - filename.length);
// Logging shared in debug mode
if (isPrivateExportPath(exportName)) {
if (index === 0 && process.env.DEBUG) {
const label = '(chunk)';
const sizePadding = ' '.repeat(Math.max(maxFilenameLength, 'File'.length) - label.length);
console.log(pc.dim(normalizeExportName(exportName)), filenamePadding, pc.dim(label), sizePadding, pc.dim(prettiedSize));
}
return;
}
console.log(exportNameWithPadding, filenamePadding, `${pc[isType ? 'dim' : 'bold'](filename)}`, sizePadding, prettiedSize);
});
});
}
function normalizeError(error) {
// Remove the noise from rollup plugin error
if (error.code === 'PLUGIN_ERROR') {
const normalizedError = new Error(error.message);
normalizedError.stack = error.stack;
error = normalizedError;
}
return error;
}
const helpMessage = `
Usage: bunchee [options]
Commands:
prepare auto setup package.json for building
lint lint configuration in package.json
Options:
-v, --version output the version number
-w, --watch watch src files changes
-m, --minify compress output. default: false
-o, --output <file> specify output filename
-f, --format <format> type of output (esm, amd, cjs, iife, umd, system), default: esm
-h, --help output usage information
--external <mod> specify an external dependency, separate by comma
--no-external do not bundle external dependencies
--no-clean do not clean dist folder before building, default: false
--target <target> js features target: swc target es versions. default: es2015
--runtime <runtime> build runtime (nodejs, browser). default: browser
--env <env> inlined process env variables, separate by comma. default: NODE_ENV
--cwd <cwd> specify current working directory
--sourcemap enable sourcemap generation
--no-dts do not generate types, default: undefined
--tsconfig path to tsconfig file, default: tsconfig.json
--dts-bundle bundle type declaration files, default: false
--success <cmd> run command after build success
`;
function help() {
logger.log(helpMessage);
}
async function lint(cwd) {
// Not package.json detected, skip package linting
if (!await hasPackageJson(cwd)) {
return;
}
await lint$1(await getPackageMeta(cwd));
}
async function parseCliArgs(argv) {
const args = await yargs__default.default(helpers.hideBin(argv)).option('watch', {
type: 'boolean',
alias: 'w',
description: 'watch src files changes'
}).option('cwd', {
type: 'string',
description: 'specify current working directory'
}).option('dts', {
coerce (arg) {
return arg === false ? false : undefined;
},
description: 'do not generate types'
}).option('clean', {
coerce (arg) {
return arg === false ? false : undefined;
},
description: 'do not clean dist folder before building'
}).option('output', {
type: 'string',
alias: 'o',
description: 'specify output filename'
}).option('format', {
type: 'string',
alias: 'f',
default: 'esm',
description: 'type of output (esm, amd, cjs, iife, umd, system)'
}).option('minify', {
type: 'boolean',
alias: 'm',
description: 'compress output'
}).option('help', {
type: 'boolean',
alias: 'h',
description: 'output usage information'
}).option('runtime', {
type: 'string',
default: 'browser',
description: 'build runtime (nodejs, browser)'
}).option('target', {
type: 'string',
description: 'js features target: swc target es versions'
}).option('sourcemap', {
type: 'boolean',
default: undefined,
description: 'enable sourcemap generation'
}).option('env', {
type: 'string',
description: 'inlined process env variables, separate by comma'
}).option('external', {
coerce (arg) {
return typeof arg === 'string' || typeof arg === 'boolean' ? arg : undefined;
},
description: 'specify an external dependency, separate by comma'
}).option('tsconfig', {
type: 'string',
description: 'path to tsconfig file'
}).option('dts-bundle', {
type: 'boolean',
description: 'bundle type declaration files'
}).option('prepare', {
type: 'boolean',
description: 'auto setup package.json for building'
}).option('success', {
type: 'string',
description: 'run command after build success'
}).command('prepare', 'auto configure package.json exports for building', ()=>{}, (argv)=>{
return prepare(argv.cwd || process.cwd());
}).command('lint', 'lint package.json', ()=>{}, (argv)=>{
return lint(argv.cwd || process.cwd());
}).version(version).help('help', 'output usage information').showHelpOnFail(true).parse();
const cmd = args._[0];
if (cmd === 'prepare' || cmd === 'lint') {
return {
cmd
};
}
// Warn about this command being deprecated
if (args['prepare']) {
logger.warn('The "--prepare" option is deprecated. Please use `bunchee prepare` instead.');
return;
}
const source = args._[0];
const parsedArgs = {
source,
format: args['format'],
file: args['output'],
watch: !!args['watch'],
minify: args['minify'],
sourcemap: !!args['sourcemap'],
cwd: args['cwd'],
dts: args['dts'] === false ? false : undefined,
dtsBundle: args['dts-bundle'],
help: args['help'],
runtime: args['runtime'],
target: args['target'],
// no-external is a boolean flag, turning external to `false`
external: args['external'] === false ? null : args['external'],
clean: args['clean'] !== false,
env: args['env'],
tsconfig: args['tsconfig'],
onSuccess: args['success']
};
// When minify is enabled, sourcemap should be enabled by default, unless explicitly opted out
if (parsedArgs.minify && typeof args['sourcemap'] === 'undefined') {
parsedArgs.sourcemap = true;
}
return parsedArgs;
}
async function run(args) {
var _args_external;
const { source, format, watch, minify, sourcemap, target, runtime, dts, dtsBundle, env, clean, tsconfig, onSuccess } = args;
const cwd = args.cwd || process.cwd();
const file = args.file ? path__default.default.resolve(cwd, args.file) : undefined;
const bundleConfig = {
dts: dts !== false && {
respectExternal: dtsBundle ? true : undefined
},
file,
format,
cwd,
target,
runtime,
external: args.external === null ? null : ((_args_external = args.external) == null ? void 0 : _args_external.split(',')) || [],
watch: !!watch,
minify: !!minify,
sourcemap: sourcemap === false ? false : true,
env: (env == null ? void 0 : env.split(',')) || [],
clean,
tsconfig,
onSuccess
};
const cliEntry = source ? path__default.default.resolve(cwd, source) : '';
// lint package by default
await lint(cwd);
const { default: ora } = await import('ora');
const oraInstance = process.stdout.isTTY ? ora({
text: 'Building...\n\n',
color: 'green'
}) : {
start: ()=>{},
stop: ()=>{},
clear: ()=>{},
stopAndPersist: ()=>{},
isSpinning: false
};
const spinner = {
start: startSpinner,
stop: stopSpinner
};
function startSpinner() {
oraInstance.start();
}
funct