惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
V
Visual Studio Blog
C
Check Point Blog
Google DeepMind News
Google DeepMind News
S
SegmentFault 最新的问题
博客园 - 聂微东
量子位
T
Tailwind CSS Blog
罗磊的独立博客
I
InfoQ
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Y
Y Combinator Blog
L
LangChain Blog
小众软件
小众软件
Engineering at Meta
Engineering at Meta
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Security Latest
Security Latest
M
MIT News - Artificial intelligence
Know Your Adversary
Know Your Adversary
MongoDB | Blog
MongoDB | Blog
Google DeepMind News
Google DeepMind News
大猫的无限游戏
大猫的无限游戏
H
Help Net Security
爱范儿
爱范儿
T
The Exploit Database - CXSecurity.com
有赞技术团队
有赞技术团队
V
Vulnerabilities – Threatpost
Martin Fowler
Martin Fowler
A
Arctic Wolf
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 司徒正美
Cyberwarzone
Cyberwarzone
阮一峰的网络日志
阮一峰的网络日志
The Hacker News
The Hacker News
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
GbyAI
GbyAI
Latest news
Latest news
云风的 BLOG
云风的 BLOG
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
腾讯CDC
AWS News Blog
AWS News Blog
aimingoo的专栏
aimingoo的专栏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
Lohrmann on Cybersecurity
博客园 - Franky
S
Securelist
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Threatpost
美团技术团队

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Structuring Modern Browser Extensions for Maintainability and Scale
Ran Tayeb · 2026-05-17 · via DEV Community

 Browser extensions often begin as simple utilities—a few listeners in a background script, a small content script, and maybe a popup. But as the extension grows to include multiple runtime contexts, more messaging, state, and browser-specific behavior, the codebase can quickly become difficult to reason about.

That problem gets sharper in modern extensions because the application is split across isolated environments:

  • Background
  • Content
  • Managed UI such as popup or DevTools

These are not just folders. They are separate runtime contexts with different capabilities and lifecycles.

That’s where architecture starts to matter.

HexaJS is a structured, decorator-driven framework for browser extensions. It brings together Dependency Injection, tokens, controllers, handlers, and an AOT build pipeline so extension code stays modular, predictable, and scalable.

Why structure matters in browser extensions

In a typical extension, complexity grows in a few familiar ways:

  • message names become scattered strings
  • browser APIs leak into too many places
  • content and background responsibilities get mixed together
  • configuration is hidden in globals or utility modules
  • testing gets harder as dependencies become implicit

HexaJS addresses this by treating runtime boundaries as first-class architecture.

Its docs describe a model where Background, Content, and UI each run with their own generated bootstrap and isolated DI container. That means a class valid in one context is not automatically valid in another.

Dependency Injection with context awareness

HexaJS uses a lightweight decorator-based DI model.

A service can be declared with an explicit runtime context:

import { Injectable, HexaContext } from '@hexajs-dev/common';

@Injectable({ context: HexaContext.Background })
export class TabQueryService {
  getActiveIdFromTabs(tabs: Array<{ id?: number }>): number {
    return tabs[0]?.id ?? -1;
  }
}

Enter fullscreen mode Exit fullscreen mode

This is important because browser extensions are inherently multi-context. A background service should not accidentally be injected into content or UI code if it depends on background-only capabilities.

HexaJS validates those boundaries during its AOT build step.

So DI here is not just about cleaner constructors—it is also about enforcing architectural correctness before runtime.

Tokens make configuration explicit

Not every dependency is a class. Some are values: platform, build mode, feature flags, or API URLs.

HexaJS supports token-based injection for those cases.

Built-in system tokens include values like HEXA_PLATFORM and HEXA_BUILD_MODE, and custom tokens can be created with createToken(...).

import { createToken, Inject, Injectable, HexaContext } from '@hexajs-dev/common';

export const API_BASE_URL = createToken(
  'API_BASE_URL',
  'https://api.example.com',
  HexaContext.Background
);

@Injectable({ context: HexaContext.Background })
export class ApiConfigService {
  constructor(@Inject(API_BASE_URL) private apiBaseUrl: string) {}

  getBaseUrl(): string {
    return this.apiBaseUrl;
  }
}

Enter fullscreen mode Exit fullscreen mode

This keeps configuration explicit and testable.

Instead of pulling values from scattered globals, the dependency is visible in the constructor and can be overridden through configuration layers in the HexaJS build setup.

Background communication with Controllers

In HexaJS, background-side message endpoints are modeled with Controllers and Actions.

import { Controller, Action } from '@hexajs-dev/core';
import { TabsPort } from '@hexajs-dev/ports';

@Controller({ namespace: 'tabs' })
export class TabsController {
  constructor(private tabsPort: TabsPort) {}

  @Action('active')
  async activeTab(): Promise<{ tabId: number }> {
    const tabs = await this.tabsPort.queryTabs({ active: true, currentWindow: true });
    return { tabId: tabs[0]?.id ?? -1 };
  }
}

Enter fullscreen mode Exit fullscreen mode

This is one of the biggest corrections from the original draft:

  • it is @Controller({ namespace: '...' }), not @Controller('...')
  • it is @Action('...'), not @Message('...')

The resulting route is effectively namespaced as:

tabs:active

Enter fullscreen mode Exit fullscreen mode

That makes background communication more structured than manually wiring chrome.runtime.onMessage handlers or large switch statements.

HexaJS also recommends using ports instead of direct chrome.* or browser.* API calls, which keeps controller logic focused on orchestration rather than browser-specific plumbing.

Content behavior with Handlers

On the content side, HexaJS uses Handlers and Handle decorators.

import { Handler, Handle } from '@hexajs-dev/core';
import { LoggerService } from '../services/logger.service';
import { MyContentEntry } from './content';

@Handler({ namespace: 'tabs', Contents: [MyContentEntry] })
export class TabsHandler {
  constructor(private logger: LoggerService) {}

  @Handle('active-id')
  onActiveId(payload: { tabId: number }): { ok: boolean } {
    this.logger.log('Active tab id from background:', payload.tabId);
    return { ok: true };
  }
}

Enter fullscreen mode Exit fullscreen mode

This is another important fix from the original version:

  • it is @Handler({ namespace, Contents }), not @Handler('page')
  • it is @Handle('...'), not @Action('...') inside content handlers

The Contents option is especially useful because content scripts are often more fragmented than background logic. A single extension may have multiple content entry classes for different sites or page families, and HexaJS lets handlers bind intentionally to those entries.

That makes large multi-site extensions much easier to organize.

Clean transport across contexts

A common problem in browser extension code is that transport logic ends up mixed into business logic. Raw chrome.runtime.sendMessage(...) calls get scattered everywhere, and eventually every feature carries its own messaging details.

HexaJS pushes toward structured routing instead.

For UI surfaces, the docs show using HexaUIClient via DI:

import { inject } from '@hexajs-dev/common';
import { HexaUIClient } from '@hexajs-dev/ui';

const uiClient = inject(HexaUIClient);

const result = await uiClient.sendMessage('tabInfo:current', {});
console.log('Active tab metadata:', result);

Enter fullscreen mode Exit fullscreen mode

This is another correction from the original example: I could not verify HexaContentClientService in the repo/docs you pointed me to, so I would avoid naming it in the post unless you specifically want to introduce it separately and confirm it exists in your public API.

What is clearly documented is the idea that UI and content layers communicate through structured routes, while browser-specific behavior is delegated through ports and handled in the appropriate context.

Why this scales better

This kind of structure helps browser extensions scale in a few practical ways:

1. Better separation of concerns

Controllers stay background-focused. Handlers stay content-focused. Services hold reusable logic. Tokens represent values and configuration.

2. Easier testing

DI makes dependencies explicit, so services and ports are easier to mock.

3. Safer refactoring

When route definitions and context boundaries are structured, it becomes much easier to move code without accidentally crossing runtime boundaries.

4. Reduced browser-specific coupling

Ports isolate platform details so the rest of the application can focus on behavior.

5. Long-term maintainability

The framework is designed less for the fastest possible hello-world and more for the codebase you can still understand months later.

Final thought

Browser extensions are no longer tiny scripts.

They are multi-context applications with isolated runtimes, lifecycle boundaries, and transport concerns. If the architecture does not reflect that reality, complexity spreads fast.

HexaJS approaches that problem with a structured model:

  • Dependency Injection for explicit dependencies
  • Tokens for values and configuration
  • Controllers for background routes
  • Handlers for content-side behavior
  • AOT analysis for context-aware validation and generated wiring

The result is a browser extension codebase that is easier to reason about, easier to test, and easier to scale.

If you’re building modern browser extensions and want a more structured approach, check out HexaJS.


Quick reference snippets

Background service

import { Injectable, HexaContext } from '@hexajs-dev/common';

@Injectable({ context: HexaContext.Background })
export class TabQueryService {
  getActiveIdFromTabs(tabs: Array<{ id?: number }>): number {
    return tabs[0]?.id ?? -1;
  }
}

Enter fullscreen mode Exit fullscreen mode

Token injection

import { Inject, Injectable, HEXA_PLATFORM } from '@hexajs-dev/common';

@Injectable()
export class PlatformLabelService {
  constructor(@Inject(HEXA_PLATFORM) private platform: string) {}

  getLabel(): string {
    return `running on ${this.platform}`;
  }
}

Enter fullscreen mode Exit fullscreen mode

Background controller

import { Controller, Action } from '@hexajs-dev/core';
import { TabsPort } from '@hexajs-dev/ports';

@Controller({ namespace: 'tabs' })
export class TabsController {
  constructor(private tabsPort: TabsPort) {}

  @Action('active')
  async activeTab(): Promise<{ tabId: number }> {
    const tabs = await this.tabsPort.queryTabs({ active: true, currentWindow: true });
    return { tabId: tabs[0]?.id ?? -1 };
  }
}

Enter fullscreen mode Exit fullscreen mode

Content handler

import { Handler, Handle } from '@hexajs-dev/core';
import { MyContentEntry } from './content';

@Handler({ namespace: 'tabs', Contents: [MyContentEntry] })
export class TabsHandler {
  @Handle('active-id')
  onActiveId(payload: { tabId: number }): { ok: boolean } {
    return { ok: true };
  }
}

Enter fullscreen mode Exit fullscreen mode