@crxjs/vite-plugin 2.3.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/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';
@@ -9,13 +9,13 @@ 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';
18
- import { load } from 'cheerio';
17
+ import { glob, isDynamicPattern } from 'tinyglobby';
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 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
- 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
  }
@@ -192,7 +209,7 @@ function formatFileData(script) {
192
209
  return script;
193
210
  }
194
211
  function getFileName({ type, id }) {
195
- 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, "-");
196
213
  if (fileName.includes("node_modules/")) {
197
214
  fileName = `vendor/${fileName.split("node_modules/").pop().replace(/\//g, "-")}`;
198
215
  } else if (fileName.startsWith("@")) {
@@ -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`;
@@ -230,7 +248,7 @@ function getViteUrl({ type, id }) {
230
248
  if (type === "asset") {
231
249
  throw new Error(`File type "${type}" not implemented.`);
232
250
  } else if (type === "iife") {
233
- throw new Error(`File type "${type}" not implemented.`);
251
+ throw new Error(`File type "iife" is handled via dedicated IIFE bundler, not Vite transform.`);
234
252
  } else if (type === "loader") {
235
253
  throw new Error("Vite does not transform loader files.");
236
254
  } else if (type === "module") {
@@ -257,10 +275,25 @@ 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;
263
295
  let browser;
296
+ let liveReload = true;
264
297
  return [
265
298
  {
266
299
  name: "crx:background-client",
@@ -271,9 +304,9 @@ const pluginBackground = () => {
271
304
  },
272
305
  load(id) {
273
306
  if (id === workerClientId) {
274
- const base = `http://localhost:${config.server.port}/`;
307
+ const base = `${config.server.https ? "https" : "http"}://localhost:${config.server.port}/`;
275
308
  return defineClientValues(
276
- workerHmrClient.replace("__BASE__", JSON.stringify(base)),
309
+ workerHmrClient.replace("__BASE__", JSON.stringify(base)).replace("__LIVE_RELOAD__", JSON.stringify(liveReload)),
277
310
  config
278
311
  );
279
312
  }
@@ -286,6 +319,7 @@ const pluginBackground = () => {
286
319
  async config(config2) {
287
320
  const opts = await getOptions(config2);
288
321
  browser = opts.browser || "chrome";
322
+ liveReload = opts.liveReload !== false;
289
323
  },
290
324
  configResolved(_config) {
291
325
  config = _config;
@@ -294,24 +328,25 @@ const pluginBackground = () => {
294
328
  const worker = browser === "firefox" ? manifest.background?.scripts[0] : manifest.background?.service_worker;
295
329
  let loader;
296
330
  if (config.command === "serve") {
331
+ const proto = config.server.https ? "https" : "http";
297
332
  const port = config.server.port?.toString();
298
333
  if (typeof port === "undefined")
299
334
  throw new Error("server port is undefined in watch mode");
300
335
  if (browser === "firefox") {
301
- loader = `import('http://localhost:${port}/@vite/env');
336
+ loader = `import('${proto}://localhost:${port}/@vite/env');
302
337
  `;
303
- loader += `import('http://localhost:${port}${workerClientId}');
338
+ loader += `import('${proto}://localhost:${port}${workerClientId}');
304
339
  `;
305
340
  if (worker)
306
- loader += `import('http://localhost:${port}/${worker}');
341
+ loader += `import('${proto}://localhost:${port}/${worker}');
307
342
  `;
308
343
  } else {
309
- loader = `import 'http://localhost:${port}/@vite/env';
344
+ loader = `import '${proto}://localhost:${port}/@vite/env';
310
345
  `;
311
- loader += `import 'http://localhost:${port}${workerClientId}';
346
+ loader += `import '${proto}://localhost:${port}${workerClientId}';
312
347
  `;
313
348
  if (worker)
314
- loader += `import 'http://localhost:${port}/${worker}';
349
+ loader += `import '${proto}://localhost:${port}/${worker}';
315
350
  `;
316
351
  }
317
352
  } else if (worker) {
@@ -343,7 +378,7 @@ const pluginBackground = () => {
343
378
  ];
344
379
  };
345
380
 
346
- 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";
347
382
 
348
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";
349
384
 
@@ -394,6 +429,21 @@ function createProMainLoader({ fileName }) {
394
429
  return contentProMainLoader.replace(/__SCRIPT__/g, JSON.stringify(fileName));
395
430
  }
396
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";
397
447
  const serverEvent$ = new ReplaySubject(1);
398
448
  const close$ = serverEvent$.pipe(
399
449
  filter((e) => e.type === "close"),
@@ -453,6 +503,8 @@ function prepAsset(fileName, { id, source }) {
453
503
  );
454
504
  }
455
505
  function prepScript(fileName, script) {
506
+ if (script.type === "iife")
507
+ return prepIifeScript(fileName, script);
456
508
  return ($) => $.pipe(
457
509
  // get script contents from dev server
458
510
  mergeMap(async ({ server }) => {
@@ -510,12 +562,111 @@ ${sourceMap}
510
562
  })
511
563
  );
512
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
+ }
513
664
  async function allFilesReady() {
514
665
  await firstValueFrom(allFilesReady$);
515
666
  }
516
667
 
517
668
  const { outputFile } = fsx;
518
- _debug("file-writer");
669
+ const debug$4 = _debug("file-writer");
519
670
  async function start({
520
671
  server
521
672
  }) {
@@ -546,6 +697,12 @@ async function close() {
546
697
  }
547
698
  function add(script) {
548
699
  const fileName = getFileName(script);
700
+ debug$4(
701
+ "add: script.id=%s script.type=%s fileName=%s",
702
+ script.id,
703
+ script.type,
704
+ fileName
705
+ );
549
706
  let file = outputFiles.get(fileName);
550
707
  if (typeof file === "undefined") {
551
708
  file = formatFileData({
@@ -554,6 +711,17 @@ function add(script) {
554
711
  file: write(script)
555
712
  });
556
713
  outputFiles.set(file.fileName, file);
714
+ debug$4("add: stored new file %s", file.fileName);
715
+ } else {
716
+ const isVirtualModule = script.id.startsWith("/@id/") || script.id.startsWith("/__");
717
+ if (isVirtualModule) {
718
+ debug$4(
719
+ "add: virtual module already exists, triggering re-write for %s",
720
+ fileName
721
+ );
722
+ file.file = write(script);
723
+ outputFiles.set(fileName, file);
724
+ }
557
725
  }
558
726
  return file;
559
727
  }
@@ -561,15 +729,19 @@ function update(_id) {
561
729
  const id = prefix$1("/", _id);
562
730
  const types = ["iife", "module"];
563
731
  const updatedFiles = [];
732
+ debug$4("update called: _id=%s id=%s", _id, id);
564
733
  for (const type of types) {
565
734
  const fileName = getFileName({ id, type });
735
+ debug$4("update: looking for fileName=%s", fileName);
566
736
  const scriptFile = outputFiles.get(fileName);
567
737
  if (scriptFile) {
738
+ debug$4("update: found file, calling write()");
568
739
  scriptFile.file = write({ id, type });
569
740
  updatedFiles.push(scriptFile);
570
741
  outputFiles.set(fileName, scriptFile);
571
742
  }
572
743
  }
744
+ debug$4("update: returning %d files", updatedFiles.length);
573
745
  return updatedFiles;
574
746
  }
575
747
  async function write(fileId) {
@@ -609,6 +781,7 @@ const pluginContentScripts = () => {
609
781
  let server;
610
782
  let preambleCode;
611
783
  let hmrTimeout;
784
+ let liveReload = true;
612
785
  let sub = new Subscription();
613
786
  const worldMainIds = /* @__PURE__ */ new Set();
614
787
  const findWorldMainIds = async (config, env) => {
@@ -621,7 +794,7 @@ const pluginContentScripts = () => {
621
794
  });
622
795
  if (worldMainIds.size) {
623
796
  const name = `[${pluginName}]`;
624
- const message = pc.yellow(
797
+ const message = colors.yellow(
625
798
  [
626
799
  `${name} Some content-scripts don't support HMR because the world is MAIN:`,
627
800
  ...[...worldMainIds].map((id) => ` ${id}`)
@@ -636,9 +809,11 @@ const pluginContentScripts = () => {
636
809
  apply: "serve",
637
810
  async config(config, env) {
638
811
  await findWorldMainIds(config, env);
639
- const { contentScripts: contentScripts2 = {} } = await getOptions(config);
812
+ const opts = await getOptions(config);
813
+ const { contentScripts: contentScripts2 = {} } = opts;
640
814
  hmrTimeout = contentScripts2.hmrTimeout ?? 5e3;
641
815
  preambleCode = preambleCode ?? contentScripts2.preambleCode;
816
+ liveReload = opts.liveReload !== false;
642
817
  },
643
818
  async configureServer(_server) {
644
819
  server = _server;
@@ -674,7 +849,8 @@ const pluginContentScripts = () => {
674
849
  });
675
850
  script.fileName = loader.fileName;
676
851
  } else if (type === "iife") {
677
- throw new Error("IIFE content scripts are not implemented");
852
+ const file = add({ type: "iife", id });
853
+ script.fileName = file.fileName;
678
854
  } else {
679
855
  const file = add({ type: "module", id });
680
856
  script.fileName = file.fileName;
@@ -694,10 +870,7 @@ const pluginContentScripts = () => {
694
870
  return defined;
695
871
  }
696
872
  if (id === contentHmrPortId) {
697
- const defined = contentHmrPort.replace(
698
- "__CRX_HMR_TIMEOUT__",
699
- JSON.stringify(hmrTimeout)
700
- );
873
+ const defined = contentHmrPort.replace("__CRX_HMR_TIMEOUT__", JSON.stringify(hmrTimeout)).replace("__CRX_LIVE_RELOAD__", JSON.stringify(liveReload));
701
874
  return defined;
702
875
  }
703
876
  },
@@ -742,7 +915,9 @@ const pluginContentScripts = () => {
742
915
  type: "loader",
743
916
  id: basename(script.id)
744
917
  }),
745
- source: worldMainIds.has(script.id) ? createProMainLoader({ fileName: `./${fileName.split("/").at(-1)}` }) : createProLoader({ fileName })
918
+ source: worldMainIds.has(script.id) ? createProMainLoader({
919
+ fileName: `./${fileName.split("/").at(-1)}`
920
+ }) : createProLoader({ fileName })
746
921
  });
747
922
  script.loaderName = this.getFileName(refId);
748
923
  } else {
@@ -750,7 +925,7 @@ const pluginContentScripts = () => {
750
925
  `;
751
926
  }
752
927
  } else if (script.type === "iife") {
753
- throw new Error("IIFE content scripts are not implemented");
928
+ continue;
754
929
  }
755
930
  contentScripts.set(script.refId, formatFileData(script));
756
931
  }
@@ -790,9 +965,208 @@ const pluginContentScriptsCss = () => {
790
965
  };
791
966
  };
792
967
 
968
+ const contentCssEntries = /* @__PURE__ */ new Map();
969
+ function getContentCssEntries() {
970
+ return Array.from(contentCssEntries.values());
971
+ }
972
+ function clearContentCssEntries() {
973
+ contentCssEntries.clear();
974
+ }
975
+ function registerContentCssEntry(index, cssFiles) {
976
+ const virtualId = getContentCssId(index);
977
+ const entry = { index, cssFiles, virtualId };
978
+ contentCssEntries.set(index, entry);
979
+ return entry;
980
+ }
793
981
  const pluginDeclaredContentScripts = () => {
794
- return [];
982
+ return {
983
+ name: "crx:content-scripts-declared-css",
984
+ apply: "serve",
985
+ resolveId(source) {
986
+ if (isContentCssId(source)) {
987
+ return source;
988
+ }
989
+ },
990
+ load(id) {
991
+ if (!isContentCssId(id))
992
+ return;
993
+ const index = getContentCssIndex(id);
994
+ if (index === null)
995
+ return;
996
+ const entry = contentCssEntries.get(index);
997
+ if (!entry) {
998
+ console.warn(
999
+ `[crx:content-scripts-declared-css] No CSS entry found for index ${index}`
1000
+ );
1001
+ return "";
1002
+ }
1003
+ const cssImports = entry.cssFiles.map((cssPath) => {
1004
+ const importPath = cssPath.startsWith("/") ? cssPath : `/${cssPath}`;
1005
+ return `import "${importPath}";`;
1006
+ }).join("\n");
1007
+ return cssImports + "\n";
1008
+ }
1009
+ };
1010
+ };
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
+ ];
795
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
+ }
796
1170
 
797
1171
  const _dynamicScriptRegEx = /\b(import.meta).CRX_DYNAMIC_SCRIPT_(.+?)[,;]/gm;
798
1172
  const dynamicScriptRegEx = () => {
@@ -801,12 +1175,18 @@ const dynamicScriptRegEx = () => {
801
1175
  };
802
1176
  const pluginDynamicContentScripts = () => {
803
1177
  let config;
1178
+ let standaloneFiles = [];
804
1179
  return [
805
1180
  {
806
1181
  name: "crx:dynamic-content-scripts-loader",
807
1182
  enforce: "pre",
808
1183
  configResolved(_config) {
809
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);
810
1190
  },
811
1191
  configureServer(server) {
812
1192
  return () => {
@@ -837,9 +1217,9 @@ const pluginDynamicContentScripts = () => {
837
1217
  };
838
1218
  },
839
1219
  async resolveId(_source, importer) {
840
- if (importer && _source.includes("?script")) {
1220
+ if (importer && (_source.includes("?script") || _source.includes("?iife"))) {
841
1221
  const url = new URL(_source, "stub://stub");
842
- if (url.searchParams.has("script")) {
1222
+ if (url.searchParams.has("script") || url.searchParams.has("iife")) {
843
1223
  const [source] = _source.split("?");
844
1224
  const resolved = await this.resolve(source, importer, {
845
1225
  skipSelf: true
@@ -849,8 +1229,11 @@ const pluginDynamicContentScripts = () => {
849
1229
  `Could not resolve dynamic script: "${_source}" from "${importer}"`
850
1230
  );
851
1231
  const { id } = resolved;
1232
+ const relId = relative(config.root, id).replace(/^\//, "");
852
1233
  let type = "loader";
853
- if (url.searchParams.has("module")) {
1234
+ if (isIifeContentScript(relId) || standaloneFiles.includes(relId)) {
1235
+ type = "iife";
1236
+ } else if (url.searchParams.has("module")) {
854
1237
  type = "module";
855
1238
  } else if (url.searchParams.has("iife")) {
856
1239
  type = "iife";
@@ -870,13 +1253,13 @@ const pluginDynamicContentScripts = () => {
870
1253
  });
871
1254
  } else {
872
1255
  refId = scriptId;
873
- const relId = relative(config.root, id);
1256
+ const relId2 = relative(config.root, id);
874
1257
  fileName = getFileName({
875
1258
  type: type === "iife" ? "iife" : "module",
876
- id: relId
1259
+ id: relId2
877
1260
  });
878
1261
  if (type === "loader")
879
- loaderName = getFileName({ type, id: relId });
1262
+ loaderName = getFileName({ type, id: relId2 });
880
1263
  }
881
1264
  script = formatFileData({
882
1265
  type,
@@ -922,6 +1305,7 @@ const pluginDynamicContentScripts = () => {
922
1305
  * Can't use `renderChunk` b/c pre plugin crx:content-scripts uses
923
1306
  * `generateBundle` to emit loaders. Must come after "enforce: pre".
924
1307
  */
1308
+ enforce: "post",
925
1309
  generateBundle(options, bundle) {
926
1310
  for (const chunk of Object.values(bundle))
927
1311
  if (chunk.type === "chunk") {
@@ -938,9 +1322,9 @@ const pluginDynamicContentScripts = () => {
938
1322
  throw new Error(
939
1323
  `Content script fileName is undefined: "${script.id}"`
940
1324
  );
941
- return `${JSON.stringify(
942
- `/${script.loaderName ?? script.fileName}`
943
- )}${match.split(scriptKey)[1]}`;
1325
+ const fileName = script.loaderName ?? script.fileName;
1326
+ const path = fileName;
1327
+ return `${JSON.stringify(path)}${match.split(scriptKey)[1]}`;
944
1328
  }
945
1329
  );
946
1330
  chunk.code = replaced;
@@ -1025,7 +1409,7 @@ const pluginFileWriterPolyfill = () => {
1025
1409
  async function manifestFiles(manifest, options = {}) {
1026
1410
  let locales = [];
1027
1411
  if (manifest.default_locale)
1028
- locales = await fg("_locales/**/messages.json", options);
1412
+ locales = await glob("_locales/**/messages.json", options);
1029
1413
  const rulesets = manifest.declarative_net_request?.rule_resources.flatMap(
1030
1414
  ({ path }) => path
1031
1415
  ) ?? [];
@@ -1049,8 +1433,8 @@ async function manifestFiles(manifest, options = {}) {
1049
1433
  manifest.web_accessible_resources.flatMap(({ resources: resources2 }) => resources2).map(async (r) => {
1050
1434
  if (["*", "**/*"].includes(r))
1051
1435
  return void 0;
1052
- if (fg.isDynamicPattern(r))
1053
- return fg(r, options);
1436
+ if (isDynamicPattern(r))
1437
+ return glob(r, options);
1054
1438
  return r;
1055
1439
  })
1056
1440
  );
@@ -1068,7 +1452,7 @@ async function manifestFiles(manifest, options = {}) {
1068
1452
  };
1069
1453
  }
1070
1454
  async function dirFiles(dir) {
1071
- const files = await fg(join(dir, "**", "*"));
1455
+ const files = await glob(join(dir, "**", "*"));
1072
1456
  return files;
1073
1457
  }
1074
1458
  function htmlFiles(manifest) {
@@ -1104,7 +1488,7 @@ const pluginFileWriterPublic = () => {
1104
1488
  };
1105
1489
  };
1106
1490
 
1107
- const debug$2 = _debug("file-writer").extend("hmr");
1491
+ const debug$3 = _debug("file-writer").extend("hmr");
1108
1492
  const isCustomPayload = (p) => {
1109
1493
  return p.type === "custom";
1110
1494
  };
@@ -1142,7 +1526,21 @@ const crxHMRPayload$ = hmrPayload$.pipe(
1142
1526
  return prune;
1143
1527
  }
1144
1528
  case "update": {
1145
- const update = {
1529
+ debug$3("update payload with %d updates", p.updates.length);
1530
+ for (const u of p.updates) {
1531
+ debug$3(
1532
+ "update item: path=%s acceptedPath=%s type=%s",
1533
+ u.path,
1534
+ u.acceptedPath,
1535
+ u.type
1536
+ );
1537
+ const isVirtualModule = u.path.startsWith("/@id/") || u.path.startsWith("/__");
1538
+ if (isVirtualModule) {
1539
+ debug$3("updating virtual module: %s", u.path);
1540
+ update(u.path);
1541
+ }
1542
+ }
1543
+ const update_ = {
1146
1544
  type: "update",
1147
1545
  updates: p.updates.map(({ acceptedPath: ap, path: p2, ...rest }) => ({
1148
1546
  ...rest,
@@ -1150,7 +1548,7 @@ const crxHMRPayload$ = hmrPayload$.pipe(
1150
1548
  path: prefix$1("/", getFileName({ id: p2, type: "module" }))
1151
1549
  }))
1152
1550
  };
1153
- return update;
1551
+ return update_;
1154
1552
  }
1155
1553
  default:
1156
1554
  return p;
@@ -1169,7 +1567,7 @@ const crxHMRPayload$ = hmrPayload$.pipe(
1169
1567
  }
1170
1568
  }),
1171
1569
  map((data) => {
1172
- debug$2(`hmr payload`, data);
1570
+ debug$3(`hmr payload`, data);
1173
1571
  return {
1174
1572
  type: "custom",
1175
1573
  event: "crx:content-script-payload",
@@ -1194,7 +1592,7 @@ function isImporter(file) {
1194
1592
  return pred;
1195
1593
  }
1196
1594
 
1197
- const debug$1 = _debug("hmr");
1595
+ const debug$2 = _debug("hmr");
1198
1596
  const crxRuntimeReload = {
1199
1597
  type: "custom",
1200
1598
  event: "crx:runtime-reload"
@@ -1204,6 +1602,7 @@ const pluginHMR = () => {
1204
1602
  let decoratedSend;
1205
1603
  let config;
1206
1604
  let subs;
1605
+ let liveReload = true;
1207
1606
  return [
1208
1607
  {
1209
1608
  name: "crx:hmr",
@@ -1211,6 +1610,8 @@ const pluginHMR = () => {
1211
1610
  enforce: "pre",
1212
1611
  // server hmr host should be localhost
1213
1612
  async config({ server = {}, ...config2 }) {
1613
+ const opts = await getOptions({ ...config2, server });
1614
+ liveReload = opts.liveReload !== false;
1214
1615
  if (server.hmr === false)
1215
1616
  return;
1216
1617
  if (server.hmr === true)
@@ -1249,7 +1650,9 @@ const pluginHMR = () => {
1249
1650
  subs.add(fileWriterError$.subscribe(send));
1250
1651
  subs.add(
1251
1652
  crxHMRPayload$.subscribe((payload) => {
1252
- send(payload);
1653
+ if (liveReload) {
1654
+ send(payload);
1655
+ }
1253
1656
  })
1254
1657
  );
1255
1658
  }
@@ -1258,29 +1661,71 @@ const pluginHMR = () => {
1258
1661
  subs.unsubscribe();
1259
1662
  },
1260
1663
  // background changes require a full extension reload
1261
- handleHotUpdate({ modules, server }) {
1664
+ handleHotUpdate({ file, modules, server }) {
1262
1665
  const { root } = server.config;
1666
+ const changedFilePath = file ? file.startsWith(root) ? prefix$1("/", file.slice(root.length)) : null : null;
1263
1667
  const relFiles = /* @__PURE__ */ new Set();
1264
1668
  const fsFiles = /* @__PURE__ */ new Set();
1669
+ const virtualModules = /* @__PURE__ */ new Set();
1265
1670
  for (const m of modules) {
1266
1671
  if (m.id?.startsWith(root)) {
1267
1672
  relFiles.add(m.id.slice(server.config.root.length));
1268
1673
  } else if (m.url?.startsWith("/@fs")) {
1269
1674
  fsFiles.add(m.url);
1675
+ } else if (m.id?.startsWith("\0") || m.url?.startsWith("/@id/__x00__")) {
1676
+ const virtualId = m.url ?? m.id;
1677
+ if (virtualId) {
1678
+ virtualModules.add(virtualId);
1679
+ debug$2("virtual module detected:", virtualId);
1680
+ }
1270
1681
  }
1271
1682
  }
1272
- fsFiles.forEach((file) => update(file));
1683
+ fsFiles.forEach((file2) => update(file2));
1684
+ virtualModules.forEach((file2) => update(file2));
1273
1685
  if (inputManifestFiles.background.length) {
1274
1686
  const background = prefix$1("/", inputManifestFiles.background[0]);
1275
1687
  if (relFiles.has(background) || modules.some(isImporter(join(server.config.root, background)))) {
1276
- debug$1("sending runtime reload");
1277
- 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
+ }
1278
1694
  }
1279
1695
  }
1280
1696
  for (const [key, script] of contentScripts)
1281
1697
  if (key === script.id) {
1282
- if (relFiles.has(script.id) || modules.some(isImporter(join(server.config.root, script.id)))) {
1283
- relFiles.forEach((relFile) => update(relFile));
1698
+ if (isContentCssId(script.id)) {
1699
+ const cssEntries = getContentCssEntries();
1700
+ const entry = cssEntries.find((e) => e.virtualId === script.id);
1701
+ if (entry) {
1702
+ const changedCssFiles = [...relFiles].filter(
1703
+ (relFile) => entry.cssFiles.some(
1704
+ (cssFile) => relFile === prefix$1("/", cssFile) || relFile.endsWith(cssFile)
1705
+ )
1706
+ );
1707
+ if (changedCssFiles.length > 0) {
1708
+ changedCssFiles.forEach((relFile) => update(relFile));
1709
+ update(script.id);
1710
+ virtualModules.forEach((file2) => update(file2));
1711
+ }
1712
+ }
1713
+ } else {
1714
+ if (relFiles.has(script.id) || modules.some(isImporter(join(server.config.root, script.id)))) {
1715
+ relFiles.forEach((relFile) => update(relFile));
1716
+ virtualModules.forEach((file2) => update(file2));
1717
+ }
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
+ });
1284
1729
  }
1285
1730
  }
1286
1731
  }
@@ -1313,11 +1758,11 @@ const pluginHMR = () => {
1313
1758
  };
1314
1759
 
1315
1760
  function printStr(dir) {
1316
- return ` ${pc.magentaBright("B R O W S E R")}
1317
- ${pc.greenBright("E X T E N S I O N")}
1318
- ${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")}
1319
1764
 
1320
- ${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`)}`;
1321
1766
  }
1322
1767
  const pluginPrint = () => {
1323
1768
  let outDir = "dist";
@@ -1339,12 +1784,20 @@ const pluginPrint = () => {
1339
1784
 
1340
1785
  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";
1341
1786
 
1787
+ function extractScriptsAndRemove(html) {
1788
+ const root = parse(html);
1789
+ const scripts = root.querySelectorAll("script");
1790
+ const scriptSrcs = scripts.map((el) => el.getAttribute("src"));
1791
+ scripts.forEach((el) => el.remove());
1792
+ return { scriptSrcs, html: root.toString() };
1793
+ }
1794
+
1342
1795
  const pluginName = "crx:html-inline-scripts";
1343
- const debug = _debug(pluginName);
1796
+ const debug$1 = _debug(pluginName);
1344
1797
  const prefix = "@crx/inline-script";
1345
1798
  const isInlineTag = (t) => t.tag === "script" && !t.attrs?.src;
1346
1799
  const toKey = (ctx) => {
1347
- const { dir, name } = parse(ctx.path);
1800
+ const { dir, name } = parse$1(ctx.path);
1348
1801
  return join(prefix, dir, name);
1349
1802
  };
1350
1803
  const pluginHtmlInlineScripts = () => {
@@ -1410,19 +1863,18 @@ const pluginHtmlInlineScripts = () => {
1410
1863
  const key = toKey(ctx);
1411
1864
  const p = pages.get(key);
1412
1865
  if (p?.scripts.some(isInlineTag)) {
1413
- const $ = load(html);
1866
+ const { scriptSrcs, html: cleanedHtml } = extractScriptsAndRemove(html);
1414
1867
  p.scripts.push(
1415
- ...$("script").toArray().map((el) => ({
1868
+ ...scriptSrcs.map((src) => ({
1416
1869
  tag: "script",
1417
- attrs: { src: $(el).attr("src"), type: "module" }
1870
+ attrs: { src, type: "module" }
1418
1871
  }))
1419
1872
  );
1420
- $("script").remove();
1421
1873
  const loader2 = {
1422
1874
  tag: "script",
1423
1875
  attrs: { src: `${key}?t=${Date.now()}`, type: "module" }
1424
1876
  };
1425
- return { html: $.html(), tags: [loader2] };
1877
+ return { html: cleanedHtml, tags: [loader2] };
1426
1878
  }
1427
1879
  return p?.scripts ?? void 0;
1428
1880
  }
@@ -1464,21 +1916,21 @@ const pluginHtmlInlineScripts = () => {
1464
1916
  })}"`;
1465
1917
  return [inline, loader.replace("SCRIPTS", json)].join("\n");
1466
1918
  } else {
1467
- debug("page missing %s", id);
1919
+ debug$1("page missing %s", id);
1468
1920
  }
1469
1921
  }
1470
1922
  }
1471
1923
  };
1472
1924
  };
1473
1925
 
1474
- 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";
1926
+ 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";
1475
1927
 
1476
- 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";
1928
+ 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";
1477
1929
 
1478
1930
  const { readFile } = promises;
1479
1931
  const pluginManifest = () => {
1480
1932
  let manifest;
1481
- let plugins;
1933
+ let plugins = [];
1482
1934
  let refId;
1483
1935
  let config;
1484
1936
  return [
@@ -1523,6 +1975,14 @@ const pluginManifest = () => {
1523
1975
  };
1524
1976
  }
1525
1977
  },
1978
+ // Use configResolved to get plugins for rolldown-vite (Vite 7) compatibility
1979
+ // In rolldown-vite, buildStart doesn't receive options.plugins, so we grab
1980
+ // them from the resolved config instead
1981
+ configResolved(resolvedConfig) {
1982
+ if (resolvedConfig.plugins) {
1983
+ plugins = resolvedConfig.plugins;
1984
+ }
1985
+ },
1526
1986
  buildStart(options) {
1527
1987
  if (options.plugins)
1528
1988
  plugins = options.plugins;
@@ -1618,8 +2078,33 @@ const pluginManifest = () => {
1618
2078
  }
1619
2079
  }
1620
2080
  if (config.command === "serve") {
2081
+ clearContentCssEntries();
1621
2082
  if (manifest2.content_scripts)
1622
- for (const { js = [], matches = [] } of manifest2.content_scripts)
2083
+ for (let i = 0; i < manifest2.content_scripts.length; i++) {
2084
+ const {
2085
+ js = [],
2086
+ css = [],
2087
+ matches = []
2088
+ } = manifest2.content_scripts[i];
2089
+ if (css.length > 0) {
2090
+ const cssEntry = registerContentCssEntry(i, css);
2091
+ contentScripts.set(
2092
+ cssEntry.virtualId,
2093
+ formatFileData({
2094
+ type: "loader",
2095
+ id: cssEntry.virtualId,
2096
+ matches,
2097
+ refId: hashScriptId({
2098
+ type: "loader",
2099
+ id: cssEntry.virtualId
2100
+ }),
2101
+ fileName: getFileName({
2102
+ type: "loader",
2103
+ id: cssEntry.virtualId
2104
+ })
2105
+ })
2106
+ );
2107
+ }
1623
2108
  for (const id2 of js) {
1624
2109
  contentScripts.set(
1625
2110
  prefix$1("/", id2),
@@ -1632,10 +2117,21 @@ const pluginManifest = () => {
1632
2117
  })
1633
2118
  );
1634
2119
  }
2120
+ }
1635
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
+ };
1636
2130
  if (manifest2.content_scripts)
1637
2131
  for (const { js = [], matches = [] } of manifest2.content_scripts)
1638
2132
  for (const file of js) {
2133
+ if (isIifeContentScript(file) || isStandaloneFile(file))
2134
+ continue;
1639
2135
  const id2 = join(config.root, file);
1640
2136
  const refId2 = this.emitFile({
1641
2137
  type: "chunk",
@@ -1689,12 +2185,26 @@ const pluginManifest = () => {
1689
2185
  const manifestJs = bundle[manifestName];
1690
2186
  let manifest2 = decodeManifest.call(this, manifestJs.code);
1691
2187
  if (config.command === "serve") {
1692
- if (manifest2.content_scripts)
1693
- for (const script of manifest2.content_scripts) {
1694
- script.js = script.js?.map(
2188
+ if (manifest2.content_scripts) {
2189
+ const cssEntries = getContentCssEntries();
2190
+ const cssEntryMap = new Map(cssEntries.map((e) => [e.index, e]));
2191
+ for (let i = 0; i < manifest2.content_scripts.length; i++) {
2192
+ const script = manifest2.content_scripts[i];
2193
+ const cssEntry = cssEntryMap.get(i);
2194
+ const jsLoaders = (script.js || []).map(
1695
2195
  (id) => getFileName({ id, type: "loader" })
1696
2196
  );
2197
+ if (cssEntry) {
2198
+ const cssLoader = getFileName({
2199
+ id: cssEntry.virtualId,
2200
+ type: "loader"
2201
+ });
2202
+ script.js = [cssLoader, ...jsLoaders];
2203
+ } else {
2204
+ script.js = jsLoaders;
2205
+ }
1697
2206
  }
2207
+ }
1698
2208
  } else {
1699
2209
  if (manifest2.background && "service_worker" in manifest2.background) {
1700
2210
  const ref = manifest2.background.service_worker;
@@ -1732,17 +2242,18 @@ const pluginManifest = () => {
1732
2242
  const name = `[${plugin.name}]`;
1733
2243
  let message = error;
1734
2244
  if (error instanceof Error) {
1735
- message = pc.red(
2245
+ message = colors.red(
1736
2246
  `${name} ${error.stack ? error.stack : error.message}`
1737
2247
  );
1738
2248
  } else if (typeof error === "string") {
1739
- message = pc.red(`${name} ${error}`);
2249
+ message = colors.red(`${name} ${error}`);
1740
2250
  }
1741
2251
  console.log(message);
1742
2252
  throw new Error(`Error in ${plugin.name}.renderCrxManifest`);
1743
2253
  }
1744
2254
  }
1745
2255
  const assetTypes = [
2256
+ "contentStyles",
1746
2257
  "icons",
1747
2258
  "locales",
1748
2259
  "rulesets",
@@ -1780,10 +2291,7 @@ Public dir: "${config.publicDir}"`
1780
2291
  const refId2 = this.emitFile({
1781
2292
  type: "asset",
1782
2293
  name: "loading-page.js",
1783
- source: loadingPageScript.replace(
1784
- "%PORT%",
1785
- `${config.server.port ?? 0}`
1786
- )
2294
+ source: loadingPageScript.replace("%PROTO%", config.server.https ? "https" : "http").replace("%PORT%", `${config.server.port ?? 0}`)
1787
2295
  });
1788
2296
  const loadingPageScriptName = this.getFileName(refId2);
1789
2297
  files.html.map(
@@ -1875,11 +2383,12 @@ const defineDynamicResource = ({
1875
2383
  });
1876
2384
  const DYNAMIC_RESOURCE = "<dynamic_resource>";
1877
2385
 
1878
- _debug("web-acc-res");
2386
+ const debug = _debug("web-acc-res");
1879
2387
  const pluginWebAccessibleResources = () => {
1880
2388
  let config;
1881
2389
  let injectCss;
1882
2390
  let browser;
2391
+ let userWantsViteManifest;
1883
2392
  return [
1884
2393
  {
1885
2394
  name: "crx:web-accessible-resources",
@@ -1919,6 +2428,7 @@ const pluginWebAccessibleResources = () => {
1919
2428
  const contentScripts2 = opts.contentScripts || {};
1920
2429
  browser = opts.browser || "chrome";
1921
2430
  injectCss = contentScripts2.injectCss ?? true;
2431
+ userWantsViteManifest = build?.manifest;
1922
2432
  return { ...config2, build: { ...build, manifest: command === "build" } };
1923
2433
  },
1924
2434
  configResolved(_config) {
@@ -2053,6 +2563,18 @@ const pluginWebAccessibleResources = () => {
2053
2563
  delete manifest.web_accessible_resources;
2054
2564
  else
2055
2565
  manifest.web_accessible_resources = combinedResources;
2566
+ if (!userWantsViteManifest) {
2567
+ const viteMajorVersion = parseInt(version.split(".")[0]);
2568
+ const manifestPath = viteMajorVersion > 4 ? ".vite/manifest.json" : "manifest.json";
2569
+ if (bundle[manifestPath]) {
2570
+ debug(
2571
+ "Removing Vite manifest: %s (userWantsViteManifest=%s)",
2572
+ manifestPath,
2573
+ userWantsViteManifest
2574
+ );
2575
+ delete bundle[manifestPath];
2576
+ }
2577
+ }
2056
2578
  return manifest;
2057
2579
  }
2058
2580
  }
@@ -2063,6 +2585,8 @@ const crx = (options) => {
2063
2585
  contentScripts.clear();
2064
2586
  return [
2065
2587
  pluginOptionsProvider(options),
2588
+ pluginContentScriptsIife(),
2589
+ // Must come early so isIifeModeEnabled is set before manifest plugin
2066
2590
  pluginBackground(),
2067
2591
  pluginContentScripts(),
2068
2592
  pluginDeclaredContentScripts(),