Skip to content
/ BundleTools/Unbuild
3/27/2024
6m
AI 摘要

unbuild 是基于 Rollup 的打包工具,内置常用插件,简化配置。支持 stub 模式用于命令行工具开发,提供 mkdist 实现 file-to-file 编译,保持源码结构。支持 cjsBridge 和多种钩子,提高灵活性。

Unbuild

unbuild 是基于 Rollup 的强大的汇总打包器,支持 typescript 并生成 commonjs 和模块格式 + 类型声明。

特点

众所周知,单纯使用 rollup 作为项目打包器虽然灵活,但是配置起来十分复杂。因为除了要安装 rollup 本身,还得安装一系列的 rollup 插件才能满足一个基本项目的要求,例如 @rollup/plugin-alias@rollup/plugin-typescript@rollup/plugin-commonjsrollup-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 here
import { 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,查看具体插件

Released under the MIT License.