深耕webpack中的Loader加载器、rules、plugins的前奏

138 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情

借用Linux内核发明人Linus Benedict Torvalds 的依据经典名言:RTFSC (Read The F**king Source Code)”。如果想要了解webpack的原理,那可能除了直接调试源码之外,没有比这更好的办法了。

之前几篇文章笔者分别从不同角度介绍了,npmnpm runnpm run devnpm 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的调试窗口中点击启动运行,入口处为如下内容:

image-20220809145659882.png

3.internal/Modules/cjs/loader 模块对应于 loader.js 下的 runMain 方法,如下:

image-20220809145834023.png

从图中的注释代码看,Bootstrap main module 为主模块启动程序。需要注意一点的就是这里的 process.argv 的参数内容如下:

image-20220809150351751.png

4.检查缓存中是否存在请求的模块

image-20220809151128754.png

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之后的大概流程如下:

webpack.drawio.png

这个过程实际上是基于node环境下按照配置找文件,并且编译转换的过程,如果这一点不理解,那可能整个开启的流程都是云里雾里的。

需要注意的是compile是在node中用c++实现的编译过程,它对应于CompileFunction,具体实现在node源码中可以找到。笔者的重点是webpack,所以对此不做更多篇幅的说明。

上述流程图中解析了从启动到动态加载所需要的脚本的过程。为下一步解析webpack.dev.conf.js中的配置内容的读取和应用过程进行分析。