Architecture principles
Dependency rule
Dependencies point inward. Core has zero dependencies on UI or infrastructure.
Layer isolation
Each package has a single responsibility with well-defined interfaces.
Testability
Pure business logic in core enables isolated unit testing.
Type safety
TypeScript enforces contracts across package boundaries.
Package architecture
The monorepo consists of five packages organized by responsibility:Core package
Location:src/core/Purpose: Domain logic, state management, and business rules
Directory structure
Directory structure
Key technologies
Key technologies
- Redux Toolkit: State management with minimal boilerplate
- redux-observable: Side effect management using RxJS operators
- RxJS: Reactive programming for async operations
- TypeScript: Compile-time type checking
Design patterns
Design patterns
Use cases: Implement single business operations as pure functions. Example:Epics: Orchestrate async workflows and coordinate use cases:Service interfaces: Define contracts without implementation:
Background package
Location:src/background/Purpose: Extension runtime, service implementations, and infrastructure
Directory structure
Directory structure
Key responsibilities
Key responsibilities
- Store initialization: Creates Redux store with core reducers and epics
- Service wiring: Injects concrete implementations into core use cases
- Extension events: Listens to browser APIs (tabs, network, downloads)
- FFmpeg integration: Loads and manages
ffmpeg.wasmfor video merging - Manifest compatibility: Adapts between MV2 background pages and MV3 service workers
Dependencies
Dependencies
Background scripts should only coordinate use cases from core. Keep business logic in the core package.
Popup package
Location:src/popup/Purpose: React-based user interface
Directory structure
Directory structure
Key features
Key features
- React Router: Multi-page navigation within the popup
- React Redux: Connects to the store via
webext-redux - Design system: Imports components from
@hls-downloader/design-system - Storybook: Component development and documentation
- Tailwind CSS: Utility-first styling
Store connection
Store connection
The popup connects to the background store using Actions dispatched in the popup are forwarded to the background, which applies them to the main store.
webext-redux:Run
pnpm storybook to develop components in isolation before integrating them into the extension.Design system package
Location:src/design-system/Purpose: Shared UI component library
Directory structure
Directory structure
Component library
Component library
Built on top of:
- Radix UI: Accessible, unstyled component primitives
- Tailwind CSS: Utility-first styling framework
- tailwindcss-animate: Animation utilities
- class-variance-authority: Type-safe variant management
- Lucide React: Icon library
Styling consistency
Styling consistency
All popup components should use the design system to ensure:
- Consistent visual language
- Accessible components out of the box
- Reduced duplication of styles
- Easier maintenance and updates
Assets package
Location:src/assets/Purpose: Static resources and manifests
Directory structure
Directory structure
Manifest variants
Manifest variants
MV2 (manifest-mv2.json):
- Uses persistent background page
- Required for Firefox (full support)
- Compatible with legacy Chromium browsers
- Uses service worker for background script
- Uses offscreen document for FFmpeg
- Required for modern Chrome, Edge, Brave, Arc
Data flow
The extension follows a unidirectional data flow pattern:Dependency graph
The dependency relationships between packages:The core package has no dependencies on other workspace packages. This isolation enables pure unit testing.
Build order
Packages must build in dependency order:Background and Popup (parallel)
Build both packages simultaneously since they don’t depend on each other
Extension communication
The extension uses multiple communication patterns:Store synchronization
Store synchronization
webext-redux keeps popup and background stores in sync:- Background hosts the main store
- Popup has a proxy store
- Actions from popup are forwarded to background
- State changes broadcast to all connected popups
Message passing
Message passing
Direct communication via
browser.runtime.sendMessage:Storage API
Storage API
Persistent settings via
browser.storage.local:Best practices
When adding a feature:
- Create the use case in
src/core/src/use-cases/ - Add an epic in
src/core/src/controllers/to orchestrate it - Implement required services in
src/background/src/services/ - Connect UI components in
src/popup/src/components/
Keep styling consistent by using components from the design system. Only create new components when existing ones don’t fit your needs.
Next steps
Building
Learn how to build packages and create extension archives
Testing
Write and run tests for each package