自己动手写简易JS打包器教程
**自己动手写简单JS打包器教程:轻松理解模块依赖与代码合并** 想知道Webpack等打包工具背后的秘密吗?本文将带你一步步实现一个简单的JavaScript打包器,无需复杂机制,仅用基础语法和递归遍历,即可解析CommonJS模块的`require`调用,构建依赖关系图,并生成自执行函数包裹的合并代码。教程将深入模块系统设计、依赖解析、模块图构建以及代码生成等关键环节,教你如何利用`fs`模块读取文件,通过DFS遍历依赖树,并为每个模块生成唯一key。通过这个教程,你将能深刻理解打包器的本质:收集依赖、重写`require`、合并代码,为后续学习更高级的打包技术打下坚实基础。即使是简化版实现,也能帮助你掌握核心要点,轻松应对路径解析和循环依赖等细节问题。
答案:通过递归解析CommonJS模块的require调用,构建依赖图并生成自执行函数包裹的合并代码,实现基础打包器。

实现一个简单的 JavaScript 打包器或模块加载器,核心是理解模块的依赖关系并将其合并为一个可执行文件。不需要借助 Webpack 或 Rollup 的复杂机制,我们可以用最基础的语法解析和递归遍历来完成。
1. 模块系统设计(CommonJS 风格)
我们采用类似 Node.js 的 require / module.exports 语法,这样结构清晰且易于解析。
例如,一个模块写成:
module.exports = function() {console.log("hello");
};
另一个文件通过 require('./xxx') 引入它。
2. 解析模块依赖
我们需要读取每个文件,提取其中的 require() 调用,找出依赖路径。可以使用正则简单匹配,虽然不完美,但对简单场景足够。
关键步骤:
- 读取入口文件内容
- 用正则 /require\(['"`](.+?)['"`]\)/g 提取依赖路径
- 将相对路径转为绝对路径,避免重复处理
- 递归分析每个依赖文件的依赖
3. 构建模块图(Module Graph)
把每个模块表示为一个对象,包含其内容、唯一 ID 和依赖列表。
代码示意:
function createModuleGraph(entry) {const modules = [];
const seen = new Set();
function visit(filepath) {
if (seen.has(filepath)) return;
seen.add(filepath);
const content = fs.readFileSync(filepath, 'utf-8');
const deps = [];
const requireRegex = /require\(['"`](.+?)['"`]\)/g;
let match;
while ((match = requireRegex.exec(content)) !== null) {
deps.push(resolve(dirname(filepath), match[1]));
}
modules.push({
id: pathToFileURL(filepath).href,
filepath,
content,
deps
});
deps.forEach(visit);
}
visit(entry);
return modules;
}
4. 生成打包代码
将模块图转换为自执行函数,模拟模块作用域和 require 行为。
输出结构如下:
(function(modules) {function require(id) {
const module = { exports: {} };
modules[id](module, module.exports, require);
return module.exports;
}
require(entryId);
})({ ./a.js: function(module, exports, require) { module.exports = "hi"; }, ./b.js: function(module, exports, require) { const a = require("./a.js"); console.log(a); } });
这样所有模块都在闭包内运行,互不污染。
5. 简化版实现要点
实际写一个最小可用版本,只需:
- 使用 fs 读文件(Node.js 环境)
- 用 DFS 遍历依赖树
- 为每个模块生成唯一 key(如相对路径)
- 拼接最终字符串,注入 runtime 的 require 函数
基本上就这些。虽不能处理 ES Module、动态导入或第三方库,但能帮助理解打包器本质:收集依赖、重写 require、合并代码。进阶可支持 transform(如 Babel)、code split 等。不复杂但容易忽略细节,比如路径解析和循环依赖。
以上就是本文的全部内容了,是否有顺利帮助你解决问题?若是能给你带来学习上的帮助,请大家多多支持golang学习网!更多关于文章的相关知识,也可关注golang学习网公众号。
Linux镜像源选择与设置教程
- 上一篇
- Linux镜像源选择与设置教程
- 下一篇
- Anaconda安装JupyterNotebook教程
-
- 文章 · 前端 | 9分钟前 |
- 类型数组与Web Workers多线程计算实战
- 134浏览 收藏
-
- 文章 · 前端 | 10分钟前 |
- CSS实现工具栏左右对齐与居中布局
- 239浏览 收藏
-
- 文章 · 前端 | 13分钟前 | html
- 点击复制内容,CLIPBOARD接口实现方法
- 218浏览 收藏
-
- 文章 · 前端 | 15分钟前 |
- HTML运行JS代码大全|实战指南
- 208浏览 收藏
