webpack笔记

问题列表

  • webpack是用来解决什么问题的 ?
  • 它有什么亮点 ?
  • loader 和 plugin 有什么区别 ?
  • 运行原理 ?
  • 如何编写 Loader?
  • Webpack 和 Rollup 有什么相同点与不同点?

webpack 作用

本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

根据官网的高度概述,我们可以简单理解webpack是一个针对JavaScript项目的打包工具。

简单浏览一遍官方文档,再看看上面这张图,就会发现webpack的关键点在于模块处理打包,在webpack构建的流程中,通过loader处理非 JS 文件,将其转换为 webpack 能够处理的有效模块,使用各式各样的plugin在构建时的关键步骤进行一些操作(比如按需加载,代码压缩等),最终输出浏览器能使用的静态资源。

这里思考一下为什么需要模块化呢?

答案

那怎么理解模块化呢?

webpack中,核心思想就是一切皆模块,比如说一张图片,一个SCSS文件。

这样做的好处就是能够清晰地描述出各个模块之间依赖关系,便于webpack对模块进行组合、打包。

为什么是 webpack

这些年前框框架、工具非常多,那为什么webpack更受青睐呢?

这里来分析一下其它的工具

  • 基于任务执行的工具

    比如说:gulpgrunt

    这些工具能够自动执行指定的任务。它们简单高效,社区活跃,有着丰富的插件,可以方便打造各种工作流。

  • 基于模块化打包的工具

    比如说:browserifywebpackrollup

    在 Node 环境下,如果需要引用组件,使用require关键字即可,这类工具就是这个模式,还可以实现按需加载、异步加载模块

  • 整合型工具

    比如说:FIS3cooking

    这类工具使用了多种技术栈实现的脚手架工具,好处是即开即用,缺点就是它们约束了技术选型,并且学习成本相对较高。

为什么选用 webpack

  • 大多数团队在开发新项目时会采用紧跟时代的技术,这些技术几乎都会采用“模块化+新语言+新框架”,webpack可以为这些新项目提供一站式的解决方案
  • webpack 有良好的生态链和维护团队,能提供良好的开发体验和保证质量
  • webpack 被全世界的大量 Web 开发者使用和验证,能找到各个层面所需的教程和经验分享

核心概念

  • entry: 入口
    webpack执行构建的第一步就从入口开始,可以抽象理解为输入
  • module: 模块
    webpack中一切皆模块,一个文件就是一个模块,webpack会从入口开始递归遍历找出所有依赖的模块
  • chunk: 代码块
    一个Chunk由多个模块组合而成,它是代码合并、分割的产物
  • loader: 模块转换器
    webpack使用它把原内容转换成浏览器能够使用的文件
  • plugin: 插件拓展
    webpack在构建流程中的特定时机注入扩展逻辑来改变构建结果
  • output: 输出结果
    webpack经过一系列处理后,输出的最终结果

loader

webpack 只能理解JavaScriptJSON文件,这是webpack开箱可用的自带能力。

loaderwebpack能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。

用法

处理文件时可以使用多个loader,loader的执行顺序和配置中的顺序是相反的。也就是说最后一个loader先执行,它接收源文件作为参数,处理之后把处理结果传给下一个loader

常用的 loader

  • css-loader
    加载CSS、支持模块化、压缩、文件导入等特性
  • style-loader
    CSS代码注入到JavaScript中,通过DOM操作去加载CSS
  • file-loader
    把文件输出到一个文件夹中,在代码中通过相对URL去引用输出的文件
  • url-loader
    它和 filr-loader 类似,但是能在文件很小的情况下以base64的方式把文件内容注入到代码中
  • babel-loader
    让开发者能够在项目中使用新的JavaScript特性

plugin

插件是webpack支柱功能。

插件目的在于解决loader无法实现的其他事。

webpack自身也是构建于插件系统之上。

原理

webpack运行的生命周期中会广播出许多事件,插件可以监听这些事件,在合适的时机通过webpack提供的API改变输出结果,在这个过程中插件可以访问到compilecompilation,通过钩子拦截webpack的执行。

常用的 plugin

  • clean-webpack-plugin
    每次打包后自动清理output.path内的文件,只保留本次构建的结果
  • html-webpack-plugin
    创建一个html文件,并把webpack打包后的静态文件自动插入到这个html文件当中
  • DefinePlugin
    这是一个定义全局变量的插件,定义的变量可以在webpack打包范围内任意JavaScript环境内访问
  • MiniCssExtractPlugin
    CSS单独打包成一个文件,它为每个包含CSSJavaScript文件都创建一个CSS文件,结合html-webpack-plugin插件,以link的形式插入到html文档中
  • webpack-merge
    该插件用于合并配置
  • webpack-bundle-analyzer
    项目打包后进行性能分析

