如何将 JSX 这样的语言嵌入到 python 脚本中?

How to embed a language like JSX into a python script?

在我们公司,我们喜欢编写 django 驱动的应用程序,我们也喜欢使用 React。最近我们考虑为 python 编写一个基于组件的模板引擎,其中模板可以使用 JSX 编写为类似反应的组件。

理想情况下,应该可以将 JSX 嵌入到 python 代码中,这样您就可以像这样编写组件:

header.pyx中:

import PyReact
from my_awsome_project.components import Logo, Link


def Header(context):
    page_title = context.get('page_title')
    links = context.get('links')

    return (
        <div>
            <Logo /> 
            {page_title}
            <ul>
                {[<Link href={link.url}>{link.title}</Link> for link in links]}
            </ul>
        </div>
    )

这当然需要先转译文件以获得有效的 python 代码。它会转换为类似于以下内容的内容:

import PyReact
from my_awsome_project.components import Logo, Link


def Header(context):
    page_title = context.get('page_title')
    links = context.get('links')

    return (
        PyReact.createComponent('div', context, [
            PyReact.createComponent(Logo),
            page_title,
            PyReact.createComponent('ul', context, [
                 [
                      PyReact.createComponent(Link, {'href': link.url}, link.title)
                      for link in links
                 ]
            ]),
        ])
    )

问题是:我将如何编写这样的转译器?

我们还考虑过,我们可以 return 一个包含独立解析的 JSX 的字符串,而不是直接将 JSX 直接嵌入到 python 代码中。那会是 better/easier 方法吗?

我认为这对 SO 来说基本上是一个太宽泛的问题,任何答案都将在 SO 指导意见的边缘滑行。您本质上是在寻求有关复杂问题的设计建议,而 SO 并非真正用于该目的。

不过,这是一个有趣的问题。我将尝试解决这些问题,但不会太深入自以为是的设计(因为我对这个问题确实有意见)。

  1. 转译是实用的,至少在理论上是这样,如果你能实现它,它会给你合理的性能。

  2. 重复解析模板字符串让我觉得效率低下且复杂;复杂性与评估嵌入式 Python 代码有关,您将希望在定义字符串文字的范围内执行此操作,这可能不是它被解析的范围。

  3. JSX-style 词法分析和语法分析并不是特别复杂,但是您假设的转译器还需要理解 Python 词法和句法分析。 Python 的标准库包括用于词法分析和解析的模块 Python 但据我所知,它们是不可扩展的,这可能使得难以利用它们与嵌入式语言一起使用。您可以编写自己的词法分析器和解析器,可能使用您选择的代码生成器,或者您可以将词法分析器和解析器基于某些开源 Python 实现。在这两种情况下,您的可维护性挑战将是使您的自定义代码与未来的 Python 版本保持同步。

  4. 将 pseudo-HTML 嵌入另一种语言的主要问题是检测 < 何时是比较运算符以及何时启动模板。最简单的解决方案是仅当 < 被词法分析为一个完整的标记(因此 <= 始终是一个运算符),后跟一个标识符,并且在句法环境中遇到时才允许模板其中需要一个表达式。

  5. 上面的最后一个要求是确保 3 < count(例如)不会让转译器认为它即将看到一个 <count...> 组件。我很确定在 Python 中你可以使用基于前面标记的简单词法规则,但是需要完整的语法分析来验证

  6. 模板启动后,它将一直持续到您到达匹配的结束标记为止;如果需要标签匹配,那就很简单了。但是它比bottom-up解析更适合top-down解析,因为end-tag匹配是context-sensitive。如果您在词法分析和句法分析之间进行密切合作,这很容易做到,但这种合作有时会让人不悦:-)

  7. 由于嵌入在模板中的 Python 代码本身可以包含一个嵌入模板,而该模板又可以嵌入更多 Python 代码等,因此您的分析需要递归。预期的递归深度不是很大,所以递归 本身 没有问题,但是许多解析器生成器并不能很好地处理这种递归。我建议使用(或实现)一个 "push-parser" 和一个与缓冲区处理程序分开的词法分析器框架,这样您就可以轻松地在缓冲区中间更改扫描器。

  8. 缓冲区处理可以非常简单;最低要求只是一个字符串和该字符串的索引。如果您在缓冲区处理程序中隔离实现细节,您应该能够在以后更改为不同的实现,例如不需要在开始解析之前让整个输入可用的实现。您可能实际上并不需要该功能,但维护独立组件总是好的,以防万一

  9. 您的转译器面临的另一个挑战是将其与 Python 的模块系统集成。 Pythonic 集成可能建议在导入模块时执行转译。另一方面,您可能希望能够分发一个 pre-transpiled 包,它可以在不安装转译器的情况下使用,并且不依赖于转译器的特定版本。如果您花一些时间考虑清楚这一点,您可能会避免以后出现问题。 (例如,Ply 问题导致无法将 Ply 项目捆绑到 single-file 分发系统中。)

希望对您有所帮助。

实际上,这是可能的。有一个库叫做:packed[1]。但似乎该项目已被放弃,最后一次提交是 5 年前。引用其自述文件:

@packed
def tag(self):
    share = get_share_link()
    return <a href={share}>Share on internet</a>

# to:

@packed
def tag(self):
   share = get_share_link()
   return Elem(
        'a',
        {
            'href': share,
        },
        'Share on internet',
    )
  1. https://github.com/michaeljones/packed

你可以查看我的包裹:https://pypi.org/project/html2js/
安装它:pip install Html2js>=1.4

from  Html2js import  html2js
html  = """<div style="margin-top:-20px; letter-spacing:3px;font-size:45px" class="sfbg">ISSAM</div>"""
print(html2js(html,root = "document.body"))

这将输出以下代码:

var itm_139927509068160 = document.createElement("div");
itm_139927509068160.setAttribute("style","margin-top:-20px; letter-spacing:3px;font-size:45px");
itm_139927509068160.classList.add("sfbg");
var itm_139927509052800 = document.createTextNode("ISSAM");
itm_139927509068160.appendChild(itm_139927509052800);
document.body.appendChild(itm_139927509068160);