Sebastian Gomez
Exploring How to Build Chrome Extensions with Esbuild
This time I wanted to learn how esbuild works for Chrome extensions, so I did a quick Google search and found a very interesting repository created by Marton Lederer. I decided to clone it and see what happened when I ran it. Here I share my experience and a few key takeaways, step by step, so you can follow along with me.
The repository is available on GitHub: martonlederer/esbuild react chrome extension.
A note on Manifest V3. Every new extension must use Manifest V3. Manifest V2 is deprecated and Chrome no longer loads MV2 extensions, so this is a point we cannot skip. This repository predates that migration, so it is worth checking its manifest.json: if it still declares "manifest_version": 2, you will have to migrate it to MV3 before the extension will load in a current Chrome.
Cloning the repository
First I cloned the repository with git clone. Once it was on my machine, I ran yarn install to install all the necessary dependencies. Then I loaded the extension in the browser to see how it worked.
Project structure
There is a public folder that contains the popup.html file and the manifest.json. When I loaded the extension, an error appeared at first that said "could not load content scripts". After some investigation, I found there was a content script that was not needed for this demo, so I removed it and reloaded the extension.
Build process
To build the extension I used the yarn build command, which generates the necessary files in the build folder. Then I loaded these unpacked files into Chrome through the extensions page (chrome://extensions/). The extension loaded correctly and I could see a pop up that said "React Pop up".
Exploring the code
The package.json file includes dependencies like Babel, esbuild, Prettier, React, and TypeScript. There is also a dependency called Yorkie, similar to Husky, which is used to run pre commit commands and keep the code clean.
The entry point for esbuild includes files like background.ts, content_script.ts, popup.ts, and injected.ts. In the public folder, the manifest.json defines the accessible resources and the URLs the extension applies to.
Esbuild configuration
In the esbuild configuration file, several important options are specified, such as bundle, minify, and sourcemap. It also defines the target runtime environment and the output directory for the compiled files.
Here is an example esbuild configuration:
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['src/background.ts', 'src/content_script.ts', 'src/popup.ts', 'src/injected.ts'],
bundle: true,
minify: true,
sourcemap: process.env.NODE_ENV !== 'production',
target: ['chrome120'],
outdir: 'public/build',
define: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
},
}).catch(() => process.exit(1));A note on target: that option tells esbuild what level of JavaScript syntax it should down level your code to. The original repository used ['chrome58'], a version from April 2017, which forced it to transform modern syntax for no reason. Since extensions run on an evergreen Chrome that updates itself, we can use a much more recent baseline like ['chrome120'], or even ['esnext'] if we want to emit the most modern JavaScript possible. The result is a cleaner and usually smaller bundle.
A note on CommonJS vs ESM. The example uses require('esbuild'), which is still a valid and current API. If your project is set up as an ESM module, the equivalent form is import * as esbuild from 'esbuild'. The esbuild.build({... }) call is identical in both cases.
Conclusion
Learning to use esbuild to build Chrome extensions can be a rewarding and quite efficient process compared to other build tools. This particular repository offers a good base to get started and to explore esbuild's capabilities in more depth.
I want to thank Marton Lederer for sharing his work. If you are interested in learning more about building Chrome extensions with esbuild, I recommend checking out his repository: martonlederer/esbuild react chrome extension.
Suggested exercises
- Clone the repository, check its
manifest.json, and if it is still MV2, migrate the basic fields to Manifest V3 (for example,service_workerinstead ofbackground.scriptsandactioninstead ofbrowser_action). - Change esbuild's
targetfrom['chrome120']to['esnext']and compare the resulting bundle size withyarn build. - Add a new entry point to the
entryPointsarray (for example anoptions.ts) and check that it shows up compiled inpublic/build.
3-point summary
- esbuild is a fast and efficient way to bundle Chrome extensions, and a base repository like Marton Lederer's is a good starting point.
- Every new extension must use Manifest V3; verify the repo's
manifest.jsonbefore loading it into a current Chrome. - The
targetoption controls JavaScript syntax down leveling; with an evergreen Chrome you can target a modern baseline like['chrome120']or['esnext'].
That's all, I hope this post is useful to you and that you can apply it to a project you have in mind. Leave me a comment if it helped or if you have any questions, and remember that if you liked it, you can also share it using the social links below. Good luck and keep building!
Sebastian Gomez
Creador de contenido principalmente acerca de tecnología.