webpack学习笔记

webpack基础知识

webpack定义

webpack 是一个打包工具,他的宗旨是一切静态资源皆可打包。

webpack目的

自从出现模块化以后,大家可以将原本一坨代码分离到个个模块中,但是由此引发了一个问题。

每个 JS 文件都需要从服务器去拿,由此会导致加载速度变慢。

Webpack 最主要的目的就是为了解决这个问题,将所有小文件打包成一个或多个大文件。

webpack 是一个模块绑定器,它可以从AMD 模块UMDCommon JS 还有 ES 模块这些模块中获取依赖关系

webpack 将整个文件视为模块。 但是,请不要忘记它的主要目的:加载 ES 模块

webpack 的最终目标是将所有这些不同的源和模块类型统一起来,从而将所有内容导入 JavaScript 代码,并最生成可以运行的代码。

webpack核心概念

wepack V4.0.0 开始, webpack 是开箱即用的,在不引入任何配置文件的情况下就可以使用。

Entry

Webpackentry (入口点) 是收集前端项目的所有依赖项的起点。

实际上,这是一个简单的 JavaScript 文件。

Webpack 的默认入口点(从版本 4 开始)是 src/index.js,它是可配置的。

webpack 可以有多个入口点。

Output

出口,output 是生成的 JavaScript 和静态文件的地方。

Loaders

Loaders 可帮助 webpack 处理各种文件扩展名,例如 CSS,图像或 txt 文件

Loader 就是将 Webpack 不认识的内容转化为认识的内容

Loaders 的目标是在模块中转换文件(JavaScript 以外的文件)

注意点:webpack loaders 是从右到左执行的。

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      }
    ]
  }
};

const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ["style-loader", "css-loader", "sass-loader"]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "src", "index.html")
    })
  ]
};
  • css-loader: 解析 css 代码中的 url、@import 语法像 importrequire 一样去处理 css 里面引入的模块
  • style-loader:帮我们直接将 css-loader 解析后的内容挂载到 html 页面当中
  • sass-loader:加载 SASS / SCSS 文件并将其编译为 CSS

注意 loader 的出现顺序:首先是 sass-loader,然后是 css-loader,最后是 style-loader

loader 有一个参数可以修改优先级:enforce 参数,其值可以为: pre(优先执行) 或 post (滞后执行)。

webpack 本身并不知道如何转换 JavaScript 代码。

该任务已外包给 babel 的第三方 loader,特别是 babel-loader。

babel 是一个 JavaScript 编译器和 “编译器”。 babel 可以将现代 JS (es6, es7…) 转换为可以在(几乎)任何浏览器中运行的兼容代码。

同样,要使用它需要安装一些 Loader:

  • babel-core :把 js 代码分析成 ast ,方便各个插件分析语法进行相应的处理
  • babel-preset-env:将现代 JS 编译为 ES5
  • **babel-loader **:用于 webpack

注意: 即使没有 babel,webpack 也可以正常工作。 仅在执行 ES5 代码时才需要进行代码转换过程。

Plugins

Plugins 是 webpack 中的插件,就是对 webpack 现有功能的各种扩展,比如打包优化、文件压缩、提取 HTML,CSS 或设置环境变量等

与 Loader 用于转换特定类型的文件不同,插件(Plugin)可以贯穿 Webpack 打包的生命周期,执行不同的任务

Mode

webpack 有两种操作模式:开发(development)生产(production)

  • development:将 process.env.NODE_ENV 的值设置为 development,启用 NamedChunksPluginNamedModulesPlugin
  • production:将 process.env.NODE_ENV 的值设置为 production,启用 TerserWebpackPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPluginUglifyJsPlugin

在开发模式中,为了便于代码调试方便我们快速定位错误,不会压缩混淆源代码。

在生产模式下,webpack 进行了许多优化:

  • 使用 TerserWebpackPlugin 进行缩小以减小 bundle 的大小
  • 使用 ModuleConcatenationPlugin 提升作用域

在生产模式下配置 webpack 请打开 package.json 并添加一个 “build” 命令

运行 npm run build,webpack 会生成一个压缩的包。

Code splitting

代码拆分 (Code splitting) 是指针对以下方面的优化技术:

  • 避免出现一个很大的 bundle
  • 避免重复的依赖关系

通过代码拆分,开发人员可以决定仅在响应某些用户交互时加载整个 JavaScript 块,比如单击或路由更改 (或其他条件)。被拆分的一段代码称为 chunk

在 webpack 中有三种激活 code splitting 的主要方法:

  • 有多个入口点(适用于较小的项目,但是从长远来看它是不可扩展的)
  • 使用 optimization.splitChunks 选项
  • 动态导入

考虑一个使用 Moment.js 的 JS 应用程序,Moment.js 是流行的时间和日期 JS 库。

在项目文件夹中安装该库:

npm i moment

现在清除 src/index.js 的内容,并引入 moment 库:

import moment from "moment";

运行 npm run build 并查看控制的输出内容:

 main.js    350 KiB       0  [emitted]  [big]  main

整个 moment 库都绑定到了 main.js 中这样是不好的。

借助 optimization.splitChunks,我们可以从主包中移出 moment.js

要使用它需要在 webpack.config.js 添加 optimization 选项:

const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");

module.exports = {
  module: {
  // ...
  },
  optimization: {
    splitChunks: { chunks: "all" }
  },
  // ...
};

运行 npm run build 并查看运行结果:

        main.js   5.05 KiB       0  [emitted]         main
vendors~main.js    346 KiB       1  [emitted]  [big]  vendors~main

现在,我们有了一个带有 moment.js 的 vendors〜main.js,而主入口点的大小更合理。

注意:即使进行代码拆分,moment.js 仍然是一个体积较大的库。 有更好的选择,如使用 luxondate-fns

Code splitting 的一种更强大的技术是使用动态导入来有条件地加载代码。

在 ECMAScript 2020 中提供此功能之前,webpack 提供了动态导入。

这种方法在 Vue 和 React 之类的现代前端库中得到了广泛使用(React 有其自己的方式,但是概念是相同的)。

Code splitting 可用于:模块级别、路由级别

例如,你可以有条件地加载一些 JavaScript 模块,以响应用户的交互(例如单击或鼠标移动)。

或者可以在响应路由更改时加载代码的相关部分:

const getUserModule = () => import("./common/usersAPI");

webpack面试题

webpack 的作用(重要)

从官网上的描述我们其实不难理解,webpack 的作用其实有以下几点:

  • 模块打包。可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。利用打包我们就可以在开发的时候根据我们自己的业务自由划分文件模块,保证项目结构的清晰和可读性。
  • 编译兼容。在前端的 “上古时期”,手写一堆浏览器兼容代码一直是令前端工程师头皮发麻的事情,而在今天这个问题被大大的弱化了,通过 webpackLoader 机制,不仅仅可以帮助我们对代码做 polyfill,还可以编译转换诸如.less, .vue, .jsx 这类在浏览器无法识别的格式文件,让我们在开发的时候可以使用新特性和新语法做开发,提高开发效率。
  • 能力扩展。通过 webpackPlugin 机制,我们在实现模块化打包和编译兼容的基础上,可以进一步实现诸如按需加载,代码压缩等一系列功能,帮助我们进一步提高自动化程度,工程效率以及打包输出的质量。

有哪些常见的 Loader?

  • file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)
  • url-loader:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader 处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)
  • source-map-loader:加载额外的 Source Map 文件,以方便断点调试
  • svg-inline-loader:将压缩后的 SVG 内容注入代码中
  • image-loader:加载并且压缩图片文件
  • babel-loader:把 ES6 转换成 ES5
  • ts-loader: 将 TypeScript 转换成 JavaScript
  • awesome-typescript-loader:将 TypeScript 转换成 JavaScript,性能优于 ts-loader
  • sass-loader:将 SCSS/SASS 代码转换成 CSS
  • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
  • style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
  • postcss-loader:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀
  • eslint-loader:通过 ESLint 检查 JavaScript 代码
  • tslint-loader:通过 TSLint 检查 TypeScript 代码
  • vue-loader:加载 Vue.js 单文件组件
  • cache-loader: 可以在一些性能开销较大的 Loader 之前添加,目的是将结果缓存到磁盘里

有哪些常见的 Plugin?

  • html-webpack-plugin:简化 HTML 文件创建 (依赖于 html-loader)
  • web-webpack-plugin:可方便地为单页应用输出 HTML,比 html-webpack-plugin 好用
  • terser-webpack-plugin: 支持压缩 ES6 (Webpack4)
  • webpack-parallel-uglify-plugin: 多进程执行代码压缩,提升构建速度
  • mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代 extract-text-webpack-plugin)
  • serviceworker-webpack-plugin:为网页应用增加离线缓存功能
  • clean-webpack-plugin: 目录清理
  • ModuleConcatenationPlugin: 开启 Scope Hoisting
  • speed-measure-webpack-plugin: 可以看到每个 Loader 和 Plugin 执行耗时 (整个打包耗时、每个 Plugin 和 Loader 耗时)
  • webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)

Loader 和 Plugin 的区别?(重要)

Loader 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。 因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。

Plugin 就是插件,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

Loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test (类型文件)、loader、options (参数) 等属性。

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

简洁版
1、loader 主要用于转化某些类型的模块,它是一个转化器
2、plugin 是插件,它是对 webpack 本身的扩展,是一个扩展器

与 Loader 用于转换特定类型的文件不同,插件(Plugin)可以贯穿 Webpack 打包的生命周期,执行不同的任务

Webpack 构建流程(重要)

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

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

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

简单说

  • 初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler
  • 编译:从 Entry 出发,针对每个 Module 串行调用对应的 Loader 去翻译文件的内容,再找到该 Module 依赖的 Module,递归地进行编译处理
  • 输出:将编译后的 Module 组合成 Chunk,将 Chunk 转换成文件,输出到文件系统中

