webpack传输性能优化

手动分包

基本原理

手动分包的总体思路是:先打包公共模块,然后再打包业务代码。

打包公共模块

在这里插入图片描述

公共模块会被打包成为动态链接库(dll Dynamic Link Library),并生成资源清单。

打包业务代码

打包时,如果发现模块中使用了资源清单中描述的模块,则不会形成下面的代码结构

//源码,入口文件index.js
import $ from "jquery"
import _ from "lodash"
_.isArray($(".red"));

由于资源清单中包含 jquerylodash 两个模块,因此打包结果的大致格式是:

(function(modules){
    //...
})({
    // index.js文件的打包结果并没有变化
    "./src/index.js":
    function(module, exports, __webpack_require__){
        var $ = __webpack_require__("./node_modules/jquery/index.js")
        var _ = __webpack_require__("./node_modules/lodash/index.js")
        _.isArray($(".red"));
    },
    // 由于资源清单中存在,jquery的代码并不会出现在这里
    "./node_modules/jquery/index.js":
    function(module, exports, __webpack_require__){
        module.exports = jquery;
    },
    // 由于资源清单中存在,lodash的代码并不会出现在这里
    "./node_modules/lodash/index.js":
    function(module, exports, __webpack_require__){
        module.exports = lodash;
    }
})

打包公共模块

打包公共模块是一个独立的打包过程

package.json

{
    "name": "webpack",
    "version": "1.0.0",
    "private": true,
    "scripts": {
        "dll": "webpack --config webpack.dll.config.js",
        "build": "webpack"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "description": "",
    "devDependencies": {
        "html-webpack-plugin": "^5.6.3",
        "webpack": "^5.96.1",
        "webpack-cli": "^5.1.4"
    },
    "dependencies": {
        "jquery": "^3.7.1",
        "lodash": "^4.17.21"
    }
}

webpack.dll.config.js

const webpack = require("webpack")
const path = require("path")

module.exports = {
    mode: "production",
    entry: {
        lodash: "lodash",
        jquery: "jquery",
    },
    output: {
        filename: "dll/[name].js", // 不需要用[hash]
        library: "[name]", // 导出的全局变量名
    },
    plugins: [
        new webpack.DllPlugin({
            path: path.resolve(__dirname, "dll", "[name].manifest.json"), // 资源清单的保存位置
            name: "[name]" // 资源清单中,暴露的变量名
        })
    ]
}

运行 npm run dll 命令,可以看到在根目录下生成了 dll 目录,目录下有 lodash 和 jquery 的资源清单文件;同时在 dist/dll 目录下也可以看到打包好的 lodash 和 jquery 文件。

打包业务代码

package.json

{
    "name": "webpack",
    "version": "1.0.0",
    "private": true,
    "scripts": {
        "dll": "webpack --config webpack.dll.config.js",
        "build": "webpack"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "description": "",
    "devDependencies": {
        "html-webpack-plugin": "^5.6.3",
        "webpack": "^5.96.1",
        "webpack-cli": "^5.1.4"
    },
    "dependencies": {
        "jquery": "^3.7.1",
        "lodash": "^4.17.21"
    }
}

webpack.config.js

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

module.exports = {
    mode: "development",
    context: path.resolve(__dirname, "src"), 
    entry: {
        index: "./index"
    },
    output: {
        filename: "[name].[contenthash:5].js",
        clean: {
            keep: /dll\//, // 保留 'dll/' 下的资源
        },
    },
    plugins: [
        new HtmlWebpackPlugin({
            title: "webpack-手动分包",
        }),
        new webpack.DllReferencePlugin({
            manifest: require("./dll/jquery.manifest.json"), // 应用 jquery 的资源清单
        }),
        new webpack.DllReferencePlugin({
            manifest: require("./dll/lodash.manifest.json"), // 应用 lodash 的资源清单
        }),
    ],
}

index.js

import $ from "jquery"
import _ from "lodash"

console.log($);
console.log(_);

运行 npm run build 命令,发现打包的文件中还是包含了 lodash 和 jquery,这是为什么呢?检查打包的文件发现:

dist/index.06f97.js

(function(modules){
    //...
})({
    "./index.js": (
        __unused_webpack_module,
        __webpack_exports__,
        __webpack_require__
    ) => {
        // ...
    },

    "../node_modules/jquery/dist/jquery.js": function (module, exports) {
        // ...
    },

    "../node_modules/lodash/lodash.js": function (module, exports, __webpack_require__) {
        // ...
    }
})

发现 ../node_modules/jquery/dist/jquery.js../node_modules/lodash/lodash.js

dll/jquery.manifest.json

{
    "name": "jquery",
    "content": {
        "./node_modules/jquery/dist/jquery.js": { "id": 692, "buildMeta": {} }
    }
}

dll/lodash.manifest.json

{
    "name": "lodash",
    "content": {
        "./node_modules/lodash/lodash.js": { "id": 543, "buildMeta": {} }
    }
}

中的 ./node_modules/jquery/dist/jquery.js./node_modules/lodash/lodash.js 对不上,这是因为使用了 context 导致的,更改 webpack.config.js 文件:

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

module.exports = {
    // ...
    // context: path.resolve(__dirname, "src"), 
    entry: {
        index: "./src/index"
    }
    // ...
}

再次运行 npm run build 命令,发现结果文件中已不再包含 lodash 和 jquery。

使用公共模块

webpack.config.js

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

module.exports = {
    mode: "development",
    // context: path.resolve(__dirname, "src"), 
    entry: {
        index: "./src/index"
    },
    output: {
        filename: "[name].[contenthash:5].js",
        clean: {
            keep: /dll\//, // 保留 'dll/' 下的资源
        },
    },
    plugins: [
        new HtmlWebpackPlugin({
            title: "webpack-手动分包",
            template: "./index.ejs", // 使用 ejs 模板语法
            templateParameters: {
                scripts: ["./dll/jquery.js", "./dll/lodash.js"], // 将公共模块插入页面
            }
        }),
        new webpack.DllReferencePlugin({
            manifest: require("./dll/jquery.manifest.json"), // 应用 jquery 的资源清单
        }),
        new webpack.DllReferencePlugin({
            manifest: require("./dll/lodash.manifest.json"), // 应用 lodash 的资源清单
        }),
    ],
}

index.ejs

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <% if (scripts) { %>
        <% for (item of scripts) { %>
        <script src="<%= item %>"></script>
        <% } %>
        <% } %>
    </head>
    <body></body>
</html>

总结

手动打包的过程

  1. 开启 output.library 暴露公共模块
  2. DllPlugin 创建资源清单
  3. DllReferencePlugin 使用资源清单

手动打包的注意事项

  1. 资源清单不参与运行,可以不放到打包目录中。
  2. 记得手动引入公共 JS,以及避免被删除。
  3. 不要对小型的公共 JS 库使用。

优点

  1. 极大提升自身模块的打包速度。
  2. 极大的缩小了自身文件体积。
  3. 有利于浏览器缓存第三方库的公共代码。

缺点

  1. 使用非常繁琐。
  2. 如果第三方库中都导入了相同的依赖,则第三方库打包后都会包含重复的代码,效果不太理想。

自动分包

基本原理

不同与手动分包,自动分包是从实际的角度出发,从一个更加宏观的角度来控制分包,而一般不对具体哪个包要分出去进行控制。

因此使用自动分包,不仅非常方便,而且更加贴合实际的开发需要。

要控制自动分包,关键是要配置一个合理的分包策略

有了分包策略之后,不需要额外安装任何插件,webpack 会自动的按照策略进行分包。

实际上,webpack 在内部是使用 SplitChunksPlugin 进行分包的,过去有一个库 CommonsChunkPlugin 也可以实现分包,不过由于该库某些地方并不完善,到了 webpack4 之后,已被 SplitChunksPlugin 取代。

在这里插入图片描述

从分包流程中至少可以看出以下几点:

  • 分包策略至关重要,它决定了如何分包。
  • 分包时,webpack 开启了一个新的 chunk,对分离的模块进行打包。
  • 打包结果中,公共的部分被提取出来形成了一个单独的文件,它是新 chunk 的产物。

分包策略的基本配置

webpack 提供了 optimization 配置项,用于配置一些优化信息,其中 splitChunks 是分包策略的配置

module.exports = {
    optimization: {
        splitChunks: {
            // 分包策略
        }
    }
}

默认值

开箱即用的 SplitChunksPlugin 对于大部分用户来说非常友好。

默认情况下,它只会影响到按需加载的 chunks(动态导入的模块),因为修改 initial chunks 会影响到项目的 HTML 文件中的脚本标签。

webpack 将根据以下默认条件自动拆分 chunks:

  • 新的 chunk 可以被共享,或者模块来自于 node_modules 文件夹
  • 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积)
  • 当按需加载 chunks 时,并行请求的最大数量小于或等于 30
  • 当加载初始化页面时,并发请求的最大数量小于或等于 30

当尝试满足最后两个条件时,最好使用较大的 chunks。

splitChunks.chunks

类型:string | function (chunk),默认值:async

  • 'all'

    对于所有的 chunk 都要应用分包策略。

  • 'async'

    仅针对异步 chunk 应用分包策略。

  • 'initial'

    仅针对普通 chunk 应用分包策略。

  • function (chunk)

    函数返回 boolean 值,决定是否对该 chunk 进行分包。

    webpack.config.js

    const path = require("path")
    const HtmlWebpackPlugin = require("html-webpack-plugin")
    
    module.exports = {
        mode: "production",
        context: path.resolve(__dirname, "src"),
        entry: {
            index: "./index",
            main: "./main",
        },
        output: {
            filename: "[name].[contenthash:5].js",
            clean: true,
        },
        plugins: [
            new HtmlWebpackPlugin({
                title: "webpack-自动分包",
            }),
        ],
        optimization: {
            splitChunks: {
                chunks(chunk) {
                    return true // 都返回 true 相当于 'all'
                },
            },
        },
    }
    

splitChunks.minChunks

类型:number,默认值:1

模块被 minChunks 个 chunk 共享才需要拆分成 chunk,优先级可能受其他设置影响。

splitChunks.minSize

类型:number,默认值:20000

生成 chunk 的最小体积(以 bytes 为单位),超过这个体积会被拆分。

在这里插入图片描述

可以看到 index.js + jquery 大小是 279 KiB,没有达到 400000 个字节,没有被拆分;而 main.js + lodash 大小是 531 KiB,超过 400000 个字节,所以被拆分。

splitChunks.minSizeReduction

类型:number

如果分割成一个 chunk 并没有减少主 chunk(bundle)的给定的 minSizeReduction 字节数,它将不会被分割,即使它满足 splitChunks.minSize

为了生成 chunk,splitChunks.minSizeReductionsplitChunks.minSize 都需要被满足。

在这里插入图片描述

可以看到 index.js 如果拆分体积可以减少 279 KiB,没有达到 400000 个字节,即使达到 minSize,也没有拆分;再看 main.js 如果拆分体积可以减少 531 KiB,所以被拆分。

splitChunks.maxSize

类型:number,默认值: 0

使用 maxSize 告诉 webpack 尝试将大于 maxSize 个字节的 chunk 分割成较小的部分。 这些较小的部分在体积上至少为 minSize(仅次于 maxSize)。maxSize 只是一个提示,当模块大于 maxSize 或者拆分不符合 minSize 时可能会被违反。

maxSizemaxInitialRequest/maxAsyncRequests 具有更高的优先级。实际优先级是 maxInitialRequest/maxAsyncRequests < maxSize < minSize

设置 maxSize 的值会同时设置 maxAsyncSizemaxInitialSize 的值。

splitChunks.cacheGroups

类型:object

缓存组可以继承和/或覆盖来自 splitChunks.* 的任何选项。但是 testpriorityreuseExistingChunk 只能在缓存组级别上进行配置。将它们设置为 false以禁用任何默认缓存组。splitChunks 自带两个缓存组:

module.exports = {
    //...
    optimization: {
        splitChunks: {
            //...
            cacheGroups: {
                defaultVendors: {
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10,
                    reuseExistingChunk: true,
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true,
                },
            },
        },
    },
};
cacheGroups.{cacheGroup}.priority

类型:number ,默认值:-20

一个模块可以属于多个缓存组。优化将优先考虑具有更高 priority(优先级)的缓存组。默认组的优先级为负,以允许自定义组获得更高的优先级(自定义组的默认值为 0)。

cacheGroups.{cacheGroup}.reuseExistingChunk

类型:boolean,默认值:true

如果当前 chunk 包含已经从 main bundle 中分离出来的模块,它将被重用,而不是生成新的 module。这可能会影响块的结果文件名。

cacheGroups.{cacheGroup}.type

类型:function|RegExp|string

允许按模块类型将模块分配给缓存组处理。

webpack.config.js

module.exports = {
    //...
    optimization: {
        splitChunks: {
            cacheGroups: {
                json: {
                    type: 'json',
                },
            },
        },
    },
};
cacheGroups.{cacheGroup}.test

类型:function (module, { chunkGraph, moduleGraph }) => boolean | RegExp | string

控制此缓存组选择的模块。省略它会选择所有模块。它可以匹配绝对模块资源路径或 chunk 名称。匹配 chunk 名称时,将选择 chunk 中的所有模块。

cacheGroups.{cacheGroup}.filename

类型:string | function (pathData, assetInfo) => string

仅在初始 chunk 时才允许覆盖文件名。 也可以使用 output.filename 中的所有占位符。

cacheGroups.{cacheGroup}.enforce

类型:boolean,默认值:false

告诉 webpack 忽略 splitChunks.minSizesplitChunks.minChunkssplitChunks.maxAsyncRequestssplitChunks.maxInitialRequests 选项,并始终为此缓存组创建 chunk。

示例

首先我们先按以下图片初始化项目

在这里插入图片描述

然后打包,可以看到在没有使用缓存组去提取 CSS 代码,只用 MiniCssExtractPlugin 插件去分离 CSS 代码,公共代码会被重复引用:

在这里插入图片描述

然后我们修改 webpack.config.js 使用缓存组去提取公共代码:

在这里插入图片描述

可以看到公共的 CSS 已经被拆分成独立的文件:

在这里插入图片描述

原理

自动分包的原理其实并不复杂,主要经过以下步骤:

  1. 检查每个 chunk 编译的结果
  2. 根据分包策略,找到那些满足策略的模块
  3. 根据分包策略,生成新的 chunk 打包这些模块(代码有所变化)
  4. 把打包出去的模块从原始包中移除,并修正原始包代码

在代码层面,有以下变动

  1. 分包的代码中,加入一个全局变量,类型为数组,其中包含公共模块的代码
  2. 原始包的代码中,使用数组中的公共代码

代码压缩

为什么要进行代码压缩

减少代码体积;破坏代码的可读性,提升破解成本;

什么时候要进行代码压缩

生产环境

使用什么压缩工具

目前最流行的代码压缩工具主要有两个:UglifyJsTerser

UglifyJs 是一个传统的代码压缩工具,已存在多年,曾经是前端应用的必备工具,但由于它不支持 ES6 语法,所以目前的流行度已有所下降。

Terser 是一个新起的代码压缩工具,支持 ES6+ 语法,因此被很多构建工具内置使用。webpack 安装后会内置 Terser,当启用生产环境后即可用其进行代码压缩。

因此,我们选择 Terser

关于副作用 side effect

副作用:函数运行过程中,可能会对外部环境造成影响的功能

如果函数中包含以下代码,该函数叫做副作用函数:

  • 异步代码
  • localStorage
  • 对外部数据的修改

如果一个函数没有副作用,同时,函数的返回结果仅依赖参数,则该函数叫做纯函数(pure function)

Terser

Terser 的官网可尝试它的压缩效果,Terser官网:https://terser.org/

我们可以通过 /*#__PURE__*/ 注释来帮助 terser。这个注释的作用是标记此语句没有副作用。这样一个简单的改变就能够 tree-shake 下面的代码了:

var Button$1 = /*#__PURE__*/ withAppProvider()(Button);

这将允许删除这段代码。但是除此之外,引入的内容可能仍然存在副作用的问题,因此需要对其进入评估。

为了解决这个问题,我们需要在 package.json 中添加 "sideEffects" 属性。

它与 /*#__PURE__*/ 类似,但是作用于模块层面,而非代码语句的层面。"sideEffects" 属性的意思是:“如果没有使用被标记为无副作用的模块的直接导出,那么捆绑器会跳过对此模块的副作用评估”。

webpack+Terser

webpack 自动集成了 Terser,如果你想更改、添加压缩工具,又或者是想对 Terser 进行配置,使用下面的 webpack 配置即可:

const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
    optimization: {
        // 是否要启用压缩,默认情况下,生产环境会自动开启
        minimize: true, 
        minimizer: [ // 压缩时使用的插件,可以有多个
            new TerserPlugin(), 
            new OptimizeCSSAssetsPlugin()
        ],
    },
};

Tree Shaking

代码压缩可以移除模块内部的无效代码,tree shaking 可以移除模块之间的无效代码。

背景

某些模块导出的代码并不一定会被用到

// myMath.js
export function add(a, b){
    console.log("add")
    return a+b;
}

export function sub(a, b){
    console.log("sub")
    return a-b;
}
// index.js
import {add} from "./myMath"
console.log(add(1,2));

tree shaking 用于移除掉不会用到的导出。

使用

webpack2 开始就支持了 tree shaking,只要是生产环境,tree shaking 自动开启。

原理

webpack 会从入口模块出发寻找依赖关系,当解析一个模块时,webpack 会根据 ES6 的模块导入语句来判断,该模块依赖了另一个模块的哪个导出,webpack 之所以选择 ES6 的模块导入语句,是因为 ES6 模块有以下特点:

  1. 导入导出语句只能是顶层语句
  2. import 的模块名只能是字符串常量
  3. import 绑定的变量是不可变的

这些特征都非常有利于分析出稳定的依赖

在具体分析依赖时,webpack 坚持的原则是:保证代码正常运行,然后再尽量 tree shaking

所以,如果你依赖的是一个导出的对象,由于 JS 语言的动态特性,以及 webpack 还不够智能,为了保证代码正常运行,它不会移除对象中的任何信息。

