ES6 modules in chrome extensions — An introduction

As of version 61, Chrome added support for ES6 module. That means you don’t have to keep maintaining bundlers just to support this indispensable feature. And that also means that you can use this awesome feature in extensions as well.

Let’s start with a very brief introduction to modules. First of all let’s create a function with the export statement and save it in a file called module.js:

export function myFunction () {
...
}

All you have to do now to use this function in your HTML page is to create a script tag with type=”module” and use the import statement:

<script type="module">
import { myFunction } from './module.js'
...
myFunction()
</src>

Just be careful how you specify the file that containes the exported function. As described in this great article:

Valid module specifiers must match one of the following:
A full non-relative URL. As in, it doesn’t throw an error when put through new URL(moduleSpecifier).
Starts with /.
Starts with ./.
Starts with ../.

In layman’s terms, that means that if you use:

import { ... } from 'module.js'

you will get the following error:

Uncaught TypeError: Failed to resolve module specifier ‘module.js’

Usage in extensions

In chrome extensions, inline scripts are not allowed. So the script that imports the module should be added like that:

<script type="module" src="script.js">

Let’s see now how to add modules suppot to background, browser action (a.k.a. popup) and “extension pages”.

All the code in this article is available here.

Page action (popup)

Adding modules support to popup page is the easiest step. The popup page are defined as HTML page. So all you have to do is to add the following tag in the popup.html

<script type="module" src="popup.js">

Background script

Normally the background script gets defined in the background field in the manifest like that:

“background”: {
“script”: [ “background.js” ]
}

Problem is that, as we saw above, Chrome only support ES6 modules in script that are referenced in script tag with type module. Thankfully the chrome extension manifest accepts an html page for background. So let’s use this:

“background”: {
“page”: “background.html”
}

and then create the following html page called background.html:

<script type=”module” src=”background.js”></script>

To see our module in action, open the extension page and click on the “background.html” link (next to “Inspect views”).

Extension details in the chrome://extensions

This will open a console and you should see the following:

Console log

Extension pages

The extension pages are the ones that are part of the extension but are open in their own tab. Being this defined as HTML page, all you have to do is to use the script tag as in a normal HTML page. You can have a look to this. Note as in this example, the page.js script is actually importing the common.js module which is used by the popup.js script as well. This allows re-use of same code in different part of the extension (e.g. if you want to create one single API to access to the extension storage)

Limitations

The only limitation I have found so far is with the content scripts. This cannot be defined through HTML, so there is no way to specify that the script needs to be loaded with module support. In this case you can keep using a bundler, or list all the needed scripts in the manifest.json, like for example:

“content_scripts”: [
{
...
“js”: [“content-module.js”, “content.js”]
...
}]

[UPDATE] As pointed out by Jared D (thanks!) a workaround for this limitation can be found here. Basically the content script can be used to inject a script with ES6 module support into the open page. Since the script is executed directly in the HTML page, it can use the browser ES6 module support. This require changes to manifest to make the additional scripts available to the content (using the “web_accessible_resource” property of the manifest). See here for more details on this implementation.

Still, in my opinion, this is just a workaround with still some limitation. For example, the script injected cannot access the chrome.runtime object, so (just to name one) it cannot exchange messages with the rest of the extension. Also you cannot call function defined in the content script from the injected script (and vice versa).

Another limitation is that ES6 modules are not (yet) supported in Opera. Currently (with version 49.0.2725.64) the following error is thrown if you are using modules in extensions:

Failed to load module script: The server responded with a non-JavaScript MIME type of “”. Strict MIME type checking is enforced for module scripts per HTML spec.

This seems to be due to this bug that has been fixed in chrome 64 but not yet in Opera.

[UPDATE] This limitation is not present in Opera version 52.0.x and ES6 module works as in Chrome (included the workaround above for content script)

Finally (but this is not a limitation) if you are adding module support to an extension you already developed, remember that adding module support to a script, reinforces the use strict directive. So you may have to review your code if something doesn’t work.

Further reading