How NetEase Cloud Music Overcame RN 0.60→0.70 Upgrade Challenges
This article details the end‑to‑end technical journey of upgrading NetEase Cloud Music's React Native apps from version 0.60 to 0.70, covering research, break‑change analysis, dependency management, dual‑bundle strategies, automation scripts, and the hidden pitfalls encountered along the way.
The upgrade of NetEase Cloud Music's React Native (RN) applications from 0.60 to 0.70 was divided into four stages—research, solution design, implementation, and split‑traffic verification—with the front‑end team deeply involved in the first three.
Research Phase
Using the react-native upgrade tool, the team listed all direct dependencies (babel, react, etc.) and manually inspected third‑party and internal components. They collected changelogs and commit histories to enumerate every break change, prioritising those that impacted the API surface.
Break‑Change Handling Strategies
Apply patch-package to patch npm packages without modifying source. Example steps:
diff --git a/node_modules/react-native/index.js b/node_modules/react-native/index.js
index d59ba34..1bc8c9d 100644
--- a/node_modules/react-native/index.js
+++ b/node_modules/react-native/index.js
@@ -435,32 +435,16 @@ module.exports = {
},
// Deprecated Prop Types
get ViewPropTypes(): $FlowFixMe {
- invariant(
- false,
- 'ViewPropTypes has been removed from React Native. Migrate to '
- + "ViewPropTypes exported from 'deprecated-react-native-prop-types'.",
- );
+ return require('deprecated-react-native-prop-types').ViewPropTypes;
},
};
...Create the patch file with patch-package documentation.
Add deprecated-react-native-prop-types as a project dependency.
Insert an npm hook in package.json: "postinstall": "npx patch-package" For API‑level break changes, the team used conditional checks (compatibility code) such as:
if (this.scrollView.getNode) {
this.scrollView.getNode().scrollTo(...);
} else {
this.scrollView.scrollTo(...);
}When a single component could not be made compatible across versions, they introduced "capability sinking" by wrapping the original component in two versions—one for 0.60 and one for 0.70—while exposing a unified API.
Dependency Upgrade
Dependencies were grouped into three categories:
Official RN packages (react‑native, metro, etc.).
Common community packages used by Cloud Music (react‑navigation, react‑native‑svg, etc.).
Internal packages (e.g., @music/mnb-rn, utils, business modules).
Over 60 packages required updates. The guiding principle was to keep changes at the lowest possible layer, preserving API stability for business code.
Community Dependency Strategy
Large API changes (e.g., react‑navigation 4.x → 7.x) were avoided; the team kept the 4.x version and privatized it for the 0.70 build.
Minor API changes without 0.70 support were upgraded and privatized (e.g., react-native-gesture-handler).
Some packages required no changes (e.g., react-native-screens).
Internal Dependency Strategy
Internal upgrades focused on adapting break changes and aligning version declarations. Peer dependencies were set to * to let the container dictate the RN version, avoiding duplicate bundles.
{
"dependencies": {
"react-native": "0.60",
"react-native-gesture-handler": "^1.3.0"
...
},
"devDependencies": {
"react-native": "*",
"react-native-gesture-handler": "*"
...
}
}Solution Design
Two main approaches were evaluated:
Branch per RN version (maintain separate 0.70 branch). This conflicted with existing infrastructure (deployment, data platforms).
Single codebase producing two bundles (0.60 and 0.70). This required careful handling of version‑specific dependencies and resulted in a custom packaging workflow.
The final workflow included:
Dependency elevation: install the bundling tool globally on the build machine to ensure clean removal between builds.
Adding a degrade field in package.json to lock versions for the 0.60 build.
{
"degrade": {
"devDependencies": {
"@babel/core": "^7.5.5"
},
"dependencies": {
"react-native": "0.60.5"
}
}
}Using babel-plugin-module-resolver alias to map privateized packages for the 0.70 build and removing the alias for the 0.60 build.
Automation Script
A Node.js script distributed via npm and executed with npx automates most upgrade steps—generating patches, updating package.json, adjusting Babel config, and running resolutions. The script has been iterated over 110 times to stay current with RN changes.
Hidden Pitfalls
Metro's removal of JSON from default asset types broke a pre‑load mechanism that relied on bundled JSON files.
Hermes engine no longer supports Date.parse and named capture groups in regular expressions, requiring custom polyfills.
Bytecode bundles produced large patch files when using bsdiff. Adding the flag --base-bytecode previous.hbc during incremental compilation reduced patch size.
Inconsistent dependency versions across the 100+ RN apps caused lock‑file conflicts, missing modules (e.g., @babel/runtime/helpers/regeneratorruntime), and runtime errors such as duplicate native component registration.
Conclusion
The RN upgrade demonstrated that thorough research and a flexible dual‑bundle strategy are essential when many applications must migrate simultaneously without downtime. Continuous collaboration across business lines and rapid iteration of automation scripts were key to overcoming the numerous break changes and dependency mismatches.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
