@crxjs/vite-plugin 2.6.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.mjs +170 -32
  2. package/package.json +7 -9
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { simple } from 'acorn-walk';
2
- import { createHash } from 'crypto';
2
+ import { createHash, randomBytes } from 'crypto';
3
3
  import debug$5 from 'debug';
4
4
  import { join, normalize, dirname, basename, isAbsolute, relative, resolve, parse as parse$1 } from 'pathe';
5
- import { Subject, filter, ReplaySubject, switchMap, of, startWith, map, BehaviorSubject, mergeMap, firstValueFrom, takeUntil, first, toArray, retry, concatWith, Subscription, buffer } from 'rxjs';
5
+ import { Subject, filter, ReplaySubject, switchMap, of, startWith, tap, debounceTime, map, share, BehaviorSubject, mergeMap, firstValueFrom, takeUntil, first, toArray, retry, concatWith, Subscription, buffer } from 'rxjs';
6
6
  import fsx from 'fs-extra';
7
7
  import { performance } from 'perf_hooks';
8
8
  import { rollup } from 'rollup';
@@ -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 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";
61
+ var workerHmrClient = "const crxClientPortName = `@crx/client:${__CRX_HMR_TOKEN__}`;\nconst 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();\nfunction isExternalSenderAllowed(port) {\n return !port.sender?.id || port.sender.id === chrome.runtime.id;\n}\nfunction handlePort(port, { external = false } = {}) {\n if (port.name === crxClientPortName && (!external || isExternalSenderAllowed(port))) {\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}\nchrome.runtime.onConnect.addListener(handlePort);\nchrome.runtime.onConnectExternal.addListener(\n (port) => handlePort(port, { external: true })\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);
@@ -274,6 +274,18 @@ async function fileReady(script) {
274
274
  await Promise.all(deps.map(fileReady));
275
275
  }
276
276
 
277
+ const crxHmrTokens = /* @__PURE__ */ new WeakMap();
278
+ function getCrxHmrToken(config) {
279
+ if (config.webSocketToken)
280
+ return config.webSocketToken;
281
+ let token = crxHmrTokens.get(config);
282
+ if (!token) {
283
+ token = randomBytes(16).toString("hex");
284
+ crxHmrTokens.set(config, token);
285
+ }
286
+ return token;
287
+ }
288
+
277
289
  const viteClientId = "/@vite/client";
278
290
  const customElementsId = "/@webcomponents/custom-elements";
279
291
  const contentHmrPortId = "/@crx/client-port";
@@ -312,7 +324,12 @@ const pluginBackground = () => {
312
324
  if (id === workerClientId) {
313
325
  const base = `${config.server.https ? "https" : "http"}://localhost:${config.server.port}/`;
314
326
  return defineClientValues(
315
- workerHmrClient.replace("__BASE__", JSON.stringify(base)).replace("__LIVE_RELOAD__", JSON.stringify(liveReload)),
327
+ workerHmrClient.replace("__BASE__", JSON.stringify(base)).replace("__LIVE_RELOAD__", JSON.stringify(liveReload)).replace(
328
+ "__CRX_HMR_TOKEN__",
329
+ JSON.stringify(
330
+ getCrxHmrToken(config)
331
+ )
332
+ ),
316
333
  config
317
334
  );
318
335
  }
@@ -430,11 +447,11 @@ function pluginExtensionCors() {
430
447
  };
431
448
  }
432
449
 
433
- 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";
450
+ var contentHmrPort = "const crxClientPortName = `@crx/client:${__CRX_HMR_TOKEN__}`;\nfunction hasOwnExtensionRuntime(runtime2, extensionId2) {\n try {\n return new URL(runtime2.getURL(\"\")).host === extensionId2;\n } catch {\n return false;\n }\n}\nconst runtime = typeof chrome === \"undefined\" ? void 0 : chrome.runtime;\nconst extensionId = new URL(import.meta.url).host;\nconst connectsToOwnRuntime = runtime ? hasOwnExtensionRuntime(runtime, extensionId) : false;\nfunction 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 if (!runtime)\n throw new Error(\"[crx] chrome.runtime is not available\");\n const connectInfo = { name: crxClientPortName };\n this.port?.disconnect();\n this.port = connectsToOwnRuntime ? runtime.connect(connectInfo) : runtime.connect(extensionId, connectInfo);\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";
434
451
 
