@crxjs/vite-plugin 2.4.0 → 2.6.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 CHANGED
@@ -38,6 +38,10 @@ npm create crxjs@latest
38
38
  > [!NOTE]
39
39
  > Looking for MV2 support? See [`rollup-plugin`](packages/rollup-plugin/README.md)
40
40
 
41
+ ## 💻 Contributing
42
+
43
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for local development workflows.
44
+
41
45
  ## 💝 Contributors
42
46
 
43
47
  This project exists thanks to all the people who contribute.
package/client.d.ts CHANGED
@@ -43,6 +43,21 @@ declare module '*?script&iife' {
43
43
  export default fileName
44
44
  }
45
45
 
46
+ declare module '*?iife' {
47
+ /**
48
+ * Alias for `*?script&iife`.
49
+ *
50
+ * Script format is IIFE. Use for content scripts with opaque origins.
51
+ *
52
+ * Exports the file name of the output script file.
53
+ *
54
+ * If imported inside a content script, RPCE will include the file name in
55
+ * `web_accessible_resources`.
56
+ */
57
+ const fileName: string
58
+ export default fileName
59
+ }
60
+
46
61
  declare module '*?script&module' {
47
62
  /**
48
63
  * Script format is ESM. No loader and no HMR. Does not support frameworks
@@ -0,0 +1,448 @@
1
+ import { ConfigEnv, Plugin, PluginOption } from 'vite';
2
+ import { IsStringLiteral } from 'type-fest';
3
+ import { GlobOptions } from 'tinyglobby';
4
+ import { PluginContext, OutputBundle } from 'rollup';
5
+
6
+ interface DeclarativeNetRequestResource {
7
+ id: string;
8
+ enabled: boolean;
9
+ path: string;
10
+ }
11
+ interface WebAccessibleResourceByMatch {
12
+ matches: string[];
13
+ resources: string[];
14
+ use_dynamic_url?: boolean;
15
+ }
16
+ interface WebAccessibleResourceById {
17
+ extension_ids: string[];
18
+ resources: string[];
19
+ use_dynamic_url?: boolean;
20
+ }
21
+ interface ChromeManifestBackground {
22
+ service_worker: string;
23
+ type?: 'module' | (string & Record<never, never>);
24
+ }
25
+ interface FirefoxManifestBackground {
26
+ scripts: string[];
27
+ persistent?: false;
28
+ }
29
+ interface ManifestV3 {
30
+ manifest_version: number;
31
+ name: string;
32
+ version: string;
33
+ default_locale?: string | undefined;
34
+ description?: string | undefined;
35
+ icons?: chrome.runtime.ManifestIcons | undefined;
36
+ action?: chrome.runtime.ManifestAction | undefined;
37
+ /**
38
+ * @see https://developer.chrome.com/docs/extensions/reference/manifest/author
39
+ */
40
+ author?: {
41
+ email: string;
42
+ } | undefined;
43
+ background?: ChromeManifestBackground | FirefoxManifestBackground | undefined;
44
+ chrome_settings_overrides?: {
45
+ homepage?: string | undefined;
46
+ search_provider?: chrome.runtime.SearchProvider | undefined;
47
+ startup_pages?: string[] | undefined;
48
+ } | undefined;
49
+ chrome_ui_overrides?: {
50
+ bookmarks_ui?: {
51
+ remove_bookmark_shortcut?: boolean | undefined;
52
+ remove_button?: boolean | undefined;
53
+ } | undefined;
54
+ } | undefined;
55
+ chrome_url_overrides?: {
56
+ bookmarks?: string | undefined;
57
+ history?: string | undefined;
58
+ newtab?: string | undefined;
59
+ } | undefined;
60
+ commands?: {
61
+ [name: string]: {
62
+ suggested_key?: {
63
+ default?: string | undefined;
64
+ windows?: string | undefined;
65
+ mac?: string | undefined;
66
+ chromeos?: string | undefined;
67
+ linux?: string | undefined;
68
+ } | undefined;
69
+ description?: string | undefined;
70
+ global?: boolean | undefined;
71
+ };
72
+ } | undefined;
73
+ content_capabilities?: {
74
+ matches?: string[] | undefined;
75
+ permissions?: string[] | undefined;
76
+ } | undefined;
77
+ content_scripts?: {
78
+ matches?: string[] | undefined;
79
+ exclude_matches?: string[] | undefined;
80
+ css?: string[] | undefined;
81
+ js?: string[] | undefined;
82
+ run_at?: string | undefined;
83
+ all_frames?: boolean | undefined;
84
+ match_about_blank?: boolean | undefined;
85
+ include_globs?: string[] | undefined;
86
+ exclude_globs?: string[] | undefined;
87
+ world?: chrome.scripting.ExecutionWorld | string | undefined;
88
+ }[] | undefined;
89
+ content_security_policy?: {
90
+ extension_pages?: string;
91
+ sandbox?: string;
92
+ };
93
+ converted_from_user_script?: boolean | undefined;
94
+ current_locale?: string | undefined;
95
+ declarative_net_request?: {
96
+ rule_resources: DeclarativeNetRequestResource[];
97
+ };
98
+ devtools_page?: string | undefined;
99
+ event_rules?: {
100
+ event?: string | undefined;
101
+ actions?: {
102
+ type: string;
103
+ }[] | undefined;
104
+ conditions?: chrome.declarativeContent.PageStateMatcherProperties[] | undefined;
105
+ }[] | undefined;
106
+ externally_connectable?: {
107
+ ids?: string[] | undefined;
108
+ matches?: string[] | undefined;
109
+ accepts_tls_channel_id?: boolean | undefined;
110
+ } | undefined;
111
+ file_browser_handlers?: {
112
+ id?: string | undefined;
113
+ default_title?: string | undefined;
114
+ file_filters?: string[] | undefined;
115
+ }[] | undefined;
116
+ file_system_provider_capabilities?: {
117
+ configurable?: boolean | undefined;
118
+ watchable?: boolean | undefined;
119
+ multiple_mounts?: boolean | undefined;
120
+ source?: string | undefined;
121
+ } | undefined;
122
+ homepage_url?: string | undefined;
123
+ host_permissions?: string[] | undefined;
124
+ import?: {
125
+ id: string;
126
+ minimum_version?: string | undefined;
127
+ }[] | undefined;
128
+ export?: {
129
+ whitelist?: string[] | undefined;
130
+ } | undefined;
131
+ incognito?: string | undefined;
132
+ input_components?: {
133
+ name: string;
134
+ id?: string | undefined;
135
+ language?: string | string[] | undefined;
136
+ layouts?: string | string[] | undefined;
137
+ input_view?: string | undefined;
138
+ options_page?: string | undefined;
139
+ }[] | undefined;
140
+ key?: string | undefined;
141
+ minimum_chrome_version?: string | undefined;
142
+ nacl_modules?: {
143
+ path: string;
144
+ mime_type: string;
145
+ }[] | undefined;
146
+ oauth2?: {
147
+ client_id: string;
148
+ scopes?: string[] | undefined;
149
+ } | undefined;
150
+ offline_enabled?: boolean | undefined;
151
+ omnibox?: {
152
+ keyword: string;
153
+ } | undefined;
154
+ optional_host_permissions?: string[] | undefined;
155
+ optional_permissions?: chrome.runtime.ManifestPermissions[] | string[] | undefined;
156
+ options_page?: string | undefined;
157
+ options_ui?: {
158
+ page?: string | undefined;
159
+ chrome_style?: boolean | undefined;
160
+ open_in_tab?: boolean | undefined;
161
+ } | undefined;
162
+ permissions?: chrome.runtime.ManifestPermissions[] | string[] | undefined;
163
+ platforms?: {
164
+ nacl_arch?: string | undefined;
165
+ sub_package_path: string;
166
+ }[] | undefined;
167
+ plugins?: {
168
+ path: string;
169
+ }[] | undefined;
170
+ requirements?: {
171
+ '3D'?: {
172
+ features?: string[] | undefined;
173
+ } | undefined;
174
+ plugins?: {
175
+ npapi?: boolean | undefined;
176
+ } | undefined;
177
+ } | undefined;
178
+ sandbox?: {
179
+ pages: string[];
180
+ content_security_policy?: string | undefined;
181
+ } | undefined;
182
+ side_panel?: {
183
+ default_path?: string | undefined;
184
+ } | undefined;
185
+ short_name?: string | undefined;
186
+ spellcheck?: {
187
+ dictionary_language?: string | undefined;
188
+ dictionary_locale?: string | undefined;
189
+ dictionary_format?: string | undefined;
190
+ dictionary_path?: string | undefined;
191
+ } | undefined;
192
+ storage?: {
193
+ managed_schema: string;
194
+ } | undefined;
195
+ tts_engine?: {
196
+ voices: {
197
+ voice_name: string;
198
+ lang?: string | undefined;
199
+ gender?: string | undefined;
200
+ event_types?: string[] | undefined;
201
+ }[];
202
+ } | undefined;
203
+ update_url?: string | undefined;
204
+ version_name?: string | undefined;
205
+ web_accessible_resources?: (WebAccessibleResourceById | WebAccessibleResourceByMatch)[] | undefined;
206
+ browser_specific_settings?: {
207
+ gecko: {
208
+ id: string;
209
+ strict_min_version?: string | undefined;
210
+ strict_max_version?: string | undefined;
211
+ update_url?: string | undefined;
212
+ data_collection_permissions?: {
213
+ /**
214
+ * available value: "personallyIdentifyingInfo" | "healthInfo" | "financialAndPaymentInfo" | "authenticationInfo" | "personalCommunications" | "locationInfo" | "browsingActivity" | "websiteContent" | "websiteActivity" | "searchTerms" | "bookmarksInfo" | "none".
215
+ * see also: https://extensionworkshop.com/documentation/develop/firefox-builtin-data-consent/
216
+ */
217
+ required: GeckoPermissionsRequired[];
218
+ /**
219
+ * available value: "personallyIdentifyingInfo" | "healthInfo" | "financialAndPaymentInfo" | "authenticationInfo" | "personalCommunications" | "locationInfo" | "browsingActivity" | "websiteContent" | "websiteActivity" | "searchTerms" | "bookmarksInfo" | "technicalAndInteraction".
220
+ * see also: https://extensionworkshop.com/documentation/develop/firefox-builtin-data-consent/
221
+ */
222
+ optional?: GeckoPermissionsOptional[] | undefined;
223
+ };
224
+ };
225
+ } | undefined;
226
+ }
227
+ type GeckoPermissionsRequired = "personallyIdentifyingInfo" | "healthInfo" | "financialAndPaymentInfo" | "authenticationInfo" | "personalCommunications" | "locationInfo" | "browsingActivity" | "websiteContent" | "websiteActivity" | "searchTerms" | "bookmarksInfo" | "none";
228
+ type GeckoPermissionsOptional = "personallyIdentifyingInfo" | "healthInfo" | "financialAndPaymentInfo" | "authenticationInfo" | "personalCommunications" | "locationInfo" | "browsingActivity" | "websiteContent" | "websiteActivity" | "searchTerms" | "bookmarksInfo" | "technicalAndInteraction";
229
+
230
+ type ManifestV3Fn = (env: ConfigEnv) => ManifestV3 | Promise<ManifestV3>;
231
+ type ManifestV3Export = ManifestV3 | Promise<ManifestV3> | ManifestV3Fn;
232
+ type Code = '.' | '/' | '\\';
233
+ type LiteralManifestFilePath<T extends string> = T extends `${Code}${string}` ? never : T extends `${string}.${infer Ext}` ? Ext extends '' ? never : T : never;
234
+ type ManifestFilePath<T extends string> = IsStringLiteral<T> extends true ? LiteralManifestFilePath<T> : T;
235
+ interface ManifestIcons<T extends string> {
236
+ [size: number]: ManifestFilePath<T>;
237
+ }
238
+ type FilePathFields<T extends string> = {
239
+ icons?: ManifestIcons<T>;
240
+ action?: {
241
+ /**
242
+ * - Relative to Vite project root (where vite.config.js is)
243
+ * - Format: "subdir/icon.png" (no leading ./ or /)
244
+ *
245
+ * @example "assets/icon.png"
246
+ */
247
+ default_icon?: ManifestIcons<T>;
248
+ default_title?: string;
249
+ /**
250
+ * - Relative to Vite project root (where vite.config.js is)
251
+ * - Format: "subdir/index.html" (no leading ./ or /)
252
+ *
253
+ * @example "src/popup.html"
254
+ */
255
+ default_popup?: ManifestFilePath<T>;
256
+ };
257
+ background?: {
258
+ /**
259
+ * - Relative to Vite project root (where vite.config.js is)
260
+ * - Format: "subdir/index.js" (no leading ./ or /)
261
+ *
262
+ * @example "src/background.js"
263
+ */
264
+ service_worker: ManifestFilePath<T>;
265
+ type?: 'module' | (string & Record<never, never>);
266
+ } | FirefoxManifestBackground;
267
+ content_scripts?: {
268
+ matches?: string[];
269
+ exclude_matches?: string[];
270
+ /**
271
+ * - Relative to Vite project root (where vite.config.js is)
272
+ * - Format: "subdir/content.css" (no leading ./ or /)
273
+ *
274
+ * @example "src/content.css"
275
+ */
276
+ css?: ManifestFilePath<T>[];
277
+ /**
278
+ * - Relative to Vite project root (where vite.config.js is)
279
+ * - Format: "subdir/content.js" (no leading ./ or /)
280
+ *
281
+ * @example "src/content.js"
282
+ */
283
+ js?: ManifestFilePath<T>[];
284
+ run_at?: string;
285
+ all_frames?: boolean;
286
+ match_about_blank?: boolean;
287
+ include_globs?: string[];
288
+ exclude_globs?: string[];
289
+ /**
290
+ * - 'ISOLATED' (default): Content script runs in an isolated world.
291
+ * - 'MAIN': Content script runs in the main world.
292
+ * NOTE: MAIN currently does NOT support crxjs HMR
293
+ * @see https://developer.chrome.com/docs/extensions/mv3/content_scripts/#world
294
+ */
295
+ world?: 'ISOLATED' | 'MAIN';
296
+ }[];
297
+ input_components?: {
298
+ name: string;
299
+ id?: string;
300
+ language?: string | string[];
301
+ layouts?: string | string[];
302
+ input_view?: string;
303
+ /**
304
+ * - Relative to Vite project root (where vite.config.js is)
305
+ * - Format: "subdir/options.html" (no leading ./ or /)
306
+ *
307
+ * @example "src/options.html"
308
+ */
309
+ options_page?: ManifestFilePath<T>;
310
+ }[];
311
+ /**
312
+ * - Relative to Vite project root (where vite.config.js is)
313
+ * - Format: "subdir/options.html" (no leading ./ or /)
314
+ *
315
+ * @example "src/options.html"
316
+ */
317
+ options_page?: ManifestFilePath<T>;
318
+ /**
319
+ * - Relative to Vite project root (where vite.config.js is)
320
+ * - Format: "subdir/devtools.html" (no leading ./ or /)
321
+ *
322
+ * @example "src/devtools.html"
323
+ */
324
+ devtools_page?: ManifestFilePath<T>;
325
+ };
326
+ type ManifestOptions<T extends string> = Omit<ManifestV3, keyof FilePathFields<string>> & FilePathFields<T>;
327
+ type ManifestV3Options<T extends string = string> = ManifestOptions<T> | Promise<ManifestOptions<T>> | ManifestV3Define<T>;
328
+ type ManifestV3Define<T extends string> = (env: ConfigEnv) => ManifestOptions<T> | Promise<ManifestOptions<T>>;
329
+ declare const defineManifest: <T extends string>(manifest: ManifestV3Options<T>) => ManifestV3Export;
330
+ /**
331
+ * Content script resources like CSS and image files must be declared in the
332
+ * manifest under `web_accessible_resources`. Manifest V3 uses a match pattern
333
+ * to narrow the origins that can access a Chrome CRX resource.
334
+ *
335
+ * Content script resources use the same match pattern as the content script for
336
+ * web accessible resources.
337
+ *
338
+ * You don't need to define a match pattern for dynamic content script
339
+ * resources, but if you want to do so, you can use the helper function
340
+ * `defineDynamicResource` to define your web accessible resources in a
341
+ * TypeScript file:
342
+ *
343
+ * ```typescript
344
+ * import { crx, defineManifest, defineDynamicResource }
345
+ * const manifest = defineManifest({
346
+ * "web_accessible_resources": [
347
+ * defineDynamicResource({
348
+ * matches: ["https://example.com/*", "file:///*.mp3", "..."]
349
+ * use_dynamic_url?: true
350
+ * })
351
+ * ]
352
+ * })
353
+ * ```
354
+ */
355
+ declare const defineDynamicResource: ({ matches, use_dynamic_url, }: Omit<WebAccessibleResourceByMatch, 'resources'>) => WebAccessibleResourceByMatch;
356
+
357
+ type CrxDevAssetId = {
358
+ id: string;
359
+ type: 'asset';
360
+ source?: string | Uint8Array;
361
+ };
362
+ type CrxDevScriptId = {
363
+ id: string;
364
+ type: 'module' | 'iife';
365
+ };
366
+ interface CrxPlugin extends Plugin {
367
+ /**
368
+ * Runs during the transform hook for the manifest. Filenames use input
369
+ * filenames.
370
+ */
371
+ transformCrxManifest?: (this: PluginContext, manifest: ManifestV3) => Promise<ManifestV3 | null | undefined> | ManifestV3 | null | undefined;
372
+ /**
373
+ * Runs during generateBundle, before manifest output. Filenames use output
374
+ * filenames.
375
+ */
376
+ renderCrxManifest?: (this: PluginContext, manifest: ManifestV3, bundle: OutputBundle) => Promise<ManifestV3 | null | undefined> | ManifestV3 | null | undefined;
377
+ /**
378
+ * Runs in the file writer on content scripts during development. `script.id`
379
+ * is Vite URL format.
380
+ */
381
+ renderCrxDevScript?: (code: string, script: CrxDevScriptId) => Promise<string | null | undefined> | string | null | undefined;
382
+ }
383
+ interface CrxOptions {
384
+ contentScripts?: {
385
+ preambleCode?: string | false;
386
+ hmrTimeout?: number;
387
+ injectCss?: boolean;
388
+ /**
389
+ * List of content script files (relative to project root) that should be
390
+ * built as standalone IIFE bundles (self-contained, no loader, all imports
391
+ * inlined). This allows using normal filenames (without the `.iife.*`
392
+ * convention) for IIFE content scripts, e.g. for `world: 'MAIN'` or to
393
+ * avoid loader overhead.
394
+ *
395
+ * The files must still be listed in `manifest.content_scripts` (or used
396
+ * via `?script` / `?iife`).
397
+ *
398
+ * Example:
399
+ * crx({
400
+ * manifest,
401
+ * contentScripts: {
402
+ * standaloneFiles: ['src/injected.ts', 'src/another.js']
403
+ * }
404
+ * })
405
+ */
406
+ standaloneFiles?: string[];
407
+ };
408
+ globOptions?: GlobOptions;
409
+ /**
410
+ * The browser that this extension is targeting, can be "firefox" or "chrome".
411
+ * Default is "chrome".
412
+ */
413
+ browser?: Browser;
414
+ /**
415
+ * Enable automatic extension reload and HMR during development. When false:
416
+ *
417
+ * - The extension will not call `chrome.runtime.reload()` on background changes
418
+ * or dev server reconnection.
419
+ * - Content scripts will not receive HMR updates or reload their host pages.
420
+ * - Files are still rebuilt and written to the output directory on change.
421
+ *
422
+ * Use this when content scripts have side effects on injection and you want
423
+ * to manually reload the extension in the browser.
424
+ *
425
+ * Default is `true`.
426
+ */
427
+ liveReload?: boolean;
428
+ }
429
+ type Browser = 'firefox' | 'chrome';
430
+
431
+ /** Resolves when all existing files in scriptFiles are written. */
432
+ declare function allFilesReady(): Promise<void>;
433
+
434
+ type FileWriterId = {
435
+ type: CrxDevAssetId['type'] | CrxDevScriptId['type'] | 'loader';
436
+ id: string;
437
+ };
438
+ /** Resolves when file and dependencies are written. */
439
+ declare function fileReady(script: FileWriterId): Promise<void>;
440
+
441
+ declare const crx: (options: {
442
+ manifest: ManifestV3Export;
443
+ } & CrxOptions) => PluginOption[];
444
+ declare const chromeExtension: (options: {
445
+ manifest: ManifestV3Export;
446
+ } & CrxOptions) => PluginOption[];
447
+
448
+ export { CrxPlugin, ManifestV3Export, allFilesReady, chromeExtension, crx, defineDynamicResource, defineManifest, fileReady as filesReady };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ConfigEnv, Plugin, PluginOption } from 'vite';
2
2
  import { IsStringLiteral } from 'type-fest';
