webpack 学习笔记系列06-打包优化

webpack 学习笔记系列06-打包优化

Write By CS逍遥剑仙

我的主页: csxiaoyao.com

GitHub: github.com/csxiaoyaojianxian

Email: sunjianfeng@csxiaoyao.com

QQ: 1724338257

1. splitChunks 拆分代码

1.1 三种拆分方式

webpack 的三种代码拆分方式:

  • entry 入口配置
  • 使用 import()require.ensure 动态按需加载
  • webpack4 的 splitChunks 配置取代之前的 CommonsChunkPlugin

1.2 splitChunks 默认配置

splitChunks 默认配置对应上述的第二种按需加载方式:

module.exports = {
    // ... 
    optimization: {
        splitChunks: {
            chunks: 'async', // initial|all|async(默认)
          	maxInitialRequests: 3, // 初始化最大文件数,优先级高于 cacheGroup,为 1 时不抽取 initial common
          	maxAsyncRequests: 5, // 按需加载最大异步请求数量,为 1 时不抽取公共 chunk
            maxSize: 0, // 文件最大尺寸,0不限制
          	minSize: 30000, // 文件最小尺寸,默认30K,development 下10k,与 chunk 数量成反比
            minChunks: 1, // 默认 1,被提取的模块至少要在几个 chunk 中被引用,值越大,抽取出的文件越小
            automaticNameDelimiter: '~', // 打包文件名分隔符
            name: true, // 拆分的文件名,默认 true 自动生成文件名,若设置为固定字符串,则所有 chunk 合并成一个
            cacheGroups: {
                vendors: {
                    test: /[\\/]node_modules[\\/]/, // 正则规则,如果符合就提取 chunk 
                    priority: -10 // 缓存组优先级,当一个模块可能属于多个 chunkGroup,这里是优先级 
                },
                default: {
                    minChunks: 2,
                    priority: -20, // 优先级 
                    reuseExistingChunk: true // 如果该chunk包含的modules都已经另一个被分割的chunk中存在,那么直接引用已存在的c hunk,不会再重新产生一个 
                }
            }
        }
    }
};

除 JavaScript, splitChunks 也适用于使用 mini-css-extract-plugin 插件的 css 配置

1.2.1 chunks

可选值:async(默认) | initial | all(推荐),针对下面的 a.jsb.js

// a.js
import $ from 'jquery';
import react from 'react';
import( /* webpackChunkName: "a-lodash" */ 'lodash');

// b.js
import $ from 'jquery';
import( /* webpackChunkName: "b-react" */ 'react');
import( /* webpackChunkName: "b-lodash" */ 'lodash');

采用三种不同配置:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
    mode: 'development',
    entry: {
        a: './a.js',
        b: './b.js'
    },
    plugins: [new BundleAnalyzerPlugin()],
    optimization: {
        splitChunks: {
            cacheGroups: {
                vendors: {
                    chunks: 'async', // async|initial|all 
                    test: /[\\/]node_modules[\\/]/
                }
            }
        }
    }
};

async: 只对动态引入的代码拆分

  • jquery 分别打包进 a.jsb.js
  • react 被打包进 a.js 和拆出 venders~b-react.js
  • lodash 拆为同一个 venders~a-lodash.js

initial: 共用即拆(动态引入一定拆分),根据阈值 minChunks 配置拆分

  • jquery 因共用被拆为 vendors~a~b.js
  • react 分别拆为 vendors~a.js(动态引入) 和 b-react.js(魔法注释),注意:若 minSize 设置较大,不会单独拆出 vendors~a.js
  • lodash 拆为同一个 a-lodash.js(魔法注释)

all: 推荐,在 initial 基础上尽可能生成复用代码,如 initialreact 拆为同一个 vendors~a~b-react.js

1.2.2 maxInitialRequests / maxAsyncRequests / maxSize / minSize

优先级:maxInitialRequest / maxAsyncRequests < maxSize < minSize

maxInitialRequests: 针对每个 entry 初始化的最大文件数,优先级高于 cacheGroup,因此为 1 时不会抽取 initial common

maxAsyncRequests: 每次按需加载最多有多少个异步请求,为 1 时不抽取公共 chunk

maxSize: 文件最大尺寸,0不限制

minSize: 文件最小尺寸,默认30K,development 下10k,与 chunk 数量成反比

webpack 魔法注释

import(/* webpackChunkName: "react" */ 'react'); // 可以设置生成的 bundle 名称

使用 webpack-bundle-analyzer 插件查看打包情况

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { mode: 'production', entry: { main: './default/index.js' }, plugins: [new BundleAnalyzerPlugin()] };

1.3 cacheGroups 缓存组

splitChunks 的配置项都是作用于 cacheGroup 上的,默认有两个 cacheGroup:vendorsdefault (上节 splitChunks 默认配置),支持重写,也支持设置为 false (splitChunks将失效)

cacheGroups 除拥有上节所有 splitChunks 默认配置,额外支持 test / priority / reuseExistingChunk

1.3.1 reuseExistingChunk

是否使用已有的 chunk

1.3.2 priority

权重,若一个模块满足多个缓存组条件,则按权重决定

1.3.3 test

缓存组命中条件,取值为正则、字符串和函数

cacheGroups: {
    vendors: {
      	// test: /[\\/]node_modules[\\/]/,
        test(module, chunks) {
            //...
            return module.type === 'javascript/auto'; 
        },
        priority: -20
    }
}

2. 构建速度优化

影响 webpack 构建速度的主要是:

  • loader/plugin 的构建过程
  • 压缩过程

可以从减少查找过程、多线程、提前编译和 Cache 多角度优化

