Seamlessly Migrate Taro 2.x to 3.x with Babel AST Transformations
This article explains how to upgrade Taro 2.x projects to Taro 3.x by converting source code into an AST using Babel, programmatically editing imports, configs, routers, and styles, and then generating the updated code, complete with practical code examples and migration tips.
Background
Taro is an open‑source cross‑platform framework that supports React, Vue, Nerv and many mini‑program environments. Although Taro 3.x has been stable for over two years, many projects still use 2.x, and a simple find‑and‑replace upgrade often leads to case‑sensitivity errors, ESLint failures, and other issues.
To achieve a more reliable migration, we convert the source code into an AST with babel, edit the tree, and generate new code.
AST and Babel Quick Start
AST (Abstract Syntax Tree) represents the syntactic structure of source code as a tree. Because the AST type system is large, we use AST Explorer to inspect nodes. babel is a toolchain that transforms modern ECMAScript syntax into backward‑compatible JavaScript. The Babel packages used in this article are:
@babel/parser – parses TypeScript/JavaScript into an AST.
@babel/traverse – walks the AST and allows modifications.
@babel/types – creates new node types.
@babel/generator – generates source code from an AST.
import * as parser from '@babel/parser';
const code = `function square(n) {
return n * n;
}`;
// Parse to AST
const ast = parser.parse(code);Using @babel/traverse we can define a Visitor to edit the tree:
declare function traverse<T>(
node: Node,
handlers: TraversalHandler<T> | TraversalHandlers<T>,
state?: T,
): void;Below is an example that changes a function name:
import traverse from '@babel/traverse';
import generate from '@babel/generator';
traverse(ast, {
FunctionDeclaration(path) {
if (path.node.id) {
path.node.id.name = 'x';
}
},
});
const resultCode = generate(ast).code;Migration Practice
API Migration
Import Statements
In Taro 2.x, imports come from @tarojs/taro. In 3.x, React APIs must be imported from react. We locate ImportDeclaration nodes whose source.value equals '@tarojs/taro', separate React‑related specifiers, and rewrite the import list.
traverse(ast, {
ImportDeclaration(path) {
if (path.node.source.value === TARO_TOKEN) {
const specifiers = [];
path.node.specifiers.forEach(specifier => {
if (!REACT_IMPORTS.includes(specifier.local.name)) {
specifiers.push(specifier);
} else {
reactSpecifiers.push(specifier);
}
});
path.node.specifiers = specifiers;
}
},
});We also replace Taro.FC and Taro.useState with their React equivalents by handling TSQualifiedName and MemberExpression nodes.
traverse(ast, {
TSQualifiedName(path) {
if (REACT_IMPORTS.includes(path.node.right.name)) {
path.node.left.name = REACT_SPACE;
}
},
MemberExpression(path) {
const node = path.node;
if (
t.isIdentifier(node.property) &&
REACT_IMPORTS.includes(node.property.name) &&
t.isIdentifier(node.object) &&
node.object.name === TARO_SPACE
) {
node.object.name = REACT_SPACE;
}
},
});Config Migration
Class Components
Class‑component configs are stored as ClassProperty nodes. We extract the config property via its key, save the specifiers, and remove the original node.
traverse(ast, {
ClassProperty(path) {
const node = path.node;
pageConfigSpecifiers = node.value.properties;
path.remove();
},
});Function Components
Function‑component configs appear as AssignmentExpression nodes like ComponentName.config = {}. We collect these assignments, then match them with the default‑exported component name to identify the real config and remove the original node.
traverse(ast, {
AssignmentExpression(path) {
const node = path.node;
allConfigSpecifiers[node.left.object.name] = {
path,
specifiers: node.right.properties,
};
},
ExportDefaultDeclaration(path) {
if (!t.isIdentifier(path.node.declaration)) return;
const findPath = allConfigSpecifiers[path.node.declaration.name];
if (findPath) {
pageConfigSpecifiers = findPath.specifiers;
findPath.path.remove();
}
},
});Router Migration
Replace this.$router (or Taro.useRouter) with getCurrentInstance().router and adjust imports accordingly.
import { getCurrentInstance } from '@tarojs/taro';
class C extends Component {
current = getCurrentInstance();
componentWillMount() {
console.log(this.current.router);
}
}
function C() {
const { router } = getCurrentInstance();
}Style Migration
In Taro 3.x the concepts of externalClasses and addGlobalClass are removed. We delete any static class properties whose key matches these style‑config keys.
traverse(ast, {
ClassProperty(path) {
const node = path.node;
if (node.static && STYLE_CONFIG_KEYS.includes(node.key.name)) {
path.remove();
}
},
});Code Generation
After all AST edits, we generate the final source code and write it to a file.
const code = generate(ast).code;
fs.writeFileSync(filePath, code);Conclusion
By leveraging Babel we successfully migrated a Taro 2.x codebase to Taro 3.x, handling API, config, router, and style changes. The process deepened our understanding of AST manipulation and demonstrated Babel’s powerful, versatile capabilities.
References
[1]Upgrade guide: https://taro-docs.jd.com/docs/migration [2] AST Explorer: https://astexplorer.net/ [3] Babel: https://www.babeljs.cn/docs [4] @babel/parser: https://www.babeljs.cn/docs/babel-parser [5] @babel/traverse: https://www.babeljs.cn/docs/babel-traverse [6] @babel/types: https://www.babeljs.cn/docs/babel-types [7] @babel/generator: https://www.babeljs.cn/docs/babel-generator [8] Component style docs: https://taro-docs.jd.com/docs/component-style [9] Full demo code: https://github.com/mvpleung/taro2-taro3
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.
