webpack原理

工作原理概括

基本概念

在了解webpack原理前,需要掌握以下几个核心概念,以方便后面的理解:

  • Entry 入口,webpack执行构建的第一步将从Entry开始,可抽象成输入
  • Module 模块,在webpack里一切皆模块,一个模块对应着一个文件。webpack会从配置的Entry开始递归找出所有依赖的模块。
  • Chunk 代码块,一个Chunk由多个模块组合而成,用于代码合并与分割。
  • Loader 模块转换器,用于把模块原内容按照需求转换成新内容。
  • Plugin 扩展插件,在webpack构建流程中特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。

流程概括

webpack的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:

  1. 初始化参数:从配置文件和Shell语句中读取与合并参数,得出最终的参数;
  2. 开始编译: 用上一步得到的参数初始化Complier对象,加载所有配置的插件,执行对象的run方法开始执行编译;
  3. 确定入口: 根据配置中的entry找出所有入口文件;
  4. 编译模块:从入口文件出发,调用所有配置的Loader对模块进行翻译,再找出该模块依赖的模块,再递归本步骤知道所有入口依赖的文件都经过了本步骤的处理;
  5. 完成模块编译: 在经过第4步使用Loader翻译完所有模块后,得到了每个模块被翻译后的最终内容以及他们之间的依赖关系;
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  7. 输出完成: 在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

在以上过程中,webpack会在特定的时间点广播出特定的时间,插件在监听到感兴趣的时间后会执行特定的逻辑,并且插件可以调用Webpack提供的API改变Webpack的运行结果。

流程细节

Webpack的构建流程可以分为以下三个阶段:

  1. 初始化:启动构建,读取与合并配置参数,加载Plugin,实例化Complier.
  2. 编译:从Entry出发,针对每个Module串行调用对应的Loader去翻译文件内容,再找到该Module依赖的Module,递归地进行编译处理。
  3. 输出: 对编译后的Module组合成Chunk,把Chunk转换成文件,输出到文件系统。
    如果只执行一次构建,以上阶段将会按照顺序各执行一次。但在开启监听模式下,流程将变为如下:


    image.png

    在每个大阶段中又会发生很多时间,webpack会把这些事件广播出来供给Plugin使用,下面一一介绍

初始化阶段


事件名 解释
初始化参数 从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。 这个过程中还会执行配置文件中的插件实例化语句 new Plugin()。
实例化 Compiler 用上一步得到的参数初始化 Compiler 实例,Compiler 负责文件监听和启动编译。Compiler 实例中包含了完整的 Webpack 配置,全局只有一个 Compiler 实例。
加载插件 依次调用插件的 apply 方法,让插件可以监听后续的所有事件节点。同时给插件传入 compiler 实例的引用,以方便插件通过 compiler 调用 Webpack 提供的 API。
environment 开始应用 Node.js 风格的文件系统到 compiler 对象,以方便后续的文件寻找和读取。
entry-option 读取配置的 Entrys,为每个 Entry 实例化一个对应的 EntryPlugin,为后面该 Entry 的递归解析工作做准备。
after-plugins 调用完所有内置的和配置的插件的 apply 方法。
after-resolvers 根据配置初始化完 resolver,resolver 负责在文件系统中寻找指定路径的文件。

编译阶段


事件名 解释
run 启动一次新的编译
watch-run 和run类似,区别在于它是在监听模式下启动的编译,在这个事件中可以获取到是哪些文件发生了变化导致重新启动一次新的编译
compile 该事件是为了告诉插件一次新的编译将要启动,同时会给插件带上compiler对象。
compilation 当webpack以开发模式运行时,每当检测到文件变化,一次新的Compilation将被创建。一个Compilation对象包含了当前的模块资源、编译生成资源、变化的文件等。Compilation对象也提供了很多时间回调供插件做扩展。
make 一个新的Comilation创建完毕,即将从Entry开始读取文件,根据文件类型和配置的Loader对文件进行编译,编译完后再找出该文件依赖的文件,递归的编译和解析。
after-compile 一次Compilation执行完成
invalid 当遇到文件不存在、文件编译错误等异常时会触发该事件,该事件不会导致Webpack退出。

在编译阶段中,最重要的要数compilation事件了,因为在compilation阶段调用了Loader完成了每个模块的转换操作,在compilation阶段又包括很多小的事件,它们分别是:

事件名 解释
build-module 使用对应的Loader去转换一个模块
normal-module-loader 在用Loader对一个模块转换完后,使用acorn解析转换后的内容,输出对应的抽象语法树(AST),以方便Webpack后面对代码的分析
program 从配置的入口模块开始,分析其AST,当遇到require等导入其它模块语句时,便将其加入到依赖的模块列表,同时对新找出的依赖模块递归分析,最终搞清所有模块的依赖关系。
seal 所有模块及其依赖的模块都通过Loader转换完成后,根据依赖关系开始生成Chunk

输出阶段


事件名 解释
should-emit 所有需要输出的文件已经生成好,询问插件哪些文件需要输出,哪些不需要
emit 确定好要输出哪些文件,执行文件输出,可以在这里获取和修改输出内容
after-emit 文件输出完毕
done 成功完成一次玩的编译和输出流程
failed 如果在编译和输出流程中遇到异常导致webpack退出时,就会直接跳转到本步骤,插件可以在本事件中获取到具体的错误原因

在输出阶段已经得到了各个模块经过转换后的结果和其依赖关系,并且把相关模块组合在一起形成一个个Chunk。在输出阶段会根据Chunk的类型,使用对应模板生成最终要输出的文件内容。

输出文件分析


Webpack 输出的 bundle.js 是什么样子的吗? 为什么原来一个个的模块文件被合并成了一个单独的文件?为什么 bundle.js 能直接运行在浏览器中?

bundle.js能直接运行在浏览器中的原因在于输出的文件中通过webpack_require函数定义了一个可以在浏览器中执行的加载函数来模拟Node.js中的require语句。

原来一个个独立的模块文件被合并到了一个单独的bundle.js的原因在于浏览器不像Node.js那样快速地去本地加载一个个模块文件,而必须通过网络请求去加载还未得到的文件。如果模块数量很多,加载时间会很长,因此把所有模块都存放到了数组中,执行一次网络加载。

如果仔细分析webpack_require函数的实现,你还会发现webpack做了缓存优化:执行加载过的模块不会再执行第二次,执行结果会缓存在内存中,当某个模块第二次被访问时会直接去内存中读取被缓存的返回值。

分割代码时的输出


例如把源码中的main.js修改为如下:

// 异步加载show.js
import('./show').then((show) => {
    // 执行show函数
    show('webpack');
})

重新构建后会输出两个文件,分别是执行入口文件bundle.js和异步加载文件0.bundle.js
其中0.bundle.js内容如下:

// 加载在本文件中包含的模块
webpackJsonp(
    // 在其它文件中存放着的模块的ID
    [0],
    // 本文件所包含的模块
    [
        // show.js所对应的模块
        (function (module, exports) {
            function show(content) {
                window.document.getElementById('app').innerText = 'Hello,' + content;
            }
            module.exports = show;
        })
    ]
);

在使用了 CommonsChunkPlugin 去提取公共代码时输出的文件和使用了异步加载时输出的文件是一样的,都会有 webpack_require.e 和 webpackJsonp。 原因在于提取公共代码和异步加载本质上都是代码分割。

编写Loader


Loader就像一个翻译员,能把源文件经过转化后输出新的结果,并且一个文件还可以链式的经过多个翻译员翻译。
以处理SCSS文件为例:

  • SCSS源代码会先交给sass-loader把SCSS转换成CSS;
  • 把sass-loader输出的css交给css-loader处理,找出css中依赖的资源、压缩CSS等;
  • 把css-loader输出的css交给style-loader处理,转换成通过脚本加载的Javascript代码;

可以看出以上处理过程需要有顺序的链式执行,先sass-loader再css-loader再style-loader。

Loader的职责


由上面的例子可以看出:一个Loader的职责是单一的,只需要完成一种转换。如果一个源文件需要经历多步转换才能正常使用,就需要通过多个loader转换。在调用多个Loader去转换一个文件时,每个Loader会链式的顺序执行,第一个Loader将会拿到需处理的原内容,上一个Loader处理后的结果会传给下一个接着处理,最后的Loader将处理后的最终结果返回给Webpack.

所以,在拟开发一个Loader时,请保持其职责的单一性,你只需关心输入和输出。

Loader基础


由于Webpack是运行在Node.js之上的,一个Loader其实就是一个Node.js模块,这个模块需要导出一个函数。这个导出函数的工作就是获得处理前的原内容,对原内容执行处理后,返回处理后的内容。

一个最简单的Loader的源码如下:

module.exports = function(source) {
      // source为compiler传递给Loader的一个文件的原内容
      // 该函数需要返回处理后的内容,这里简单起见,直接把原内容返回了,相当于该Loader没有做任何转换
      return source
}

由于Loader运行在Node.js中,你可以调用任何Node.js自带的API,或者安装第三方模块进行调用:

const sass = require('node-sass')
module.exports = function(source) {
    return sass(source)
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,961评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,444评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,009评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,082评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,101评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,271评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,738评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,395评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,539评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,434评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,481评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,160评论 3 317
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,749评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,816评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,038评论 1 256
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,548评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,140评论 2 341

推荐阅读更多精彩内容

  • 工作原理概括 基本概念 在了解Webpack原理前,需要掌握以下几个核心概念,以方便后面的理解: Entry:入口...
    oWSQo阅读 4,766评论 0 8
  • webpack 核心 核心概述 entry 入口文件:js 代码文件,可执行的 node 模块或打包的入口文件。 ...
    coolheadedY阅读 3,462评论 1 6
  • GitChat技术杂谈 前言 本文较长,为了节省你的阅读时间,在文前列写作思路如下: 什么是 webpack,它要...
    萧玄辞阅读 12,662评论 7 110
  • Ba la la la ~ 读者朋友们,你们好啊,又到了冷锋时间,话不多说,发车! webpack是什么...
    王饱饱阅读 583评论 0 9
  • 版权声明:本文为博主原创文章,未经博主允许不得转载。 webpack介绍和使用 一、webpack介绍 1、由来 ...
    it筱竹阅读 11,021评论 0 21