Mathjax를 4버전으로 업그레이드하기

@sam 및 Discourse에서 수식을 입력하는 데 관심이 있는 모든 분들에게. discourse-math 플러그인을 업데이트하여 훨씬 느리고 매우 구식인 V2 대신 MathJax V3를 사용하도록 변경했습니다. 예상대로, KaTeX와 비교했을 때 풍부한 기능을 유지하면서도 훨씬 더 빠르고 매끄러운 사용자 경험을 제공합니다.

결과가 좋다고 생각하시면 풀 리퀘스트를 제출하고 싶습니다.


제 클래스 Discourse 사이트에서 실제로 동작하는 모습을 볼 수 있습니다:

https://discourse.marksmath.org/

해당 사이트의 대부분의 콘텐츠는 비공개 또는 미상장 상태입니다. MathJax V3 카테고리 상단에 아이디어를 보여주는 여러 토픽이 있을 것입니다.

플러그인 코드는 이 독립형 discourse-mathjax 플러그인 저장소에서 확인하실 수 있습니다. 수정이 가장 많이 이루어진 파일은 초기화 파일입니다.

해당 저장소를 사용하여 현재 독립형 사이트에 바로 설치할 수도 있습니다. 설치 시 기존 저장소를 제거하는 것을 잊지 마세요. 따라서 표준 플러그인 설치 방법을 다음과 같이 수정해야 합니다:

hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - rm -r discourse-math
          - git clone https://github.com/discourse/docker_manager.git
          - git clone https://github.com/mcmcclur/discourse-math.git

코멘트

MathJax의 최신 버전은 실제로 4.0.0입니다. 여러 이유로 V3.2.2를 선택했습니다.

  • V4는 V2보다 확실히 훨씬 빠르지만, V3만큼 빠르지는 않습니다.
  • V4에서는 사용자 경험이 약간 다릅니다. 특히 사용자가 출력을 클릭할 때 그렇습니다.
  • 4.0.0의 상태가 어떤지 궁금해지면서 버그가 얼마나 많을지 의문이 듭니다.

그럼에도 불구하고, V4의 API는 V3와 동일합니다. 최신 MathJax 저장소를 단순히 교체하는 방식으로 나중에 업그레이드할 수 있을 것입니다.

locales/server.en.yml 파일에서 작은 변경 사항을 하나 수행해야 했습니다. 물론 다양한 언어를 위한 이러한 파일이 훨씬 더 많이 있습니다. 제 이해로는 다른 언어 파일들은 나중에 자동으로 번역될 것입니다.

저는 채팅을 거의 사용하지 않으며, 해당 컨텍스트에서 테스트해 본 적도 없습니다.

MathJax를 V3로 업그레이드하는 Pull Request를 생성했으며, 모든 테스트가 통과했습니다!

관련 링크:

정말 훌륭합니다 :hugs: , 하지만 이걸 기회로 삼아 저장소를 조금 정리할 수 있지 않을까 생각합니다.

이제 mathjax를 코어로 이동했으므로, pnpm을 사용하여 패키지를 가져오고 FullCalendar처럼 소스 전체를 번들링하는 것을 피할 수 있습니다.

특히 저장소에는 "링크"만 유지하고, 빌드 프로세스를 통해 올바른 의존성을 가져오도록 하는 것이 목표입니다.

여기서 개발 경험 팀과 상의할 수 있도록 며칠만 시간을 주십시오. 이 일에 기울여 주신 노력에 진심으로 감사드립니다!

네, 분명히 올바른 접근이라고 생각합니다. 왜 전체를 패키징했는지 늘 궁금했거든요!

그러면 라이브러리에 MathJax를 로드하기 위해 loadMathJax 함수를 만들어 사용하게 될까요?

모든 플러그인을 코어로 통합하면서 플레이하기가 조금 더 까다로워졌다고 말씀드리고 싶네요. 의존성을 빌드 과정에 묶으면 더 어려워질 수 있지만, MathJax나 FullCalendar를 CDN에서 가져오는 것은 문제없을 것 같습니다.

주로 제 자체 포럼에서 플러그인을 만지작거릴 때의 이야기인데, 빌드 시 MathJax를 가져오는 것이 맞다고 생각합니다.

물론이죠! 수년간 Discourse를 사용해 왔는데, 이 기능이 훌륭하다고 생각하신다니 정말 기쁩니다! :rocket:

네, 정확합니다. 참고할 만한 좋은 예시는 morphlex입니다:

