当前位置: 首页 > Web前端 > JavaScript

90行代码实现模块打包器

时间:2023-03-27 14:53:46 JavaScript

大家好,我是Kason。今天我们就来说说如何用90行代码实现一个现代的JS模块打包器。我们的packager虽然小,但是实现了webpack的核心功能。另外,我知道你看到大块代码会头疼,所以这篇文章都是关于图表的。看完有兴趣的话,这里是完整代码的仓库地址,只有90行代码。让我们找点乐子。生成依赖图如果应用程序是一团羊毛,那么入口文件就是线程。打包器做的第一件事是:从行首过滤整行。假设入口文件是entry.js://entry.jsimportafrom'./a.js';importbfrom'./b.js';控制台日志(a,b);他依赖a.js和b.js。em...有点太简单了,我们来展开a.js和b.js吧://a.jsimportcfrom'./c.js';//...//b.jsimportdfrom'./d.js';importefrom'./e.js';//...所以整个依赖关系是这样的:打包器将从入口文件,并尝试建立模块(即js文件)之间的依赖关系,也就是我们刚才说的,从线程的开头开始过滤整行的方向。通过分析模块代码中的导入声明语句,可以知道模块间的依赖关系。为了分析import声明语句,可以使用babel等编译工具将模块代码分解成AST(AbstractSyntaxTree)。遍历AST,类型为ImportDeclaration的节点为导入声明语句。最后,我们将AST重新转换成可执行的目标代码,可能还需要根据代码执行的宿主环境(通常是浏览器)对代码做一些转换。比如浏览器不支持import'./a.js'这样的ESM语法,那么我们就需要将ESM语法全部转换为CJS语法。//源代码import'./a.js';//转换后require('./a.js');因此,对于任何一个模块(js文件),都会经过:右边包含目标代码和模块间依赖关系的数据结构,称为资产。每个资产都可以通过模块间的依赖找到依赖的模块,重复这个过程,生成新的资产,最终形成整个应用中所有资产之间的依赖关系:应用的完整依赖关系称为依赖图(dependencygraph)。打包代码接下来只需要遍历依赖图,将所有资产的目标代码打包在一起即可。所有代码将被打包在一个立即执行的函数中:(function(modules){//打包代码})(modules)modules保存所有资产及其依赖。如果对模块的细节感兴趣,可以去文末仓库翻代码。刚才说了asset的objectcode是CJS规范,类似://entry.jsrequire('./a.js');require('./b.js');这意味着我们需要实现:require方法(用于引入依赖的其他资产的目标代码)模块对象(用于保存当前资产的目标代码执行后导出的数据)同时,为了防止不同资产的目标代码中的变量相互污染,每个目标代码都需要一个独立的作用域。我们将目标代码包装在一个函数中://我们操作的是一个字符串模板`function(require,module,exports){${asset.code}}`所以,最终的包装结果是:(function(modules){functionrequire(){//...}require(entryassetID)})(modules)这个字符串被包裹在浏览器