Как импортировать модули ES6 в контент script для расширения Chrome

В Chrome 61 была добавлена поддержка модулей в JavaScript. Сейчас я использую Chrome 63.

Я пытаюсь использовать синтаксис импорта/экспорта в скрипте содержимого расширения Chrome для использования модулей.

В manifest.json:

"content_scripts": [
    {
        "js": [
            "content.js"
        ],
    }
]

В my-script.js в той же директории, что и content.js

'use strict';

const injectFunction = () => window.alert('hello world');

export default injectFunction;

В content.js

'use strict';

import injectFunction from './my-script.js';
injectFunction();

Я получаю эту ошибку: Uncaught SyntaxError: Неожиданный идентификатор

Если я изменю синтаксис импорта на import {injectFunction} from './my-script.js'; Я получаю эту ошибку: Uncaught SyntaxError: Неожиданный токен {

Есть ли проблема с использованием этого синтаксиса в content.js в расширении Chrome, поскольку в HTML вы должны использовать синтаксис <script type="module" src="script.js">, или я что-то делаю не так? Кажется странным, что Google игнорирует поддержку расширений.

Ответ 1

Мне удалось найти обходной путь.


Отказ от ответственности

Прежде всего, важно сказать, что скрипты содержимого не поддерживают модули с января 2018 года. Этот обходной путь обходит ограничение, встраивая тег модуля script в страницу, которая ведет к вашему расширению.


Обход

Это в моем manifest.js:

    "content_scripts": [ {
       "js": [
         "content.js"
       ]
    }],
    "web_accessible_resources": [
       "main.js",
       "my-script.js"
    ]

Обратите внимание, что у меня есть два сценария в web_accessible_resources.

Мой content.js содержит только эту логику:

    'use strict';

    const script = document.createElement('script');
    script.setAttribute("type", "module");
    script.setAttribute("src", chrome.extension.getURL('main.js'));
    const head = document.head || document.getElementsByTagName("head")[0] || document.documentElement;
    head.insertBefore(script, head.lastChild);

Это вставит main.js на веб-страницу как скрипт модуля.

Вся моя бизнес-логика теперь в main.js.

Чтобы этот метод работал, main.js (а также все сценарии, которые я буду import) должны быть в web_accessible_resources в манифесте.

Пример использования: my-script.js

    'use strict';

    const injectFunction = () => window.alert('hello world');

    export {injectFunction};

А в main.js это пример импорта скрипта:

    'use strict';

    import {injectFunction} from './my-script.js';
    injectFunction();

Это работает! Нет ошибок, и я счастлив. :)

Ответ 2

Как уже упоминалось, для фонового скрипта было бы неплохо использовать background.page и использовать <script type="module"> для запуска вашего JavaScript.

Проблема заключается в content script, и решение проблемы может быть связано с добавлением тега <script> с атрибутом type.

Другой подход, чем внедрение тега script, заключается в использовании функции dynamic import. При таком подходе вам не нужно терять область действия модуля chrome и вы все равно можете использовать chrome.runtime или другие модули.

В content_script.js это выглядит так

(async () => {
  const src = chrome.extension.getURL("your/content_main.js");
  const contentMain = await import(src);
  contentMain.main();
})();

Больше подробностей:

Надеюсь, поможет.

Ответ 3

Я просто наткнулся на этот вопрос, пытаясь решить то же самое сам.

В любом случае, я думаю, что есть более простое решение для внедрения ваших собственных пользовательских модулей в ваш контент-скрипт. Я смотрел на то, как вводится Jquery, и мне кажется, что вы можете сделать то же самое, создав IIFE (выражение для немедленного вызова функции) и объявив его в файле manifest.json.

Это выглядит примерно так:

В вашем manifest.json:

"content_scripts": [
{
  "matches": ["https://*"],
  "css": ["css/popup.css"],
  "js": ["helpers/helpers.js"]
}],

Затем просто создайте IIFE в вашем файле helpers/helpers.js:

var Helpers = (function() {
  var getRandomArbitrary = function(min, max) {
    return Math.floor(Math.random() * (max - min)) + min;
  }
  return {
    getRandomArbitrary: getRandomArbitrary
  }
})()

Теперь вы можете свободно использовать свои вспомогательные функции в вашем скрипте контента:

Helpers.getRandomArbitrary(0, 10) // voila!

Я думаю, это здорово, если вы используете этот метод для рефакторинга некоторых ваших общих функций. Надеюсь это поможет!

Ответ 4

Лучший способ - использовать такие пакеты, как webpack или Rollup.

Я сошел с базовой конфигурации

const path = require('path');

module.exports = {
  entry: {
    background: './background.js',
    content: './content.js',
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, '../build')
  }
};

Запустите файл командой

webpack --config ./ext/webpack-ext.config.js

Поставщики объединяют связанные файлы, и мы можем использовать модульность в расширениях Chrome! : D

Вам нужно будет хранить все остальные файлы, такие как манифест и статические файлы, в папке сборки.

Поиграйте с ним, и вы в конце концов найдете способ заставить его работать!

Ответ 5

import недоступны в скриптах содержимого.

Вот обходной путь с использованием глобальной области видимости.

