babel 是否可以解析 JSX 注释?

Is it possible for babel to parse a JSX comment?

问题

我正在编写一个在编译时修改一些 JSX 代码的 babel 插件。

<MyComponent >
   {/* comment here */}
</MyComponent>

我想让它做的是读取一些本地配置并注入一些代码。我的目标是:

import Something from '/Absolute/path/to/local/config';

// component here:

<MyComponent >
   <SomeHighlyConfigDependentComponent {...withConfiguredProps} />
</MyComponent>

其他一切都很好,但我实际上无法获得 JSX 注释。在 AST explorer 上,AST 显示为:

{
  "type": "JSXExpressionContainer",
  "start": 82,
  "end": 102,
  "expression": {
    "type": "JSXEmptyExpression",
    "start": 83,
    "end": 101
  }
},

但这并没有给我太多关于以下事实的信息:

因此我无法可靠地区分这条评论(“请输入”)与其他评论。

请不要质疑why我在做这个;上面的伪代码是一个非常非常小的、脱离上下文的概念,它是我项目中一个更大的东西的一部分。如果您有更好的设计方案或想法,请随时发表评论!

问题

Babel 有什么方法可以“检测”JSX 注释的存在并解析其内容吗?如果不是,你能解释为什么吗?如果没有,是否有任何我可以尝试的轻量级替代方案允许我在编译时执行此操作(暴力解析正则表达式除外,那绝对不行)?

我尝试过的替代方案

根据 this issue,我试过这个:

<MyComponent >
  { null /* comment here */}
</MyComponent>

AST 浏览器将其解析为

{
  "type": "JSXExpressionContainer",
  "start": 82,
  "end": 107,
  "expression": {
    "type": "Literal",
    "start": 83,
    "end": 87,
    "value": null,
    "raw": "null"
  }
},

并没有解决问题。未能解析评论,我试过这个:

<MyComponent >
  <div data-inject-code="SOME_CONSTANT" />
</MyComponent >

这行得通,但不是很理想,因为我们必须记录这个 div 如何没有出现在 DOM 树中,它被替换了,以及真正想要包装器的人div 周围修改后的代码可能会对这种行为感到困惑。想象一下编码 <div data-inject-code="SOME_CONSTANT" className="flex flex-row" /> 并发现他们的 flexbox 包装器已经消失了。

如果所有其他方法都失败了,我将使用此解决方案。但是——你能理解我在这里说的话吗——我实际上对问题背后的“原因”更感兴趣。 babel 是如何处理 JSX 注释的,我可以让它解析它们吗?

是的,这是可能的。问题是我正在使用 acorn 查看 AST,这是默认的解析器,在 AST explorer. According to this issue "@babel/parser was born as a fork of Acorn, but it has been completely rewritten." One major difference is that they have different AST formats. Switch the parser to @babel/parser and we'll see a correct representation of how babel parses the AST tree. Also, according to this issue acorn 中不包含其 AST 中的注释,但 @babel/parser 包含注释。

Comments are not included in the syntax tree that Acorn returns. You can use the onComment option to get information about comments in the file, but Acorn won't put them in the tree for you (as there is no unambiguous way to do so)

    <MyComponent >
    {/* comment */}
    </MyComponent>

这被解析为 JSXExpressionContainerexpression 值为:

"expression": {
  "type": "JSXEmptyExpression",
  "start": 84,
  "end": 97,
  "loc": {
    "start": {
      "line": 6,
      "column": 5
    },
    "end": {
      "line": 6,
      "column": 18
    }
  },
  "innerComments": [
    {
      "type": "CommentBlock",
      "value": " comment ",
      "start": 84,
      "end": 97,
      "loc": {
        "start": {
          "line": 6,
          "column": 5
        },
        "end": {
          "line": 6,
          "column": 18
        }
      }
    }
  ]
}

评论可以标记为内部、前导或尾随,具体取决于它们在表达式容器中出现的位置。这将解析为尾随评论:

{null /* comment */}

并且在这个表达式中,注释被解析为第二个数字文字的前导注释。

{1 + /* comment */ 2}

所以我们可以像这样解析 JSX 注释(具体来说,像 {/* comment */} 这样的注释):

JSXExpressionContainer: {
  enter(path) {
    const hasComment = path.node.expression.find(expression => expression.type === 'CommentBlock' && expression.value.trim() === 'some value');
  },
}

这里有一个关于babel如何解析评论的非常详细的解释:https://github.com/babel/babel/blob/main/packages/babel-parser/ast/comment-attachment.md

  • 我们在packages/babel-parser/src/tokenizer/index.jsTokenizer#skipSpace构造评论空白,退出skip循环后,我们收集评论,标记位置信息并推送到parser.state.commentStack。
  • 对于从 parser#finishNode 调用的每个完成的 AST 节点。然后我们反向迭代state.commentStack。我们在 comment.end = node.start 时标记 trailingNode,在未定义时标记 containingNode,所以这里第一个 finishNode() 是赢家,它正是我们可以附加到注释的最里面的包含节点。
  • 设置好包含节点后,我们就可以给相关节点分配评论了。然后解析器附加注释并进行尾随逗号调整。