개발자 경험 팀과 논의해 보셨는지 궁금합니다. 도움이 될 수 있다면 기꺼이 도와드리겠습니다. 다만, 그 부분에 대한 피드백이 없으면 제가 할 수 있는 일이 거의 없을 것 같다는 인상을 받습니다.

별개의 브랜치에서 몇 가지 추가 변경 사항을 넣었는데, 곧 공유할 예정입니다. 바쁘신 걸 잘 알고 있으니 부담스러우셨으면 죄송합니다!

discourse-math 플러그인을 수정하여 훨씬 더 많은 수학적 입력을 파싱할 수 있도록 했습니다.

@sam 2017년에 이 플러그인에 처음 기여했을 때, 매우 엄격한 파싱을 원하셨던 기억이 납니다. 파싱을 완화하고 확장한 주요 동기가 AI와의 호환성을 높이기 위해서였다는 점을 미리 말씀드립니다. 특히 AI 봇과 수학에 대해 대화할 때, AI가 LaTeX를 사용하여 응답하는 경우가 많으며, LaTeX 입력을 구분하는 방식도 다양합니다. 따라서 엄격한 파싱에 대한 의도는 이해하지만, 제가行った 변경 사항은 해당 사용 사례에서 상당히 필수적입니다.

물론 그 사용 사례를 중요하게 생각하지 않으실 수도 있으므로, V3 풀 리퀘스트와 별도로 별도의 브랜치에 변경 사항을 넣었습니다. 마음에 드시면 다른 풀 리퀘스트를 발행하는 데 기꺼이 응하겠습니다.

풀 리퀘스트에 대한 구체적인 변경 사항은 다음과 같습니다:

\(a^2+b^2=c^2\)와 같이 슬래시-괄호로 구분된 인라인 수학을 허용합니다.

$$a^2+b^2=c^2.$$와 같이 단일 줄 이중 달러로 구분된 디스플레이 수학을 허용합니다.

\[a^2+b^2=c^2.\]와 같이 단일 줄 슬래시-괄호로 구분된 디스플레이 수학을 허용합니다.

\[
a^2+b^2=c^2.
\]와 같이 다중 줄 슬래시-괄호로 구분된 디스플레이 수학을 허용합니다.

물론, 원래의 입력도 여전히 허용합니다:

달러로 구분된 인라인 수학: $a^2+b^2=c^2$.

다중 줄, 이중 달러로 구분된 디스플레이 수학:
$$
a^2+b^2=c^2.
$$

관련 브랜치를 여기서 확인하실 수 있습니다.

코드는 또한 독립적인 플러그인으로 존재합니다.

아, 실제로 동작하는 모습도 볼 수 있습니다!

@mcmcclur 수고 많으셨습니다. 이 기능들이 코어에 포함되면 좋겠습니다.

마크, 정말 감사합니다.

제가 현재 가장 큰 걸림돌은 종속성 분배를 위한 새로운 패턴으로 전환하는 것입니다. 관련 링크는 다음과 같습니다:

https://meta.discourse.org/discourse-ai/ai-bot/shared-ai-conversations/gXnFaLtodVQCzx8l0lSsQg

이 부분을 검토해 주실 수 있을까요?

완화된 구문에 대해서는 사이트 설정으로 두는 것이 적절하다고 생각됩니다. 현재 나와 있는 LLM들을 고려하면 기본값으로 켜 두는 것도 괜찮지 않을까요?

@mcmcclur 오늘 이걸로 좀 만져봤어요:

아직 멀었지만, 4.1로 대략 부팅이 되는 건 좋은 점이에요.

네, 확실히 진전이 있습니다!

제가 추측하길, 이미 알고 계실 첫 번째 핵심 문제는 폰트가 찾지지 않는다는 것입니다. 실제로 저는 discourse-math-mathjax.js의 다음 줄을 만져보았습니다:

fontURL: getURLWithCDN("/assets/mathjax/woff-v2"),

테스트로 URL을 제 웹서버의 임시 디렉토리를 가리키도록 설정해 보았는데, 초기 결과는 매우 좋습니다. 따라서 문제는 Discourse에 해당 폰트를 올바르게 설치하는 것입니다.

제 머신에서 간단한 pnpm 프로젝트에서 다음 명령어로 폰트를 설치할 수 있습니다:

pnpm install @mathjax/mathjax-newcm-font@4

discourse/frontend/discourse 내에서 이 명령어를 실행하면 폰트가 다음 위치에 나타납니다.

