ウィジェットやその他の場所での h (createElement) 関数に代わる JSX

こんにちは、テーマの開発者体験を向上させるために、ウィジェットで JSX を実験しています(ヘッダー.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-L149transform-react-jsx を追加し、https://github.com/discourse/discourse/blob/master/vendor/assets/javascripts/babel.js で Babel 設定を公開して jsxPragma を変更することで、これが実現できるはずです。

最初の部分は既に実装済みですが、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: 。機能制限のある hbs ではなく、実際の JavaScript である JSX の力を活用したいと考えています。

Discourse テーマで JSX を使えるようにする方法を見出しました。ただし、これは私の独自フォークの Discourse でのみ動作しており、一般的なテーマ開発にはあまり役立ちません。

最終的には @eviltrout の判断によります。ただ、私の考えをお伝えしますと、JSX は使いやすく、Discourse に導入することも可能ですが、それは実現しないでしょう。なぜなら、JSX をサポートする必要があるからです。また、私たちはオプションを増やすよりも、将来的にウィジェットを廃止する方向に傾いています。

ご指摘の点は完全に理解できます。ただ、なぜテーマやプラグインの開発者が Babel(および他の多くの機能)の設定を自由にできるような仕組みにしないのでしょうか?

例えば、テーマフォルダ内に .babelrc ファイルを置くことで、JS トランスパイラへの追加オプションを指定できるようにするのです。もし .babelrc が存在すれば、discourse_js_processor がそれを読み取り、Babel の設定を拡張して、さまざまな追加的な JS 機能を利用できるようにするといった具合です。

単なるアイデアですが。

広義の「パブリック API」という観点から言えば、私たちが許可するものはすべて維持する責任が生じます。今日サポートが容易に見えるものでも、明日には難しくなる可能性があります。最近、私たちはパイプライン内の多くの機能やアプリのクライアント側部分で実験を続けています。ロビンがこれらについてどう考えるか、様子を見てみましょう。

ご遠慮なくお尋ねください。私は単に好奇心旺盛な開発者で、いくつか質問があります。私はフルスタックJS(Node/Vue)の世界からDiscourseに来ました。そこではEmberは選択肢ではなく、特にReactや、さらに優れたVueがある場合です。

Emberは非常に不自然に感じられ、handlebarsは機能の面で時代遅れです。JSXのテンプレート機能、特にVue SFCのテンプレート機能は、hbsのような基本的なものとは比較できません。

Ember がフルスタック JS の選択肢ではないという意見には同意できません。Ember は他のフレームワークほど人気がなく、その理由で別のフレームワークを選ぶべきだという議論は理解できますが、バックエンドで Node を、フロントエンドで Ember を使用しているサイトは確かに多数存在します。そもそも Ember のすべてのツールは Node に基づいています。

とは言え、JSX は Discourse コア今後のロードマップには含まれていません。ただし、プラグインで JSX を使用できるようにするための改善策があれば、検討する用意はあります。どのように実装するか提案をお願いします!

確かに選択肢の一つではありますが、私の開発経験や周辺環境を考慮すると、それは現実的な選択肢ではありません。個人的には、Vue がこのようなアプリケーションには自然な選択だと考えています。

少し背景を説明します。

過去数ヶ月、クライアント向けにかなりカスタマイズされたテーマの開発に従事しており、その過程で Discourse の内部仕組みについて多くを学びました。ただし、それは全体のごく一部に過ぎないかもしれません。

現在は、初心者の頃の間違いをリファクタリングし、将来的に私のコードを保守することになる同僚にとって可能な限り扱いやすいものにするよう努めています。そのため、JSX に関するいくつかの実験を行いました。

今日は、トピックの最初の投稿で示されたアプローチを試しました。具体的には、transform-react-jsx を以下の場所に追加しました:https://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 処理されたコード内で React.createElement の代わりに virtual-domh 関数を使用できるようになりました。h は Discourse に組み込まれており、これは自然な選択だと感じました。

Discourse の開発の歴史については詳しくありませんが、多くの便利なプラグインを備えたフル機能の babel.js ファイルを追加したのには、おそらく理由があるのでしょう。残念ながら、私のようなエンドユーザーのデベロッパーにはこれらのプラグインを利用できません。そこで、以下の提案を行います:

  1. 何らかの Babel 設定(.babelrc、.babel.yml、あるいは about.json 内に package.json のような 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 はテンプレート言語ではなく JS そのものだということです。JS であるがゆえに、より強力になります(例えば、三項演算子や map 関数など)。Ember/Handlebars でも何でも作れますが、個人的な意見としては、JSX の方が便利で開発者に優しいです。

それは機能しましたか?JSX を追加でき、すべて問題なく動作しましたか?

さて、あなたが以前に抱いていた「なぜ Babel がそのような設定になっているのか」という疑問についてですが、Discourse はアセットパイプラインを使用する Rails ベースで構築されています。Webpack のサポートが Rails に追加されたのはバージョン 6 からです。Discourse のコードベースは 8 年前に作られたものです!そのため、時間の経過とともに機能を追加してきましたが、その限界を超えてしまいました。

今年の大きなプロジェクトの一つは、Ember CLI への移行です。コミット履歴をご覧いただければ、この目標に向けた作業が Discourse に行われていることがお分かりいただけるでしょうが、これは長い道のりです。

それが完了すれば、現在 Ruby 内で実行しなければ動作しない JavaScript ビルド機能を多く利用できるようになります。Ruby での実行は手間がかかります。当面の間、package.json を読み込んでそれを自動的に Rails パイプラインに組み込むことにリソースを割くことは考えていません。

その仕組みを実証するための POC PR を作成します。

貴重な洞察とご説明をありがとうございます!

アップデートのお知らせ — ThemeField 用の Babel を作成できました。これにより、テーマから babel.config.json を読み取り、discourse_js_processor 内の Babel.transform にプラグインを注入できます。

すぐにプルリクエストを提出して、概念実証として示す予定です。ただし、かなりごちゃごちゃになることをご了承ください :sweat_smile:

追伸:Ruby は作業が非常に簡単です :star_struck:

こちらです: