likes
comments
collection
share

webpack5 源码详解 - 先导

作者站长头像
站长
· 阅读数 17

阅读源码先导

webpack源码很庞大,并且配置很丰富,阅读起来十分困难。有很多重要的功能模块,概念,独立于webpack外的包等。在阅读源码之前最好先把它们单独整理出来进行简单的介绍,更利于后面的阅读。

Compiler

webpack在运行的时候,会传入配置信息,返回实例化的Compiler对象。它在运行环境里是唯一的,所有主流程都由它来控制,比如开始编译,从入口开始处理模块,对于不同模块根据配置进行封装在一个组里,最后生成文件等等。当然,它只是操作流程,流程内的事情都是托付给其他模块处理。

Compilation

它能够调用许多处理模块的方法,并保存相应的构建的信息,比如modules,assets,moduleGraph, chunkGraph等。每次资源的重构建都会产生新的Compilation。

Module

在webpack里,万物都是模块,js文件,图片,视频等。普通的模块都会生成NormalModule对象,它可以调用方法解析模块,可以调用parser解析成AST(抽象语法树)等。NormalModule对象包含着Loader处理过后的代码,被哪个模块解析引入,模块解析的路径,引入其他模块的依赖等信息。而且在构建模块的时候,会生成moduleGraph来记录模块之间引用信息,可以保存导出信息,用于分析导出是否有被使用。

Chunk

在构建完之后,如果不进行相关配置,所有的module都会被打包在一个Chunk里,但也可以自行配置,用最优的方法来进行代码分块,优化网页的加载速度。

Loader

webpack能够处理js,并且webpack5还添加了许多能处理其他资源的能力。但是还有很多文件webpack本身不能处理,需要引入其他Loader转换成js,才能够被webpack操作。如果你想写一个Loader处理相关资源,可以阅读文档

Plugin

插件能够帮助webpack扩展功能,包括内置的很多功能也是通过插件的方式引入的,比如热模块替换,external,splitChunk等。它们统统扩展于Tapable,这样的好处在于能够很好的扩展webpack的功能并且和webpack解耦。如果你想写一个Plugin扩展功能,可以阅读这里

Parser

Parser用于解析资源,最常用到的parser就是JavascriptParser。它用acorn将js解析成AST(抽象语法树),并且对所有的声明或表达式进行遍历,找出相关模块的导入导出信息,添加至依赖里。也能收集导出导入情况,为后续的tree-shaking做准备。也能解析注释,实现webpack magic comment功能。

除了JavascriptParser,还有webassemblyParser,会调用@webassemblyjs/ast进行解析。还有cssParser , jsonParser等

独立库

除了webpack核心的代码,它还将很多功能独立成一个库,这些库不仅用于webpack,也能用于vanillaJs。

Tapable

这是webpack贯穿全文的库,基本重要的功能模块都有它的身影,功能模块与plugin的通信都需要靠它。在webpack里它叫做hooks,其实就是一个高级的EventEmitter,我也写了一篇详细的文章去介绍它。

如果你想写好一个插件,最好了解webpack主要模块的hooks都有哪些:

enhanse-resolve

enhanse-resolve是一个高度可配置的resolve库,它在webpack用来解析文件和loader的路径信息,也可以支持许多配置,比如我们最常用的配置就是resolve.alias,通过别名更方便导入资源,还有resolve.extensions增加文件扩展名等,webpack resolve options都是用于配置这个库。

loader-runner

loader-runner用于执行webpack loader。

webpack-sources

webpack-sources可以生成源码的sourceMap,hash等

运行环境

webpack v5.70.0

webpack.config.js

const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
    mode: "development",
    devtool: "source-map",
    entry: "./index.js",
    output: {
        path: path.join(__dirname, "./dist"),
        filename: "[fullhash].[name].bundle.js"
    },
    optimization: {
        usedExports: true
    },
    plugins: [new CleanWebpackPlugin()]
};

index.js

import { c_var } from "./module_c"

import(/* webpackExports: ["a"] */ "./module_a").then(({ a }) => {
    console.log(a)
})

var log_var = c_var;
console.log(log_var)

module_c.js

const c_var = "module_c";
const b_var = "unused";
export { c_var, b_var };

module_a.js

export const a = "moudle_a";
export const b = "unused";
export { a, b };

入口为./index.js

全篇执行目录context为c:\Users\Administrator\Desktop\webpack

流程

webpack的流程可以分为四个大块来讲

run

初始化,做好开始编译的准备工作。处理options,rules,注册插件,实例化compiler所需要的其他对象等。

make

通过入口文件开始构建模块,构建模块会分为几个步骤,解析路径,通过loader转换模块,进行paser收集依赖,递归处理所有外部依赖等。

seal

seal是属于Compilation的hooks,不像run,make,emit都属于Compiler,单独拿出来因为这个阶段也做了很多事,并且也是在make之后执行的。seal阶段不再接收模块,会根据ChunkGroup进行封装成一个或多个chunk,之后会做一些代码优化工作,生成文件Hash name等。

emit

输出文件阶段,webpack会根据不同的template来生成代码,一些代码会被替换,比如import,export会被替换成__webpack_require__,__webpack_exports__等。

webpack的很多代码的运行基于是否有配置,因为只是为了了解打包过程与方便调试,所以后续代码示例大部分会去掉一些无用代码,错误处理,为了实现cache的代码等。并且运行模式为"development",在"production"下很多配置会默认为true,会对代码压缩,不利于理解。