corejs从入门到踩坑完全指南

博主世杰 工具 阅读 2,793
赞 12 收藏
二维码
手机扫码查看
反馈

优化前:卡得不行

最近接手了一个老项目,用户反馈页面加载慢得要命,特别是在低端设备上,经常出现卡顿甚至白屏。Chrome DevTools显示JS执行时间高达15秒,bundle体积有3MB多,polyfill文件占了一半。我一看代码,core-js的引入方式简直是灾难现场,全量导入,什么版本兼容都来一份,难怪这么慢。

corejs从入门到踩坑完全指南

用户投诉说点击按钮要等好几秒才有反应,滚动也卡顿得不行。这种体验根本没法上线,必须重构core-js的使用方式。

找到瓶颈了!

用webpack-bundle-analyzer分析了一下,发现core-js占了整个bundle的40%,其中很多polyfill其实根本用不到。比如Array.from、Promise这些现代浏览器原生支持的API,却还在打补丁。而且babel-preset-env的配置也是默认全量导入,完全没有按需加载的概念。

性能监控工具显示,页面启动时大量polyfill的初始化占用了主线程,导致渲染阻塞。特别是移动端设备,CPU性能有限,这些不必要的polyfill更是雪上加霜。

按需导入是关键

第一件事就是改babel配置。以前是这样的:

// .babelrc - 老配置
{
  "presets": [
    ["@babel/preset-env", {
      "useBuiltIns": "entry",
      "corejs": 3
    }]
  ]
}
// main.js - 全量导入core-js
import 'core-js/stable';
import 'regenerator-runtime/runtime';

现在改成按需导入:

// .babelrc - 新配置
{
  "presets": [
    ["@babel/preset-env", {
      "useBuiltIns": "usage",
      "corejs": 3,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }]
  ]
}
// main.js - 删除全量导入
// 删除这两行
// import 'core-js/stable';
// import 'regenerator-runtime/runtime';

这样babel会在编译时自动根据代码中使用的API来导入对应的polyfill,而不是一股脑全导入。比如代码里只用了Promise,就只导入Promise相关的polyfill。

手动控制polyfill

对于一些特殊的API,我还手动控制了polyfill的导入。比如项目中用到了Array.includes(),但只在某些模块需要,我就会在具体文件中导入:

// utils/array-helper.js
import 'core-js/features/array/includes';

export function checkIncludes(arr, value) {
  return arr.includes(value);
}

或者针对特定浏览器的兼容问题,我还会条件导入:

// polyfills.js
if (!window.Promise) {
  import('core-js/features/promise');
}

if (!Array.prototype.includes) {
  import('core-js/features/array/includes');
}

这里需要注意,动态导入的polyfill要在业务代码之前执行完,否则可能会出错。

版本精简策略

原来项目为了兼容性,core-js版本很老,而且同时用了多个版本。清理后统一到core-js@3.21.1,这个版本对ES2021的支持比较完整,也不需要太多的polyfill补丁。

package.json里也做了清理:

{
  "dependencies": {
    "core-js": "^3.21.1",
    "@babel/core": "^7.17.0",
    "@babel/preset-env": "^7.16.11"
  }
}

删除了原来的各种polyfill包,比如es6-promise、whatwg-fetch这些,统一用core-js管理。

构建配置优化

webpack配置也需要配合调整,确保tree-shaking生效:

// webpack.config.js
module.exports = {
  optimization: {
    usedExports: true,
    sideEffects: ['*.css', '*.scss', './src/polyfills.js']
  },
  resolve: {
    alias: {
      'core-js': path.resolve(__dirname, 'node_modules/core-js')
    }
  }
};

sideEffects设置很重要,core-js的polyfill都是有副作用的,不能被摇树优化掉,所以要明确声明。

性能数据对比

优化后的效果还是很明显的:

  • Bundle大小:从3.2MB降到1.1MB(减少65.6%)
  • JS执行时间:从15.2秒降到4.1秒(减少73.0%)
  • 首屏渲染时间:从8.3秒降到2.2秒(减少73.5%)
  • polyfill文件:从1.8MB降到420KB(减少76.7%)

内存占用也有明显改善,原来平均占用180MB,现在只有85MB左右。用户体验提升很大,特别是低端设备上的表现。

当然,也不是所有polyfill都能优化掉,比如Promise、async/await相关的还是必须保留,毕竟现在大部分项目都需要。

踩坑提醒

这里我踩过几个坑,提醒一下:

第一,useBuiltIns: “usage”模式下,如果某个polyfill被动态代码分割导入,可能会导致运行时错误。解决办法是在入口文件预先导入可能用到的polyfill。

第二,某些老版本IE的兼容问题,core-js@3已经不再支持了,如果还需要支持IE11以下,就得降级到core-js@2,但这会增加很多不必要的polyfill。

第三,测试环境一定要覆盖各种目标浏览器,有些polyfill在开发环境下没问题,生产环境下可能出意外。

以上是我的优化经验

这次core-js的优化让我对前端性能有了更深的认识。有时候性能问题不是代码写的不好,而是工具配置不当导致的。按需导入确实是个好习惯,虽然配置起来稍微麻烦点,但收益很明显。

以上是我踩坑后的总结,希望对你有帮助。如果有更优的实现方式欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论