In Chrome 61, support for modules in JavaScript was added. Right now I am running Chrome 63.
I am trying to use import
/export
syntax in Chrome extension content script to use modules.
In manifest.json
:
"content_scripts": [
{
"js": [
"content.js"
],
}
]
In my-script.js
(same directory as content.js
):
'use strict';
const injectFunction = () => window.alert('hello world');
export default injectFunction;
In content.js
:
'use strict';
import injectFunction from './my-script.js';
injectFunction();
I receive this error: Uncaught SyntaxError: Unexpected identifier
If I change the import syntax to import {injectFunction} from './my-script.js';
I get this error: Uncaught SyntaxError: Unexpected token {
Is there some issue with using this syntax in content.js
in Chrome extension (since in HTML you have to use <script type="module" src="script.js">
syntax), or am I doing something wrong? It seems strange that Google would ignore support for extensions.
The "SyntaxError: Cannot use import statement outside a module" occurs when we use the ES6 Modules syntax in a script that was not loaded as a module. To solve the error, set the type attribute to module when loading a script, or in your package. json for Node. js apps.
I stumbled on this error: Uncaught SyntaxError: cannot use import statement outside a module while importing a function from a JavaScript file. This error occurs for one reason: you're trying to use import and you're not inside an ES module. It can happen in a Node.js environment, or in the browser.
To solve the error "Cannot use import statement outside a module" in TypeScript, set the module option to commonjs in your tsconfig. json file and make sure to compile your TypeScript files (e.g. with ts-node ), and not to run them directly with node .
As it's already mentioned, for background script, it's good idea to use background.page
and use <script type="module">
to kick your JavaScript.
The problem is content script
, and injecting <script>
tag with type
attribute can be a solution.
Another approach than injecting script tag is to use dynamic import
function. By this approach, you don't need to loose scope of chrome
module and still can use chrome.runtime
or other modules.
In content_script.js
, it looks like
(async () => {
const src = chrome.runtime.getURL("your/content_main.js");
const contentMain = await import(src);
contentMain.main();
})();
You'll also need to declare the imported scripts in manifest's Web Accessible Resources:
{
"web_accessible_resources": [
"your/content_main.js"
]
}
For more details:
chrome.runtime.getURL
Hope it helps.
I managed to find a workaround.
First of all, it’s important to say that content scripts don’t support modules as of January 2018. This workaround sidesteps the limitation by embedding module script
tag into the page that leads back to your extension.
This is my manifest.json
:
"content_scripts": [ {
"js": [
"content.js"
]
}],
"web_accessible_resources": [
"main.js",
"my-script.js"
]
Note that I have two scripts in web_accessible_resources
.
This is my 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);
This will insert main.js
into the webpage as a module script.
All my business logic is now in main.js
.
For this method to work, main.js
(as well as all scripts that I will import
) must be in web_accessible_resources
in the manifest.
my-script.js
'use strict';
const injectFunction = () => window.alert('hello world');
export {injectFunction};
And in main.js
this is an example of importing the script:
'use strict';
import {injectFunction} from './my-script.js';
injectFunction();
This works! No errors are thrown, and I am happy. :)
import
s are not available in content scripts.
Here's a workaround using global scope.
Since content scripts live in their own 'isolated world' - they share the same global namespace. It is only accessible to content scripts declared in manifest.json
.
Here's the implementation:
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);
Same way you can factor out re-usable functions and other actors you would otherwise import
in content script files.
N.B.: global namespace of content scripts is not available to any pages besides content scripts - so there is little to no global scope pollution.
In case you need to import some libs - you will have to use a bundler like Parcel
to package up your content script files along with the needed libs into one huge-content-script.js
and then metion it in manifest.json
.
P.S.: docs on globalThis
The best way would be to use bundlers like webpack or Rollup.
I got away with basic configuration
const path = require('path');
module.exports = {
entry: {
background: './background.js',
content: './content.js',
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, '../build')
}
};
Run the file with the command
webpack --config ./ext/webpack-ext.config.js
Bundlers combine the related files and we can use modularisation in chrome extensions! :D
You will need to keep all other files like manifest and static files in build folder.
Play around with it and you will eventually find a way to make it work!
I just stumbled across this question while trying to solve the same thing myself.
Anyways, I think there's a simpler solution to injecting your own custom modules into your content script. I was looking at how Jquery is injected and it occurs to me you can do the same thing by creating an IIFE (Immediately Invoked Function Expression), and declaring it in your manifest.json
It goes something like this:
In your manifest.json:
"content_scripts": [
{
"matches": ["https://*"],
"css": ["css/popup.css"],
"js": ["helpers/helpers.js"]
}],
Then just create an IIFE in your helpers/helpers.js:
var Helpers = (function() {
var getRandomArbitrary = function(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
return {
getRandomArbitrary: getRandomArbitrary
}
})()
Now, you can freely use your helper functions in your content script:
Helpers.getRandomArbitrary(0, 10) // voila!
I think it's great if you use this method to refactor some of your generic functions. Hope this helps!
Short Answer:
You can mimic some of the functionality and get some of the benefits of import
/export
in browser extensions by creating the following file and listing it early in your 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];
}
Then, export from one file/module like this:
exportVars({ var1, var2, var3 }).from('my-utility');
Import into another file/module like this:
const { var1, var3: newNameForVar3 } = importVarsFrom('my-utility');
Discussion:
This strategy:
chrome.runtime
, etc.) that is eliminated by, e.g., the approach in another answer (currently the accepted answer) using module script tag embedding,import
and export
functions in JavaScript,import
and export
commands work in JavaScript, but doesn't have to be (i.e. the name-space names could be anything you want), andimport { fn as myFn }...
works.To do this, your manifest.json
needs to load your JavaScript as follows:
modules-start.js
in the example below),Of course, you might have a file that both imports and exports. In that case, just ensure it is listed after the files it imports from but before the files it exports to.
Working Example
The following code demonstrates this strategy.
It is important to note that all of the code in each module/file is contained within curly braces. The only exception is the first line in modules-start.js
which establishes the exporting and importing functions as global variables.
The code in the snippet below is necessarily contained in a single "place". In a real project, however, the code could be split into separate files. Note, though, that even in this artificial context here (i.e. within the single code snippet below), this strategy allows the different sections of code it contains to be modular and yet still interconnected.
// 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)}.`);
}
With this example, your manifest.json
should list the files in the following order:
{ ...
"content_scripts": [
{
"js": [
"modules-start.js",
"my-general-utilities.js",
"content.js"
]
}
], ...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With