手写webpack打包基础实现
实现了最基础打包,源码可以查看https://github.com/huker/webpack-demo
- 整体思路
- 分析和处理
- 创建依赖关系
- AST解析
- 打包结果生成
- 添加loader
- 添加plugins
整体思路
写个简单的有引入的文件,然后npx webpack打包一下看下打包出来的内容是什么。
|
|
把打包出来的内容缩减了一下,重点是3个地方
- 实现的require方法
- 去执行的入口文件
- 传入的这个对象(解析出来的模块的依赖关系)
那就来尝试达到上面这个打包的效果,新建个项目叫my-pack好了,初始化后在package.json中添加bin,来指定命令行执行的路径。在执行文件头部加上如下的代码,来指定这代码是在node环境下跑的。
接着我们把my-pack链接到npm全局,这样才能在别的项目中命令行中使用,npm link
即可。最后在要打包的项目中,link下我们全局的my-pack,npm link my-pack
这样my-pack就安装到项目中了,可以npx my-pack
来执行。
|
|
分析和处理
读取webpack.config.js配置
这边可以参考在node环境下运行webpack打包。
所以我们也创建一个用来编译的类,叫Compiler,实例化的时候把config传进去,运行run就是进行编译
1234//平时node环境下运行webpack打包的代码const webpack = require('webpack');let compiler = webpack(mergeConfig);compiler.run();Compiler类中,需要保存下来入口和模块依赖,大概的逻辑和要做的事情如下伪代码
123456789101112131415161718192021class Compiler {constructor(config) {this.config = config;//打包文件中的入口 ./src/index.jsthis.entryId;//模块依赖this.modules = {};//配置中的入口路径this.entry = config.entry;//入口路径是相对路径 我们需要手动加上工作路径this.root = process.cwd();}buildModules(modulePath, isEntry) {...}emitBundle(){...}run() {//执行 并生成模块依赖this.buildModules(path.resolve(this.root, this.entry), true);//发射打包的文件this.emitBundle();}}
创建依赖关系
这一步就是buildModules的内容。
思路是调用buildModules首先传入的是入口文件,解析后得到模块源码和模块依赖,如果有依赖就继续解析,形成递归。
|
|
AST解析语法树
这一步就是parse的内容,解析读取到的模块的内容。
- 替换require,替换依赖的路径,把修改后的模板放进sourceCode
- 把依赖放进dependencies数组中
用到了几个库来做这件事:
- babylon 主要是把源码解析成AST
- @babel/traverse 遍历节点(遍历到对应的节点)
- @babel/types 替换遍历到的节点
- @babel/generator 替换好的结果生成
(traverse和generator是es6模块 引用的时候要require(‘@babel/traverse’).default 不然默认导出的是一个对象)
https://astexplorer.net/可以在这个网站直观的查看AST节点。
打包结果生成
- 获取打包后文件输出的路径
- 写一个打包后文件的ejs模板,把入口和模块依赖传进去
ejs模板把最开始webpack打包出来的内容拿过来修改下,入口的地方改成变量,模块依赖部分写个循环 - 写入文件
添加loader
思路是在读取每个依赖模块的时候,获取所有的rules,modulePath正则匹配下每个rule的test,匹配到就通过loader转化这个模块。
添加plugins
在实例化的时候apply所有plugins