위젯 및 기타 위치에서 h(createElement) 함수 대신 JSX 사용

안녕하세요, 테마 개발 시 개발자 경험을 향상시키기 위해 위젯에서 JSX를 실험하고 있습니다. (header.html 같은 파일 없이 테마 개발을 위한 자체 세팅을 만들었는데, 이 부분은 다른 토픽에서 더 자세히 다룰 예정입니다.)

Discourse 코드베이스에서 왜 babel 플러그인을 활용하지 않는지 궁금합니다. Discourse에는 transform-react-jsx babel 플러그인이 사용 가능하며, jsxPragma 옵션을 사용해 JSX를 사용자 정의 createElement 함수로 변환할 수 있습니다. 이 모든 것이 Discourse에서 이미 사용 가능한 상태입니다.

여기에 transform-react-jsx를 추가하고 https://github.com/discourse/discourse/blob/master/lib/discourse_js_processor.rb#L143-L149
그리고 Babel 설정을 노출하여 jsxPragma를 변경할 수 있게 하면 https://github.com/discourse/discourse/blob/master/vendor/assets/javascripts/babel.js
이것이 가능해질 것입니다.

첫 번째 부분(플러그인 추가)은 이미 구현했으며, 프라그마 부분은 아직 탐색이 필요합니다. 불행히도 개별 플러그인 설정을 위한 .babelrc 파일이 없습니다. 그 결과 사용자 정의 함수 대신 React.createElement 함수가 반환되고 있습니다.

React 없이 JSX를 사용하는 방법에 대한 더 자세한 내용은 여기에서 확인하세요: Using jsx WITHOUT React | r0b blog

이것을 어떻게 달성할 수 있을지에 대한 의견이나 도움이 있다면 알려주시겠습니까?

업데이트:
vendor/assets/javascripts/babel.js의 26586번 줄에서 프라그마 설정을 변경하여 이 문제를 해결했습니다.

var id = state.opts.pragma || "React.createElement";
// 아래로 변경
var id = state.opts.pragma || "h";

이거 보셨나요? Hyperscript가 마음에 안 드신다면 이제 Templates를 사용할 수 있습니다:

@merefield 네, hbs에 대해 알고 있습니다. 하지만 저는 "전형적인 discourse 테마 개발자"는 아니에요 :slight_smile: . hbs는 기능이 제한적이기 때문에, 실제로는 JS인 JSX의 힘을 활용하고 싶습니다.

드디어 discourse 테마에서 JSX를 사용하게 되었습니다. 다만 제 자신의 discourse 포크에서 했기 때문에, 일반적인 테마 개발에는 크게 유용하지는 않습니다.

결국 @eviltrout의 판단에 달려 있습니다. 다만 제 개인적인 의견은 말씀드릴 수 있겠네요. JSX는 사용이 편할 수 있고 Discourse에 도입할 수도 있지만, 도입하지 않을 것 같습니다. 그 이유는 JSX를 지원해야 하기 때문이고, 우리는 새로운 옵션을 추가하기보다는 언젠가 위젯을 제거하는 방향으로 더 기울어져 있기 때문입니다.

말씀하신 내용은 충분히 이해합니다. 다만, 왜 babel(그리고 다른 여러 설정들)을 테마 및 플러그인 개발자에게 개방하지 않는지 궁금합니다.

예를 들어, 테마 폴더에 .babelrc 파일을 두면 JS 트랜스파일러에 추가 옵션을 지정할 수 있습니다. .babelrc가 존재하면 discourse_js_processor가 이를 활용하여 babel 설정을 확장하고, 추가적인 JS 기능을 활성화할 수 있습니다.

단순히 아이디어를 제시해 본 것입니다.

이것은 넓은 의미에서 '공개 API’의 문제입니다. 우리가 허용하는 것은 결국 우리가 감당해야 하는 것이기 때문입니다. 오늘 지원하기 쉬워 보이는 것이 내일에는 어려울 수 있습니다. 최근 파이프라인과 앱의 클라이언트 측 부분에서 많은 실험을 하고 있습니다. 로빈이 이 모든 것에 대해 어떻게 생각하는지 지켜보겠습니다.

부담 갖지 마세요. 저는 그저 궁금한 점이 있는 개발자일 뿐입니다. 저는 Node/Vue 기반의 풀스택 JS 세계에서 왔는데, 그 환경에서는 Ember는 선택지가 아니에요. 특히 React나 더 나아가 Vue가 있을 때 말입니다.

