如何找到未使用 JS/CSS 并移除?

如何找到未使用 JS/CSS 并移除?未使用的 JavaScript 会拖慢页面加载速度,代码在下载时也会与其他资源竞争带宽,这会对性能产生重大影响。

未使用的 JavaScript 会拖慢页面加载速度,主要体现在以下几点:

▪   如果 JavaScript 是渲染阻塞的,则浏览器必须下载、解析、编译和执行脚本,然后才能继续渲染页面所需的所有其他工作。

▪   即使 JavaScript 是异步的(不是渲染阻塞),代码在下载时也会与其他资源竞争带宽,这会对性能产生重大影响。 在昂贵的手机流量费用面前,通过网络发送未使用的代码也是一种浪费。

如何删除未使用的代码

查找未使用的代码

Chrome DevTools 中的coverage选项卡可以为开发者提供未使用代码的逐行细分。 Puppeteer 中的 Coverage 类也可以帮助自动执行检测未使用代码和提取已使用代码的过程,比如下面的代码:

// 启动 JavaScript 和 CSS coverage分析
await Promise.all([
  page.coverage.startJSCoverage(),
  page.coverage.startCSSCoverage(),
]);
// 导航到一个页面
await page.goto('https://example.com');
// 禁止 JavaScript 和 CSS coverage分析
const [jsCoverage, cssCoverage] = await Promise.all([
  page.coverage.stopJSCoverage(),
  page.coverage.stopCSSCoverage(),
]);
let totalBytes = 0;
let usedBytes = 0;
const coverage = [...jsCoverage, ...cssCoverage];
for (const entry of coverage) {
  totalBytes += entry.text.length;
  for (const range of entry.ranges) usedBytes += range.end - range.start - 1;
}
console.log(`Bytes used: ${(usedBytes / totalBytes) * 100}%`);

在浏览器中,可以通过下面的方法查找未使用的代码:
▪   当 DevTools 处于焦点状态时,按 Command+Shift+P (Mac) 或 Control+Shift+P(Windows、Linux、ChromeOS)可打开命令菜单,输入coverage。

如何找到未使用 JS/CSS 并移除?

▪   选择Show Coverage

如何找到未使用 JS/CSS 并移除?

▪   单机录制按钮,Coverage选项卡提供了浏览器加载的每个文件使用了多少 CSS(和 JavaScript)的概览。

如何找到未使用 JS/CSS 并移除?

绿色代表使用的 CSS,红色代表未使用的 CSS。

▪   单击 CSS 文件可在上面的预览中查看其使用的 CSS 的逐行细分
如何找到未使用 JS/CSS 并移除?

在上面的屏幕截图中,devsite-google-blue.css 的第 55 至 57 行和 65 至 67 行未使用,而第 59 至 63 行已使用。

支持删除未使用代码的构建工具

查看以下 Tooling.Report 测试,了解打包程序是否支持删除未使用的代码的功能:

代码分割

代码分割是交付高性能 JavaScript 应用程序的重要组成部分,有助于避免下载和执行超出给定页面所需的 JavaScript。 从更高的层次上来说,“代码拆分”是指将打包的代码分解为多个较小的 Bundle 包的过程,这些 Bundle 包可以根据需要独立加载和执行。

死代码消除(Unused Code Elimination)

死代码消除是删除当前应用程序未使用的代码的过程。 代码被解析以创建一个抽象语法树,然后遍历该树以查找未使用的函数和变量,最后该树被转换回 JavaScript 源代码。

有许多工具可以在 JavaScript 源代码上执行死代码消除,其中最流行的是 Terser 和 Closure Compiler。

在下面的测试中,每个构建工具都配置为通过其内置的“production”选项来优化 Bundle 包,或者在没有此类选项的情况下使用最常见的配置。 一些工具能够作为 Bundle 的一部分执行死代码消除,其他工具可能依赖于 Terser 等其他工具。

// index.js
import { logCaps } from './utils.js';
logCaps(exclaim('This is index'));

function thisIsNeverCalled() {
  console.log(`No, really, it isn't`);
}

下面是 Utils.js 的内容:

export function logCaps(msg) {
  console.log(msg.toUpperCase());
}

export function thisIsNeverCalledEither(msg) {
  return msg + '!';
}

一旦为生产而构建, thisIsNeverCalled 和 thisIsNeverCalledEither 函数都应该从包中完全删除。

死导入代码(Dead Imported Code)

未由应用程序中的任何其他模块导入或使用的模块的导出也可以被视为死代码并需要删除。

然而,这可能会导致一些棘手的优化问题,因为模块的导出可能会以难以静态分析的方式被使用。 动态导入就是其中一种情况,因为动态导入返回的模块记录具有每个导出的属性,可以通过多种不同的方式引用这些属性,其中一些无法在构建时确定。

