В Next.js клиентский JS, взятый из iframe, не работает.

Вопрос или проблема

Требовалось встроить статически сгенерированный HTML в Next.js в родительский сайт, и мы хотели динамически переключать HTML Next внутри внешнего слоя.

Чтобы увидеть, как это работает, я попробовал следующую реализацию, которая сначала загружает SG файл Next через iframe и передаёт его в новый отдельный элемент div.

'use client';

import { useEffect, useRef, useState } from 'react';

export default function Parent() {
  const iframeRef = useRef<HTMLIFrameElement>(null);
  const targetRef = useRef<HTMLDivElement>(null);
  const [nodes, setNodes] = useState<NodeListOf<ChildNode>>();

  useEffect(() => {
    const iframeDocumentBody = iframeRef.current?.contentWindow?.document?.body;

    if (!iframeDocumentBody) return;

    const childNodes = iframeDocumentBody.cloneNode(true).childNodes;

    if (!hasCopied.current) {
      setNodes(childNodes);
      iframeRef.current.remove();
    }
  }, []);

  useEffect(() => {
    const targetDiv = targetRef.current;
    nodes?.forEach((node) => {
      targetDiv?.appendChild(node.cloneNode(true));
    });
  }, [nodes]);

  return (
    <>
      <div ref={targetRef} id="target" /> // целевой div для перемещения

      <iframe
        ref={iframeRef}
        src="/ssg/child.html" // сгенерированный SG HTML
      />
    </>
  );
}

Внешний вид страницы на данный момент следующий:

<!-- parent.html -->
<html lang="en">
  <head>...</head>
  <body>
    <div id="target">
      <!-- body child.html -->
      <script src="/_next/static/chunks/webpack-46d202d2d09b00c3.js" async=""></script>
      <script>...</script>
      <script>...</script>
      <next-route-announcer style="position: absolute;"></next-route-announcer>
      <div>...</div> // контент SG (не работает событие клика и так далее)
      ...
    </div>
    <script src="/_next/static/chunks/webpack.js?v=1727746810006" async=""></script>
    <script>...</script>
  </body>

На первый взгляд всё выглядит хорошо, но js на статической странице, который работал внутри iframe, не работает на новом месте, даже несмотря на то, что тег скрипта также был перемещён.

Почему это не работает и как заставить это работать или это невозможно?

Ответ или решение

Ваша проблема связана с тем, как браузеры обрабатывают и выполняют JavaScript-код, особенно в контексте iframe. Когда вы загружаете страницу в iframe, она работает в изолированном контексте, который не связан напрямую с родительским документом. Когда вы перемещаете содержимое из iframe в родительский документ, это может привести к нескольким проблемам, особенно в случае динамически подключаемого JavaScript-кода.

Вот основные причины, по которым ваш JavaScript код, перенесенный из iframe в родительский документ, не работает:

  1. Контекст выполнения: Скрипты, которые были загружены в контексте iframe, могут ожидать, что некоторые вещи будут доступны только внутри этого контекста. Когда вы переносите все содержимое, они могут не иметь доступа к необходимым ресурсам и объектам.

  2. События и обработчики: Обработчики событий, которые были установлены на элементах внутри iframe, не будут работать после их перемещения в родительский документ. Это связано с тем, что они могут быть привязаны к контексту iframe, и при их перемещении этот контекст теряется.

  3. Асинхронная загрузка скриптов: Скрипты, которые загружаются асинхронно, могут не быть выполнены до того момента, как вы переместите элемент, что приводит к отсутствию необходимых функций и данных.

Чтобы разрешить данную проблему, можно применить следующие подходы:

1. Перемещать только содержимое без скриптов

Если JavaScript не является критически важным для работы вашего приложения, вы можете просто переносить HTML-контент без скриптов. Вы сможете избежать проблем с контекстом исполнения.

2. Встраивание нужных скриптов

Вместо переноса скриптов из iframe, вы можете загрузить их заново в родительском документе после того, как переместите содержимое. Для этого выполните следующие шаги:

  • Сохраните необходимые URL скриптов из iframe.
  • После добавления содержимого в родительский документ, динамически загрузите эти скрипты с помощью JavaScript.

Примерно так:

nodes?.forEach((node) => {
  targetDiv?.appendChild(node.cloneNode(true));
});

// Теперь загружаем нужные скрипты заново
const scriptSources = ["/_next/static/chunks/webpack-46d202d2d09b00c3.js", "/_next/static/chunks/webpack.js?v=1727746810006"];
scriptSources.forEach(src => {
  const script = document.createElement('script');
  script.src = src;
  script.async = true;
  document.body.appendChild(script);
});

3. Используйте события window

Если в вашем приложении важны определенные события или данные, которые должны быть доступны после завершения обработки, вы можете воспользоваться механизмом событий window для передачи необходимых данных из iframe в родительский документ.

4. Конвертация приложения

Если ваше приложение предполагает взаимодействие в реальном времени, возможно, вам стоит рассмотреть графический интерфейс на базе JavaScript (например, React) в родительском документе, а не пытаться интегрировать статически сгенерированное приложение.

Заключение

В общем, интеграция статически сгенерированного HTML из iframe в родительскую страницу в Next.js может создать проблемы, связанные с контекстом исполнения скриптов и обработчиками событий. Для корректной работы следует либо не переносить скрипты, либо загружать их заново. Если потребности вашего проекта усложняются, подумайте об использовании более интегрированного подхода без использования iframe.

Оцените материал
Добавить комментарий

Капча загружается...