webpack优化:主要是在代码编译/运行时性能更好,主要从以下四个角度进行优化配置
- 提升开发体验
- 提升打包构建速度
- 减少代码体积
- 优化代码运行性能
一、提升开发体验
引文:https://www.webpackjs.com/configuration/devtool/
但在实际开发中我们只需要关注俩种情况即可
- 开发模式: cheap-module-source-map
- 优点:打包编译速度快,只包含行映射
- 缺点:没有列映射
- module.exports = {
- //省略其他
- mode: "development",
- devtool: "cheap-module-source-map"
- }
- 生产模式:source-map
- 优点:包含行/列映射
- 缺点:打包速度更慢
- module.exports = {
- mode: "production",
- devtool: "source-map"
- }
二、提升打包构建速度
1、使用HotModuleReplacement
由于在开发中我们修改了其中一个模块代码,webpack默认会将所有模块全部重新打包编译,速度很慢。所以我们需要做到修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变,这样打包速度就能很快,而HotModuleReplacement(HMP/热模块替换):在程序运行中、替换、添加、或删除模块、而无需重新加载整个页面
1.基本配置
- devServer: {
- host: "localhost", //启动服务器域名
- port: 3000, //启动服务器端口号
- open: true, //是否自动打开浏览器
- hot: true, //热更新
- },
如果hot为false的话,当css文件发出改变的时候当前浏览器页面就会全局刷新但是当hot为true的时候,修改css文件只会在控制台输出且不会进行页面刷新
在原生js项目中如果想要实现js文件修改后进行热刷新需要在入口文件中一一判断:
- if (module.hot) {
- module.hot.accept("./js/count")
- module.hot.accept("./js/sum")
-
- }
上面这样做写起来会很麻烦,所以实际开发中我们会使用其他loader来解决
比如:vue-loader,react-hot-loader;
2、使用oneof来配置加载器,使其文件只能匹配其中的一个loader
只需要在加载器规则内部数据套在oneOf上就行
3、include \ exclude
可以在loader或者是在plugin中使用但是俩着只能使用其中一个属性
- plugins: [
- // 插件的配置
- new EslintPlugin({
- context: path.resolve(__dirname, "../src"), //检测那些文件
- exclude: ["node_modules"], //排除那些文件
- }),
- {
- test: /\.js$/,
- // exclude: /node_modules/, //排除node_modules 文件
- include: path.resolve(__dirname, "../src"), //只检测src目录下的js文件
-
- loader: "babel-loader",
- options: {
- presets: ["@babel/preset-env"] //智能预测能够编译为es6语法但是一般为了好修改兼容性会在根目录下面创建babel.config.js文件
- }
- },
4、cache
可以为babel编译和eslint检查开启缓存,由于每次打包时js文件都要经过eslint检查和babel编译,速度比较慢,我们可以缓存之前的Eslint检查和Babel编译结果,这样第二次打包时速度就会更快,也就是该属性提升的第一次以后的打包速度,由于第一次没有进行缓存所以并不会提升;
- babel
- {
- test: /\.js$/,
- exclude: /node_modules/, //排除node_modules 文件
-
- loader: "babel-loader",
- options: {
- presets: ["@babel/preset-env"] ,//智能预测能够编译为es6语法但是一般为了好修改兼容性会在根目录下面创建babel.config.js文件
- cacheDirectory: true, // 开启babel缓存
- cacheCompression: false, // 关闭缓存文件压缩
- }
- },
- eslint
- plugins: [
- // 插件的配置
- new EslintPlugin({
- context: path.resolve(__dirname, "../src"), //检测那些文件
- cache: true, // 开启缓存
- cacheLocation: path.resolve(__dirname, "../node_modules/.cache/eslintcache")
- }),
5、Thead
在项目开发中,项目会越来越大,打包速度也会越来越慢,甚至需要一个下午才能打包出来代码,这个速度是比较慢的,所以想要继续提升打包速度,其实就是在提升js的打包速度,因为其他文件都比较少,而对于js文件处理主要就是eslint、babel、terser这三个工具,所以我们要提升的是它们的运行速度。
我们可以开启多进程同时处理js文件,这样速度就比之前的单进程打包更快了。
而多进程打包:开启电脑的多个进程同时干一件事,速度更快。
需要注意:请仅在特别耗时的操作中使用,因为每个进程启动就会大约在600ms左右开销
#使用过程
启动进程的数量就是我们CPU的核数,
1、如何获取CPU的核数,因为每个电脑的都不一样(例:双核四核等)
- const os = require("os")
- const threads = os.cpus.length
2、引入
npm i thread-loader -D
3、使用
- //
- const path = require("path");
- const MiniCssExtractPlugin = require("mini-css-extract-plugin");
- const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
- const os = require("os")
- const threads = os.cpus().length // 获取cpu的核心数
- const terserWebpackPlugin = require("terser-webpack-plugin")
- const cssLoader = [
- MiniCssExtractPlugin.loader, //将css文件生成link标签插入到html中插入html标签是因为引入了html-webpack-plugin插件
- "css-loader",
- {
- loader: 'postcss-loader',
- options: {
- postcssOptions: {
- plugins: [
- [
- 'postcss-preset-env', // 解决兼容性问题
- {
- // 其他选项
- },
- ],
- ],
- },
- },
- },
- ]
- const EslintPlugin = require("eslint-webpack-plugin")
- const HtmlWebpackPlugin = require("html-webpack-plugin");
- module.exports = {
- // 入口
- entry: "./src/main.js",
- // 出口
- output: {
- // 所有文件得打包目录
- path: path.resolve(__dirname, "../dist"),
- // 入口文件的打包目录
- filename: "static/js/main.js",
- // 打包前清空上一次打包的目录也就是path的目录
- // clean: true,
- },
- // 加载器loader
- module: {
- rules: [
- // loader的配置
-
- {
- test: /\.js$/,
- exclude: /node_modules/, //排除node_modules 文件
- use: [{
- loader: "babel-loader",
- options: {
- presets: ["@babel/preset-env"] ,//智能预测能够编译为es6语法但是一般为了好修改兼容性会在根目录下面创建babel.config.js文件
- cacheDirectory: true, // 开启babel缓存
- cacheCompression: false, // 关闭缓存文件压缩
- }
- },
- {
- loader: "thread-loader",
- options: {
- works: threads, //开启多进程
- }
- }],
-
- },
-
- ]
- },
- // 插件
- plugins: [
- // 插件的配置
- new EslintPlugin({
- context: path.resolve(__dirname, "../src"), //检测那些文件
- cache: true, // 开启缓存
- cacheLocation: path.resolve(__dirname, "../node_modules/.cache/eslintcache"),
- threads: threads, //开启多进程
- }),
- new HtmlWebpackPlugin({
- // 模板:以public/index.html文件创建新的html文件
- //新的html文件特点: 1、结构和原来一致,2、自动引入打包输出的资源
- template: path.resolve(__dirname, "../public/index.html"),
- }),
- new MiniCssExtractPlugin({
- filename: "static/css/main.css"
- }),
- // new CssMinimizerPlugin(),
- // new terserWebpackPlugin({
- // parallel: threads, //开启多进程
- // })
- ],
- // 给压缩优化放在一起
- optimization: {
- minimizer: [
- new CssMinimizerPlugin(),
- new terserWebpackPlugin({
- parallel: threads, //开启多进程
- })
- ]
- },
- // 开发服务器, 不会生成打包后的文件,自会在内存中编译
- // 模式
- mode: "production",
- devtool: "source-map" // 生产环境下生成source-map文件,方便调试可以映射行和列得关系
- }
三、减少代码体积
1、Tree Shaking
开发时我们定义了一些工具函数库,或许引用第三方工具函数库或组件库。
如果没有特殊处理的话我们打包时会引入整个库,但是实际上可能我们只是上级小部分功能,这样将整个库都打包进来,体积就太大了;
Tree Shaking是一个术语,通常用于描述移除javascript中没有使用上得代码,
注意:它依赖 ES Module
#怎么用
webpack已经默认开启了这个功能,无需其他配置;
2、Babel
Babel为编译的每个文件都插入了辅助代码,使代码体积过大!
Babel对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下辉被添加到每一个需要它的文件中。可以将这些辅助代码作为一个独立模块,来避免重复引入
是什么
@Babel/plugin-transform-runtime:禁用了Babel自动对每个文件的runtime注入,而是引入@babel/plugin-transform-runtime:并且使所有辅助代码从这里引用
怎么用
1)、下载包
npm i @babel/plugin-transform-runtime -D
2)、配置
- {
- test: /\.js$/,
- // exclude: /node_modules/, //排除node_modules 文件
- include: path.resolve(__dirname, "../src"), //只检测src目录下的js文件
-
- loader: "babel-loader",
- options: {
- presets: ["@babel/preset-env"], //智能预测能够编译为es6语法但是一般为了好修改兼容性会在根目录下面创建babel.config.js文件
-
- }
- },
四、Image minimizer
为什么
开发如果项目种引入了较多图片,那么图片体积会比较大,将来请求速度比较慢。
我么可以对图片进行压缩,减少图片体积
注意:如果项目种图片都是在线链接,那么就不需要了,本地项目静态图片才需要进行压缩。
是什么
image-minimizer-webpack-plugin:用来压缩图片的插件
下载包
npm i image-minimizer-webpack-plugin imagemin -D
还有剩下的包需要下载,有俩种模式:
- 无损压缩
npm i imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D
- 有损压缩
npm i imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D
4、最后:优化代码的运行性能
1、Code Split
为什么
打包代码时会将所有js文件打包到一个文件中,体积太大了,我们如果只要渲染首页,就应该只加载首页的js文件,其他文件不应该加载;所以我们需要将打包生成的文件进行代码分割,生成多个js文件,渲染那个页面就只加载某个js文件,这样加载的资源就少,速度就更快;
是什么
代码分割(Code split)主要做了俩件事
- 分割文件:将打包生成的文件进行分割,生成多个js文件
- 按需加载,需要那个文件就加载那个文件
怎么用
代码分割实现方式有不同的方式,为了更加方便体现它们之间的差异,我们会分别创建新的文件来演示
- const path = require("path")
- const HtmlWebpackPlugin = require("html-webpack-plugin")
- module.exports = {
-
- entry: {
- app: "./src/app.js",
- main: "./src/main.js"
- },
- output: {
- path: path.resolve(__dirname, "dist"),
- filename: "[name].js",
- },
- plugins: [
- new HtmlWebpackPlugin({
- template: path.resolve(__dirname, "public/index.html"),
- }),
- ],
- // module:
- mode: "production",
- optimization: {
- // 代码分割配置
- splitChunks: {
- chunks: "all", //对所有模块进行分割
- // 以下是默认值
- // minSize: 20000, // 分割代码大小为20kb
- // minRemainingSize: 0, // 剩余代码大小不得少于0kb, 类似于minSize
- // minChunks: 1, // 最少被引用的次数,满足条件才会代码分割
- // maxAsyncRequests: 30, // 异步加载的最大并行请求数的最大数量
- // maxInitialRequests: 30, // 初始加载的最大并行请求数的最大数量, 既入口js文件最大并行请求数量
- // enforceSizeThreshold: 50000, // 强制代码分割的阈值,大于该阈值才会进行代码分割
- // cacheGroups: { //组, 那些模块要打包到一个组
- // defaultVendors: { // 组名
- // test: /[\\/]node_modules[\\/]/, // 匹配node_modules目录
- // priority: -10, // 优先级,数字越大,优先级越高,优先打包到该组 (权重, 越大越高)
- // reuseExistingChunk: true, // 如果该组的模块已经被打包过,是否复用该组的模块 ,(如果当前chunk包含已从budle中拆分出的模块,则它将被重用,而不是生成新的模块)
- // },
- // default: {
- // minChunks: 2, // 最少被引用的次数,满足条件才会代码分割
- // priority: -20, // 优先级,数字越大,优先级越高,优先打包到该组 (权重, 越大越高)
- // reuseExistingChunk: true, // 如果该组的模块已经被打包过,是否复用该组的模块 ,(如果当前chunk包含已从budle中拆分出的模块,则它将被重用,而不是生成新的模块)
- // }
- // }
- // 修改配置
- cacheGroups: {
- // 组,那些模块要打包到一个组
- // defaultVendors: { // 组名
- // test: /[\\/]node_modules[\\/]/, // 匹配node_modules目录
- // priority: -10, // 优先级,数字越大,优先级越高,优先打包到该组 (权重, 越大越高)
- // reuseExistingChunk: true, // 如果该组的模块已经被打包过,是否复用该组的模块 ,(如果当前chunk包含已从budle中拆分出的模块,则它将被重用,而不是生成新的模块)
- // }
- default: {
- minSize: 0, // 分割代码大小为0kb
- minChunks: 2, // 最少被引用的次数,满足条件才会代码分割
- priority: -20, // 优先级,数字越大,优先级越高,优先打包到该组 (权重, 越大越高)
- reuseExistingChunk: true, // 如果该组的模块已经被打包过,是否复用该组的模块 ,(如果当前chunk包含已从budle中拆分出的模块,则它将被重用,而不是生成新的模块)
- }
- }
- }
- }
- }
对于一些事件或者是异步的js文件可以使用动态加载,按需导入
例如:
- document.getElementById("btn").onclick = function() {
- // import 动态导入模块,会将动态导入得文件代码分割(拆分成单独得模块)在需要使用得时候自动加载
- import("./count.js").then(module => {
- // console.log(module.default(1, 2))
- console.log("模块加载成功")
- }).catch(error => {
- console.log("模块加载失败")
- })
- console.log(count(1, 2))
- }
如上所示,这样可以避免一上来就加载该count.js文件,只有当点击的时候才会去加载该文件
2、Network Cache
如果我们在入口文件中引入的js文件里面有的内容发生变化的时候重新打包就会发现打包后的文件无论是出口的mian.js文件还是math.chunk.js文件都会重新打包,这是因为在main.js文件中引入了math.chunk.js文件的hash文件名,故而我们需要在打包后生成一个中间的文件名来使其入口文件不重新打包
故而需要在webpack.config.js文件中进行配置如下
- optimization: {
- runtimeChunk: {
- name: (entrypoint) => `runtime-${entrypoint.name}.js`
- }
- }
如上这样,我们就不会使其入口文件重新打包
3、Core.js
为什么:
过去我们使用babel对js代码进行了兼容性处理,其中使用@babel/preset-env智能预设来处理兼容性问题,它能将es6的一些语法进行编译转换,比如箭头函数,...运算符等,但是如果是async函数,promise对象,数组的一些方法(include)等,它没办法处理
所以此时我们js代码仍然存在兼容性问题,一旦遇到低版本浏览器会直接报错,所以我们想要将js兼容性问题彻底解决
是什么:
core.js 是专门用来做es6以及以上api的polyfull。
polyfull翻译过来叫做垫片/补丁,就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性
引入库
npm i core-js --force
可以全局引入例如
import "core-js"
或局部引用
import "core-js/js/promise"
如上所示:打包后文件会在文件中多显示一些 .js文件
4、PWA
为什么:
开发web app 项目,项目一旦处于网络离线情况,就没办法访问了,我们希望给项目提供离线体验
是什么:
渐进式网络应用程序(progressive web appliction . PWA): 是一种可以提供类似于native app(原生应用程序)体验的web app 的技术,其中最重要的是,在离线(offine)时应用程序能够继续运行功能,内部通过,service Workers 技术实现的
这么用:
1、下载包
npm i workbox-webpack-plugin -D
2、修改配置文件
具体请查看webpack官网,可以直接根据官网引入
渐进式网络应用程序 | webpack 中文文档 | webpack中文文档 | webpack中文网 (webpackjs.com)
- if ('serviceWorker' in navigator) {
- + window.addEventListener('load', () => {
- + navigator.serviceWorker.register('/service-worker.js').then(registration => {
- + console.log('SW registered: ', registration);
- + }).catch(registrationError => {
- + console.log('SW registration failed: ', registrationError);
- + });
- + });
- + }
5、preload / prefetch
为什么
我们前面已经做了代码分割,同时回使用import动态导入语法来进行代码按需加载(我们也叫懒加载,比如路由懒加载就是这样实现的),但是加载速度还不够好,比如:是用户点击按钮时才加载这个资源的,如果资源体积很大,那么用户会感觉到明显卡顿效果。我们想在浏览器空闲时间,加载后续需要使用的资源,我们就需要用上preload 或prefetch技术
是什么
- preload:告诉浏览器立刻加载资源
- prefetch:告诉浏览器在空闲时才开始加载资源
它们的共同点:
- 都会只加载资源,并不执行
- 都会有缓存
它们区别:
- preload加载优先级高,prefetch加载优先级低
- preload只能加载当前页面需要使用的资源,prefetch可以加载当前页面资源,也可以加载下一个页面需要使用的资源
总结:
- 当前页面优先级高的资源用preload加载
- 下一个页面需要使用的资源用prefetch加载
它们的问题:兼容性较差
- 我们可以去Can I Use 官网查询api的兼容性问题
- preload相对于prefetch兼容性号一点
引入库
npm i --save-dev @vue/preload-webpack-plugin --force
- const preloadWebpackPlugin = require("@vue/preload-webpack-plugin")
- module.exports = {
- plugins: [
- // 插件的配置
-
- new preloadWebpackPlugin({
- rel: "preload",
- as: "script"
- })
- ],
- }
打包后的文件在index.html文件中显示为
评论记录:
回复评论: