Unbuild
unbuild 是基于 Rollup 的强大的汇总打包器,支持 typescript 并生成 commonjs 和模块格式 + 类型声明。
特点
众所周知,单纯使用 rollup 作为项目打包器虽然灵活,但是配置起来十分复杂。因为除了要安装 rollup 本身,还得安装一系列的 rollup 插件才能满足一个基本项目的要求,例如 @rollup/plugin-alias、@rollup/plugin-typescript、@rollup/plugin-commonjs、rollup-plugin-dts 等等
而 unbuild 内置了 rollup 的常用的一些插件,让我们可以快速进行配置,unbuild 的示例配置如下:
import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
// If entries is not provided, will be automatically inferred from package.json
entries: [
// default
'./src/index',
// mkdist builder transpiles file-to-file keeping original sources structure
{
builder: 'mkdist',
input: './src/package/components/',
outDir: './build/components'
},
],
// Change outDir, default is 'dist'
outDir: 'build',
// Generates .d.ts declaration file
declaration: true,
})配置的部分 TS 定义:
interface BuildConfig extends DeepPartial<Omit<BuildOptions, "entries">> {
entries?: (BuildEntry | string)[];
preset?: string | BuildPreset;
hooks?: Partial<BuildHooks>;
}
interface BuildOptions {
name: string;
rootDir: string;
entries: BuildEntry[];
clean: boolean;
declaration?: boolean;
outDir: string;
stub: boolean;
externals: (string | RegExp)[];
dependencies: string[];
peerDependencies: string[];
devDependencies: string[];
alias: {
[find: string]: string;
};
replace: {
[find: string]: string;
};
failOnWarn?: boolean;
rollup: RollupBuildOptions;
}
interface RollupBuildOptions {
emitCJS?: boolean;
cjsBridge?: boolean;
inlineDependencies?: boolean;
replace: RollupReplaceOptions | false;
alias: RollupAliasOptions | false;
resolve: RollupNodeResolveOptions | false;
json: RollupJsonOptions | false;
esbuild: Options | false;
commonjs: RollupCommonJSOptions | false;
dts: Options$1;
}stub 模式
除了快速上手之外,unbuild 还有一个好玩的点是 stub 模式,可以用于命令行工具的开发。
在此之前,如果我们想要开发一个命令行工具,会通过 npm link 到全局,然后在本地测试这个工具。由于我们想要看到编辑代码后,命令行工具在本地效果实时更新,我们不得不使用打包工具的 watch 模式,对源代码进行监听和更新产物代码。
而现在 unbuild 有了 stub 模式,我们有更加简单和优雅的方式进行命令行工具的开发了。
假设我们的项目结构如下:
// your core code is hereimport { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
entries: [
'./src/index',
]
})然后执行 unbuild --stub
$ npx unbuld --stub
将会得到一个 dist/index.mjs 的文件
将会得到一个 dist/index.mjs 的文件
#!/usr/bin/env node
import jiti from "file:///path/to/your/project/node_modules/.pnpm/[email protected]/node_modules/jiti/lib/index.js";
/** @type {import("/path/to/your/project/src/index")} */
const _module = jiti(null, { interopDefault: true, esmResolve: true })("/path/to/your/project/src/index.ts");
export default _module;上面的这个文件,通过 jiti 实现了 TypeScript 的即时编译。
由于这个文件可以被 node 直接执行,可以被 npm link,这意味着我们不再需要 watch 模式监听源代码
mkdist 编译
所谓 file-to-file,就是打包之后的产物目录结构,保持和源码一致。例如假设我们的配置如下:
import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
entries: [
{
input: './src', // 指定目录而非文件,
builder: 'mkdist' // 使用 mkdist 作为打包器
}
]
})生成的目录可能是如下所示:
在开发命令行工具的时候,假设其中的我的源代码中存在如下代码,获取上一层的某个文件的绝对路径:
在开发命令行工具的时候,假设其中的我的源代码中存在如下代码,获取上一层的某个文件的绝对路径:
const pkgPath = path.resolve("../../package.json")
fse.readJSONFile(pkgPath)以 src/index 为入口打包之后,非 file-to-file 模式下,通常只会生成一个文件,例如 dist/index.mjs:
Hooks
此时就会导致,开发模式下和生产模式下,path.resolve("../../package.json") 执行的结果是不一样的,进而导致 fse.readJSONFile(pkgPath) 报错。
cjsBridge
从 ESM 到 CJS 的时候,注入的一些 polyfill,查看具体插件