테마 컴포넌트에서 JS 에셋을 로드할 때 발생하는 문제

안녕하세요, Discourse에서 처음 개발을 시작해서 부끄러운 질문을 드려 죄송합니다.

PGN(체스판과 대국 기록)을 표시하기 위한 테마 컴포넌트를 만들고 있습니다.
이 컴포넌트는 이론적으로는 어렵지 않으며, 이를 위한 JS 컴포넌트(PGNViewer.js)가 있습니다. 저는 [wrap] 태그를 적절한 <script>var x='pgn here'; ...</script> 및 <div class='pgn-blahblah'></div> 블록으로 변환하는 데 성공했습니다.

지금까지는 잘 진행되고 있지만, 이제 JS를 로드해야 합니다.
import loadScript from "discourse/lib/load-script";와 await loadScript(settings.theme_uploads_local.pgnviewer_js);를 사용하여 초기화 시점에 /assets/ 디렉터리에서 파일을 로드할 수 있다는 것을 알게 되었습니다.

하지만 여기에서 몇 가지 문제가 있습니다.
개발 환경에서는 컴포넌트를 로드할 수 있지만, 프로덕션 서버에서는 에셋이 너무 크다고 나와서 로드할 수 없습니다. 이 문제를 해결할 방법이 있을까요?
두 번째로, 개발 환경에서는 /theme-javascripts/dist.js.map를 로드할 수 없다는 경고가 표시됩니다. 이 경고가 어디서 오는지 전혀 모르겠습니다. 제가 제공해야 할 것이 있나요?

전체적으로 이 접근 방식이 올바른가요, 아니면 플러그인으로 구현하는 것이 더 나은가요?

감사합니다.

존경하는 마음으로,

FF

Meta에 오신 것을 환영합니다, @happycactus :wave:

네, 아마도 그렇습니다. 플러그인이 필요한 유일한 이유는 API(즉, 백엔드)를 변경하려는 경우입니다.

과거에 비슷한 문제를 겪어본 적이 있습니다.

네, 리버스 프록시(nginx)를 통해 큰 파일이 전송될 수 있도록 app.yml을 변경해야 합니다.

다행히도 이는 upload_size:라는 간단한 설정이며, 컨텍스트는 다음과 같습니다:

params:
  ## Which Git revision should this container use? (default: tests-passed)
  version: tests-passed

  ## Maximum upload size (default: 10m)
  upload_size: 20m

이 설정을 변경한 후에는 적용되도록 다시 빌드해야 합니다:

./launcher rebuild app

물론, 이는 서버에 적절한 접근 권한이 있다는 전제 하에 가능합니다 …

로버트님, 감사합니다!

저는 이런 플랫폼에 대해 경험이 전혀 없어서 확신이 서지 않습니다.
제가 궁금한 점은 다음과 같습니다:

제 접근 방식은 div 블록에 내용을 채우기 위해 필요한 작은 JavaScript 스니펫을 "임베드(embed)"하는 것입니다. 하지만 mermaid 컴포넌트에서처럼 cookPgn 함수에서 실행할 수도 있을 것 같다는 생각이 듭니다:

async function applyMermaid(element, key = "composer") {
  const mermaids = element.querySelectorAll("pre[data-code-wrap=mermaid]");
...
  await loadScript(settings.theme_uploads_local.mermaid_js);
...
  mermaids.forEach((mermaid) => {
    if (mermaid.dataset.processed) {
      return;
    }

    const spinner = document.createElement("div");
    spinner.classList.add("spinner");
...
    mermaid.append(spinner);
  });

제 이해가 맞다면, 이 방식은 페이지에서 스크립트를 로드할 필요(아마도 loadScript를 사용하여??)가 없고, 테마 js에 단순히 import만 하면 되는 것 같습니다. 아니면 제가 완전히 잘못 이해하고 있는 걸까요?
즉, loadScript와 단순한 import의 차이는 무엇인가요?
이 부분을 더 잘 이해할 수 있는 웹 자료가 있다면 추천해 주실 수 있을까요?
질문이 사소하거나 멍청한 것 같아 다시 한번 죄송합니다!

또 다른 문제로는, 제한 값이 기본적으로 10M인데 제 에셋은 약 300kb이고 전체 zip 파일은 337kb인데도 업로드 시 512kb를 초과한다고 표시됩니다.

감사합니다,

FF

import를 시도해 보셨나요? 외부 스크립트를 import할 수 없다고 생각됩니다.

Loadscript가 올바른 접근 방식이며, 코드가 진행되고 스크립트를 사용하기 전에 스크립트가 완전히 로드되었는지 보장합니다(이것이 promise를 반환하는 이유입니다).

정확한 오류 메시지를 공유해 주실 수 있나요 - 이것이 브라우저 콘솔에서 발생하는 것인가요?

실제로 문제가 되는 부분은 CSP(콘텐츠 보안 정책)에서 금지된 인라인 JS를 사용하려는 것입니다.

이 코드를 테마 컴포넌트 코드의 헬퍼 함수에 넣고, 게시물 안에 주입하지 마세요.

현재 테마를 GitHub에 업로드해 주시면 저희도 확인해 볼 수 있습니다.

Discourse Mermaid 컴포넌트를 살펴보세요. 이 컴포넌트는 라이브러리를 로드하고 게시글에 다양한 효과를 적용합니다.

감사합니다, @Falco. 그것이 제 궁금증 중 하나였습니다.

네, 여기 있습니다: GitHub - studiofuga/discourse-pgn-component: A theme component for Discourse to display PGN blocks · GitHub
플러그인 버전도 있지만, 이 접근 방식은 따르지 않고 테마 컴포넌트로 유지할 것 같습니다.

감사합니다 @pfaffman. 실제로 거기서 영감을 얻었고, 위에서 제안한 것처럼 코드를 주입하지 않고도 제가 필요한 것을 정확하고 올바른 방식으로 수행하고 있다는 것을 알게 되었습니다.

모두 감사합니다. 여러분의 제안대로 시도해 보고 결과를 알려드리겠습니다. 제 코드는 오픈소스이므로 기여나 비판을 매우 환영합니다.

죄송합니다. 첨부 파일 크기를 512kB로 제한해둔 걸 잊고 있었네요. 기본값(4096)으로 올려놓으니 잘 작동했습니다. 감사합니다, 그리고 어리석은 질문을 해서 죄송합니다.

네, 모든 문제를 수정했고 컴포넌트가 작동합니다! 그런데 문제가 하나 있어요. 제가 사용하는 라이브러리에 "unsafe eval"이 있거든요. 아마도 업스트림 라이브러리를 수정해야 할 것 같은데, 어떻게 패키징해야 하는지 전혀 모르겠어요.

관심 있는 분들을 위해, 제 모든 변경 사항을 위에서 언급한 제 저장소에 푸시해 두었고, 오류에 대한 세부 사항은 다음과 같습니다.

그리고 해당 줄은 이렇습니다 (원본 js 모듈 기준)

let isBrowser=new Function("try {return this===window;}catch(e){ return false;}")

여기를 참고해 주세요.

스크립트가 프로덕션 환경에서 Content Security Policy에 걸려 실행되지 않고 있습니다.

사이트 설정인 content security policy script src에 'unsafe-eval'을 항목으로 추가해야 합니다.

감사합니다. 어떻게 해야 하는지 확인하려고 문서를 보고 있어요. :slight_smile: 많이 배우고 있네요, 감사합니다.

좋아요, 해냈어요. 다만 ’ … 헐… 어쨌든, 제 스크립트가 거의 작동했어요. 모든 것을 main에 push했는데 어떻게든 돌아갔어요. 렌더링에 문제가 있는 것 같지만, 적어도 메인 컴포넌트는 호출되고 있어요 (문제가 있는 곳이라고 의심합니다).
모두 감사합니다… 곧 더 많은 질문으로 돌아올게요 ㅋㅋ. 감사합니다!

CSP에 unsafe-eval을 추가하는 것은 CSP의 존재 이유 자체를 제거하는 것이므로, 결과적으로 사이트의 보안이 약해짐을 명심해야 합니다.

eval 사용은 코드 스멜이며, 위에서 지적했듯이 상류에서 수정되어야 합니다.

네, 당연히요. 지금은 해결할 수 없지만, 먼저 제 컴포넌트를 수정한 뒤, 업스트림 라이브러리를 수정하고 CSP 오버라이드를 제거할 계획입니다.
조언 감사합니다!

거의 다 끝났습니다. "무언가"는 표시되긴 하는데, 랜덤하게 작동합니다.
제가 사용하는 JS 라이브러리는 다음과 같이 동작합니다:

먼저 고유한 id를 가진 <div id='board'> 블록을 정의해야 합니다. 그런 다음 div를 채우기 위해 함수를 실행해야 하는데, 예를 들어 다음과 같습니다. PGNV.pgnBoard('board', {});. 이 스크립트는 같은 id를 사용하여 내용을 채웁니다.

제가 이해하기로는, cooked block을 데코레이팅하는 동안에는 이 작업을 수행할 수 없는 것 같습니다. 문서가 아직 채워지지 않았기 때문에 PGNV 객체가 해당 블록을 찾을 수 없기 때문입니다. 그래서 처음에는 위젯을 사용했지만(HTML 함수가 블록을 반환해야 하는데, 제가 그것을 가지고 있지 않아서 작동하지 않았습니다), 이후에는 디바운스된 함수로 전환했습니다: debounceFunction(this, renderPgn, attrs, 200);.

하지만 이것은 "가끔"만 작동합니다.

무엇을 놓치고 있는 걸까요?

감사합니다

debounceFunction은 추가적인 debounce 호출이 없는 동안 debounce 기간이 지나면 대상 메서드 호출을 지연시킵니다. 하지만 여전히 작동하지 않을 수 있는 이유는 다음과 같습니다.

귀하의 사용 사례에서 debounce는 해결책이 아닐 수 있습니다.

afterAdopt 옵션을 사용해보시기 바랍니다.

Hawn님, 힌트 주셔서 감사합니다!
제가 찾던 바로 그 것 같습니다. 두 가지 다른 접근법을 시도해 보았습니다:

  • 먼저 일반적인 "decorateCooked"를 사용하여 div 블록을 파싱하고 첨부한 다음, PGNV.pgnviewer()를 호출하여 afterAdopt로 렌더링하거나
  • afterAdopt 옵션만 사용하여 첨부와 렌더링을 모두 수행

아직 작동하지 않았습니다. 제가 완전히 파악하지 못하는 또 다른 문제가 있는 것 같습니다. 내부 라이브러리가 DOM 요소에서 className 속성을 찾을 수 없어 오류로 충돌하는 것 같습니다. 조금 헤매고 있습니다 :slight_smile: . 누군가가 loadScript를 사용하는 것을 봤지만, 저는 단순히 라이브러리를 import하고 debounce 함수를 사용하니 잘 작동했습니다. 두 방법의 차이점은 무엇인가요?

감사합니다!

안녕하세요 @hawm, 작동하지 않는 이유를 찾았지만 막혀 있습니다.
물론 저는 Ember와 최신 JS 전반에 대해 완전히 초보입니다. 그래서 조금 공부해 보니 원인을 파악했습니다.
PgnViewerJs는 가상 DOM이 아닌 실제 DOM에서 작동하도록 설계되어 있습니다. 따라서 cooked 요소가 채택된 후에도 실제 DOM에는 존재하지 않으며, PGNV는 document.getElementById를 사용하여 ID를 검색합니다.
찾을 수 있는 모든 자료를 공부해 봤는데, Ember 컴포넌트가 제 요구사항에 맞는 것 같지만 어떻게 처리해야 할지 확신이 서지 않습니다.
아니면 API.onAppEvent를 사용하는 것이 좋을까요?

감사합니다.

ember의 later()를 사용했는데, 좀 더러운 해킹 기법이지만 작동은 합니다.
더 나은 해결책이 있을까요?