构建流程和原理

构建之前先了解几个名词:

  • Compiler:编译管理器,webpack启动后会创建compiler对象,该对象一直存活直到结束退出
  • Complilation:单次编译过程的管理器,比如watch=true时,运行过程中只有一个compiler,但每次文件变更触发重新编译时,都会创建一个新的compilation对象
  • Dependence:依赖对象,webpack基于该类型记录模块间依赖关系
  • Module:webpack内部所有资源都会以module对象形式存在,所有关于资源的操作、转译、合并都是以module为基本单位进行的
  • Chunk:编译完成准备输出时,webpack会将module按特定的规则组织成一个一个的chunk,这些chunk某种程度上跟最终输出一一对应

构建流程:

  • 初始化阶段

    • 初始化参数:从配置文件、配置对象、Shell参数中读取,进行错误检查,与默认配置结合得出最终的参数
    • 创建编译器对象:用上一步得到的参数创建Compiler对象
    • 初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化RuleSet集合、加载配置的插件等
    • 开始编译:执行compiler对象的run方法
    • 确定入口:根据配置中的entry找出所有的入口文件,调用compilition.addEntry将入口文件转换为dependence对象
  • 构建阶段

    • 编译模块:根据entry对应的dependence创建module对象,调用loader将模块转移为标准JS内容,调用JS解释器将内容转换为AST对象,从中找出该模块依赖的模块,再递归这个步骤直到所有入口依赖的文件都经过了本步骤的处理
    • 完成模块编译:上一步递归处理所有能触达到的模块后,得到了每个模块被翻译后的内容以及它们之间的依赖关系图
  • 生成阶段

    • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转换成一个单独的文件加入到输出列表。这个步骤是可以修改输出内容的最后机会
    • 写入文件系统:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

源码中的一些重要方法:

  • webpack_require: 我们在模块化开发的时候,通常会使用ES Module或者CommonJS规范导入导出以来的模块,webpack在打包的时候,会统一替换成自己的__webpack_require__来实现模块的引入和导出,从而实现模块缓存机制,以及抹平不同模块规范之间的一些差异性
  • webpack_modules:存放了编译后的各个文件模块的JS内容
  • webpack_module_cache:用来做模块缓存

配置优化

webpack配置虽然简单,但是配置项非常多,每个配置项的取值可能也有很多的选择,故而优化的空间很大。

讲一些平常用的多的配置优化,主要分为以下三种:

  • 优化构建速度

    • 使用多进程多实例解析资源

      • thread-loader
      • happypack
    • 使用DLLPlugin对基础依赖库进行分包,减少编译次数

      DllPlugin动态链接库插件,其原理是把网页依赖的基础模块抽离出来打包到dll文件中,当需要导入的模块存在于某个dll中时,这个模块不再被打包,而是去dll中获取。dll中大多包含的是常用的第三方模块,如react、react-dom,所以只要这些模块版本不升级,就只需被编译一次。

    • externals,从输出的bundle中排除依赖

      从 CDN 引入 jQuery,而不是把它打包

    • 缩小范围

      配置loader时,指定loader的作用目录或者需要排除的目录,通过使用include和exclude两个配置项

    • noParse,使用noParse进行忽略的模块文件中不会解析import、require等语法

    • 使用缓存

      • babel-loader
      • cache-loader
  • 优化构建结果

    • 构建结果分析,使用webpack-bundle-analyzer插件

      可以看到项目各模块大小,进行按需优化

      stat表示文件的输入大小,parsed表示文件的输出大小,gzip表示通过gzip压缩运行解析的包/模块大小

  • webpack默认支持,使用tree shaking擦除无用代码

    移除JavaScript上下文中的未引用代码

  • 开启scope hosting减少函数申明和内存开销

    这个名词直译过来就是作用域提升,它把所有的代码按照引用顺序放在一个函数作用域里面,适当的重命名变量防止冲突

  • 优化运行时体验

    • 入口分割点分割,多页打包

    • 使用splitChunksPlugin分离页面公共文件,抽取公共代码

    • 代码懒加载

    • prefetch,在浏览器空闲的时候进行资源的拉取

    • preload,提前加载后面会用到的关键资源

总结来看,可以使用大量插件对编译过程进行优化。

参考

官网

当面试官问Webpack的时候他想知道什么

一文吃透 Webpack 核心原理

构建工具

书目:

  • 深入浅出 Webpack