webpack的核心是用于现代JavaScript应用程序的静态模块捆绑器。 当webpack处理您的应用程序时,它会在内部构建一个依赖关系图,它映射您的项目所需的每个模块并生成一个或多个包。

1. Webpack解析过程

首先,从入口文件开始,webpack开始解析文件中引用到的模块,这里使用JavaScript parser。JavaScript parser是可以读和理解JavaScript代码的工具,它生成一个更抽象的模型,称为AST(抽象语法树)。AST包含了很多我们代码的信息。接下来我们遍历AST找出当前模块依赖哪些模块,也就是当前模块import了哪些模块。在这一步也会使用当前文件对应的loader来编译当前文件,以便将当前文件包含到最终的bundle中。
接下来,我们将提取每个模块的依赖模块,原理大概是:

  1. 定义一个数组queue,将entry文件的相关信息存入queue,相关信息会包括entry的filename和entry的依赖项dependencies等。
  2. 遍历数组queue的每一项,对当前项的dependencies对应路径的文件再进行JavaScript parser,然后将这个文件的相关信息再存入queue中,从而最终遍历完成,构建出一个依赖图。
    最后,由这个依赖图构建进行js打包、图片打包、css打包等。简单而言,从入口文件开始,如果有依赖模块,则根据路径拿到该模块export的内容

2. loader

webpack通过loader支持用各种语言和预编译器编写的模块,loader向webpack描述如何处理非JavaScript模块并将这些依赖项添加到依赖关系图中。
常用的loader有:
babel-loader、css-loader、file-loader、less-loader
下面主要讲一讲babel的原理:
babel类似于一个编译器,将同种语言的高版本规则翻译成低版本规则,babel的转译过程也分为三个阶段:parsing、transforming、generating,以ES6代码转译为ES5代码为例,babel转译的具体过程如下:

ES6代码输入 ==》 babylon进行解析 ==》 得到AST
==》 plugin用babel-traverse对AST树进行遍历转译 ==》 得到新的AST树
==》 用babel-generator通过AST树生成ES5代码

3. optimization

optimization被用来执行一些优化的操作。
CommonsChunkPlugin和optimization.splitChunks:webpack4已经移除CommonsChunkPlugin可以用optimization.splitChunks代替当有多个入口文件时,可能每个入口文件都有相同的引用,所以可以优化将相同的引用给取出来,模块之间的公共引用给取出就叫做chunk,splitChunks可以具体配置如何拆分。
vendor是针对node_module中的第三方库,将公用的第三方库提出来,放在公共chunk中。

4. plugin

plugin被用来执行一些优化bundle、资产管理和环境变量注入的功能。
经常会用到的plugin有:UglifyjsWebpackPlugin、ExtractTextWebpackPlugin、HtmlWebpackPlugin、HotModuleReplacementPlugin
下面分别介绍一下:

  1. HtmlWebpackPlugin
    HtmlWebpackPlugin简化了html文件的创建
  2. ExtractTextWebpackPlugin
    ExtractTextWebpackPlugin将入口文件中所有引入css模块的部分,提出到一个单独的css文件中
  3. UglifyjsWebpackPlugin
    用来缩小JS的代码量
  4. HotModuleReplacementPlugin
    热模块更换(HMR)在应用程序运行时交换,添加或删除模块,无需完全重新加载。热加载的好处如下:
  • 保留在完全重新加载期间丢失的应用程序状态。
  • 只需更新已更改的内容,即可节省宝贵的开发时间。
  • 更快地调整样式 - 几乎可以与浏览器调试器中的样式更改相媲美。
    下面讨论一下热加载的原理:
    从应用程序来讲:
    应用程序要求HMR runtime检查更新。
    runtime异步的下载更新,并且通知应用程序
    应用程序让runtime去进行更新
    runtime程序异步的进行更新
    从编译器来讲:
    编译器的更新包括两个方面:
  • 更新 manifest
  • 更新chunks
    编译后需要保持chunks和module的能对应上
    manifest文件包含了新的编译后的hash和更新的chunks。
    从模块来讲:
    应用程序让HMR去进行更新模块,在模块中包含HMR的处理函数,当HMR通知模块更新,模块就进行更新。但是很多时候不强制在模块中写HMR接口,所以当模块没有HMR处理函数,更新就会冒泡,这就意味着,一个小模块的更新会让整个模块树更新。
    从runtime来讲:
    runtime可以跟踪父模块和子模块。runtime支持check和apply这两个方法。
    check方法发送HTTP请求更新manifest。如果请求失败,就没有内容可更新;请求成功,则下载所有需要更新的chunks,所有的模块更新都存储在runtime中。当所有的chunks更新都被下载下来并且准备被应用,runtime的状态切换到ready状态。
    apply方法将所有待更新的模块标记为无效,无效模块或者其父模块需要有更新处理函数,否则,将其父模块也标记为无效,并继续冒泡,一直冒到某个模块有更新处理函数,如果一直冒到entry文件都没有,则处理失败。
    之后,所有无效模块都被处理(通过配置处理程序)并卸载。runtime切换回空闲状态,一切都正常继续。
    上面讲的都是放屁,实现HMR需要开启一个server,然后当文件改变通过websocket或者sse通知浏览器,浏览器获取到改变了之后的内容,再更新到网页中去。

5. mode

这是webpack4的新特性,通过设置mode为production或development,可以启用与每个环境相对应的webpack内置优化。 默认值为production。

6. runtime

在一个用webpack构建的应用程序中,有三类代码:

  • 源代码
  • 第三方库的代码
  • 用于执行所有模块交互的webpack runtime和manifest
    下面主要介绍第三类代码。分为runtime和manifest。
    runtime和manifest是当运行在浏览器时,webpack用来连接模块化应用程序的代码,它包含在模块交互时连接模块所需的加载和解析逻辑。 这包括连接已经加载到浏览器中的模块以及延迟加载尚未加载的模块的逻辑。
    随着编译器进入,解析并映射出应用程序,manifest会记录所有模块的详细说明,包括模块的位置等。当运行时,runtime在解析并加载模块时,会需要从manifest中查询模块位置。

7. Webpack4的新特性

  1. 需要设置mode
    在webpack4中直接指定mode,可以针对不同环境的默认优化配置,比如development模式的NamedModulesPlugin和NamedChunksPlugin。
    production模式的UglifyJsPlugin、NoEmitOnErrorsPlugin、ModuleConcatenationPlugin
  2. webpack4删除了CommonsChunkPlugin插件,它使用内置API optimization.splitChunks
  3. 需要安装webpack -cli

8. development和production环境下配置的不同

development

  • source-map
    方便展示错误信息来自具体哪个文件,方便调试
  • webpack-dev-server
    提供一个简单的server
    production
  • UglifyjsWebpackPlugin