Оптимизация конструктора страниц: устранение дублирования React и ошибок загрузки ESM

    При создании конструктора страниц на основе ESM-модулей часто возникают проблемы с дублированием библиотек (особенно React) и ошибками разрешения зависимостей. В этой статье мы разберём причины и предложим практические решения для сборщика esbuild.

    Почему возникает дублирование React в ESM-бандлах?

    Когда каждый компонент собирается в отдельный ESM-файл с помощью esbuild с опцией bundle: true, все зависимости (включая React) встраиваются внутрь каждого бандла. Это приводит к:

    • Увеличению размера каждого файла компонента
    • Конфликтам версий - если разные компоненты используют разные версии React
    • Ошибкам хуков - например, Cannot read properties of null (reading 'useMemo')

    Основная ошибка: Failed to resolve module specifier 'react'

    Эта ошибка возникает, когда браузер пытается импортировать React как внешний модуль, но не находит его. В ESM-модулях все импорты должны быть относительными или абсолютными URL. Если React не помечен как external в esbuild, он будет встроен в бандл, но при попытке импорта из другого контекста (например, из основного приложения) браузер не может его разрешить.

    Решение: external зависимости и shared модули

    Чтобы избежать дублирования и ошибок, нужно вынести React и другие общие библиотеки за пределы бандлов компонентов. Для этого в конфигурации esbuild используется опция external:

    esbuild.build({
      ...
      external: ['react', 'react-dom'],
      ...
    })

    Теперь React не будет встраиваться в каждый компонент. Вместо этого браузер будет ожидать, что React уже загружен глобально (например, через CDN или основной бандл).

    Загрузка React как shared модуля

    В основном приложении (где используется конструктор) нужно убедиться, что React загружен до компонентов. Это можно сделать через тег <script> с указанием type="module" или через динамический импорт в точке входа:

    import React from 'react';
    import ReactDOM from 'react-dom';
    // ... остальной код конструктора

    Если конструктор работает в iframe или отдельном контексте, можно использовать importmap для указания URL загрузки React.

    Как исправить ошибку 'Cannot read properties of null (reading 'useMemo')'

    Эта ошибка часто связана с тем, что React загружается несколько раз (дублируется) или используется разная версия React в разных частях приложения. После вынесения React в external необходимо:

    • Проверить, что все компоненты используют одну и ту же версию React
    • Убедиться, что React не встроен в бандл (используйте external)
    • Если используется customProcessingPlugin для вставки React. перед хуками, убедитесь, что React доступен глобально

    Оптимизация сборки esbuild для конструктора страниц

    Вот пример обновлённой конфигурации esbuild с external зависимостями и правильной обработкой модулей:

    esbuild.build({
      entryPoints: [entryPoint],
      outfile: outFile,
      format: "esm",
      bundle: true,
      external: ['react', 'react-dom', 'react-router-dom'], // внешние зависимости
      loader: { '.tsx': 'tsx', '.ts': 'ts', '.jpg': 'file', '.png': 'file' },
      platform: 'browser',
      plugins: [
        aliasPlugin({...}),
        svgrPlugin(),
        customProcessingPlugin,
        sassPlugin({...})
      ]
    })

    После этого каждый компонент будет содержать только свою логику и стили, а React будет общим для всех.

    Дополнительные рекомендации

    • Используйте tree-shaking (включён по умолчанию) для удаления мёртвого кода
    • Рассмотрите code splitting для больших компонентов
    • Для избежания конфликтов версий используйте single version policy в package.json
    • Тестируйте импорт компонентов в разных браузерах - некоторые старые версии могут не поддерживать ESM

    Часто задаваемые вопросы