Webpack完整打包流程分析

前言

webpack 在前端工程领域起到了中流砥柱的作用,理解它的内部实现机制会对你的工程建设提供很大的帮助(不论是定制功能还是优化打包)。

下面我们基于 webpack5 源码结构,对整个打包流程进行简单梳理并进行实现,便与思考和理解每个阶段所做的事情,为今后扩展和定制工程化能力打下基础。

一、准备工作

在流程分析过程中我们会简单实现 webpack 的一些功能,部分功能的实现会借助第三方工具:

  • tapable 提供 Hooks 机制来接入插件进行工作;
  • babel 相关依赖可用于将源代码解析为 AST,进行模块依赖收集和代码改写。
// 创建仓库
mkdir webpack-demo && cd webpack-demo && npm init -y

// 安装 babel 相关依赖
npm install @babel/parser @babel/traverse @babel/types @babel/generator -D

// 安装 tapable(注册/触发事件流)和 fs-extra 文件操作依赖
npm install tapable fs-extra -D

接下来我们在 src 目录下新建两个入口文件和一个公共模块文件:

mkdir src && cd src && touch entry1.js && touch entry2.js && touch module.js

并分别为文件添加一些内容:

// src/entry1.js
const module = require('./module');
const start = () => 'start';
start();
console.log('entry1 module: ', module);

// src/entry2.js
const module = require('./module');
const end = () => 'end';
end();
console.log('entry2 module: ', module);

// src/module.js
const name = 'cegz';
module.exports = {
   
  name,
};

有了打包入口,我们再来创建一个 webpack.config.js 配置文件做一些基础配置:

// ./webpack.config.js
const path = require('path');
const CustomWebpackPlugin = require('./plugins/custom-webpack-plugin.js');

module.exports = {
   
  entry: {
   
    entry1: path.resolve(__dirname, './src/entry1.js'),
    entry2: path.resolve(__dirname, './src/entry2.js'),
  },
  context: process.cwd(),
  output: {
   
    path: path.resolve(__dirname, './build'),
    filename: '[name].js',
  },
  plugins: [new CustomWebpackPlugin()],
  resolve: {
   
    extensions: ['.js', '.ts'],
  },
  module: {
   
    rules: [
      {
   
        test: /\.js/,
        use: [
          path.resolve(__dirname, './loaders/transformArrowFnLoader.js'), // 转换箭头函数
        ],
      },
    ],
  },
};
参考webpack视频讲解:进入学习

以上配置,指定了两个入口文件,以及一个 output.build 输出目录,同时还指定了一个 plugin 和一个 loader

接下来我们编写 webpack 的核心入口文件,来实现打包逻辑。这里我们创建 webpack 核心实现所需的文件:

// cd webpack-demo
mkdir lib && cd lib
touch webpack.js // webpack 入口文件
touch compiler.js // webpack 核心编译器
touch compilation.js // webpack 核心编译对象
touch utils.js // 工具函数

这里我们创建了两个比较相似的文件:compilercompilation,在这里做下简要说明:

  • compiler:webpack 的编译器,它提供的 run 方法可用于创建 compilation 编译对象来处理代码构建工作;
  • compilation:由 compiler.run 创建生成,打包编译的工作都由它来完成,并将打包产物移交给 compiler 做输出写入操作。

对于入口文件 lib/webpack.js,你会看到大致如下结构:

// lib/webpack.js
function webpack(options) {
   
  ...
}

module.exports = webpack;

对于执行入口文件的测试用例,代码如下:

// 测试用例 webpack-demo/build.js
const webpack = require('./lib/webpack');
const config = require('./webpack.config');

const compiler = webpack(config);

// 调用run方法进行打包
compiler.run((err, stats) => {
   
  if (err) {
   
    console.log(err, 'err');
  }
  // console.log('构建完成!', stats.toJSON());
});

接下来,我们从 lib/webpack.js 入口文件,按照以下步骤开始分析打包流程。

1、初始化阶段 - webpack

  • 合并配置项
  • 创建 compiler
  • 注册插件

2、编译阶段 - build

  • 读取入口文件
  • 从入口文件开始进行编译
  • 调用 loader 对源代码进行转换
  • 借助 babel 解析为 AST 收集依赖模块
  • 递归对依赖模块进行编译操作

3、生成阶段 - seal

  • 创建 chunk 对象
  • 生成 assets 对象

4、写入阶段 - emit

二、初始化阶段

初始化阶段的逻辑集中在调用 webpack(config) 时候,下面我们来看看 webpack() 函数体内做了哪些事项。

2.1、读取与合并配置信息

通常,在我们的工程的根目录下,会有一个 webpack.config.js 作为 webpack 的配置来源;

除此之外,还有一种是通过 webpak bin cli 命令进行打包时,命令行上携带的参数也会作为 webpack 的配置。

在配置文件中包含了我们要让 webpack 打包处理的入口模块、输出位置、以及各种 loader、plugin 等;

在命令行上也同样可以指定相关的配置,且权重高于配置文件。(下面将模拟 webpack cli 参数合并处理)

所以,我们在 webpack 入口文件这里将先做一件事情:合并配置文件与命令行的配置。

// lib/webpack.js
function webpack(options) {
   
  // 1、合并配置项
  const mergeOptions = _mergeOptions(options);
  ...
}

function _mergeOptions(options) {
   
  const shellOptions = process.argv.slice(2).reduce((option, argv) => {
   
    // argv -> --mode=production
    const [key, value] = argv.split('=');
    if (key && value) {
   
      const parseKey = key.slice(2);
      option[parseKey] = value;
    }
    return option;
  }, {
   });
  return {
    ...options, ...shellOptions };
}

module.exports = webpack;

2.2、创建编译器(compiler)对象

好的程序结构离不开一个实例对象,webpack 同样也不甘示弱,其编译运转是由一个叫做 compiler 的实例对象来驱动运转。

compiler 实例对象上会记录我们传入的配置参数,以及一些串联插件进行工作的 hooks API。

同时,还提供了 run 方法启动打包构建,emitAssets 对打包产物进行输出磁盘写入。这部分内容后面介绍。

// lib/webpack.js
const Compiler = require('./compiler');

function webpack(options) {
   
  // 1、合并配置项
  const mergeOptions = _mergeOptions(options);
  // 2、创建 compiler
  const compiler = new Compiler(mergeOptions);
  ...
  return compiler;
}

module.exports = webpack;

Compiler 构造函数基础结构如下:

// core/compiler.js
const fs = require('fs')
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值