上一篇,介绍了unbuild的插桩技术,并且该文章冲到了热榜第六。想来大家对unbuild还是满感兴趣的。故本文再接再厉,来分析unbuild的另一项技术点:bundleless
unbuild中实现该能力是依靠的mkdist包
何为bundleless
bundleless和bundle是相对应的,即是否打包合并
在传统的webpack构建过程中,是会从入口开始,在无拆分的情况下,你得到的将是一个完整的包含所有模块内容的.js文件
而较新的vite则利用浏览器对esm的支持,在开发阶段实现了按模块进行读取,而不是加载一整个构建后的文件(注:想要了解vite实现的可以关注笔者公众号,目前正在针对vite更新系列文章)
这二者的区别就是:是否合并bundle
本文目标
了解mkdist的核心实现流程
既是核心,笔者就不会对其进行细致拆分,实际上,笔者看这部分代码也是直接通过github看的。故若细节上有偏差,还望各位看官海涵
源码解析
将代码定位到src/make.ts,这是包的起点。根据笔者个人喜好,它大致可以分为如下几个阶段:初始化准备、编译、重写导入、生成入口、外部集成
- 初始化准备
这包括参数准备。如下,默认将src/index作为入口,将dist作为出口
ts
options.rootDir = resolve(process.cwd(), options.rootDir || ".");
options.srcDir = resolve(options.rootDir, options.srcDir || "src");
options.distDir = resolve(options.rootDir, options.distDir || "dist");
输入地准备。如下,创建一个空的dist目录。但下边这三个操作笔者个人感觉是有重复部分的,按文档来说,只需要emptyDir或者mkdirp一个操作就够了
ts
if (options.cleanDist !== false) {
await fse.unlink(options.distDir).catch(() => {});
await fse.emptyDir(options.distDir);
await fse.mkdirp(options.distDir);
}
最后就是文件目录准备。如下,mkdist将入口src下的所有文件扫描出来,并格式化处理成指定的格式
ts
const { globby } = await import("globby");
const filePaths = await globby(options.pattern || "**", {
absolute: false,
cwd: options.srcDir,
});
const files: InputFile[] = filePaths.map((path) => {
const sourcePath = resolve(options.srcDir, path);
return {
path,
srcPath: sourcePath,
extension: extname(path),
getContents: () => fse.readFile(sourcePath, { encoding: "utf8" }),
};
});
- 编译
这里说的编译其实并不准确,因为它的实际编译过程其实是在unbuild中的,不过笔者看它以loader来处理,且也包含内容处理的逻辑,故以此命名
ts
const { loadFile } = createLoader(options);
其实loader是什么并不需要关心,根据webpack的理念,它无非是针对各种文件类型的处理,事实上也确实如此
- 重写导入
这部分包含import、require等,核心就是对路径的重写
ts
output.contents = output.contents.replace(
/require\((["'])(.*)(["'])\)/g,
(_, head, id, tail) =>
"require(" +
head +
resolveId(output.path, id, cjsResolveExtensions) +
tail +
);
- 生成入口
最后一步,就是对这些路径进行收集,将其在dist目录下创建一份,然后将其作为新的入口收集起来等待被unbuild调用即可
ts
const writtenFiles: string[] = [];
await Promise.all(
outputs
.filter((o) => !o.skip)
.map(async (output) => {
const outFile = join(options.distDir, output.path);
await fse.mkdirp(dirname(outFile));
await (output.raw
? copyFileWithStream(output.srcPath, outFile)
: fse.writeFile(outFile, output.contents, "utf8"));
writtenFiles.push(outFile);
}),
);
- 外部集成
最后只需要在外部构建工具(这里指的unbuild)中集成即可实现bundleless,如下,拿到mkdist收集到的入口列表重写配置项即可
总结
通过对unbuild的分析,现在可以来呼应题目了,所谓3行指的是:
- 一行引入
你需要在你的构建工程中引入mkdist包
- 一行调用
你需要嵌入你的构建工程运行流程并对mkdist进行调用
- 一行重写
你需要将mkdist的返回值作为新的入口列表重写默认的构建工具入口点配置