因此,我们在编写代码的时候,尽量

  • 使用 export xxx 导出,而不使用 export default {xxx} 导出
  • 使用 import {xxx} from "xxx" 导入,而不使用 import xxx from "xxx" 导入

依赖分析完毕后,webpack会根据每个模块每个导出是否被使用,标记其他导出为dead code,然后交给代码压缩工具处理,最终移除掉那些 dead code 代码。

使用第三方库

某些第三方库可能使用的是 commonjs 的方式导出,比如 lodash,又或者没有提供普通的 ES6 方式导出,对于这些库,tree shaking 是无法发挥作用的,因此要寻找这些库的 es6 版本,好在很多流行但没有使用的 ES6 的第三方库,都发布了它的 ES6 版本,比如 lodash-es

在这里插入图片描述

可以看到使用 commonjs 方式导出的 lodash ,tree shaking 并不起作用;而使用 esmodule 导出的 lodash-es,并且使用 import {xxx} from "xxx" 方式导入,tree shaking 就会删除 dead code ,极大的减少代码体积:

在这里插入图片描述

作用域分析

tree shaking 本身并没有完善的作用域分析,可能导致在一些 dead code 函数中的依赖仍然会被视为依赖。

插件 webpack-deep-scope-plugin 提供了作用域分析,可解决这些问题

副作用问题

webpack 在 tree shaking 的使用,有一个原则:一定要保证代码正确运行,在满足该原则的基础上,再来决定如何 tree shaking

因此,当 webpack 无法确定某个模块是否有副作用时,它往往将其视为有副作用。

因此,某些情况可能并不是我们所想要的。

//common.js
var n  = Math.random();

//index.js
import "./common.js"

虽然我们根本没用有 common.js 的导出,但 webpack 担心 common.js 有副作用,如果去掉会影响某些功能。

如果要解决该问题,就需要标记该文件是没有副作用的

package.json 中加入sideEffects

{
    "sideEffects": false
}

有两种配置方式:

  • false:当前工程中,所有模块都没有副作用。注意,这种写法会影响到某些 css 文件的导入
  • 数组:设置哪些文件拥有副作用,例如:["!src/common.js"],表示只要不是 src/common.js 的文件,都有副作用

这种方式我们一般不处理,通常是一些第三方库在它们自己的 package.json 中标注

css tree shaking

webpack 无法对 css 完成 tree shaking,因为 csses6 没有任何关系,因此对 csstree shaking 需要其他插件完成

例如:purgecss-webpack-plugin

注意:purgecss-webpack-plugincss module 无能为力。

动态导入

webpack 提供了 ECMAScript 提案的 import() 语法实现动态导入。让我们先尝试以下。

