一个进行中或完成的
React
项目如果要进行国际化,那么第一步需要从源码中提取中文词条,这往往是一个体力活,并且有无法找到所有的中文词条的风险。我们可以开发工具来代替人工提取,简单点可以使用基于字符串和正则表达式查找就可以完成,这种方式有一个很大问题:中文词条的提取效率取决于你正则表达式有多强大,并且如果后续有词条替换的需求,实现起来相对复杂。下面要介绍另一种方式,从String -> AST -> String
的方式,这里我使用了TS Compiler API
。
认识Compiler APIs
TS
早在2.x版本就提供了一系列的API
来更好的操作TypeScript AST
,利用这些API,可以很方便的编写插件来影响TS
编译过程,当然这些API
也可以单独使用。关于AST
这里并不做过多介绍,推荐一篇文章:深入Babel,这一篇就够了,以Babel
举例,详细描述了Babel
编译(转译)的过程,以及如何编写Babel
插件,TS
的工作流程和Babel
某种程度上是相似的。
下面介绍几个常用的API
。
createSourceFile
1 | function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes?: boolean, scriptKind: ScriptKind): SourceFile; |
该方法接受源代码(文件名/字符串)并返回SourceFile
,那SourceFile
是否就是AST
呢?再看SourceFile
的定义:1
2
3
4
5
6
7
8interface SourceFile extends Declaration {
kind: SyntaxKind.SourceFile;
statements: NodeArray<Statement>;
endOfFileToken: Token<SyntaxKind.EndOfFileToken>;
fileName: string;
text: string;
...
}
通过查看SourceFile
的定义,我们可以把SourceFile
当做是TypeScript
的AST
,其中statements
属性是源代码语句的数组,Statement
也就是AST
中的节点(Node
)。
好了,如果我们有一份使用TS
的React
源代码,对于每个.tsx
文件而言,使用下面的方式就可以得到AST
了。1
const ast = ts.createSourceFile('', codeString, ts.ScriptTarget.ES2015, true, ts.ScriptKind.TSX)
Printer
使用createSourceFile
可以将源代码转成SourceFile
,那么Printer
就是把SourceFile/Node
转成字符串的APIs
,其常用的几个API
如下:1
2
3
4
5function createPrinter(printerOptions?: PrinterOptions, handlers?: PrintHandlers): Printer;
interface Printer {
printFile(sourceFile: SourceFile): string;
printNode(hint: EmitHint, node: Node, sourceFile: SourceFile): string;
}
createPrinter
返回一个Printer
实例,该实例可使用既定的printerOptions
对Node
和SourceFile
进行打印(生成字符串形式)printFile
依据SourceFile
打印字符串源码,不进行任何转换printNode
打印节点
1 | const sourceFile: ts.SourceFile = |
transform
1 | function transform<T extends Node>(source: T | T[], transformers: TransformerFactory<T>[], compilerOptions?: CompilerOptions): TransformationResult<T>; |
生成AST
后就要开始处理了,ts
也提供了一系列的API
用于遍历AST
,forEachChild
和visitEachChild
都可以遍历AST
,初次之外visitEachChild
还可以修改节点,并返回修改后节点。
1 | function visitEachChild<T extends Node>(node: T, visitor: Visitor, context: TransformationContext): T; |
transform
方法就和其字面意思一样,使用该方法可以转换AST
。它接收多个transformer
,最简单的transformer
可以是下面这样:1
2
3
4
5
6
7const transformer = <T extends ts.Node>(context: ts.TransformationContext) => (rootNode: T) => {
function visit(node: T) {
console.log(node.kind)
return ts.visitEachChild(node, visit, context)
}
return ts.visitNode(rootNode, visit)
}
上面的transformer
访问了每个节点,并且打印出当前访问节点的kind
。
下面再写一个比较实际的transformer
,找到所有的中文字符串节点,并使用变量来替换该节点。
1 | const transformer = <T extends ts.Node>(context: ts.TransformationContext) => (rootNode: T) => { |
使用上面的transformer
,var name = '张三'
将会被转换成var name = placeholder
,文件中所有的StringLiteral
节点中只要包含中文都会被转成指定的Identifier
。
TS
还提供了create*
和update*
多个API
用于创建和更新节点,当对compiler API
更加了解后,我们就可以做更多的事,例如var total = 1 + 2
变成var t = 3
类似的代码压缩,自定义lint规则等等。
Compiler APIs
的应用
基于Compiler APIs
实现了一款React国际化工具 ext-intl,实现了React
项目词条提取、词条key生成、代码原处替换等功能。该工具目前是可用的,一定程度上可以提升React
项目国际化效率。
使用方式:
1 | $ yarn add --dev ext-intl |
完。