Development Guide

September 17, 2026 · View on GitHub

Table of Contents

Prerequisites

ToolVersionNotes
Node.js>= 22.xLTS recommended
pnpm10.x (>=10.33.4 <11)npm install -g pnpm@10.33.4

Verify your setup:

node --version   # v22.x or higher
pnpm --version   # 10.x

Getting Started

Clone the Repository

Clone the private repository — see the README for repository and mirror details.

git clone https://github.com/AdGuardSoftwareLimited/ext-popup-blocker.git
cd ext-popup-blocker

Install Dependencies

pnpm install

Build the Userscript

Build a development version with logging enabled:

pnpm userscript-dev

The output is written to the build/ directory. Install the resulting build/userscript/popupblocker.user.js in your userscript manager (Tampermonkey, Violentmonkey, Greasemonkey, or AdGuard).

Development Workflow

Build Targets

The build system supports four targets, each producing output in the build/ directory:

  • userscript — the main popup blocker userscript
  • options — standalone options page (Preact SPA)
  • tests — browser-based Mocha test runner
  • bundle — all of the above combined

Available Scripts

CommandDescription
pnpm userscript-devBuild userscript with logging
pnpm userscript-betaBuild userscript (beta)
pnpm userscript-releaseBuild userscript (minified)
pnpm bundle:devBuild all targets (dev)
pnpm bundle:betaBuild all targets (beta)
pnpm bundle:releaseBuild all targets (release)
pnpm options-pageBuild the options page
pnpm options-page:betaBuild the beta options page
pnpm options-page:releaseBuild the release options page
pnpm testsBuild the test runner
pnpm lintRun ESLint
pnpm lint:mdRun Markdownlint

Versions are driven by CHANGELOG.md via the tag-from-changelog.yml reusable workflow — no manual bump script is needed.

You can also invoke the builder directly:

cross-env NODE_ENV=<channel> ts-node tasks/builder --target=<target>

where

  • <channel> is dev | beta | release;
  • <target> is userscript | options | tests | bundle.

Build-time Environment

The build channel is controlled by the NODE_ENV environment variable. The Rollup config injects these compile-time flags based on the channel:

Flagdevbeta / releasePurpose
DEBUGtruefalseEnable console logging
RECORDtruefalseEnable timeline recording
NO_PROXYfalsetrueDisable extra proxy wrapping

Beta and release builds are minified via Terser and have all debug code stripped.

Running Tests

Tests run in the browser — there is no CLI test runner.

  1. Build the test runner:

    pnpm tests
    
  2. Open test/index.html in a browser to execute the Mocha suite. It loads the compiled test bundle from test/build/index.js.

The test entry point is test/index.ts. Test files mirror the src/ structure (e.g., test/events/verify.ts tests src/events/verify.ts). Mocks are located in test/mocks/.

Linting

Run all linters before submitting changes:

pnpm lint        # ESLint
pnpm lint:md     # Markdownlint

Also verify that the project compiles without TypeScript errors:

pnpm userscript-dev

See the Code Quality section in AGENTS.md for coding standards and ESLint configuration details.

Commit Message Convention

See the Contribution Instructions in AGENTS.md for the required commit message format (AG-XXX prefix).

Common Tasks

Debugging the Options Page

  1. Build the options page:

    pnpm options-page
    
  2. Serve the build/ directory with a local HTTP server:

    cd build && npx serve .
    
  3. isOptionsPage in src/init/utils.ts already whitelists localhost: and http://127.0.0.1 addresses ending in /options.html, so no source change is needed for those. For any other address, add it there.

  4. Ensure the userscript (e.g., installed in AdGuard) also references the same local address.

  5. Verify that your ad blocker is filtering the debug page so the userscript is active.

Previewing the Options Page Deployment

The options page is deployed to GitHub Pages by .github/workflows/deploy-pages.yml when a release tag (v*) is pushed to the public mirror, so no manual step is needed for a normal release.

To preview exactly what the workflow publishes:

pnpm install --frozen-lockfile
rm -rf site
pnpm options-page:beta
mkdir -p site/beta/v1
cp -R build/. site/beta/v1/
pnpm options-page:release
mkdir -p site/release/v1
cp -R build/. site/release/v1/
cp -R build/. site/
# The workflow derives this from the release tag; any version works for a preview.
version=0.0.0
for dir in site/beta/v1 site/release/v1 site; do
  echo "version=${version}" > "$dir/build.txt"
done
find site -maxdepth 3 -type f | sort

To re-deploy without a release, run the Deploy options page to GitHub Pages workflow manually from the Actions tab of the public repository (AdguardTeam/PopupBlocker). Manual runs must pass the version input, which the workflow records in build.txt.

Manual Testing

An easy way to verify popup blocking is to visit a test page such as http://code.ptcong.com/better-js-popunder-script/ and click anywhere on the page. Popups should be blocked with a notification in the top-right corner.

Managing Exclusions

AdGuard exclusions are manually maintained in /exclusions.ts. Edit the file and rebuild.

TinyShield exclusions are auto-updated:

pnpm update-tinyshield-websites

This downloads the latest TinyShield domains and writes them to tasks/tinyShieldWebsites.json. Both sets of exclusions are added to the userscript metadata (@exclude) during the build.

Working with Locales

Translation strings live in src/locales/. To sync with the Crowdin translation platform:

pnpm locales:download   # Pull latest translations
pnpm locales:upload     # Push source strings

Running the Full CI Pipeline Locally

The GitHub Actions CI (.github/workflows/ci.yml) runs lint, test, and build inside Docker. To reproduce the same pipeline locally:

# Lint + test + dev build (matches the test-output Docker target)
DOCKER_BUILDKIT=1 docker build --progress plain --target test-output --output ./artifacts .

# Build release artifacts (matches the build-output Docker target)
DOCKER_BUILDKIT=1 docker build --progress plain --target build-output --output ./artifacts .

The compiled userscript files (popupblocker.user.js, popupblocker.meta.js, and assets/) appear in ./artifacts/.

Troubleshooting

  • ts-node errors on build — ensure you are using Node.js 22.x or higher. Older versions may lack required ES module support.

  • Tests page is blank — run pnpm tests, make sure test/build/index.js exists, and then open test/index.html.

  • Lint errors after pulling — run pnpm install to ensure dependencies are up to date, then pnpm lint.

  • Build fails with missing module — delete node_modules/, then run pnpm install again.

  • Options page not loading settings — the options page communicates with the userscript via message passing. Ensure the userscript is installed and active on the page you are testing.

Additional Resources