Поскольку скрипты контента живут в своем собственном "изолированном мире" - они имеют общее глобальное пространство имен. Он доступен только для скриптов контента, объявленных в manifest.json.

Вот реализация:

manifest.json

"content_scripts": [
  {
    "matches": ["<all_urls>"],
    "js": [
      "content-scripts/globals.js",
      "content-scripts/script1.js",
      "content-scripts/script2.js"
    ]
  }
],

globals.js

globalThis.foo = 123;

script1.js

some_fn_that_needs_foo(globalThis.foo);

Точно так же, как вы можете выделить повторно используемые функции и других действующих лиц, которые вы в противном случае import использовали бы в файлах сценариев контента.

N.B.: глобальное пространство имен сценариев содержимого не доступно ни для каких страниц, кроме сценариев содержимого, поэтому загрязнение глобального объема практически отсутствует.

В случае, если вам нужно импортировать несколько библиотек - вам нужно будет использовать упаковщик, такой как Parcel, чтобы упаковать файлы сценариев контента вместе с необходимыми библиотеками в один huge-content-script.js и затем передать его в manifest.json.

П.С.: документы по глобальному

Ответ 6

Короткий ответ:

Вы можете имитировать некоторые функции и получить некоторые из преимуществ import/export в расширениях браузера, создав следующий файл и разместив его в начале файла manifest.json:

let exportVars, importVarsFrom;
{
  const modules = {};
  exportVars = varsObj => ({
    from(nameSpace) {
      modules[nameSpace] || (modules[nameSpace] = {});
      for (let [k,v] of Object.entries(varsObj)) {
        modules[nameSpace][k] = v;
      }
    }
  });
  importVarsFrom = nameSpace => modules[nameSpace];
}

Затем экспортируйте из одного файла/модуля следующим образом:

exportVars({ var1, var2, var3 }).from('my-utility');

Импортируйте в другой файл/модуль следующим образом:

const { var1, var3: newNameForVar3 } = importVarsFrom('my-utility');

Обсуждение:

Эта стратегия:

  • позволяет модульный код в расширении браузера, так что вы можете разбить код на несколько файлов, но не иметь конфликты переменных из-за общей глобальной области видимости между различными файлами,
  • по-прежнему позволяет экспортировать и импортировать переменные из и в разные файлы/модули JavaScript,
  • вводит только две глобальные переменные, а именно функцию экспорта и функцию импорта,
  • поддерживает полную функциональность расширения браузера в каждом файле (например, chrome.runtime и т.д.), который исключается, например, подходом в другом ответе (в настоящее время принятый ответ) с использованием встраивания тегов сценария модуля,
  • использует краткий синтаксис, аналогичный истинным функциям import и export в JavaScript,
  • разрешает интервал имен, который может быть именами файлов экспортирующих модулей способом, подобным тому, как работают настоящие команды import и export в JavaScript, но это не обязательно (т.е. имена пространств имен могут быть любыми), а также
  • разрешает переименование переменных при импорте, аналогично тому, как работает import { fn as myFn }...

Чтобы сделать это, ваш manifest.json должен загрузить ваш JavaScript следующим образом:

  • файл, устанавливающий функции экспорта/импорта в первую очередь (названный modules-start.js в приведенном ниже примере),
  • экспортирующие файлы затем, и
  • импортируемые файлы сохраняются

Конечно, у вас может быть файл, который импортирует и экспортирует. В этом случае просто убедитесь, что он указан после файлов, из которых он импортирует, но до файлов, в которые он экспортирует.

Рабочий пример

Следующий код демонстрирует эту стратегию.

Важно отметить, что весь код в каждом модуле/файле содержится в фигурных скобках. Единственным исключением является первая строка в modules-start.js которая устанавливает функции экспорта и импорта как глобальные переменные.

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

// modules-start.js:
let exportVars, importVarsFrom; // the only line NOT within curly braces
{
  const modules = {};
  exportVars = varsObj => ({
    from(nameSpace) {
      modules[nameSpace] || (modules[nameSpace] = {});
      for (let [k,v] of Object.entries(varsObj)) {
        modules[nameSpace][k] = v;
      }
    }
  });
  importVarsFrom = nameSpace => modules[nameSpace];
}


// *** All of the following is just demo code
// *** showing how to use this export/import functionality:

// my-general-utilities.js (an example file that exports):
{
  const wontPolluteTheGlobalScope = 'f';
  const myString = wontPolluteTheGlobalScope + 'oo';
  const myFunction = (a, b) => a + b;
  
  // the export statement:
  exportVars({ myString, myFunction }).from('my-general-utilities');
}

// content.js (an example file that imports):
{
  // the import statement:
  const { myString, myFunction: sum } = importVarsFrom('my-general-utilities');

  console.log('The imported string is "${myString}".');
  console.log('The renamed imported function shows that 2 + 3 = ${sum(2,3)}.');
}

Ответ 7

Экспортировать модуль как объект:

'use strict';

const injectFunction = () => window.alert('hello world');

export {injectFunction};

Затем вы можете импортировать его свойство:

'use strict';
import {injectFunction} from './my-script.js';