webpack 知识总结

作用

模块打包。可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序

编译兼容。

  • 通过webpack的Loader机制,不仅仅可以帮助我们对代码做polyfill,还可以编译转换诸如.less, .vue, .jsx这类在浏览器无法识别的格式文件,让我们在开发的时候可以使用新特性和新语法做开发,提高开发效率。

能力扩展。

  • 通过webpack的Plugin机制,我们在实现模块化打包和编译兼容的基础上,可以进一步实现诸如按需加载,代码压缩等一系列功能,帮助我们进一步提高自动化程度,工程效率以及打包输出的质量。

loader

常见的Loader

  • source-map-loader:加载额外的 Source Map 文件,以方便断点调试

  • babel-loader:把 ES6 转换成 ES5

  • eslint-loader:通过 ESLint 检查 JavaScript 代码

  • 样式loader

    • less-loader

    • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性

    • style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS

    • sass-loader

  • 文件loader

    • url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去

    • image-loader:加载并且压缩图片文件

    • file-loade把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件

    • raw-loader可以将文件已字符串的形式返回

loader的使用方式

  • 在配置文件webpack.config.js中配置 rule

  • 通过命令行参数方式 webpack –module-bind ‘txt=raw-loader’复制代码

  • 3:通过内联使用

概念

  • Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader,loader让webpack拥有解析和加载非js模块的能力

Loader在module.rules中配置,也就是说他作为模块的解析规则而存在。 类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件(test),使用什么加载(loader)和使用的参数(options)

Loader时要遵循单一原则

顺序

  • 自下而上,自右向左

编写loader

  • Loader就是⼀个函数,声明式函数,不能⽤箭头函数,因为要上到上下⽂的this,⽤到this的数据,该函数接
    受⼀个参数,是源码

webpack性能优化

常见功能

  • 清理构建文件 clean-webpack-plugin

  • css 样式兼容 px2torem rem 兼容

  • css 前缀 兼容 postcsss auto prefixer

测试

  • speed-measure-webpack-plugin

    • 分析整个打包总耗时 每个插件和loader的耗时情况
  • 定位问题

    • 使⽤ source map
  • webpack-bundle-analyzer 分析体积

scope hoisting 原理

  • 原理:将所有模块的代码按照引⽤顺序放在⼀个函数作⽤域⾥,然后适当的重命名⼀些变量以防⽌变量名冲突

  • 对⽐: 通过 scope hoisting 可以减少函数声明代码和内存开销

删除死代码

  • 可以通过在启动webpack时追加参数–optimize-minimize来实现

使用高版本的 webpack 和 Node.js

  • 构建时间降低了 60%-98%!

    • V8 带来的优化(for of 替代 forEach、Map 和 Set 替代 Object、includes 替代 indexOf)

    • 默认使用更快的 md4 hash 算法

    • webpacks AST 可以直接从 loader 传递给 AST,减少解析时间

    • 使用字符串方法替代正则表达式

资源内联的意义

  • · css 内联避免⻚⾯闪动

  • 减少 HTTP ⽹络请求数

  • 方法

    • ⼩图⽚或者字体内联 (url-loader)

    • HTML 和 JS 内联 raw-loader 内

    • CSS 内联

      • 借助 style-loader

      • html-inline-css-webpack-plugin

懒加载 JS 脚本的⽅式

  • CommonJS:require.ensure

  • ES6:动态 import(⽬前还没有原⽣⽀持,需要 babel 转换)

  • npm install @babel/plugin-syntax-dynamic-import –save-dev

基础库分离

  • exclude / exclude (排除一些不需要编译的文件)

  • 分包:设置 Externals

    • 思路:将 react、react-dom 基础包通过 cdn 引入,不打入 bundle 中

      • 利用CDN加速

        • 在构建过程中,将引用的静态资源路径修改为CDN上对应的路径

          • 可以利用webpack对于output参数

          • 各loader的publicPath参数来修改资源路径

    • externals (排除不需要被打包的第三方)

    • 方法:使用 html-webpack-externals- plugin

  • 利⽤ SplitChunksPlugin 进⾏公共脚本分离

    • Webpack4 内置的,替代CommonsChunkPlugin插件
  • 进一步分包:预编译资源模块

    • 思路:将 react、react-dom、redux、react-redux 基础包和业务基础包打包成一个文件

      • 方法:使用 DLLPlugin 进行分包,DllReferencePlugin 对 manifest.json 引用

        • 使用 DllReferencePlugin 引用 manifest.json
      • 动态链 ( DLL )

        • 利用DllPlugin和DllReferencePlugin预编译资源模块 通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加载进来。

多进程/多实例构建:资源并行解析可选方案

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

    • 原理:每次 webapck 解析一个模块,HappyPack 会将它及它的依赖分配给 worker 线程中
  • 多进程/多实例:使用 thread-loader 解析资源

    • 原理:每次 webpack 解析一个模块,thread- loader 会将它及它的依赖分配给 worker 线程中
  • 多进程/多实例:并行压

    • 方法一:使用 parallel-uglify-plugin 插件

    • 方法二:uglifyjs-webpack-plugin 开启 parallel 参数

    • 方法三:terser-webpack-plugin 开启 parallel 参数

缓存

  • 目的:提升二次构建速度

    • 缩小构建目标

      • babel-loader 不解析 node_modules
    • terser-webpack-plugin 开启缓存

    • 使用 cache-loader 或者 hard-source-webpack-plugin

  • ⽂件指纹如何⽣成

    • Hash:和整个项⽬的构建相关,只要项⽬⽂件有修改,整个项⽬构建的 hash 值就会更改

    • Chunkhash:和 webpack 打包的 chunk 有关,不同的 entry 会⽣成不同的 chunkhash 值

    • Contenthash:根据⽂件内容来定义 hash ,⽂件内容不变,则 contenthash 不变

压缩代码

  • css压缩

    • mini-css-extract-plugin)

    • cssnano(css-loader?minimize)来压缩css

  • html压缩

    • (html-webpack-plugin )
  • 图片压缩

    • 要求:基于 Node 库的 imagemin 或者 tinypng API

    • 配置 image-webpack-loader

    • Imagemin的压缩原理

      • pngquant: 是一款PNG压缩器,通过将图像转换为具有alpha通道(通常比24/32位PNG 文件小60-80%)的更高效的8位PNG格式,可显著减小文件大小。 pngcrush:其主要目的是通过尝试不同的压缩级别和PNG过滤方法来降低PNG IDAT数据 流的大小。 optipng:其设计灵感来自于pngcrush。optipng可将图像文件重新压缩为更小尺寸,而不 会丢失任何信息。 tinypng:也是将24位png文件转化为更小有索引的8位图片,同时所有非必要的metadata 也会被剥离掉
  • js压缩 (production模式自动开启)

    • webpack的UglifyJsPlugin和ParallelUglifyPlugin来压缩JS文件

构建体积优化:

  • 动态 Polyfill

    • polyfill.io 官方提供的服务
  • tree shaking(摇树优化)

    • 概念:1 个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打到 bundle 里面去,tree shaking 就是只把用到的方法打入 bundle ,没用到的方法会在 uglify 阶段被擦除掉

      • 使用:webpack 默认支持,在 .babelrc 里设置 modules: false 即可 · production mode的情况下默认开启
  • 减少文件搜索范围

    • 优化 resolve.modules 配置(减少模块搜索层级)

    • 优化 resolve.mainFields 配置

    • 优化 resolve.extensions 配置

    • 合理使用 alias

  • 无用的 CSS 如何删除掉?

    • PurifyCSS: 遍历代码,识别已经用到的 CSS class

    • uncss: HTML 需要通过 jsdom 加载,所有的样式通过PostCSS解析,通过 document.querySelector 来识别在 html 文件里面不存在的选择器

    • 使用 purgecss-webpack-plugin · https://github.com/FullHuman/purgecss-webpack-plugin 和 mini-css-extract-plugin 配合使用

SSR 的优势

  • 总结:服务端渲染 (SSR) 的核⼼是减少请求

  • 减少⽩屏时间

  • 对于 SEO 友好

  • 实现

    • ·使⽤ react-dom/server 的 renderToString ⽅法将

    • React 组件渲染成字符串

    • ·服务端路由返回对应的模板

  • webpack ssr 打包存在的问题

    • 浏览器的全局变量 (Node.js 中没有 document, window)

      • 组件适配:将不兼容的组件根据打包环境进⾏适配

      • ·请求适配:将 fetch 或者 ajax 发送请求的写法改成 isomorphic-fetch 或者 axi

    • 样式问题 (Node.js ⽆法解析 css)

      • ⽅案⼀:服务端打包通过 ignore-loader 忽略掉 CSS 的解析

      • ·⽅案⼆:将 style-loader 替换成 isomorphic-style-loader

    • 如何解决样式不显示的问题?

      • 使⽤打包出来的浏览器端 html 为模板

      • 设置占位符,动态插⼊组件

    • ⾸屏数据如何处理?

      • 服务端获取数据

      • 替换占位符

常见构建工具对比

懒加载 路由

懒加载前提的实现:ES6的动态地加载模块——import()。

调用 import() 之处,被作为分离的模块起点,意思是,被请求的模块和它引用的所有子模块,会分离到一个单独的 chunk 中。

  • webpack 打包生成的chunk有一下几种:

    • webpack当中配置的入口文件(entry)是chunk,可以理解为entry chunk;

    • 入口文件以及它的依赖文件通过code split (代码分割)出来的也是chunk(也就是我们这里一直讲到的),可以理解为children chunk;

    • 通过commonsChunkPlugin创建出来的文件也是chunk,可以理解为commons chunk;

距离实现懒加载(按需加载) 还差关键的一步——如何正确使用独立打包的子模块文件(children chunk)实现懒加载。这也是懒加载的原理

  • 借助函数实现懒加载(按需加载)

    • 无论使用函数声明还是函数表达式创建函数,函数被创建后并不会立即执行函数内部的代码,只有等到函数被调用之后,才执行内部的代码。
  • 只要将需要进行懒加载的子模块文件(children chunk)的引入语句(本文特指import())放到一个函数内部。然后在需要加载的时候再执行该函数。这样就可以实现懒加载(按需加载)。

路由懒加载

  • 将子组件加载语句封装到一个function中,将function赋给component。这样就可以实现Vue-router懒加载(按需加载)。 component: () => import( / webpackChunkName: “home” / ‘../views/Home.vue’)

import 经过webpack 打包后变成什么

import经过webpack打包以后变成一些Map对象,key为模块路径,value为模块的可执行函数;

代码加载到浏览器以后从入口模块开始执行,其中执行的过程中,最重要的就是webpack定义的webpack_require函数,负责实际的模块加载并执行这些模块内容,返回执行结果,其实就是读取Map对象,然后执行相应的函数;

当然其中的异步方法(import(‘xxModule’))比较特殊一些,它会单独打成一个包,采用动态加载的方式,具体过程:当用户触发其加载的动作时,会动态的在head标签中创建一个script标签,然后发送一个http请求,加载模块,模块加载完成以后自动执行其中的代码,主要的工作有两个,更改缓存中模块的状态,另一个就是执行模块代码。

babel

概念

  • babel是一个编译器,用于将ECMA2015+代码转换为向后兼容的javascript语法

执行过程

  • 源代码 经过编译器 parse 编译成抽象语法书AST

    • @babel/parser,编译器插件
  • AST 通过转换过程Transform 转换成修改后的AST

    • @babel/plugin-transform-react-jsx是将react中的jsx转换为react的节点对象

    • babel-traverse :用于对AST(抽象语法树Abstract Syntax Tree)的遍历

    • babel-types :用于检验,构建和变更AST的节点

  • 生成器generator 生成需要的代码

    • @babel/generator

  4.babel-core——整合基本插件,简化操作

预设(preset)——babel的插件套装

  •   那么问题来了新语法新特性那么多,难道我们要挨个去加吗?当然不是,babel已经预设了几套插件,将最新的语法进行转换,可以使用在不同的环境中,如下:

  •   babel-polyfill :JS标准新增的原生对象和API的shim,实现上仅仅是core-js和regenerator-runtime两个包的封装

AST 是怎么来的?

  • 分词:将整个代码字符串分割成语法单元数组

  • 语法分析:建立分析语法单元之间的关系

写过 babel 插件吗?用来干啥的?怎么写的 babel 插件

  • 正式环境下,应该将插件单独打包,发布到 npm 进行引用。

  • 我们也可以借助 Babel 插件的力量完成一些很麻烦的工作,比如代码版本升级,自定义语法,自定义规范等功能

  • 编写插件注意点(插件本身是一个函数)

    • babel:插件的入参,可以从中拿到 types 对象,操作 AST 节点,由于 types 对象太常用了,babel 大部分情况下写做 {types:t}。

    • visitor:插件核心对象,其中定义了插件生效的节点类型,以及生效方式。

    • Identifier 等方法名:声明了插件作用的 AST 节点类型,入参是 path 和 state,每个 visitor 可以包含多个这样的方法,每个方法的方法名称都是一种或多种的节点类型。

    • path:path 对象代表了当前节点的路径,通过 path 节点可以访获得当前的 node 对象,以及和该路径相关的对象,比如父节点、兄弟节点等。path 对象上还包含一些操作路径的方法等。

    • state:表示代码和插件的状态,一般通过该对象访问插件的配置项。

  • Babel 插件通过定义访问者(visitor)对象,定义了需要处理的节点类型以及执行的操作。babel-types 中定义了不同类型节点的构建方式,校验方式,遍历方式以及别名,这些信息可以帮助我们编写插件。理解 Babel 插件,最重要的就是理解 path 对象,该对象表示节点之间的关系,我们通过 path 对象可以拿到任意的节点信息,插件通过修改 path 对象达到修改 AST 结构的目的。

  • Babel 插件比较难以编写的点就在于它修改的是代码底层中的底层,我们需要保证代码的健壮性,否则很可能导致整个程序的崩溃。Babel 通过 .babelrc 文件配置生效的插件列表,目前官网也提供了很多插件,大部分是做语法转换的,我们在编写一个插件之前最好看看这些插件是怎么写的。

