# How to Build and Package the Markdown Here Extension Bundle

> Learn to build and package the Markdown Here extension bundle using the Node.js build script. Generate Chrome, Firefox, and Thunderbird zip archives efficiently.

- Repository: [Adam Pritchard/markdown-here](https://github.com/adam-p/markdown-here)
- Tags: how-to-guide
- Published: 2026-03-05

---

**The Markdown Here repository uses a Node.js build script at [`utils/build.js`](https://github.com/adam-p/markdown-here/blob/main/utils/build.js) to assemble browser-specific extension packages by filtering source files, adjusting manifests, and generating zip archives for Chrome, Firefox, and Thunderbird.**

The `adam-p/markdown-here` project provides a streamlined build system for creating cross-browser extension bundles from a shared source tree. To build and package the Markdown Here extension bundle correctly, developers run a single Node-based script that handles platform-specific file selection, manifest modifications, and archive compression automatically.

## Build System Architecture

The build orchestration lives entirely within [`utils/build.js`](https://github.com/adam-p/markdown-here/blob/main/utils/build.js), which defines the core directory structure and platform targeting logic. At **lines 15-18**, the script establishes three essential constants: `BASE_DIR` (repository root), `SRC_DIR` (the `src/` source tree), and `DIST_DIR` (the output folder where bundles are written).

Platform-specific input arrays determine which files enter each bundle. The script defines `CHROME_INPUT`, `FIREFOX_INPUT`, and `THUNDERBIRD_INPUT` globs at **lines 23-26** to segment the source code appropriately. A `skipFileRegexes` array defined at **lines 31-34** filters out test files, OS-specific temporary files, and other non-production assets before packaging begins.

## Step-by-Step Build Process

### 1. Directory Setup and File Filtering

The build begins by establishing the output structure and filtering rules. The `setUpZips()` function (**lines 74-92** and **94-111**) initializes three separate `archiver` zip streams—one for each target platform—and removes any previous bundle files from `dist/`. This ensures clean builds without stale artifacts contaminating the output.

File selection respects platform boundaries through the input globs defined earlier. When the `main()` function walks the `src/` directory tree (**lines 24-44** and **48-56**), it tests each file against the platform-specific input arrays. Valid files are passed to `addBuildFile()`, which routes them into the appropriate archive stream based on the current platform context.

### 2. Platform-Aware Manifest Handling

Chrome, Opera, and WebExtension-based browsers require strict manifest validation. When packaging for Chrome, the build script performs a critical transformation at **lines 62-67**: it strips the Firefox-only `browser_specific_settings` block from [`src/manifest.json`](https://github.com/adam-p/markdown-here/blob/main/src/manifest.json). This ensures Chrome accepts the manifest without validation errors. All other files are added to the archives unchanged, preserving the integrity of shared logic in `src/common/` and platform-specific scripts in `src/chrome/`, `src/firefox/`, and `src/thunderbird/`.

### 3. Archive Finalization

After `main()` completes its directory traversal and queues all eligible files, the script executes the finalization phase at **lines 46-48**. Each zip stream is sealed and piped to its target destination, producing three distinct bundles: `dist/chrome.zip` for Chrome/Opera/WebExtension, `dist/firefox.zip` for Firefox, and `dist/thunderbird.xpi` for Thunderbird.

## Running the Build Commands

Execute the following commands from the repository root to install dependencies and trigger the build process:

```bash

# Clone the repository

git clone https://github.com/adam-p/markdown-here.git
cd markdown-here

# Install Node.js dependencies (run once)

npm install --prefix utils

# Build all extension bundles

cd utils
node build.js

```

Upon completion, the console outputs:

```

Done! Built extensions written to ../dist

```

## Build Output and Bundle Structure

The build generates three platform-specific archives in the `dist/` directory:

- **`../dist/chrome.zip`** – Contains the Chrome, Opera, and generic WebExtension bundle
- **`../dist/firefox.zip`** – Contains the Firefox-specific extension package
- **`../dist/thunderbird.xpi`** – Contains the Thunderbird email client extension

Each bundle includes the shared `src/common/` assets (core logic, UI components, and icons) combined with platform-specific scripts from `src/chrome/`, `src/firefox/`, or `src/thunderbird/` as appropriate. The README.md "Building the Extension Bundles" section (**lines 70-76**) documents these steps for developer reference, mirroring the automated logic implemented in the build script.

## Summary

- **Single script orchestration**: The [`utils/build.js`](https://github.com/adam-p/markdown-here/blob/main/utils/build.js) file handles all packaging logic through its `main()` and `setUpZips()` functions.
- **Platform-specific filtering**: Input globs (`CHROME_INPUT`, `FIREFOX_INPUT`, `THUNDERBIRD_INPUT`) and `skipFileRegexes` ensure only relevant files enter each bundle.
- **Manifest compatibility**: The build automatically strips Firefox-specific `browser_specific_settings` from the manifest when targeting Chrome to prevent validation errors.
- **Three-bundle output**: Running `node build.js` produces `chrome.zip`, `firefox.zip`, and `thunderbird.xpi` in the `dist/` directory.
- **Source separation**: Shared code lives in `src/common/`, while browser-specific implementations reside in `src/chrome/`, `src/firefox/`, and `src/thunderbird/`.

## Frequently Asked Questions

### What Node.js version is required to build the Markdown Here extension?

The build script uses standard Node.js file system and stream APIs available in Node.js 8.0 and higher. As long as you can run `npm install` successfully in the `utils/` directory, the build process will execute correctly using the `archiver` package dependency specified in the project.

### Can I modify the build to exclude certain files from only one platform bundle?

Yes. Edit the `CHROME_INPUT`, `FIREFOX_INPUT`, or `THUNDERBIRD_INPUT` arrays at **lines 23-26** in [`utils/build.js`](https://github.com/adam-p/markdown-here/blob/main/utils/build.js). Each array accepts glob patterns that define which source directories and files are eligible for that specific platform. Files matching the `skipFileRegexes` pattern are excluded from all bundles automatically.

### Why does the Chrome bundle need manifest modifications while Firefox does not?

Chrome and WebExtension-standard browsers reject unknown manifest keys such as `browser_specific_settings`, which Firefox requires for extension signing and update URLs. The build script's logic at **lines 62-67** programmatically removes this Firefox-only block when assembling `chrome.zip`, ensuring the resulting manifest passes Chrome's strict validation without maintaining separate manifest files in the source tree.

### How do I verify the build output before distribution?

After running `node build.js`, inspect the contents of `dist/chrome.zip`, `dist/firefox.zip`, and `dist/thunderbird.xpi` using any archive utility. Verify that [`manifest.json`](https://github.com/adam-p/markdown-here/blob/main/manifest.json) contains platform-appropriate keys (no `browser_specific_settings` in the Chrome version) and that platform-specific scripts from `src/chrome/` or `src/firefox/` appear in their respective bundles while being absent from others.