@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/client.d.ts +15 -0
- package/dist/index.d.ts +57 -2
- package/dist/index.mjs +606 -82
- package/index.d.cts +1 -0
- package/package.json +19 -8
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$
|
|
4
|
-
import { join, normalize,
|
|
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
|
|
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
|
|
18
|
-
import {
|
|
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 = \"
|
|
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$
|
|
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(
|
|
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 "
|
|
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 =
|
|
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('
|
|
336
|
+
loader = `import('${proto}://localhost:${port}/@vite/env');
|
|
302
337
|
`;
|
|
303
|
-
loader += `import('
|
|
338
|
+
loader += `import('${proto}://localhost:${port}${workerClientId}');
|
|
304
339
|
`;
|
|
305
340
|
if (worker)
|
|
306
|
-
loader += `import('
|
|
341
|
+
loader += `import('${proto}://localhost:${port}/${worker}');
|
|
307
342
|
`;
|
|
308
343
|
} else {
|
|
309
|
-
loader = `import '
|
|
344
|
+
loader = `import '${proto}://localhost:${port}/@vite/env';
|
|
310
345
|
`;
|
|
311
|
-
loader += `import '
|
|
346
|
+
loader += `import '${proto}://localhost:${port}${workerClientId}';
|
|
312
347
|
`;
|
|
313
348
|
if (worker)
|
|
314
|
-
loader += `import '
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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 (
|
|
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
|
|
1256
|
+
const relId2 = relative(config.root, id);
|
|
874
1257
|
fileName = getFileName({
|
|
875
1258
|
type: type === "iife" ? "iife" : "module",
|
|
876
|
-
id:
|
|
1259
|
+
id: relId2
|
|
877
1260
|
});
|
|
878
1261
|
if (type === "loader")
|
|
879
|
-
loaderName = getFileName({ type, id:
|
|
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
|
-
|
|
942
|
-
|
|
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
|
|
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 (
|
|
1053
|
-
return
|
|
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
|
|
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$
|
|
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
|
-
|
|
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
|
|
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$
|
|
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$
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
1277
|
-
|
|
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 (
|
|
1283
|
-
|
|
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 ` ${
|
|
1317
|
-
${
|
|
1318
|
-
${
|
|
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
|
-
${
|
|
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
|
|
1866
|
+
const { scriptSrcs, html: cleanedHtml } = extractScriptsAndRemove(html);
|
|
1414
1867
|
p.scripts.push(
|
|
1415
|
-
|
|
1868
|
+
...scriptSrcs.map((src) => ({
|
|
1416
1869
|
tag: "script",
|
|
1417
|
-
attrs: { src
|
|
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:
|
|
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 = \"
|
|
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>
|
|
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 (
|
|
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
|
-
|
|
1694
|
-
|
|
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 =
|
|
2245
|
+
message = colors.red(
|
|
1736
2246
|
`${name} ${error.stack ? error.stack : error.message}`
|
|
1737
2247
|
);
|
|
1738
2248
|
} else if (typeof error === "string") {
|
|
1739
|
-
message =
|
|
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(),
|