在这里插入图片描述

打开 dist/index.html 打开控制台,然后点击按钮,可以看到 lodash 通过网络实现了动态加载;并且更重要的是 tree shaking 起作用了,lodash 代码被极大的压缩了。

预获取/预加载模块

Webpack v4.6.0+ 增加了对预获取(prefetch)和预加载(preload)的支持。

在声明 import 时,使用下面这些内置指令,可以让 webpack 输出“resource hint”,来告知浏览器:

  • prefetch(预获取):将来某些导航下可能需要的资源
  • preload(预加载):当前导航下可能需要资源

预获取

下面这个预获取的简单示例中,有一个按钮,然后在点击后按需加载 lodash,只需在 import() 中添加 /* webpackPrefetch: true */ 即可。

在这里插入图片描述

打包完成后。打开 dist/index.html 页面,在控制台中可以看到在父 chunk 完成加载时,会将 <link rel="prefetch" as="script" href="http://127.0.0.1:5500/dist/185.34d25.js"> 追加到页面头部,指示浏览器在闲置时间预获取 185.34d25.js 文件。

预加载

想要使用预加载,只需在 import() 中添加 /* webpackPreload: true */ 即可。

在这里插入图片描述

预获取与预加载的区别

  • 预加载 chunk 会在父 chunk 加载时,以并行方式开始加载。预获取 chunk 会在父 chunk 加载结束后开始加载。
  • 预加载 chunk 具有中等优先级,并立即下载。预获取 chunk 在浏览器闲置时下载。
  • 预加载 chunk 会在父 chunk 中立即请求,用于当下时刻。预获取 chunk 会用于未来的某个时刻。
  • 浏览器支持程度不同。

gzip

gzip 是一种压缩文件的算法

B/S结构中的压缩传输

在这里插入图片描述

优点:传输效率可能得到大幅提升

缺点:服务器的压缩需要时间,客户端的解压需要时间

使用 webpack 进行预压缩

使用 compression-webpack-plugin 插件对打包结果进行预压缩,可以移除服务器的压缩时间。

在这里插入图片描述

安装

npm i -D compression-webpack-plugin

使用

const HtmlWebpackPlugin = require("html-webpack-plugin")
const CompressionWebpackPlugin = require('compression-webpack-plugin')

module.exports = {
    mode: "production",
    entry: { index: "./src/index" },
    output: {
        filename: "[name].[contenthash:5].js",
        clean: true,
    },
    plugins: [
        new HtmlWebpackPlugin({ 
            title: "webpack-treeShaking", 
            template: "./index.html" 
        }),
        new CompressionWebpackPlugin()
    ]
}

更多关于 compression-webpack-plugin 的配置信心请查看文档。
输效率可能得到大幅提升

缺点:服务器的压缩需要时间,客户端的解压需要时间

使用 webpack 进行预压缩

使用 compression-webpack-plugin 插件对打包结果进行预压缩,可以移除服务器的压缩时间。

[外链图片转存中…(img-SWI0oElW-1738589697670)]

