How to use ES6 “import” with Chrome Extension
--
Expecting JavaScript developers who have / will have developed Chrome Extension ever.
tl;dr
The minimum example is working here.
Table of Contents
- Why?
- Background Script
- Content Script
- Conclusion
Why?
Static files like CSS and JavaScript always have a specific problem to deliver themselves: “How to bundle bunch of dependencies to one file”.
For JavaScript, as you know, there are many bundler middleware, e.g. Browserify, RequireJS and Webpack, can resolve “require” method or “import” keyword and bundle JavaScript (sometimes AltJS) files to a single output.
Thanks to browsers’ support of ES2015 (so called ES6), “import” keyword can be used without using such middleware mentioned above, only when you add type="module"
to your <script>
tag.
<script type="module" src="your-script.js"></script>
OK, then, can we use module importing with developing Chrome Extension, with which JavaScript is loaded by manifest.json
?
Background Script
Since background script of Chrome Extension is loaded by background.scripts
field of manifest.json
, it’s not possible to add “module” attribute as <script>
tag. So just let’s use background.page
field instead, as an entrypoint of your JavaScript files.
{
"background": {
"page": "src/html/background.html"
}
}
then src/html/background.html
looks like
<script type="module" src="src/js/background.js"></script>
It works fine 👍
Content Script
Content Script, on the other hand, must be loaded automatically when content_scripts
are specified in manifest.json
, kind of hacks are needed.
If import
keyword simply appears on one of content_scripts
, following error message comes up.
Uncaught SyntaxError: Unexpected identifier
This is because it’s loaded without type="module"
attribute. We, somehow, kick our JavaScript by <script>
tag with type="module"
, at least the entrypoint of it.
The idea is to use dynamic import which is equivalent to add type="module"
to the dynamically imported script.
The content_script would look like
(async () => {
const src = chrome.extension.getURL('src/js/main.js');
const contentScript = await import(src);
contentScript.main();
})();
Once it’s imported by dynamic import, import
keyword can be used just as it’s loaded with type="module"
.
NOTE: Because
import(src)
is executed in a page-renderer context, which is outside of Chrome Extension, you might need to make thesrc
accessible from website, by using “web_accessible_resources” directive in yourmanifest.json
.
src/js/main.js
can be like this
import User from "./my-models/User";export function main() {
// Do what you want
const user = new User({name: "otiai10"});
console.log(user);
}
Alternatively, injecting script tag by content_script (introduced in this thread) can kick other JavaScript files with type="module"
, but as it’s external script for Chrome Extension Lifecycle, chrome
namespace can be eliminated and it requires some hack to use chrome
, e.g. dependency injection.
Conclusion
Thus, it’s not so hard work to use import
for your Chrome Extension. But still we have problems which are usually solved by kind of Webpack.
- How to solve
node_modules
and sub dependencies inside them? - Minifying / Uglifying / Transpiling AltJS are still needed in some cases.
We can take more benefits by using such middleware with well-structured functionalities. I decided to leave native import
keywords for now.