如何用 Expo Atlas 分析生产 JavaScript Bundle 并通过 Tree Shaking 减小体积
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
如果你的 Expo 应用 Web 端首屏慢、原生端 OTA 下载包偏大,下一步该做的不是猜,而是先看清楚生产 Bundle 里到底装了什么。Expo 提供 Expo Atlas 来可视化生产 JavaScript Bundle,定位哪些库贡献了体积;再配合 Expo CLI 内建的 tree shaking 与代码消除能力,把未使用的模块从 Bundle 中移除。本文覆盖从“分析出谁占了体积”到“用 tree shaking 减掉它”再到“再次分析确认效果”的完整路径。
前提说明:Bundle 体积对 Web 平台尤其关键,因为浏览器不支持预编译字节码(precompiled bytecode),Bundle 越小,下载和解析越快。版本方面,EXPO_ATLAS环境变量从 SDK 53 起可用(SDK 51/52 对应的EXPO_UNSTABLE_ATLAS自 SDK 53 起被弃用);跨模块的自动 tree shaking 为实验性特性,SDK 52 起可用。细节可分别查阅 Analyzing JavaScript bundles 与 Tree shaking and code removal。
用 Expo Atlas 分析生产 Bundle
分析有两条路径:本地开发服务器(适合随改随看)和expo export(适合拿到可分享的分析文件)。
路径一:通过本地开发服务器打开 Atlas
启动本地开发服务器时设置EXPO_ATLAS=true,Atlas 会跟随你的代码改动实时更新:
# 启动带 Atlas 的本地开发服务器 EXPO_ATLAS=true npx expo start应用通过本地开发服务器跑在 Android、iOS 和/或 Web 上之后,在终端按Shift+M打开 dev tools plugin 菜单,从中选择 Expo Atlas,它会在新的 Chrome 窗口打开。
默认情况下npx expo start运行在开发模式,该模式会关闭部分生产模式才启用的优化,测出来的体积会偏大。要得到更接近真实生产 Bundle 的数据,改用生产模式启动:
# 以生产模式运行本地开发服务器,体积数据更接近真实生产 Bundle EXPO_ATLAS=true npx expo start --no-dev路径二:通过expo export生成可分享的分析文件
生成生产 Bundle 时带上EXPO_ATLAS=true,export 过程会生成.expo/atlas.jsonl文件:
# 导出应用(默认所有平台) EXPO_ATLAS=true npx expo export # 打开生成的 Expo Atlas 文件 npx expo-atlas .expo/atlas.jsonl用 yarn / pnpm / bun 时对应为yarn dlx expo-atlas .expo/atlas.jsonl、pnpm dlx expo-atlas .expo/atlas.jsonl、bunx expo-atlas .expo/atlas.jsonl。
这个文件的价值在于不需要访问项目源码就能打开和分享,适合团队协作定位大体积模块。它包含每个打包模块的原始与转换后源码,以及内联进去的EXPO_PUBLIC_环境变量值——按对待源码的方式对待它,只分享给信任的人。
只关心特定平台时,用--platform选项限定导出平台,Atlas 也只收集对应平台的数据。
在 Atlas 里定位具体模块
在 Atlas 的依赖图上,按住⌘ Cmd点击某个图节点,可以看到该模块的转换详情:它经过 Babel 后变成了什么样、它 import 了哪些模块、哪些模块 import 了它。借助这个信息可以沿依赖图追溯某个模块的来源——例如确认某个图标库为什么整包进来了。
用 Tree Shaking 与代码消除减小体积
Expo CLI 在生产导出(npx expo export、eas build等)时执行多种优化。以下几类代码消除是内建行为,不需要额外开关:
平台消除(platform shaking):按平台分别打 Android、iOS、Web 的 Bundle,仅在某平台使用的条件代码会被从其他平台的 Bundle 中移除。但有两个限制:仅在生产 Bundle 生效;Platform.OS/Platform.select必须在每个文件里直接从react-nativeimport 才会被消除,通过其他模块 re-export 的不会被移除。
开发专用代码消除:用process.env.NODE_ENV或__DEV__包起来的代码,经常量折叠(constants folding)后可被静态求值,再由压缩器(minification)删掉不可达分支。例如if (__DEV__) { ... }在生产 Bundle 中会变为空。开发模式下这些条件保留,不做消除。
EXPO_PUBLIC_环境变量内联:这类变量在压缩前被内联,因此可以写成开关来裁掉整段功能代码。注意:该机制不适用于 server 代码(server Bundle 不内联环境变量),库作者也不应使用它(出于安全考虑只作用于应用代码)。
server 代码消除:babel-preset-expo在为 server 环境(API routes、SSR)打包时会把typeof window === 'undefined'转换为true;为 Web 客户端打包时默认不转换(Web worker 没有window全局),如需要可在babel-preset-expo配置中传入{ minifyTypeofWindow: true }启用。
react-native-web桶文件优化:对 Web 端,用静态 ESMimport语法从react-native导入时,react-native-web的 barrel 文件会从生产 Bundle 中移除并按导出项拆分;用require()导入则保留原样。
启用实验性跨模块 tree shaking(SDK 52+)
前面几项处理的是单文件内的条件代码,而自动移除跨模块的未使用 import/export 需要显式开启,步骤如下:
第 1 步:确认 import 支持已启用。确保experimentalImportSupport已开启且应用能正常构建运行。SDK 54 起该项默认开启;更老的 SDK 版本需要在metro.config.js中手动配置:
// metro.config.js const { getDefaultConfig } = require('expo/metro-config'); const config = getDefaultConfig(__dirname); config.transformer.getTransformOptions = async () => ({ transform: { experimentalImportSupport: true, }, }); module.exports = config;第 2 步:设置环境变量EXPO_UNSTABLE_METRO_OPTIMIZE_GRAPH=1,让 Metro 保留模块直到整个依赖图创建完成。该变量仅在生产模式生效。开启后先确认应用在生产模式下能正常构建和运行,再继续:
# .env EXPO_UNSTABLE_METRO_OPTIMIZE_GRAPH=1第 3 步:设置环境变量EXPO_UNSTABLE_TREE_SHAKING=1,正式启用 tree shaking。同样仅在生产模式生效:
# .env EXPO_UNSTABLE_METRO_OPTIMIZE_GRAPH=1 EXPO_UNSTABLE_TREE_SHAKING=1第 4 步:以生产模式打包,查看 tree shaking 效果:
npx expo export效果示例(来自文档):index.js只 import 了icons.js里的ArrowUp,则ArrowDown、ArrowRight、ArrowLeft都会被从生产 Bundle 中移除。对lucide-react这类图标库,配合 star export 展开,可以把未使用的图标全部摇掉。
验证优化效果
验证方式回到 Atlas:
- 开启 tree shaking 前后各执行一次
EXPO_ATLAS=true npx expo export,分别用npx expo-atlas .expo/atlas.jsonl打开,对比模块图的整体规模与具体库的占比。 - 针对 barrel 文件:如果
export * from "./icons"展开后仍带着未使用的导出,或 star export 拉入了module.exports.ArrowUp、exports.ArrowDown这类含糊导出(此时优化 pass 不会展开,桶文件里不会移除任何导出),在 Atlas 中检查展开后的导出就能确认问题出在哪个模块。 - 文档同时要求:开启
EXPO_UNSTABLE_METRO_OPTIMIZE_GRAPH后,先在生产模式确认应用构建和运行正常再往下走,这是每一步之间的判断点。
对 Web 端还可以补充 Lighthouse 验证:npx expo export -p web生成生产构建后用npx serve dist(或正式部署)提供访问,再运行npx lighthouse <url> --view(<url>替换为你站点实际地址)查看性能表现。这一步属于可选分支,原生端不适用。
限制与已知边界
- 该特性整体处于实验状态,因为它改变了 Metro 打包的基本结构:tree shaking 要求部分转换延迟到整个 Bundle 创建之后,可缓存的代码变少(生产 Bundle 通常不用转换缓存,一般无碍)。
- tree shaking 只在生产 Bundle 运行,且只处理使用
import/export语法的模块;module.exports和require的文件不会被 shake。 - 避免添加
@babel/plugin-transform-modules-commonjs这类把 ESM 转成 CJS 的 Babel 插件,它会破坏全项目的 tree shaking。 - 标记为有副作用的模块不会被移除。在package.json中用
sideEffects字段声明副作用文件(如"sideEffects": ["./src/*.js"]);side-effects 还会禁用模块内联,以保证执行顺序。 - 每个模块的递归优化最多进行 5 轮,之后因性能原因中止。
export * from "..."会被展开并优化,除非导出使用了module.exports或exports。- 开启 Expo tree shaking 后,可以安全地在metro.config.js中启用
inlineRequires以延迟加载模块、加快启动;没有 tree shaking 时不要使用它,因为它会移动模块位置、可能改变 side-effects 执行顺序。 - 所有 Expo SDK 模块都以 ESM 发布,可被完整 tree shake;处理 JS 本身消耗时间和内存,避免一次 import 数百万个模块。
完成上述操作后,Atlas 依赖图上未使用模块的消失情况就是本次优化的直接结果;若某个 barrel 文件的导出没有被移除,按“限制”一节检查它是否混入了 CJS 导出或副作用声明。
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考