What Is the make-manifest-plugin in the Vite Configuration?
The make-manifest-plugin is a custom Vite plugin that dynamically generates the Chrome extension's manifest.json file at build time, injects a development-only hot-reload script, and watches the source manifest.js for changes.
In the jonghakseo/chrome-extension-boilerplate-react-vite repository, the make-manifest-plugin serves as the critical bridge between your editable JavaScript manifest definition and the static JSON file required by Chrome browsers. This plugin handles cache-busting, file watching, and environment-specific script injection to streamline the extension development workflow.
How make-manifest-plugin Works
The plugin operates through five distinct stages during the Vite build lifecycle, each implemented in chrome-extension/utils/plugins/make-manifest-plugin.ts.
Watching the Source Manifest File
During the buildStart() hook, the plugin registers the source manifest.js file with Vite's watcher. This ensures that any modification to the manifest definition triggers an automatic rebuild.
In chrome-extension/vite.config.mts, the plugin is instantiated with an outDir parameter:
makeManifestPlugin({ outDir })
The plugin implementation adds the file to the watch list via this.addWatchFile(manifestFile) (lines 75-76 in make-manifest-plugin.ts).
Cache-Busting Manifest Imports
To prevent stale Node.js module caches during development, the plugin imports the manifest definition using a timestamp query string. The getManifestWithCacheBurst() function (lines 27-38) dynamically constructs the import path:
const manifest = await import(`${manifestFile}?${Date.now()}`);
This guarantees that every build incorporates the latest manifest changes without requiring a manual Vite server restart.
Injecting the Development Refresh Script
When the IS_DEV environment variable is true, the plugin automatically injects a refresh.js content script into the manifest's content_scripts array. The addRefreshContentScript() function (lines 41-47) appends this entry, while makeManifest() (lines 57-60) orchestrates the injection.
This script enables hot-module reloading (HMR) by establishing a WebSocket connection to the Vite dev server, allowing instant updates to content scripts and background pages without manual extension reloading.
Writing the Final manifest.json
After processing the manifest object, the plugin serializes it to JSON using ManifestParser.convertManifestToString(). This utility (provided by @extension/dev-utils) handles browser-specific quirks, particularly for Firefox compatibility.
The final file is written synchronously to the output directory:
writeFileSync(manifestPath, ManifestParser.convertManifestToString(manifest, color));
Copying the HMR Helper in Development
In development mode, the plugin copies the HMR helper script from packages/hmr/dist/lib/injections/refresh.js into the extension's output folder. Lines 63-67 wrap this script in an IIFE that tags it with a unique HMR ID, ensuring the refresh logic executes correctly within the Chrome extension context.
Configuring make-manifest-plugin in Your Vite Setup
The plugin integrates directly into the Vite configuration at chrome-extension/vite.config.mts. Import the plugin and conditionally include development plugins as needed:
import { defineConfig } from 'vite';
import makeManifestPlugin from './utils/plugins/make-manifest-plugin.js';
import { IS_DEV } from '@extension/env';
const outDir = /* path to dist folder */;
export default defineConfig({
plugins: [
// …other plugins
makeManifestPlugin({ outDir }), // ← generates manifest.json
IS_DEV && watchRebuildPlugin({ reload: true }) // optional HMR
],
// …rest of the config
});
To customize the output location, pass a custom outDir parameter:
makeManifestPlugin({ outDir: resolve(__dirname, '../custom-dist') })
Development vs. Production Output
The plugin produces different manifest.json outputs depending on the build environment.
Source Definition (manifest.js):
export default {
manifest_version: 3,
name: "My Extension",
version: "1.0.0",
// …other fields
};
Development Build (npm run dev):
The plugin injects the refresh.js content script:
{
"manifest_version": 3,
"name": "My Extension",
"version": "1.0.0",
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*", "<all_urls>"],
"js": ["refresh.js"]
}
]
}
Production Build (npm run build):
The HMR script is omitted when IS_DEV is false:
{
"manifest_version": 3,
"name": "My Extension",
"version": "1.0.0"
}
Summary
- The
make-manifest-plugingenerates the staticmanifest.jsonrequired by Chrome from your dynamicmanifest.jssource file. - It watches the source file for changes using
this.addWatchFile()in thebuildStart()hook, triggering automatic rebuilds. - Cache-busting via timestamp query strings ensures fresh manifest imports during development.
- In development mode, it injects
refresh.jsfrompackages/hmr/dist/lib/injections/refresh.jsand copies the HMR helper to enable live reloading. - The plugin uses
ManifestParser.convertManifestToString()to handle browser-specific serialization quirks before writing to disk.
Frequently Asked Questions
What file does make-manifest-plugin watch for changes?
The plugin watches the source manifest.js file located in the chrome-extension directory. In the buildStart() hook (lines 75-76 of make-manifest-plugin.ts), it calls this.addWatchFile(manifestFile) to register the file with Vite's watcher, ensuring any modification triggers a rebuild.
How does make-manifest-plugin handle hot module reloading?
During development, when IS_DEV is true, the plugin calls addRefreshContentScript() (lines 41-47) to inject a refresh.js entry into the manifest's content_scripts array. It also copies the actual HMR helper script from packages/hmr/dist/lib/injections/refresh.js into the output directory, wrapped in an IIFE with a unique HMR ID.
Where is the final manifest.json written?
The plugin writes the final manifest.json to the directory specified by the outDir parameter passed to makeManifestPlugin(). The write operation occurs via writeFileSync() at line 61 of make-manifest-plugin.ts, using the path constructed by joining outDir with the filename manifest.json.
Can I customize the output directory for manifest.json?
Yes, you can customize the output directory by passing a custom outDir option when instantiating the plugin in chrome-extension/vite.config.mts. For example: makeManifestPlugin({ outDir: resolve(__dirname, '../custom-dist') }). The plugin will emit both manifest.json and the development refresh.js (when applicable) to this location.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →