前端性能优化之防腐篇(三)

引言

在上一期的性能优化专栏中,我们深入探讨了《前端性能优化之统计篇》,其中我们学习了如何用数据量化性能,并将性能与业务指标如到达率、转化率等紧密关联,从而凸显前端在性能提升上的显著成果。紧接着,我们在《前端性能优化之实践篇》中详细介绍了具体的性能优化策略和实施原理,旨在帮助读者实现优化目标。

然而,当我们成功地将项目优化至理想状态时,如何确保这些优化成果得以持久保持,避免性能回退呢?这正是今天我们要共同探讨的主题。接下来,我们将学习如何利用技术手段来守护我们辛勤优化所取得的胜利果实。

历史包袱瘦身

在软件开发的漫长历程中,许多项目都经历了从初创到成熟,再到维护的多个阶段。在这个过程中,随着业务需求的不断增加和技术的不断迭代,项目代码库往往会变得庞大而复杂,形成所谓的"代码屎山"。这些项目可能包含成千上万的代码行、分散在多个文件中的工具函数、以及大量从未被使用过的僵尸代码。面对这样的项目,如何进行有效的精简和瘦身,成为了每个开发者都需要面对的挑战。

首先,我们需要对项目的整体结构进行梳理。通过代码审计和静态分析,识别出冗余、重复或不必要的代码段。对于长时间未使用的方法、类或文件,我们可以考虑进行删除或归档,以减少项目的体积和复杂度。

其次,我们需要对项目的依赖关系进行整理。通过依赖管理工具(如npm、yarn等),我们可以清晰地看到项目中使用了哪些库和模块,以及它们之间的依赖关系。对于不再需要或已经废弃的依赖,我们应该及时删除,以减轻项目的负担。

此外,我们还可以利用重构技术来优化项目的结构。通过重新设计类的继承关系、拆分过大的文件、提取公共方法等手段,我们可以使代码更加清晰、易于理解和维护。在重构过程中,我们应该遵循SOLID等面向对象设计原则,确保代码的可读性、可维护性和可扩展性。

webpack-deadcode-plugin

Webpack Deadcode Plugin是一个Webpack插件,用于检测和移除JavaScript代码中的死代码(Dead Code)。死代码指的是那些永远不会被执行到的代码,例如未使用的函数、变量或者条件分支。通过移除这些代码,我们可以显著减少代码包的体积,提升应用的加载速度和运行效率。

工作原理

Webpack Deadcode Plugin的工作原理基于静态代码分析。在Webpack构建过程中,该插件会遍历所有的JavaScript模块,并尝试分析每个模块的执行路径。通过跟踪函数的调用关系和变量的引用情况,插件可以识别出那些永远不会被执行到的代码,并将其从最终构建的代码包中移除。

需要注意的是,由于JavaScript的动态特性(例如通过字符串动态调用函数或变量),有些死代码可能无法被静态分析器完全识别出来。因此,在使用Webpack Deadcode Plugin时,建议结合其他优化手段(例如代码分割、Tree Shaking等)来进一步提高代码包的优化效果。

删除无使用模块及变量

  • 删除无用页面后,使用 webpack-deadcode-plugin 工具检测扫描 + 手动确认后删除(注:新增一条 npm script 命令用于后续持续性分析优化)

  • 开启未使用变量检测提示,配合 VS Code 插件提示辅助删除

    • 开启 eslint 规则检测 js/ts/vue 文件,针对未使用变量提交时强报错提示

    perl 复制代码
      {    "rules": {            "@typescript-eslint/no-unused-vars": [            "error",            {                "argsIgnorePattern": "^_"            }      ],    }}

开启 tsc 编译配置校验未使用变量与未使用函数参数

json 复制代码
{    "noUnusedLocals": true,    "noUnusedParameters": true} 
  • 手动修改建议优先参考 VS Code 提示,注意以下情况:

    • 未使用函数参数不在末尾时不能直接删除,使用 _ 前缀约定
    • 存在扩展参数解构时,不能直接删除未使用变量导致副作用产生,需要针对特定行禁用语法(同 VS Code 提示)
    • 调用函数返回值声明未使用时 VS Code 提示会删除整段声明 + 调用,这里只删除声明即可
    • service 方法中参数未使用时如 controller 未传入可直接删除,否则使用 _ 前缀约定

注意事项

  1. 谨慎使用:虽然Webpack Deadcode Plugin可以帮助你移除死代码,但有时候也可能会误删除一些必要的代码。因此,在使用该插件时,请务必仔细检查生成的代码包,确保没有误删除任何重要的代码。
  2. 兼容性:该插件可能无法完全兼容所有的Webpack版本和配置。在使用之前,请查阅官方文档以了解最新的兼容性和使用说明。
  3. 性能影响:由于静态代码分析需要消耗一定的计算资源,因此在大型项目中使用该插件可能会增加构建时间。你可以根据实际情况权衡利弊,选择是否使用该插件。

性能守卫

为了进一步保护我们的性能优化的劳动成果,除了删除无使用的代码、变量等,还可以在代码提交时进一步进行性能评分,针对严重影响性能的可以进一步警告和拦截。

性能守卫是一种系统或工具,用于监控和管理应用程序或系统的性能。它旨在确保应用程序在各种负载和使用情况下能够提供稳定和良好的性能。

Lighthouse