/discourse/frontend/discourse/npm_modules/@mathjax/mathjax-newcm-font/chtml/woff2/

그러나 빌드 후 /assets/mathjax/woff-v2에 해당 폰트가 저장되지 않는 것 같습니다. 디렉토리에 대해 여러 가지 변형을 시도해 보았지만 작동하지 않았습니다. 이것은 제가 전문가가 아닌 어떤 종류의 라우팅 마법이라고 추정합니다. 그 경로 문제가 해결되면 정리 작업을 위해 상당한 진전을 이룰 수 있다고 확신합니다.

@sam 이 작업에서 꽤 상당한 진전을 이룬 것 같습니다. 다만, 중요한 주의사항이 하나 있습니다. 원하는 컴포넌트를 어디서 로드해야 하는지 확실하지 않습니다. 코드로 표현하면 다음과 같습니다.

window.MathJax = {
    loader: {
      // This does not work:
      // paths: { mathjax: getURLWithCDN("/assets/mathjax") },
      // But this works great:
      paths: { mathjax: "https://cdn.jsdelivr.net/npm/mathjax@4.1.0" },
      load: ["core", "input/tex", "input/mml", "output/chtml", "output/svg"],
    },
    // More configuration ...
  };

주석 처리된 버전이 작동하지 않는다고 할 때, 저는 명시적인 메시지인 다음을 받는다는 의미입니다:
MathJax(core): Can’t load “/assets/mathjax/core.js”

참고로, 두 경우 모두 loadMathJax 함수가 로컬 복사본에서 MathJax 시작 모듈을 가져오고 있습니다. 즉,
/discourse/frontend/discourse/app/static/mathjax-bundle.js
에 다음이 있습니다.

export * from "mathjax/startup.js";

그런 다음,
/discourse/frontend/discourse/app/lib/load-mathjax.js
에 정의된 loadMathJax는 다음을 호출합니다.

const bundle = await import("discourse/static/mathjax-bundle");

이것은 몇 가지 가능성을 시사합니다:

  1. /assets/mathjax가 올바른 위치가 아니거나
  2. dist에 표시되도록 이러한 자산이 어떤 방식으로 등록되어야 할 수도 있습니다.

CDN 버전을 기준으로 하면 상당한 진전을 이룰 수 있을 것으로 보이지만, 그것이 당신에게는 큰 장애물이 될 것이라고 생각합니다.

원하시면 제 코드를 공유할 수 있지만, 진단을 위한 충분한 정보일 수도 있습니다.

물론, 여기서는 코드가 매우 도움이 될 것입니다. 아마도 discourse를 포크한 후 변경 사항을 브랜치에 푸시하고, 제가 그 브랜치에서 변경 사항을 PR으로 가져오면 좋겠습니다.

이 문제를 진단하기 위해 진행 중이시니 정말 기쁩니다.

최신 코드를 풀러오실 수 있을까요? 제가 정리 작업을 한 번 해두었습니다.

네, 코드는 다음과 같습니다:

https://github.com/mcmcclur/discourse/tree/mathjax-mcmcclur

다만, 주의할 점은 제가 여러분의 최신 커밋에서 직접 작업하지는 않았다는 것입니다. 저는 Discourse main에서 직접 시작하여 거기서 변경 사항을 만들었습니다. 따라서 여러분의 작업에서 많은 것을 배웠지만, 전체적인 구조는 다릅니다.

주요 차이를 요약하자면 다음과 같습니다. 여러분은 (당연히) 로딩과 타이포그래피와 같은 작업과 관련된 타이밍을 조정하기 위해 Ember에서 상속된 Discourse 기능을 사용하는 반면, 저는 MathJax 기능을 사용합니다. 따라서 저의 load-mathjax와 mathjax 번들(svg용 하나, chtml용 하나)은 여러분의 것보다 훨씬 단순합니다. 로딩은 모두 discourse-math-mathjax의 window.MathJax 객체를 통해 조정됩니다.

앞서 설명했던 문제와 동일한 문제가 여전히 있습니다. 즉, 이 주석 처리된 로더가 작동하지 않는다는 것입니다. 대신 이 CDN 버전을 사용해야 합니다. 왜 그런지 정말로 모르겠습니다.

여러분의 코드도 동일한 문제에서 자유롭지 않다고 생각합니다. 그것이 AsciiMath가 작동하지 않는 것처럼 보이는 이유입니다.

제 최신 커밋을 다시 한번 확인해 주시겠어요? Ember용 퍼널(funnel)을 추가한 것 같아서, 이제 Ember 빌드가 모든 파일을 올바른 위치에 배치하는 것 같습니다.

매우 좋은 소식과 함께 다소 답답한 소식도 있습니다.

첫째, 퍼널을 추가하면 해당 파일들이 올바른 위치에 배치된다는 점에 대해 완전히 맞습니다. 제 브랜치에 퍼널을 추가했는데, 이제 CDN 의존성 없이도 완벽하게 작동합니다. :tada:

불행히도 현재는 당신의 코드를 실행할 수 없습니다. 수식이 포함된 페이지로 이동할 때마다 수식이 타입셋(typeset)되지 않고, 콘솔에 다음 오류 메시지가 표시됩니다:
Uncaught (in promise) Error: State EXPLORER already exists

이전에 당신의 코드가 정상적으로 작동했던 것을 분명히 기억하고 있으므로, 제가 무언가를 잘못한 것 같습니다. 하지만 분명히 하기 위해, macOS에서 개발용 Discourse 설치에 설명된 기법을 사용하여 완전히 새로운 디렉터리를 처음부터 시작했습니다.

git clone https://github.com/discourse/discourse.git ./discourse
cd ./discourse
bundle install
pnpm install
bundle exec rake db:create
bundle exec rake db:migrate
RAILS_ENV=test bundle exec rake db:create db:migrate

# 한 터미널에서
bundle exec rails server

# 다른 터미널에서
bin/ember-cli

그 후 다음 명령으로 당신의 코드를 가져왔습니다.

git checkout 71ad0305f812311f2a4570edf7c33f97de46c457
git switch -c mathjax-sam

이러한 새로운 설정에서도 여전히 오류가 발생합니다.


이 시점에서 저는 제 코드 버전에 대해 꽤 만족하고 있지만, 당신의 코드에서 무슨 일이 일어나고 있는지 여전히 궁금합니다. 다만, 휴가를 위해 이 작업에서 잠시 벗어나야 합니다. 며칠 후 다시 확인해 보겠습니다.

마지막으로 한 가지 더: 제가 아는 한,

await import("tex-mml-chtml.js") // 이후
await import("input/asciimath.js")

은 작동하지 않아야 합니다. 이것이 당신의 코드가 실제로 수행하는 일이라고 생각합니다.

여기서 경로를 정확히 표현하지는 않았지만, 제 지점은 연속적인 동적 import 호출이 올바른 MathJax 구조를 생성하는지 확신할 수 없다는 것입니다. MathJax 컴포넌트 로딩은 꽤 복잡하고, MathJax 객체와 관련된 상세한 로딩 프로세스가 있는 이유도 바로 그 때문이라고 생각합니다.

도와주셔서 그리고 인내심을 가져주셔서 정말 감사합니다 @sam!

여기서 진전이 있었습니다:

거대한 JavaScript 페이로드를 전용 gem으로 옮겼습니다.

이로 인해 최신 상태로 유지하기가 훨씬 쉬워졌으며, mathjax가 더 이상 reop에 체크인되지 않습니다.

샘, 안녕하세요 - 오늘 이걸 꽤 많이 만져 봤습니다. 정말 잘 나왔네요! 다만 아직 해야 할 일이 꽤 많다고 생각합니다. 그중 일부는 제가 확실히 도와드릴 수 있을 것 같고, 일부는 제 역량을 넘어서는 것일 수도 있습니다. 특히 대학이 다시 시작되면서요.

어쨌든, 제 생각 중 몇 가지를 말씀드릴게요.

줌 (Zoom)

마우스를 올렸을 때 줌 기능이 MathJax V4에서는 더 이상 사용할 수 없습니다. 대신 Alt 키를 누르며 클릭했을 때 줌되도록 설정하는 것은 쉽습니다. 저는 이렇게 설정했습니다:

https://github.com/mcmcclur/discourse/compare/5ad3add3a4b323be3b325f3571a492f14255759...c8b4a636e828a7a401a846a160deeaa5af734ece

참고로, 이 GitHub 이슈에 설명된 대로 CSS를 조금만 추가하면 해결할 수 있는 알려진 MathJax 버그가 있습니다. 이 코드에도 해당 수정 사항을 포함했습니다.

로딩 옵션

현재 상태로는 AsciiMath를 켤 수 없고, 접근성(Accessibility)을 끌 수도 없습니다. 이는 load-mathjax.js에서 서브모듈이 순차적으로 로드되는 방식 때문에 그런 것 같습니다.

