首页 > web前端 > js教程 > 最小化 JavaScript 包大小的实用技巧

最小化 JavaScript 包大小的实用技巧

Patricia Arquette
发布: 2024-12-31 01:39:12
原创
463 人浏览过

ractical Tips to Minimize Your JavaScript Bundle Size

作品:https://code-art.pictures/

为什么要麻烦?

现在可能会令人惊讶,但在许多情况下互联网流量仍然是一个问题。移动网络的数据套餐通常有限,设备电池也不是无限的,最重要的是,用户在等待网站加载时的注意力也是有限的。这就是为什么捆绑包大小仍然很重要。这里有七个建议供您考虑。

1. 不要转换为 ES5

2020 年,我正在为本地社交网络维护一个促销应用程序。这是一个典型的针对 ES5 的 React TypeScript Webpack 应用程序。当 webpack 5 发布时,我决定升级。一切都很顺利;我监控了错误分析和用户反馈,没有什么意外的。一周后,我意外地发现我的包中包含了箭头函数——这是一个新的 webpack 功能。

这是一篇关于 ES5 状态的精彩文章。要点:

  • 许多库已经包含 ES6 代码,这意味着它们的捆绑包不兼容 ES5。
  • 世界上大多数流行的网站都不兼容 ES5 — 您的网站可能也不需要它。
  • 如果您确定仍然需要 ES5 兼容性,则必须在构建过程中包含这些库。

2.了解并使用现代 JavaScript 语言特性

这里有一些功能,可以让您编写更好、更紧凑的代码。

2.1.发电机

生成器是遍历嵌套结构的有效方法:

type TreeNode<T> = {
    left?: TreeNode<T>
    value: T
    right?: TreeNode<T>
};

function* traverse<T>(root: TreeNode<T>): Generator<T> {
    if (root.left) yield* traverse(root.left)
    yield root.value
    if (root.right) yield* traverse(root.right)
}
登录后复制
登录后复制
登录后复制

2.2.私有类字段

压缩器确信这些字段不能有外部用途,即使在导出的对象中也是如此,并且可以自由缩短它们的名称。

来源

export class A {
  #myFancyStateObject
}
登录后复制
登录后复制
登录后复制

捆绑包

export class A{#t}
登录后复制
登录后复制
登录后复制

当然,对于 TypeScript 私有字段来说,这不起作用,因为一旦 tsc 完成其工作,它们是私有的这一知识就会消失。

2.3.现代 API

你听说过 Promise.withResolvers() 或 Map.groupBy() 吗?在撰写本文时,这些 API 尚未广泛使用,但很快就会广泛使用。现在花点时间熟悉它们,并准备好在几年后采用它们。

提示:如何发现新的 JavaScript API

有无数的博客和播客,但我发现最好的“新闻通讯”是 TypeScript 存储库中的新 .d.ts 文件。例如,只需打开 es2024.collection.d.ts 即可享受?

3. 避免代码重复

你注意到重复的模式了吗?

type TreeNode<T> = {
    left?: TreeNode<T>
    value: T
    right?: TreeNode<T>
};

function* traverse<T>(root: TreeNode<T>): Generator<T> {
    if (root.left) yield* traverse(root.left)
    yield root.value
    if (root.right) yield* traverse(root.right)
}
登录后复制
登录后复制
登录后复制

重复的代码不仅增加了包的大小,而且还使理解每个部分的作用变得更加困难。这通常会导致开发人员编写新代码,而不是识别和重用现有的实用函数,从而进一步使捆绑包变得臃肿。

关于这个主题已经有很多优秀的材料,所以我不再重述它,而是推荐经典:Martin Fowler 的重构。它不仅涵盖了上面的简单示例,还涵盖了耦合层次结构和重复设计等复杂情况。

现在,让我们改进我们的小例子。看来clamp经常用于将参数限制在数组索引范围内,所以我们可以创建一个快捷方式:

export class A {
  #myFancyStateObject
}
登录后复制
登录后复制
登录后复制

此更改明确表明 n 可能是一个整数,目前尚未检查。它还突出显示了一个未处理的边缘情况:空数组。通过进行这个小的重复数据删除,我们还发现了两个潜在的错误?

4.避免过度设计

我不记得这句话的确切来源,但我认为它是正确的:

过度设计正在解决你没有的问题。

在 Web 开发领域,我观察到两种主要类型的过度设计。

4.1.过度概括

考虑这段代码。内边距是 4px 的倍数,背景颜色是蓝色阴影。这可能不是巧合,如果是这样,则可能表明可能存在重复。但是我们真的有足够的信息来提取通用 Button 组件,还是我们过度设计了?

CSS

export class A{#t}
登录后复制
登录后复制
登录后复制

JSX

const clamp = (min, val, max) =>
  Math.max(min, Math.min(val, max))
const x = clamp(0, v1, a.length - 1)
const y = clamp(0, v2, b.length - 1)
const z = clamp(0, v3, c.length - 1)
登录后复制
登录后复制

这个建议确实与“避免重复”有些冲突。过度重复代码删除可能会导致过度设计。那么,你在哪里划清界限呢?就我个人而言,我使用神奇的数字“3”:一旦我看到三个具有相似模式的地方,可能是时候提取通用组件了。

对于我们的蓝色按钮,我相信最好的解决方案是使用 CSS 变量,至少用于填充,而不是创建一个新组件。

4.2.使用不正确的框架

是的,我说的是我们喜欢的东西——Next.js、React、Vue 等等。如果您的应用程序在 DOM 元素级别不涉及大量交互性,或者不是动态的,或者非常简单,请考虑其他选项:

  • 静态站点生成器 - 您可以从一些精选列表开始。
    • 注意:其中一些在幕后使用 React 或其他框架。如果您的目标是捆绑最小化,请尝试不同的方法。
  • 内容管理系统,例如 WordPress。
  • Vanilla — 在两种情况下特别有用:
    • 该应用程序非常简单。
    • 该应用程序不会操作太多 DOM,而是在画布上绘制一些东西。我有一个与此完全相同的项目。

5. 避免过时的 TypeScript 功能

TypeScript 当前的目标主要是对 JavaScript 进行类型检查,但情况并非总是如此。早在 ES6 出现之前,人们就曾多次尝试创建“更好的 JavaScript”,TypeScript 也不例外。有些功能可以追溯到早期。

5.1.枚举

它们不仅难以正确使用,而且还会转换成相当冗长的结构:

TypeScript

type TreeNode<T> = {
    left?: TreeNode<T>
    value: T
    right?: TreeNode<T>
};

function* traverse<T>(root: TreeNode<T>): Generator<T> {
    if (root.left) yield* traverse(root.left)
    yield root.value
    if (root.right) yield* traverse(root.right)
}
登录后复制
登录后复制
登录后复制

JavaScript

export class A {
  #myFancyStateObject
}
登录后复制
登录后复制
登录后复制

官方 TypeScript 手册建议使用简单对象而不是枚举。您也可以考虑联合类型。

5.2.命名空间

命名空间是 ESM 之前的模块解决方案。它们不仅增加了包的大小,而且由于命名空间是全局的,因此在大型项目中很难避免命名冲突。

TypeScript

export class A{#t}
登录后复制
登录后复制
登录后复制

JavaScript

const clamp = (min, val, max) =>
  Math.max(min, Math.min(val, max))
const x = clamp(0, v1, a.length - 1)
const y = clamp(0, v2, b.length - 1)
const z = clamp(0, v3, c.length - 1)
登录后复制
登录后复制

使用 ES 模块代替命名空间。

注意:但是,对于为全局库编写类型定义,命名空间仍然有用。

6. 不要忽视小的优化

这些小技巧中的每一个都可以为您节省捆绑中的几个到几十个字节。如果坚持使用,可以带来明显的效果。

6.1.使用真/假属性

例如,空字符串是假的。要检查它是否已定义且非空,您可以简单地编写:

const clampToRange = (n, {length}) =>
  clamp(0, n, length - 1)
const x = clampToRange(v1, a)
// ...
登录后复制

6.2.有时允许非严格比较

我相信使用 == 强制 null 为 undefined,反之亦然,是完全合理的。

.btn-a {
    background-color: skyblue;
    padding: 4px;
}
.btn-b {
    background-color: deepskyblue;
    padding: 8px;
}
登录后复制

6.3.使用空合并、逻辑或和默认参数来替换默认值

<button className='btn-a' onClick={handleClick}>
    Show
</button>
// ...
<button className='btn-b' onClick={handleSubmit}>
    Submit
</button>
登录后复制

6.4.使用单行箭头函数

而不是这个:

enum A {
  x, y
}
登录后复制

写下:

var A;
(function (A) {
    A[A["x"] = 0] = "x";
    A[A["y"] = 1] = "y";
})(A || (A = {}));
登录后复制

6.5.不要对非常简单的对象使用类

而不是这个:

namespace A {
  export let x = 1
}
登录后复制

写下:

var A;
(function (A) {
    A.x = 1;
})(A || (A = {}));
登录后复制

您还可以冻结对象以保护其属性免遭更改。

7.定期检查捆绑包

对于每个捆绑器,都有可视化其内容的工具,例如用于 webpack 的 webpack-bundle-analyzer 和用于 Vite 的 vite-bundle-analyzer。这些工具可以帮助您识别捆绑包的常见问题:

  • 库占用了不成比例的空间 — 也许是时候迁移或升级了?
  • 项目的不同部分使用了两个相似的库 - 您可以合并并仅使用一个吗?
  • 您的捆绑包中存在一个大文件,但只能由 0.5% 用户访问的页面访问(例如许可证文本)——也许您可以使用动态 import() 对捆绑包进行分区?

除了这些工具之外,偶尔手动阅读捆绑包以发现违规行为也是一个好主意。例如,由于 TypeScript 配置错误,您可能会在 ES6 捆绑包中找到 ES5 帮助程序,或在 ESM 项目中找到 CJS 帮助程序。这些问题可能无法被自动化工具发现,但仍然会增加加载时间,并可能会损失您最宝贵的资产 - 用户的注意力。


感谢您的阅读。快乐编码!

以上是最小化 JavaScript 包大小的实用技巧的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板