我注意到在比较intl.formatMessage({ id: 'section.someid' })
与intl.messages['section.someid']
之后,react-intl有一些性能提升机会。
点击此处:https://github.com/yahoo/react-intl/issues/1044
第二个速度提高了5倍(并且在具有大量翻译元素的页面中产生了巨大差异),但似乎不是正式的方法(我猜他们可能会在未来版本中更改变量名称)。
所以我有了创建一个进行转换的babel插件的想法(formatMessage(对于messages [)。但是我很难做到这一点因为babel插件创建没有很好的文档记录(我发现了一些教程,但它没有'我有我需要的东西。。我理解基础但没有找到我需要的访客功能名称。
我的样板代码目前是:
module.exports = function(babel) {
var t = babel.types;
return {
visitor: {
CallExpression(path, state) {
console.log(path);
},
}
};
};
所以这是我的问题:
答案 0 :(得分:2)
我使用哪种访问者方法来提取类调用 - intl.formatMessage(它真的是CallExpression)吗?
是的,它是CallExpression
,与函数调用相比,方法调用没有特殊的AST节点,唯一改变的是接收者(被调用者)。每当您想知道AST的样子时,您都可以使用精彩的AST Explorer。作为奖励,您甚至可以通过在“变换”菜单中选择“Babel”来在AST Explorer中编写Babel插件。
如何检测对formatMessage的调用?
为了简洁起见,我将只关注对intl.formatMessage(arg)
的确切调用,对于一个真正的插件,您需要覆盖其他具有不同AST表示的情况(例如intl["formatMessage"](arg)
)。
首先要确定被叫方是intl.formatMessage
。如您所知,这是一个简单的对象属性访问,相应的AST节点称为MemberExpression
。在这种情况下,访问者会收到匹配的AST节点CallExpression
,为path.node
。这意味着我们需要验证path.node.callee
是MemberExpression
。值得庆幸的是,这非常简单,因为babel.types
以isX
的形式提供方法,其中X
是AST节点类型。
if (t.isMemberExpression(path.node.callee)) {}
现在我们知道它是MemberExpression
,其object
和property
对应object.property
。因此,我们可以检查object
是否为标识符intl
,property
是否为formatMessage
。为此,我们使用isIdentifier(node, opts)
,它接受第二个参数,允许您检查它是否具有给定值的属性。所有isX
方法都采用该形式提供快捷方式,有关详细信息,请参阅Check if a node is a certain type。他们还检查节点不是null
或undefined
,因此isMemberExpression
在技术上不是必需的,但您可能希望以不同的方式处理其他类型。
if (
t.isIdentifier(path.node.callee.object, { name: "intl" }) &&
t.isIdentifier(path.node.callee.property, { name: "formatMessage" })
) {}
如何检测通话中的参数数量? (如果有格式化,则不应该进行替换)
CallExpression
具有arguments
属性,该属性是参数的AST节点的数组。同样,为了简洁起见,我只考虑只有一个参数的调用,但实际上你也可以转换像intl.formatMessage(arg, undefined)
这样的东西。在这种情况下,它只是检查path.node.arguments
的长度。我们还希望参数成为一个对象,因此我们检查ObjectExpression
。
if (
path.node.arguments.length === 1 &&
t.isObjectExpression(path.node.arguments[0])
) {}
ObjectExpression
具有properties
属性,该属性是ObjectProperty
个节点的数组。从技术上来说,您可以检查id
是唯一的属性,但我会在此处跳过,而只是查找id
属性。 ObjectProperty
有一个key
和value
,我们可以使用Array.prototype.find()
搜索属性,标识为id
。
const idProp = path.node.arguments[0].properties.find(prop =>
t.isIdentifier(prop.key, { name: "id" })
);
idProp
如果存在,则为相应的ObjectProperty
,否则为undefined
。当它不是undefined
时,我们想要替换节点。
我如何更换? (intl.formatMessage({id:'某些'})到intl.messages ['某事']?
我们想要替换整个CallExpression
和Babel提供的path.replaceWith(node)
。唯一剩下的就是创建应该替换它的AST节点。为此,我们首先需要了解如何在AST中表示intl.messages["section.someid"]
。 intl.messages
就像MemberExpression
一样intl.formatMessage
。 obj["property"]
是计算属性对象访问,在AST中也表示为MemberExpression
,但computed
属性设置为true
。这意味着intl.messages["section.someid"]
是MemberExpression
,其中MemberExpression
为对象。
请记住,这两者在语义上是等价的:
intl.messages["section.someid"];
const msgs = intl.messages;
msgs["section.someid"];
要构建MemberExpression
,我们可以使用t.memberExpression(object, property, computed, optional)
。要创建intl.messages
,我们可以重用intl
中的path.node.callee.object
,因为我们要使用相同的对象,但更改属性。对于该属性,我们需要创建名为Identifier
的{{1}}。
messages
只需要前两个参数,其余的我们使用默认值(t.memberExpression(path.node.callee.object, t.identifier("messages"))
false
和computed
可选)。现在我们可以使用null
作为对象,我们需要查找与MemberExpression
属性的值对应的计算属性(第三个参数设置为true
),这是可在我们之前计算的id
上获得。最后,我们用新创建的节点替换idProp
节点。
CallExpression
完整代码:
if (idProp) {
path.replaceWith(
t.memberExpression(
t.memberExpression(
path.node.callee.object,
t.identifier("messages")
),
idProp.value,
// Is a computed property
true
)
);
}
完整代码和一些测试用例可以在this AST Explorer Gist中找到。
正如我多次提到的那样,这是一个天真的版本,很多案例都没有涵盖,有资格进行转换。覆盖更多案例并不困难,但您必须识别它们并将它们粘贴到AST Explorer中,它将为您提供所需的所有信息。例如,如果对象是export default function({ types: t }) {
return {
visitor: {
CallExpression(path) {
// Make sure it's a method call (obj.method)
if (t.isMemberExpression(path.node.callee)) {
// The object should be an identifier with the name intl and the
// method name should be an identifier with the name formatMessage
if (
t.isIdentifier(path.node.callee.object, { name: "intl" }) &&
t.isIdentifier(path.node.callee.property, { name: "formatMessage" })
) {
// Exactly 1 argument which is an object
if (
path.node.arguments.length === 1 &&
t.isObjectExpression(path.node.arguments[0])
) {
// Find the property id on the object
const idProp = path.node.arguments[0].properties.find(prop =>
t.isIdentifier(prop.key, { name: "id" })
);
if (idProp) {
// When all of the above was true, the node can be replaced
// with an array access. An array access is a member
// expression with a computed value.
path.replaceWith(
t.memberExpression(
t.memberExpression(
path.node.callee.object,
t.identifier("messages")
),
idProp.value,
// Is a computed property
true
)
);
}
}
}
}
}
}
};
}
而不是{ "id": "section.someid" }
,则无法对其进行转换,但除了{{1}之外,还要检查{ id: "section.someid" }
这一过},像这样:
StringLiteral
我也没有故意引入任何抽象,以避免额外的认知负担,因此条件看起来很长。
有用的资源: