代码之家  ›  专栏  ›  技术社区  ›  Gustavo Huffenbacher Daud

react intl的Babel插件开发

  •  4
  • Gustavo Huffenbacher Daud  · 技术社区  · 8 年前

    在比较之后,我注意到react intl有一些性能提升的机会 intl.formatMessage({ id: 'section.someid' }) vs intl.messages['section.someid'] 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);
          },
        }
      };
    };
    

    • 如何检测对formatMessage的调用?
    • 我如何更换?(intl.formatMessage({id:'something})到intl.messages['something']?
    • (可选)是否有方法检测formatMessage是否真的来自react intl库?
    1 回复  |  直到 8 年前
        1
  •  3
  •   Michael Jungo    8 年前

    我使用哪个访问者方法提取类调用-intl.formatMessage(它真的是CallExpression吗)?

    是的,它是一个 CallExpression AST Explorer . 作为奖励,您甚至可以通过在Transform菜单中选择Babel,在AST浏览器中编写Babel插件。

    为了简单起见,我将只关注 intl.formatMessage(arg) ,对于一个真正的插件,您还需要涵盖其他情况(例如。 intl["formatMessage"](arg) )具有不同的AST表示。

    第一件事是确定被叫方是 intl.formatMessage MemberExpression 调用表达式 在这种情况下,作为 path.node . 这意味着我们需要验证 path.node.callee 成员表达式 . 谢天谢地,这很简单,因为 babel.types isX 哪里 X 是AST节点类型。

    if (t.isMemberExpression(path.node.callee)) {}
    

    成员表达式 object property 对应于 object.property . 所以我们可以检查一下 对象 是标识符 intl 所有物 标识符 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" })
    ) {}
    

    如何检测通话中的参数数量?(如果存在格式,则不应进行替换)

    这个 有一个 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 身份证件 所有物这个 key value ,我们可以使用 Array.prototype.find() 搜索键为标识符的属性 .

    const idProp = path.node.arguments[0].properties.find(prop =>
      t.isIdentifier(prop.key, { name: "id" })
    );
    

    idProp 将是相应的 ObjectProperty 如果它存在,否则它将是 . 当它不是 未定义

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

    我们想替换整个 调用表达式 path.replaceWith(node) . 剩下的唯一一件事是创建应该替换的AST节点。为此,我们首先需要了解 intl.messages["section.someid"] 在AST中表示。 intl.messages 是一个 就像 obj["property"] 是计算属性对象访问,也表示为 在AST中,但具有 computed 属性设置为 true . 这意味着 intl.messages[“section.someid”] 是一个 成员表达式 用一个

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

    intl.messages["section.someid"];
    
    const msgs = intl.messages;
    msgs["section.someid"];
    

    构建 成员表达式 我们可以使用 t.memberExpression(object, property, computed, optional) . 用于创建 国际消息 我们可以重复使用 从…起 path.node.callee.object 因为我们想使用同一个对象,但改变了属性。对于属性,我们需要创建一个 Identifier messages .

    t.memberExpression(path.node.callee.object, t.identifier("messages"))
    

    false 计算的 可选)。现在我们可以用它了 成员表达式 真的 身份证件 属性,该属性可在 我们早些时候计算过。最后,我们替换 具有新创建的节点。

    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
                      )
                    );
                  }
                }
              }
            }
          }
        }
      };
    }
    

    完整的代码和一些测试用例可以在中找到 this AST Explorer Gist

    { "id": "section.someid" } 而不是 { id: "section.someid" } StringLiteral 除了一个 标识符

    const idProp = path.node.arguments[0].properties.find(prop =>
      t.isIdentifier(prop.key, { name: "id" }) ||
      t.isStringLiteral(prop.key, { value: "id" })
    );
    

    我也没有故意引入任何抽象,以避免额外的认知负荷,因此条件看起来很长。

    有用的资源: