在 widgets 和其他地方使用 JSX 而非 h (createElement) 函数

你好,我一直在主题中尝试使用 JSX 以改善开发体验(我设计了一套自己的主题开发方案,无需 header.html 等文件,详情将在另一个主题中讨论)。

我想知道为什么 Discourse 在其代码库中没有利用 Babel 插件。在 Discourse 中,我们已有可用的 transform-react-jsx Babel 插件,可通过 jsxPragma 选项将 JSX 转换为自定义的 createElement 函数。所有这些功能在 Discourse 中都是可用的。

如果在 https://github.com/discourse/discourse/blob/master/lib/discourse_js_processor.rb#L143-L149 处添加 transform-react-jsx,并以某种方式暴露 Babel 配置以更改这里的 jsxPragma(见 https://github.com/discourse/discourse/blob/master/vendor/assets/javascripts/babel.js),就能实现这一目标。

我已经完成了第一部分,但 pragma 部分仍需进一步探索。不幸的是,目前没有 .babelrc 文件可用于配置各个插件。因此,我得到的是 React.createElement 函数,而不是自定义函数。

关于在不使用 React 的情况下使用 JSX,可参考:Using jsx WITHOUT React | r0b blog

大家有什么想法,或者能否提供如何实现这一目标的帮助?

更新:
我成功通过修改 vendor/assets/javascripts/babel.js 第 26586 行的 pragma 设置实现了该功能:

var id = state.opts.pragma || "React.createElement";
// 修改为
var id = state.opts.pragma || "h";

你看到这个了吗?如果你不喜欢 Hyperscript,现在可以使用模板了:

@merefield 是的,我知道 hbs,但我并不是“典型的 Discourse 主题开发者” :slight_smile: 。我希望利用 JSX 的强大功能,因为它本质上是 JavaScript,而 hbs 在功能上存在局限。

我确实已经在我的 Discourse 分支中成功使用了 JSX。但这仅适用于我个人的分支,对通用的主题开发帮助不大。

最终这取决于 @eviltrout 的决定。不过我可以分享一下我的看法。是的,JSX 用起来确实很愉快,我们本可以将其引入 Discourse,但我认为我们不会这样做,因为我们必须为此提供支持,而我们目前的倾向是在某个时候逐步淘汰小部件,而不是增加更多选项。

我完全理解你的观点。我只是在想,为什么不向主题和插件开发者开放 Babel(以及许多其他工具)的配置呢?

例如,主题文件夹中的 .babelrc 文件可以用于为 JavaScript 转译器添加一些额外选项。如果存在 .babelrc 文件,discourse_js_processor 就可以使用它并扩展其 Babel 配置,从而启用许多额外的 JavaScript 功能。

只是一个想法。

这仅仅是广义上“公共 API

请别感到有压力,我只是一个有些好奇的开发者,有几个问题想请教。我是从全栈 JavaScript(Node/Vue)领域来到 Discourse 的,在那里 Ember 根本不是一个选项,尤其是当你有 React,或者更好的 Vue 可选时。

Ember 感觉非常不自然,而且 handlebars 的功能已经过时了。JSX 的模板能力,尤其是 Vue 的单文件组件(SFC),是像 hbs 这样基础的东西无法比拟的。

我不同意 Ember 不是全栈 JavaScript 的选项。我理解有人主张 Ember 不如其他框架流行,因此你可能出于这个原因选择其他框架,但确实有许多网站在后端使用 Node,在前端使用 Ember。毕竟,Ember 的所有工具都是基于 Node 构建的。

话虽如此,JSX 不在 Discourse 核心功能的未来规划中,但如果我们能通过某些方式使其在插件中得以使用,我很乐意考虑。请提出一个关于如何实现的具体方案!

这确实是一个选项,但在我的开发实践和相关工作中并非如此。就我个人而言,我不会选择它,因为 Vue 看起来是这类应用更自然的选择。

一点背景信息:

在过去的几个月里,我一直在为一个客户开发一个高度定制的主题,期间我学到了很多关于 Discourse 内部运作机制的知识,但这可能只是很小的一部分。

目前,我正在重构一些初学者的错误,并努力让代码对将来可能接手维护的同事尽可能友好。因此,我针对 JSX 做了一些实验。

