编程 用 Baseline 减少 JavaScript 依赖:审计 package.json 中浏览器已原生支持的库

2026-09-06 19:16:13

用 Baseline 减少 JavaScript 依赖:审计 package.json 中浏览器已原生支持的库

Smashing Magazine 发表技术文章,由独立 JavaScript 与 Web 性能顾问 Jad Joubran 撰写,详细介绍了如何通过审计项目依赖,发现并移除那些浏览器已经原生支持的 JavaScript 库,从而减少打包体积、提升性能。文章指出,"你需要一个库来做这个"和"浏览器已经能做这个"之间的差距正在不断缩小。在一个典型的中等规模 JavaScript 应用中,通常可以找到 60KB 到 90KB(minified + gzipped)的依赖是浏览器平台现在已经可以自行处理的。日期和数字格式化、HTTP 请求、模态框、工具提示、深拷贝、数组合组——这些在几年前都是真正的功能缺口,现在很多已经不是了。

为什么依赖会累积

安装后不再审视

大多数开发者的经历:

  • 安装一个依赖来解决某个问题
  • 它能工作,测试通过
  • 然后继续做其他事情
  • 再也不回头看这个依赖

但 Web 平台在不断发展:

  • 浏览器每年发布多个新版本
  • 新的 Web API 不断标准化
  • 曾经需要库的功能现在可能已经原生支持
  • 库的维护状态可能已经变化

缺乏定期审计

大多数团队:

  • 会运行 npm audit 检查安全漏洞
  • 但很少问"这个库现在还在做浏览器做不到的事情吗?"
  • 没有定期的依赖审计流程
  • 不知道浏览器平台发展有多快

结果就是:库留在了 package.json 中,即使它们已经不再必要。

可被原生替代的常见依赖类别

1. 日期和时间格式化

常见库:moment.js、date-fns、dayjs

原生替代Intl.DateTimeFormat

// 以前需要 moment.js
const moment = require('moment');
moment(date).format('YYYY-MM-DD');

// 现在可以用 Intl.DateTimeFormat
new Intl.DateTimeFormat('zh-CN', {
  year: 'numeric',
  month: '2-digit',
  day: '2-digit'
}).format(date);

Intl.DateTimeFormat 支持:

  • 多种语言和地区
  • 自定义日期和时间格式
  • 相对时间格式化(Intl.RelativeTimeFormat
  • 性能优于大多数库

注意:如果需要复杂的日期运算(如加减、比较、时区处理),date-fns 或 dayjs 仍然有价值。但如果只是格式化,原生 API 足够。

2. 数字格式化

常见库:numeral.js、accounting.js

原生替代Intl.NumberFormat

// 货币格式化
new Intl.NumberFormat('zh-CN', {
  style: 'currency',
  currency: 'CNY'
}).format(1234.56); // ¥1,234.56

// 百分比
new Intl.NumberFormat('en-US', {
  style: 'percent',
  minimumFractionDigits: 1
}).format(0.123); // 12.3%

// 大数字缩写
new Intl.NumberFormat('en', {
  notation: 'compact',
  compactDisplay: 'short'
}).format(1234567); // 1.2M

3. HTTP 请求

常见库:axios、request、superagent

原生替代fetch API(现在所有现代浏览器和 Node.js 18+ 都支持)

// GET 请求
const response = await fetch('/api/users');
const data = await response.json();

// POST 请求
const response = await fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'John' })
});

fetch 的优势

  • 原生支持,无需额外依赖
  • 基于 Promise,支持 async/await
  • 支持流式处理(Streams API)
  • 支持 AbortController 取消请求

axios 仍然有价值的场景

  • 需要请求/响应拦截器
  • 需要自动 JSON 转换
  • 需要超时处理
  • 需要上传进度
  • 需要广泛的旧浏览器支持

但对于简单的 HTTP 请求,fetch 已经足够。

4. 深拷贝

常见库:lodash.cloneDeep、clone

原生替代structuredClone

// 以前需要 lodash
const clone = _.cloneDeep(original);

// 现在可以用 structuredClone
const clone = structuredClone(original);

structuredClone 支持:

  • 嵌套对象和数组
  • Date、RegExp、Map、Set
  • ArrayBuffer、TypedArray
  • 循环引用

不支持

  • Function
  • DOM 节点
  • 属性描述符(getter/setter)
  • 原型链(会丢失原型)

对于大多数数据对象的深拷贝,structuredClone 是完美的原生替代。

5. 数组合组和操作

常见库:lodash.groupBy、lodash.uniq、lodash.orderBy

原生替代Object.groupByMap.groupBy、Set、sort

// 分组(ES2024)
const grouped = Object.groupBy(users, user => user.role);

// 去重
const unique = [...new Set(items)];

// 排序
const sorted = [...items].sort((a, b) => a.age - b.age);

6. 模态框和对话框

常见库:各种 Modal 组件库

原生替代<dialog> 元素

<dialog id="myDialog">
  <h2>对话框标题</h2>
  <p>对话框内容</p>
  <button onclick="document.getElementById('myDialog').close()">关闭</button>
</dialog>

<button onclick="document.getElementById('myDialog').showModal()">打开</button>

<dialog> 元素的优势:

  • 原生支持,无需 JavaScript 库
  • 内置焦点管理
  • 内置模态行为(点击遮罩关闭、ESC 关闭)
  • 无障碍支持
  • 可以用 CSS ::backdrop 自定义遮罩

7. 工具提示(Tooltip)

