@crxjs/vite-plugin 2.2.1 → 2.4.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/dist/index.d.ts CHANGED
@@ -84,6 +84,7 @@ interface ManifestV3 {
84
84
  match_about_blank?: boolean | undefined;
85
85
  include_globs?: string[] | undefined;
86
86
  exclude_globs?: string[] | undefined;
87
+ world?: chrome.scripting.ExecutionWorld | string | undefined;
87
88
  }[] | undefined;
88
89
  content_security_policy?: {
89
90
  extension_pages?: string;
@@ -202,7 +203,29 @@ interface ManifestV3 {
202
203
  update_url?: string | undefined;
203
204
  version_name?: string | undefined;
204
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;
205
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";
206
229
 
207
230
  type ManifestV3Fn = (env: ConfigEnv) => ManifestV3 | Promise<ManifestV3>;
208
231
  type ManifestV3Export = ManifestV3 | Promise<ManifestV3> | ManifestV3Fn;
@@ -263,6 +286,13 @@ type FilePathFields<T extends string> = {
263
286
  match_about_blank?: boolean;
264
287
  include_globs?: string[];
265
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';
266
296
  }[];
267
297
  input_components?: {
268
298
  name: string;
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { simple } from 'acorn-walk';
2
2
  import { createHash } from 'crypto';
3
- import debug$3 from 'debug';
4
- import { join, normalize, isAbsolute, basename, relative, resolve, dirname, parse } from 'pathe';
3
+ import debug$5 from 'debug';
4
+ import { join, normalize, dirname, basename, isAbsolute, relative, resolve, parse as parse$1 } from 'pathe';
5
5
  import { Subject, filter, ReplaySubject, switchMap, of, startWith, map, 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';
@@ -10,12 +10,12 @@ 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
12
  import convertSourceMap from 'convert-source-map';
13
+ import pc from 'picocolors';
13
14
  import { createLogger, version } from 'vite';
14
15
  import { readFileSync, existsSync, promises } from 'fs';
15
16
  import { createRequire } from 'module';
16
17
  import fg from 'fast-glob';
17
- import pc from 'picocolors';
18
- import { load } from 'cheerio';
18
+ import { parse } from 'node-html-parser';
19
19
  import jsesc from 'jsesc';
20
20
 
21
21
  const pluginName$1 = "crx:optionsProvider";
@@ -58,9 +58,9 @@ 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 = \"http:\";\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(\"Content-Type\", responseHeaders.get(\"Content-Type\") ?? \"text/javascript\");\n responseHeaders.set(\"Cache-Control\", responseHeaders.get(\"Cache-Control\") ?? \"\");\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(`${socketProtocol}://${socketHost}?token=${socketToken}`, \"vite-hmr\");\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 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";
62
62
 
63
- const _debug = (id) => debug$3("crx").extend(id);
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);
65
65
  const isString = (x) => typeof x === "string";
66
66
  function isObject(value) {
@@ -140,7 +140,13 @@ function defineClientValues(code, config) {
140
140
  if (hmrBase !== "/") {
141
141
  hmrPort = normalize(`${hmrPort}${hmrBase}`);
142
142
  }
143
- return code.replace(`__MODE__`, JSON.stringify(config.mode)).replace(`__BASE__`, JSON.stringify(config.base)).replace(`__DEFINES__`, serializeDefine(config.define || {})).replace(`__HMR_TOKEN__`, JSON.stringify(config.webSocketToken || "")).replace(`__HMR_PROTOCOL__`, JSON.stringify(protocol)).replace(`__HMR_HOSTNAME__`, JSON.stringify(host)).replace(`__HMR_PORT__`, JSON.stringify(hmrPort)).replace(`__HMR_TIMEOUT__`, JSON.stringify(timeout)).replace(`__HMR_ENABLE_OVERLAY__`, JSON.stringify(overlay)).replace(`__SERVER_PORT__`, JSON.stringify(config.server.port?.toString()));
143
+ return code.replace(`__MODE__`, JSON.stringify(config.mode)).replace(`__BASE__`, JSON.stringify(config.base)).replace(`__DEFINES__`, serializeDefine(config.define || {})).replace(`__HMR_TOKEN__`, JSON.stringify(config.webSocketToken || "")).replace(`__HMR_PROTOCOL__`, JSON.stringify(protocol)).replace(`__HMR_HOSTNAME__`, JSON.stringify(host)).replace(`__HMR_PORT__`, JSON.stringify(hmrPort)).replace(`__HMR_TIMEOUT__`, JSON.stringify(timeout)).replace(`__HMR_ENABLE_OVERLAY__`, JSON.stringify(overlay)).replace(
144
+ `__SERVER_PROTO__`,
145
+ JSON.stringify(config.server.https ? "https" : "http")
146
+ ).replace(
147
+ `__SERVER_PORT__`,
148
+ JSON.stringify(config.server.port?.toString())
149
+ );
144
150
  function serializeDefine(define) {
145
151
  let res = `{`;
146
152
  for (const key in define) {
@@ -177,6 +183,17 @@ class RxMap extends Map {
177
183
  const outputFiles = new RxMap();
178
184
 
179
185
  _debug("file-writer").extend("utilities");
186
+ function sanitizeUnderscorePrefix(fileName) {
187
+ const dir = dirname(fileName);
188
+ let base = basename(fileName);
189
+ while (base.startsWith("_")) {
190
+ base = base.slice(1);
191
+ }
192
+ if (!base) {
193
+ base = "file";
194
+ }
195
+ return dir === "." ? base : join(dir, base);
196
+ }
180
197
  function prefix$1(prefix2, text) {
181
198
  return text.startsWith(prefix2) ? text : prefix2 + text;
182
199
  }
@@ -200,6 +217,7 @@ function getFileName({ type, id }) {
200
217
  } else if (fileName.startsWith(".vite/deps/")) {
201
218
  fileName = `vendor/${fileName.slice(".vite/deps/".length)}`;
202
219
  }
220
+ fileName = sanitizeUnderscorePrefix(fileName);
203
221
  switch (type) {
204
222
  case "iife":
205
223
  return `${fileName}.iife.js`;
@@ -257,6 +275,20 @@ const manifestId = "/@crx/manifest";
257
275
  const preambleId = "/@crx/client-preamble";
258
276
  const stubId = "/@crx/stub";
259
277
  const workerClientId = "/@crx/client-worker";
278
+ const contentCssPrefix = "/@crx/content-css/";
279
+ function isContentCssId(id) {
280
+ return id.startsWith(contentCssPrefix);
281
+ }
282
+ function getContentCssId(index) {
283
+ return `${contentCssPrefix}${index}`;
284
+ }
285
+ function getContentCssIndex(id) {
286
+ if (!isContentCssId(id))
287
+ return null;
288
+ const indexStr = id.slice(contentCssPrefix.length);
289
+ const index = parseInt(indexStr, 10);
290
+ return isNaN(index) ? null : index;
291
+ }
260
292
 
261
293
  const pluginBackground = () => {
262
294
  let config;
@@ -271,7 +303,7 @@ const pluginBackground = () => {
271
303
  },
272
304
  load(id) {
273
305
  if (id === workerClientId) {
274
- const base = `http://localhost:${config.server.port}/`;
306
+ const base = `${config.server.https ? "https" : "http"}://localhost:${config.server.port}/`;
275
307
  return defineClientValues(
276
308
  workerHmrClient.replace("__BASE__", JSON.stringify(base)),
277
309
  config
@@ -294,24 +326,25 @@ const pluginBackground = () => {
294
326
  const worker = browser === "firefox" ? manifest.background?.scripts[0] : manifest.background?.service_worker;
295
327
  let loader;
296
328
  if (config.command === "serve") {
329
+ const proto = config.server.https ? "https" : "http";
297
330
  const port = config.server.port?.toString();
298
331
  if (typeof port === "undefined")
299
332
  throw new Error("server port is undefined in watch mode");
300
333
  if (browser === "firefox") {
301
- loader = `import('http://localhost:${port}/@vite/env');
334
+ loader = `import('${proto}://localhost:${port}/@vite/env');
302
335
  `;
303
- loader += `import('http://localhost:${port}${workerClientId}');
336
+ loader += `import('${proto}://localhost:${port}${workerClientId}');
304
337
  `;
305
338
  if (worker)
306
- loader += `import('http://localhost:${port}/${worker}');
339
+ loader += `import('${proto}://localhost:${port}/${worker}');
307
340
  `;
308
341
  } else {
309
- loader = `import 'http://localhost:${port}/@vite/env';
342
+ loader = `import '${proto}://localhost:${port}/@vite/env';
310
343
  `;
311
- loader += `import 'http://localhost:${port}${workerClientId}';
344
+ loader += `import '${proto}://localhost:${port}${workerClientId}';
312
345
  `;
313
346
  if (worker)
314
- loader += `import 'http://localhost:${port}/${worker}';
347
+ loader += `import '${proto}://localhost:${port}/${worker}';
315
348
  `;
316
349
  }
317
350
  } else if (worker) {
@@ -347,8 +380,12 @@ var contentHmrPort = "function isCrxHMRPayload(x) {\n return x.type === \"custo
347
380
 
348
381
  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";
349
382
 
383
+ 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";
384
+
350
385
  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";
351
386
 
387
+ var contentProMainLoader = "(function () {\n 'use strict';\n\n const injectTime = performance.now();\n (async () => {\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";
388
+
352
389
  const contentScripts = new RxMap();
353
390
  contentScripts.change$.pipe(filter(RxMap.isChangeType.set)).subscribe(({ map, value }) => {
354
391
  const keyNames = [
@@ -381,6 +418,14 @@ function createDevLoader({
381
418
  function createProLoader({ fileName }) {
382
419
  return contentProLoader.replace(/__SCRIPT__/g, JSON.stringify(fileName));
383
420
  }
421
+ function createDevMainLoader({
422
+ fileName
423
+ }) {
424
+ return contentDevMainLoader.replace(/__SCRIPT__/g, JSON.stringify(fileName)).replace(/__TIMESTAMP__/g, JSON.stringify(Date.now()));
425
+ }
426
+ function createProMainLoader({ fileName }) {
427
+ return contentProMainLoader.replace(/__SCRIPT__/g, JSON.stringify(fileName));
428
+ }
384
429
 
385
430
  const serverEvent$ = new ReplaySubject(1);
386
431
  const close$ = serverEvent$.pipe(
@@ -503,7 +548,7 @@ async function allFilesReady() {
503
548
  }
504
549
 
505
550
  const { outputFile } = fsx;
506
- _debug("file-writer");
551
+ const debug$4 = _debug("file-writer");
507
552
  async function start({
508
553
  server
509
554
  }) {
@@ -534,6 +579,12 @@ async function close() {
534
579
  }
535
580
  function add(script) {
536
581
  const fileName = getFileName(script);
582
+ debug$4(
583
+ "add: script.id=%s script.type=%s fileName=%s",
584
+ script.id,
585
+ script.type,
586
+ fileName
587
+ );
537
588
  let file = outputFiles.get(fileName);
538
589
  if (typeof file === "undefined") {
539
590
  file = formatFileData({
@@ -542,6 +593,17 @@ function add(script) {
542
593
  file: write(script)
543
594
  });
544
595
  outputFiles.set(file.fileName, file);
596
+ debug$4("add: stored new file %s", file.fileName);
597
+ } else {
598
+ const isVirtualModule = script.id.startsWith("/@id/") || script.id.startsWith("/__");
599
+ if (isVirtualModule) {
600
+ debug$4(
601
+ "add: virtual module already exists, triggering re-write for %s",
602
+ fileName
603
+ );
604
+ file.file = write(script);
605
+ outputFiles.set(fileName, file);
606
+ }
545
607
  }
546
608
  return file;
547
609
  }
@@ -549,15 +611,19 @@ function update(_id) {
549
611
  const id = prefix$1("/", _id);
550
612
  const types = ["iife", "module"];
551
613
  const updatedFiles = [];
614
+ debug$4("update called: _id=%s id=%s", _id, id);
552
615
  for (const type of types) {
553
616
  const fileName = getFileName({ id, type });
617
+ debug$4("update: looking for fileName=%s", fileName);
554
618
  const scriptFile = outputFiles.get(fileName);
555
619
  if (scriptFile) {
620
+ debug$4("update: found file, calling write()");
556
621
  scriptFile.file = write({ id, type });
557
622
  updatedFiles.push(scriptFile);
558
623
  outputFiles.set(fileName, scriptFile);
559
624
  }
560
625
  }
626
+ debug$4("update: returning %d files", updatedFiles.length);
561
627
  return updatedFiles;
562
628
  }
563
629
  async function write(fileId) {
@@ -593,15 +659,37 @@ async function write(fileId) {
593
659
  }
594
660
 
595
661
  const pluginContentScripts = () => {
662
+ const pluginName = "crx:content-scripts";
596
663
  let server;
597
664
  let preambleCode;
598
665
  let hmrTimeout;
599
666
  let sub = new Subscription();
667
+ const worldMainIds = /* @__PURE__ */ new Set();
668
+ const findWorldMainIds = async (config, env) => {
669
+ const { manifest: _manifest } = await getOptions(config);
670
+ const manifest = await (typeof _manifest === "function" ? _manifest(env) : _manifest);
671
+ (manifest.content_scripts || []).forEach(({ world, js }) => {
672
+ if (world === "MAIN" && js) {
673
+ js.forEach((path) => worldMainIds.add(prefix$1("/", path)));
674
+ }
675
+ });
676
+ if (worldMainIds.size) {
677
+ const name = `[${pluginName}]`;
678
+ const message = pc.yellow(
679
+ [
680
+ `${name} Some content-scripts don't support HMR because the world is MAIN:`,
681
+ ...[...worldMainIds].map((id) => ` ${id}`)
682
+ ].join("\r\n")
683
+ );
684
+ console.log(message);
685
+ }
686
+ };
600
687
  return [
601
688
  {
602
- name: "crx:content-scripts",
689
+ name: pluginName,
603
690
  apply: "serve",
604
- async config(config) {
691
+ async config(config, env) {
692
+ await findWorldMainIds(config, env);
605
693
  const { contentScripts: contentScripts2 = {} } = await getOptions(config);
606
694
  hmrTimeout = contentScripts2.hmrTimeout ?? 5e3;
607
695
  preambleCode = preambleCode ?? contentScripts2.preambleCode;
@@ -630,7 +718,9 @@ const pluginContentScripts = () => {
630
718
  const loader = add({
631
719
  type: "asset",
632
720
  id: getFileName({ type: "loader", id }),
633
- source: createDevLoader({
721
+ source: worldMainIds.has(file.id) ? createDevMainLoader({
722
+ fileName: `./${file.fileName.split("/").at(-1)}`
723
+ }) : createDevLoader({
634
724
  preamble: preamble.fileName,
635
725
  client: client.fileName,
636
726
  fileName: file.fileName
@@ -671,10 +761,11 @@ const pluginContentScripts = () => {
671
761
  }
672
762
  },
673
763
  {
674
- name: "crx:content-scripts",
764
+ name: pluginName,
675
765
  apply: "build",
676
766
  enforce: "pre",
677
- config(config) {
767
+ async config(config, env) {
768
+ await findWorldMainIds(config, env);
678
769
  return {
679
770
  ...config,
680
771
  build: {
@@ -705,7 +796,9 @@ const pluginContentScripts = () => {
705
796
  type: "loader",
706
797
  id: basename(script.id)
707
798
  }),
708
- source: createProLoader({ fileName })
799
+ source: worldMainIds.has(script.id) ? createProMainLoader({
800
+ fileName: `./${fileName.split("/").at(-1)}`
801
+ }) : createProLoader({ fileName })
709
802
  });
710
803
  script.loaderName = this.getFileName(refId);
711
804
  } else {
@@ -753,8 +846,48 @@ const pluginContentScriptsCss = () => {
753
846
  };
754
847
  };
755
848
 
849
+ const contentCssEntries = /* @__PURE__ */ new Map();
850
+ function getContentCssEntries() {
851
+ return Array.from(contentCssEntries.values());
852
+ }
853
+ function clearContentCssEntries() {
854
+ contentCssEntries.clear();
855
+ }
856
+ function registerContentCssEntry(index, cssFiles) {
857
+ const virtualId = getContentCssId(index);
858
+ const entry = { index, cssFiles, virtualId };
859
+ contentCssEntries.set(index, entry);
860
+ return entry;
861
+ }
756
862
  const pluginDeclaredContentScripts = () => {
757
- return [];
863
+ return {
864
+ name: "crx:content-scripts-declared-css",
865
+ apply: "serve",
866
+ resolveId(source) {
867
+ if (isContentCssId(source)) {
868
+ return source;
869
+ }
870
+ },
871
+ load(id) {
872
+ if (!isContentCssId(id))
873
+ return;
874
+ const index = getContentCssIndex(id);
875
+ if (index === null)
876
+ return;
877
+ const entry = contentCssEntries.get(index);
878
+ if (!entry) {
879
+ console.warn(
880
+ `[crx:content-scripts-declared-css] No CSS entry found for index ${index}`
881
+ );
882
+ return "";
883
+ }
884
+ const cssImports = entry.cssFiles.map((cssPath) => {
885
+ const importPath = cssPath.startsWith("/") ? cssPath : `/${cssPath}`;
886
+ return `import "${importPath}";`;
887
+ }).join("\n");
888
+ return cssImports + "\n";
889
+ }
890
+ };
758
891
  };
759
892
 
760
893
  const _dynamicScriptRegEx = /\b(import.meta).CRX_DYNAMIC_SCRIPT_(.+?)[,;]/gm;
@@ -1067,7 +1200,7 @@ const pluginFileWriterPublic = () => {
1067
1200
  };
1068
1201
  };
1069
1202
 
1070
- const debug$2 = _debug("file-writer").extend("hmr");
1203
+ const debug$3 = _debug("file-writer").extend("hmr");
1071
1204
  const isCustomPayload = (p) => {
1072
1205
  return p.type === "custom";
1073
1206
  };
@@ -1105,7 +1238,21 @@ const crxHMRPayload$ = hmrPayload$.pipe(
1105
1238
  return prune;
1106
1239
  }
1107
1240
  case "update": {
1108
- const update = {
1241
+ debug$3("update payload with %d updates", p.updates.length);
1242
+ for (const u of p.updates) {
1243
+ debug$3(
1244
+ "update item: path=%s acceptedPath=%s type=%s",
1245
+ u.path,
1246
+ u.acceptedPath,
1247
+ u.type
1248
+ );
1249
+ const isVirtualModule = u.path.startsWith("/@id/") || u.path.startsWith("/__");
1250
+ if (isVirtualModule) {
1251
+ debug$3("updating virtual module: %s", u.path);
1252
+ update(u.path);
1253
+ }
1254
+ }
1255
+ const update_ = {
1109
1256
  type: "update",
1110
1257
  updates: p.updates.map(({ acceptedPath: ap, path: p2, ...rest }) => ({
1111
1258
  ...rest,
@@ -1113,7 +1260,7 @@ const crxHMRPayload$ = hmrPayload$.pipe(
1113
1260
  path: prefix$1("/", getFileName({ id: p2, type: "module" }))
1114
1261
  }))
1115
1262
  };
1116
- return update;
1263
+ return update_;
1117
1264
  }
1118
1265
  default:
1119
1266
  return p;
@@ -1132,7 +1279,7 @@ const crxHMRPayload$ = hmrPayload$.pipe(
1132
1279
  }
1133
1280
  }),
1134
1281
  map((data) => {
1135
- debug$2(`hmr payload`, data);
1282
+ debug$3(`hmr payload`, data);
1136
1283
  return {
1137
1284
  type: "custom",
1138
1285
  event: "crx:content-script-payload",
@@ -1157,7 +1304,7 @@ function isImporter(file) {
1157
1304
  return pred;
1158
1305
  }
1159
1306
 
1160
- const debug$1 = _debug("hmr");
1307
+ const debug$2 = _debug("hmr");
1161
1308
  const crxRuntimeReload = {
1162
1309
  type: "custom",
1163
1310
  event: "crx:runtime-reload"
@@ -1225,25 +1372,51 @@ const pluginHMR = () => {
1225
1372
  const { root } = server.config;
1226
1373
  const relFiles = /* @__PURE__ */ new Set();
1227
1374
  const fsFiles = /* @__PURE__ */ new Set();
1375
+ const virtualModules = /* @__PURE__ */ new Set();
1228
1376
  for (const m of modules) {
1229
1377
  if (m.id?.startsWith(root)) {
1230
1378
  relFiles.add(m.id.slice(server.config.root.length));
1231
1379
  } else if (m.url?.startsWith("/@fs")) {
1232
1380
  fsFiles.add(m.url);
1381
+ } else if (m.id?.startsWith("\0") || m.url?.startsWith("/@id/__x00__")) {
1382
+ const virtualId = m.url ?? m.id;
1383
+ if (virtualId) {
1384
+ virtualModules.add(virtualId);
1385
+ debug$2("virtual module detected:", virtualId);
1386
+ }
1233
1387
  }
1234
1388
  }
1235
1389
  fsFiles.forEach((file) => update(file));
1390
+ virtualModules.forEach((file) => update(file));
1236
1391
  if (inputManifestFiles.background.length) {
1237
1392
  const background = prefix$1("/", inputManifestFiles.background[0]);
1238
1393
  if (relFiles.has(background) || modules.some(isImporter(join(server.config.root, background)))) {
1239
- debug$1("sending runtime reload");
1394
+ debug$2("sending runtime reload");
1240
1395
  server.ws.send(crxRuntimeReload);
1241
1396
  }
1242
1397
  }
1243
1398
  for (const [key, script] of contentScripts)
1244
1399
  if (key === script.id) {
1245
- if (relFiles.has(script.id) || modules.some(isImporter(join(server.config.root, script.id)))) {
1246
- relFiles.forEach((relFile) => update(relFile));
1400
+ if (isContentCssId(script.id)) {
1401
+ const cssEntries = getContentCssEntries();
1402
+ const entry = cssEntries.find((e) => e.virtualId === script.id);
1403
+ if (entry) {
1404
+ const changedCssFiles = [...relFiles].filter(
1405
+ (relFile) => entry.cssFiles.some(
1406
+ (cssFile) => relFile === prefix$1("/", cssFile) || relFile.endsWith(cssFile)
1407
+ )
1408
+ );
1409
+ if (changedCssFiles.length > 0) {
1410
+ changedCssFiles.forEach((relFile) => update(relFile));
1411
+ update(script.id);
1412
+ virtualModules.forEach((file) => update(file));
1413
+ }
1414
+ }
1415
+ } else {
1416
+ if (relFiles.has(script.id) || modules.some(isImporter(join(server.config.root, script.id)))) {
1417
+ relFiles.forEach((relFile) => update(relFile));
1418
+ virtualModules.forEach((file) => update(file));
1419
+ }
1247
1420
  }
1248
1421
  }
1249
1422
  }
@@ -1302,12 +1475,20 @@ const pluginPrint = () => {
1302
1475
 
1303
1476
  var loader = "try {\n for (const p of JSON.parse(SCRIPTS)) {\n const url = new URL(p, \"https://stub\");\n url.searchParams.set(\"t\", Date.now().toString());\n const req = url.pathname + url.search;\n await import(\n /* @vite-ignore */\n req\n );\n }\n} catch (error) {\n console.error(error);\n}\n";
1304
1477
 
1478
+ function extractScriptsAndRemove(html) {
1479
+ const root = parse(html);
1480
+ const scripts = root.querySelectorAll("script");
1481
+ const scriptSrcs = scripts.map((el) => el.getAttribute("src"));
1482
+ scripts.forEach((el) => el.remove());
1483
+ return { scriptSrcs, html: root.toString() };
1484
+ }
1485
+
1305
1486
  const pluginName = "crx:html-inline-scripts";
1306
- const debug = _debug(pluginName);
1487
+ const debug$1 = _debug(pluginName);
1307
1488
  const prefix = "@crx/inline-script";
1308
1489
  const isInlineTag = (t) => t.tag === "script" && !t.attrs?.src;
1309
1490
  const toKey = (ctx) => {
1310
- const { dir, name } = parse(ctx.path);
1491
+ const { dir, name } = parse$1(ctx.path);
1311
1492
  return join(prefix, dir, name);
1312
1493
  };
1313
1494
  const pluginHtmlInlineScripts = () => {
@@ -1373,19 +1554,18 @@ const pluginHtmlInlineScripts = () => {
1373
1554
  const key = toKey(ctx);
1374
1555
  const p = pages.get(key);
1375
1556
  if (p?.scripts.some(isInlineTag)) {
1376
- const $ = load(html);
1557
+ const { scriptSrcs, html: cleanedHtml } = extractScriptsAndRemove(html);
1377
1558
  p.scripts.push(
1378
- ...$("script").toArray().map((el) => ({
1559
+ ...scriptSrcs.map((src) => ({
1379
1560
  tag: "script",
1380
- attrs: { src: $(el).attr("src"), type: "module" }
1561
+ attrs: { src, type: "module" }
1381
1562
  }))
1382
1563
  );
1383
- $("script").remove();
1384
1564
  const loader2 = {
1385
1565
  tag: "script",
1386
1566
  attrs: { src: `${key}?t=${Date.now()}`, type: "module" }
1387
1567
  };
1388
- return { html: $.html(), tags: [loader2] };
1568
+ return { html: cleanedHtml, tags: [loader2] };
1389
1569
  }
1390
1570
  return p?.scripts ?? void 0;
1391
1571
  }
@@ -1427,21 +1607,21 @@ const pluginHtmlInlineScripts = () => {
1427
1607
  })}"`;
1428
1608
  return [inline, loader.replace("SCRIPTS", json)].join("\n");
1429
1609
  } else {
1430
- debug("page missing %s", id);
1610
+ debug$1("page missing %s", id);
1431
1611
  }
1432
1612
  }
1433
1613
  }
1434
1614
  };
1435
1615
  };
1436
1616
 
1437
- var loadingPageScript = "const VITE_URL = \"http://localhost:%PORT%\";\ndocument.body.innerHTML = `\n<div\n id=\"app\"\n style=\"\n border: 1px solid #ddd;\n padding: 20px;\n border-radius: 5px;\n box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);\n \"\n >\n <h1 style=\"color: #333\">Vite Dev Mode</h1>\n <p style=\"color: #666\">\n Cannot connect to the Vite Dev Server on <a href=\"${VITE_URL}\">${VITE_URL}</a>\n </p>\n <p style=\"color: #666\">\n Double-check that Vite is working and reload the extension.\n </p>\n <p style=\"color: #666\">\n This page will close when the extension reloads.\n </p>\n <button\n style=\"\n padding: 10px 20px;\n border: none;\n background-color: #007bff;\n color: #fff;\n border-radius: 5px;\n cursor: pointer;\n \"\n >\n Reload Extension\n </button>\n </div>`;\ndocument.body.querySelector(\"button\")?.addEventListener(\"click\", () => {\n chrome.runtime.reload();\n});\nlet tries = 0;\nlet ready = false;\ndo {\n try {\n await fetch(VITE_URL);\n ready = true;\n } catch {\n const timeout = Math.min(100 * Math.pow(2, ++tries), 5e3);\n console.log(`[CRXJS] Vite Dev Server is not available on ${VITE_URL}`);\n console.log(`[CRXJS] Retrying in ${timeout}ms...`);\n await new Promise((resolve) => setTimeout(resolve, timeout));\n }\n} while (!ready);\nlocation.reload();\n";
1617
+ var loadingPageScript = "const VITE_URL = \"%PROTO%://localhost:%PORT%\";\ndocument.body.innerHTML = /* html */\n`\n<style>\n :root {\n color-scheme: light;\n --ink: #111827;\n --muted: #5f6b7a;\n --muted-subtle: rgba(95, 107, 122, 0.7);\n --muted-hint: rgba(95, 107, 122, 0.6);\n --card: #ffffff;\n --badge-bg: rgba(17, 24, 39, 0.04);\n --accent: #ff6b2c;\n --accent-2: #2563eb;\n --link-underline: rgba(37, 99, 235, 0.45);\n --glow-1: rgba(37, 99, 235, 0.12);\n --glow-2: rgba(255, 107, 44, 0.1);\n --button-grad-1: #ff7a43;\n --button-grad-2: #ff9a73;\n --button-shadow: rgba(255, 107, 44, 0.18);\n --button-shadow-hover: rgba(255, 107, 44, 0.22);\n --pulse: rgba(255, 107, 44, 0.6);\n --pulse-ring: rgba(255, 107, 44, 0.5);\n }\n\n @media (prefers-color-scheme: dark) {\n :root {\n color-scheme: dark;\n --ink: #e5e7eb;\n --muted: #a3aab5;\n --muted-subtle: rgba(163, 170, 181, 0.78);\n --muted-hint: rgba(163, 170, 181, 0.6);\n --card: #0f172a;\n --badge-bg: rgba(148, 163, 184, 0.16);\n --accent: #ff8a5a;\n --accent-2: #7aa2ff;\n --link-underline: rgba(122, 162, 255, 0.45);\n --glow-1: rgba(96, 165, 250, 0.14);\n --glow-2: rgba(251, 146, 60, 0.16);\n --button-grad-1: #f07b4d;\n --button-grad-2: #f39a76;\n --button-shadow: rgba(240, 123, 77, 0.16);\n --button-shadow-hover: rgba(240, 123, 77, 0.2);\n --pulse: rgba(255, 138, 90, 0.65);\n --pulse-ring: rgba(255, 138, 90, 0.5);\n }\n }\n\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n font-family: \"IBM Plex Sans\", \"Inter\", -apple-system, system-ui, sans-serif;\n color: var(--ink);\n background: transparent;\n width: 420px;\n height: 250px;\n margin: 0;\n padding: 0 !important;\n }\n\n #app {\n background: var(--card);\n position: relative;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n min-height: 100%;\n padding: 24px 24px 20px;\n gap: 14px;\n justify-content: center;\n }\n\n #app::before {\n content: \"\";\n position: absolute;\n inset: 0;\n background:\n radial-gradient(240px 140px at 100% 0%, var(--glow-1), transparent 70%),\n radial-gradient(220px 140px at 0% 100%, var(--glow-2), transparent 70%);\n pointer-events: none;\n }\n\n .header {\n position: relative;\n display: flex;\n flex-direction: column;\n gap: 6px;\n align-items: flex-start;\n padding-right: 96px;\n }\n\n .header-text {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n\n .badge {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-size: 10px;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--muted);\n background: var(--badge-bg);\n border-radius: 999px;\n padding: 6px 10px;\n white-space: nowrap;\n position: absolute;\n top: 0;\n right: 0;\n }\n\n .pulse {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--accent);\n box-shadow: 0 0 0 0 var(--pulse);\n animation: pulse 1.6s ease-in-out infinite;\n }\n\n h1 {\n font-size: clamp(18px, 4vw, 22px);\n letter-spacing: -0.02em;\n line-height: 1.25;\n }\n\n p {\n margin: 0;\n color: var(--muted);\n font-size: 13px;\n line-height: 1.6;\n }\n\n .subtle {\n color: var(--muted-subtle);\n font-size: 12px;\n }\n\n a {\n color: var(--accent-2);\n text-decoration: none;\n border-bottom: 1px dashed var(--link-underline);\n }\n\n .content {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n\n .footer {\n display: flex;\n flex-direction: column;\n gap: 10px;\n align-items: center;\n }\n\n .actions {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n justify-content: center;\n }\n\n button {\n appearance: none;\n border: none;\n border-radius: 999px;\n background: linear-gradient(135deg, var(--button-grad-1), var(--button-grad-2));\n color: white;\n padding: 8px 14px;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n box-shadow: 0 8px 16px var(--button-shadow);\n transition: transform 160ms ease, box-shadow 160ms ease;\n }\n\n button:hover {\n transform: translateY(-1px);\n box-shadow: 0 10px 20px var(--button-shadow-hover);\n }\n\n button:focus-visible {\n outline: 2px solid var(--accent-2);\n outline-offset: 2px;\n }\n\n .hint {\n font-size: 11px;\n color: var(--muted-hint);\n text-align: center;\n }\n\n @keyframes pulse {\n 0% { box-shadow: 0 0 0 0 var(--pulse-ring); }\n 70% { box-shadow: 0 0 0 10px rgba(255, 107, 44, 0); }\n 100% { box-shadow: 0 0 0 0 rgba(255, 107, 44, 0); }\n }\n</style>\n\n<div id=\"app\">\n <div class=\"header\">\n <span class=\"badge\"><span class=\"pulse\"></span>dev server</span>\n <div class=\"header-text\">\n <h1>CRXJS DEV MODE</h1>\n <p class=\"subtle\">Connecting to the Vite dev server\\u2026</p>\n </div>\n </div>\n\n <div class=\"content\">\n <p>\n Cannot connect to <a href=\"${VITE_URL}\">${VITE_URL}</a>.\n Make sure Vite is running, then reload the extension.\n </p>\n <p>This page will close automatically after the extension reloads.</p>\n </div>\n\n <div class=\"footer\">\n <div class=\"actions\">\n <button>Reload Extension</button>\n </div>\n <div class=\"hint\">Tip: if the URL is wrong, restart Vite so it picks the right port.</div>\n </div>\n</div>\n`;\ndocument.body.querySelector(\"button\")?.addEventListener(\"click\", () => {\n chrome.runtime.reload();\n});\nlet tries = 0;\nlet ready = false;\ndo {\n try {\n await fetch(VITE_URL);\n ready = true;\n } catch {\n const timeout = Math.min(100 * Math.pow(2, ++tries), 5e3);\n console.log(`[CRXJS] Vite Dev Server is not available on ${VITE_URL}`);\n console.log(`[CRXJS] Retrying in ${timeout}ms...`);\n await new Promise((resolve) => setTimeout(resolve, timeout));\n }\n} while (!ready);\nlocation.reload();\n";
1438
1618
 
1439
- var loadingPageHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>Vite Dev Mode</title>\n <script src=\"%SCRIPT%\" type=\"module\"></script>\n </head>\n <body\n style=\"font-family: Arial, sans-serif; padding: 20px; text-align: center\"\n >\n <h1>Vite Dev Mode</h1>\n </body>\n</html>\n";
1619
+ var loadingPageHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>CRXJS DEV MODE</title>\n <script src=\"%SCRIPT%\" type=\"module\"></script>\n </head>\n <body>\n <p>An unknown error occurred. Failed to load the script.</p>\n </body>\n</html>\n";
1440
1620
 
1441
1621
  const { readFile } = promises;
1442
1622
  const pluginManifest = () => {
1443
1623
  let manifest;
1444
- let plugins;
1624
+ let plugins = [];
1445
1625
  let refId;
1446
1626
  let config;
1447
1627
  return [
@@ -1486,6 +1666,14 @@ const pluginManifest = () => {
1486
1666
  };
1487
1667
  }
1488
1668
  },
1669
+ // Use configResolved to get plugins for rolldown-vite (Vite 7) compatibility
1670
+ // In rolldown-vite, buildStart doesn't receive options.plugins, so we grab
1671
+ // them from the resolved config instead
1672
+ configResolved(resolvedConfig) {
1673
+ if (resolvedConfig.plugins) {
1674
+ plugins = resolvedConfig.plugins;
1675
+ }
1676
+ },
1489
1677
  buildStart(options) {
1490
1678
  if (options.plugins)
1491
1679
  plugins = options.plugins;
@@ -1581,8 +1769,33 @@ const pluginManifest = () => {
1581
1769
  }
1582
1770
  }
1583
1771
  if (config.command === "serve") {
1772
+ clearContentCssEntries();
1584
1773
  if (manifest2.content_scripts)
1585
- for (const { js = [], matches = [] } of manifest2.content_scripts)
1774
+ for (let i = 0; i < manifest2.content_scripts.length; i++) {
1775
+ const {
1776
+ js = [],
1777
+ css = [],
1778
+ matches = []
1779
+ } = manifest2.content_scripts[i];
1780
+ if (css.length > 0) {
1781
+ const cssEntry = registerContentCssEntry(i, css);
1782
+ contentScripts.set(
1783
+ cssEntry.virtualId,
1784
+ formatFileData({
1785
+ type: "loader",
1786
+ id: cssEntry.virtualId,
1787
+ matches,
1788
+ refId: hashScriptId({
1789
+ type: "loader",
1790
+ id: cssEntry.virtualId
1791
+ }),
1792
+ fileName: getFileName({
1793
+ type: "loader",
1794
+ id: cssEntry.virtualId
1795
+ })
1796
+ })
1797
+ );
1798
+ }
1586
1799
  for (const id2 of js) {
1587
1800
  contentScripts.set(
1588
1801
  prefix$1("/", id2),
@@ -1595,6 +1808,7 @@ const pluginManifest = () => {
1595
1808
  })
1596
1809
  );
1597
1810
  }
1811
+ }
1598
1812
  } else {
1599
1813
  if (manifest2.content_scripts)
1600
1814
  for (const { js = [], matches = [] } of manifest2.content_scripts)
@@ -1652,12 +1866,26 @@ const pluginManifest = () => {
1652
1866
  const manifestJs = bundle[manifestName];
1653
1867
  let manifest2 = decodeManifest.call(this, manifestJs.code);
1654
1868
  if (config.command === "serve") {
1655
- if (manifest2.content_scripts)
1656
- for (const script of manifest2.content_scripts) {
1657
- script.js = script.js?.map(
1869
+ if (manifest2.content_scripts) {
1870
+ const cssEntries = getContentCssEntries();
1871
+ const cssEntryMap = new Map(cssEntries.map((e) => [e.index, e]));
1872
+ for (let i = 0; i < manifest2.content_scripts.length; i++) {
1873
+ const script = manifest2.content_scripts[i];
1874
+ const cssEntry = cssEntryMap.get(i);
1875
+ const jsLoaders = (script.js || []).map(
1658
1876
  (id) => getFileName({ id, type: "loader" })
1659
1877
  );
1878
+ if (cssEntry) {
1879
+ const cssLoader = getFileName({
1880
+ id: cssEntry.virtualId,
1881
+ type: "loader"
1882
+ });
1883
+ script.js = [cssLoader, ...jsLoaders];
1884
+ } else {
1885
+ script.js = jsLoaders;
1886
+ }
1660
1887
  }
1888
+ }
1661
1889
  } else {
1662
1890
  if (manifest2.background && "service_worker" in manifest2.background) {
1663
1891
  const ref = manifest2.background.service_worker;
@@ -1706,6 +1934,7 @@ const pluginManifest = () => {
1706
1934
  }
1707
1935
  }
1708
1936
  const assetTypes = [
1937
+ "contentStyles",
1709
1938
  "icons",
1710
1939
  "locales",
1711
1940
  "rulesets",
@@ -1743,10 +1972,7 @@ Public dir: "${config.publicDir}"`
1743
1972
  const refId2 = this.emitFile({
1744
1973
  type: "asset",
1745
1974
  name: "loading-page.js",
1746
- source: loadingPageScript.replace(
1747
- "%PORT%",
1748
- `${config.server.port ?? 0}`
1749
- )
1975
+ source: loadingPageScript.replace("%PROTO%", config.server.https ? "https" : "http").replace("%PORT%", `${config.server.port ?? 0}`)
1750
1976
  });
1751
1977
  const loadingPageScriptName = this.getFileName(refId2);
1752
1978
  files.html.map(
@@ -1838,11 +2064,12 @@ const defineDynamicResource = ({
1838
2064
  });
1839
2065
  const DYNAMIC_RESOURCE = "<dynamic_resource>";
1840
2066
 
1841
- _debug("web-acc-res");
2067
+ const debug = _debug("web-acc-res");
1842
2068
  const pluginWebAccessibleResources = () => {
1843
2069
  let config;
1844
2070
  let injectCss;
1845
2071
  let browser;
2072
+ let userWantsViteManifest;
1846
2073
  return [
1847
2074
  {
1848
2075
  name: "crx:web-accessible-resources",
@@ -1882,6 +2109,7 @@ const pluginWebAccessibleResources = () => {
1882
2109
  const contentScripts2 = opts.contentScripts || {};
1883
2110
  browser = opts.browser || "chrome";
1884
2111
  injectCss = contentScripts2.injectCss ?? true;
2112
+ userWantsViteManifest = build?.manifest;
1885
2113
  return { ...config2, build: { ...build, manifest: command === "build" } };
1886
2114
  },
1887
2115
  configResolved(_config) {
@@ -2016,6 +2244,18 @@ const pluginWebAccessibleResources = () => {
2016
2244
  delete manifest.web_accessible_resources;
2017
2245
  else
2018
2246
  manifest.web_accessible_resources = combinedResources;
2247
+ if (!userWantsViteManifest) {
2248
+ const viteMajorVersion = parseInt(version.split(".")[0]);
2249
+ const manifestPath = viteMajorVersion > 4 ? ".vite/manifest.json" : "manifest.json";
2250
+ if (bundle[manifestPath]) {
2251
+ debug(
2252
+ "Removing Vite manifest: %s (userWantsViteManifest=%s)",
2253
+ manifestPath,
2254
+ userWantsViteManifest
2255
+ );
2256
+ delete bundle[manifestPath];
2257
+ }
2258
+ }
2019
2259
  return manifest;
2020
2260
  }
2021
2261
  }
package/index.d.cts ADDED
@@ -0,0 +1 @@
1
+ export * from './dist/index.d.ts'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crxjs/vite-plugin",
3
- "version": "2.2.1",
3
+ "version": "2.4.0",
4
4
  "description": "Build Chrome Extensions with this Vite plugin.",
5
5
  "keywords": [
6
6
  "rollup-plugin",
@@ -27,9 +27,14 @@
27
27
  "type": "module",
28
28
  "exports": {
29
29
  ".": {
30
- "require": "./index.cjs",
31
- "import": "./dist/index.mjs",
32
- "types": "./dist/index.d.ts"
30
+ "import": {
31
+ "types": "./dist/index.d.ts",
32
+ "default": "./dist/index.mjs"
33
+ },
34
+ "require": {
35
+ "types": "./index.d.cts",
36
+ "default": "./index.cjs"
37
+ }
33
38
  },
34
39
  "./*": "./*",
35
40
  "./client": {
@@ -44,13 +49,13 @@
44
49
  "types",
45
50
  "manifest.schema.json",
46
51
  "index.cjs",
52
+ "index.d.cts",
47
53
  "client.d.ts"
48
54
  ],
49
55
  "dependencies": {
50
56
  "@rollup/pluginutils": "^4.1.2",
51
57
  "@webcomponents/custom-elements": "^1.5.0",
52
58
  "acorn-walk": "^8.2.0",
53
- "cheerio": "^1.0.0-rc.10",
54
59
  "convert-source-map": "^1.7.0",
55
60
  "debug": "^4.3.3",
56
61
  "es-module-lexer": "^0.10.0",
@@ -58,6 +63,7 @@
58
63
  "fs-extra": "^10.0.1",
59
64
  "jsesc": "^3.0.2",
60
65
  "magic-string": "^0.30.12",
66
+ "node-html-parser": "^7.0.2",
61
67
  "pathe": "^2.0.1",
62
68
  "picocolors": "^1.1.1",
63
69
  "react-refresh": "^0.13.0",
@@ -102,12 +108,17 @@
102
108
  "svelte": "^3.48.0",
103
109
  "type-fest": "^5.1.0",
104
110
  "typescript": "^4.6.4",
111
+ "unocss": "^66.5.12",
105
112
  "vite": "^3.2.11",
106
113
  "vite-plugin-inspect": "0.7.25",
107
114
  "vitest": "0.28.5",
108
115
  "vue": "3.2.47"
109
116
  },
117
+ "peerDependencies": {
118
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
119
+ },
110
120
  "scripts": {
121
+ "release": "bumpp",
111
122
  "format": "prettier -w -c ../../.prettierrc.yaml",
112
123
  "build": "run-s build:clean build:js",
113
124
  "build:clean": "rimraf dist",