今天,我尝试了该主题帖首帖中的方法。我在此处包含了 transform-react-jsxhttps://github.com/discourse/discourse/blob/master/lib/discourse_js_processor.rb#L143-L149:

      if opts[:module_name] && !@skip_module
        filename = opts[:filename] || 'unknown'
        "Babel.transform(#{js_source}, { moduleId: '#{opts[:module_name]}', filename: '#{filename}', ast: false, presets: ['es2015'], plugins: [['transform-es2015-modules-amd', {noInterop: true}], 'transform-decorators-legacy', 'transform-react-jsx', exports.WidgetHbsCompiler] }).code"
      else
        "Babel.transform(#{js_source}, { ast: false, plugins: ['check-es2015-constants', 'transform-es2015-arrow-functions', 'transform-es2015-block-scoped-functions', 'transform-es2015-block-scoping', 'transform-es2015-classes', 'transform-es2015-computed-properties', 'transform-es2015-destructuring', 'transform-es2015-duplicate-keys', 'transform-es2015-for-of', 'transform-es2015-function-name', 'transform-es2015-literals', 'transform-es2015-object-super', 'transform-es2015-parameters', 'transform-es2015-shorthand-properties', 'transform-es2015-spread', 'transform-es2015-sticky-regex', 'transform-es2015-template-literals', 'transform-es2015-typeof-symbol', 'transform-es2015-unicode-regex', 'transform-regenerator', 'transform-decorators-legacy', 'transform-react-jsx',exports.WidgetHbsCompiler] }).code"
      end

并修改了 vendor/assets/javascripts/babel.js 第 26586 行的 pragma 设置:

var id = state.opts.pragma || "React.createElement";
// 改为
var id = state.opts.pragma || "h";

这使得我能够在经过 Babel 处理的代码中使用来自 virtual-domh 函数,而不是 React.createElementh 是 Discourse 的一部分,这感觉非常自然。

我不了解 Discourse 的开发历史,你们在代码中加入包含大量实用插件的完整 babel.js 文件肯定有你们的理由。遗憾的是,这些插件无法像我这样的最终开发者使用。因此,我的建议是:

  1. 使用某种 Babel 配置文件,如 .babelrc.babel.yml,或者在 about.json 中定义一个类似 package.json 中的 Babel 配置字段(参考:Configure Babel · Babel babel.js 中的所有这些插件(或者可能是其他插件,见第 3 点)。

  2. discourse_js_processor.rb#babel_parsehttps://github.com/discourse/discourse/blob/master/lib/discourse_js_processor.rb#L138)中读取此配置,并将其与当前配置合并。

  3. 如果开发者希望拥有自定义的 Babel 插件,可以将其添加到 vendor/assets/javascripts/babel/plugins 文件夹(或仅仅是 babel/plugins 文件夹)中,Discourse 将自动识别并在转译过程中使用它。

我不熟悉 Rails 后端开发,所以这只是一个初步的提议。

另外还有一个问题:既然提到了,Discourse 读取主题 package.json 中的依赖项并在首次运行时安装它们,然后在主题内部使用(例如进行缓存)会有多困难?(如果依赖项发生变化,检查哪些发生了变化并安装或移除它们等)。

再次强调,你们的工作非常出色,成果令人惊叹。请不要感到来自我的任何压力,我只是来这里学习,进行有益的讨论,分享观点和想法。我并不期望你们会实施我的请求或做任何其他事情。

你这是什么意思?在 Handlebars 和 Ember 中有哪些功能无法实现,却在其他框架中已经实现了?

我想表达的是,JSX 本身就是 JavaScript,而非模板语言。正因为它本质上是 JS,所以功能更强大(例如:三元运算符、map 函数等)。虽然在 Ember/Handlebars 中也能实现各种功能,但在我看来,JSX 更加便捷且对开发者更友好。

那它奏效了吗?你能够添加 JSX 并且一切正常吗?

现在来回答你之前关于 Babel 为何如此配置的问题:Discourse 是基于 Rails 构建的,并使用了资源管道(asset pipeline)。Webpack 支持直到 Rails 6 版本才被引入。Discourse 的代码库已有 8 年历史!因此,随着时间的推移,我们不断为其添加新功能,但也逐渐超出了其原有限制。

今年的一个重大项目是迁移到 Ember CLI。如果你查看我们的提交记录,会看到为实现这一目标而进行的工作正在逐步融入 Discourse,但这将是一段漫长的旅程。

一旦完成迁移,你将能够使用更多目前无法直接使用的 JavaScript 构建功能,除非通过 Ruby 运行它们,而这非常繁琐。在此期间,我们不太可能投入时间去解析 package.json 并让其自动集成到 Rails 管道中。

我将提交一个概念验证(POC)拉取请求来演示其工作原理。

感谢你的见解和解释!

更新一下——我成功制作了一个 ThemeField Babel 插件,用于从主题中读取 babel.config.json,并将插件注入到 discourse_js_processor 中的 Babel.transform

我希望能尽快提交这个拉取请求,以展示一个概念验证。温馨提示:代码会非常混乱 :sweat_smile:

附:Ruby 用起来太顺手了 :star_struck:

在这里:

https://github.com/discourse/discourse/pull/9729