webpack 4 源码主流程分析(一):前言及总流程概览

3,999 阅读3分钟

原文首发于 blog.flqin.com。如有错误,请联系笔者。分析码字不易,转载请表明出处,谢谢!

前言

此系列文章作为笔记,用于记录分析 webpack 源码主流程的过程。

概览

目录

根据 webpack 构建流程及相关,本系列文章一共分为以下章节:

  1. 配置初始化
  2. 编译前的准备
  3. reslove 前的准备
  4. reslove 流程
  5. 构建 module(上)
  6. 构建 module(下)
  7. 生成 chunk
  8. 优化 chunk
  9. 资源的构建
  10. 文件的生成
  11. 打包后文件解析
  12. watch
  13. webpack 优化

流程图

webpack 构建流程图:

webpack 构建流程

本系列代码环境

  "devDependencies": {
    "@babel/core": "^7.7.5",
    "@babel/preset-env": "^7.7.6",
    "@fe_korey/test-loader": "^1.0.0",
    "babel-loader": "^8.0.6",
    "html-webpack-plugin": "^3.2.0"
  },
  "dependencies": {
    "webpack": "^4.41.2",
    "webpack-cli": "^3.3.10"
  }

版本不同,源码略微有差异。

本项目 demo 开源在github,欢迎交流学习。

分析源码前的一系列准备工作

采用 vscode 来打断点调试分析。

配置 vscode

//launch.json
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "runtimeExecutable": "npm",
      "runtimeArgs": ["run", "debug"],
      "port": 5858,
      "console": "externalTerminal",
      "skipFiles": ["<node_internals>/**/*.js"]
    }
  ]

这些配置是怎么来的?

配置 npm script

  "scripts": {
    "debug": "node --inspect-brk=5858 ./node_modules/.bin/webpack"
  },

了解 webpack 的插件架构

webpack 从配置初始化到打包完成定义了一个生命周期,在这个生命周期中的每一个阶段定义一些不同的功能。webpack 的流程同样也是一个事件驱动的架构,利用插件系统 tabpable,通过 发布订阅事件 来实现所有内部的,外部扩展的功能。

了解 webpack 的核心模块

webpack 的构建是通过 Compiler 控制构建流程,Compilation 解析,ModuleFactory 生成模块,Parser 解析源码,Template 渲染代码,最后输出打包后的文件。

了解 tapable

tabpable 本质就是一个事件发布订阅机制,支持同步异步,使用xxx.tap之类的来事件订阅,使用xxx.call之类的来进行事件发布。 相关文档查阅:

demo 准备

npm 安装

npm i webpack-cli webpack
npm i @babel/core @babel/preset-env babel-loader -D
npm i @fe_korey/test-loader -D

demo 文件

我们以 development 模式为例,暂时忽略支线剧情,只分析探索 webpack 的打包主流程。

//src/a.js
import { add } from 'Src/b';
import('./c.js').then(m => m.sub(2, 1));
const a = 1;
add(3, 2 + a);
//src/b.js
import { mul } from '@fe_korey/test-loader?number=20!Src/e';
export function add(a, b) {
  return a + b + mul(10, 5);
}
export function addddd(a, b) {
  return a + b * b;
}
//src/c.js
import { mul } from 'Src/d';
import('./b.js').then(m => m.add(200, 100));
export function sub(a, b) {
  return a - b + mul(100, 50);
}
//src/d.js
export function mul(a, b) {
  const d = 10000;
  return a * b + d;
}
//webpack.config.js
var path = require('path');

module.exports = {
  entry: {
    bundle: './src/a.js'
  },
  devtool: 'none',
  output: {
    path: __dirname + '/dist',
    filename: '[name].[chunkhash:4].js',
    chunkFilename: '[name].[chunkhash:8].js'
  },
  mode: 'development',
  resolve: {
    alias: {
      Src: path.resolve(__dirname, 'src/')
    }
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: 'babel-loader'
          }
        ]
      }
    ]
  }
};
//babel.config.js
module.exports = {
  presets: ['@babel/env']
};

一颗坚定且耐操的心

为什么要阅读它?

  • 因为可以让我们更好的理解海量配置,知道每一个配置在打包的具体哪个环节
  • 在对构建流程进行优化时能更清楚的根据过程思考优化点
  • 还可以学习下在这种大型项目里,如何实现稳定的架构和良好的扩展性
  • 对自定义开发一些 pluginloader 有更深刻的理解
  • 了解它的一些代码设计方式能给我们的日常搬砖带来一些新的启发

最重要的还是想满足自己的好奇欲,想知道在这犀利的打包背后,到底是怎么实现的。

webpack 里包含数不清的变量和钩子,海量插件,这些足以让你怀疑人生,请务必保持一颗耐操的心。 一切准备就绪后,进入 vscode 的调试模式!