MetaMask Development Build Tool
June 10, 2026 ยท View on GitHub
This tool is used to build the MetaMask extension for development purposes. It is not (yet) intended for production builds.
Usage
For usage, examples, and options, run the following command:
yarn webpack --help
To build the MetaMask extension, run the following command:
yarn webpack
This will create a dist/chrome directory containing the built extension. See usage for more options.
Dev server
yarn start
yarn start rebuilds on file changes and starts a webpack dev server on localhost:8080 that auto-reloads the extension's UI pages (popup, home, notification, sidepanel). The service worker, background page, and content scripts are not auto-reloaded โ reload the extension manually for changes to those.
Set options using a config.json file
You can skip using command line options and specify options using a JSON file
instead. You can use the same options as the command line, but in JSON form. For
example, to build a zip of the extension for Chrome and Firefox, create a
config.json file as follows (notice the use of an array for the browser
option):
{
"browser": ["chrome", "firefox"],
"zip": true
}
Then you can use it as follows:
yarn webpack --config config.json
And you can combine it with CLI options, too:
yarn webpack --config config.json --dry-run
Run yarn webpack --help for the list of options.
Set options using environment variables
You can use environment variables instead of command line options:
BUNDLE_MINIFY=true yarn webpack
Run yarn webpack --help for the list of options.
Note: multiple array options cannot be set this way, due to this bug in yargs: https://github.com/yargs/yargs/issues/821
SOURCE_DATE_EPOCH is also supported for reproducible zip
artifacts. It is the standard unprefixed environment variable, specified as a Unix timestamp in seconds:
SOURCE_DATE_EPOCH=1711141205 yarn webpack --zip
When --zip is used, zip entry modification times are resolved from SOURCE_DATE_EPOCH when set, then the latest git
commit timestamp, then a deterministic fallback.
CLI Arguments
--env (alias: -e)
Explicitly sets the build environment for Sentry reporting and feature flags. If not specified, it is auto-detected from git.
| Value | When Used |
|---|---|
production | Must be explicitly set (never auto-detected) |
development | --mode development |
testing | --test flag |
staging | main branch on CI |
release-candidate | release/* branch on CI |
pull-request | pull_request GitHub event |
other | Local builds or any other context (default) |
Examples:
# Local development (auto-detects as 'other')
yarn webpack --mode production
# Explicit production (for actual releases only)
yarn webpack --mode production --env production
# Check what environment will be used
yarn webpack --mode production --dry-run
You can also combine environment variables with --config and CLI options:
BUNDLE_MINIFY=true yarn webpack --config config.json --dry-run
Cache Invalidation
The cache is invalidated when the build tool's code itself changes, or when the package.json file changes. The cache
is keyed by the effective options, so changing the options will also invalidate the cache. Not all options affect
the cache, but most do. Search for "cacheKey" in ./utils/cli.ts to see which options affect the cache.
Tips
- You can use the
--configflag to specify your own JSON config file to use as the build configuration. This is useful if you want to customize the defaults - You can specify options via environment variables by prefixing the option name with
BUNDLE_, e.g.,BUNDLE_BROWSER=opera yarn webpackon *nix. - don't run the build process with the Node Debugger attached; it will make things build much more slowly.
Development
Debugging the Build Process
Webpack makes use of a cache to speed up builds. If you encounter issues with the build tool, try clearing the cache by running the following command:
yarn webpack:clearcache
You can also avoid using the cache by setting the --no-cache option.
yarn webpack --no-cache
Please to file an issue if you do encounter issues!
Linting
Linting is exactly the same as the rest of the MetaMask project. To lint the build tool, run the following command:
yarn lint
That said, the webpack build has its own eslint configuration that overrides some restrictive rules that either: don't work well when optimizing for performance, or disable JavaScript features that are useful and generally necessary.
Testing
To run the build tool's test suite, run the following command:
yarn test:unit:webpack
This will run the test suite for the build tool. These tests are also run as part of the MetaMask test suite in CI.
To output an HTML, JSON, and text coverage reports, run the following command:
yarn test:unit:webpack:coverage
Test coverage should be around 100% for the build tool, exceptions are made for some edge cases that are overly difficult or complex to test, like exceptions.
Testing uses node's built-in node:test and node:assert modules instead of jest/mocha.
Unit tests are organized in development/webpack/test and are named *.test.ts, where * is the name of the file being
tested. This is a guideline and not a rule.
When checking coverage its is sometimes good to check if your coverage is intentional. One way to do that is to run the test coverage on a single test file. This can be done by running the following command:
yarn nyc --reporter=html tsx --test development/webpack/test/your-test-file.test.ts
Performance
The build tool only exists to build the project quickly. Don't make it slow. If you're adding a feature that makes the build tool slower, go for a walk and maybe don't come back until you change your mind.
Some things that might make the build tool slower:
- using JavaScript (this tool is only fast because it uses SWC for compilation, which is written in Rust)
- requiring/importing large libraries
- functional programming paradigms (JavaScript is not Haskell after all)
- like chaining map, filter, reduce, etc. when a single loop would do.
- try to avoid looping over the same data/file multiple times
- using async IO when sync IO would do
- non-blocking IO is great, but not when it's the only IO happening at the time and we don't care about blocking the main process.
- launching shells, workers, or other processes without measuring the cost
- unnecessary IO
- validation, linting, or other checks that are not necessary
If you must add something that slows it down, consider putting it behind a flag. If it must be in the default mode, try to run it in parallel with other tasks.
The Cache
The build process uses a cache to speed up successive builds. The cache is stored in the node_modules/.cache/webpack
directory.
The cache is slow. Very slow. It takes about 50% of the total time just to create the cache. But you shouldn't notice that because the caching step is pushed to a background process.
The way this works is by running the build in a background child process, and then detaching that child process from the parent process once the build is complete (and cache reconciliation and persistance begins).
Launching the build in a background process does take time, but its much less time than cache creation, so it works out.
The child process is run with its own TTY for stderr and stdout; the child's stdio dimensions are kept in sync with
the parent's, and all TTY features of the parent are available in the child (formatting, colors, cursors, etc.). On
Windows an IPC channel is used to communicate between the parent and child processes, on *nix this is done via signals.
The parent process listens for the child process and signal the parent, and when it does, the parent disconnects from
the child and shuts down, leaving the child to run in the background so the cache can be processed and persisted.
To do:
- define and wrangle the difference between
lockdownandlavamoatoptions. - MV3 support
- Service workers, used by MV3, must load dependencies via
importScripts. - there are existing webpack plugins that do this, but they are not yet integrated into this build tool and would require changes to our code and existing gulp-based build process to work.
- Service workers, used by MV3, must load dependencies via
- Make lavamoat work so we can run production builds
- Make LiveReload, Hot Module Reloading, and/or React Refresh work
- Make the build tool even faster (switch to RSPack once it hits 1.0.0?)
- enable
yarn webpack completion- It doesn't work with multiple-word commands (
yarn webpack ...) and is currently disabled.
- It doesn't work with multiple-word commands (
- implement overrides for icons and manifests fields for non-main builds
Ideas
- investigate using
DLLPluginfor even faster builds - make it work in Bun.js and/or Deno
- investigate adding a long-running background daemon mode for always up-to-date builds
- investigate adding linting, testing, validation, AI code review, etc.; especially in
--watchmode - investigate a "one CLI to rule them all" approach to MetaMask developer tooling and scripts
- allow changing some options without restarting the build process