How to import ES6 modules in content script for Chrome Extension

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.

  • Dynamic import()

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:

  • How to use ES6 “import” with Chrome Extension
  • Working Example of ES6 import in Chrome Extension
  • chrome.runtime.getURL

Hope it helps.


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!


imports 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


I managed to find a workaround.


Disclaimer

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.


Workaround

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.

Example Usage: 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. :)