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