Оптимизация конструктора страниц: устранение дублирования 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