@zigrivers/scaffold 3.8.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +73 -8
  2. package/content/knowledge/browser-extension/browser-extension-architecture.md +195 -0
  3. package/content/knowledge/browser-extension/browser-extension-content-scripts.md +264 -0
  4. package/content/knowledge/browser-extension/browser-extension-conventions.md +156 -0
  5. package/content/knowledge/browser-extension/browser-extension-cross-browser.md +229 -0
  6. package/content/knowledge/browser-extension/browser-extension-dev-environment.md +247 -0
  7. package/content/knowledge/browser-extension/browser-extension-manifest.md +220 -0
  8. package/content/knowledge/browser-extension/browser-extension-project-structure.md +183 -0
  9. package/content/knowledge/browser-extension/browser-extension-requirements.md +107 -0
  10. package/content/knowledge/browser-extension/browser-extension-security.md +202 -0
  11. package/content/knowledge/browser-extension/browser-extension-service-workers.md +265 -0
  12. package/content/knowledge/browser-extension/browser-extension-store-submission.md +155 -0
  13. package/content/knowledge/browser-extension/browser-extension-testing.md +270 -0
  14. package/content/knowledge/data-pipeline/data-pipeline-architecture.md +175 -0
  15. package/content/knowledge/data-pipeline/data-pipeline-batch-patterns.md +263 -0
  16. package/content/knowledge/data-pipeline/data-pipeline-conventions.md +176 -0
  17. package/content/knowledge/data-pipeline/data-pipeline-dev-environment.md +350 -0
  18. package/content/knowledge/data-pipeline/data-pipeline-orchestration.md +291 -0
  19. package/content/knowledge/data-pipeline/data-pipeline-project-structure.md +257 -0
  20. package/content/knowledge/data-pipeline/data-pipeline-quality.md +324 -0
  21. package/content/knowledge/data-pipeline/data-pipeline-requirements.md +145 -0
  22. package/content/knowledge/data-pipeline/data-pipeline-schema-management.md +295 -0
  23. package/content/knowledge/data-pipeline/data-pipeline-security.md +326 -0
  24. package/content/knowledge/data-pipeline/data-pipeline-streaming-patterns.md +280 -0
  25. package/content/knowledge/data-pipeline/data-pipeline-testing.md +406 -0
  26. package/content/knowledge/ml/ml-architecture.md +172 -0
  27. package/content/knowledge/ml/ml-conventions.md +209 -0
  28. package/content/knowledge/ml/ml-dev-environment.md +299 -0
  29. package/content/knowledge/ml/ml-experiment-tracking.md +285 -0
  30. package/content/knowledge/ml/ml-model-evaluation.md +256 -0
  31. package/content/knowledge/ml/ml-observability.md +253 -0
  32. package/content/knowledge/ml/ml-project-structure.md +216 -0
  33. package/content/knowledge/ml/ml-requirements.md +138 -0
  34. package/content/knowledge/ml/ml-security.md +188 -0
  35. package/content/knowledge/ml/ml-serving-patterns.md +243 -0
  36. package/content/knowledge/ml/ml-testing.md +301 -0
  37. package/content/knowledge/ml/ml-training-patterns.md +269 -0
  38. package/content/methodology/browser-extension-overlay.yml +82 -0
  39. package/content/methodology/data-pipeline-overlay.yml +70 -0
  40. package/content/methodology/ml-overlay.yml +70 -0
  41. package/dist/cli/commands/init.d.ts +13 -0
  42. package/dist/cli/commands/init.d.ts.map +1 -1
  43. package/dist/cli/commands/init.js +122 -2
  44. package/dist/cli/commands/init.js.map +1 -1
  45. package/dist/cli/commands/init.test.js +120 -0
  46. package/dist/cli/commands/init.test.js.map +1 -1
  47. package/dist/config/schema.d.ts +864 -48
  48. package/dist/config/schema.d.ts.map +1 -1
  49. package/dist/config/schema.js +53 -0
  50. package/dist/config/schema.js.map +1 -1
  51. package/dist/config/schema.test.js +166 -3
  52. package/dist/config/schema.test.js.map +1 -1
  53. package/dist/core/assembly/overlay-loader.test.js +33 -0
  54. package/dist/core/assembly/overlay-loader.test.js.map +1 -1
  55. package/dist/e2e/project-type-overlays.test.d.ts +2 -2
  56. package/dist/e2e/project-type-overlays.test.js +499 -33
  57. package/dist/e2e/project-type-overlays.test.js.map +1 -1
  58. package/dist/types/config.d.ts +10 -1
  59. package/dist/types/config.d.ts.map +1 -1
  60. package/dist/wizard/questions.d.ts +17 -1
  61. package/dist/wizard/questions.d.ts.map +1 -1
  62. package/dist/wizard/questions.js +72 -1
  63. package/dist/wizard/questions.js.map +1 -1
  64. package/dist/wizard/questions.test.js +135 -0
  65. package/dist/wizard/questions.test.js.map +1 -1
  66. package/dist/wizard/wizard.d.ts +13 -0
  67. package/dist/wizard/wizard.d.ts.map +1 -1
  68. package/dist/wizard/wizard.js +17 -1
  69. package/dist/wizard/wizard.js.map +1 -1
  70. package/package.json +1 -1
@@ -0,0 +1,156 @@
1
+ ---
2
+ name: browser-extension-conventions
3
+ description: Naming conventions for manifests, message action types, file organization, and i18n structure in browser extensions
4
+ topics: [browser-extension, conventions, naming, i18n, file-organization, messaging]
5
+ ---
6
+
7
+ Browser extensions accumulate technical debt faster than typical web apps because they span multiple execution contexts — content scripts, service workers, popup pages, options pages — each with distinct constraints. Consistent naming conventions and file organization make cross-context code navigable and reduce the cognitive overhead of working across these boundaries. Establish conventions before writing code.
8
+
9
+ ## Summary
10
+
11
+ Browser extension conventions cover four areas: manifest field naming (follow the WebExtensions spec naming exactly, never invent aliases), message action types (use `SCREAMING_SNAKE_CASE` with a `namespace/ACTION` pattern to prevent cross-feature collisions), file organization (separate each execution context into its own source directory), and i18n structure (`_locales/` with `messages.json` per locale, all user-visible strings externalized from day one). Apply these conventions uniformly from the first commit.
12
+
13
+ ## Deep Guidance
14
+
15
+ ### Manifest Naming Conventions
16
+
17
+ The `manifest.json` is not a place for creative naming. Every field name is defined by the WebExtensions specification. Use the canonical names exactly as specified:
18
+
19
+ - `manifest_version` — always `3` for new extensions targeting Manifest V3.
20
+ - `action` — the toolbar button (previously `browser_action` in MV2). Do not use `browser_action` in MV3 manifests.
21
+ - `background.service_worker` — the path to the service worker script. Not `background.scripts` (MV2 syntax).
22
+ - `content_scripts` — array of content script declarations. Each entry has `matches`, `js`, `css`, `run_at`.
23
+ - `host_permissions` — separate from `permissions` in MV3. Do not mix host patterns into the `permissions` array.
24
+ - `web_accessible_resources` — array of objects with `resources` and `matches`. MV3 requires the `matches` field; MV2 accepted a plain string array.
25
+
26
+ **Version string format:** Follow semantic versioning with exactly four dot-separated integers: `MAJOR.MINOR.PATCH.BUILD` (e.g., `1.2.3.0`). The Chrome Web Store enforces four-part version strings. Keep the manifest `version` field in sync with `package.json` `version` via a build script.
27
+
28
+ **Extension name and description:**
29
+ - `name`: Under 45 characters. This appears in the toolbar tooltip and store listing.
30
+ - `short_name`: Under 12 characters. Appears in constrained UI contexts. Define it explicitly rather than relying on automatic truncation.
31
+ - `description`: Under 132 characters. This is the store listing short description.
32
+
33
+ ### Message Action Type Naming
34
+
35
+ Cross-context messaging (`chrome.runtime.sendMessage`, `chrome.tabs.sendMessage`) requires a shared vocabulary for message types. Collisions between unrelated features produce hard-to-diagnose bugs because every message listener receives every message.
36
+
37
+ **Recommended pattern — namespace/ACTION:**
38
+
39
+ ```typescript
40
+ // Shared constants (src/shared/messages.ts)
41
+ export const Messages = {
42
+ // Popup → Background
43
+ POPUP_GET_STATUS: 'popup/GET_STATUS',
44
+ POPUP_TOGGLE_ENABLED: 'popup/TOGGLE_ENABLED',
45
+
46
+ // Content → Background
47
+ CONTENT_PAGE_LOADED: 'content/PAGE_LOADED',
48
+ CONTENT_REQUEST_CONFIG: 'content/REQUEST_CONFIG',
49
+
50
+ // Background → Content
51
+ BG_INJECT_OVERLAY: 'bg/INJECT_OVERLAY',
52
+ BG_CLEAR_STATE: 'bg/CLEAR_STATE',
53
+ } as const;
54
+
55
+ export type MessageType = typeof Messages[keyof typeof Messages];
56
+ ```
57
+
58
+ **Rules:**
59
+ - All message types are defined in one shared constants file imported by all contexts.
60
+ - Use `SCREAMING_SNAKE_CASE` for the constant key; use `namespace/ACTION` for the string value.
61
+ - Never use raw string literals for message types — always reference the constant.
62
+ - Each message type has exactly one handler. If multiple handlers respond to the same type, it is a design flaw.
63
+
64
+ **Message payload typing:**
65
+
66
+ ```typescript
67
+ type MessageMap = {
68
+ [Messages.POPUP_GET_STATUS]: { payload: undefined; response: StatusPayload };
69
+ [Messages.POPUP_TOGGLE_ENABLED]: { payload: { enabled: boolean }; response: void };
70
+ };
71
+ ```
72
+
73
+ Typed message maps prevent callers from passing incorrect payloads and give type-safe responses.
74
+
75
+ ### File Organization Conventions
76
+
77
+ Each execution context gets its own top-level source directory. Do not co-locate content script code with popup code — they have different APIs, different DOM access, and different lifecycle constraints.
78
+
79
+ **Source directory structure:**
80
+
81
+ ```
82
+ src/
83
+ background/ # Service worker entry and background logic
84
+ index.ts # Service worker entry point
85
+ handlers/ # Message handlers, one file per domain
86
+ alarms.ts # Alarm setup and handlers
87
+ content/ # Content scripts
88
+ index.ts # Content script entry point
89
+ injectors/ # DOM injection logic
90
+ observers/ # MutationObserver setup
91
+ popup/ # Popup page
92
+ index.html
93
+ index.ts
94
+ components/ # Popup-specific UI components
95
+ options/ # Options page
96
+ index.html
97
+ index.ts
98
+ shared/ # Code imported by multiple contexts
99
+ messages.ts # Message type constants
100
+ storage.ts # Storage key constants and typed wrappers
101
+ types.ts # Shared TypeScript interfaces
102
+ ```
103
+
104
+ **File naming:**
105
+ - Use `kebab-case` for all source files.
106
+ - Entry points for each context are always named `index.ts` / `index.html`.
107
+ - Handler files are named after the domain they handle: `tab-handlers.ts`, `storage-handlers.ts`.
108
+ - Never name a file `utils.ts` — name it after what it does: `url-helpers.ts`, `dom-sanitizer.ts`.
109
+
110
+ ### i18n Structure and Conventions
111
+
112
+ Internationalization in extensions is mandatory if you ever plan to submit to markets outside your primary language. The `_locales/` directory is the WebExtensions standard mechanism.
113
+
114
+ **Directory structure:**
115
+
116
+ ```
117
+ _locales/
118
+ en/
119
+ messages.json
120
+ es/
121
+ messages.json
122
+ fr/
123
+ messages.json
124
+ ```
125
+
126
+ **messages.json format:**
127
+
128
+ ```json
129
+ {
130
+ "extensionName": {
131
+ "message": "My Extension",
132
+ "description": "The name of the extension"
133
+ },
134
+ "popupToggleLabel": {
135
+ "message": "Enable on this site",
136
+ "description": "Label for the enable/disable toggle in the popup"
137
+ },
138
+ "statusEnabled": {
139
+ "message": "Active",
140
+ "description": "Status label when extension is enabled"
141
+ }
142
+ }
143
+ ```
144
+
145
+ **Conventions:**
146
+ - Message keys use `camelCase`. Never use dots or underscores — they suggest hierarchy that the flat message file does not support.
147
+ - Every message has a `description` field. Translators need context. Empty descriptions lead to mistranslations.
148
+ - Externalize all user-visible strings from day one, even if only targeting English initially. Retrofitting i18n into an extension that hardcodes strings is expensive.
149
+ - Reference messages in the manifest with `__MSG_keyName__` syntax: `"name": "__MSG_extensionName__"`.
150
+ - Reference messages in JavaScript with `chrome.i18n.getMessage('keyName')`.
151
+ - Reference messages in HTML with the `data-i18n` attribute pattern (requires a small initialization script in the popup to apply translations on load).
152
+
153
+ **Locale fallback:**
154
+ - The `_locales/en/` directory is the fallback locale. If a translation is missing in another locale, Chrome falls back to `en`.
155
+ - Always maintain the `en` locale as the source of truth.
156
+ - Use the `browser.i18n.getUILanguage()` API to detect the user's browser language for locale-specific logic beyond string translation.
@@ -0,0 +1,229 @@
1
+ ---
2
+ name: browser-extension-cross-browser
3
+ description: Using webextension-polyfill for API compatibility, manifest differences between Chrome and Firefox, browser-specific APIs, and managing a multi-browser build matrix
4
+ topics: [browser-extension, cross-browser, firefox, chrome, webextension-polyfill, compatibility, build-matrix]
5
+ ---
6
+
7
+ Browser extensions that target both Chrome and Firefox share most of their codebase, but the differences between the two platforms are significant enough to require explicit management. API namespaces differ, manifest syntax diverges in subtle ways, and some APIs exist only in Chrome or only in Firefox. A systematic cross-browser strategy prevents the "works in Chrome, broken in Firefox" class of bugs.
8
+
9
+ ## Summary
10
+
11
+ Use `webextension-polyfill` to normalize the `chrome.*` API (callback-based) to the `browser.*` API (Promise-based) and fill gaps between browsers. Maintain separate manifest files per browser target (or a shared base with environment-specific overrides) because Chrome uses `background.service_worker` while Firefox still supports `background.scripts` with persistent pages. Build separate artifacts for each target using a build matrix with environment variables. Test on both browsers before each release — API parity is high but not complete.
12
+
13
+ ## Deep Guidance
14
+
15
+ ### webextension-polyfill
16
+
17
+ Mozilla's `webextension-polyfill` wraps the `chrome.*` namespace (callback-based) with a `browser.*` namespace (Promise-based) that works in both Chrome and Firefox:
18
+
19
+ **Installation:**
20
+
21
+ ```bash
22
+ npm install webextension-polyfill
23
+ npm install -D @types/webextension-polyfill
24
+ ```
25
+
26
+ **Usage:**
27
+
28
+ ```typescript
29
+ // Without polyfill — Chrome callback API
30
+ chrome.storage.sync.get('key', (result) => {
31
+ console.log(result.key);
32
+ });
33
+
34
+ // With polyfill — Promise-based, works in Chrome and Firefox
35
+ import browser from 'webextension-polyfill';
36
+
37
+ const result = await browser.storage.sync.get('key');
38
+ console.log(result.key);
39
+ ```
40
+
41
+ Import `webextension-polyfill` once at the top of each context's entry point. All subsequent `browser.*` calls will be polyfilled in Chrome and will use Firefox's native Promise API in Firefox.
42
+
43
+ **What the polyfill covers:**
44
+ - All `chrome.*` → `browser.*` name normalization.
45
+ - Converts callback-based APIs to Promises.
46
+ - Fills some API gaps between Chrome and Firefox.
47
+
48
+ **What the polyfill does not cover:**
49
+ - APIs that only exist in Chrome (`chrome.declarativeNetRequest` advanced features, `chrome.sidePanel`, some scripting features).
50
+ - Manifest syntax differences — those require separate manifest files.
51
+ - Firefox's different extension signing and review requirements.
52
+
53
+ ### Manifest Differences
54
+
55
+ Chrome and Firefox have diverged enough that maintaining separate manifest files (or a build-time merge strategy) is safer than one shared manifest.
56
+
57
+ **Background script declaration:**
58
+
59
+ ```json
60
+ // Chrome (Manifest V3) — manifest.json
61
+ {
62
+ "background": {
63
+ "service_worker": "background.js",
64
+ "type": "module"
65
+ }
66
+ }
67
+ ```
68
+
69
+ ```json
70
+ // Firefox — manifest.json
71
+ // Firefox supports MV3 but still supports MV2 with persistent background pages
72
+ // Firefox MV3 uses background.scripts (plural) in some versions
73
+ {
74
+ "background": {
75
+ "scripts": ["background.js"],
76
+ "type": "module"
77
+ }
78
+ }
79
+ ```
80
+
81
+ Firefox's MV3 support is still maturing (as of 2025). Many production extensions targeting Firefox maintain an MV2 manifest for Firefox to avoid compatibility issues with Firefox's incomplete MV3 implementation.
82
+
83
+ **browser_specific_settings (Firefox only):**
84
+
85
+ ```json
86
+ // Firefox manifest requires a unique extension ID
87
+ {
88
+ "browser_specific_settings": {
89
+ "gecko": {
90
+ "id": "my-extension@example.com",
91
+ "strict_min_version": "109.0"
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ Chrome ignores `browser_specific_settings`. Firefox requires a `gecko.id` for extensions submitted to AMO — without it, the extension ID is auto-generated and changes between installs.
98
+
99
+ **Recommended build strategy — manifest merging:**
100
+
101
+ ```typescript
102
+ // scripts/build-manifests.ts
103
+ import baseManifest from './manifest.base.json';
104
+ import chromeOverrides from './manifest.chrome.json';
105
+ import firefoxOverrides from './manifest.firefox.json';
106
+
107
+ const chromeManifest = { ...baseManifest, ...chromeOverrides };
108
+ const firefoxManifest = { ...baseManifest, ...firefoxOverrides };
109
+
110
+ writeFileSync('dist/chrome/manifest.json', JSON.stringify(chromeManifest, null, 2));
111
+ writeFileSync('dist/firefox/manifest.json', JSON.stringify(firefoxManifest, null, 2));
112
+ ```
113
+
114
+ ### Browser-Specific API Differences
115
+
116
+ **APIs available in Chrome but not Firefox:**
117
+
118
+ | API | Chrome | Firefox | Notes |
119
+ |---|---|---|---|
120
+ | `chrome.sidePanel` | Yes (Chrome 114+) | No | Side panel UI in the browser window |
121
+ | `chrome.declarativeNetRequest` | Full | Partial | Firefox support is partial |
122
+ | `chrome.offscreen` | Yes (Chrome 109+) | No | Offscreen document for audio/clipboard |
123
+ | `chrome.readingList` | Yes | No | Reading list integration |
124
+ | `chrome.ttsEngine` | Yes | No | TTS engine extension |
125
+
126
+ **APIs available in Firefox but not Chrome:**
127
+
128
+ | API | Chrome | Firefox | Notes |
129
+ |---|---|---|---|
130
+ | `browser.menus` (full) | Partial | Yes | Firefox has richer context menu API |
131
+ | `browser.pkcs11` | No | Yes | Smart card access |
132
+ | `browser.theme` | No | Yes | Browser theme management |
133
+ | `browser.userScripts` | Limited | Yes (via MV2) | User script management |
134
+
135
+ **API behavior differences:**
136
+
137
+ - `chrome.tabs.query({ active: true })` — Returns an array in both browsers, but Chrome always returns a single-element array for the focused window; Firefox may return empty if no window is focused.
138
+ - `chrome.storage.sync` — Chrome syncs across devices via Google account. Firefox syncs via Firefox Sync. Storage limits differ slightly.
139
+ - `chrome.runtime.sendMessage` — Chrome throws if no listener is registered. Firefox returns a rejected Promise with a specific error code. Handle both in cross-browser code.
140
+ - Content script `matches` patterns — Both support the standard match pattern syntax, but Firefox has slightly stricter validation. Test patterns in both browsers.
141
+
142
+ ### Feature Detection Pattern
143
+
144
+ Rather than browser-sniffing, use feature detection for browser-specific APIs:
145
+
146
+ ```typescript
147
+ // Feature detection — works in both browsers
148
+ export const canUseSidePanel = 'sidePanel' in chrome;
149
+ export const canUseOffscreen = 'offscreen' in chrome;
150
+
151
+ // Use conditionally
152
+ if (canUseSidePanel) {
153
+ chrome.sidePanel.setOptions({ enabled: true });
154
+ } else {
155
+ // Fallback: open as a popup instead
156
+ chrome.action.setPopup({ popup: 'popup/index.html' });
157
+ }
158
+ ```
159
+
160
+ Avoid `navigator.userAgent` parsing to detect Chrome vs Firefox — it is fragile and breaks with Chromium-based browsers that are not Chrome.
161
+
162
+ ### Build Matrix Configuration
163
+
164
+ Structure the build to produce separate artifacts for each browser target:
165
+
166
+ ```json
167
+ // package.json scripts
168
+ {
169
+ "scripts": {
170
+ "build": "npm run build:chrome && npm run build:firefox",
171
+ "build:chrome": "BROWSER=chrome vite build --outDir dist/chrome",
172
+ "build:firefox": "BROWSER=firefox vite build --outDir dist/firefox",
173
+ "pack:chrome": "cd dist/chrome && zip -r ../../web-ext-artifacts/extension-chrome.zip .",
174
+ "pack:firefox": "web-ext build --source-dir dist/firefox --artifacts-dir web-ext-artifacts"
175
+ }
176
+ }
177
+ ```
178
+
179
+ ```typescript
180
+ // vite.config.ts — browser-conditional build
181
+ const browser = process.env.BROWSER ?? 'chrome';
182
+
183
+ export default defineConfig({
184
+ define: {
185
+ __BROWSER__: JSON.stringify(browser),
186
+ __IS_CHROME__: browser === 'chrome',
187
+ __IS_FIREFOX__: browser === 'firefox',
188
+ },
189
+ plugins: [
190
+ webExtension({
191
+ manifest: browser === 'chrome'
192
+ ? require('./manifest.chrome.json')
193
+ : require('./manifest.firefox.json'),
194
+ }),
195
+ ],
196
+ });
197
+ ```
198
+
199
+ ### Cross-Browser Testing Checklist
200
+
201
+ Run this checklist before every release:
202
+
203
+ - Extension loads without errors in `chrome://extensions` (Chrome) and `about:debugging` (Firefox).
204
+ - Content scripts inject correctly on target pages in both browsers.
205
+ - Storage read/write works in both browsers (test with `chrome.storage.sync` and `browser.storage.sync`).
206
+ - Message passing between popup ↔ background works in both browsers.
207
+ - Permissions are granted correctly on install in both browsers.
208
+ - Options page loads and saves settings in both browsers.
209
+ - Extension icon appears correctly at 16×16 in the toolbar (both browsers render icons differently at small sizes).
210
+ - `chrome.runtime.getURL()` returns correct paths in both browsers.
211
+ - Any browser-specific feature fallbacks activate correctly in the non-supporting browser.
212
+
213
+ ### Continuous Integration for Cross-Browser
214
+
215
+ Run automated tests against both browser targets in CI:
216
+
217
+ ```yaml
218
+ # .github/workflows/test.yml
219
+ jobs:
220
+ test:
221
+ strategy:
222
+ matrix:
223
+ browser: [chrome, firefox]
224
+ steps:
225
+ - run: npm run build:${{ matrix.browser }}
226
+ - run: npm run test:e2e -- --browser ${{ matrix.browser }}
227
+ ```
228
+
229
+ Puppeteer supports loading Chrome extensions. Playwright supports loading Chrome extensions and (with additional configuration) Firefox extensions. Both are suitable for automated cross-browser extension testing in CI.
@@ -0,0 +1,247 @@
1
+ ---
2
+ name: browser-extension-dev-environment
3
+ description: Build tooling with Webpack/Vite, hot reload via web-ext and crx-hotreload, and browser launch configuration for extension development
4
+ topics: [browser-extension, dev-environment, vite, webpack, hot-reload, web-ext, build]
5
+ ---
6
+
7
+ Browser extension development requires a different local setup than web app development. There is no dev server to navigate to — the extension must be loaded into a real browser instance, and changes require either a manual reload or a dedicated hot-reload tool. Getting this setup right at the start of the project eliminates the most friction-heavy part of the development loop.
8
+
9
+ ## Summary
10
+
11
+ Use Vite with `vite-plugin-web-extension` (or `@crxjs/vite-plugin` for Chrome-only projects) as the build tool — it handles multi-entry bundling, manifest processing, and dev-mode content script injection automatically. For hot reload in development, use `web-ext` (Mozilla's CLI tool, works for both Chrome and Firefox) with its `--watch` flag, which reloads the extension on file changes. For Chrome-specific hot reload, `crx-hotreload` provides a lightweight alternative. Use separate npm scripts for dev (with watch + auto-reload) and build (production bundle for store submission).
12
+
13
+ ## Deep Guidance
14
+
15
+ ### Build Tool: Vite with Extension Plugin
16
+
17
+ Vite is the recommended build tool for new browser extension projects. Its ES module native dev server is not used directly (extensions load from `dist/`, not a dev server), but Vite's build pipeline provides fast incremental rebuilds, TypeScript compilation, CSS processing, and tree shaking.
18
+
19
+ **vite-plugin-web-extension** (cross-browser, recommended):
20
+
21
+ ```bash
22
+ npm install -D vite vite-plugin-web-extension
23
+ ```
24
+
25
+ ```typescript
26
+ // vite.config.ts
27
+ import { defineConfig } from 'vite';
28
+ import webExtension from 'vite-plugin-web-extension';
29
+
30
+ export default defineConfig({
31
+ plugins: [
32
+ webExtension({
33
+ manifest: () => require('./manifest.json'),
34
+ // Automatically discovers entry points from manifest.json
35
+ // and configures multi-entry build
36
+ }),
37
+ ],
38
+ });
39
+ ```
40
+
41
+ `vite-plugin-web-extension` reads `manifest.json` and automatically derives all build entry points from `background.service_worker`, `content_scripts[].js`, `action.default_popup`, and `options_page` fields. No manual entry point configuration required.
42
+
43
+ **@crxjs/vite-plugin** (Chrome-only, with HMR support):
44
+
45
+ ```bash
46
+ npm install -D vite @crxjs/vite-plugin
47
+ ```
48
+
49
+ ```typescript
50
+ // vite.config.ts
51
+ import { defineConfig } from 'vite';
52
+ import { crx } from '@crxjs/vite-plugin';
53
+ import manifest from './manifest.json';
54
+
55
+ export default defineConfig({
56
+ plugins: [
57
+ crx({ manifest }),
58
+ ],
59
+ });
60
+ ```
61
+
62
+ `@crxjs/vite-plugin` additionally supports Hot Module Replacement for popup and options pages in Chrome, making UI development significantly faster. Note: Firefox support is limited and experimental with this plugin.
63
+
64
+ **Webpack alternative** (for projects with existing Webpack investment):
65
+
66
+ ```bash
67
+ npm install -D webpack webpack-cli copy-webpack-plugin ts-loader
68
+ ```
69
+
70
+ Webpack requires manual entry point configuration for each context. Use `copy-webpack-plugin` to copy `manifest.json`, `_locales/`, and `public/` assets to `dist/`. The manual configuration is verbose; prefer Vite unless the project already uses Webpack.
71
+
72
+ ### Hot Reload: web-ext
73
+
74
+ `web-ext` is Mozilla's official CLI for extension development. Despite being maintained by Mozilla, it works equally well with Chromium-based browsers.
75
+
76
+ **Installation:**
77
+
78
+ ```bash
79
+ npm install -D web-ext
80
+ ```
81
+
82
+ **Configuration file (web-ext-config.yml):**
83
+
84
+ ```yaml
85
+ sourceDir: ./dist
86
+ artifactsDir: ./web-ext-artifacts
87
+ build:
88
+ overwriteDest: true
89
+ run:
90
+ firefox: '/Applications/Firefox.app/Contents/MacOS/firefox'
91
+ # For Chrome:
92
+ # chromiumBinary: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
93
+ # chromiumProfile: ./dev-profile
94
+ startUrl:
95
+ - about:newtab
96
+ watchFiles:
97
+ - dist/**/*
98
+ ```
99
+
100
+ **Development workflow with web-ext:**
101
+
102
+ ```json
103
+ // package.json scripts
104
+ {
105
+ "scripts": {
106
+ "dev": "vite build --watch & web-ext run --config web-ext-config.yml",
107
+ "build": "vite build",
108
+ "build:watch": "vite build --watch",
109
+ "start:chrome": "web-ext run --target chromium --config web-ext-config.yml",
110
+ "start:firefox": "web-ext run --target firefox --config web-ext-config.yml"
111
+ }
112
+ }
113
+ ```
114
+
115
+ `vite build --watch` rebuilds `dist/` on every source change. `web-ext run --config web-ext-config.yml` watches `dist/` and reloads the extension in the browser when the built files change. Together they form a full hot-reload loop.
116
+
117
+ **What web-ext does on reload:**
118
+ - Reloads the extension manifest and background service worker.
119
+ - Reloads all open popup instances.
120
+ - Content scripts on already-open tabs are NOT automatically re-injected — you must navigate to a new page or manually reload the tab to test content script changes.
121
+
122
+ ### Hot Reload: crx-hotreload
123
+
124
+ For Chrome-only development, `crx-hotreload` is a lightweight alternative that adds hot reload by injecting a small background script into your extension:
125
+
126
+ ```bash
127
+ npm install -D crx-hotreload
128
+ ```
129
+
130
+ Add the hot reload script to your manifest in development:
131
+
132
+ ```json
133
+ // manifest.json (development variant)
134
+ {
135
+ "background": {
136
+ "service_worker": "background.js"
137
+ }
138
+ }
139
+ ```
140
+
141
+ ```typescript
142
+ // background/index.ts
143
+ if (process.env.NODE_ENV === 'development') {
144
+ // crx-hotreload polls for dist/ changes and triggers extension reload
145
+ import('crx-hotreload');
146
+ }
147
+ ```
148
+
149
+ `crx-hotreload` is simpler than `web-ext` but Chrome-only and does not handle Firefox or cross-browser testing.
150
+
151
+ ### Browser Launch Configuration
152
+
153
+ **Loading an unpacked extension in Chrome:**
154
+ 1. Navigate to `chrome://extensions`.
155
+ 2. Enable "Developer mode" (top-right toggle).
156
+ 3. Click "Load unpacked" and select the `dist/` directory.
157
+ 4. Note the assigned extension ID — it changes if you remove and re-add the extension, which breaks `chrome.runtime.getURL()` calls that embed the ID.
158
+
159
+ **Persistent dev profile for Chrome:**
160
+
161
+ Using a dedicated Chrome profile for development prevents the extension from interfering with your daily browsing and keeps the dev extension ID stable:
162
+
163
+ ```bash
164
+ # Launch Chrome with a dedicated dev profile
165
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
166
+ --user-data-dir="$(pwd)/.dev-profile" \
167
+ --load-extension="$(pwd)/dist" \
168
+ --no-first-run
169
+ ```
170
+
171
+ Add this to a `scripts/dev-chrome.sh` helper. The `--load-extension` flag auto-loads the extension at browser start, skipping the manual "Load unpacked" step.
172
+
173
+ **Loading in Firefox:**
174
+
175
+ ```bash
176
+ # Temporary install (survives until browser restart)
177
+ web-ext run --source-dir ./dist --firefox firefox
178
+
179
+ # Or via about:debugging
180
+ # Navigate to about:debugging → This Firefox → Load Temporary Add-on
181
+ # Select dist/manifest.json
182
+ ```
183
+
184
+ Temporary installs in Firefox are removed when the browser restarts. For persistent dev installs, create a signed development build via `web-ext build` and install the `.zip` as a permanent add-on.
185
+
186
+ ### Environment Variables and Build Modes
187
+
188
+ Use Vite's mode system to separate development and production builds:
189
+
190
+ ```typescript
191
+ // vite.config.ts
192
+ import { defineConfig } from 'vite';
193
+
194
+ export default defineConfig(({ mode }) => ({
195
+ define: {
196
+ __DEV__: mode === 'development',
197
+ __EXT_VERSION__: JSON.stringify(process.env.npm_package_version),
198
+ },
199
+ build: {
200
+ sourcemap: mode === 'development' ? 'inline' : false,
201
+ minify: mode !== 'development',
202
+ },
203
+ }));
204
+ ```
205
+
206
+ **Never ship development-only code to the store.** Use `__DEV__` guards around logging, hot-reload imports, and debug panels. Dead-code elimination in the production build will strip guarded code when `__DEV__` is `false`.
207
+
208
+ ### TypeScript Configuration for Extension Contexts
209
+
210
+ ```json
211
+ // tsconfig.json (base)
212
+ {
213
+ "compilerOptions": {
214
+ "target": "ES2022",
215
+ "module": "ESNext",
216
+ "moduleResolution": "bundler",
217
+ "strict": true,
218
+ "types": ["chrome"]
219
+ }
220
+ }
221
+ ```
222
+
223
+ ```json
224
+ // tsconfig.background.json (service worker — no DOM)
225
+ {
226
+ "extends": "./tsconfig.json",
227
+ "compilerOptions": {
228
+ "lib": ["ES2022", "WebWorker"],
229
+ "types": ["chrome"]
230
+ },
231
+ "include": ["src/background/**/*", "src/shared/**/*"]
232
+ }
233
+ ```
234
+
235
+ ```json
236
+ // tsconfig.content.json (content scripts — DOM access)
237
+ {
238
+ "extends": "./tsconfig.json",
239
+ "compilerOptions": {
240
+ "lib": ["ES2022", "DOM"],
241
+ "types": ["chrome"]
242
+ },
243
+ "include": ["src/content/**/*", "src/shared/**/*"]
244
+ }
245
+ ```
246
+
247
+ Install the Chrome types package: `npm install -D @types/chrome`. This provides full type definitions for the `chrome.*` API namespace across all contexts.