React Native 與 Web 共用 Codebase 時的動態載入:import()、require 與平台邊界

在 React Web 與 React Native 共用同一份 Codebase時,有時會遇到這種情況:

  • 某些模組只需要在 App 端(RN)載入,Web 不需要
  • 反過來:某些模組只在 Web 端需要
  • 你不希望「另一端」在 build-time 就因為 resolve 不到平台專屬模組而爆掉
  • 更不希望 Web bundle 不小心把 App-only 程式碼打進去,造成體積膨脹

直覺上,可能會在 shared code 裡這樣寫:

if (isAPP) {
  // 希望只在 App 載入某個檔案
}

問題來了:這裡到底該用 importimport(),還是 require

❌ 錯誤用法:在 if 裡使用靜態 import

下面這種寫法在語法層級就不成立:

if (isAPP) {
  import NativeModule from 'react-native-some-module' // ❌ 語法層面錯誤
}

因為 import ... from ...ESM 靜態模組語法,必須出現在 top-level。更重要的是:

  • 靜態 import 會在 build-time 被 bundler(Webpack/Vite/Metro)用來建立 dependency graph
  • 這件事發生在程式執行之前,與 runtime 的分支判斷無關

所以只要這份 shared code 會被 Web 端掃到,Web bundler 就可能嘗試 resolve 這個 RN-only 模組,導致 build-time 直接爆掉或引入不必要的依賴。

✅ Runtime 條件載入:import() vs require()

A:動態 import(非同步)—— 推薦首選

if (isAPP) {
  const module = await import('react-native-some-module');
  module.default.doSomething();
}

B:CommonJS require(同步)—— 有風險

if (isAPP) {
  const NativeModule = require('react-native-some-module');
  NativeModule.doSomething();
}

import() 對多數 Web bundler 是「明確的動態邊界」,通常會被視為可延遲載入的 boundary(chunk 或等效機制),因此主 bundle 不必包含該依賴。

建議:specifier 盡量用靜態字串(不要 import(someVar)),避免變成難以分析的動態路徑。

require() 在 RN 生態常見,但放在「Web/RN 共用碼」裡對 Web bundler 往往是保守解析:它不敢假設分支永遠不走,因此仍可能 resolve/納入依賴,導致 build-time 失敗或 bundle 膨脹(行為視 bundler 與配置而定)。

只有在你能保證 Web 端根本不會讀到該檔案時,conditional require 才比較可控。


🚀 架構層面的解決方案:檔案後綴法 (Platform Suffix)

這是 React Native 生態系中比較優雅的解法。同一個 module path,不同平台吃不同檔案。

原理

同一個 module path,讓打包工具自己決定吃哪一個檔案。

  • Metro Bundler (RN 端):預設優先尋找 .native.js (以及 .ios.js, .android.js)。
  • Web bundler (Web 端):通常解析 .js.web.js
範例

建立兩個檔案:

  1. MyModule.js (Web 實作)
  2. MyModule.native.js (RN 實作)

在使用時:

// 不用寫任何 if 判斷,直接 import(不要寫副檔名)
import { doSomething } from './MyModule';

doSomething();

這是在 build-time 就解決分歧:Web bundler 根本不會讀到 .native 檔案,因此 .native 裡可以放心使用 RN-only 的靜態 import;同理,RN 端也會自動吃到 .native 版本。

總結:

策略層級方法優點缺點推薦情境
架構層級檔案後綴
x.native.js
最乾淨。Build-time 隔離。需要建立多個檔案。核心模組、UI Component
差異較大時使用。
語法層級動態 import()靈活。Web Bundler 會自動切分 Chunk。需處理 async/await 非同步邏輯。偶爾使用的邏輯
不想為了小功能拆檔案時。
語法層級require同步執行,寫法簡單。有風險。Web 可能誤打包導致崩潰。僅限 RN 舊代碼
或確定 Web 絕對讀不到時。

Similar Posts