@crxjs/vite-plugin 2.4.0 → 2.5.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/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
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 {
@@ -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/
@@ -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
 
package/dist/index.mjs CHANGED
@@ -9,12 +9,12 @@ import { rollup } from 'rollup';
9
9
  import * as lexer from 'es-module-lexer';
10
10
  import { readFile as readFile$1 } from 'fs/promises';
11
11
  import MagicString from 'magic-string';
12
+ import { build, mergeConfig, createLogger, version } from 'vite';
12
13
  import convertSourceMap from 'convert-source-map';
13
- import pc from 'picocolors';
14
- import { createLogger, version } from 'vite';
14
+ import colors from 'picocolors';
15
15
  import { readFileSync, existsSync, promises } from 'fs';
16
16
  import { createRequire } from 'module';
17
- import fg from 'fast-glob';
17
+ import { glob, isDynamicPattern } from 'tinyglobby';
18
18
  import { parse } from 'node-html-parser';
19
19
  import jsesc from 'jsesc';
20
20
 
@@ -58,7 +58,7 @@ function isCrxPlugin(p) {
58
58
  return !!p && typeof p === "object" && !(p instanceof Promise) && !Array.isArray(p) && p.name.startsWith("crx:");
59
59
  }
60
60
 
61
- var workerHmrClient = "const ownOrigin = `chrome-extension://${chrome.runtime.id}`;\nself.addEventListener(\"fetch\", (fetchEvent) => {\n const url = new URL(fetchEvent.request.url);\n if (url.origin === ownOrigin) {\n fetchEvent.respondWith(sendToServer(fetchEvent.request));\n }\n});\nasync function sendToServer(req) {\n const url = new URL(req.url);\n const requestHeaders = new Headers(req.headers);\n url.protocol = __SERVER_PROTO__ + \":\";\n url.host = \"localhost\";\n url.port = __SERVER_PORT__;\n url.searchParams.set(\"t\", Date.now().toString());\n const response = await fetch(url.href.replace(/=$|=(?=&)/g, \"\"), {\n headers: requestHeaders\n });\n const responseHeaders = new Headers(response.headers);\n responseHeaders.set(\n \"Content-Type\",\n responseHeaders.get(\"Content-Type\") ?? \"text/javascript\"\n );\n responseHeaders.set(\n \"Cache-Control\",\n responseHeaders.get(\"Cache-Control\") ?? \"\"\n );\n return new Response(response.body, {\n headers: responseHeaders\n });\n}\nconst ports = /* @__PURE__ */ new Set();\nchrome.runtime.onConnect.addListener((port) => {\n if (port.name === \"@crx/client\") {\n ports.add(port);\n port.onDisconnect.addListener((port2) => {\n if (chrome.runtime.lastError) {\n console.error(chrome.runtime.lastError);\n }\n ports.delete(port2);\n });\n port.onMessage.addListener((message) => {\n });\n port.postMessage({ data: JSON.stringify({ type: \"connected\" }) });\n }\n});\nfunction notifyContentScripts(payload) {\n const data = JSON.stringify(payload);\n for (const port of ports)\n port.postMessage({ data });\n}\nconsole.log(\"[vite] connecting...\");\nconst socketProtocol = __HMR_PROTOCOL__ || (location.protocol === \"https:\" ? \"wss\" : \"ws\");\nconst socketToken = __HMR_TOKEN__;\nconst socketHost = `${__HMR_HOSTNAME__ || location.hostname}:${__HMR_PORT__}`;\nconst socket = new WebSocket(\n `${socketProtocol}://${socketHost}?token=${socketToken}`,\n \"vite-hmr\"\n);\nconst base = __BASE__ || \"/\";\nsocket.addEventListener(\"message\", async ({ data }) => {\n handleSocketMessage(JSON.parse(data));\n});\nfunction isCrxHmrPayload(x) {\n return x.type === \"custom\" && x.event.startsWith(\"crx:\");\n}\nfunction handleSocketMessage(payload) {\n if (isCrxHmrPayload(payload)) {\n handleCrxHmrPayload(payload);\n } else if (payload.type === \"connected\") {\n console.log(`[vite] connected.`);\n const interval = setInterval(() => socket.send(\"ping\"), __HMR_TIMEOUT__);\n socket.addEventListener(\"close\", () => clearInterval(interval));\n }\n}\nfunction handleCrxHmrPayload(payload) {\n notifyContentScripts(payload);\n switch (payload.event) {\n case \"crx:runtime-reload\":\n console.log(\"[crx] runtime reload\");\n chrome.runtime.reload();\n break;\n }\n}\nasync function waitForSuccessfulPing(ms = 1e3) {\n while (true) {\n try {\n await fetch(`${base}__vite_ping`);\n break;\n } catch (e) {\n await new Promise((resolve) => setTimeout(resolve, ms));\n }\n }\n}\nsocket.addEventListener(\"close\", async ({ wasClean }) => {\n if (wasClean)\n return;\n console.log(`[vite] server connection lost. polling for restart...`);\n await waitForSuccessfulPing();\n handleCrxHmrPayload({\n type: \"custom\",\n event: \"crx:runtime-reload\"\n });\n});\n";
61
+ var workerHmrClient = "const ownOrigin = `chrome-extension://${chrome.runtime.id}`;\nself.addEventListener(\"fetch\", (fetchEvent) => {\n const url = new URL(fetchEvent.request.url);\n if (url.origin === ownOrigin) {\n fetchEvent.respondWith(sendToServer(fetchEvent.request));\n }\n});\nasync function sendToServer(req) {\n const url = new URL(req.url);\n const requestHeaders = new Headers(req.headers);\n url.protocol = __SERVER_PROTO__ + \":\";\n url.host = \"localhost\";\n url.port = __SERVER_PORT__;\n url.searchParams.set(\"t\", Date.now().toString());\n const response = await fetch(url.href.replace(/=$|=(?=&)/g, \"\"), {\n headers: requestHeaders\n });\n const responseHeaders = new Headers(response.headers);\n responseHeaders.set(\n \"Content-Type\",\n responseHeaders.get(\"Content-Type\") ?? \"text/javascript\"\n );\n responseHeaders.set(\n \"Cache-Control\",\n responseHeaders.get(\"Cache-Control\") ?? \"\"\n );\n return new Response(response.body, {\n headers: responseHeaders\n });\n}\nconst ports = /* @__PURE__ */ new Set();\nchrome.runtime.onConnect.addListener((port) => {\n if (port.name === \"@crx/client\") {\n ports.add(port);\n port.onDisconnect.addListener((port2) => {\n if (chrome.runtime.lastError) {\n console.error(chrome.runtime.lastError);\n }\n ports.delete(port2);\n });\n port.onMessage.addListener((message) => {\n });\n port.postMessage({ data: JSON.stringify({ type: \"connected\" }) });\n }\n});\nfunction notifyContentScripts(payload) {\n const data = JSON.stringify(payload);\n for (const port of ports)\n port.postMessage({ data });\n}\nconsole.log(\"[vite] connecting...\");\nconst socketProtocol = __HMR_PROTOCOL__ || (location.protocol === \"https:\" ? \"wss\" : \"ws\");\nconst socketToken = __HMR_TOKEN__;\nconst socketHost = `${__HMR_HOSTNAME__ || location.hostname}:${__HMR_PORT__}`;\nconst socket = new WebSocket(\n `${socketProtocol}://${socketHost}?token=${socketToken}`,\n \"vite-hmr\"\n);\nconst base = __BASE__ || \"/\";\nsocket.addEventListener(\"message\", async ({ data }) => {\n handleSocketMessage(JSON.parse(data));\n});\nfunction isCrxHmrPayload(x) {\n return x.type === \"custom\" && x.event.startsWith(\"crx:\");\n}\nfunction handleSocketMessage(payload) {\n if (isCrxHmrPayload(payload)) {\n handleCrxHmrPayload(payload);\n } else if (payload.type === \"connected\") {\n console.log(`[vite] connected.`);\n const interval = setInterval(() => socket.send(\"ping\"), __HMR_TIMEOUT__);\n socket.addEventListener(\"close\", () => clearInterval(interval));\n }\n}\nfunction handleCrxHmrPayload(payload) {\n if (!__LIVE_RELOAD__) {\n if (payload.event === \"crx:runtime-reload\") {\n console.log(\"[crx] runtime reload suppressed (liveReload disabled)\");\n }\n return;\n }\n notifyContentScripts(payload);\n switch (payload.event) {\n case \"crx:runtime-reload\":\n console.log(\"[crx] runtime reload\");\n chrome.runtime.reload();\n break;\n }\n}\nasync function waitForSuccessfulPing(ms = 1e3) {\n while (true) {\n try {\n await fetch(`${base}__vite_ping`);\n break;\n } catch (e) {\n await new Promise((resolve) => setTimeout(resolve, ms));\n }\n }\n}\nsocket.addEventListener(\"close\", async ({ wasClean }) => {\n if (wasClean)\n return;\n console.log(`[vite] server connection lost. polling for restart...`);\n await waitForSuccessfulPing();\n if (__LIVE_RELOAD__) {\n handleCrxHmrPayload({\n type: \"custom\",\n event: \"crx:runtime-reload\"\n });\n } else {\n console.log(\n \"[crx] server reconnected, skipping reload (liveReload disabled)\"\n );\n }\n});\n";
62
62
 
63
63
  const _debug = (id) => debug$5("crx").extend(id);
64
64
  const hash = (data, length = 5) => createHash("sha1").update(data).digest("base64").replace(/[^A-Za-z0-9]/g, "").slice(0, length);
@@ -209,7 +209,7 @@ function formatFileData(script) {
209
209
  return script;
210
210
  }
211
211
  function getFileName({ type, id }) {
212
- let fileName = id.replace(/t=\d+&/, "").replace(/\?t=\d+$/, "").replace(/^\//, "").replace(/\?/g, "__").replace(/&/g, "_").replace(/=/g, "--");
212
+ let fileName = id.replace(/t=\d+&/, "").replace(/\?t=\d+$/, "").replace(/^\//, "").replace(/\?/g, "__").replace(/&/g, "_").replace(/=/g, "--").replace(/:/g, "-");
213
213
  if (fileName.includes("node_modules/")) {
214
214
  fileName = `vendor/${fileName.split("node_modules/").pop().replace(/\//g, "-")}`;
215
215
  } else if (fileName.startsWith("@")) {
@@ -248,7 +248,7 @@ function getViteUrl({ type, id }) {
248
248
  if (type === "asset") {
249
249
  throw new Error(`File type "${type}" not implemented.`);
250
250
  } else if (type === "iife") {
251
- throw new Error(`File type "${type}" not implemented.`);
251
+ throw new Error(`File type "iife" is handled via dedicated IIFE bundler, not Vite transform.`);
252
252
  } else if (type === "loader") {
253
253
  throw new Error("Vite does not transform loader files.");
254
254
  } else if (type === "module") {
@@ -293,6 +293,7 @@ function getContentCssIndex(id) {
293
293
  const pluginBackground = () => {
294
294
  let config;
295
295
  let browser;
296
+ let liveReload = true;
296
297
  return [
297
298
  {
298
299
  name: "crx:background-client",
@@ -305,7 +306,7 @@ const pluginBackground = () => {
305
306
  if (id === workerClientId) {
306
307
  const base = `${config.server.https ? "https" : "http"}://localhost:${config.server.port}/`;
307
308
  return defineClientValues(
308
- workerHmrClient.replace("__BASE__", JSON.stringify(base)),
309
+ workerHmrClient.replace("__BASE__", JSON.stringify(base)).replace("__LIVE_RELOAD__", JSON.stringify(liveReload)),
309
310
  config
310
311
  );
311
312
  }
@@ -318,6 +319,7 @@ const pluginBackground = () => {
318
319
  async config(config2) {
319
320
  const opts = await getOptions(config2);
320
321
  browser = opts.browser || "chrome";
322
+ liveReload = opts.liveReload !== false;
321
323
  },
322
324
  configResolved(_config) {
323
325
  config = _config;
@@ -376,7 +378,7 @@ const pluginBackground = () => {
376
378
  ];
377
379
  };
378
380
 
379
- var contentHmrPort = "function isCrxHMRPayload(x) {\n return x.type === \"custom\" && x.event.startsWith(\"crx:\");\n}\nclass HMRPort {\n port;\n callbacks = /* @__PURE__ */ new Map();\n constructor() {\n setInterval(() => {\n try {\n this.port?.postMessage({ data: \"ping\" });\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"Extension context invalidated.\")) {\n location.reload();\n } else\n throw error;\n }\n }, __CRX_HMR_TIMEOUT__);\n setInterval(this.initPort, 5 * 60 * 1e3);\n this.initPort();\n }\n initPort = () => {\n this.port?.disconnect();\n this.port = chrome.runtime.connect({ name: \"@crx/client\" });\n this.port.onDisconnect.addListener(this.handleDisconnect.bind(this));\n this.port.onMessage.addListener(this.handleMessage.bind(this));\n this.port.postMessage({ type: \"connected\" });\n };\n handleDisconnect = () => {\n if (this.callbacks.has(\"close\"))\n for (const cb of this.callbacks.get(\"close\")) {\n cb({ wasClean: true });\n }\n };\n handleMessage = (message) => {\n const forward = (data) => {\n if (this.callbacks.has(\"message\"))\n for (const cb of this.callbacks.get(\"message\")) {\n cb({ data });\n }\n };\n const payload = JSON.parse(message.data);\n if (isCrxHMRPayload(payload)) {\n if (payload.event === \"crx:runtime-reload\") {\n console.log(\"[crx] runtime reload\");\n setTimeout(() => location.reload(), 500);\n } else {\n forward(JSON.stringify(payload.data));\n }\n } else {\n forward(message.data);\n }\n };\n addEventListener = (event, callback) => {\n const cbs = this.callbacks.get(event) ?? /* @__PURE__ */ new Set();\n cbs.add(callback);\n this.callbacks.set(event, cbs);\n };\n send = (data) => {\n if (this.port)\n this.port.postMessage({ data });\n else\n throw new Error(\"HMRPort is not initialized\");\n };\n}\n\nexport { HMRPort };\n";
381
+ var contentHmrPort = "function isCrxHMRPayload(x) {\n return x.type === \"custom\" && x.event.startsWith(\"crx:\");\n}\nclass HMRPort {\n port;\n callbacks = /* @__PURE__ */ new Map();\n constructor() {\n setInterval(() => {\n try {\n this.port?.postMessage({ data: \"ping\" });\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"Extension context invalidated.\")) {\n location.reload();\n } else\n throw error;\n }\n }, __CRX_HMR_TIMEOUT__);\n setInterval(this.initPort, 5 * 60 * 1e3);\n this.initPort();\n }\n initPort = () => {\n this.port?.disconnect();\n this.port = chrome.runtime.connect({ name: \"@crx/client\" });\n this.port.onDisconnect.addListener(this.handleDisconnect.bind(this));\n this.port.onMessage.addListener(this.handleMessage.bind(this));\n this.port.postMessage({ type: \"connected\" });\n };\n handleDisconnect = () => {\n if (this.callbacks.has(\"close\"))\n for (const cb of this.callbacks.get(\"close\")) {\n cb({ wasClean: true });\n }\n };\n handleMessage = (message) => {\n const forward = (data) => {\n if (this.callbacks.has(\"message\"))\n for (const cb of this.callbacks.get(\"message\")) {\n cb({ data });\n }\n };\n const payload = JSON.parse(message.data);\n if (isCrxHMRPayload(payload)) {\n if (payload.event === \"crx:runtime-reload\") {\n if (__CRX_LIVE_RELOAD__) {\n console.log(\"[crx] runtime reload\");\n setTimeout(() => location.reload(), 500);\n } else {\n console.log(\"[crx] runtime reload suppressed (liveReload disabled)\");\n }\n } else {\n forward(JSON.stringify(payload.data));\n }\n } else {\n forward(message.data);\n }\n };\n addEventListener = (event, callback) => {\n const cbs = this.callbacks.get(event) ?? /* @__PURE__ */ new Set();\n cbs.add(callback);\n this.callbacks.set(event, cbs);\n };\n send = (data) => {\n if (this.port)\n this.port.postMessage({ data });\n else\n throw new Error(\"HMRPort is not initialized\");\n };\n}\n\nexport { HMRPort };\n";
380
382
 
381
383
  var contentDevLoader = "(function () {\n 'use strict';\n\n const injectTime = performance.now();\n (async () => {\n if (__PREAMBLE__)\n await import(\n /* @vite-ignore */\n chrome.runtime.getURL(__PREAMBLE__)\n );\n await import(\n /* @vite-ignore */\n chrome.runtime.getURL(__CLIENT__)\n );\n const { onExecute } = await import(\n /* @vite-ignore */\n chrome.runtime.getURL(__SCRIPT__)\n );\n onExecute?.({ perf: { injectTime, loadTime: performance.now() - injectTime } });\n })().catch(console.error);\n\n})();\n";
382
384
 
@@ -427,6 +429,21 @@ function createProMainLoader({ fileName }) {
427
429
  return contentProMainLoader.replace(/__SCRIPT__/g, JSON.stringify(fileName));
428
430
  }
429
431
 
432
+ const { outputFile: outputFile$1 } = fsx;
433
+ const getIifeGlobalName = (fileName) => {
434
+ const base = fileName.split("/").pop() ?? fileName;
435
+ const sanitized = base.replace(/\W+/g, "_").replace(/^_+/, "");
436
+ return `crx_${sanitized || "content_script"}`;
437
+ };
438
+ const resolveScriptInput = (server, id) => {
439
+ if (id.startsWith("/@fs/"))
440
+ return id.slice("/@fs/".length);
441
+ if (id.startsWith("/"))
442
+ return join(server.config.root, id.slice(1));
443
+ return id;
444
+ };
445
+ const isOutputChunk = (item) => item.type === "chunk";
446
+ const isOutputAsset = (item) => item.type === "asset";
430
447
  const serverEvent$ = new ReplaySubject(1);
431
448
  const close$ = serverEvent$.pipe(
432
449
  filter((e) => e.type === "close"),
@@ -486,6 +503,8 @@ function prepAsset(fileName, { id, source }) {
486
503
  );
487
504
  }
488
505
  function prepScript(fileName, script) {
506
+ if (script.type === "iife")
507
+ return prepIifeScript(fileName, script);
489
508
  return ($) => $.pipe(
490
509
  // get script contents from dev server
491
510
  mergeMap(async ({ server }) => {
@@ -543,6 +562,105 @@ ${sourceMap}
543
562
  })
544
563
  );
545
564
  }
565
+ async function bundleIife(server, script, fileName) {
566
+ const input = resolveScriptInput(server, script.id);
567
+ const sourcemap = server.config.build.sourcemap === "inline" ? "inline" : false;
568
+ const result = await build({
569
+ root: server.config.root,
570
+ mode: server.config.mode,
571
+ configFile: false,
572
+ // Don't load user's config - use minimal IIFE-specific settings
573
+ logLevel: "silent",
574
+ resolve: {
575
+ // Copy resolve settings from the dev server for consistency
576
+ alias: server.config.resolve.alias,
577
+ extensions: server.config.resolve.extensions,
578
+ conditions: server.config.resolve.conditions
579
+ },
580
+ build: {
581
+ write: false,
582
+ // Don't write to disk, we'll handle that
583
+ manifest: false,
584
+ // Don't generate Vite manifest
585
+ rollupOptions: {
586
+ input,
587
+ output: {
588
+ format: "iife",
589
+ name: getIifeGlobalName(fileName),
590
+ entryFileNames: fileName,
591
+ inlineDynamicImports: true,
592
+ // Required for IIFE format
593
+ sourcemap
594
+ }
595
+ },
596
+ minify: false,
597
+ copyPublicDir: false
598
+ }
599
+ });
600
+ const outputs = Array.isArray(result) ? result : [result];
601
+ const firstOutput = outputs[0];
602
+ const output = "output" in firstOutput ? firstOutput.output : void 0;
603
+ if (!output) {
604
+ throw new Error(`Unable to generate IIFE bundle for "${script.id}"`);
605
+ }
606
+ const entryChunk = output.find(
607
+ (item) => isOutputChunk(item) && item.isEntry
608
+ );
609
+ if (!entryChunk) {
610
+ throw new Error(`Unable to generate IIFE bundle for "${script.id}"`);
611
+ }
612
+ const assets = output.filter(isOutputAsset).filter(
613
+ // Filter out manifest.json to avoid overwriting extension manifest
614
+ (asset) => asset.fileName !== "manifest.json" && !asset.fileName.startsWith(".vite/")
615
+ );
616
+ const extraChunks = output.filter(
617
+ (item) => isOutputChunk(item) && !item.isEntry
618
+ );
619
+ return {
620
+ code: entryChunk.code,
621
+ assets,
622
+ extraChunks
623
+ };
624
+ }
625
+ function prepIifeScript(fileName, script) {
626
+ return ($) => $.pipe(
627
+ mergeMap(async ({ server }) => {
628
+ const target = getOutputPath(server, fileName);
629
+ const { code, assets, extraChunks } = await bundleIife(
630
+ server,
631
+ script,
632
+ fileName
633
+ );
634
+ return { target, source: code, deps: [], server, assets, extraChunks };
635
+ }),
636
+ mergeMap(
637
+ async ({ target, source, deps, server, assets, extraChunks }) => {
638
+ const extras = [
639
+ ...assets.map((asset) => ({
640
+ fileName: asset.fileName,
641
+ source: asset.source
642
+ })),
643
+ ...extraChunks.map((chunk) => ({
644
+ fileName: chunk.fileName,
645
+ source: chunk.code
646
+ }))
647
+ ].filter((item) => item.fileName !== fileName);
648
+ await Promise.all(
649
+ extras.map(async (item) => {
650
+ const outputPath = getOutputPath(server, item.fileName);
651
+ if (typeof item.source === "undefined" || item.source === null)
652
+ return;
653
+ if (item.source instanceof Uint8Array)
654
+ await outputFile$1(outputPath, item.source);
655
+ else
656
+ await outputFile$1(outputPath, item.source, { encoding: "utf8" });
657
+ })
658
+ );
659
+ return { target, source, deps };
660
+ }
661
+ )
662
+ );
663
+ }
546
664
  async function allFilesReady() {
547
665
  await firstValueFrom(allFilesReady$);
548
666
  }
@@ -663,6 +781,7 @@ const pluginContentScripts = () => {
663
781
  let server;
664
782
  let preambleCode;
665
783
  let hmrTimeout;
784
+ let liveReload = true;
666
785
  let sub = new Subscription();
667
786
  const worldMainIds = /* @__PURE__ */ new Set();
668
787
  const findWorldMainIds = async (config, env) => {
@@ -675,7 +794,7 @@ const pluginContentScripts = () => {
675
794
  });
676
795
  if (worldMainIds.size) {
677
796
  const name = `[${pluginName}]`;
678
- const message = pc.yellow(
797
+ const message = colors.yellow(
679
798
  [
680
799
  `${name} Some content-scripts don't support HMR because the world is MAIN:`,
681
800
  ...[...worldMainIds].map((id) => ` ${id}`)
@@ -690,9 +809,11 @@ const pluginContentScripts = () => {
690
809
  apply: "serve",
691
810
  async config(config, env) {
692
811
  await findWorldMainIds(config, env);
693
- const { contentScripts: contentScripts2 = {} } = await getOptions(config);
812
+ const opts = await getOptions(config);
813
+ const { contentScripts: contentScripts2 = {} } = opts;
694
814
  hmrTimeout = contentScripts2.hmrTimeout ?? 5e3;
695
815
  preambleCode = preambleCode ?? contentScripts2.preambleCode;
816
+ liveReload = opts.liveReload !== false;
696
817
  },
697
818
  async configureServer(_server) {
698
819
  server = _server;
@@ -728,7 +849,8 @@ const pluginContentScripts = () => {
728
849
  });
729
850
  script.fileName = loader.fileName;
730
851
  } else if (type === "iife") {
731
- throw new Error("IIFE content scripts are not implemented");
852
+ const file = add({ type: "iife", id });
853
+ script.fileName = file.fileName;
732
854
  } else {
733
855
  const file = add({ type: "module", id });
734
856
  script.fileName = file.fileName;
@@ -748,10 +870,7 @@ const pluginContentScripts = () => {
748
870
  return defined;
749
871
  }
750
872
  if (id === contentHmrPortId) {
751
- const defined = contentHmrPort.replace(
752
- "__CRX_HMR_TIMEOUT__",
753
- JSON.stringify(hmrTimeout)
754
- );
873
+ const defined = contentHmrPort.replace("__CRX_HMR_TIMEOUT__", JSON.stringify(hmrTimeout)).replace("__CRX_LIVE_RELOAD__", JSON.stringify(liveReload));
755
874
  return defined;
756
875
  }
757
876
  },
@@ -806,7 +925,7 @@ const pluginContentScripts = () => {
806
925
  `;
807
926
  }
808
927
  } else if (script.type === "iife") {
809
- throw new Error("IIFE content scripts are not implemented");
928
+ continue;
810
929
  }
811
930
  contentScripts.set(script.refId, formatFileData(script));
812
931
  }
@@ -890,6 +1009,165 @@ const pluginDeclaredContentScripts = () => {
890
1009
  };
891
1010
  };
892
1011
 
1012
+ function isIifeContentScript(file) {
1013
+ return /\.iife\.(ts|tsx|js|jsx|mjs|cjs)$/.test(file);
1014
+ }
1015
+ const pluginContentScriptsIife = () => {
1016
+ const pluginName = "crx:content-scripts-iife";
1017
+ let config;
1018
+ return [
1019
+ {
1020
+ name: `${pluginName}-config`,
1021
+ enforce: "pre",
1022
+ configResolved(resolvedConfig) {
1023
+ config = resolvedConfig;
1024
+ }
1025
+ },
1026
+ {
1027
+ name: pluginName,
1028
+ apply: "build",
1029
+ enforce: "post",
1030
+ async generateBundle(options, bundle) {
1031
+ const opts = await getOptions({ plugins: config.plugins });
1032
+ const _manifest = opts.manifest;
1033
+ const manifest = typeof _manifest === "function" ? await _manifest({ command: "build", mode: config.mode }) : await Promise.resolve(_manifest);
1034
+ const standaloneFiles = (opts.contentScripts?.standaloneFiles || []).map(
1035
+ (f) => f.replace(/^\//, "")
1036
+ );
1037
+ const isStandaloneFile = (file) => {
1038
+ const normalized = file.replace(/^\//, "");
1039
+ return standaloneFiles.includes(normalized);
1040
+ };
1041
+ const iifeEntries = [];
1042
+ if (manifest.content_scripts) {
1043
+ for (const { js = [], matches = [] } of manifest.content_scripts) {
1044
+ for (const file of js) {
1045
+ if (isIifeContentScript(file) || isStandaloneFile(file)) {
1046
+ const id = join(config.root, file);
1047
+ iifeEntries.push({ file, id, matches });
1048
+ }
1049
+ }
1050
+ }
1051
+ }
1052
+ for (const [, script] of contentScripts.entries()) {
1053
+ if (script.type === "iife" && script.isDynamicScript) {
1054
+ const id = join(config.root, script.id);
1055
+ if (!iifeEntries.some((e) => e.id === id)) {
1056
+ iifeEntries.push({
1057
+ file: script.id,
1058
+ id,
1059
+ matches: script.matches ?? [],
1060
+ isDynamic: true
1061
+ });
1062
+ }
1063
+ }
1064
+ }
1065
+ if (iifeEntries.length === 0)
1066
+ return;
1067
+ console.log(
1068
+ colors.cyan(`
1069
+ [${pluginName}] Building ${iifeEntries.length} content script(s) as IIFE...`)
1070
+ );
1071
+ for (const entry of iifeEntries) {
1072
+ const outputFileName = getIifeOutputPath(entry.file);
1073
+ try {
1074
+ const iifeConfig = createIifeConfig(config, entry.id, outputFileName);
1075
+ const result = await build(iifeConfig);
1076
+ if ("on" in result) {
1077
+ console.error(colors.red(` Unexpected watcher result for ${entry.file}`));
1078
+ continue;
1079
+ }
1080
+ const outputs = Array.isArray(result) ? result.flatMap((r) => "output" in r ? r.output : []) : result.output;
1081
+ for (const chunk of outputs) {
1082
+ if (chunk.type === "chunk" && chunk.isEntry) {
1083
+ bundle[outputFileName] = {
1084
+ ...chunk,
1085
+ fileName: outputFileName
1086
+ };
1087
+ const existingScript = contentScripts.get(entry.file);
1088
+ if (existingScript) {
1089
+ existingScript.fileName = outputFileName;
1090
+ contentScripts.set(entry.file, formatFileData(existingScript));
1091
+ } else {
1092
+ contentScripts.set(
1093
+ entry.file,
1094
+ formatFileData({
1095
+ type: "iife",
1096
+ id: entry.file,
1097
+ refId: entry.file,
1098
+ matches: entry.matches,
1099
+ fileName: outputFileName
1100
+ })
1101
+ );
1102
+ }
1103
+ console.log(
1104
+ colors.green(` \u2713 ${basename(entry.file)} \u2192 ${outputFileName}`)
1105
+ );
1106
+ }
1107
+ }
1108
+ } catch (error) {
1109
+ console.error(
1110
+ colors.red(` \u2717 Failed to build ${entry.file}:`),
1111
+ error
1112
+ );
1113
+ throw error;
1114
+ }
1115
+ }
1116
+ console.log(colors.cyan(`[${pluginName}] IIFE build complete
1117
+ `));
1118
+ }
1119
+ }
1120
+ ];
1121
+ };
1122
+ function getIifeOutputPath(file) {
1123
+ const normalizedFile = file.replace(/^\//, "");
1124
+ const dir = dirname(normalizedFile);
1125
+ const name = basename(normalizedFile).replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
1126
+ return dir && dir !== "." ? `${dir}/${name}.js` : `${name}.js`;
1127
+ }
1128
+ function createIifeConfig(parentConfig, entryPath, outputFileName) {
1129
+ const entryName = basename(outputFileName, ".js");
1130
+ const baseConfig = {
1131
+ // Inherit relevant settings from parent config
1132
+ root: parentConfig.root,
1133
+ mode: parentConfig.mode,
1134
+ resolve: parentConfig.resolve,
1135
+ define: parentConfig.define,
1136
+ esbuild: parentConfig.esbuild
1137
+ };
1138
+ const iifeConfig = {
1139
+ configFile: false,
1140
+ logLevel: "warn",
1141
+ plugins: [],
1142
+ build: {
1143
+ write: false,
1144
+ // Don't write to disk, we'll add to bundle
1145
+ emptyOutDir: false,
1146
+ lib: {
1147
+ entry: entryPath,
1148
+ formats: ["iife"],
1149
+ name: safeVarName(entryName),
1150
+ fileName: () => outputFileName
1151
+ },
1152
+ rollupOptions: {
1153
+ output: {
1154
+ entryFileNames: outputFileName,
1155
+ // Ensure all dependencies are inlined
1156
+ inlineDynamicImports: true
1157
+ }
1158
+ },
1159
+ // Match parent config settings
1160
+ minify: parentConfig.build.minify,
1161
+ sourcemap: parentConfig.build.sourcemap,
1162
+ target: parentConfig.build.target
1163
+ }
1164
+ };
1165
+ return mergeConfig(baseConfig, iifeConfig);
1166
+ }
1167
+ function safeVarName(name) {
1168
+ return name.replace(/[^a-zA-Z0-9_$]/g, "_").replace(/^(\d)/, "_$1").replace(/^$/, "_");
1169
+ }
1170
+
893
1171
  const _dynamicScriptRegEx = /\b(import.meta).CRX_DYNAMIC_SCRIPT_(.+?)[,;]/gm;
894
1172
  const dynamicScriptRegEx = () => {
895
1173
  _dynamicScriptRegEx.lastIndex = 0;
@@ -897,12 +1175,18 @@ const dynamicScriptRegEx = () => {
897
1175
  };
898
1176
  const pluginDynamicContentScripts = () => {
899
1177
  let config;
1178
+ let standaloneFiles = [];
900
1179
  return [
901
1180
  {
902
1181
  name: "crx:dynamic-content-scripts-loader",
903
1182
  enforce: "pre",
904
1183
  configResolved(_config) {
905
1184
  config = _config;
1185
+ getOptions({ plugins: _config.plugins }).then((opts) => {
1186
+ standaloneFiles = (opts.contentScripts?.standaloneFiles || []).map(
1187
+ (f) => f.replace(/^\//, "")
1188
+ );
1189
+ }).catch(() => void 0);
906
1190
  },
907
1191
  configureServer(server) {
908
1192
  return () => {
@@ -933,9 +1217,9 @@ const pluginDynamicContentScripts = () => {
933
1217
  };
934
1218
  },
935
1219
  async resolveId(_source, importer) {
936
- if (importer && _source.includes("?script")) {
1220
+ if (importer && (_source.includes("?script") || _source.includes("?iife"))) {
937
1221
  const url = new URL(_source, "stub://stub");
938
- if (url.searchParams.has("script")) {
1222
+ if (url.searchParams.has("script") || url.searchParams.has("iife")) {
939
1223
  const [source] = _source.split("?");
940
1224
  const resolved = await this.resolve(source, importer, {
941
1225
  skipSelf: true
@@ -945,8 +1229,11 @@ const pluginDynamicContentScripts = () => {
945
1229
  `Could not resolve dynamic script: "${_source}" from "${importer}"`
946
1230
  );
947
1231
  const { id } = resolved;
1232
+ const relId = relative(config.root, id).replace(/^\//, "");
948
1233
  let type = "loader";
949
- if (url.searchParams.has("module")) {
1234
+ if (isIifeContentScript(relId) || standaloneFiles.includes(relId)) {
1235
+ type = "iife";
1236
+ } else if (url.searchParams.has("module")) {
950
1237
  type = "module";
951
1238
  } else if (url.searchParams.has("iife")) {
952
1239
  type = "iife";
@@ -966,13 +1253,13 @@ const pluginDynamicContentScripts = () => {
966
1253
  });
967
1254
  } else {
968
1255
  refId = scriptId;
969
- const relId = relative(config.root, id);
1256
+ const relId2 = relative(config.root, id);
970
1257
  fileName = getFileName({
971
1258
  type: type === "iife" ? "iife" : "module",
972
- id: relId
1259
+ id: relId2
973
1260
  });
974
1261
  if (type === "loader")
975
- loaderName = getFileName({ type, id: relId });
1262
+ loaderName = getFileName({ type, id: relId2 });
976
1263
  }
977
1264
  script = formatFileData({
978
1265
  type,
@@ -1018,6 +1305,7 @@ const pluginDynamicContentScripts = () => {
1018
1305
  * Can't use `renderChunk` b/c pre plugin crx:content-scripts uses
1019
1306
  * `generateBundle` to emit loaders. Must come after "enforce: pre".
1020
1307
  */
1308
+ enforce: "post",
1021
1309
  generateBundle(options, bundle) {
1022
1310
  for (const chunk of Object.values(bundle))
1023
1311
  if (chunk.type === "chunk") {
@@ -1034,9 +1322,9 @@ const pluginDynamicContentScripts = () => {
1034
1322
  throw new Error(
1035
1323
  `Content script fileName is undefined: "${script.id}"`
1036
1324
  );
1037
- return `${JSON.stringify(
1038
- `/${script.loaderName ?? script.fileName}`
1039
- )}${match.split(scriptKey)[1]}`;
1325
+ const fileName = script.loaderName ?? script.fileName;
1326
+ const path = fileName;
1327
+ return `${JSON.stringify(path)}${match.split(scriptKey)[1]}`;
1040
1328
  }
1041
1329
  );
1042
1330
  chunk.code = replaced;
@@ -1121,7 +1409,7 @@ const pluginFileWriterPolyfill = () => {
1121
1409
  async function manifestFiles(manifest, options = {}) {
1122
1410
  let locales = [];
1123
1411
  if (manifest.default_locale)
1124
- locales = await fg("_locales/**/messages.json", options);
1412
+ locales = await glob("_locales/**/messages.json", options);
1125
1413
  const rulesets = manifest.declarative_net_request?.rule_resources.flatMap(
1126
1414
  ({ path }) => path
1127
1415
  ) ?? [];
@@ -1145,8 +1433,8 @@ async function manifestFiles(manifest, options = {}) {
1145
1433
  manifest.web_accessible_resources.flatMap(({ resources: resources2 }) => resources2).map(async (r) => {
1146
1434
  if (["*", "**/*"].includes(r))
1147
1435
  return void 0;
1148
- if (fg.isDynamicPattern(r))
1149
- return fg(r, options);
1436
+ if (isDynamicPattern(r))
1437
+ return glob(r, options);
1150
1438
  return r;
1151
1439
  })
1152
1440
  );
@@ -1164,7 +1452,7 @@ async function manifestFiles(manifest, options = {}) {
1164
1452
  };
1165
1453
  }
1166
1454
  async function dirFiles(dir) {
1167
- const files = await fg(join(dir, "**", "*"));
1455
+ const files = await glob(join(dir, "**", "*"));
1168
1456
  return files;
1169
1457
  }
1170
1458
  function htmlFiles(manifest) {
@@ -1314,6 +1602,7 @@ const pluginHMR = () => {
1314
1602
  let decoratedSend;
1315
1603
  let config;
1316
1604
  let subs;
1605
+ let liveReload = true;
1317
1606
  return [
1318
1607
  {
1319
1608
  name: "crx:hmr",
@@ -1321,6 +1610,8 @@ const pluginHMR = () => {
1321
1610
  enforce: "pre",
1322
1611
  // server hmr host should be localhost
1323
1612
  async config({ server = {}, ...config2 }) {
1613
+ const opts = await getOptions({ ...config2, server });
1614
+ liveReload = opts.liveReload !== false;
1324
1615
  if (server.hmr === false)
1325
1616
  return;
1326
1617
  if (server.hmr === true)
@@ -1359,7 +1650,9 @@ const pluginHMR = () => {
1359
1650
  subs.add(fileWriterError$.subscribe(send));
1360
1651
  subs.add(
1361
1652
  crxHMRPayload$.subscribe((payload) => {
1362
- send(payload);
1653
+ if (liveReload) {
1654
+ send(payload);
1655
+ }
1363
1656
  })
1364
1657
  );
1365
1658
  }
@@ -1368,8 +1661,9 @@ const pluginHMR = () => {
1368
1661
  subs.unsubscribe();
1369
1662
  },
1370
1663
  // background changes require a full extension reload
1371
- handleHotUpdate({ modules, server }) {
1664
+ handleHotUpdate({ file, modules, server }) {
1372
1665
  const { root } = server.config;
1666
+ const changedFilePath = file ? file.startsWith(root) ? prefix$1("/", file.slice(root.length)) : null : null;
1373
1667
  const relFiles = /* @__PURE__ */ new Set();
1374
1668
  const fsFiles = /* @__PURE__ */ new Set();
1375
1669
  const virtualModules = /* @__PURE__ */ new Set();
@@ -1386,13 +1680,17 @@ const pluginHMR = () => {
1386
1680
  }
1387
1681
  }
1388
1682
  }
1389
- fsFiles.forEach((file) => update(file));
1390
- virtualModules.forEach((file) => update(file));
1683
+ fsFiles.forEach((file2) => update(file2));
1684
+ virtualModules.forEach((file2) => update(file2));
1391
1685
  if (inputManifestFiles.background.length) {
1392
1686
  const background = prefix$1("/", inputManifestFiles.background[0]);
1393
1687
  if (relFiles.has(background) || modules.some(isImporter(join(server.config.root, background)))) {
1394
- debug$2("sending runtime reload");
1395
- server.ws.send(crxRuntimeReload);
1688
+ if (liveReload) {
1689
+ debug$2("sending runtime reload");
1690
+ server.ws.send(crxRuntimeReload);
1691
+ } else {
1692
+ debug$2("skipping runtime reload (liveReload disabled)");
1693
+ }
1396
1694
  }
1397
1695
  }
1398
1696
  for (const [key, script] of contentScripts)
@@ -1409,15 +1707,26 @@ const pluginHMR = () => {
1409
1707
  if (changedCssFiles.length > 0) {
1410
1708
  changedCssFiles.forEach((relFile) => update(relFile));
1411
1709
  update(script.id);
1412
- virtualModules.forEach((file) => update(file));
1710
+ virtualModules.forEach((file2) => update(file2));
1413
1711
  }
1414
1712
  }
1415
1713
  } else {
1416
1714
  if (relFiles.has(script.id) || modules.some(isImporter(join(server.config.root, script.id)))) {
1417
1715
  relFiles.forEach((relFile) => update(relFile));
1418
- virtualModules.forEach((file) => update(file));
1716
+ virtualModules.forEach((file2) => update(file2));
1419
1717
  }
1420
1718
  }
1719
+ const scriptPath = prefix$1("/", script.id);
1720
+ if (script.type === "iife" && changedFilePath === scriptPath) {
1721
+ debug$2("IIFE script changed, triggering rebuild: %s", script.id);
1722
+ const updatedFiles = update(scriptPath);
1723
+ Promise.all(updatedFiles.map((f) => f.file)).then(() => {
1724
+ if (liveReload) {
1725
+ debug$2("IIFE rebuild complete, sending runtime reload");
1726
+ server.ws.send(crxRuntimeReload);
1727
+ }
1728
+ });
1729
+ }
1421
1730
  }
1422
1731
  }
1423
1732
  },
@@ -1449,11 +1758,11 @@ const pluginHMR = () => {
1449
1758
  };
1450
1759
 
1451
1760
  function printStr(dir) {
1452
- return ` ${pc.magentaBright("B R O W S E R")}
1453
- ${pc.greenBright("E X T E N S I O N")}
1454
- ${pc.blueBright("T O O L S")}
1761
+ return ` ${colors.magentaBright("B R O W S E R")}
1762
+ ${colors.greenBright("E X T E N S I O N")}
1763
+ ${colors.blueBright("T O O L S")}
1455
1764
 
1456
- ${pc.green("\u279C")} ${pc.bold("CRXJS")}: ${pc.green(`Load ${pc.cyan(dir)} as unpacked extension`)}`;
1765
+ ${colors.green("\u279C")} ${colors.bold("CRXJS")}: ${colors.green(`Load ${colors.cyan(dir)} as unpacked extension`)}`;
1457
1766
  }
1458
1767
  const pluginPrint = () => {
1459
1768
  let outDir = "dist";
@@ -1810,9 +2119,19 @@ const pluginManifest = () => {
1810
2119
  }
1811
2120
  }
1812
2121
  } else {
2122
+ const opts = await getOptions({ plugins: config.plugins });
2123
+ const standaloneFiles = (opts.contentScripts?.standaloneFiles || []).map(
2124
+ (f) => f.replace(/^\//, "")
2125
+ );
2126
+ const isStandaloneFile = (file) => {
2127
+ const normalized = file.replace(/^\//, "");
2128
+ return standaloneFiles.includes(normalized);
2129
+ };
1813
2130
  if (manifest2.content_scripts)
1814
2131
  for (const { js = [], matches = [] } of manifest2.content_scripts)
1815
2132
  for (const file of js) {
2133
+ if (isIifeContentScript(file) || isStandaloneFile(file))
2134
+ continue;
1816
2135
  const id2 = join(config.root, file);
1817
2136
  const refId2 = this.emitFile({
1818
2137
  type: "chunk",
@@ -1923,11 +2242,11 @@ const pluginManifest = () => {
1923
2242
  const name = `[${plugin.name}]`;
1924
2243
  let message = error;
1925
2244
  if (error instanceof Error) {
1926
- message = pc.red(
2245
+ message = colors.red(
1927
2246
  `${name} ${error.stack ? error.stack : error.message}`
1928
2247
  );
1929
2248
  } else if (typeof error === "string") {
1930
- message = pc.red(`${name} ${error}`);
2249
+ message = colors.red(`${name} ${error}`);
1931
2250
  }
1932
2251
  console.log(message);
1933
2252
  throw new Error(`Error in ${plugin.name}.renderCrxManifest`);
@@ -2266,6 +2585,8 @@ const crx = (options) => {
2266
2585
  contentScripts.clear();
2267
2586
  return [
2268
2587
  pluginOptionsProvider(options),
2588
+ pluginContentScriptsIife(),
2589
+ // Must come early so isIifeModeEnabled is set before manifest plugin
2269
2590
  pluginBackground(),
2270
2591
  pluginContentScripts(),
2271
2592
  pluginDeclaredContentScripts(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crxjs/vite-plugin",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "Build Chrome Extensions with this Vite plugin.",
5
5
  "keywords": [
6
6
  "rollup-plugin",
@@ -59,7 +59,7 @@
59
59
  "convert-source-map": "^1.7.0",
60
60
  "debug": "^4.3.3",
61
61
  "es-module-lexer": "^0.10.0",
62
- "fast-glob": "^3.2.11",
62
+ "tinyglobby": "^0.2.15",
63
63
  "fs-extra": "^10.0.1",
64
64
  "jsesc": "^3.0.2",
65
65
  "magic-string": "^0.30.12",
@@ -67,7 +67,7 @@
67
67
  "pathe": "^2.0.1",
68
68
  "picocolors": "^1.1.1",
69
69
  "react-refresh": "^0.13.0",
70
- "rollup": "2.79.2",
70
+ "rollup": "2.80.0",
71
71
  "rxjs": "7.5.7"
72
72
  },
73
73
  "devDependencies": {
@@ -92,7 +92,7 @@
92
92
  "@typescript-eslint/parser": "5.41.0",
93
93
  "@vitejs/plugin-react": "^2.2.0",
94
94
  "@vitejs/plugin-vue": "3.2.0",
95
- "chokidar": "^3.5.3",
95
+ "chokidar": "^5.0.0",
96
96
  "esbuild": "0.17.14",
97
97
  "esbuild-runner": "2.2.2",
98
98
  "eslint": "8.43.0",