지난 메시지에서 말씀드린 것처럼, 원하는 구성 요소를 지정하는 window.MathJax 객체를 미리 정의하는 것이 훨씬 더 일반적입니다. 메인 스크립트가 로드될 때 MathJax 객체가 다시 정의됩니다. 저의 V3 버전에서 이 방식으로 작동하게 만들 수 있었던 이유입니다. 원하신다면 다음 주 초에 이 접근 방식을 여러분의 코드베이스에 통합해 볼 수 있을 것 같습니다.

옵션을 정리한 후에는 V4에서 사용할 수 있는 새로운 옵션 중 포함해야 할 것이 있는지 고려해 볼 가치가 있을 수도 있습니다.

리치 에디터 (Rich Editor)

이것은 정말 훌륭합니다 - 이렇게 보니 정말 기쁩니다!

모달 내부에서 반짝이는 AI 컨텍스트 메뉴를 사용할 수 있게 만들 수 있을까요? 학생들이(그리고 교수님들 :confused:) 때때로 LaTeX 입력에 어려움을 겪기 때문에 이렇게 묻습니다. 작은 AI 교정 도구가 그 과정을 훨씬 부드럽게 만들어 줄 수 있습니다. 저는 제 강의용 Discourse에 이를 통합했으며, 다가오는 학기에 사용할 것을 기대하고 있습니다.


아직 더 많은 내용이 있을 것이라고 확신하지만, 오늘 저는 거의 마무리 단계에 있습니다.

정말 감사합니다!!! :rocket: :fire: :tada:

discourse-math 플러그인이 수식 라이브러리를 직접 벤더링(vendoring)하는 대신 별도의 MathJax/KaTeX 에셋 제이를 사용하는 방식이라는 점을 이해하고 있습니다. 이를 통해 플러그인의 경량화를 유지하면서도 수식 라이브러리를 독립적으로 업데이트할 수 있게 됩니다.

첫 번째 프로덕션 릴리스 전에 이를 검증하는 데 도움을 주고 싶습니다. 초기 계획은 별도的一次적인 인스턴스를 구동하고, 그곳에서 플러그인을 활성화한 후 수식이 많이 포함된 콘텐츠, 표준 파이프라인을 통한 에셋 로딩, CSP 동작, 성능 등을 테스트하는 것이었습니다.

이를 수행하기 전에, 현재 단계에서 권장되는 환경이 무엇인지 확인하고 싶었습니다. 프로덕션과 유사한 설정에서 초기 테스트를 수행하는 것이 적절한지, 아니면 첫 프로덕션 릴리스까지 개발 환경에서 수행하는 것이 더 나은지 의견을 듣고 싶습니다.

가장 유용한 방식으로 테스트를 수행하고, 발견하는 문제나 엣지 케이스를 업스트림에 보고하는 데 기꺼이 참여하겠습니다. 대학 수업으로 인해 고정된 테스트 일정은 약속할 수 없지만, 시간이 나면 최선을 다해 테스트를 진행할 것입니다. 6월 6일 이후에는 가용 시간이 훨씬 더 늘어날 것으로 예상됩니다.

이제 옵션이 잘 작동합니다. 코드는 여기서 확인하실 수 있습니다:
https://github.com/mcmcclur/discourse/compare/5ad3add3a4b2823be3b325f3571a492f14255759...ad6c3fa877632eba40cd83d1534aaa0d5dd6dc8d

몇 가지 코멘트를 드리겠습니다:

  • 모든 설정 및 로딩은 math-renderer.js에 정의된 MathJaxInitConfig 객체에 의해 처리됩니다.
  • load-mathjax.js에서 상당량의 비활성 코드(inert code)를 제거했습니다.
  • ‘ui/safe’ 확장 프로그램은 항상 로드됩니다.
  • 기본값이 true인 “Discourse math enable menu” 옵션을 추가했습니다. false인 경우 메뉴가 완전히 제거되어 MathJax의 속도가 더 빨라집니다.
  • 다음 두 가지 메뉴 항목은 다음과 같습니다.
    • Discourse math zoom on click
    • Discourse math enable accessibility
      이 항목들은 메뉴가 비활성화되어 있으면 효과가 없지만, 활성화되어 있으면 서로 독립적으로 작동합니다.

전체 메뉴는 다음과 같은 모습입니다:

아직 테스트를 추가하지는 않았지만, 원하시면 풀 리퀘스트를 시도해 볼 수 있습니다.