Ember은 매우 부자연스럽게 느껴지고, handlebars의 기능은 이제 구식이 되었습니다. JSX의 템플링 기능, 특히 Vue SFC의 강력함은 hbs 같은 기본적인 것과 비교할 수조차 없습니다.

Ember가 풀스택 JS에 적합하지 않다는 데에는 동의하지 않습니다. Ember가 다른 프레임워크만큼 인기가 많지 않다는 논리는 이해하지만, 그 이유 때문에 다른 프레임워크를 선택하고 싶을 수 있다는 것도 압니다. 하지만 백엔드에 Node를 사용하고 프론트엔드에 Ember를 사용하는 사이트가 실제로 매우 많습니다. 결국 Ember의 모든 툴링은 Node 기반으로 구축되어 있기도 하니까요.

다만, JSX는 Discourse 코어에 포함될 계획은 없습니다. 하지만 플러그인에서 JSX를 사용할 수 있도록 도와줄 수 있는 방법이 있다면 그 아이디어에는 열려 있습니다. 어떻게 작동할지 제안서를 작성해 주세요!

Ember는 하나의 선택지이긴 하지만, 제가 개발하고 그 주변에서 일하면서 느낀 바로는 그렇지 않습니다. 또한 저는 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-dom의 h 함수를 사용할 수 있게 되었습니다. h는 Discourse의 일부이며, 이는 자연스러운 선택처럼 느껴졌습니다.

저는 Discourse 개발의 역사를 잘 모르고, 여러분은 코드에 유용한 플러그인들이 가득한 완전한 babel.js 파일을 추가하는 데 그만한 이유가 있었을 것입니다. 하지만 이러한 플러그인들이 저와 같은 최종 개발자 사용자에게 제공되지 않는다는 것은 아쉽습니다. 따라서 제 제안은 다음과 같습니다:

  1. .babelrc, .babel.yml 또는 about.json과 같은 Babel 설정 파일을 사용하거나, package.json에서처럼 Configure Babel · Babel Babel 설정 필드를 정의합니다. 이 설정은 babel.js 내의 모든 플러그인(또는 3번 항목에서 확인하듯 다른 플러그인들)을 참조할 수 있습니다.

  2. discourse_js_processor.rb#babel_parse에서 https://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 자체라는 것입니다. JSX 자체가 JS이기 때문에 더 강력합니다(예: 삼항 연산자, map 함수 등). Ember/Handlebars에서는 원하는 것을 무엇이든 만들 수 있지만, 제 생각에는 JSX가 더 편리하고 개발자 친화적입니다.

그리고 그게 잘 작동했나요? jsx를 추가하고 모든 것이 잘 되었던 건가요?

이제 babel이 왜 그렇게 설정되어 있는지 묻던 다른 질문들에 대해 말씀드리자면, Discourse는 Rails와 자산 파이프라인(asset pipeline)을 기반으로 구축되었습니다. webpack 지원은 Rails 6 버전에서야 비로소 추가되었죠. Discourse의 코드 베이스는 8년 전 것입니다! 그래서 시간이 지나면서 기능들을 추가해 왔지만, 원래 시스템의 한계를 벗어난 상태입니다.

올해의 주요 프로젝트 중 하나는 Ember CLI로의 마이그레이션입니다. 커밋 내역을 확인해 보시면 Discourse로 이 목표를 향한 작업이 진행되고 있는 것을 보실 수 있지만, 아직 갈 길이 멉니다.

그 작업이 완료되면, 현재는 Ruby 안에서 실행해야만 작동하는(매우 번거로운) 많은 JavaScript 빌드 기능을 자유롭게 사용할 수 있게 될 것입니다. 그 사이에 package.json을 읽어들이고 그것을 자동으로 Rails 파이프라인에 반영하는 작업에 시간을 할애하는 것은 현실적으로 어렵다고 봅니다.

그것이 어떻게 작동하는지 보여주는 POC PR을 만들겠습니다.

통찰력과 설명에 감사합니다!

업데이트 - 테마에서 babel.config.json을 읽고 discourse_js_processor의 Babel.transform에 플러그인을 주입하는 데 사용되는 ThemeField babel을 만들었습니다.

개념 증명(PoC)을 보여주기 위해 PR을 빨리 올리고 싶습니다. 경고 - 엄청나게 엉망이 될 것입니다 :sweat_smile:.

PS. Ruby는 다루기가 정말 쉽네요 :star_struck:

여기 있습니다: