CRXJSCI/CDWindowsDevOpsOpen Source

How CI/CD in CRXJS Unlocked Windows Development for Browser Extensions

Published · 12 min read · By the Optymized team

CRXJS is the Vite plugin that powers modern Chrome extension development for thousands of developers. But it had a problem: it did not work reliably on Windows. We fixed that — with 27+ pull requests, a cross-platform CI/CD pipeline, and 6,110 lines of new code in a single PR. Here is the full story.

1. The problem: "works on my Mac"

CRXJS is an open-source Vite plugin with 3.9k+ GitHub stars that makes building extensions for Chrome-based browsers feel like building a modern web app. It handles HMR, manifest parsing, content scripts, and the entire build pipeline.

But like many open-source projects born on macOS and Linux, it had a blind spot: Windows. The CI ran only on Ubuntu. Tests were never validated on Windows. Path handling assumed forward slashes. File copying used Unix-specific behaviors. Snapshot tests captured platform-dependent output.

For the significant portion of developers who work on Windows — and for enterprise teams where Windows is the mandated platform — this meant dealing with cryptic build failures, broken HMR, and missing public files with no clear path to fixing them.

2. Making CRXJS build and test on Windows

We have been developing Chrome extensions on Windows since 2021 using CRXJS. We knew where the bugs were because we hit them daily. When we became contributors to the project, making it work cross-platform was the first thing we tackled.

The approach was straightforward: add Windows to the CI matrix, run the entire test suite, and fix everything that fails. The reality was more complex — the flagship PR ( #1008 ) ended up touching 45 files with 6,110 additions and 3,926 deletions.

6,110
Lines added
3,926
Lines removed
45
Files changed
27+
Total PRs

3. What actually broke (and why)

Windows is not just "Linux with a different UI." The file system, path separators, line endings, and file locking behavior are fundamentally different. Here is what we found and fixed:

Public files not being copied

Static assets like .ico, .svg, and .js files in the public directory were not being copied to the build output on Windows. The file copy logic used path patterns that worked on Unix but failed silently on Windows. We first identified this in 2024 and finally fixed it as part of the Windows CI work.

HMR not triggering on Windows

Vite's HMR (Hot Module Replacement) relies on file system events to detect changes. On Windows, fs.copy() alone does not always update the file's modified timestamp the way HMR expects. We had to modify the code to explicitly touch the file (update its modified date) after copying, ensuring HMR picks up changes reliably.

Path separator differences

Windows uses backslashes (\) while Unix uses forward slashes (/). Any path comparison, file resolution, or manifest entry that hard-coded forward slashes broke on Windows. We normalized path handling across the codebase.

Platform-sensitive snapshots

Svelte's add_location() function includes character positions that depend on line endings. Windows uses \r\n (2 bytes) while Unix uses \n (1 byte), shifting character positions and making snapshot tests fail. We implemented platform-agnostic snapshot scrubbing and fixed base64 sourcemap placeholders that were accidentally committed.

CI timeout issues

Windows CI runners are slower than Ubuntu. E2E tests that ran fine on Ubuntu hit timeout limits on Windows. The existing Ubuntu e2e tests were also flaky due to timeout issues, making it harder to verify the Windows PR. We increased timeouts, stabilized the Ubuntu tests, and added configurable timeout limits across all CI jobs.

4. The CI/CD pipeline we built

Beyond the Windows fix, we restructured the entire CI/CD pipeline for CRXJS. Here is what the pipeline looks like now:

Cross-platform matrix

Every PR and push to main runs tests on both Ubuntu and Windows. The matrix ensures that platform-specific regressions are caught immediately, not months later when a Windows user reports a bug.

Multi-Vite version testing

We consolidated separate workflows into a single matrix-based CI that tests across Vite 3, 6, 7, and 8 ( #1093). One workflow definition, full version coverage. When Vite 8 beta dropped, we had CI support ready on day one ( #1096).

Timeout guards

CI jobs now have configurable timeout limits ( #1106). No more stuck jobs burning CI minutes. If a test hangs, it fails fast with a clear error instead of silently consuming resources.

Automated release notifications

Every npm publish from the changeset pipeline triggers a Discord notification ( #1094). The community knows about new releases instantly.

CI on CI changes

We added a trigger that runs the full build when the CI workflow definition itself changes ( #1025). CI changes are code changes — they deserve the same validation.

5. 27 pull requests: the full contribution list

Our contributions to CRXJS go beyond CI/CD. Here are the key PRs, followed by the complete list:

Key contributions

#1008+6,110 / -3,926 lines

feat: make the project build and test on windows

The flagship PR. Added Windows CI matrix, fixed path handling across the codebase, resolved platform-sensitive snapshot differences (Svelte line endings), fixed public file copying on Windows, and made HMR work correctly with Windows file system behavior.

Unlocked Windows development for the entire CRXJS community

#1093Matrix CI

refactor: consolidate Vite version tests into matrix-based CI

Replaced duplicated CI workflows with a single matrix-based pipeline testing across Vite 3, 6, 7, and 8. Reduced CI config complexity while increasing coverage.

One workflow definition covers all Vite versions

#1096Vite 8 beta

feat: add Vite 8.0.0-beta.7 support and CI testing

Added Vite 8 beta support to CRXJS before the official release, ensuring day-one compatibility for extension developers.

CRXJS users get Vite 8 support from day one

#1106CI reliability

ci: add timeout limits to CI jobs

Added configurable timeout limits to prevent CI jobs from hanging indefinitely. A small change with big impact on CI reliability and cost.

No more stuck CI jobs burning minutes

#1094DevX

feat: add Discord notifications after changeset publish

Automated release notifications to the CRXJS Discord community. Every npm publish now triggers a notification so developers know when to upgrade.

Community knows about releases instantly

#1019DX setup

feat: make crxjs development easier by providing dev setup

Streamlined the contributor onboarding experience with dev container configs and setup scripts. Lowering the barrier for new contributors.

New contributors can start coding in minutes

All 27 pull requests

#1110feat: add HMR support for CSS declared in manifest content_scripts
#1109fix: copy CSS files declared in manifest content_scripts to output
#1106ci: add timeout limits to CI jobs
#1105ci: test plugin across Vite 3, 6, 7, and 8-beta versions with IIFE support
#1103chore: ignore @crxjs/vite-compat-test in changesets
#1102feat: add IIFE support for main-world content scripts
#1099fix: UnoCSS/TailwindCSS HMR issues with virtual CSS modules
#1098fix: resolve TypeScript types correctly for ESM and CJS consumers
#1096feat: add Vite 8.0.0-beta.7 support and CI testing
#1094feat: add Discord notifications after changeset publish
#1093refactor: consolidate Vite version tests into matrix-based CI
#1092fix: use configResolved to get plugins for rolldown-vite (Vite 7) compatibility
#1091fix: replace cheerio with node-html-parser and add vite peerDependency
#1084feat: respect vite build.manifest opt out setting in vite4+
#1057feat: add support for root url content script match in firefox
#1045fix: usage of newer picocolor api when using old dependency
#1033refactor(tests): remove all skipWaiting since they have no effects in e2e testing
#1032refactor(tests): remove not needed call to skipWaiting()
#1029chore: add missing patch
#1028tests: add favicon chrome api e2e test
#1026feat: make vite inspect plugin run also on build test cases
#1025feat: run ci/cd build on build definition PRs
#1019feat: make crxjs development easier by providing dev setup
#1008feat: make the project build and test on windows

6. What this means for your team

This is not just an open-source contribution story. It directly reflects how we approach DevOps for our clients:

Cross-platform by default

If your team uses Windows, your extension development pipeline works on Windows. No workarounds, no "just use WSL," no second-class experience. We have done the hard work of making it cross-platform at the tool level.

CI/CD that catches real bugs

Matrix-based CI testing across platforms and Vite versions means we catch regressions before they reach your extension users. The same approach we built for CRXJS is what we use for client projects.

Deep tooling knowledge

When you hire a team that contributes to the build tools they use, you get a team that can debug at every level of the stack. Not just "the extension does not work" — but exactly why it does not work, down to the Vite plugin internals, the file system behavior, and the CI runner configuration.

We do not just use open-source tools. We improve them. That same engineering discipline is what we bring to every client project.

Need a team that understands the full stack?

From Vite plugin internals to cross-platform CI/CD — we build browser extensions with the same engineering rigor we bring to open-source. Let us build yours.

Who's behind this

Tomasz Dłuski

Tomasz Dłuski

Founder & CEO

Senior Software Engineer with 10+ years of experience. Previously part of a company that scaled from 5 to 50+ engineers. Now building Optymized — a company that combines enterprise project delivery experience with own SaaS products. Maintainer of CRXJS (3.9k GitHub stars), one of the most popular tools for building browser extensions.

Let's discuss your project

Whether you need a custom browser extension, a dedicated dev team, or technical consulting — let's find the best approach together.

or send us a message