UNPKG

prebundle

Version:
375 lines (374 loc) 12.7 kB
import { dirname, extname, join, resolve } from "node:path"; import fs_extra from "../compiled/fs-extra/index.js"; import { createRequire } from "node:module"; import { pathToFileURL } from "node:url"; import ncc from "@vercel/ncc"; import fast_glob from "../compiled/fast-glob/index.js"; import rslog from "../compiled/rslog/index.js"; import { dts } from "rollup-plugin-dts"; import { rollup } from "rollup"; import { minify } from "terser"; import { format } from "prettier"; import { existsSync } from "node:fs"; const DIST_DIR = 'compiled'; const DEFAULT_EXTERNALS = { './package.json': './package.json', '../package.json': './package.json', '../../package.json': './package.json' }; const NODE_BUILTINS = [ '_stream_duplex', '_stream_passthrough', '_stream_readable', '_stream_transform', '_stream_writable', 'assert', 'buffer', 'child_process', 'cluster', 'console', 'constants', 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https', 'module', 'net', 'os', 'path', 'process', 'punycode', 'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'sys', 'timers', 'tls', 'tty', 'url', 'util', 'vm', 'zlib' ]; const cwd = process.cwd(); const helper_require = createRequire(import.meta.url); function findDepPath(name) { try { let entry = dirname(helper_require.resolve(name, { paths: [ cwd ] })); while(!dirname(entry).endsWith('node_modules'))entry = dirname(entry); if (name.includes('/')) return join(dirname(entry), name); return entry; } catch { return null; } } const resolveConfig = async (configFile)=>{ const configFiles = configFile ? [ resolve(cwd, configFile) ] : [ 'prebundle.config.ts', 'prebundle.config.mts', 'prebundle.config.mjs', 'prebundle.config.js' ].map((filename)=>join(cwd, filename)); for (const filename of configFiles)if (fs_extra.existsSync(filename)) { const config = await import(pathToFileURL(filename).href); return config.default ?? config; } if (configFile) throw new Error(`Unable to locate prebundle config file at: ${configFile}`); throw new Error('Unable to locate prebundle config file.'); }; function parseTasks(dependencies, globalPrettier) { const result = []; for (const dep of dependencies){ const depName = 'string' == typeof dep ? dep : dep.name; const importPath = join(cwd, DIST_DIR, depName); const distPath = join(cwd, DIST_DIR, depName); const depPath = findDepPath(depName); if (!depPath) throw new Error(`Failed to resolve dependency: ${depName}`); const depEntry = helper_require.resolve(depName, { paths: [ cwd ] }); const info = { depName, depPath, depEntry, distPath, importPath }; if ('string' == typeof dep) result.push({ minify: false, target: 'es2019', externals: {}, dtsExternals: [], emitFiles: [], packageJsonField: [], prettier: globalPrettier, ...info }); else result.push({ minify: dep.minify ?? false, target: dep.target ?? 'es2019', ignoreDts: dep.ignoreDts, copyDts: dep.copyDts, externals: dep.externals ?? {}, dtsOnly: dep.dtsOnly ?? false, dtsExternals: dep.dtsExternals ?? [], emitFiles: dep.emitFiles ?? [], prettier: dep.prettier ?? globalPrettier, afterBundle: dep.afterBundle, beforeBundle: dep.beforeBundle, packageJsonField: dep.packageJsonField ?? [], ...info }); } return result; } function pick(obj, keys) { return keys.reduce((ret, key)=>{ if (void 0 !== obj[key]) ret[key] = obj[key]; return ret; }, {}); } function pkgNameToAtTypes(name) { const mangled = name.replace(/^@([^\/]+)\/([^\/]+)/, '$1__$2'); return `@types/${mangled}`; } function findDirectTypeFile(filepath) { if (/\.d\.[cm]?ts/.test(filepath)) return filepath; const ext = extname(filepath); const base = filepath.slice(0, -ext.length); const _find = (list)=>{ for (const f of list)try { return helper_require.resolve(f, { paths: [ cwd ] }); } catch { continue; } }; switch(ext){ case '.js': case '.ts': return _find([ base + '.d.ts' ]); case '.mjs': case '.mts': return _find([ base + '.d.mts', base + '.d.ts' ]); case '.cjs': case '.cts': return _find([ base + '.d.cts', base + '.d.ts' ]); default: } } const { logger: logger } = rslog; function emitAssets(assets, distPath) { for (const key of Object.keys(assets)){ const asset = assets[key]; fs_extra.outputFileSync(join(distPath, key), asset.source); } } async function emitIndex(code, distPath, prettier) { const distIndex = join(distPath, 'index.js'); if (prettier) { const minimized = await minify(code, { compress: false, mangle: false, ecma: 2019 }); if (!minimized.code) throw new Error('terser minify failed'); const formatted = await format(minimized.code, { filepath: distIndex }); await fs_extra.outputFile(distIndex, formatted); } else await fs_extra.outputFile(distIndex, code); } const getPackageJsonField = (json, key)=>{ const value = json[key]; return 'string' == typeof value ? value : null; }; const getTypes = (json)=>json && (getPackageJsonField(json, 'types') || getPackageJsonField(json, 'typing') || getPackageJsonField(json, 'typings') || getTypes(json.exports?.['.'])) || null; async function emitDts(task, externals) { const outputDefaultDts = ()=>{ fs_extra.outputFileSync(join(task.distPath, 'index.d.ts'), 'export = any;\n'); }; if (task.ignoreDts) return void outputDefaultDts(); if (task.copyDts) { const dtsFiles = fast_glob.sync('**/*.d.{ts,cts}', { cwd: task.depPath, absolute: false }); for (const dtsFile of dtsFiles)fs_extra.copySync(join(task.depPath, dtsFile), join(task.distPath, dtsFile)); return; } const getInput = ()=>{ const pkgPath = join(task.depPath, 'package.json'); const pkgJson = fs_extra.readJsonSync(pkgPath, 'utf-8'); const types = getTypes(pkgJson); if (types) return join(task.depPath, types); const directTypeFile = findDirectTypeFile(task.depEntry); if (directTypeFile) return directTypeFile; const depTypesPath = findDepPath(`${pkgNameToAtTypes(task.depName)}/package.json`); if (!depTypesPath) return null; const depTypesPkg = fs_extra.readJsonSync(depTypesPath, 'utf-8'); const depTypes = getTypes(depTypesPkg); return depTypes ? join(dirname(depTypesPath), depTypes) : null; }; const input = getInput(); if (!input) return void outputDefaultDts(); try { const inputConfig = { input, external: [ ...Object.keys(externals), ...task.dtsExternals, ...NODE_BUILTINS ], plugins: [ dts({ respectExternal: true, compilerOptions: { skipLibCheck: true, preserveSymlinks: false, composite: false, declarationMap: false, declaration: true, noEmit: false, emitDeclarationOnly: true, noEmitOnError: true, checkJs: false, target: task.target } }) ] }; const outputConfig = { dir: task.distPath, format: 'esm', exports: 'named', entryFileNames: 'index.d.ts' }; const bundle = await rollup(inputConfig); await bundle.write(outputConfig); } catch (error) { logger.error(`rollup-plugin-dts failed: ${task.depName}`); logger.error(error); } } function emitPackageJson(task, assets) { const packageJsonPath = join(task.depPath, 'package.json'); const packageJson = fs_extra.readJsonSync(packageJsonPath, 'utf-8'); const outputPath = join(task.distPath, 'package.json'); const pickedPackageJson = pick(packageJson, [ 'name', 'author', 'version', 'funding', 'license', ...task.packageJsonField ]); if (task.depName !== pickedPackageJson.name) pickedPackageJson.name = task.depName; if (task.copyDts) { const types = getTypes(packageJson); if (types) pickedPackageJson.types = types; } else pickedPackageJson.types = 'index.d.ts'; if (task.dtsOnly) pickedPackageJson.type = packageJson.type || 'commonjs'; else pickedPackageJson.type = 'commonjs'; if (assets['package.json']) try { Object.assign(pickedPackageJson, pick(JSON.parse(assets['package.json'].source), [ 'type' ])); } catch {} fs_extra.outputFileSync(outputPath, JSON.stringify(pickedPackageJson, null, 2)); } function emitLicense(task) { const licensePath = join(task.depPath, 'LICENSE'); if (fs_extra.existsSync(licensePath)) fs_extra.copySync(licensePath, join(task.distPath, 'license')); } function emitExtraFiles(task) { const { emitFiles } = task; for (const item of emitFiles){ const path = join(task.distPath, item.path); fs_extra.outputFileSync(path, item.content); } } function removeSourceMap(task) { const maps = fast_glob.sync(join(task.distPath, '**/*.map')); for (const mapPath of maps)fs_extra.removeSync(mapPath); } function renameDistFolder(task) { const pkgPath = join(task.distPath, 'package.json'); const pkgJson = fs_extra.readJsonSync(pkgPath, 'utf-8'); for (const key of [ 'types', 'typing', 'typings' ])if (pkgJson[key]?.startsWith('dist/')) { pkgJson[key] = pkgJson[key].replace('dist/', 'types/'); const distFolder = join(task.distPath, 'dist'); const typesFolder = join(task.distPath, 'types'); if (fs_extra.existsSync(distFolder)) fs_extra.renameSync(distFolder, typesFolder); } fs_extra.writeJSONSync(pkgPath, pkgJson); } async function prebundle(task, commonExternals = {}) { logger.start(`prebundle: ${task.depName}`); fs_extra.removeSync(task.distPath); if (task.beforeBundle) await task.beforeBundle(task); const mergedExternals = { ...DEFAULT_EXTERNALS, ...commonExternals, ...task.externals }; const nodeModulesPath = join(process.cwd(), 'node_modules'); const hasNodeModules = existsSync(nodeModulesPath); const enableCache = !process.env.CI && hasNodeModules; if (task.dtsOnly) emitPackageJson(task, {}); else { const { code, assets } = await ncc(task.depEntry, { minify: task.minify, target: task.target, externals: mergedExternals, assetBuilds: false, cache: enableCache ? join(nodeModulesPath, '.cache', 'ncc-cache') : false }); await emitIndex(code, task.distPath, task.prettier && !task.minify); emitAssets(assets, task.distPath); emitPackageJson(task, assets); } await emitDts(task, mergedExternals); emitLicense(task); removeSourceMap(task); renameDistFolder(task); emitExtraFiles(task); if (task.afterBundle) await task.afterBundle(task); logger.success(`prebundle: ${task.depName}\n\n`); } async function run(options = {}) { const config = await resolveConfig(options.config); const parsedTasks = parseTasks(config.dependencies, config.prettier); const filters = options.packages?.length ? new Set(options.packages) : null; for (const task of parsedTasks)if (!filters || filters.has(task.depName)) await prebundle(task, config.externals); } export { run };