使用 unbuild 搭建 JavaScript 构建系统
jcLee95
已入驻阿里云博客
邮箱 :291148484@163.com
简介:
本文是笔者阅读分析 elementPlus 项目时记录的。该项目用到了一个完全没有文档和资料的工具 unbuild。
为了搞清楚其功能,在github上看了一下该模块的源代码并记录之。
unbuild 是一个快速实现编译
ts
生成mjs
、cjs
以及相应的类型声明.d.ts
等的工具,它内部调用了 rollup 等其他工具,似乎只是简化了配置。
本文地址:https://blog.csdn.net/qq_28550263/article/details/129016838
目 录
3.3 Node.js 的运行时Typescript 和 ESM 支持:jiti
3.4 将字节转换为人类可读的字符串:pretty-bytes
1. unbuild 简介与安装
搭建前端项目时,我们经常需要寻找一些构建工具来辅助我们自动化地进行打包和完成其它工作,如 webpack、rollup、gulp、vite 等等。对于一些功能的需求比较简单的场景,我们用不着使用 webpack 之类的重型构建工具,完全可以考虑使用一些小工具完成。本文介绍的 unbuild 就是这样一个小工具,它是一个强大的基于 rollup 的捆绑器,支持 TypeScript 并生成 commonjs 和 module 格式 + 类型声明。它能够能够自动推断 package.json
中的 build
配置和条目。
你可以通过如下方式安装到项目依赖中:
npm i unbuild -D # 或 yarn add unbuild -D # 或 pnpm i unbuild -D
2. unbuild 的用法
2.1 配置文件 build.config.ts
unbuild CLI 基于配置文件 build.config.ts
工作,你应该手动在项目根目录中创建它,以便于执行脚本的时候被 unbuild 所找到。一个该文件的实例为:
import { defineBuildConfig } from 'unbuild' export default defineBuildConfig({ // 如果没有提供entries,将从package.json中自动推断出 entries: [ // 指定源文件入口,默认值为 './src/index' './src/index', // mkdist builder 传输文件到文件,保持原始源结构 { builder: 'mkdist', input: './src/package/components/', outDir: './build/components' }, ], // 指定输出目录,默认为 'dist' outDir: 'build', // 指定是否生成 .d.ts 声明文件 declaration: true, })
其中 defineBuildConfig
传入的对象可以配置以下内容:
配置项 | 类型 | 描述 |
rootDir | string | 指定根目录起始位置 |
entries | BuildEntry[]; | 指定源文件入口,默认值为 './src/index' |
clean | boolean | 输出前是否清空输出目录 |
declaration | boolean | 指定是否生成 .d.ts 声明文件 |
outDir | string | 指定输出目录,默认为 'dist' |
stub | boolean | 跳过 stub 的剩余部分 |
externals | string[] | 是否所有依赖项作为外部项添加(dependencies、peerDependencies) |
dependencies | string[] | 从pkg推断的依赖关系 |
peerDependencies | string[] | 从pkg推断的依赖关系 |
devDependencies | string[] | 从pkg推断的依赖关系 |
alias | { [find: string]: string;} | 由 unbuild 传给 rollup 的 alias 配置项 |
replace | { [find: string]: string;} | 由 unbuild 传给 rollup 的 replace 配置项 |
rollup | RollupBuildOptions | 由 unbuild 传给 rollup 的构建选项 |
hooks | Partial<BuildHooks> | |
preset | string | BuildPreset |
2.2 pacakage.json 与 cli 用法
{ "main": "./dist/index.cjs", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", "scripts": { "build": "unbuild", "dev": "pnpm run stub", "stub": "unbuild --stub" }, "devDependencies": { "unbuild": "^0.7.4" } }
3. 该模块用到的一些模块
这个部分是在分析 unbuild 模块源码中用到的一些库。
3.1 一款 await 的挂钩系统:hookable
3.1.1 简介与安装
该模块用于管理注册和调用钩子,你可以通过以下方式进行安装:
npm install hookable # or yarn add hookable # or pnpm install hookable
3.1.2 通过直接创建 hookable 实例使用
import { createHooks } from 'hookable' // 创建一个 hookable 实例 const hooks = createHooks() // 挂上 'hello' hooks.hook('hello', () => { console.log('Hello World' )}) // 调用 'hello' 钩子 hooks.callHook('hello')
3.1.3 通过从 Hookable 扩展你的基类使用
import { Hookable } from 'hookable' export default class FooLib extends Hookable { constructor() { // 调用父类进行初始化 super() // 用自定义 logger 初始化可挂钩 // super(consola) } async someFunction() { // 按顺序调用并等待“hook1”挂钩(如果有) await this.callHook('hook1') } }
在插件中,注册任意钩子:
const lib = new FooLib() // 注册“hook2”的处理程序 lib.hook('hook2', async () => { /* ... */ }) // 一次注册多个处理程序 lib.addHooks({ hook1: async () => { /* ... */ }, hook2: [ /* 也可以是一个数组 */ ] })
3.1.4 Hookable 类的接口解析
3.2 轻量级文件到文件转换器:mkdist
3.2.1 简介与安装
Bundling 库有时候不是最佳的选择,因为通过捆绑传输丢失现代语法,也让我们丢失原有的文件结构,也有时通过将css提取到全局dist(vue) 而丢失关键css。
即使不使用,依赖项也将总是从 bundle 绑包中导入(第二个捆绑步骤可能会解决这个问题,但这通常不会发生在开发中和有副作用的依赖项中)。
虽然有像 tsc
和 @babel/cli
这样的工具,但它们大多专注于传输文件,而不是保持源代码级别的质量。此外,它们缺乏对处理自定义扩展的支持,如。vue和复制assets
mkdist可以:
- 复制所有的 assets;
- 支持 Vue 单文件组件;
- 通过 esbuild 实现快速和最小的转换;
- .为
.ts
,.js
和.vue
文件生成d.ts
类型声明文件
你可以通过以下方式进行安装:
npm install mkdist # or yarn add mkdist # or pnpm install mkdist
3.2.2 mkdist 的用法
CLI 的语法格式
mkdist [rootDir] [--src=src] [--dist=dist] [--pattern=glob [--pattern=more-glob]] [--format=cjs|esm] [-d|--declaration] [--ext=mjs|js|ts]
3.3 Node.js 的运行时 Typescript 和 ESM 支持:jiti
3.3.1 简介与安装
jiti 是一款Node.js 的运行时 Typescript 和 ESM 支持模块,也就是说它可以让 Nodejs 运行时运行 Typescript 和 ESM 代码,该模块可以:
- 无缝typescript和ESM语法支持;
- ESM和CommonJS之间的无缝互操作性;
- 替换 require 的同步API;
- 超级苗条和零依赖;
- 智能语法检测,避免额外的转换;
- CommonJS缓存集成;
- 文件系统传输文件硬缓存;
- V8编译缓存;
- 自定义解析别名
你可以通过如下之一的方式进行安装:
npm install jiti # or yarn add jiti # or pnpm install jiti
3.3.2 jiti 的用法
1. 编程用法
const jiti = require("jiti")(__filename); jiti("./path/to/file.ts");
2. 命令行用法
若是全局安装了 jiti ,可以直接使用 jiti 命:
jiti index.ts
若只在当前项目中安装了 jiti ,可用 npx jiti
运行脚本文件
npx jiti index.ts
3.4 将字节转换为人类可读的字符串:pretty-bytes
3.4.1 简介与安装
该模块可用于打包时在终端更好地输出文件大小,可以通过如下方式进行安装:
npm install pretty-bytes # or yarn add pretty-bytes # or pnpm install pretty-bytes
3.4.2 用法
import prettyBytes from 'pretty-bytes'; prettyBytes(1337); //=> '1.34 kB' prettyBytes(100); //=> '100 B' // 以位为单位显示 prettyBytes(1337, {bits: true}); //=> '1.34 kbit' // 显示文件大小差异 prettyBytes(42, {signed: true}); //=> '+42 B' // 使用德语语言环境的本地化输出 prettyBytes(1337, {locale: 'de'}); //=> '1,34 kB'
附录
declare type BuildEntry = BaseBuildEntry | RollupBuildEntry | UntypedBuildEntry | MkdistBuildEntry;
interface BaseBuildEntry { builder?: 'untyped' | 'rollup' | 'mkdist'; input: string; name?: string; outDir?: string; declaration?: Boolean; } interface UntypedBuildEntry extends BaseBuildEntry { builder: 'untyped'; defaults?: Record<string, any>; } interface RollupBuildEntry extends BaseBuildEntry { builder: 'rollup'; } interface MkdistBuildEntry extends BaseBuildEntry { builder: 'mkdist'; format?: 'esm' | 'cjs'; ext?: 'cjs' | 'mjs' | 'js' | 'ts'; }
import { RollupReplaceOptions } from '@rollup/plugin-replace'; import { RollupAliasOptions } from '@rollup/plugin-alias'; import { RollupNodeResolveOptions } from '@rollup/plugin-node-resolve'; import { RollupJsonOptions } from '@rollup/plugin-json'; import { Options as Options$1 } from 'rollup-plugin-dts'; declare type RollupCommonJSOptions = Parameters<typeof commonjs>[0] & {}; 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; }
import { RollupOptions, RollupBuild } from 'rollup'; interface BuildHooks { 'build:prepare': (ctx: BuildContext) => void | Promise<void>; 'build:before': (ctx: BuildContext) => void | Promise<void>; 'build:done': (ctx: BuildContext) => void | Promise<void>; 'rollup:options': (ctx: BuildContext, options: RollupOptions) => void | Promise<void>; 'rollup:build': (ctx: BuildContext, build: RollupBuild) => void | Promise<void>; 'rollup:dts:options': (ctx: BuildContext, options: RollupOptions) => void | Promise<void>; 'rollup:dts:build': (ctx: BuildContext, build: RollupBuild) => void | Promise<void>; 'rollup:done': (ctx: BuildContext) => void | Promise<void>; 'mkdist:entries': (ctx: BuildContext, entries: MkdistBuildEntry[]) => void | Promise<void>; 'mkdist:entry:options': (ctx: BuildContext, entry: MkdistBuildEntry, options: MkdistOptions) => void | Promise<void>; 'mkdist:entry:build': (ctx: BuildContext, entry: MkdistBuildEntry, output: { writtenFiles: string[]; }) => void | Promise<void>; 'mkdist:done': (ctx: BuildContext) => void | Promise<void>; 'untyped:entries': (ctx: BuildContext, entries: UntypedBuildEntry[]) => void | Promise<void>; 'untyped:entry:options': (ctx: BuildContext, entry: UntypedBuildEntry, options: any) => void | Promise<void>; 'untyped:entry:schema': (ctx: BuildContext, entry: UntypedBuildEntry, schema: Schema) => void | Promise<void>; 'untyped:entry:outputs': (ctx: BuildContext, entry: UntypedBuildEntry, outputs: UntypedOutputs) => void | Promise<void>; 'untyped:done': (ctx: BuildContext) => void | Promise<void>; }
interface BuildContext { options: BuildOptions; pkg: PackageJson; buildEntries: { path: string; bytes?: number; exports?: string[]; chunks?: string[]; }[]; usedImports: Set<string>; hooks: Hookable<BuildHooks>; }
declare type BuildPreset = BuildConfig | (() => BuildConfig); interface BuildConfig extends DeepPartial<Omit<BuildOptions, 'entries'>> { entries?: (BuildEntry | string)[]; preset?: string | BuildPreset; hooks?: Partial<BuildHooks>; }