この記事は 207 Advent Calendar 2022 9日目の記事です。
207株式会社でソフトウェアエンジニアをしている id:ryo-rm です。 207で提供している、React Nativeを使ったアプリケーション "TODOCUサポーター" のJavaScriptバンドルサイズを減らす取り組みについて紹介いたします。
React NativeのJavaScript バンドルについて
React Nativeはネイティブコード(Android/iOS)+JavaScriptコードで動作しています。
Webの場合、webpackやParcelなどのバンドラーを利用しますが、React Nativeでは Metro Bundler がJavaScriptのバンドル処理を行います。
しかし、React Nativeで利用するJavaScriptバンドラーであるMetroは、現在Tree Shakingが実装されていません。
そのため、 import lodash from "lodash"
などを書いた場合、ライブラリがまるごとバンドルされるので、最終的なバンドルサイズは巨大化します。
ネイティブアプリはWebとは事情が異なるためバンドルサイズの大きさを気にする必要性は薄いですが、Expoが提供している OTA アップデートの機能を使う場合は変わってきます。
JavaScriptコードをバンドルしてアプリケーションを配布・更新する*1ため、コードのサイズが小さいほどアプリの更新時に必要なデータ転送量が減少し、ユーザーにとっても利益があります。
弊社のアプリケーション "TODOCUサポーター" でもOTAアップデートを活用しており、ネイティブアプリでも動的に機能の変更ができるようになっています。
分析と削除
まずは現状を把握するために、 react-native-bundle-visualizer
を使ってバンドルするJavaScriptの内容を確認します。
このツールを使ってライブラリの大きさを把握します。
プロダクションのコードを載せるわけにはいかないので、今回はブログ用にReact Nativeプロジェクトを新規セットアップし、"TODOCUサポーター" の直近のリリースで追加したライブラリ newrelic-react-native-agent
をインストールしたものを分析していきます。
上記のスクリーンショットから lodash
が 71KB あるのがわかります。
この lodash
がどこで使われているのかを yarn why lodash
もしくは npm ls lodash
を使って調査します。
yarn why lodash yarn why v1.22.19 [1/4] 🤔 Why do we have the module "lodash"...? [2/4] 🚚 Initialising dependency graph... [3/4] 🔍 Finding dependency... [4/4] 🚡 Calculating file sizes... => Found "lodash@4.17.21" info Reasons this module exists - "newrelic-react-native-agent#commitizen" depends on it - Hoisted from "newrelic-react-native-agent#commitizen#lodash" - Hoisted from "expo#@expo#cli#json-schema-deref-sync#lodash" - Hoisted from "newrelic-react-native-agent#commitizen#inquirer#lodash" - Hoisted from "expo#@expo#cli#@expo#devcert#lodash" info Disk size without dependencies: "4.88MB" info Disk size with unique dependencies: "4.88MB" info Disk size with transitive dependencies: "4.88MB" info Number of shared dependencies: 0 ✨ Done in 0.22s.
上記の結果から、 newrelic-react-native-agent#commitizen
がlodashを使っているのがわかります。
コードを見に行ってみると、 import * as _ from 'lodash';
しています。この書き方をしているため、lodashがまるごとバンドルされているのを特定しました。
また、 commitizen
がDependenciesに含まれています。
https://github.com/newrelic/newrelic-react-native-agent/blob/0.0.8/index.js#L11
コードを読むと lodash.foreach
のみ使われており、foreachだけ使うように変更し、再度 react-native-bundle-visualizer
でテストしてみます。
lodashのforeachではなく args.forEach
でいい気がしますが、ライブラリの動作を完全には理解できておらず動作確認も完全にはできていないので、一旦 lodash.foreach
に変更したものを newrelic にPRを作成しました。
lodash.foreach
を使うように変更することで、およそ 70KB 削減できています。
応用編 ・Tree Shakingを使う
React Native Developer Tools (Microsoft) から提供されている、metro-serializer-esbuild
を使うことでTree Shakingが実現できます。
esbuildがバンドル処理を引き継ぐことで、 Tree Shaking が動くようになります!
babelの設定と、 metroの設定に customSerializer
と transformer
を追加するだけで動作します。
expoを使っているプロジェクトで metro.config.js
を変更する場合、下記のような設定になります。
const { getDefaultConfig } = require("expo/metro-config"); const { MetroSerializer, esbuildTransformerConfig, } = require("@rnx-kit/metro-serializer-esbuild"); const config = getDefaultConfig(__dirname); module.exports = { ...config, serializer: { ...config.serializer, customSerializer: MetroSerializer(), }, transformer: esbuildTransformerConfig, };
metro-serializer-esbuild
はβ版ですがかなり良好な結果が得られており、テスト用に作ったプロジェクトでは最終的に、 936KB → 866KB → 732KB までバンドルサイズをへらすことができました。
テストに使ったプロジェクトは以下のリポジトリで公開しています。
P.S
この記事は、 207 Advent Calendar 2022の記事でした。
最後にいつもの宣伝で、
207株式会社では、配達員向け効率化アプリ「TODOCUサポーター」を開発しています。 ネイティブアプリでもWeb開発の知識を活かすことができます!207では開発メンバーを大絶賛募集中です!
もし少しでもご興味がありましたら以下のnotionをご覧ください!
*1:JavaScriptエンジンのHermesを利用している場合、正確にはHermes bytecodeが配布される