作品:https://code-art.pictures/
现在可能会令人惊讶,但在许多情况下互联网流量仍然是一个问题。移动网络的数据套餐通常有限,设备电池也不是无限的,最重要的是,用户在等待网站加载时的注意力也是有限的。这就是为什么捆绑包大小仍然很重要。这里有七个建议供您考虑。
2020 年,我正在为本地社交网络维护一个促销应用程序。这是一个典型的针对 ES5 的 React TypeScript Webpack 应用程序。当 webpack 5 发布时,我决定升级。一切都很顺利;我监控了错误分析和用户反馈,没有什么意外的。一周后,我意外地发现我的包中包含了箭头函数——这是一个新的 webpack 功能。
这是一篇关于 ES5 状态的精彩文章。要点:
这里有一些功能,可以让您编写更好、更紧凑的代码。
生成器是遍历嵌套结构的有效方法:
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) }
压缩器确信这些字段不能有外部用途,即使在导出的对象中也是如此,并且可以自由缩短它们的名称。
来源
export class A { #myFancyStateObject }
捆绑包
export class A{#t}
当然,对于 TypeScript 私有字段来说,这不起作用,因为一旦 tsc 完成其工作,它们是私有的这一知识就会消失。
你听说过 Promise.withResolvers() 或 Map.groupBy() 吗?在撰写本文时,这些 API 尚未广泛使用,但很快就会广泛使用。现在花点时间熟悉它们,并准备好在几年后采用它们。
有无数的博客和播客,但我发现最好的“新闻通讯”是 TypeScript 存储库中的新 .d.ts 文件。例如,只需打开 es2024.collection.d.ts 即可享受?
你注意到重复的模式了吗?
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 可能是一个整数,目前尚未检查。它还突出显示了一个未处理的边缘情况:空数组。通过进行这个小的重复数据删除,我们还发现了两个潜在的错误?
我不记得这句话的确切来源,但我认为它是正确的:
过度设计正在解决你没有的问题。
在 Web 开发领域,我观察到两种主要类型的过度设计。
考虑这段代码。内边距是 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 变量,至少用于填充,而不是创建一个新组件。
是的,我说的是我们喜欢的东西——Next.js、React、Vue 等等。如果您的应用程序在 DOM 元素级别不涉及大量交互性,或者不是动态的,或者非常简单,请考虑其他选项:
TypeScript 当前的目标主要是对 JavaScript 进行类型检查,但情况并非总是如此。早在 ES6 出现之前,人们就曾多次尝试创建“更好的 JavaScript”,TypeScript 也不例外。有些功能可以追溯到早期。
它们不仅难以正确使用,而且还会转换成相当冗长的结构:
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 手册建议使用简单对象而不是枚举。您也可以考虑联合类型。
命名空间是 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 模块代替命名空间。
注意:但是,对于为全局库编写类型定义,命名空间仍然有用。
这些小技巧中的每一个都可以为您节省捆绑中的几个到几十个字节。如果坚持使用,可以带来明显的效果。
例如,空字符串是假的。要检查它是否已定义且非空,您可以简单地编写:
const clampToRange = (n, {length}) => clamp(0, n, length - 1) const x = clampToRange(v1, a) // ...
我相信使用 == 强制 null 为 undefined,反之亦然,是完全合理的。
.btn-a { background-color: skyblue; padding: 4px; } .btn-b { background-color: deepskyblue; padding: 8px; }
<button className='btn-a' onClick={handleClick}> Show </button> // ... <button className='btn-b' onClick={handleSubmit}> Submit </button>
而不是这个:
enum A { x, y }
写下:
var A; (function (A) { A[A["x"] = 0] = "x"; A[A["y"] = 1] = "y"; })(A || (A = {}));
而不是这个:
namespace A { export let x = 1 }
写下:
var A; (function (A) { A.x = 1; })(A || (A = {}));
您还可以冻结对象以保护其属性免遭更改。
对于每个捆绑器,都有可视化其内容的工具,例如用于 webpack 的 webpack-bundle-analyzer 和用于 Vite 的 vite-bundle-analyzer。这些工具可以帮助您识别捆绑包的常见问题:
除了这些工具之外,偶尔手动阅读捆绑包以发现违规行为也是一个好主意。例如,由于 TypeScript 配置错误,您可能会在 ES6 捆绑包中找到 ES5 帮助程序,或在 ESM 项目中找到 CJS 帮助程序。这些问题可能无法被自动化工具发现,但仍然会增加加载时间,并可能会损失您最宝贵的资产 - 用户的注意力。
感谢您的阅读。快乐编码!
以上是最小化 JavaScript 包大小的实用技巧的详细内容。更多信息请关注PHP中文网其他相关文章!