435
452
  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";
436
453
 
437
- var contentDevMainLoader = "(function () {\n 'use strict';\n\n const injectTime = performance.now();\n (async () => {\n console.warn(__SCRIPT__, \"Content-script doesn't support HMR because the world is MAIN\");\n const { onExecute } = await import(\n /* @vite-ignore */\n __SCRIPT__\n );\n onExecute?.({ perf: { injectTime, loadTime: performance.now() - injectTime } });\n })().catch(console.error);\n\n})();\n";
454
+ var contentDevMainLoader = "(function () {\n 'use strict';\n\n const injectTime = performance.now();\n (async () => {\n try {\n if (__PREAMBLE__)\n await import(\n /* @vite-ignore */\n __PREAMBLE__\n );\n await import(\n /* @vite-ignore */\n __CLIENT__\n );\n } catch (error) {\n console.warn(\"[crx] MAIN world HMR client failed to load\", error);\n }\n const { onExecute } = await import(\n /* @vite-ignore */\n __SCRIPT__\n );\n onExecute?.({\n perf: { injectTime, loadTime: performance.now() - injectTime }\n });\n })().catch(console.error);\n\n})();\n";
438
455
 
439
456
  var contentProLoader = "(function () {\n 'use strict';\n\n const injectTime = performance.now();\n (async () => {\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";
440
457
 
@@ -474,11 +491,15 @@ function createProLoader({ fileName }) {
474
491
  return contentProLoader.replace(/__SCRIPT__/g, JSON.stringify(fileName));
475
492
  }
476
493
  function createDevMainLoader({
494
+ preamble,
495
+ client,
477
496
  fileName
478
497
  }) {
479
- return contentDevMainLoader.replace(/__SCRIPT__/g, JSON.stringify(fileName)).replace(/__TIMESTAMP__/g, JSON.stringify(Date.now()));
498
+ return contentDevMainLoader.replace(/__PREAMBLE__/g, JSON.stringify(preamble)).replace(/__CLIENT__/g, JSON.stringify(client)).replace(/__SCRIPT__/g, JSON.stringify(fileName)).replace(/__TIMESTAMP__/g, JSON.stringify(Date.now()));
480
499
  }