常见库:tippy.js、tooltip.js

原生替代:CSS :hover + title 属性,或 Popover API

<!-- 简单的 title 属性 -->
<button title="这是提示信息">按钮</button>

<!-- Popover API(更现代) -->
<button popovertarget="myPopover">打开提示</button>
<div id="myPopover" popover>提示内容</div>

8. URL 解析和操作

常见库:query-string、url-parse

原生替代URLURLSearchParams

// 解析 URL
const url = new URL('https://example.com/path?query=value');
console.log(url.pathname); // /path
console.log(url.searchParams.get('query')); // value

// 操作查询参数
const params = new URLSearchParams();
params.append('key', 'value');
console.log(params.toString()); // key=value

9. 防抖和节流

常见库:lodash.debounce、lodash.throttle

原生替代:自己实现(代码很短)

// 防抖
function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

// 节流
function throttle(fn, limit) {
  let inThrottle;
  return (...args) => {
    if (!inThrottle) {
      fn(...args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

这两个函数总共不到 20 行代码,不需要引入整个 lodash。

10. 唯一 ID 生成

常见库:uuid、nanoid

原生替代crypto.randomUUID()

// 生成 UUID
const id = crypto.randomUUID();
console.log(id); // "550e8400-e29b-41d4-a716-446655440000"

crypto.randomUUID() 在所有现代浏览器和 Node.js 19+ 中都可用。

依赖审计的方法

1. 按类别审计

不要一个一个地检查依赖,而是按类别分组审计:

  • 日期和时间处理
  • HTTP 请求
  • 工具函数(数组、对象操作)
  • UI 组件(模态框、提示、下拉菜单)
  • 加密和安全
  • 开发工具(构建、测试、lint)

因为可替代的依赖往往成组出现。

2. 分析打包体积

使用工具分析打包体积:

  • webpack-bundle-analyzer
  • source-map-explorer
  • rollup-plugin-visualizer
  • Vite 的 rollup-plugin-visualizer

找出占用体积最大的依赖,评估是否可以替代。

3. 检查浏览器兼容性

在决定移除库之前,检查原生 API 的浏览器兼容性:

  • Can I Use(caniuse.com)
  • MDN Web Docs
  • 项目的目标浏览器列表

如果需要支持旧浏览器,可以考虑:

  • 使用 polyfill(通常比完整库小)
  • 渐进增强(新浏览器用原生,旧浏览器用库)
  • 调整目标浏览器列表

4. 渐进式迁移

不要一次性移除所有可替代的依赖:

  • 选择一个类别开始
  • 替换代码并测试
  • 确认没有回归问题
  • 移除依赖
  • 继续下一个类别

5. 建立持续审计机制

  • 将依赖审计加入代码审查流程
  • 定期(如每季度)运行依赖审计
  • 使用工具自动检测可替代的依赖
  • 在 CI 中检查打包体积变化

性能收益估算

文章指出,在典型的中等规模 JavaScript 应用中:

  • 可以找到 60KB 到 90KB(minified + gzipped)的可替代依赖
  • 这相当于减少 20-30% 的 JavaScript 体积
  • 可以带来明显的性能提升:
    • 更快的页面加载
    • 更少的网络传输
    • 更少的 JavaScript 解析和编译时间
    • 更低的内存使用
    • 更好的用户体验

对于移动端和低速网络用户,这些收益更加明显。

注意事项

不要盲目移除

在移除库之前,确保:

  • 原生 API 确实覆盖了库的所有功能
  • 项目的目标浏览器支持原生 API
  • 有足够的测试覆盖
  • 团队成员熟悉原生 API

有些库仍然有价值

以下场景库仍然有价值:

  • 复杂的日期运算(不只是格式化)
  • 高级 HTTP 功能(拦截器、重试、缓存)
  • 复杂的 UI 组件(数据表格、富文本编辑器)
  • 跨浏览器兼容性要求高
  • 团队不熟悉原生 API
  • 库提供了原生 API 没有的高级功能

考虑维护成本

移除库也有成本:

  • 需要编写和维护自己的实现
  • 团队需要学习原生 API
  • 可能需要处理边缘情况
  • 未来可能需要重新引入库

但对于简单、标准化的功能,原生 API 的维护成本通常低于第三方库。

总结

Web 平台在快速发展,许多曾经需要第三方库的功能现在已经被浏览器原生支持。通过定期审计项目依赖,可以发现并移除这些不再必要的库,减少打包体积、提升性能。常见的可替代类别包括日期格式化(Intl.DateTimeFormat)、数字格式化(Intl.NumberFormat)、HTTP 请求(fetch)、深拷贝(structuredClone)、数组合组(Object.groupBy)、模态框(dialog 元素)、URL 解析(URL API)、防抖节流(自行实现)、唯一 ID(crypto.randomUUID)等。在典型的中等规模应用中,可以减少 60-90KB(gzipped)的依赖,带来明显的性能提升。但不要盲目移除,需要评估原生 API 的功能覆盖、浏览器兼容性和维护成本。建立持续的依赖审计机制,定期审视项目中的依赖,是保持应用轻量和高效的重要实践。随着 Web 平台的持续发展,"你需要一个库来做这个"和"浏览器已经能做这个"之间的差距将继续缩小,开发者应该保持对平台新能力的关注,适时优化项目依赖。

来源:https://www.smashingmagazine.com/2026/08/how-baseline-can-help-you-ship-less-javascript/

推荐文章

程序员茄子在线接单