Javascript react intl的Babel插件开发

Javascript react intl的Babel插件开发,javascript,babeljs,react-intl,babel-core,Javascript,Babeljs,React Intl,Babel Core,我注意到,在比较intl.formatMessage({id:'section.someid'})vsintl.messages['section.someid']之后,react intl有一些性能提升的机会。 请参阅此处的更多信息: 第二种方法速度快了5倍(并且在包含大量翻译元素的页面中产生了巨大的差异),但这似乎不是正式的方法(我想他们可能会在未来的版本中更改变量名) 所以我想创建一个babel插件来进行转换(formatMessage(to messages[))。但是我很难做到这一点,

我注意到,在比较
intl.formatMessage({id:'section.someid'})
vs
intl.messages['section.someid']
之后,react intl有一些性能提升的机会。 请参阅此处的更多信息:

第二种方法速度快了5倍(并且在包含大量翻译元素的页面中产生了巨大的差异),但这似乎不是正式的方法(我想他们可能会在未来的版本中更改变量名)

所以我想创建一个babel插件来进行转换(formatMessage(to messages[))。但是我很难做到这一点,因为babel插件的创建没有很好的文档记录(我找到了一些教程,但它没有我需要的内容)。我了解基本知识,但还没有找到我需要的访问者函数名

我的样板代码当前为:

module.exports=函数(巴别塔){
var t=巴别塔类型;
返回{
参观者:{
CallExpression(路径、状态){
console.log(路径);
},
}
};
};
下面是我的问题:

  • 我使用哪个访问者方法提取类调用-intl.formatMessage(它真的是CallExpression吗)
  • 如何检测对formatMessage的调用
  • 如何检测调用中的参数数量?(如果存在格式设置,则不应进行替换)
  • 如何替换?(intl.formatMessage({id:'something'})到intl.messages['something'])
  • (可选)是否有方法检测formatMessage是否真的来自react intl库
我使用哪个访问者方法提取类调用-intl.formatMessage(它真的是CallExpression吗)

是的,它是一个
调用表达式
,与函数调用相比,方法调用没有特殊的AST节点,唯一改变的是接收方(被调用方)。当你想知道AST是什么样子的时候,你可以使用奇妙的。作为奖励,你甚至可以通过在转换菜单中选择Babel,在AST浏览器中编写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)) {}
现在我们知道它是一个
成员表达式
,它有一个
对象
和一个
属性
,对应于
对象.属性
。因此我们可以检查
对象
是否是标识符
intl
属性
标识符
格式消息
。为此,我们使用
isIdentifier(节点,选项)
,它接受第二个参数,允许您检查它是否具有具有给定值的属性。所有
isX
方法都是这种形式,以提供快捷方式,有关详细信息,请参阅。它们还检查节点是否不为
null
未定义
,因此
isMemberExpression
在技术上是不必要的,但您可能希望以不同的方式处理另一种类型

if (
  t.isIdentifier(path.node.callee.object, { name: "intl" }) &&
  t.isIdentifier(path.node.callee.property, { name: "formatMessage" })
) {}
如何检测调用中的参数数量?(如果存在格式设置,则不应进行替换)

<> >代码>调用表达式< /代码>具有<代码>参数<代码>属性,它是参数的AST节点的数组。同样,为了简洁起见,我只考虑调用一个参数,但实际上,您也可以转换一些类似于IntFrimATMeg(ARG,未定义)的内容。。在本例中,它只是检查
路径.node.arguments
的长度。我们还希望参数是一个对象,因此我们检查
对象表达式

if (
  path.node.arguments.length === 1 &&
  t.isObjectExpression(path.node.arguments[0])
) {}
ObjectExpression
有一个
properties
属性,它是
ObjectProperty
节点的数组。从技术上讲,您可以检查
id
是否是唯一的属性,但我将在这里跳过它,而只查找
id
属性。
ObjectProperty
有一个
键和
,我们可以使用来搜索键为标识符id的属性

const idProp = path.node.arguments[0].properties.find(prop =>
  t.isIdentifier(prop.key, { name: "id" })
);
idProp
将是相应的
ObjectProperty
(如果它存在),否则它将是
未定义的
。如果它不是
未定义的
,我们希望替换节点

如何替换?(intl.formatMessage({id:'something'})到intl.messages['something'])

我们想替换整个
CallExpression
,Babel提供了它。剩下的唯一一件事就是创建它应该替换的AST节点。为此,我们首先需要了解
intl.messages[“section.someid”]
在AST中表示。
intl.messages
MemberExpression
就像
intl.formatMessage
was一样。
obj[“属性”]
是一个计算属性对象访问,它在AST中也表示为
MemberExpression
,但
computed
属性设置为
true
。这意味着
intl.messages[“section.someid”]
是一个
MemberExpression
对象的
MemberExpression

请记住,这两者在语义上是等价的:

intl.messages["section.someid"];

const msgs = intl.messages;
msgs["section.someid"];
要构造
MemberExpression
,我们可以使用.For c
t.memberExpression(path.node.callee.object, t.identifier("messages"))
if (idProp) {
  path.replaceWith(
    t.memberExpression(
      t.memberExpression(
        path.node.callee.object,
        t.identifier("messages")
      ),
      idProp.value,
      // Is a computed property
      true
    )
  );
}
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
                  )
                );
              }
            }
          }
        }
      }
    }
  };
}
const idProp = path.node.arguments[0].properties.find(prop =>
  t.isIdentifier(prop.key, { name: "id" }) ||
  t.isStringLiteral(prop.key, { value: "id" })
);