Проблемы с загрузкой 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 (то есть бэкенд).

У меня раньше была похожая проблема.

Да, вам нужно изменить файл app.yml, чтобы разрешить передачу больших файлов через обратный прокси (nginx).

К счастью, это простая настройка — upload_size:. Вот пример в контексте:

params:
  ## Какую ревизию Git должен использовать этот контейнер? (по умолчанию: tests-passed)
  version: tests-passed

  ## Максимальный размер загрузки (по умолчанию: 10m)
  upload_size: 20m

После изменения этого параметра вам нужно пересобрать контейнер, чтобы изменения вступили в силу:

./launcher rebuild app

Конечно, это предполагает, что у вас есть соответствующий доступ к серверу…

Спасибо, Роберт!

Я в замешательстве, вероятно, потому что у меня нет никакого опыта работы с подобными платформами.
Мой вопрос:

мой подход заключается в том, чтобы «внедрить» небольшой фрагмент JavaScript, необходимый для заполнения блока div контентом, но я вижу, что, возможно, я мог бы выполнить его из функции «cookPgn», как это сделано в компоненте marmaid:

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 темы? Или, скорее всего, я совершенно запутался!
Я имею в виду, в чём разница между loadScript и простым импортом?
Можете ли вы направить меня к каким-либо веб-ресурсам, где я мог бы лучше в этом разобраться?
Ещё раз извините, если вопросы тривиальны или глупы!

Ещё один момент: лимит по умолчанию составляет 10 МБ, но мой файл размером около 300 КБ, а весь ZIP-архив — 337 КБ, однако при загрузке сообщается, что он больше 512 КБ.

Спасибо,

FF

Вы пробовали использовать импорт? Мне кажется, что импортировать внешний скрипт нельзя.

LoadScript — это правильный подход, он гарантирует, что скрипт полностью загрузится, прежде чем ваш код продолжит выполнение и начнёт его использовать (именно поэтому он возвращает промис).

Можете показать точное сообщение об ошибке? Это в консоли браузера?

Ваша реальная проблема заключается в попытке использовать встроенный JavaScript, что запрещено политикой CSP.

Разместите этот код во вспомогательной функции в коде компонента вашей темы и не внедряйте его внутрь постов.

Загрузите текущую тему на GitHub, и мы тоже сможем её посмотреть.

Посмотрите на компонент Discourse Mermaid, который загружает библиотеку и запускает различные действия в сообщениях.

Спасибо, @Falco, это был один из моих вопросов.

Конечно, вот она: GitHub - studiofuga/discourse-pgn-component: A theme component for Discourse to display PGN blocks · GitHub
Есть также версия в виде плагина, но я думаю, что не буду следовать этому подходу и останусь на компоненте темы.

Спасибо, @pfaffman, действительно, я вдохновлялся им, и заметил, что он делает именно то, что мне нужно, и правильным способом, без внедрения кода, как предлагалось выше.

Спасибо всем, я попробую ваши предложения и вернусь с результатами. Конечно, мой код с открытым исходным кодом, поэтому любой вклад или критика очень приветствуются.

Моя вина, я забыл, что ограничил размер вложений до 512 КБ. Вернул значение по умолчанию (4096), и всё заработало. Спасибо и извините за глупый вопрос.

Отлично, я исправил всё, и компонент работает! Но у нас есть проблема — «unsafe eval» в библиотеке, которую я использую. Думаю, мне нужно исправить исходную библиотеку, но я не знаю, как её упаковать.
Для тех, кому интересно: я выложил все свои изменения в репозиторий выше, а вот подробности ошибки.

И вот эта строка (в исходном JS-модуле):

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

здесь.

Ваш скрипт вызывает срабатывание политики Content Security Policy в продакшене.

Вам нужно добавить 'unsafe-eval' как отдельный элемент в настройку сайта: content security policy script src

Спасибо, я изучаю документацию, чтобы понять, как это сделать. :slight_smile: Я многому учусь, спасибо.

Ладно, я сделал это, просто мне нужно было использовать '… lol… В любом случае, мой скрипт почти сработал: я запушил всё в main, и каким-то образом это сработало. Похоже, у меня проблема с рендерингом, но хотя бы главный компонент вызывается (я подозреваю, что проблема именно там).
Спасибо всем… Скоро вернусь с новыми вопросами, LOL. Спасибо!

Помните, что добавление unsafe-eval в вашу CSP фактически сводит на нет всю её суть и в результате делает ваш сайт менее защищённым.

Использование eval — это тревожный сигнал, и проблему следует исправить на стороне источника, как вы и указали выше.

Да, конечно. Сейчас я не могу это исправить, но мой план — сначала исправить свой компонент, затем исправить библиотеку-источник и убрать переопределение CSP. Спасибо за совет!

Я почти закончил. Мне удалось отобразить «что-то», но это работает нестабильно.

Используемая мной JS-библиотека работает следующим образом:

Сначала нужно определить блок <div id='board'> с уникальным идентификатором. Затем необходимо вызвать функцию для заполнения div-элемента, что-то вроде этого: PGNV.pgnBoard('board', {});. Скрипт требует тот же идентификатор для заполнения.

Если я правильно понимаю, я не могу сделать это при декорировании обработанного блока, поскольку документ ещё не заполнен, и объект PGNV не может найти блок. Поэтому сначала я попробовал использовать виджет (не сработало, потому что HTML-функция должна возвращать блок, а у меня его нет), затем перешёл к функции с задержкой: debounceFunction(this, renderPgn, attrs, 200);.

Но это работает «иногда».

Что я упускаю?

Спасибо.

Функция debounceFunction откладывает вызов целевого метода до тех пор, пока не истечет период debouncing без дополнительных вызовов, но иногда она может не сработать, возможно, потому что

debounce может не быть решением в вашем случае использования.

Попробуйте использовать опцию afterAdopt.

Привет, Хоун, спасибо за подсказку!
Кажется, это именно то, что я искал. Я попробовал два разных подхода:

  • сначала использовать обычный «decorateCooked» для разбора и добавления блока div, а затем «afterAdopt» для рендеринга, вызывая PGNV.pgnviewer(), или
  • использовать только опцию «afterAdopt» для добавления и рендеринга.

Ни один из них не сработал из-за ещё одной проблемы, которую я пока не могу до конца понять: похоже, что внутренняя библиотека падает с ошибкой, не находя атрибут className, вероятно, в элементе DOM. Я немного растерян :slight_smile: . Я видел, что кто-то использует loadScript, но я просто импортировал библиотеку, и использование функции debounce сработало. В чём разница между этими двумя методами?

Спасибо!

Привет @hawm, я выяснил, почему это не работает, но застрял.
Конечно, я совершенно новичок в Ember и в современном JavaScript в целом. Поэтому я немного изучил вопрос и понял причину.
PgnViewerJs ожидает работать с реальным DOM, а не с виртуальным. Таким образом, даже после того, как обработанный элемент принят, он отсутствует в реальном DOM, а PGNV ищет ID, используя document.getElementById.
Я изучил всё, что смог найти: компоненты Ember, похоже, подходят под мои нужды, но я не уверен, как с ними работать.
Или, возможно, API.onAppEvent?

Спасибо.

Я использовал later() из Ember и, хотя это грязный хак, он работает.
Есть ли лучшее решение?