下面的测试用使用了两个模块 , 一个入口模块和一个动态导入以创建分割点的 utils.js 模块。动态导入的模块有两个导出,但只使用了 logCaps 导出。

// index.js
(async function () {
  const { logCaps } = await import('./utils.js');
  logCaps('This is index');
})(); 下面是 utils.js 内容:
// utils.js
export function logCaps(msg) {
  console.log(msg.toUpperCase());
}

export function thisIsNeverCalled(msg) {
  return msg + '!';
}

一旦为生产而构建,utils.js 中的 thisIsNeverCalled 函数不应出现在生成的 Bundle 中。

然而,不同的打包工具在这个功能上表现差异非常大。如:browserify 不支持懒加载而无法实现该功能、rollup 不支持、parcel 表现亮眼、而 webpack 不支持如下的特殊解构语法。

const { logCaps } = await import('./utils.js');

但 webpack 允许手动列出通过魔术注释使用的导出:

const { logCaps } = await import(/* webpackExports: "logCaps" */ './utils.js');

不同框架的应对策略

React

如果不是服务器端渲染,可以使用 React.lazy() 拆分 JavaScript 包。否则,使用第三方库(例如:loadable-components)进行代码分割。

比如下面的示例,Loadable 允许开发者将动态导入渲染为常规组件:

import loadable from '@loadable/component';
const OtherComponent = loadable(() => import('./OtherComponent'));
function MyComponent() {
  return (
    <div>
      <OtherComponent />
    </div>
  );
}

Vue

如果不是服务器端渲染并使用 Vue router,可以通过延迟加载路由拆分包。Vue Router 原生支持开箱即用的动态导入,这意味着可以用动态导入替换静态导入:

const UserDetails = () => import('./views/UserDetails.vue')

const router = createRouter({
  // ...
  routes: [
    { path: '/users/:id', component: UserDetails }
    // or use it directly in the route definition
    { path: '/users/:id', component: () => import('./views/UserDetails.vue') },
  ],
})

component(和components)选项接受一个返回组件 Promise 的函数,Vue Router 仅在第一次进入页面时获取,然后使用缓存的版本。 这意味着开发者还可以拥有更复杂的函数,只要它们返回 Promise:

const UserDetails = () =>
  Promise.resolve({
    /* component definition */
  });

一般来说,最好始终对所有路由使用动态导入。当使用像 webpack 这样的打包器时将自动受益于代码分割 使用 Babel 时,需要添加 syntax-dynamic-import 插件,以便 Babel 能够正确解析语法。

转载作品,原作者:,文章来源:https://www.toutiao.com/article/7304467505488151040

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2024年1月23日 14:39
下一篇 2024年1月24日 17:07

相关推荐

  • js通过单击按钮实现全屏、退出全屏

    js通过单击按钮实现全屏、退出全屏

    Html/CSS 2022年4月13日
    01640
  • PDF.js前端开发使用代码示例及实用技巧

    PDF.js前端开发使用代码示例及实用技巧,PDF.js是一个用于在网页中显示PDF文档的JavaScript库。它是由Mozilla开发的,是一个完全免费、开源的工具。在本篇文章中,我们将详细介绍如何使用PDF.js进行前端开发,包括基本的使用方法、代码示例以及一些实用的技巧。

    2024年11月15日
    0430
  • css修改导航条样式

    近期由于工作需要要修改table表格导航条样式。本人特整理出相关代码,以及最后效果,供各位小伙伴参考。 具体代码如下: .xp-table-content ::-webkit-sc…

    Html/CSS 2022年1月17日
    01410
  • 无需公众号实现微信JSSDK分享卡片!Safari浏览器分享到微信自动成卡片!

    要在微信分享卡片,需要接入微信自家的JSSDK,比较麻烦,还需要认证公众号,但是如果你没有这样的条件,那么你也可以试试使用iOS的Safari浏览器轻松实现,只需要在html中加入…

    2023年12月6日
    01250
  • JS的六种打断点的方式,你用过几种?

    JS的六种打断点的方式,Debugger 是前端开发很重要的一个工具,它可以在我们关心的代码处断住,通过单步运行来理清逻辑。而 Debugger 用的好坏与断点打得好坏有直接的关系。

    2021年12月18日 JavaScript
    02760
  • 前端上传大文件怎么处理

    背景 当我们在做文件的导入功能的时候,如果导入的文件过大,可能会导所需要的时间够长,且失败后需要重新上传,我们需要前后端结合的方式解决这个问题 思路 我们需要做几件事情如下: 对文…

    JavaScript 2022年1月19日
    01400

发表回复

登录后才能评论
分享本页
返回顶部