安装

npm i -D compression-webpack-plugin

使用

const HtmlWebpackPlugin = require("html-webpack-plugin")
const CompressionWebpackPlugin = require('compression-webpack-plugin')

module.exports = {
    mode: "production",
    entry: { index: "./src/index" },
    output: {
        filename: "[name].[contenthash:5].js",
        clean: true,
    },
    plugins: [
        new HtmlWebpackPlugin({ 
            title: "webpack-treeShaking", 
            template: "./index.html" 
        }),
        new CompressionWebpackPlugin()
    ]
}

更多关于 compression-webpack-plugin 的配置信心请查看文档。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/963937.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

6 [新一代Github投毒针对网络安全人员钓鱼]

0x01 前言 在Github上APT组织“海莲花”发布存在后门的提权BOF&#xff0c;通过该项目针对网络安全从业人员进行钓鱼。不过其实早在几年前就已经有人对Visual Studio项目恶意利用进行过研究&#xff0c;所以投毒的手法也不算是新的技术。但这次国内有大量的安全从业者转发该钓…

加载数据,并切分

# Step 3 . WebBaseLoader 配置为专门从 Lilian Weng 的博客文章中抓取和加载内容。它仅针对网页的相关部分&#xff08;例如帖子内容、标题和标头&#xff09;进行处理。 加载信息 from langchain_community.document_loaders import WebBaseLoader loader WebBaseLoader(w…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.5 高级索引应用:图像处理中的区域提取

2.5 高级索引应用&#xff1a;图像处理中的区域提取 目录/提纲 #mermaid-svg-BI09xc20YqcpUam7 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-BI09xc20YqcpUam7 .error-icon{fill:#552222;}#mermaid-svg-BI09xc20…

房屋中介管理系统的设计与实现

房屋中介管理系统的设计与实现 摘要&#xff1a;随着房地产市场的快速发展&#xff0c;房屋中介行业的信息管理需求日益增长。传统的管理方式已无法满足中介公司对房源信息、客户信息以及业务流程的高效管理需求。为此&#xff0c;本文设计并实现了一套房屋中介管理系统&#x…

Vue指令v-on

目录 一、Vue中的v-on指令是什么&#xff1f;二、v-on指令的简写三、v-on指令的使用 一、Vue中的v-on指令是什么&#xff1f; v-on指令的作用是&#xff1a;为元素绑定事件。 二、v-on指令的简写 “v-on&#xff1a;“指令可以简写为”” 三、v-on指令的使用 1、v-on指令绑…

力扣第435场周赛讲解

文章目录 题目总览题目详解3442.奇偶频次间的最大差值I3443.K次修改后的最大曼哈顿距离3444. 使数组包含目标值倍数的最少增量3445.奇偶频次间的最大差值 II 题目总览 奇偶频次间的最大差值I K次修改后的最大曼哈顿距离 使数组包含目标值倍数的最少增量 奇偶频次间的最大差值I…

编程AI深度实战:给vim装上AI

系列文章&#xff1a; 编程AI深度实战&#xff1a;私有模型deep seek r1&#xff0c;必会ollama-CSDN博客 编程AI深度实战&#xff1a;自己的AI&#xff0c;必会LangChain-CSDN博客 编程AI深度实战&#xff1a;给vim装上AI-CSDN博客 编程AI深度实战&#xff1a;火的编程AI&…

嵌入式知识点总结 操作系统 专题提升(四)-上下文

针对于嵌入式软件杂乱的知识点总结起来&#xff0c;提供给读者学习复习对下述内容的强化。 目录 1.上下文有哪些?怎么理解? 2.为什么会有上下文这种概念? 3.什么情况下进行用户态到内核态的切换? 4.中断上下文代码中有哪些注意事项&#xff1f; 5.请问线程需要保存哪些…

python算法和数据结构刷题[6]:二叉树、堆、BFS\DFS