pulgin

常见pulgin

  • define-plugin:定义环境变量

  • commons-chunk-plugin:提取公共代码

  • uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码 压缩和混淆代码

  • html-webpack-plugin可以根据模板自动生成html代码,并自动引用css和js文件

  • extract-text-webpack-plugin 将js文件中引用的样式单独抽离成css文件

  • HotModuleReplacementPlugin 热更新

  • compression-webpack-plugin 生产环境可采用gzip压缩JS和CSS

  • happypack:通过多进程模型,来加速代码构建

  • webpack-bundle-analyzer 一个webpack的bundle文件分析工具,将bundle文件以可交互缩放的treemap的形式展示。

  • ProvidePlugin:自动加载模块,代替require和import

概念

  • Plugin可以扩展webpack的功能,让webpack具有更多的灵活性, 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

Plugin在plugins中单独配置。 类型为数组,每一项是一个plugin的实例,参数都通过构造函数传入。

编写pulgin

  • plugin是⼀个类,⾥⾯包含⼀个apply函数,接受⼀个参数,compiler compiler:webpack实例

  • //hooks.emit 定义在某个时刻
    compiler.hooks.emit.tapAsync(

构建流程

初始化参数:从配置文件和shell语句中读取参数

开始编译,从上一步得到参数初始化compiler对象,加载所以配置的插件,执行run方法开始编译

确定入口 : entry

编译模块,从入口出发,调用所以配置的loader对模块进行编译,在找依赖的模块,递归本步骤,直到所以的入口依赖文件都进过处理

完成模块编译:得到每个模块被编译后的最内容与依赖关系

输出资源:根据入口与模块依赖关系,组成一个嚯多个模块chunk,再把每个chunk 转换成一个单独的文件加入输出列表

输出完成:确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

webpack的热更新是如何做到的?说明其原理?(HRM)

通过webpack-dev-server创建两个服务器:提供静态资源服务的,和socket服务

express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)

socket server 是一个 websocket 的长连接,双方可以通信

当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk)

通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)

浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新

单页面与多页面配置

直接在entry中指定单页应用的入口即可

每⼀次⻚⾯跳转的时候,后台服务器都会给返回⼀个新的 html ⽂档, 这种类型的⽹站也就是多⻚⽹站,也叫做多⻚应⽤

  • 可以使用webpack的 AutoWebPlugin来完成简单自动化的构建

    • 每个页面都有公共的代码,可以将这些代码抽离出来,避免重复的加载。比如,每个页面都引用了同一套css样式表

    • 随着业务的不断扩展,页面可能会不断的追加,所以一定要让入口的配置足够灵活,避免每次添加新页面还需要修改构建配置

  • 利⽤ glob.sync 动态获取 entry 和设置 html-webpack-plugin 数量

webpack打包原理分析

我们实现了⼀个webpack_require 来实现⾃⼰的模块

化,把代码都缓存在installedModules⾥,代码⽂件以对象传递进来,key
是路径,value是包裹的代码字符串,并且代码内部的require,都被替换
成了webpack_require

现象:构建后的代码存在⼤量闭包代码

⼤量作⽤域包裹代码,导致体积增⼤(模块越多越明显)

运⾏代码时创建的函数作⽤域变多,内存开销变⼤

模块打包

  • 被 webpack 转换后的模块会带上⼀层包裹

  • ·import 会被转换成 __webpack_require

  • 进⼀步分析 webpack 的模块机制

    • · 打包出来的是⼀个 IIFE (匿名闭包)

    • · modules 是⼀个数组,每⼀项是⼀个模块初始化函数

    • · __webpack_require ⽤来加载模块,返回 module.exports

    • · 通过 WEBPACK_REQUIRE_METHOD(0) 启动程序