Вопрос или проблема
Я разрабатываю расширение для Chrome с использованием React и Vite. Когда я загружаю расширение локально из папки dist
в Chrome в виде распакованного расширения, оно работает без каких-либо проблем.
Однако после публикации в Chrome Web Store я получаю следующую ошибку при попытке использовать расширение:
Uncaught SyntaxError: Cannot use import statement outside a module
Что я пробовал до сих пор:
- Пункт списка
- Удалил React.StrictMode из кода.
- Изменил tsconfig.json с различными настройками модулей:
- Пробовал “module”: “ESNext”
- Пробовал “module”: “commonjs”
- Убедился, что package.json содержит “type”: “module”.
- Собрал проект с помощью vite build вместо vite dev; ошибок во время сборки не было.
Код скрипта контента
import ReactDOM from 'react-dom/client';
import ContentApp from './ContentApp';
import { ExplanationContainer } from '../features/explanation/ExplanationContainer';
import { ConfigProvider } from 'antd';
import customTheme from '../theme/customTheme.ts';
import '../index.css';
import { StyleProvider } from '@ant-design/cssinjs';
import { SessionProvider } from '../features/auth/SessionContext.tsx';
import { getQuizProgression } from '../features/shared/helpers/getQuizProgression.ts';
const ROOT_ELEMENT_ID = 'crx-root';
const EXPLANATION_ROOT_ID = 'explanation-root';
interface RootInfo {
root: ReactDOM.Root;
element: HTMLElement;
}
let contentRoot: RootInfo | null = null;
let explanationRoot: RootInfo | null = null;
let isRendering = false;
const AppProviders: React.FC<{
children: React.ReactNode;
}> = ({ children }) => {
return (
<StyleProvider hashPriority="high">
<ConfigProvider prefixCls={'ant'} theme={customTheme}>
<SessionProvider>{children}</SessionProvider>
</ConfigProvider>
</StyleProvider>
);
};
const createOrGetRoot = (id: string): HTMLElement => {
let element = document.getElementById(id);
if (!element) {
element = document.createElement('div');
element.id = id;
document.body.appendChild(element);
}
return element;
};
const renderComponent = (id: string, Component: React.FC): RootInfo => {
const element = createOrGetRoot(id);
const root = ReactDOM.createRoot(element);
root.render(
<AppProviders>
<Component />
</AppProviders>
);
return { root, element };
};
const safeAppendChild = (parent: Element | null, child: HTMLElement) => {
if (parent && !parent.contains(child)) {
parent.appendChild(child);
}
};
const renderContentApp = () => {
const cardHeader = document.querySelector('.card-header');
const cardHeaderText = cardHeader?.textContent;
const keywords = ['Réglages', 'Settings', 'Einstellungen', 'Impostazioni'];
const shouldRenderProfile = keywords.some((keyword) =>
cardHeaderText?.includes(keyword)
);
const headerElement = document.querySelector('.card-body');
if (headerElement && shouldRenderProfile) {
if (contentRoot) {
safeAppendChild(headerElement, contentRoot.element);
} else {
contentRoot = renderComponent(ROOT_ELEMENT_ID, ContentApp);
safeAppendChild(headerElement, contentRoot.element);
}
} else if (contentRoot) {
contentRoot.root.unmount();
contentRoot = null;
}
};
const renderAiExplanation = () => {
const bodyElement = document.querySelector('.card-body');
const quizProgressionText = getQuizProgression();
const shouldRenderExplanation = quizProgressionText !== null;
if (shouldRenderExplanation && bodyElement) {
if (explanationRoot == null) {
explanationRoot = renderComponent(
EXPLANATION_ROOT_ID,
ExplanationContainer
);
safeAppendChild(bodyElement, explanationRoot.element);
}
} else if (explanationRoot) {
explanationRoot.root.unmount();
explanationRoot = null;
}
};
const renderComponents = () => {
if (isRendering) {
return;
}
isRendering = true;
renderContentApp();
renderAiExplanation();
isRendering = false;
};
const cleanup = () => {
if (contentRoot) {
contentRoot.root.unmount();
contentRoot = null;
}
if (explanationRoot) {
explanationRoot.root.unmount();
explanationRoot = null;
}
};
const observeDocumentBody = () => {
const observer = new MutationObserver(() => {
renderComponents();
});
observer.observe(document.body, { childList: true, subtree: true });
window.addEventListener('beforeunload', () => {
observer.disconnect();
cleanup();
});
return observer;
};
renderComponents();
const observer = observeDocumentBody();
if (import.meta.hot) {
import.meta.hot.dispose(() => {
observer.disconnect();
cleanup();
});
}
Вот мой текущий манифест:
{
"manifest_version": 3,
"version": "0.2.6",
"name": "Paragliding Copilot AI",
"description": "Улучшите опыт eLearning SHV FSVL с помощью объяснений, сгенерированных ИИ, для вопросов, обеспечивая более глубокое понимание и инсайты.",
"permissions": [
"tabs",
"storage"
],
"action": {
"default_icon": "src/assets/para-bot-no-bg.png",
"16": "src/assets/para-bot-no-bg-16.png",
"32": "src/assets/para-bot-no-bg-32.png",
"48": "src/assets/para-bot-no-bg-48.png",
"128": "src/assets/para-bot-no-bg-128.png"
},
"background": {
"service_worker": "./src/background/background.ts"
},
"content_scripts": [
{
"js": [
"./src/content/content.tsx"
],
"matches": [
"https://elearning.shv-fsvl.ch/*"
]
}
],
"web_accessible_resources": [
{
"resources": [
"./src/assets/fonts/*"
],
"matches": [
"*://*/*"
]
}
]
}
Дополнительная информация:
- Проблема возникает только после публикации расширения в Chrome Web Store, что затрудняет тестирование локально.
- Расширение работает идеально, когда загружается локально в виде распакованного расширения.
- Вы можете протестировать сайт, с которым оно взаимодействует, здесь (вход не требуется): https://elearning.shv-fsvl.ch/
- Расширение Chrome доступно здесь (может быть доступно только в швейцарском магазине): Paragliding Copilot AI (https://chromewebstore.google.com/detail/paragliding-copilot-ai/fgifmceoflmldnlmbfjjppoilngpenej)
- Я ищу предложения о том, как решить эту проблему.
Комментарии:
- Существует ли разница в том, как обрабатываются модули при публикации расширения и при его локальной загрузке?
- Может ли быть проблема с тем, как Vite собирает расширение для продакшена?
- Требуются ли специфические настройки для расширений Chrome на основе React при публикации?
Любая помощь будет очень оценена!
Ответ или решение
Проблема, с которой вы столкнулись, связана с неправильной обработкой модулей JavaScript в Chrome расширениях, особенно когда они публикуются в Chrome Web Store. Ниже приведены шаги и рекомендации, которые помогут вам устранить ошибку "Cannot use import statement outside a module".
1. Проверьте конфигурацию сборки
Убедитесь, что при сборке вашего расширения используется правильная конфигурация модулей.
- В вашем
vite.config.ts
(илиvite.config.js
) добавьте следующее для определения модуля:
export default {
build: {
rollupOptions: {
output: {
format: 'es', // убедитесь, что используете 'es'
}
}
},
// Другие настройки…
};
2. Правильная настройка manifest.json
Ваш manifest.json
уже выглядит неплохо. Однако убедитесь, что в нем есть следующее:
- Убедитесь, что в разделе
content_scripts
у вас указаны правильные пути к файлам. Обычно рекомендуется указывать путь в формате./
, чтобы обеспечить правильную загрузку.
"content_scripts": [
{
"js": [
"src/content/content.js" // убедитесь что это тот же файл, что и в Vite, возможно, пересоберите ваш проект
],
"matches": [
"https://elearning.shv-fsvl.ch/*"
]
}
]
3. Форматирование файлов
Убедитесь, что ваши исходные файлы использованы в правильном формате. Вы используете tsx
для содержимого скриптов, но их нужно конвертировать в js
(или js.map
для исходников). Убедитесь, что Vite правильно обрабатывает типы файлов:
- Попробуйте добавить разрешения для
.tsx
в конфигурации Vite, чтобы он правильно обрабатывал эти файлы.
4. Использование ES-модулей
Проверьте, правильно ли импортируются модули в ваших скриптах. Также убедитесь, что расширение поддерживает ES-модули, добавив "type": "module"
в ваш package.json
, как вы уже сделали. Но это также должно быть правильно обрабатываться при сборке.
5. Публикация и тестирование
Перед тем, как заново публиковать расширение, следуйте этим шагам:
-
Убедитесь, что вы тестируете в режиме продакшн, попробуйте его установить из пакета, который вы публикуете, вместо загрузки изначального файла.
-
Публикуйте сразу, как только у вас будет полный пакет, и убедитесь, что вы удалили кеш в Chrome перед тестированием расширения.
6. Прочее
Обратите внимание на следующие моменты:
-
Отслеживайте все ошибки в консоли, когда загружаете расширение, и примите во внимание все предупреждения о модулях, которые Chrome может выдавать.
-
Если возможно, попробуйте использовать службы, такие как
Webpack
илиParcel
, если у вас возникнут серьезные проблемы с Vite. Иногда разные сборщики могут лучше справляться с специфическими проблемами.
Заключение
Если после выполнения всех вышеперечисленных шагов проблема не исчезнет, рассмотрите возможность создания минимального репозитория, чтобы легче было диагностировать проблему. Также полезно обратиться к документации Chrome Extensions и Vite для получения актуальных советов и руководств.
Надеюсь, это поможет вам решить проблему. Удачи с вашим расширением!