1. Chrome开发者工具(DevTools)

  • 下载与安装 :
    • 下载Lighthouse插件(如"前端性能测试工具-Lighthouse"或"Lighthouse_chrome"插件)。
    • 解压下载的zip包,得到.crx后缀的离线安装包。
    • 打开Chrome浏览器的扩展程序管理菜单(地址栏输入chrome://extensions/或依次点击"右上角三个点" -> "更多工具" -> "扩展程序")。
    • 勾选"开发者模式",然后将.crx安装包拖入Chrome浏览器完成安装。
  • 使用 :
    • 按F12打开DevTools,选择"分析网页加载情况"进行网页性能分析。

2. Node CLI

  • 命令行参数:

    • --chrome-flags:传递自定义标志给Chrome/Chromium,如--headless用于无头模式运行。
    • --only-categories:仅运行指定的类别,如--only-categories=performance仅运行性能评估。
    • --preset:使用预设的类别集,如--preset=desktop评估页面在桌面设备上的表现。
    • --locale:设置报告的语言,如--locale=zh生成中文报告。
    • --quiet:静默模式,不显示进度或调试信息。
    • --output:将报告输出到文件而不是控制台,如--output=html生成HTML格式的报告。
  • 常用命令示例:

    css 复制代码
    bash复制代码lighthouse url --quiet --chrome-flags="--headless" --only-categories=performance --locale=zh-CN --output-path=report.html

3. Node Module

  • 环境初始化 :
    • 创建一个项目目录并进入。
    • 初始化Node环境(npm init -y)。
    • 安装必要的NPM包,如puppeteer用于提供浏览器环境。
  • 工作流程 :
    • 建立连接。
    • 收集日志。
    • 分析。
    • 生成报告。

4. Chrome扩展

  • 直接安装 :
    • 从Chrome网上应用店或其他可信来源直接安装Lighthouse扩展。
  • 使用 :
    • 在Chrome浏览器中打开需要分析的网页,点击Lighthouse扩展图标开始分析。

性能守卫插件

lighthouse-ci实现机制很简单,核心实现步骤如上图,差异就是lighthouse-ci实现了自己的server端,保持导出的性能指标数据,由于公司一般对这类数据敏感,所以我们一般只需要导出对应的数据指标JSON,上传到我们自己的平台就行了。同时借助无头浏览器是否可以实现一个插件呢?这个插件可以模拟用户访问基于相关指标计算出当前代码的性能的分呢?

实现一个性能守卫插件

在实现一个性能守卫插件,我们需要考虑以下因数:

    • 易用性和灵活性:插件应该易于配置和使用,以便它可以适应各种不同的CI/CD环境和应用场景。它也应该能够适应各种不同的性能指标和阈值。2
    • 稳定性和可靠性:插件需要可靠和稳定,因为它将影响整个构建流程。任何失败或错误都可能导致构建失败,所以需要有强大的错误处理和恢复能力。
    • 3性能:插件本身的性能也很重要,因为它将直接影响构建的速度和效率。它应该尽可能地快速和高效。
    • 可维护性和扩展性:插件应该设计得易于维护和扩展,以便随着应用和需求的变化进行适当的修改和更新。
    • 报告和通知:插件应该能够提供清晰和有用的报告,以便开发人员可以快速理解和处理任何性能问题。它也应该有一个通知系统,当性能指标低于预定阈值时,能够通知相关人员。
    • 集成:插件应该能够轻松集成到现有的CI/CD流程中,同时还应该支持各种流行的CI/CD工具和平台。
    • 安全性:如果插件需要访问或处理敏感数据,如用户凭证,那么必须考虑安全性。应使用最佳的安全实践来保护数据,如使用环境变量来存储敏感数据。

实现

javascript 复制代码
// 伪代码
//perfci插件
const puppeteer = require('puppeteer');
const lighthouse = require('lighthouse');
const {
    port
} = new URL(browser.wsEndpoint());

async function runAudit(url) {
    const browser = await puppeteer.launch();
    const {
        lhr
    } = await lighthouse(url, {
        port,
        output: 'json',
        logLevel: 'info',
    });
    await browser.close();

    // 在这里定义你的性能预期
    const performanceScore = lhr.categories.performance.score;
    if (performanceScore < 0.9) { // 如果性能得分低于0.9,脚本将抛出错误
        throw new Error(`Performance score of ${performanceScore} is below the threshold of 0.9`);
    }
}

runAudit('https://example.com').catch(console.error);

使用

yaml 复制代码
name: CI
on: [push]
jobs:
    lighthouseci:
    runs - on: ubuntu - latest
steps:
    -uses: actions / checkout @v3 -
    uses: actions / setup - node @v3
with:
    node - version: 16 -
    run: npm install & amp; & amp;
npm install - g @lhci / cli @0 .11.x -
    run: npm run build -
    run: perfci autorun

性能审计

javascript 复制代码
const lighthouse = require('lighthouse');
const puppeteer = require('puppeteer');
const nodemailer = require('nodemailer');

// 配置邮件发送器
const transporter = nodemailer.createTransport({
    service: 'gmail',
    auth: {
        user: 'your-email@gmail.com',
        pass: 'your-password',
    },
});

// 定义一个函数用于执行Lighthouse审计并处理结果
async function runAudit(url) {
    // 通过Puppeteer启动Chrome
    const browser = await puppeteer.launch({
        headless: true
    });
    const {
        port
    } = new URL(browser.wsEndpoint());

    // 使用Lighthouse进行性能审计
    const {
        lhr
    } = await lighthouse(url, {
        port
    });

    // 检查性能得分是否低于阈值
    if (lhr.categories.performance.score < 0.9) {
        // 如果性能低于阈值,发送警告邮件
        let mailOptions = {
            from: 'your-email@gmail.com',
            to: 'admin@example.com',
            subject: '网站性能低于阈值',
            text: `Lighthouse得分:${lhr.categories.performance.score}`,
        };

        transporter.sendMail(mailOptions, function (error, info) {
            if (error) {
                console.log(error);
            } else {
                console.log('Email sent: ' + info.response);
            }
        });
    }

    await browser.close();
}

// 使用函数
runAudit('https://example.com');

数据告警

javascript 复制代码
// 伪代码
const lighthouse = require('lighthouse');
const puppeteer = require('puppeteer');
const nodemailer = require('nodemailer');

// 配置邮件发送器
const transporter = nodemailer.createTransport({
    service: 'gmail',
    auth: {
        user: 'your-email@gmail.com',
        pass: 'your-password',
    },
});

// 定义一个函数用于执行Lighthouse审计并处理结果
async function runAudit(url) {
    // 通过Puppeteer启动Chrome
    const browser = await puppeteer.launch({
        headless: true
    });
    const {
        port
    } = new URL(browser.wsEndpoint());

    // 使用Lighthouse进行性能审计
    const {
        lhr
    } = await lighthouse(url, {
        port
    });

    // 检查性能得分是否低于阈值
    if (lhr.categories.performance.score < 0.9) {
        // 如果性能低于阈值,发送警告邮件
        let mailOptions = {
            from: 'your-email@gmail.com',
            to: 'admin@example.com',
            subject: '网站性能低于阈值',
            text: `Lighthouse得分:${lhr.categories.performance.score}`,
        };

        transporter.sendMail(mailOptions, function (error, info) {
            if (error) {
                console.log(error);
            } else {
                console.log('Email sent: ' + info.response);
            }
        });
    }

    await browser.close();
}

// 使用函数
runAudit('https://example.com');

处理设备、网络等不稳定情况

php 复制代码
// 伪代码

// 网络抖动
const {
    lhr
} = await lighthouse(url, {
    port,
    emulatedFormFactor: 'desktop',
    throttling: {
        rttMs: 150,
        throughputKbps: 1638.4,
        cpuSlowdownMultiplier: 4,
        requestLatencyMs: 0,
        downloadThroughputKbps: 0,
        uploadThroughputKbps: 0,
    },
});


// 设备
const {
    lhr
} = await lighthouse(url, {
    port,
    emulatedFormFactor: 'desktop', // 这里可以设定为 'mobile' 或 'desktop'
});

用户登录态问题

javascript 复制代码
const puppeteer = require('puppeteer');
const lighthouse = require('lighthouse');
const fs = require('fs');
const axios = require('axios');
const {
    promisify
} = require('util');
const {
    port
} = new URL(browser.wsEndpoint());

// promisify fs.writeFile for easier use
const writeFile = promisify(fs.writeFile);

async function runAudit(url, options = {
    port
}) {
    // 使用Puppeteer启动Chrome
    const browser = await puppeteer.launch();
    const page = await browser.newPage();

    // 访问登录页面
    await page.goto('https://example.com/login');

    // 输入用户名和密码
    await page.type('#username', 'example_username');
    await page.type('#password', 'example_password');

    // 提交登录表单
    await Promise.all([
    page.waitForNavigation(), // 等待页面跳转
    page.click('#login-button'), // 点击登录按钮
  ]);

    // 运行Lighthouse
    const {
        lhr
    } = await lighthouse(url, options);

    // 保存审计结果到JSON文件
    const resultJson = JSON.stringify(lhr);
    await writeFile('lighthouse.json', resultJson);

    // 上传JSON文件到服务器
    const formData = new FormData();
    formData.append('file', fs.createReadStream('lighthouse.json'));

    // 上传文件到你的服务器
    const res = await axios.post('https://your-server.com/upload', formData, {
        headers: formData.getHeaders()
    });

    console.log('File uploaded successfully');

    await browser.close();
}

// 运行函数
runAudit('https://example.com');

小结

总之,性能优化是一个持续不断的过程。通过明确目标、数据驱动、精简代码、资源优化、异步加载、渲染优化、持续监控和团队协作等手段,我们能够不断提升软件的性能表现,为用户提供更好的体验。

参考链接

cloud.tencent.com/developer/a...

github.com/MQuy/webpac...

相关推荐
brien942 分钟前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance1 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M1 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写1 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李1 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易1 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马2 小时前
前沿视界
前端
kyriewen2 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易2 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员
IT_陈寒2 小时前
Vue的这个响应式陷阱,差点让我加班到天亮
前端·人工智能·后端