2.1 减少查找过程

  • resolve.alias: 通过别名跳过耗时的递归模块解析操作
module.exports = {
    resolve: {
        // 生产环境直接使用 react.min.js
        alias: {
            react: path.resolve(__dirname, './node_modules/react/dist/react.min.js'),
            '@lib': path.resolve(__dirname, './src/lib/')
        }
    }
};
  • resolve.extensions: 配置后缀顺序,减少模块导入时的推测
resolve.extensions = ['js', 'json']
  • module.noParse: 排除不需要解析的模块

尤其是 jQuery 等未采用模块化标准且体积庞大的库,但要注意,排除的文件不能包含 importrequiredefine 等模块化语句。

  • rule: 通过 testincludeexclude 控制查找范围
rules: [{
    loader: 'babel-loader',
    test: /\.js$/, // test 正则
    exclude: [path.resolve(__dirname, './node_modules')], // 排除绝对路径 Array
    include: [path.resolve(__dirname, './src')] // 查找绝对路径 Array
}];

exclude 优先级 > include / test,建议多用 include 避免用 exclude。

2.2 多线程

使用 thread-loaderHappyPack 可以实现对大项目的多线程打包。

thread-loader: 将 loader 放在一个 worker 池中运行,以达到多线程构建

module.exports = {
    module: {
        rules: [{
            test: /\.js$/,
            include: path.resolve('src'),
            use: [
            		'thread-loader', // 需要放在其他 loader 之前
                // 其他高开销 loader (e.g babel-loader)
            ]
        }]
    }
};

HappyPack: 通过多进程模型加速代码构建,但需要对应的 loader 支持

const os = require('os');
const HappyPack = require('happypack');
// 根据 cpu 数量创建线程池 
const happyThreadPool = HappyPack.ThreadPool({
    size: os.cpus().length
});
module.exports = {
    module: {
        rules: [{
            test: /\.js$/,
            use: 'happypack/loader?id=jsx'
        }, {
            test: /\.less$/,
            use: 'happypack/loader?id=styles'
        }]
    },
    plugins: [
        new HappyPack({
            id: 'jsx',
            threads: happyThreadPool,
            loaders: ['babel-loader']
        }),
        new HappyPack({
            id: 'styles',
            threads: 2, // 自定义线程数量 
            loaders: ['style-loader', 'css-loader', 'less-loader']
        })
    ]
};

2.3 DllPlugin 预编译

预先编译打包一些不变化的库文件,在业务代码中直接引入。需要单独为 dll 文件创建一个配置文件,通过 DLLPlugin 插件将第三方依赖打包到 bundle 文件,并生成 manifest.json 文件,在项目的 webpack 配置文件中使用 DllReferencePlugin 插件解析 manifest.json,跳过 dll 中包含的依赖的打包。

// 单独的 dll 打包配置文件 webpack.config.dll.js 
const webpack = require('webpack');
const vendors = ['react', 'react-dom']; // 第三方依赖库 
module.exports = {
    mode: 'production',
    entry: {
        vendor: vendors // 打包公共文件的入口文件设为 vendor.js
    },
    output: {
        filename: '[name].[chunkhash].js',
        library: '[name]_[chunkhash]' // 将 verdor 作为 library 导出,并指定全局变量名 [name]_[chunkhash]
    },
    plugins: [
      	new webpack.DllPlugin({
            path: 'manifest.json', // 设置 mainifest.json 路径
            name: '[name]_[chunkhash]',
            context: __dirname
    		})
    ]
};

执行 webpack --config webpack.config.dll.js

// 项目配置文件 webpack.config.js 
const webpack = require('webpack');
module.exports = {
    output: {
        filename: '[name].[chunkhash].js'
    },
    entry: {
        app: './src/index.js'
    },
    plugins: [
      	new webpack.DllReferencePlugin({
            context: __dirname,
            manifest: require('./manifest.json') // 导入
    		})
    ]
};

注意:打包后的 html 中不会主动引入 dll 的 vendor.js 文件,需要手动处理。

2.4 cache 缓存

babel-loader 往往是编译过程中最耗时的环节,虽然提供了 cacheDirectory 配置指定缓存目录,但默认为 false 关闭,设为 true 则使用默认的缓存目录 node_modules/.cache/babel-loader

rules: [{
    test: /\.js$/,
    loader: 'babel-loader',
    options: {
        cacheDirectory: true
    },
    exclude: /node_modules/,
    include: [path.resolve('.src')]
}];

2.5 其他

  • sourceMap 选择合适的 devtool 值
  • 切换更快的 loader
  • terser-webpack-plugin 开启多线程和缓存
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
    optimization: {
        minimizer: [new TerserPlugin({
            cache: true, // 开启缓存
            parallel: true // 多线程
        })]
    }
};

3. Tree-Shaking

ES6 Modules 是 Tree-Shaking 静态分析的基础。Webpack 通过分析 ES6 模块的引入和使用情况,去除不使用的 import 引入;此外,可以借助工具如 uglifyjs-webpack-pluginterser-webpack-plugin 进行删除(仅 mode=production 下生效)。树摇的实现需要保持良好的开发习惯:

  1. 必须使用 ES6 模块
  2. 按需引入,尤其是 UI 框架
  3. 减少代码中的副作用(纯函数)
// package.json
{
    "name": "tree-shaking-side-effect",
    "sideEffects": ["./src/utils.js"]
}

package.json 中,除了通过 sideEffects 指定有副作用的文件,若能确保没有副作用,可以设置 sideEffects: false 不再考虑副作用。

sign

原文地址:https://cloud.tencent.com/developer/article/1839707

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340