207 Tech Blog

テクノロジーで物流を変える 207 (ニーマルナナ) 株式会社のテックブログ

バンドルサイズ削減!React Nativeでユーザーへ配布するJSコード量を減らす

この記事は 207 Advent Calendar 2022 9日目の記事です。

qiita.com

207株式会社でソフトウェアエンジニアをしている id:ryo-rm です。 207で提供している、React Nativeを使ったアプリケーション "TODOCUサポーター" のJavaScriptバンドルサイズを減らす取り組みについて紹介いたします。

React NativeのJavaScript バンドルについて

React Nativeはネイティブコード(Android/iOS)+JavaScriptコードで動作しています。
Webの場合、webpackやParcelなどのバンドラーを利用しますが、React Nativeでは Metro BundlerJavaScriptのバンドル処理を行います。

しかし、React Nativeで利用するJavaScriptバンドラーであるMetroは、現在Tree Shakingが実装されていません。
そのため、 import lodash from "lodash" などを書いた場合、ライブラリがまるごとバンドルされるので、最終的なバンドルサイズは巨大化します。

ネイティブアプリはWebとは事情が異なるためバンドルサイズの大きさを気にする必要性は薄いですが、Expoが提供している OTA アップデートの機能を使う場合は変わってきます。

JavaScriptコードをバンドルしてアプリケーションを配布・更新する*1ため、コードのサイズが小さいほどアプリの更新時に必要なデータ転送量が減少し、ユーザーにとっても利益があります。

弊社のアプリケーション "TODOCUサポーター" でもOTAアップデートを活用しており、ネイティブアプリでも動的に機能の変更ができるようになっています。

tech.207-inc.com

分析と削除

まずは現状を把握するために、 react-native-bundle-visualizer を使ってバンドルするJavaScriptの内容を確認します。
このツールを使ってライブラリの大きさを把握します。

プロダクションのコードを載せるわけにはいかないので、今回はブログ用にReact Nativeプロジェクトを新規セットアップし、"TODOCUサポーター" の直近のリリースで追加したライブラリ newrelic-react-native-agent をインストールしたものを分析していきます。

www.npmjs.com

react-native-bundle-visualizerの出力。テスト用に作った小さいプロジェクトなので、ほぼReact Native本体が占めています

上記のスクリーンショットから 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に含まれています。

New Relic Agent
https://github.com/newrelic/newrelic-react-native-agent/blob/0.0.8/index.js#L11

コードを読むと lodash.foreach のみ使われており、foreachだけ使うように変更し、再度 react-native-bundle-visualizer でテストしてみます。

lodashを削除したあとのvisualizerの出力

lodashのforeachではなく args.forEach でいい気がしますが、ライブラリの動作を完全には理解できておらず動作確認も完全にはできていないので、一旦 lodash.foreach に変更したものを newrelic にPRを作成しました。

github.com

lodash.foreach を使うように変更することで、およそ 70KB 削減できています。

応用編 ・Tree Shakingを使う

React Native Developer Tools (Microsoft) から提供されている、metro-serializer-esbuild を使うことでTree Shakingが実現できます。

microsoft.github.io

esbuildがバンドル処理を引き継ぐことで、 Tree Shaking が動くようになります!

babelの設定と、 metroの設定に customSerializertransformer を追加するだけで動作します。

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 を追加した結果。明らかに出力が変わっている

metro-serializer-esbuild はβ版ですがかなり良好な結果が得られており、テスト用に作ったプロジェクトでは最終的に、 936KB → 866KB → 732KB までバンドルサイズをへらすことができました。

テストに使ったプロジェクトは以下のリポジトリで公開しています。

github.com

P.S

この記事は、 207 Advent Calendar 2022の記事でした。

qiita.com

最後にいつもの宣伝で、

207株式会社では、配達員向け効率化アプリ「TODOCUサポーター」を開発しています。 ネイティブアプリでもWeb開発の知識を活かすことができます!207では開発メンバーを大絶賛募集中です!

もし少しでもご興味がありましたら以下のnotionをご覧ください!

207-inc.super.site

*1:JavaScriptエンジンのHermesを利用している場合、正確にはHermes bytecodeが配布される