Как импортировать модули 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';