遍历二叉树 前序遍历NLR&#xff1a;先访问根结点&#xff0c;再前序遍历左子树&#xff0c;最后前序遍历右子树。中序遍历LNR&#xff1a;先中序遍历左子树&#xff0c;再访问根结点&#xff0c;最后中序遍历右子树。后序遍历 LRN&#xff1a;先后序遍历左子树&#xff0c;再…

012-51单片机CLD1602显示万年历+闹钟+农历+整点报时

1. 硬件设计 硬件是我自己设计的一个通用的51单片机开发平台&#xff0c;可以根据需要自行焊接模块&#xff0c;这是用立创EDA画的一个双层PCB板&#xff0c;所以模块都是插针式&#xff0c;不是表贴的。电路原理图在文末的链接里&#xff0c;PCB图暂时不选择开源。 B站上传的…

w191教师工作量管理系统的设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

Python 网络爬虫实战:从基础到高级爬取技术

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 1. 引言 网络爬虫&#xff08;Web Scraping&#xff09;是一种自动化技术&#xff0c;利用程序从网页中提取数据&#xff0c;广泛…

[漏洞篇]SQL注入漏洞详解

[漏洞篇]SQL注入漏洞详解 介绍 把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串&#xff0c;最终达到欺骗服务器执行恶意的SQL命令。通过构造恶意的输入&#xff0c;使数据库执行恶意命令&#xff0c;造成数据泄露或者修改内容等&#xff0c;以达到攻击的目的。…

C#,shell32 + 调用控制面板项(.Cpl)实现“新建快捷方式对话框”(全网首发)

Made By 于子轩&#xff0c;2025.2.2 不管是使用System.IO命名空间下的File类来创建快捷方式文件&#xff0c;或是使用Windows Script Host对象创建快捷方式&#xff0c;亦或是使用Shell32对象创建快捷方式&#xff0c;都对用户很不友好&#xff0c;今天小编为大家带来一种全新…

DDD - 微服务架构模型_领域驱动设计(DDD)分层架构 vs 整洁架构(洋葱架构) vs 六边形架构(端口-适配器架构)

文章目录 引言1. 概述2. 领域驱动设计&#xff08;DDD&#xff09;分层架构模型2.1 DDD的核心概念2.2 DDD架构分层解析 3. 整洁架构&#xff1a;洋葱架构与依赖倒置3.1 整洁架构的核心思想3.2 整洁架构的层次结构 4. 六边形架构&#xff1a;解耦核心业务与外部系统4.1 六边形架…

基于SpringBoot的新闻资讯系统的设计与实现(源码+SQL脚本+LW+部署讲解等)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

xmind使用教程

xmind使用教程 前言xmind版本信息“xmind使用教程”的xmind思维导图 前言 首先xmind是什么&#xff1f;XMind 是一款思维导图和头脑风暴工具&#xff0c;用于帮助用户组织和可视化思维、创意和信息。它允许用户通过图形化的方式来创建、整理和分享思维导图&#xff0c;可以用于…

半导体器件与物理篇7 微波二极管、量子效应和热电子器件

基本微波技术 微波频率&#xff1a;微波频率涵盖约从0.1GHz到3000GHz&#xff0c;相当于波长从300cm到0.01cm。 分布效应&#xff1a;电子部件在微波频率&#xff0c;与其在较低频率的工作行为不同。 输运线&#xff1a;一个由电阻、电容、电感三种等效基本电路部件所组成的…

Java自定义IO密集型和CPU密集型线程池

文章目录 前言线程池各类场景描述常见场景案例设计思路公共类自定义工厂类-MyThreadFactory自定义拒绝策略-RejectedExecutionHandlerFactory自定义阻塞队列-TaskQueue&#xff08;实现 核心线程->最大线程数->队列&#xff09; 场景1&#xff1a;CPU密集型场景思路&…

浅谈线段树

文章同步发布于洛谷&#xff0c;建议前往洛谷查看。 前言 蒟蒻终于学会线段树&#xff08;指【模板】线段树 1 1 1&#xff09;啦&#xff01; 线段树思想 我们先来考虑 P3372&#xff08;基础线段树模板题&#xff09;给的操作&#xff1a; 区间修改&#xff08;增加&am…