3
- import { Options } from 'fast-glob';
3
+ import { GlobOptions } from 'tinyglobby';
4
4
  import { PluginContext, OutputBundle } from 'rollup';
5
5
 
6
6
  interface DeclarativeNetRequestResource {
@@ -20,7 +20,7 @@ interface WebAccessibleResourceById {
20
20
  }
21
21
  interface ChromeManifestBackground {
22
22
  service_worker: string;
23
- type?: 'module' | (string & {});
23
+ type?: 'module' | (string & Record<never, never>);
24
24
  }
25
25
  interface FirefoxManifestBackground {
26
26
  scripts: string[];
@@ -209,7 +209,7 @@ interface ManifestV3 {
209
209
  strict_min_version?: string | undefined;
210
210
  strict_max_version?: string | undefined;
211
211
  update_url?: string | undefined;
212
- data_collection_permissions: {
212
+ data_collection_permissions?: {
213
213
  /**
214
214
  * available value: "personallyIdentifyingInfo" | "healthInfo" | "financialAndPaymentInfo" | "authenticationInfo" | "personalCommunications" | "locationInfo" | "browsingActivity" | "websiteContent" | "websiteActivity" | "searchTerms" | "bookmarksInfo" | "none".
215
215
  * see also: https://extensionworkshop.com/documentation/develop/firefox-builtin-data-consent/
@@ -262,7 +262,7 @@ type FilePathFields<T extends string> = {
262
262
  * @example "src/background.js"
263
263
  */
264
264
  service_worker: ManifestFilePath<T>;
265
- type?: 'module' | (string & {});
265
+ type?: 'module' | (string & Record<never, never>);
266
266
  } | FirefoxManifestBackground;
267
267
  content_scripts?: {
268
268
  matches?: string[];
@@ -385,13 +385,46 @@ interface CrxOptions {
385
385
  preambleCode?: string | false;
386
386
  hmrTimeout?: number;
387
387
  injectCss?: boolean;
388
+ /**
389
+ * List of content script files (relative to project root) that should be
390
+ * built as standalone IIFE bundles (self-contained, no loader, all imports
391
+ * inlined). This allows using normal filenames (without the `.iife.*`
392
+ * convention) for IIFE content scripts, e.g. for `world: 'MAIN'` or to
393
+ * avoid loader overhead.
394
+ *
395
+ * The files must still be listed in `manifest.content_scripts` (or used
396
+ * via `?script` / `?iife`).
397
+ *
398
+ * Example:
399
+ * crx({
400
+ * manifest,
401
+ * contentScripts: {
402
+ * standaloneFiles: ['src/injected.ts', 'src/another.js']
403
+ * }
404
+ * })
405
+ */
406
+ standaloneFiles?: string[];
388
407
  };
389
- fastGlobOptions?: Options;
408
+ globOptions?: GlobOptions;
390
409
  /**
391
410
  * The browser that this extension is targeting, can be "firefox" or "chrome".
392
411
  * Default is "chrome".
393
412
  */
394
413
  browser?: Browser;
414
+ /**
415
+ * Enable automatic extension reload and HMR during development. When false:
416
+ *
417
+ * - The extension will not call `chrome.runtime.reload()` on background changes
418
+ * or dev server reconnection.
419
+ * - Content scripts will not receive HMR updates or reload their host pages.
420
+ * - Files are still rebuilt and written to the output directory on change.
421
+ *
422
+ * Use this when content scripts have side effects on injection and you want
423
+ * to manually reload the extension in the browser.
424
+ *
425
+ * Default is `true`.
426
+ */
427
+ liveReload?: boolean;
395
428
  }
396
429
  type Browser = 'firefox' | 'chrome';
397
430