webpack性能优化

性能优化, 可以分为三个方面:

  • 构建性能
    主要指开发阶段的构建性能, 降低从打包开始到代码呈现效果所经过的时间
  • 传输性能
    指打包后的js代码传输到浏览器经过的时间. 优化传输性能要考虑到
    1. 总传输量
    2. 文件数量
    3. 浏览器缓存
  • 运行性能
    指js代码在浏览器端的运行速度, 主要取决于如何书写高性能代码

构建性能

1. 减少模块解析

模块解析包括: 抽象语法树分析, 依赖分析, 替换依赖函数等.

哪些模块不需要解析?

  • 模块中无其他依赖: 一些已经打包好的第三方库, 比如jquery

如何让webpack不解析某些模块?

  • 配置noParse

2. 优化loader性能

  • 进一步限制loader的应用范围
    使用exlucdeinclude排除或仅包含需要应用loader的场景(在每个module下, 每个rule配置对象内)
  • 缓存loader结果
    使用cache-loader缓存loader(它必须放在所有loader第一位)
    loader是倒序运行的, cache-loader放第一位却能控制其他loader是否执行, 是因为其实loader运行还包括一个过程, 即pitch. pitch是loader函数的静态属性, 它也是一个函数, 这个函数有一个参数是文件的路径, 如果这个函数返回文件内容, 那么直接交给到配置里它的前一个loader去执行, 如果pitch函数没返回, 那么把路径按照配置的顺序交到下一个loader的pitch函数. 直到最后一个loader, pitch函数都没返回, 才读取文件内容, 然后开始倒序交给一个一个loader处理.
  • 为loader的运行开启多线程
    thread-load可以开启一个线程池, 后续的loader会放到线程池中运行.但是后续的loader不能:
    - 使用webpack api生成文件
    - 使用自定义的plugin api
    - 访问webpack options

3. 热替换

热替换并不能降低构建时间, 但是能降低代码改动到效果呈现的时间.

a) 更改配置

module.exports = {
    devServer: {
        hot: true
    },
    plugins: [
        // 可选, webpack 4之后如果设置了hot: true, 默认使用这个插件
        new webpack.HotModuleReplacementPlugin()
    ]
}

b) 更改代码

if (module.hot) {
    module.hot.accept();
}

样式热替换: 使用style-loader可以进行热替换. 但是如果用mini-css-extract-plugin, 由于它生成文件是在构建期间, 运行期间无法改动文件, 热替换对它无效

传输性能

1. 分包

什么是分包?

将一个整体的代码, 分布到不同的打包文件中

为什么要分包?

  • 减少公共代码, 降低总体积(特别是一些大型的第三方库)
  • 充分利用浏览器缓存

什么时候要分包?

  • 多个chunk引入了公共模块
  • 公共模块体积较大或较少变动

如何分包?

  • 手动分包
    步骤:

    • 打包公共模块, 生成manifest.json资源清单
    • 使用公共模块, 此时代码中引入公共模块的时候, 就不再把公共模块代码引入打包

    打包公共模块是另外的过程, 需要单独使用另一个配置文件, 专门配置, 如:

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

module.exports = {
    mode: "production",
    entry: {
        jquery: ["jquery"],
        lodash: ["lodash"]
    },
    output: {
        filename: "dll/[name].js",
        // 打包结果暴露的全局变量名
        library: "[name]"
    },
    plugins: [
        new webpack.DllPlugin({
            path: path.resolve(__dirname, "dll", "[name].manifest.json"),
            name: "[name]"
        })
    ]
}

然后要在源代码的配置文件中:

  • 重新设置clean-webpack-plugin, 避免它清除公共模块
  • 在模板html中手动引入公共模块
  • 使用DllReferencePlugin, 如
plugins: [
        new CleanWebpackPlugin({
            cleanOnceBeforeBuildPatterns: ["**/*", "!dll", "!dll/*"]
        }),
        new HtmlWebpackPlugin({
            template: "./public/index.html"
        }),
        new webpack.DllReferencePlugin({
            manifest: require("./dll/jquery.manifest.json")
        }),
        new webpack.DllReferencePlugin({
            manifest: require("./dll/lodash.manifest.json")
        }),
    ]
  • 自动分包
    自动分包现在由webpack根据分包策略配置自动完成. webpack有默认的分包配置, 能够在绝大多数情况下都适用.
    详细配置见官网, 大致配置如下:
module.exports = {
    optimization: {
        // 分包策略的配置
        splitChunks: {
            ...
        }
    }
}

2. 代码压缩

webpack自动集成了terser(一个代码压缩工具)
在生产环境下, webpack使用 terser自动完成压缩.

3. tree shaking

能够使得单模块体积优化. 可以移除模块之间的无效代码. (某些模块的导出的代码不一定用到, tree shaking用于移除掉不会用到的部分)

webpack 2开始就支持了tree shaking, 只要是生产环境, tree shaking自动开启.

原理

webpack会从入口模块出发寻找依赖关系.

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

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

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

如果依赖的是一个导出的对象, 由于js语言动态的特性, 为了保证代码正常运行, 它不会移除对象中的任何信息.

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

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

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

4. 懒加载

懒加载指在运行时再动态加载需要的文件.

直接看示例代码

const btn = document.querySelector("button");
btn.onclick = async function () {
    
    // 动态加载
    // import(xxx)是es6的草案, 不是正式标准
    // 此处可以想象为浏览器使用jsonp的方式远程去读取一个js模块
    // 这里被import的内容会被单独打包成一个js
    // import()会返回一个promise, 结果类似于(* as obj)
    const _ = await import(/* webpackChunkName: "lodash */"lodash-es");
    const result = _.chunk([3, 5, 6, 89, 1, 31], 2);
    console.log(result);
}

5. gzip

客户端 - -服务器传输之间, 有可能会对传输内容进行压缩. 浏览器发送请求时可以设置Accept-Encoding来告诉服务器支持哪些压缩方式, 服务器就可以返回压缩后的资源文件给客户端.

webpack可以使用compression-webpack-plugin来对打包内容进行压缩(增加输出压缩后的文件, 而不是只输出压缩后的文件)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343