React Native 中的套件匯出支援
隨著 React Native 0.72 的發布,Metro(我們的 JavaScript 建置工具)現在包含對 package.json
"exports"
欄位的 Beta 支援。啟用 後,它會新增下列功能
在這篇文章中,我們將涵蓋套件匯出的運作方式,以及這些變更對身為 React Native 應用程式開發人員或套件維護者的您有何意義。
什麼是套件匯出?
套件匯出在 Node.js 12.7.0 中推出,是 npm 套件指定進入點的現代方法,也就是套件子路徑的對應,這些子路徑可以從外部匯入,以及它們應解析為哪個檔案。
支援 "exports"
改善了 React Native 專案與更廣泛 JavaScript 生態系統的協作方式(目前在約 16.6k 個套件中使用),並為套件作者提供標準化的功能集,讓多平台套件可以鎖定 React Native。
"exports"
可以與 package.json
檔案中的 "main"
一起使用,或取而代之。
{
"name": "@storybook/addon-actions",
"main": "./dist/index.js",
...
"exports": {
".": {
"node": "./dist/index.js",
"import": "./dist/index.mjs",
"default": "./dist/index.js"
},
"./preview": {
"import": "./dist/preview.mjs",
"default": "./dist/preview.js"
},
...
"./package.json": "./package.json"
}
}
以下是一些應用程式程式碼,透過匯入 @storybook/addon-actions
的不同子路徑來使用上述套件。
import {action} from '@storybook/addon-actions';
// -> '@storybook/addon-actions/dist/index.js'
import {action} from '@storybook/addon-actions/preview';
// -> '@storybook/addon-actions/dist/preview.js'
import helpers from '@storybook/addon-actions/src/preset/addArgsHelpers';
// Inaccessible - not listed in "exports"!
套件匯出的重點功能如下
- 套件封裝:只有在
"exports"
中定義的子路徑才能從套件外部匯入,讓套件可以控制其公開 API。 - 子路徑別名:套件可以定義對應到不同檔案位置的自訂子路徑(包括透過 子路徑模式),允許重新定位檔案,同時保留公開 API。
- 條件式匯出:子路徑可能會根據環境解析為不同的基礎檔案。例如,為了鎖定
"node"
、"browser"
或"react-native"
執行階段,取代"browser"
欄位規格。
"exports"
的完整功能在 Node.js 套件進入點規格中詳細說明。
由於這些功能與現有的 React Native 概念(例如平台專用擴充功能)重疊,而且 "exports"
已在 npm 生態系統中存在一段時間,因此我們與 React Native 社群聯繫,以確保我們的實作符合開發人員的需求(PR、最終 RFC)。
適用於應用程式開發人員
套件匯出今天即可啟用 Beta 版。
- 針對依賴套件匯出功能(例如 Firebase 和 Storybook)的套件的匯入,現在應該可以按照設計運作。
- 使用 Metro 的 React Native for Web 專案現在將能夠使用
"browser"
條件式匯出,無需任何變通方法。
啟用套件匯入會帶來一些邊緣情況的重大變更,可能會影響特定專案,您可以立即測試。
在未來的 React Native 版本中,將預設啟用套件匯出。在雞生蛋與蛋生雞的情況下,React Native 應用程式先前一直是某些套件遷移到 "exports"
的障礙,或者使用了我們的 "react-native"
根欄位跳脫機制。在 Metro 中支援這些功能將使生態系統能夠向前發展。
啟用套件匯出(Beta 版)
可以在您應用程式的 metro.config.js 檔案中,透過 resolver.unstable_enablePackageExports
選項啟用套件匯出。
const config = {
// ...
resolver: {
unstable_enablePackageExports: true,
},
};
Metro 公開了兩個進一步的解析器選項,可設定條件式匯出的行為
unstable_conditionNames
— 解析條件式匯出時要判斷提示的條件名稱集。預設情況下,我們比對['require', 'import', 'react-native']
。unstable_conditionsByPlatform
— 解析給定平台目標時要判斷提示的其他條件名稱。預設情況下,當平台為'web'
時,這會比對'browser'
。
請記得使用 React Native Jest 預設值! Jest 預設包含對套件匯出的支援。在測試中,您可以使用 testEnvironmentOptions
選項覆寫要解析的 customExportConditions
。
如果您使用 TypeScript,則可以透過在專案的 tsconfig.json
中設定 moduleResolution: 'bundler'
和 resolvePackageJsonImports: false
來比對解析行為。
驗證專案中的變更
對於現有專案,我們建議早期採用者遵循下列步驟,查看在啟用 unstable_enablePackageExports
後是否發生解析變更。這是一次性程序。很可能完全不會有任何變更,但我們希望開發人員能夠確定是否加入。
💡 驗證專案中的變更
如果您未使用 Yarn,請將 yarn
替換為 npx
(或專案中使用的相關工具)。
-
取得所有已解析的依賴項(變更前)
# Replace index.js with your entry file if needed, such as App.js
yarn metro get-dependencies index.js --platform android --output before.txt- Expo CLI:如果您的專案尚無
metro.config.js
檔案,請執行npx expo customize metro.config.js
。 - 為了全面涵蓋,請將
--platform android
替換為您的應用程式使用的其他平台(例如ios
、web
)。
- Expo CLI:如果您的專案尚無
-
在
metro.config.js
中啟用resolver.unstable_enablePackageExports
。 -
取得所有已解析的依賴項(變更後)
yarn metro get-dependencies index.js --platform android --output after.txt
-
比較!
diff before.txt after.txt
重大變更
我們決定在 Metro 中實作符合規格的套件匯出(需要一些重大變更),但在其他方面向後相容(協助具有現有匯入的應用程式逐步遷移)。
主要的重大變更是,當套件提供 "exports"
時,將會優先諮詢它(在任何其他 package.json
欄位之前),並且將直接使用相符的子路徑目標。
- Metro 不會針對匯入規範器擴展
sourceExts
。 - Metro 不會針對目標檔案解析平台專用擴充功能。
如需更多詳細資訊,請參閱 Metro 文件中的所有重大變更。
套件封裝是寬鬆的
當 Metro 遇到未在 "exports"
中列出的子路徑時,它會回復為舊版解析。這是一項相容性功能,旨在減少現有 React Native 專案中先前允許的匯入的使用者摩擦。
Metro 不會擲回錯誤,而是記錄警告。
warn: You have imported the module "foo/private/fn.js" which is not listed in
the "exports" of "foo". Consider updating your call site or asking the package
maintainer(s) to expose this API.
我們計劃在未來實作套件封裝的嚴格模式,以符合 Node 的預設行為。因此,我們建議所有開發人員解決使用者提出的這些警告。
適用於套件維護者(預覽)
根據我們的推出計畫,套件匯出將在今年稍後的下一個 React Native 版本 (0.73) 中針對大多數專案啟用。
我們沒有計畫在近期內移除對 "main"
欄位和其他目前套件解析功能的支援。
套件匯出提供了限制存取套件內部元件的功能,以及程式庫鎖定 React Native 和 React Native for Web 的更可預測功能。
如果您今天使用 "exports"
如果您的套件將 "exports"
與目前的 "react-native"
根欄位搭配使用,請記住上述使用者的重大變更。對於在 Metro 中啟用此功能的使用者,現在在模組解析期間將優先考量 "exports"
。
實際上,我們預期使用者的主要變更將是強制執行(透過警告)其應用程式中任何無法存取的子路徑,以尊重 "exports"
套件封裝。
遷移至 "exports"
在您的套件中新增 "exports"
欄位完全是選用的。現有的套件解析功能對於未使用 "exports"
的套件的行為將完全相同 — 而且我們沒有計畫移除此行為。
我們認為 "exports"
的新功能為 React Native 套件維護者提供了一組引人注目的功能。
- 收緊您的套件 API:現在是檢閱您套件的模組 API 的絕佳時機,現在可以透過匯出的子路徑別名正式定義。這可防止使用者存取內部 API,減少錯誤的表面區域。
- 條件式匯出:如果您的套件鎖定 React Native for Web(即
"react-native"
和"browser"
),我們現在讓套件可以控制這些條件的解析順序(請參閱下一個標題)。
如果您決定引入 "exports"
,我們建議將其作為重大變更。我們在 Metro 文件中準備了一份遷移指南,其中包含如何取代平台專用擴充功能等功能。
請勿依賴 Metro 實作的寬鬆行為。 雖然 Metro 向後相容,但套件應遵循規格中記錄的 "exports"
方式,並由其他工具嚴格實作。
新的 "react-native"
條件
我們已引入 "react-native"
作為社群條件(用於條件式匯出)。這代表 React Native 這個框架,與其他公認的執行階段(例如 "node"
和 "deno"
)(RFC)並駕齊驅。
將由 React Native 框架(所有平台)比對。為了鎖定 React Native for Web,應在此條件之前指定 "browser"。
這取代了先前的 "react-native"
根欄位。先前解析此欄位的優先順序是由專案決定的,這在使用 React Native for Web 時造成了模糊性。在 "exports"
下,套件具體定義了條件式進入點的解析順序 — 消除了這種模糊性。
"exports": {
"browser": "./dist/index-browser.js",
"react-native": "./dist/index-react-native.js",
"default": "./dist/index.js"
}
由於其他現有平台選取方法的普及,以及此行為在跨框架中可能如何運作的複雜性,我們選擇不引入 "android"
和 "ios"
條件。請改用 Platform.select()
API。
未來:穩定的 "exports"
,預設啟用
在下一個 React Native 版本中,我們的目標是移除此功能的 unstable_
前綴(在解決計畫的效能工作和任何錯誤之後),並預設啟用套件匯出解析。
隨著為所有人啟用 "exports"
,我們可以開始推動 React Native 社群向前發展 — 例如,可以更新 React Native 的核心套件,以更好地分隔公用和內部模組。
感謝
感謝 React Native 社群成員對 RFC 提供意見回饋:@SimenB、@tido64、@byCedric、@thymikee。