481
- function createProMainLoader({ fileName }) {
500
+ function createProMainLoader({
501
+ fileName
502
+ }) {
482
503
  return contentProMainLoader.replace(/__SCRIPT__/g, JSON.stringify(fileName));
483
504
  }
484
505
 
@@ -515,12 +536,39 @@ fileWriterEvent$.pipe(
515
536
  filter((e) => e.type === "build_start"),
516
537
  switchMap((e) => of(e))
517
538
  );
518
- const allFilesReady$ = buildEnd$.pipe(
519
- switchMap(() => outputFiles.change$.pipe(startWith({ type: "start" }))),
520
- map(() => [...outputFiles.values()]),
539
+ const allFilesReadyDebounceMs = 100;
540
+ let currentAllFilesReadyGeneration = 0;
541
+ let completedAllFilesReadyGeneration = 0;
542
+ let lastAllFilesReadyResults;
543
+ const allFilesReadyState$ = buildEnd$.pipe(
521
544
  switchMap(
522
- (files) => Promise.allSettled(files.map((file) => waitForOutputFile(file)))
523
- )
545
+ () => outputFiles.change$.pipe(
546
+ startWith({ type: "start" }),
547
+ tap(() => {
548
+ currentAllFilesReadyGeneration += 1;
549
+ }),
550
+ debounceTime(allFilesReadyDebounceMs)
551
+ )
552
+ ),
553
+ map(() => ({
554
+ generation: currentAllFilesReadyGeneration,
555
+ files: [...outputFiles.values()]
556
+ })),
557
+ switchMap(async ({ generation, files }) => {
558
+ const seen = /* @__PURE__ */ new Set();
559
+ const results = await Promise.allSettled(
560
+ files.map((file) => waitForOutputFile(file, seen))
561
+ );
562
+ return { generation, results };
563
+ }),
564
+ tap(({ generation, results }) => {
565
+ completedAllFilesReadyGeneration = generation;
566
+ lastAllFilesReadyResults = results;
567
+ }),
568
+ share()
569
+ );
570
+ const allFilesReady$ = allFilesReadyState$.pipe(
571
+ map(({ results }) => results)
524
572
  );
525
573
  async function waitForOutputFile(file, seen = /* @__PURE__ */ new Set()) {
526
574
  if (seen.has(file))
@@ -529,6 +577,18 @@ async function waitForOutputFile(file, seen = /* @__PURE__ */ new Set()) {
529
577
  const { deps } = await file.file;
530
578
  await Promise.all(deps.map((dep) => waitForOutputFile(dep, seen)));
531
579
  }
580
+ async function waitForAllFilesReadyResults() {
581
+ const targetGeneration = currentAllFilesReadyGeneration;
582
+ if (lastAllFilesReadyResults && completedAllFilesReadyGeneration >= targetGeneration) {
583
+ return lastAllFilesReadyResults;
584
+ }
585
+ const { results } = await firstValueFrom(
586
+ allFilesReadyState$.pipe(
587
+ filter(({ generation }) => generation >= targetGeneration)
588
+ )
589
+ );
590
+ return results;
591
+ }
532
592
  const timestamp$ = new BehaviorSubject(Date.now());
533
593
  allFilesReady$.subscribe(() => {
534
594
  timestamp$.next(Date.now());
@@ -618,14 +678,16 @@ ${sourceMap}
618
678
  mergeMap(async ({ target, code, deps }) => {
619
679
  await lexer.init;
620
680
  const [imports] = lexer.parse(code, fileName);
621
- const depSet = new Set(deps);
681
+ const isSelfDependency = (id) => getFileName({ type: "module", id }) === fileName;
682
+ const depSet = new Set(deps.filter((id) => !isSelfDependency(id)));
622
683
  const magic = new MagicString(code);
623
684
  for (const i of imports)
624
685
  if (i.n) {
625
- depSet.add(i.n);
626
- const fileName2 = getFileName({ type: "module", id: i.n });
686
+ const depFileName = getFileName({ type: "module", id: i.n });
687
+ if (!isSelfDependency(i.n))
688
+ depSet.add(i.n);
627
689
  const fullImport = code.substring(i.s, i.e);
628
- magic.overwrite(i.s, i.e, fullImport.replace(i.n, `/${fileName2}`));
690
+ magic.overwrite(i.s, i.e, fullImport.replace(i.n, `/${depFileName}`));
629
691
  }
630
692
  return { target, source: magic.toString(), deps: [...depSet] };
631
693
  })
@@ -731,7 +793,7 @@ function prepIifeScript(fileName, script) {
731
793
  );
732
794
  }
733
795
  async function allFilesReady() {
734
- await firstValueFrom(allFilesReady$);
796
+ await waitForAllFilesReadyResults();
735
797
  }
736
798
 
737
799
  const { outputFile } = fsx;
@@ -856,6 +918,37 @@ async function write(fileId) {
856
918
  return { start: start2, close: close2, deps };
857
919
  }
858
920
 
921
+ function asRelativeImport(fromFileName, toFileName) {
922
+ const path = relative(dirname(fromFileName), toFileName);
923
+ return path.startsWith(".") ? path : `./${path}`;
924
+ }
925
+ function getExternallyConnectableMatch(match) {
926
+ if (match === "<all_urls>")
927
+ return null;
928
+ const parsed = /^(\*|https?):\/\/([^/]+)\/.*$/.exec(match);
929
+ if (!parsed)
930
+ return null;
931
+ const [, , host] = parsed;
932
+ if (host === "*")
933
+ return null;
934
+ return match;
935
+ }
936
+ function getExternallyConnectableMatches(matches) {
937
+ const result = /* @__PURE__ */ new Set();
938
+ const unsupported = /* @__PURE__ */ new Set();
939
+ for (const match of matches) {
940
+ const externallyConnectableMatch = getExternallyConnectableMatch(match);
941
+ if (externallyConnectableMatch) {
942
+ result.add(externallyConnectableMatch);
943
+ } else {
944
+ unsupported.add(match);
945
+ }
946
+ }
947
+ return {
948
+ matches: [...result],
949
+ unsupported: [...unsupported]
950
+ };
951
+ }
859
952
  const pluginContentScripts = () => {
860
953
  const pluginName = "crx:content-scripts";
861
954
  let server;
@@ -864,6 +957,8 @@ const pluginContentScripts = () => {
864
957
  let liveReload = true;
865
958
  let sub = new Subscription();
866
959
  const worldMainIds = /* @__PURE__ */ new Set();
960
+ const worldMainExternallyConnectableMatches = /* @__PURE__ */ new Set();
961
+ const unsupportedWorldMainExternallyConnectableMatches = /* @__PURE__ */ new Set();
867
962
  const findWorldMainIds = async (config, env) => {
868
963
  const { manifest: _manifest } = await getOptions(config);
869
964
  const manifest = await (typeof _manifest === "function" ? _manifest(env) : _manifest);
@@ -872,16 +967,32 @@ const pluginContentScripts = () => {
872
967
  js.forEach((path) => worldMainIds.add(prefix$1("/", path)));
873
968
  }
874
969
  });
875
- if (worldMainIds.size) {
876
- const name = `[${pluginName}]`;
877
- const message = pc.yellow(
878
- [
879
- `${name} Some content-scripts don't support HMR because the world is MAIN:`,
880
- ...[...worldMainIds].map((id) => ` ${id}`)
881
- ].join("\r\n")
882
- );
883
- console.log(message);
884
- }
970
+ (manifest.content_scripts || []).forEach(({ world, matches = [] }) => {
971
+ if (world === "MAIN") {
972
+ const externallyConnectable = getExternallyConnectableMatches(matches);
973
+ externallyConnectable.matches.forEach(
974
+ (match) => worldMainExternallyConnectableMatches.add(match)
975
+ );
976
+ externallyConnectable.unsupported.forEach(
977
+ (match) => unsupportedWorldMainExternallyConnectableMatches.add(match)
978
+ );
979
+ }
980
+ });
981
+ };
982
+ const warnUnsupportedWorldMainExternallyConnectableMatches = () => {
983
+ if (unsupportedWorldMainExternallyConnectableMatches.size === 0)
984
+ return;
985
+ const name = `[${pluginName}]`;
986
+ const message = pc.yellow(
987
+ [
988
+ `${name} MAIN world HMR requires externally_connectable.matches. CRX cannot auto-add these Chrome-rejected content-script match patterns:`,
989
+ ...[...unsupportedWorldMainExternallyConnectableMatches].map(
990
+ (match) => ` ${match}`
991
+ ),
992
+ "Add explicit http(s) host addresses to your content script matches during development if you want MAIN world HMR for those pages."
993
+ ].join("\r\n")
994
+ );
995
+ console.warn(message);
885
996
  };
886
997
  return [
887
998
  {
@@ -889,6 +1000,7 @@ const pluginContentScripts = () => {
889
1000
  apply: "serve",
890
1001
  async config(config, env) {
891
1002
  await findWorldMainIds(config, env);
1003
+ warnUnsupportedWorldMainExternallyConnectableMatches();
892
1004
  const opts = await getOptions(config);
893
1005
  const { contentScripts: contentScripts2 = {} } = opts;
894
1006
  hmrTimeout = contentScripts2.hmrTimeout ?? 5e3;
@@ -916,11 +1028,20 @@ const pluginContentScripts = () => {
916
1028
  preamble = add({ type: "module", id: preambleId });
917
1029
  const client = add({ type: "module", id: viteClientId });
918
1030
  const file = add({ type: "module", id });
1031
+ const loaderFileName = getFileName({ type: "loader", id });
919
1032
  const loader = add({
920
1033
  type: "asset",
921
- id: getFileName({ type: "loader", id }),
1034
+ id: loaderFileName,
922
1035
  source: worldMainIds.has(file.id) ? createDevMainLoader({
923
- fileName: `./${file.fileName.split("/").at(-1)}`
1036
+ preamble: preamble.fileName ? asRelativeImport(loaderFileName, preamble.fileName) : "",
1037
+ client: asRelativeImport(
1038
+ loaderFileName,
1039
+ client.fileName
1040
+ ),
1041
+ fileName: asRelativeImport(
1042
+ loaderFileName,
1043
+ file.fileName
1044
+ )
924
1045
  }) : createDevLoader({
925
1046
  preamble: preamble.fileName,
926
1047
  client: client.fileName,
@@ -950,13 +1071,30 @@ const pluginContentScripts = () => {
950
1071
  return defined;
951
1072
  }
952
1073
  if (id === contentHmrPortId) {
953
- const defined = contentHmrPort.replace("__CRX_HMR_TIMEOUT__", JSON.stringify(hmrTimeout)).replace("__CRX_LIVE_RELOAD__", JSON.stringify(liveReload));
1074
+ const defined = contentHmrPort.replace("__CRX_HMR_TIMEOUT__", JSON.stringify(hmrTimeout)).replace("__CRX_LIVE_RELOAD__", JSON.stringify(liveReload)).replace(
1075
+ "__CRX_HMR_TOKEN__",
1076
+ JSON.stringify(
1077
+ getCrxHmrToken(server.config)
1078
+ )
1079
+ );
954
1080
  return defined;
955
1081
  }
956
1082
  },
957
1083
  closeBundle() {
958
1084
  sub.unsubscribe();
959
1085
  sub = new Subscription();
1086
+ },
1087
+ transformCrxManifest(manifest) {
1088
+ if (worldMainExternallyConnectableMatches.size === 0)
1089
+ return null;
1090
+ manifest.externally_connectable = manifest.externally_connectable ?? {};
1091
+ manifest.externally_connectable.matches = [
1092
+ .../* @__PURE__ */ new Set([
1093
+ ...manifest.externally_connectable.matches ?? [],
1094
+ ...worldMainExternallyConnectableMatches
1095
+ ])
1096
+ ];
1097
+ return manifest;
960
1098
  }
961
1099
  },
962
1100
  {
@@ -1735,7 +1873,7 @@ async function prepareVitePayloadForCrx(p) {
1735
1873
  return update(id).map((file) => file.file);
1736
1874
  });
1737
1875
  await Promise.all(pendingFiles);
1738
- await firstValueFrom(allFilesReady$);
1876
+ await allFilesReady();
1739
1877
  }
1740
1878
  return mapVitePayloadForCrx(p);
1741
1879
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crxjs/vite-plugin",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "description": "Build Chrome Extensions with this Vite plugin.",
5
5
  "keywords": [
6
6
  "rollup-plugin",
@@ -54,28 +54,26 @@
54
54
  ],
55
55
  "dependencies": {
56
56
  "@webcomponents/custom-elements": "^1.5.0",
57
- "acorn-walk": "^8.2.0",
57
+ "acorn-walk": "^8.3.5",
58
58
  "convert-source-map": "^1.7.0",
59
59
  "debug": "^4.3.3",
60
60
  "es-module-lexer": "^0.10.0",
61
- "tinyglobby": "^0.2.15",
61
+ "tinyglobby": "^0.2.17",
62
62
  "fs-extra": "^10.0.1",
63
63
  "jsesc": "^3.0.2",
64
64
  "magic-string": "^0.30.12",
65
- "node-html-parser": "^7.0.2",
65
+ "node-html-parser": "^7.1.0",
66
66
  "pathe": "^2.0.1",
67
67
  "picocolors": "^1.1.1",
68
- "react-refresh": "^0.13.0",
69
68
  "rollup": "2.80.0",
70
69
  "rxjs": "7.5.7"
71
70
  },
72
71
  "devDependencies": {
73
72
  "@arethetypeswrong/cli": "0.18.3",
74
- "@eslint/eslintrc": "3.3.3",
73
+ "@eslint/eslintrc": "3.3.5",
75
74
  "@eslint/js": "9.26.0",
76
75
  "@extend-chrome/messages": "1.2.2",
77
76
  "@extend-chrome/storage": "1.5.0",
78
- "@rollup/pluginutils": "^4.1.2",
79
77
  "@rollup/plugin-alias": "4.0.4",
80
78
  "@rollup/plugin-commonjs": "21.1.0",
81
79
  "@rollup/plugin-json": "^5.0.0",
@@ -84,10 +82,10 @@
84
82
  "@types/acorn": "4.0.6",
85
83
  "@types/chrome": "0.0.237",
86
84
  "@types/convert-source-map": "^2.0.0",
87
- "@types/debug": "4.1.7",
85
+ "@types/debug": "4.1.13",
88
86
  "@types/fs-extra": "9.0.13",
89
87
  "@types/jest-image-snapshot": "^5.1.0",
90
- "@types/jsesc": "3.0.1",
88
+ "@types/jsesc": "3.0.3",
91
89
  "@types/node": "17.0.18",
92
90
  "@types/react": "17.0.52",
93
91
  "@types/react-dom": "17.0.18",