🔥 webpack打包优化方向指南(理论篇)

您所在的位置:网站首页 webpack多入口打包优化 🔥 webpack打包优化方向指南(理论篇)

🔥 webpack打包优化方向指南(理论篇)

2023-09-23 15:51| 来源: 网络整理| 查看: 265

对一位合格的前端开发工程师来说,完成业务功能的需求开发只是基本的要求,能够及时准确地发现系统中存在的性能瓶颈,并且给出合适的解决方案,这才是区分初,中级前端工程师和高级前端工程师的重要依据。

对于什么是webpack,这里我就不多做解释,要是你还不知道,那本片文章就不适合现在的你阅读。

webpack链接,看完之后记得过来继续阅读

webpack5 基本配置

我们先来看一份基本的配置,基于webpack5的webpack.config.js文件。

/* * webpack.config.js */ const { resolve } = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { // 模式 mode: "development", // 入口 entry: "./src/index.js", // 出口 output: { filename: "built.js", path: resolve(__dirname, "build"), // 自定义输出 静态资源文件名(图片) assetModuleFilename: "assets/[hash][ext]", }, // 模块 module: { rules: [ // loader的配置 { test: /\.css$/, // 使用loader对文件进行处理 /** * use数组中的执行顺序是 从右到左 从下到上 依次执行 * style-loader 创建style标签,将样式文件引入到header中 * css-loader 将css模块变成commonjs模块加载到js中, 文件内容是样式字符串 */ use: ["style-loader", "css-loader"], }, { test: /\.less$/, use: [ "style-loader", "css-loader", // 将less文件编译成css文件 // 需要下载 less-loader和less "less-loader", ], }, { test: /\.(png|svg|jpg|jpeg|gif)$/i, // webpack 5 内置了资源类型,已经废弃了之前的 url-loader 和 file-loader type: "asset/resource", }, { test: /\.html$/, // 处理html中的图片文件 引入img文件进而让url-loader处理 loader: "html-loader", }, { // 处理其他资源 exclude: /\.(html|js|css|less|png|svg|jpg|jpeg|gif)$/i, type: "asset/resource", }, ], }, // 插件 plugins: [ // HtmlWebpackPlugin // 默认会创建一个空的html文件,会自动引入打包完成的所有资源。 // 需要有结构的html文件 new HtmlWebpackPlugin({ template: "./src/index.html", }), ], /** * 下载 yarn add webpack-dev-server --dev * 运行 npx webpack serve */ devServer: { // 项目构建后的路径 contentBase: resolve(__dirname, "build"), // 自动打开浏览器 open: true, // 端口号 port: 5555, // 开启gzip压缩 compress: true, }, };

上面的代码是我们这篇文章的最基础的代码,后边的打包优化就是基于这个文件来进行配置的。

HMR 热更新

模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除 模块,而无需重新加载整个页面。

优点:

保留在完全重新加载页面期间丢失的应用程序状态。 只更新变更内容,以节省宝贵的开发时间。 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。

问题图片

HRM问题.gif

我们可以从上图看到,我们在改变样式的时候,我们的js文件重新执行了一遍,这样对于我们的项目来说是一个问题,我们的优化方向是改变那个文件,只有该文件重新加载。

代码

根据上边的问题,我们只需要改变devServer处的代码。

/* * webpack.config.js */ ... module.exports = { ... devServer: { // 项目构建后的路径 contentBase: resolve(__dirname, "build"), // 自动打开浏览器 open: true, // 端口号 port: 5555, // 开启gzip压缩 compress: true, // 新增---> 开启热更新 // 模块热替换功能会在程序运行过程中,替换,添加或删除模块,而无需重新加载整个页面。 hot: true, }, };

效果图片

HRM解决.gif

devtool 源码调试方式

选择一种 source map 格式来增强调试过程。不同的值会明显影响到构建(build)和重新构建(rebuild)的速度。

对于开发环境的源码调试

eval-source-map - 初始化 source map 时比较慢,但是会在重新构建时提供比较快的速度,并且生成实际的文件。行数能够正确映射,因为会映射到原始代码中。它会生成用于开发环境的最佳品质的 source map。

对于生产环境的源码调试

(none)(省略 devtool 选项) - 不生成 source map。这是一个不错的选择。

Rule.oneOf 匹配规则

loader的匹配规则。

/* * webpack.config.js */ ... module.exports = { ... // 模块 module: { rules: [ // loader的配置 { oneOf: [ // 以下的loader只会执行匹配的文件一次。 { test: /\.css$/, // 使用loader对文件进行处理 /** * use数组中的执行顺序是 从右到左 从下到上 依次执行 * style-loader 创建style标签,将样式文件引入到header中 * css-loader 将css模块变成commonjs模块加载到js中, 文件内容是样式字符串 */ use: ["style-loader", "css-loader"], }, { test: /\.less$/, use: [ "style-loader", "css-loader", // 将less文件编译成css文件 // 需要下载 less-loader和less "less-loader", ], }, { test: /\.(png|svg|jpg|jpeg|gif)$/i, // webpack 5 内置了资源类型,已经废弃了之前的 url-loader 和 file-loader type: "asset/resource", }, { test: /\.html$/, // 处理html中的图片文件 引入img文件进而让url-loader处理 loader: "html-loader", }, { // 处理其他资源 exclude: /\.(html|js|css|less|png|svg|jpg|jpeg|gif)$/i, type: "asset/resource", }, ], }, ], }, ... }

如此写法,在打包时loader的匹配将提高速度。

文件缓存

在生产环境时,我们可以将我们打包的css和js等资源缓存到浏览器中,以提高我们第二次进入的页面是的速度。所以我们得对webpack.config.js进行配置,对js文件和css文件和图片文件进行配置缓存。

首先我们得下载如下loader:

yarn add babel-loader @babel/core @babel/preset-env mini-css-extract-plugin --dev

优化步骤

将css文件从打包文件中提取为单独的文件。 对js文件使用babel缓存。

提取css并文件资源缓存

/* * webpack.config.js */ ... // 新增插件 const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { ... // 模式 mode: "production", output: { filename: "built.[contenthash:10].js", ... }, ... // 模块 module: { rules: [ // loader的配置 { oneOf: [ // 以下的loader只会执行匹配的文件一次。 { test: /\.css$/, // 将 style-loader 替换 use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.less$/, use: [ // 将 style-loader 替换 MiniCssExtractPlugin.loader, "css-loader", "less-loader", ], }, ... // 新增js的loader { test: /\.js$/, exclude: /node_modules/, use: { loader: "babel-loader", options: { presets: ["@babel/preset-env"], // 开启babel缓存 // 第二次构建时,会读取之前的缓存 // 用来缓存 loader的执行结果。之后的webpack 构建,将会尝试读取缓存, // 来避免在每次执行时,可能产生的、高性能消耗的 Babel // 重新编译过程(recompilation process)。 cacheDirectory: true, }, }, }, ], }, ], }, // 插件 plugins: [ ... //新增插件 new MiniCssExtractPlugin({ filename: "css/built.[contenthash:10].css", }), ], };

hash: 每次wepack构建时会生成一个唯一的hash值。

问题: 因为js和css同时使用一个hash值。 如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)

chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样。

问题: js和css的hash值还是一样的 因为css是在js中被引入的,所以同属于一个chunk

contenthash: 根据文件的内容生成hash值。

不同文件hash值一定不一样,让代码上线运行缓存更好使用。

新增jsloader并设置缓存

/* * webpack.config.js */ ... module.exports = { ... // 模块 module: { rules: [ // loader的配置 { oneOf: [ ... // 新增js的loader { test: /\.js$/, exclude: /node_modules/, use: { loader: "babel-loader", options: { presets: ["@babel/preset-env"], // 开启babel缓存 // 第二次构建时,会读取之前的缓存 // 用来缓存 loader的执行结果。之后的webpack 构建,将会尝试读取缓存, // 来避免在每次执行时,可能产生的、高性能消耗的 Babel // 重新编译过程(recompilation process)。 cacheDirectory: true, }, }, }, ], }, ], }, ... };

接下来我们书写测试代码。新建server.js文件,创建新的服务器进行测试。下载express框架。

yarn add express --dev

/* * server.js */ const express = require("express"); const server = express(); // 缓存一个小时 server.use(express.static("build", { maxAge: 1000 * 3600 })); server.listen(5555, () => { console.log("服务器启动成功!", "http://localhost:5555/"); });

检查缓存步骤

进行webpack打包 webpack 启动服务器 node server.js http://localhost:5555/

效果图片

文件缓存.gif

tree shaking 去除无用代码

使用前提

必须使用ES6模块化 开启production环境

优点: 在打包生产环境时,可以将我们未使用的代码进行忽略,从打包文件中删除我们未使用的代码,减小打包文件的体积。

tree shaking 只要我们满足前面的两个前提,webpack在打包时我自动进行无效代码的删除。

新增测试文件

/* * testTreeShaking.js */ export const test1 = () => { console.log("test1"); }; export const test2 = () => { console.log("test2"); };

修改文件

/* * index.js */ ... import { test1 } from "./testTreeShaking"; test1(); // 未引入test2函数。打包时会忽略。 /* * webpack.config.js */ ... module.exports = { ... // 修改打包模式 mode: 'production', ... };

package.json中配置

/* * ackage.json */ "sideEffects": false // 没有副作用(都可以进行tree shaking) // 可能会把css / @babel/polyfill (副作用)文件干掉 "sideEffects": ["*.css", "*.less"]

webpack打包,查看打包文件如下图:

image.png

可以看到,test2函数并没有打包进入打包文件。

code Split 代码分割

代码分割分为三种:

多入口文件会自动进行代码分割 optimization.splitChunks 控制代码分割 optimization.splitChunks + import() 进行代码分割

优点: 进行代码分割可以有效拒绝js文件过于庞大。

多入口文件会自动进行代码分割

/* * webpack.config.js */ ... module.exports = { ... entry: { // 多入口:有一个入口,最终输出就有一个bundle index: "./src/index.js", test: "./src/testTreeShaking.js", }, ... };

webpack打包查效果,如下图:

image.png

optimization.splitChunks控制代码分割

/* * webpack.config.js */ ... module.exports = { ... // 新增代码 optimization: { /* 1. 可以将node_modules中代码单独打包一个chunk最终输出 2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk */ splitChunks: { //这表明将选择哪些 chunk 进行优化。当提供一个字符串,有效值为 all,async 和 initial。 //设置为 all 可能特别强大,因为这意味着 chunk 可以在异步和非异步 chunk 之间共享。 chunks: "all", }, }, ... };

webpack打包查效果,如下图:

image.png

optimization.splitChunks + import() 进行代码分割

/* * webpack.config.js */ ... module.exports = { ... // 新增代码 optimization: { /* 1. 可以将node_modules中代码单独打包一个chunk最终输出 2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk */ splitChunks: { //这表明将选择哪些 chunk 进行优化。当提供一个字符串,有效值为 all,async 和 initial。 //设置为 all 可能特别强大,因为这意味着 chunk 可以在异步和非异步 chunk 之间共享。 chunks: "all", }, }, ... }; /* * index.js */ /* 通过js代码,让某个文件被单独打包成一个chunk import动态导入语法:能将某个文件单独打包 */ import("./testTreeShaking").then( (res) => { console.log("res", res); res.test1(); } );

webpack打包查效果,如下图:

image.png

文件懒加载 预加载

区别

使用文件的再加载 浏览器空闲时先进行加载

修改文件

/* *index.html */ ... 加载testTreeShaking文件 ... /* *index.js */ console.log("加载index文件"); /* *testTreeShaking.js */ export const test1 = () => { console.log("test1"); }; export const test2 = () => { console.log("test2"); }; console.log("加载index文件");

文件懒加载

/* *index.js */ console.log("加载index文件"); document.getElementById("btn").onclick = function () { // 懒加载:当文件需要使用时才加载 import("./testTreeShaking").then(({ test1 }) => { test1(); }); };

懒加载效果图

懒加载.gif

预加载

/* *index.js */ console.log("加载index文件"); document.getElementById("btn").onclick = function () { // 预加载 prefetch:会在使用之前,提前加载js文件 // 正常加载可以认为是并行加载(同一时间加载多个文件) // 预加载 prefetch: webpackPrefetch 等其他资源加载完毕,浏览器空闲了,再偷偷加载资源 import(/* webpackPrefetch: true */ "./testTreeShaking").then(({ test1 }) => { test1(); }); };

预加载效果图

预加载.gif

PWA 渐进式网络应用程序

PWA 可以用来做很多事。其中最重要的是,在离线(offline)时应用程序能够继续运行功能。这是通过使用名为 Service Workers 的 web 技术来实现的。

淘宝的PWA效果图

添加 workbox-webpack-plugin 插件,然后调整 webpack.config.js 文件:

yarn add workbox-webpack-plugin --dev

修改webpack.config.js

/* * webpack.config.js */ ... // 新增插件 const WorkboxWebpackPlugin = require("workbox-webpack-plugin"); module.exports = { ... // 插件 plugins: [ ... //新增插件 new WorkboxWebpackPlugin.GenerateSW({ /* 1. 帮助serviceworker快速启动 2. 删除旧的 serviceworker 生成一个 serviceworker 配置文件~ */ clientsClaim: true, skipWaiting: true, }), ], };

注册 Service Worker

/* * index.js */ /* sw代码必须运行在服务器上 --> nodejs --> npm server 启动服务器,将build目录下所有资源作为静态资源暴露出去 */ // 注册serviceWorker // 处理兼容性问题 if ("serviceWorker" in navigator) { window.addEventListener("load", () => { navigator.serviceWorker .register("/service-worker.js") .then(() => { console.log("sw注册成功了~"); }) .catch(() => { console.log("sw注册失败了~"); }); }); }

接下来我们将之前的server.js文件复制过来直接来使用:

先进行打包 node server.js 启动服务器 http://localhost:5555/

检查PWA效果图片

PWA.gif

多进程打包

yarn add thread-loader --dev

修改webpack.config.js文件:

/* * webpack.config .js */ ... module.exports = { ... // 模块 module: { rules: [ // loader的配置 { oneOf: [ ... // 新增js的loader { test: /\.js$/, exclude: /node_modules/, use: [ /* 新增代码 开启多进程打包。 进程启动大概为600ms,进程通信也有开销。 只有工作消耗时间比较长,才需要多进程打包 */ { loader: "thread-loader", options: { // 产生的 worker 的数量,默认是 (cpu 核心数 - 1),或者, // 在 require('os').cpus() 是 undefined 时回退至 1 workers: 2, // 进程2个 }, }, ... ], }, ], }, ], }, ... };

运行webpack可以查看效果,因为这个多进程打包启动需要600ms,所以比较适合代码体量大一点的项目。

未开启多进程打包效果图

image.png

开启多进程打包效果图

image.png

externals

防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。

修改webpack.config.js文件,添加externals属性

/* * webpack.config.js */ ... module.exports = { ... externals: { jquery: "jQuery", }, };

选择JQ的CDN链接,添加到index.html

免费CDN地址

/* * index.html */ webpack打包优化 webpack 打包优化 /* * index.js */ import $ from "jquery"; console.log("$", $);

webpack打包进行测试。

效果图片

外部扩展.gif

Dll 动态链接库

实现了拆分 bundles,同时还大幅度提升了构建的速度。"DLL" 一词代表微软最初引入的动态链接库。

新增webpack.dll.js文件,用于单独配置DllPlugin。

/* * webpack.dll.js */ const { resolve } = require("path"); const webpack = require("webpack"); module.exports = { entry: { // 最终打包生成的[name] --> dllFile // ['jquery',"lodash"] --> 要打包的库是jquery lodash dllFile: ["jquery", "lodash"], }, output: { filename: "[name].js", path: resolve(__dirname, "dll"), library: "[name]_[hash]", // 打包的库里面向外暴露出去的内容叫什么名字 }, plugins: [ // 打包生成一个 manifest.json --> 提供和jquery lodash映射 new webpack.DllPlugin({ name: "[name]_[hash]", // 映射库的暴露的内容名称 path: resolve(__dirname, "dll/manifest.json"), // 输出文件路径 }), ], mode: "production", };

运行 webpack --config webpack.dll.js 执行该文件,生成如下文件:

image.png

修改webpack.config.js文件 配置 DllReferencePlugin AddAssetHtmlWebpackPlugin。

下载 yarn add --dev add-asset-html-webpack-plugin

/* * webpack.config.js */ ... // 新增代码 const webpack = require("webpack"); const AddAssetHtmlWebpackPlugin = require("add-asset-html-webpack-plugin"); module.exports = { ... plugins: [ ... // 新增代码 // 告诉webpack哪些库不参与打包,同时使用时的名称也得变~ new webpack.DllReferencePlugin({ manifest: resolve(__dirname, "dll/manifest.json"), }), // 将某个文件打包输出去,并在html中自动引入该资源 new AddAssetHtmlWebpackPlugin({ filepath: resolve(__dirname, "dll/dllFile.js"), outputPath: "dll", // 如果设置,将用作文件的输出目录。 publicPath: "dll", // 如果设置,将用作脚本或链接标记的公共路径。 }), ], };

修改index.js 文件 进行测试:

/* * index.js */ ... import $ from "jquery"; console.log("$--->jquery", $); import _ from "lodash"; console.log("_---->lodash", _);

运行 webpack 进行打包。生成如下文件:

image.png

查看运行效果

屏幕录制2021-05-14 11.gif

webpack打包优化理论篇至此结束,其实关于打包还有很多点,欢迎大家评论区进行评论,我们共同成长!!

想查看源码的同学点击这里获取

下一篇 打包实践篇。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3