You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// ConsoleLogOnBuildWebpackPlugin.jsconstpluginName='ConsoleLogOnBuildWebpackPlugin';classConsoleLogOnBuildWebpackPlugin{apply(compiler){compiler.hooks.run.tap(pluginName,compilation=>{console.log('The webpack build process is starting!!!');});// 在文件打包结束后执行compiler.hooks.done.tap(pluginName,(compilation)=>{console.log("整个webpack打包结束")})// 在webpack输出文件的时候执行compiler.hooks.emit.tap(pluginName,(compilation)=>{console.log("文件开始发射")})}}module.exports=ConsoleLogOnBuildWebpackPlugin;
手写一个简单的 webpack 编译流程
一、webpack 打包编译的主要流程
compiler 的流程:
二、准备工作
我们先建一个项目,目录如下:
然后我们先用 webpack 进行一次打包,分析一下 我们需要做什么工作
整理一下打包后的代码
最外层是一个立即执行函数,入参是所有的 modules(模块) list。传入的 modules 参数是一个对象。
我们要是实现的两个功能
import
变成__webpack_require__
三、开始搭建自己的 selfpack
四、实现转换 AST
因为有 import ,我们要把它替换成 webpack_require 。
遍历 AST ,把其中 import 语句引入的文件路径收集起来。
4.1 获取入口文件
npm install tapable
静态方法 MDN
将 selfpack.config.js 作为参数传入 Compiler 类,执行 run 方法。
通过 new 一个 Compilation 实例,调用 buildModule()
获取入口文件的结果:
第一步成功实现,下面实现第二步转成AST
4.2 转化成AST
这一步需要用到 @babel/parser , 将代码转化为 AST 语法树。
npm install @babel/parser
sourceType 代表我们要解析的是ES模块
到这一步我们很顺利!
这是整个文件的信息,而我们需要的文件内容在它的属性 program 里的 body 里。
看一下 body 的内容
这是
src/index.js
的一个 import 的 Node 属性,它的类型是 ImportDeclaration。4.3 解析主模块文件依赖
接下来,解析主模块。
遍历AST要用到 @babel/traverse
npm install @babel/traverse
traverse() 的用法:第一个参数就是 AST ,第二个参数就是配置对象
./src/data.js
,所以这里也需要做出相应的改变import data from './data.js'
==>require('./data.js')
==>require('./src/data.js')
relativepath: 这里获取的是依赖的文件路径
dependecies: 是收集的依赖对象,key 为 node.source.value ,value 为转换后的路径。
node.source.value: 指的是 from 后面的
'./data.js' 、'./random.js'
path.relative(from, to): 方法根据当前工作目录返回 ( from ) 到 ( to ) 的 ( 相对路径 )
process.cwd(): 返回 Node.js 进程的当前工作目录(path.resolve())
遍历之后 在 ast 里面找到节点类型,
通过 index.js 的 ast 获取到 index.js 文件的依赖(也就是data.js、random.js)
主模块的依赖路径已经全部找到啦!
走到这一步,离成功就不远了。
4.4 转换代码
接下来是转换代码,就是将修改后的 AST 转换成 JS 代码。
用到了 @babel/core 的 transformFromAst 和 @babel/preset-env。
安装一下
npm install @babel/core @babel/preset-env
@babel/preset-env
)里配置的模块类型,会返回转换后的代码@babel/preset-env 是将我们使用的 JS 新特性转换成兼容的代码。
此时 Parser.js 长这样
先来看下结果:
可以看到 const 成功转换成了 var,但是
require("./data.js")
引用的路径还没有和 modules 的 key 保持一致。4.5 递归收集依赖
我们怎么去确定一个模块应该包含什么信息呢?
首先要确定这个文件的唯一性,所以我们需要要的文件路径,因为这个是唯一的。
然后再来分析文件的内容:
所以我们需要的模块信息如下:
这里我们获取转换后的代码,并在 buildModule 返回一个对象,返回值结构如下:
但是 buildModule 只能收集一个模块的依赖,而我们最终的目的是收集所有依赖,所以我们要做一个递归处理。
修改一下 compiler.js
先来看一下 compile 中递归的方法:
buildModule
,得到主入口的文件模块来看一下最终的 modules
成功得到了包含所有模块的:路径、依赖、转换后的代码。
五、生成 webpack 模版文件
编译的最后一步就是 生成模板文件,并放到 output 目录。
我们直接借用文章开头那段打包出来的 dist/main.js 文件的内容,然后做些修改。
来看修改后的的 compilation.js
打包之后的文件内容,大体上长这样,还有点小瑕疵。
看下 emitFiles 函数的作用
完整的 compiler
编译后的代码如下:
走到这里一个简单 webpack 的编译流程代码就算写完啦。
把代码复制到浏览器测试一下
六、实现 webpack 的 Plugins 功能
怎么开发一个自定义的plugins?
webpack中内部实现了自己的一套生命周期,而 plugins 就是用 apply 来调用webpack里面提供的生命周期。
而 webpack 的生命周期主要就是 tapable 来实现的。
这里只用到了
SyncHook
,更多可参考这篇 Tapable 详解。我们修改一下官网的 ConsoleLogOnBuildWebpackPlugin.js 例子。
在 src同级目录新建一个 plugins
编写一个简单的 plugins
然后再配置文件引入这个 plugins
要让我们的 selfwebpack 支持 plugins ,还要做些改动。
在 compiler 函数一初始化的时候就定义自己的 webpack 的生命周期,并且在 run 期间进行相应的调用,这样我们就实现了自己的生命周期。
打印结果如下:
本文只实现了简单的编译原理,更多实现请看 webapck-github
对应的代码放到了这里 github
参考文章:手写webpack核心原理
The text was updated successfully, but these errors were encountered: