I'm developing a Chrome extension using React and Vite. When I load the extension locally from the dist folder as an unpacked extension in Chrome, it runs without any issues.
However, after publishing it to the Chrome Web Store, I receive the following error when trying to use the extension:
Uncaught SyntaxError: Cannot use import statement outside a moduleWhat I've tried so far:
- List item
- Removed React.StrictMode from the code.
- Modified tsconfig.json with different module settings:
- Tried "module": "ESNext"
- Tried "module": "commonjs"
- Ensured that package.json includes "type": "module".
- Built the project using vite build instead of vite dev; no errors were shown during the build process.
Content Script Code
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(); }); }
Here is my current manifest:
{ "manifest_version": 3, "version": "0.2.6", "name": "Paragliding Copilot AI", "description": "Enhance SHV FSVL eLearning experience with AI-generated explanations for questions, providing deeper understanding and insights.", "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": [ "*://*/*" ] } ] }
Additional Information:
Comments:
- Is there a difference in how modules are handled when an extension is published versus when it's loaded locally?
- Could there be an issue with the way Vite bundles the extension for production?
- Are there specific configurations required for React-based Chrome extensions when publishing?
Any help would be greatly appreciated!