source map 是什么?(重要)

source map 是将编译、打包、压缩后的代码映射回源代码的过程。

打包压缩后的代码不具备良好的可读性,想要调试源码就需要 soucre map。

map 文件只要不打开开发者工具,浏览器是不会加载的。

线上环境一般有三种处理方案:

  • hidden-source-map:借助第三方错误监控平台 Sentry 使用
  • nosources-source-map:只会显示具体行数以及查看源代码的错误栈。安全性比 sourcemap 高
  • sourcemap:通过 nginx 设置将 .map 文件只对白名单开放 (公司内网)

注意:避免在生产中使用 inline-eval-,因为它们会增加 bundle 体积大小,并降低整体性能。

文件监听原理

在发现源码发生变化时,自动重新构建出新的输出文件。

Webpack 开启监听模式,有两种方式:

  • 启动 webpack 命令时,带上 --watch 参数
  • 在配置 webpack.config.js 中设置 watch:true

缺点:每次需要手动刷新浏览器

原理:轮询判断文件的最后编辑时间是否变化,如果某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等 aggregateTimeout 后再执行。

Webpack 的热更新原理(重要)

Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR

这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。

HMR 的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分)

实际上 WDS 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。

客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容 (文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该 chunk 的增量更新。

后续的部分 (拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?) 由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像 react-hot-loadervue-loader 都是借助这些 API 实现 HMR。

其实是开启了express应用,添加了对 webpack 编译的监听,添加了和浏览器的 websocket 长连接

当文件变化触发 webpack 进行编译并完成后,会通过socket消息告诉浏览器准备刷新。

而为了减少刷新的代价,就是不用刷新网页,而是刷新某个模块

webpack-dev-server 可以支持热更新,通过生成文件的 hash 值来比对需要更新的模块,浏览器再进行热替换

文件指纹是什么?怎么用?

文件指纹是打包后输出的文件名的后缀。

  • Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改 (图片)
  • Chunkhash:和 Webpack 打包的 chunk 有关,不同的 entry 会生出不同的 chunkhash (js)
  • Contenthash:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变 (css)

hash 代表每次 webpack 编译中生成的 hash 值,所有使用这种方式的文件 hash 都相同。每次构建都会使 webpack 计算新的 hash。

chunkhash 基于入口文件及其关联的 chunk 形成,某个文件的改动只会影响与它有关联的 chunk 的 hash 值,不会影响其他文件 contenthash 根据文件内容创建。

当文件内容发生变化时,contenthash 发生变化

如何优化 Webpack 构建速度(重要)

  • 使用高版本的 Webpack 和 Node.js

  • 压缩代码 terser-webpack-plugin 开启 parallel 参数

  • 通过 mini-css-extract-plugin 提取 Chunk 中的 CSS 代码到单独文件,通过 css-loader 的 minimize 选项开启 cssnano 压缩 CSS。

  • 图片压缩,配置 image-webpack-loader

  • 缩小打包作用域

  1. exclude/include (确定 loader 规则范围)
  2. resolve.modules 指明第三方模块的绝对路径 (减少不必要的查找)
  3. 合理使用 alias
  • 充分利用缓存提升二次构建速度
  1. babel-loader 开启缓存
  2. terser-webpack-plugin 开启缓存
  3. 使用 cache-loader 或者 hard-source-webpack-plugin
  • Scope hoisting,构建后的代码会存在大量闭包,造成体积增大,运行代码时创建的函数作用域变多,内存开销变大。Scope hoisting 将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突

webpack 如何实现持久化缓存

  • 服务端设置http缓存头(cache-control)
  • 打包依赖和运行时到不同的 chunk,即作为splitChunk,因为他们几乎是不变的
  • 延迟加载:使用 import()方式,可以动态加载的文件分到独立的 chunk, 以得到自己的 chunkhash
  • 保持hash值的稳定:编译过程和文件内通的更改尽量不影响其他文件 hash 的计算,对于低版本 webpack 生成的增量数字 id 不稳定问题,可用 hashedModuleIdsPlugin 基于文件路径生成解决

如何⽤ webpack 来优化前端性能?(重要)

⽤ webpack 优化前端性能是指优化 webpack 的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。

  • 压缩代码:删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤ webpack 的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩 JS ⽂件, 利⽤ cssnano (css-loader?minimize)来压缩 css
  • 利⽤CDN加速: 在构建过程中,将引⽤的静态资源路径修改为 CDN 上对应的路径。可以利⽤ webpack 对于 output 参数和各 loader 的 publicPath 参数来修改资源路径
  • Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动 webpack 时追加参数 --optimize-minimize 来实现
  • Code Splitting: 将代码按路由维度或者组件分块 (chunk), 这样做到按需加载,同时可以充分利⽤浏览器缓存
  • 提取公共第三⽅库: SplitChunksPlugin 插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值