携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情
借用Linux内核发明人Linus Benedict Torvalds 的依据经典名言:RTFSC (Read The F**king Source Code)”。如果想要了解webpack的原理,那可能除了直接调试源码之外,没有比这更好的办法了。
之前几篇文章笔者分别从不同角度介绍了,npm、npm run、npm run dev、npm run build的批处理过程。这一篇开始webpack原理的征途。
1.配置调试环境launch.json
{
"configurations": [
{
"name": "DebuggerWebpack",
"type": "node-terminal",
"request": "launch",
"command": "npm run dev",
"skipFiles": [],
"cwd": "${workspaceFolder}"
},
]
}
其中npm run dev会找到package.json下的:
{
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js"
},
}
2.然后在vscode的调试窗口中点击启动运行,入口处为如下内容:
3.internal/Modules/cjs/loader 模块对应于 loader.js 下的 runMain 方法,如下:
从图中的注释代码看,Bootstrap main module 为主模块启动程序。需要注意一点的就是这里的 process.argv 的参数内容如下:
4.检查缓存中是否存在请求的模块
Module._load = function(request, parent, isMain) {
let relResolveCacheIdentifier;
if (parent) {
...
}
}
//解析请求的文件,runMain调用的情况下 request 为解析的文件绝对路径,parent为空,isMain为true
const filename = Module._resolveFilename(request, parent, isMain);
//如果没有缓存的情况下, 对filename进行缓存
const cachedModule = Module._cache[filename];
if (cachedModule !== undefined) {
updateChildren(parent, cachedModule, true);
return cachedModule.exports;
}
//加载原生模块
const mod = loadNativeModule(filename, request, experimentalModules);
if (mod && mod.canBeRequiredByUsers) return mod.exports;
// Don't call updateChildren(), Module constructor already does.
const module = new Module(filename, parent);
if (isMain) {
process.mainModule = module;
module.id = '.';
}
Module._cache[filename] = module;
if (parent !== undefined) {
relativeResolveCache[relResolveCacheIdentifier] = filename;
}
let threw = true;
try {
if (enableSourceMaps) {
try {
module.load(filename);
} catch (err) {
rekeySourceMap(Module._cache[filename], err);
throw err;
}
} else {
module.load(filename);
}
threw = false;
} finally {
if (threw) {
delete Module._cache[filename];
if (parent !== undefined) {
delete relativeResolveCache[relResolveCacheIdentifier];
}
}
}
return module.exports;
};
//解析查找路径
Module._resolveLookupPaths = function(request, parent) {
if (NativeModule.canBeRequiredByUsers(request)) {
debug('looking for %j in []', request);
return null;
}
//检查node模块路径
...
const parentDir = [path.dirname(parent.filename)];
debug('looking for %j', parentDir);
return parentDir;
};
5.在Module._load之后的大概流程如下:
这个过程实际上是基于node环境下按照配置找文件,并且编译转换的过程,如果这一点不理解,那可能整个开启的流程都是云里雾里的。
需要注意的是compile是在node中用c++实现的编译过程,它对应于CompileFunction,具体实现在node源码中可以找到。笔者的重点是webpack,所以对此不做更多篇幅的说明。
上述流程图中解析了从启动到动态加载所需要的脚本的过程。为下一步解析webpack.dev.conf.js中的配置内容的读取和应用过程进行分析。