@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.
- package/README.md +73 -8
- package/content/knowledge/browser-extension/browser-extension-architecture.md +195 -0
- package/content/knowledge/browser-extension/browser-extension-content-scripts.md +264 -0
- package/content/knowledge/browser-extension/browser-extension-conventions.md +156 -0
- package/content/knowledge/browser-extension/browser-extension-cross-browser.md +229 -0
- package/content/knowledge/browser-extension/browser-extension-dev-environment.md +247 -0
- package/content/knowledge/browser-extension/browser-extension-manifest.md +220 -0
- package/content/knowledge/browser-extension/browser-extension-project-structure.md +183 -0
- package/content/knowledge/browser-extension/browser-extension-requirements.md +107 -0
- package/content/knowledge/browser-extension/browser-extension-security.md +202 -0
- package/content/knowledge/browser-extension/browser-extension-service-workers.md +265 -0
- package/content/knowledge/browser-extension/browser-extension-store-submission.md +155 -0
- package/content/knowledge/browser-extension/browser-extension-testing.md +270 -0
- package/content/knowledge/data-pipeline/data-pipeline-architecture.md +175 -0
- package/content/knowledge/data-pipeline/data-pipeline-batch-patterns.md +263 -0
- package/content/knowledge/data-pipeline/data-pipeline-conventions.md +176 -0
- package/content/knowledge/data-pipeline/data-pipeline-dev-environment.md +350 -0
- package/content/knowledge/data-pipeline/data-pipeline-orchestration.md +291 -0
- package/content/knowledge/data-pipeline/data-pipeline-project-structure.md +257 -0
- package/content/knowledge/data-pipeline/data-pipeline-quality.md +324 -0
- package/content/knowledge/data-pipeline/data-pipeline-requirements.md +145 -0
- package/content/knowledge/data-pipeline/data-pipeline-schema-management.md +295 -0
- package/content/knowledge/data-pipeline/data-pipeline-security.md +326 -0
- package/content/knowledge/data-pipeline/data-pipeline-streaming-patterns.md +280 -0
- package/content/knowledge/data-pipeline/data-pipeline-testing.md +406 -0
- package/content/knowledge/ml/ml-architecture.md +172 -0
- package/content/knowledge/ml/ml-conventions.md +209 -0
- package/content/knowledge/ml/ml-dev-environment.md +299 -0
- package/content/knowledge/ml/ml-experiment-tracking.md +285 -0
- package/content/knowledge/ml/ml-model-evaluation.md +256 -0
- package/content/knowledge/ml/ml-observability.md +253 -0
- package/content/knowledge/ml/ml-project-structure.md +216 -0
- package/content/knowledge/ml/ml-requirements.md +138 -0
- package/content/knowledge/ml/ml-security.md +188 -0
- package/content/knowledge/ml/ml-serving-patterns.md +243 -0
- package/content/knowledge/ml/ml-testing.md +301 -0
- package/content/knowledge/ml/ml-training-patterns.md +269 -0
- package/content/methodology/browser-extension-overlay.yml +82 -0
- package/content/methodology/data-pipeline-overlay.yml +70 -0
- package/content/methodology/ml-overlay.yml +70 -0
- package/dist/cli/commands/init.d.ts +13 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +122 -2
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/init.test.js +120 -0
- package/dist/cli/commands/init.test.js.map +1 -1
- package/dist/config/schema.d.ts +864 -48
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +53 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/config/schema.test.js +166 -3
- package/dist/config/schema.test.js.map +1 -1
- package/dist/core/assembly/overlay-loader.test.js +33 -0
- package/dist/core/assembly/overlay-loader.test.js.map +1 -1
- package/dist/e2e/project-type-overlays.test.d.ts +2 -2
- package/dist/e2e/project-type-overlays.test.js +499 -33
- package/dist/e2e/project-type-overlays.test.js.map +1 -1
- package/dist/types/config.d.ts +10 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/wizard/questions.d.ts +17 -1
- package/dist/wizard/questions.d.ts.map +1 -1
- package/dist/wizard/questions.js +72 -1
- package/dist/wizard/questions.js.map +1 -1
- package/dist/wizard/questions.test.js +135 -0
- package/dist/wizard/questions.test.js.map +1 -1
- package/dist/wizard/wizard.d.ts +13 -0
- package/dist/wizard/wizard.d.ts.map +1 -1
- package/dist/wizard/wizard.js +17 -1
- package/dist/wizard/wizard.js.map +1 -1
- 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.
|