1.clone webpack代码到本地
const path = require('path');
module.exports = {
devtool: 'source-map', //配置source map,查看源文件
entry: { //构建入口
index: path.resolve(__dirname, 'src/index.js'),
},
output: { //构建输出到dist目录
devtoolModuleFilenameTemplate: '[resource-path]', //配置source map在浏览器中的展现方式
path: path.resolve(__dirname, 'dist/'),
},
module: { //配置loader,解析相应的文件
rules: [
{ test: /\.js$/, use: { loader: 'babel-loader', query: { presets: ['@babel/preset-env'] } } },
]
},
};
生成的
source map
文件2.启动调试项目
npm install
如果安装过程中出错,使用管理员
身份再重新使用安装
package.json
里有用来调试的script
,直接在vscode
中按F5
就可以启动调试了,
windows
用户会报错:
,
windows
用户需要把这两条行改成
"debug:build": "node --inspect-brk=5858 node_modules/webpack/bin/webpack.js",
"debug:watch": "node --inspect-brk=5859 node_modules/webpack --watch"
因为webpack
是直接安装在node_modules
下的。
3.调试webpack
资源加载流程
修改之后再按F5
启动调试,进入到node_modules/webpack/bin/webpack.js
这个文件的逻辑是判断一下
cli
是否安装了,然后去调webpack.run
;然后在
150行
打上断点,按F5
,这里去调webpack-cli
去了,然后webpack-cli
又调回到webpack/lib
,后面我们不去看
weboack-cli
,直接在webpack/lib/Compiler.js
的199行
打个断点总体来说webpack build
做了这几件事:
1.载入资源,这个过程会调用各个loader
,然后用loader
去载入资源文件,涉及到的模块是loader-runner
2.代码压缩,这里只看js
文件,涉及到的模块是uglifyjs-webpack-plugin
3.代码生成,就是在Compiler.js
中,emit
了一下,存到文件中
接下来看下compile
函数,跟一下资源加载过程,在536行
打个断点
这里会调用
make这个hook
,webpack
就是用hook
这种东西实现了插件化
,也就是webpack
只负责调一下,至于哪个插件实现了make这个hook
,webpack
不管的,单步调试
进去看一下跑到了
tapable
这个库中,这个tapable
就是webpack
实现hook
的模块,tapable
读起来比较费力,它是用生成代码的方式做的,这个应该是兼容性和功能性的考虑,因为es6
的proxy
能力有限,虽然能做一些拦截,但是做不到hook
这么强大也不好搞。继续,按
单步跳过(F10)
到下一行,然后
单步调试(F11)
,就会跑到这里来,这个VM1372
,就是tapable
临时生成的代码,一般出现这种代码的时候,就意味着有人用了new Function
这种东西,动态生成函数
接下来
单步跳过
,单步调试进入_fn0中
进去就到了
webpack/lib/SingleEntryPlugin.js
中的第43行
,compiler.hooks.make.tapAsync
(这个SingleEntryPlugin.js
实现了make这个hook
,然后Compiler.js中this.make.hooks
调用时,就调到了。总结:这里主要是从Compiler.js的compile
函数,看webpack
怎样从this.hooks.make.callAsync
调到SingleEntryPlugin.js
的compiler.hooks.make.tapAsync
实现的。
中间涉及到了tapable
,他提供了一个功能,让hook
可以在其他地方实现,webpack
只需要调一下这个hook
就行了。同样的hook
,可以实现多次,webpack
会放到一个队列中调用。hook
还有很多种类,原理是一样的,只不过处理方式不同,比如异步的,同步的。
继续下去,现在到这里了,这里直接调用了compilation.addEntry
,这是webpack
的另一个重要的文件,Compilation.js
进入看看,在
1028行
打上断点,这里主要看这个文件,开始加载资源,this._addModuleChain
的逻辑是这样的,先加载入口文件,然后再递归加载它依赖的文件,依赖的依赖的文件,就是通过require的import
的那些。进去看
_addModuleChain
,开始加载入口文件了,往下看,在943和953行
打上断点module
出来了,包含了如下这些内容request
是loader
,resource
是待加载的资源
,webpack
造出module
后再调用这个module
的build
方法来加载资源,十分的OO
,不直接干,而是先搞一个factory
,让factory
造module
,再让module
自己build
这里开始
build
this.buildModule
-> module.build
-> doBuild
最后到了
NormalModule.js
的265行
调了runLoaders
,从loader-runner
里require
进来的
const { getContext, runLoaders } = require("loader-runner");
进去看看
loader-runner
首先会载入那个loader
,然后在用loader
加载资源,例子中的是babel-loader
,资源文件就是src/index.js
,这个LOADER_EXECUTION
就是用loader
载入资源用的
fn
是loader,args
是源文件的内容,,然后webpack
调用这个loader
,babel-loader
可以看做一个函数把ES6
代码转译成ES5
。以上就是解析入口文件,一图胜千言
参考
webpack debug:https://github.com/thzt/debug-webpack
webpack群侠传系列:https://www.jianshu.com/p/de262ad255c3
深入浅出webpack:https://wangchong.tech/webpack/
webpack4.0官方文档:https://webpack.js.org/concepts