Расширение Chrome: React не может использовать оператор импорта вне модуля только в магазине расширений Chrome

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

Я разрабатываю расширение для 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 для получения актуальных советов и руководств.

Надеюсь, это поможет вам решить проблему. Удачи с вашим расширением!

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

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