@crxjs/vite-plugin 2.5.0 → 2.6.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/README.md +4 -0
- package/dist/index.d.mts +448 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +473 -187
- package/package.json +16 -7
package/dist/index.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { readFile as readFile$1 } from 'fs/promises';
|
|
|
11
11
|
import MagicString from 'magic-string';
|
|
12
12
|
import { build, mergeConfig, createLogger, version } from 'vite';
|
|
13
13
|
import convertSourceMap from 'convert-source-map';
|
|
14
|
-
import
|
|
14
|
+
import pc from 'picocolors';
|
|
15
15
|
import { readFileSync, existsSync, promises } from 'fs';
|
|
16
16
|
import { createRequire } from 'module';
|
|
17
17
|
import { glob, isDynamicPattern } from 'tinyglobby';
|
|
@@ -244,7 +244,13 @@ function getOutputPath(server, fileName) {
|
|
|
244
244
|
const target = isAbsolute(outDir) ? join(outDir, fileName) : join(root, outDir, fileName);
|
|
245
245
|
return target;
|
|
246
246
|
}
|
|
247
|
-
function getViteUrl({ type, id }) {
|
|
247
|
+
function getViteUrl({ type, id }, { timestamp = false } = {}) {
|
|
248
|
+
if (timestamp && !id.startsWith("/@") && !id.includes("?v=")) {
|
|
249
|
+
const t = `t=${Date.now()}` + (id.includes("?") ? "&" : "");
|
|
250
|
+
const parts = id.split("?");
|
|
251
|
+
parts[1] = typeof parts[1] === "undefined" ? t : t + parts[1];
|
|
252
|
+
id = parts.join("?");
|
|
253
|
+
}
|
|
248
254
|
if (type === "asset") {
|
|
249
255
|
throw new Error(`File type "${type}" not implemented.`);
|
|
250
256
|
} else if (type === "iife") {
|
|
@@ -378,6 +384,52 @@ const pluginBackground = () => {
|
|
|
378
384
|
];
|
|
379
385
|
};
|
|
380
386
|
|
|
387
|
+
const extensionOrigins = [/^chrome-extension:\/\//, /^moz-extension:\/\//];
|
|
388
|
+
function isExtensionOrigin(origin) {
|
|
389
|
+
return origin ? extensionOrigins.some((pattern) => pattern.test(origin)) : false;
|
|
390
|
+
}
|
|
391
|
+
function addExtensionOrigins(origin) {
|
|
392
|
+
if (origin === true)
|
|
393
|
+
return true;
|
|
394
|
+
if (typeof origin === "function") {
|
|
395
|
+
return (requestOrigin, cb) => {
|
|
396
|
+
if (isExtensionOrigin(requestOrigin)) {
|
|
397
|
+
cb(null, true);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
origin(requestOrigin, cb);
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
if (Array.isArray(origin))
|
|
404
|
+
return [...origin, ...extensionOrigins];
|
|
405
|
+
if (origin)
|
|
406
|
+
return [origin, ...extensionOrigins];
|
|
407
|
+
return extensionOrigins;
|
|
408
|
+
}
|
|
409
|
+
function addExtensionCors(cors) {
|
|
410
|
+
if (cors === true)
|
|
411
|
+
return true;
|
|
412
|
+
if (cors && typeof cors === "object") {
|
|
413
|
+
return {
|
|
414
|
+
...cors,
|
|
415
|
+
origin: addExtensionOrigins(cors.origin)
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
return { origin: extensionOrigins };
|
|
419
|
+
}
|
|
420
|
+
function pluginExtensionCors() {
|
|
421
|
+
return {
|
|
422
|
+
name: "crx:extension-cors",
|
|
423
|
+
apply: "serve",
|
|
424
|
+
config(config) {
|
|
425
|
+
config.server = {
|
|
426
|
+
...config.server,
|
|
427
|
+
cors: addExtensionCors(config.server?.cors)
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
381
433
|
var contentHmrPort = "function isCrxHMRPayload(x) {\n return x.type === \"custom\" && x.event.startsWith(\"crx:\");\n}\nclass HMRPort {\n port;\n callbacks = /* @__PURE__ */ new Map();\n constructor() {\n setInterval(() => {\n try {\n this.port?.postMessage({ data: \"ping\" });\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"Extension context invalidated.\")) {\n location.reload();\n } else\n throw error;\n }\n }, __CRX_HMR_TIMEOUT__);\n setInterval(this.initPort, 5 * 60 * 1e3);\n this.initPort();\n }\n initPort = () => {\n this.port?.disconnect();\n this.port = chrome.runtime.connect({ name: \"@crx/client\" });\n this.port.onDisconnect.addListener(this.handleDisconnect.bind(this));\n this.port.onMessage.addListener(this.handleMessage.bind(this));\n this.port.postMessage({ type: \"connected\" });\n };\n handleDisconnect = () => {\n if (this.callbacks.has(\"close\"))\n for (const cb of this.callbacks.get(\"close\")) {\n cb({ wasClean: true });\n }\n };\n handleMessage = (message) => {\n const forward = (data) => {\n if (this.callbacks.has(\"message\"))\n for (const cb of this.callbacks.get(\"message\")) {\n cb({ data });\n }\n };\n const payload = JSON.parse(message.data);\n if (isCrxHMRPayload(payload)) {\n if (payload.event === \"crx:runtime-reload\") {\n if (__CRX_LIVE_RELOAD__) {\n console.log(\"[crx] runtime reload\");\n setTimeout(() => location.reload(), 500);\n } else {\n console.log(\"[crx] runtime reload suppressed (liveReload disabled)\");\n }\n } else {\n forward(JSON.stringify(payload.data));\n }\n } else {\n forward(message.data);\n }\n };\n addEventListener = (event, callback) => {\n const cbs = this.callbacks.get(event) ?? /* @__PURE__ */ new Set();\n cbs.add(callback);\n this.callbacks.set(event, cbs);\n };\n send = (data) => {\n if (this.port)\n this.port.postMessage({ data });\n else\n throw new Error(\"HMRPort is not initialized\");\n };\n}\n\nexport { HMRPort };\n";
|
|
382
434
|
|
|
383
435
|
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";
|
|
@@ -398,9 +450,10 @@ contentScripts.change$.pipe(filter(RxMap.isChangeType.set)).subscribe(({ map, va
|
|
|
398
450
|
"resolvedId",
|
|
399
451
|
"scriptId"
|
|
400
452
|
];
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
453
|
+
const keys = keyNames.map((keyName) => value[keyName]);
|
|
454
|
+
keys.push(value.id.replace(/^\//, ""));
|
|
455
|
+
for (const key of keys) {
|
|
456
|
+
if (typeof key === "undefined" || map.get(key) === value) {
|
|
404
457
|
continue;
|
|
405
458
|
} else {
|
|
406
459
|
map.set(key, value);
|
|
@@ -465,8 +518,17 @@ fileWriterEvent$.pipe(
|
|
|
465
518
|
const allFilesReady$ = buildEnd$.pipe(
|
|
466
519
|
switchMap(() => outputFiles.change$.pipe(startWith({ type: "start" }))),
|
|
467
520
|
map(() => [...outputFiles.values()]),
|
|
468
|
-
switchMap(
|
|
521
|
+
switchMap(
|
|
522
|
+
(files) => Promise.allSettled(files.map((file) => waitForOutputFile(file)))
|
|
523
|
+
)
|
|
469
524
|
);
|
|
525
|
+
async function waitForOutputFile(file, seen = /* @__PURE__ */ new Set()) {
|
|
526
|
+
if (seen.has(file))
|
|
527
|
+
return;
|
|
528
|
+
seen.add(file);
|
|
529
|
+
const { deps } = await file.file;
|
|
530
|
+
await Promise.all(deps.map((dep) => waitForOutputFile(dep, seen)));
|
|
531
|
+
}
|
|
470
532
|
const timestamp$ = new BehaviorSubject(Date.now());
|
|
471
533
|
allFilesReady$.subscribe(() => {
|
|
472
534
|
timestamp$.next(Date.now());
|
|
@@ -509,7 +571,14 @@ function prepScript(fileName, script) {
|
|
|
509
571
|
// get script contents from dev server
|
|
510
572
|
mergeMap(async ({ server }) => {
|
|
511
573
|
const target = getOutputPath(server, fileName);
|
|
512
|
-
const
|
|
574
|
+
const originalViteUrl = getViteUrl(script);
|
|
575
|
+
const isVueSfcQuery = script.id.includes("?vue");
|
|
576
|
+
const viteUrl = getViteUrl(script, { timestamp: isVueSfcQuery });
|
|
577
|
+
if (isVueSfcQuery) {
|
|
578
|
+
const module = await server.moduleGraph.getModuleByUrl(originalViteUrl);
|
|
579
|
+
if (module)
|
|
580
|
+
server.moduleGraph.invalidateModule(module);
|
|
581
|
+
}
|
|
513
582
|
const transformResult = await server.transformRequest(viteUrl);
|
|
514
583
|
if (!transformResult)
|
|
515
584
|
throw new TypeError(`Unable to load "${script.id}" from server.`);
|
|
@@ -667,6 +736,11 @@ async function allFilesReady() {
|
|
|
667
736
|
|
|
668
737
|
const { outputFile } = fsx;
|
|
669
738
|
const debug$4 = _debug("file-writer");
|
|
739
|
+
function queueWrite(script, previous) {
|
|
740
|
+
if (!previous)
|
|
741
|
+
return write(script);
|
|
742
|
+
return previous.file.catch(() => void 0).then(() => write(script));
|
|
743
|
+
}
|
|
670
744
|
async function start({
|
|
671
745
|
server
|
|
672
746
|
}) {
|
|
@@ -708,18 +782,24 @@ function add(script) {
|
|
|
708
782
|
file = formatFileData({
|
|
709
783
|
...script,
|
|
710
784
|
fileName,
|
|
711
|
-
file:
|
|
785
|
+
file: queueWrite(script)
|
|
712
786
|
});
|
|
713
787
|
outputFiles.set(file.fileName, file);
|
|
714
788
|
debug$4("add: stored new file %s", file.fileName);
|
|
715
789
|
} else {
|
|
716
790
|
const isVirtualModule = script.id.startsWith("/@id/") || script.id.startsWith("/__");
|
|
717
|
-
|
|
791
|
+
const isTimestampedModule = script.type === "module" && /[?&]t=\d+/.test(script.id);
|
|
792
|
+
if (isVirtualModule || isTimestampedModule) {
|
|
718
793
|
debug$4(
|
|
719
|
-
"add:
|
|
794
|
+
"add: module already exists, triggering re-write for %s",
|
|
720
795
|
fileName
|
|
721
796
|
);
|
|
722
|
-
file
|
|
797
|
+
file = formatFileData({
|
|
798
|
+
...file,
|
|
799
|
+
...script,
|
|
800
|
+
fileName,
|
|
801
|
+
file: queueWrite(script, file)
|
|
802
|
+
});
|
|
723
803
|
outputFiles.set(fileName, file);
|
|
724
804
|
}
|
|
725
805
|
}
|
|
@@ -736,7 +816,7 @@ function update(_id) {
|
|
|
736
816
|
const scriptFile = outputFiles.get(fileName);
|
|
737
817
|
if (scriptFile) {
|
|
738
818
|
debug$4("update: found file, calling write()");
|
|
739
|
-
scriptFile.file =
|
|
819
|
+
scriptFile.file = queueWrite({ id, type }, scriptFile);
|
|
740
820
|
updatedFiles.push(scriptFile);
|
|
741
821
|
outputFiles.set(fileName, scriptFile);
|
|
742
822
|
}
|
|
@@ -794,7 +874,7 @@ const pluginContentScripts = () => {
|
|
|
794
874
|
});
|
|
795
875
|
if (worldMainIds.size) {
|
|
796
876
|
const name = `[${pluginName}]`;
|
|
797
|
-
const message =
|
|
877
|
+
const message = pc.yellow(
|
|
798
878
|
[
|
|
799
879
|
`${name} Some content-scripts don't support HMR because the world is MAIN:`,
|
|
800
880
|
...[...worldMainIds].map((id) => ` ${id}`)
|
|
@@ -823,7 +903,7 @@ const pluginContentScripts = () => {
|
|
|
823
903
|
try {
|
|
824
904
|
const react = await import('@vitejs/plugin-react');
|
|
825
905
|
preambleCode = react.default.preambleCode;
|
|
826
|
-
} catch
|
|
906
|
+
} catch {
|
|
827
907
|
preambleCode = false;
|
|
828
908
|
}
|
|
829
909
|
}
|
|
@@ -898,41 +978,50 @@ const pluginContentScripts = () => {
|
|
|
898
978
|
};
|
|
899
979
|
},
|
|
900
980
|
generateBundle(_options, bundle) {
|
|
901
|
-
|
|
902
|
-
if (key === script.refId) {
|
|
903
|
-
if (script.type === "module") {
|
|
904
|
-
const fileName = this.getFileName(script.refId);
|
|
905
|
-
script.fileName = fileName;
|
|
906
|
-
} else if (script.type === "loader") {
|
|
907
|
-
const fileName = this.getFileName(script.refId);
|
|
908
|
-
script.fileName = fileName;
|
|
909
|
-
const bundleFileInfo = bundle[fileName];
|
|
910
|
-
const shouldUseLoader = !(bundleFileInfo.type === "chunk" && bundleFileInfo.imports.length === 0 && bundleFileInfo.dynamicImports.length === 0 && bundleFileInfo.exports.length === 0);
|
|
911
|
-
if (shouldUseLoader) {
|
|
912
|
-
const refId = this.emitFile({
|
|
913
|
-
type: "asset",
|
|
914
|
-
name: getFileName({
|
|
915
|
-
type: "loader",
|
|
916
|
-
id: basename(script.id)
|
|
917
|
-
}),
|
|
918
|
-
source: worldMainIds.has(script.id) ? createProMainLoader({
|
|
919
|
-
fileName: `./${fileName.split("/").at(-1)}`
|
|
920
|
-
}) : createProLoader({ fileName })
|
|
921
|
-
});
|
|
922
|
-
script.loaderName = this.getFileName(refId);
|
|
923
|
-
} else {
|
|
924
|
-
bundleFileInfo.code = `(function(){${bundleFileInfo.code}})()
|
|
925
|
-
`;
|
|
926
|
-
}
|
|
927
|
-
} else if (script.type === "iife") {
|
|
928
|
-
continue;
|
|
929
|
-
}
|
|
930
|
-
contentScripts.set(script.refId, formatFileData(script));
|
|
931
|
-
}
|
|
981
|
+
finalizeBuildContentScripts(this, bundle, worldMainIds);
|
|
932
982
|
}
|
|
933
983
|
}
|
|
934
984
|
];
|
|
935
985
|
};
|
|
986
|
+
function finalizeBuildContentScripts(context, bundle, worldMainIds = /* @__PURE__ */ new Set()) {
|
|
987
|
+
const processed = /* @__PURE__ */ new Set();
|
|
988
|
+
for (const [key, script] of contentScripts) {
|
|
989
|
+
if (key !== script.refId || processed.has(script))
|
|
990
|
+
continue;
|
|
991
|
+
processed.add(script);
|
|
992
|
+
if (script.type === "module") {
|
|
993
|
+
script.fileName = script.fileName ?? context.getFileName(script.refId);
|
|
994
|
+
} else if (script.type === "loader") {
|
|
995
|
+
const fileName = script.fileName ?? context.getFileName(script.refId);
|
|
996
|
+
script.fileName = fileName;
|
|
997
|
+
const bundleFileInfo = bundle[fileName];
|
|
998
|
+
if (bundleFileInfo?.type !== "chunk")
|
|
999
|
+
continue;
|
|
1000
|
+
const shouldUseLoader = !(bundleFileInfo.imports.length === 0 && bundleFileInfo.dynamicImports.length === 0 && bundleFileInfo.exports.length === 0);
|
|
1001
|
+
if (shouldUseLoader) {
|
|
1002
|
+
if (typeof script.loaderName === "undefined") {
|
|
1003
|
+
const refId = context.emitFile({
|
|
1004
|
+
type: "asset",
|
|
1005
|
+
name: getFileName({
|
|
1006
|
+
type: "loader",
|
|
1007
|
+
id: basename(script.id)
|
|
1008
|
+
}),
|
|
1009
|
+
source: worldMainIds.has(script.id) ? createProMainLoader({
|
|
1010
|
+
fileName: `./${fileName.split("/").at(-1)}`
|
|
1011
|
+
}) : createProLoader({ fileName })
|
|
1012
|
+
});
|
|
1013
|
+
script.loaderName = context.getFileName(refId);
|
|
1014
|
+
}
|
|
1015
|
+
} else if (typeof script.loaderName === "undefined" && !bundleFileInfo.code.startsWith("(function(){")) {
|
|
1016
|
+
bundleFileInfo.code = `(function(){${bundleFileInfo.code}})()
|
|
1017
|
+
`;
|
|
1018
|
+
}
|
|
1019
|
+
} else if (script.type === "iife") {
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
contentScripts.set(script.refId, formatFileData(script));
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
936
1025
|
|
|
937
1026
|
const pluginContentScriptsCss = () => {
|
|
938
1027
|
let injectCss;
|
|
@@ -1027,45 +1116,22 @@ const pluginContentScriptsIife = () => {
|
|
|
1027
1116
|
name: pluginName,
|
|
1028
1117
|
apply: "build",
|
|
1029
1118
|
enforce: "post",
|
|
1030
|
-
async generateBundle(
|
|
1119
|
+
async generateBundle() {
|
|
1031
1120
|
const opts = await getOptions({ plugins: config.plugins });
|
|
1032
1121
|
const _manifest = opts.manifest;
|
|
1033
1122
|
const manifest = typeof _manifest === "function" ? await _manifest({ command: "build", mode: config.mode }) : await Promise.resolve(_manifest);
|
|
1034
1123
|
const standaloneFiles = (opts.contentScripts?.standaloneFiles || []).map(
|
|
1035
|
-
|
|
1124
|
+
normalizeContentScriptPath
|
|
1125
|
+
);
|
|
1126
|
+
const iifeEntries = collectIifeEntries(
|
|
1127
|
+
manifest,
|
|
1128
|
+
standaloneFiles,
|
|
1129
|
+
config.root
|
|
1036
1130
|
);
|
|
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
1131
|
if (iifeEntries.length === 0)
|
|
1066
1132
|
return;
|
|
1067
1133
|
console.log(
|
|
1068
|
-
|
|
1134
|
+
pc.cyan(`
|
|
1069
1135
|
[${pluginName}] Building ${iifeEntries.length} content script(s) as IIFE...`)
|
|
1070
1136
|
);
|
|
1071
1137
|
for (const entry of iifeEntries) {
|
|
@@ -1073,52 +1139,106 @@ const pluginContentScriptsIife = () => {
|
|
|
1073
1139
|
try {
|
|
1074
1140
|
const iifeConfig = createIifeConfig(config, entry.id, outputFileName);
|
|
1075
1141
|
const result = await build(iifeConfig);
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
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
|
-
}
|
|
1142
|
+
const outputs = getBuildOutputs(result, entry.file);
|
|
1143
|
+
emitIifeOutputs(this, outputs, entry.file, outputFileName);
|
|
1144
|
+
registerIifeContentScript(entry, outputFileName);
|
|
1145
|
+
console.log(
|
|
1146
|
+
pc.green(` \u2713 ${basename(entry.file)} \u2192 ${outputFileName}`)
|
|
1147
|
+
);
|
|
1108
1148
|
} catch (error) {
|
|
1109
1149
|
console.error(
|
|
1110
|
-
|
|
1150
|
+
pc.red(` \u2717 Failed to build ${entry.file}:`),
|
|
1111
1151
|
error
|
|
1112
1152
|
);
|
|
1113
1153
|
throw error;
|
|
1114
1154
|
}
|
|
1115
1155
|
}
|
|
1116
|
-
console.log(
|
|
1156
|
+
console.log(pc.cyan(`[${pluginName}] IIFE build complete
|
|
1117
1157
|
`));
|
|
1118
1158
|
}
|
|
1119
1159
|
}
|
|
1120
1160
|
];
|
|
1121
1161
|
};
|
|
1162
|
+
function normalizeContentScriptPath(file) {
|
|
1163
|
+
return file.replace(/^\//, "");
|
|
1164
|
+
}
|
|
1165
|
+
function isStandaloneFile(file, standaloneFiles) {
|
|
1166
|
+
return standaloneFiles.includes(normalizeContentScriptPath(file));
|
|
1167
|
+
}
|
|
1168
|
+
function collectIifeEntries(manifest, standaloneFiles, root) {
|
|
1169
|
+
const entries = [];
|
|
1170
|
+
const entryIds = /* @__PURE__ */ new Set();
|
|
1171
|
+
const addEntry = (entry) => {
|
|
1172
|
+
if (entryIds.has(entry.id))
|
|
1173
|
+
return;
|
|
1174
|
+
entryIds.add(entry.id);
|
|
1175
|
+
entries.push(entry);
|
|
1176
|
+
};
|
|
1177
|
+
for (const { js = [], matches = [] } of manifest.content_scripts ?? []) {
|
|
1178
|
+
for (const file of js) {
|
|
1179
|
+
if (isIifeContentScript(file) || isStandaloneFile(file, standaloneFiles)) {
|
|
1180
|
+
addEntry({ file, id: join(root, file), matches });
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
for (const [, script] of contentScripts.entries()) {
|
|
1185
|
+
if (script.type !== "iife" || !script.isDynamicScript)
|
|
1186
|
+
continue;
|
|
1187
|
+
addEntry({
|
|
1188
|
+
file: script.id,
|
|
1189
|
+
id: join(root, script.id),
|
|
1190
|
+
matches: script.matches ?? []
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
return entries;
|
|
1194
|
+
}
|
|
1195
|
+
function getBuildOutputs(result, entryFile) {
|
|
1196
|
+
if ("on" in result) {
|
|
1197
|
+
throw new Error(`Unexpected watcher result for "${entryFile}"`);
|
|
1198
|
+
}
|
|
1199
|
+
return Array.isArray(result) ? result.flatMap((r) => r.output) : result.output;
|
|
1200
|
+
}
|
|
1201
|
+
function emitIifeOutputs(context, outputs, entryFile, outputFileName) {
|
|
1202
|
+
const entryChunk = outputs.find(
|
|
1203
|
+
(output) => output.type === "chunk" && output.isEntry
|
|
1204
|
+
);
|
|
1205
|
+
if (!entryChunk) {
|
|
1206
|
+
throw new Error(`Unable to generate IIFE bundle for "${entryFile}"`);
|
|
1207
|
+
}
|
|
1208
|
+
context.emitFile({
|
|
1209
|
+
type: "asset",
|
|
1210
|
+
fileName: outputFileName,
|
|
1211
|
+
source: entryChunk.code
|
|
1212
|
+
});
|
|
1213
|
+
for (const output of outputs) {
|
|
1214
|
+
if (output.type === "asset") {
|
|
1215
|
+
if (output.fileName !== outputFileName && output.fileName !== "manifest.json" && !output.fileName.startsWith(".vite/")) {
|
|
1216
|
+
context.emitFile({
|
|
1217
|
+
type: "asset",
|
|
1218
|
+
fileName: output.fileName,
|
|
1219
|
+
source: output.source
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
} else if (!output.isEntry) {
|
|
1223
|
+
context.emitFile({
|
|
1224
|
+
type: "asset",
|
|
1225
|
+
fileName: output.fileName,
|
|
1226
|
+
source: output.code
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
function registerIifeContentScript(entry, outputFileName) {
|
|
1232
|
+
const existingScript = contentScripts.get(entry.file);
|
|
1233
|
+
const script = existingScript ? { ...existingScript, fileName: outputFileName } : {
|
|
1234
|
+
type: "iife",
|
|
1235
|
+
id: entry.file,
|
|
1236
|
+
refId: entry.file,
|
|
1237
|
+
matches: entry.matches,
|
|
1238
|
+
fileName: outputFileName
|
|
1239
|
+
};
|
|
1240
|
+
contentScripts.set(entry.file, formatFileData(script));
|
|
1241
|
+
}
|
|
1122
1242
|
function getIifeOutputPath(file) {
|
|
1123
1243
|
const normalizedFile = file.replace(/^\//, "");
|
|
1124
1244
|
const dir = dirname(normalizedFile);
|
|
@@ -1188,6 +1308,36 @@ const pluginDynamicContentScripts = () => {
|
|
|
1188
1308
|
);
|
|
1189
1309
|
}).catch(() => void 0);
|
|
1190
1310
|
},
|
|
1311
|
+
buildStart() {
|
|
1312
|
+
if (config.command === "build") {
|
|
1313
|
+
const dynamicScripts = [];
|
|
1314
|
+
for (const [key, script] of contentScripts) {
|
|
1315
|
+
if (script.isDynamicScript && key === script.scriptId) {
|
|
1316
|
+
dynamicScripts.push(script);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
contentScripts.clear();
|
|
1320
|
+
for (const script of dynamicScripts) {
|
|
1321
|
+
const absoluteId = script.id.startsWith("/") ? `${config.root}${script.id}` : `${config.root}/${script.id}`;
|
|
1322
|
+
const refId = this.emitFile({
|
|
1323
|
+
type: "chunk",
|
|
1324
|
+
id: absoluteId,
|
|
1325
|
+
name: basename(script.id)
|
|
1326
|
+
});
|
|
1327
|
+
contentScripts.set(
|
|
1328
|
+
script.id,
|
|
1329
|
+
formatFileData({
|
|
1330
|
+
type: script.type,
|
|
1331
|
+
id: script.id,
|
|
1332
|
+
isDynamicScript: true,
|
|
1333
|
+
refId,
|
|
1334
|
+
scriptId: script.scriptId,
|
|
1335
|
+
matches: script.matches
|
|
1336
|
+
})
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
},
|
|
1191
1341
|
configureServer(server) {
|
|
1192
1342
|
return () => {
|
|
1193
1343
|
server.middlewares.use(async (req, res, next) => {
|
|
@@ -1249,7 +1399,10 @@ const pluginDynamicContentScripts = () => {
|
|
|
1249
1399
|
refId = this.emitFile({
|
|
1250
1400
|
type: "chunk",
|
|
1251
1401
|
id,
|
|
1252
|
-
name: basename(id)
|
|
1402
|
+
name: basename(id),
|
|
1403
|
+
// Preserve content script entry exports so the build finalizer
|
|
1404
|
+
// can decide whether the script needs a loader wrapper.
|
|
1405
|
+
preserveSignature: "exports-only"
|
|
1253
1406
|
});
|
|
1254
1407
|
} else {
|
|
1255
1408
|
refId = scriptId;
|
|
@@ -1283,9 +1436,29 @@ const pluginDynamicContentScripts = () => {
|
|
|
1283
1436
|
const index = id.indexOf("?scriptId=");
|
|
1284
1437
|
if (index > -1) {
|
|
1285
1438
|
const scriptId = id.slice(index + "?scriptId=".length);
|
|
1286
|
-
|
|
1439
|
+
let script = contentScripts.get(scriptId);
|
|
1440
|
+
if (!script && config.command === "build") {
|
|
1441
|
+
const fileId = id.slice(0, index);
|
|
1442
|
+
const refId = this.emitFile({
|
|
1443
|
+
type: "chunk",
|
|
1444
|
+
id: fileId,
|
|
1445
|
+
name: basename(fileId)
|
|
1446
|
+
});
|
|
1447
|
+
script = formatFileData({
|
|
1448
|
+
type: "loader",
|
|
1449
|
+
id: relative(config.root, fileId),
|
|
1450
|
+
isDynamicScript: true,
|
|
1451
|
+
refId,
|
|
1452
|
+
scriptId,
|
|
1453
|
+
matches: []
|
|
1454
|
+
});
|
|
1455
|
+
contentScripts.set(script.id, script);
|
|
1456
|
+
}
|
|
1457
|
+
if (!script) {
|
|
1458
|
+
throw new Error(`Content script not found for scriptId: "${scriptId}"`);
|
|
1459
|
+
}
|
|
1287
1460
|
if (config.command === "build") {
|
|
1288
|
-
return `export default import.meta.CRX_DYNAMIC_SCRIPT_${script.
|
|
1461
|
+
return `export default import.meta.CRX_DYNAMIC_SCRIPT_${script.scriptId};`;
|
|
1289
1462
|
} else if (typeof script.fileName === "string") {
|
|
1290
1463
|
return `export default ${JSON.stringify(script.fileName)};`;
|
|
1291
1464
|
} else {
|
|
@@ -1493,6 +1666,89 @@ const isCustomPayload = (p) => {
|
|
|
1493
1666
|
return p.type === "custom";
|
|
1494
1667
|
};
|
|
1495
1668
|
const hmrPayload$ = new Subject();
|
|
1669
|
+
function withTimestamp(id, timestamp) {
|
|
1670
|
+
if (id.includes("?t=") || id.includes("&t="))
|
|
1671
|
+
return id;
|
|
1672
|
+
const t = `t=${timestamp}` + (id.includes("?") ? "&" : "");
|
|
1673
|
+
const parts = id.split("?");
|
|
1674
|
+
parts[1] = typeof parts[1] === "undefined" ? t : t + parts[1];
|
|
1675
|
+
return parts.join("?");
|
|
1676
|
+
}
|
|
1677
|
+
function getUpdatePayloadFileIds(p, { timestamp = false } = {}) {
|
|
1678
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1679
|
+
for (const u of p.updates) {
|
|
1680
|
+
for (const id of [u.path, u.acceptedPath]) {
|
|
1681
|
+
const isVirtualModule = id.startsWith("/@id/") || id.startsWith("/__");
|
|
1682
|
+
const isQueryModule = id.includes("?");
|
|
1683
|
+
if (isVirtualModule || isQueryModule)
|
|
1684
|
+
ids.add(timestamp ? withTimestamp(id, u.timestamp) : id);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
return [...ids];
|
|
1688
|
+
}
|
|
1689
|
+
function mapVitePayloadForCrx(p) {
|
|
1690
|
+
switch (p.type) {
|
|
1691
|
+
case "full-reload": {
|
|
1692
|
+
const fullReload = {
|
|
1693
|
+
type: "full-reload",
|
|
1694
|
+
path: p.path && getViteUrl({ id: p.path, type: "module" })
|
|
1695
|
+
};
|
|
1696
|
+
return fullReload;
|
|
1697
|
+
}
|
|
1698
|
+
case "prune": {
|
|
1699
|
+
const prune = {
|
|
1700
|
+
type: "prune",
|
|
1701
|
+
paths: p.paths.map((id) => getViteUrl({ id, type: "module" }))
|
|
1702
|
+
};
|
|
1703
|
+
return prune;
|
|
1704
|
+
}
|
|
1705
|
+
case "update": {
|
|
1706
|
+
debug$3("update payload with %d updates", p.updates.length);
|
|
1707
|
+
for (const u of p.updates) {
|
|
1708
|
+
debug$3(
|
|
1709
|
+
"update item: path=%s acceptedPath=%s type=%s",
|
|
1710
|
+
u.path,
|
|
1711
|
+
u.acceptedPath,
|
|
1712
|
+
u.type
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
const update_ = {
|
|
1716
|
+
type: "update",
|
|
1717
|
+
updates: p.updates.map(({ acceptedPath: ap, path: p2, ...rest }) => ({
|
|
1718
|
+
...rest,
|
|
1719
|
+
acceptedPath: prefix$1("/", getFileName({ id: ap, type: "module" })),
|
|
1720
|
+
path: prefix$1("/", getFileName({ id: p2, type: "module" }))
|
|
1721
|
+
}))
|
|
1722
|
+
};
|
|
1723
|
+
return update_;
|
|
1724
|
+
}
|
|
1725
|
+
default:
|
|
1726
|
+
return p;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
async function prepareVitePayloadForCrx(p) {
|
|
1730
|
+
if (p.type === "update") {
|
|
1731
|
+
const pendingFiles = getUpdatePayloadFileIds(p, {
|
|
1732
|
+
timestamp: true
|
|
1733
|
+
}).flatMap((id) => {
|
|
1734
|
+
debug$3("updating payload module: %s", id);
|
|
1735
|
+
return update(id).map((file) => file.file);
|
|
1736
|
+
});
|
|
1737
|
+
await Promise.all(pendingFiles);
|
|
1738
|
+
await firstValueFrom(allFilesReady$);
|
|
1739
|
+
}
|
|
1740
|
+
return mapVitePayloadForCrx(p);
|
|
1741
|
+
}
|
|
1742
|
+
function shouldForwardCrxPayload(p) {
|
|
1743
|
+
switch (p.type) {
|
|
1744
|
+
case "prune":
|
|
1745
|
+
return p.paths.length > 0;
|
|
1746
|
+
case "update":
|
|
1747
|
+
return p.updates.length > 0;
|
|
1748
|
+
default:
|
|
1749
|
+
return true;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1496
1752
|
const crxHMRPayload$ = hmrPayload$.pipe(
|
|
1497
1753
|
filter((p) => !isCustomPayload(p)),
|
|
1498
1754
|
buffer(allFilesReady$),
|
|
@@ -1509,63 +1765,8 @@ const crxHMRPayload$ = hmrPayload$.pipe(
|
|
|
1509
1765
|
payloads.push(fullReload);
|
|
1510
1766
|
return payloads;
|
|
1511
1767
|
}),
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
case "full-reload": {
|
|
1515
|
-
const fullReload = {
|
|
1516
|
-
type: "full-reload",
|
|
1517
|
-
path: p.path && getViteUrl({ id: p.path, type: "module" })
|
|
1518
|
-
};
|
|
1519
|
-
return fullReload;
|
|
1520
|
-
}
|
|
1521
|
-
case "prune": {
|
|
1522
|
-
const prune = {
|
|
1523
|
-
type: "prune",
|
|
1524
|
-
paths: p.paths.map((id) => getViteUrl({ id, type: "module" }))
|
|
1525
|
-
};
|
|
1526
|
-
return prune;
|
|
1527
|
-
}
|
|
1528
|
-
case "update": {
|
|
1529
|
-
debug$3("update payload with %d updates", p.updates.length);
|
|
1530
|
-
for (const u of p.updates) {
|
|
1531
|
-
debug$3(
|
|
1532
|
-
"update item: path=%s acceptedPath=%s type=%s",
|
|
1533
|
-
u.path,
|
|
1534
|
-
u.acceptedPath,
|
|
1535
|
-
u.type
|
|
1536
|
-
);
|
|
1537
|
-
const isVirtualModule = u.path.startsWith("/@id/") || u.path.startsWith("/__");
|
|
1538
|
-
if (isVirtualModule) {
|
|
1539
|
-
debug$3("updating virtual module: %s", u.path);
|
|
1540
|
-
update(u.path);
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
const update_ = {
|
|
1544
|
-
type: "update",
|
|
1545
|
-
updates: p.updates.map(({ acceptedPath: ap, path: p2, ...rest }) => ({
|
|
1546
|
-
...rest,
|
|
1547
|
-
acceptedPath: prefix$1("/", getFileName({ id: ap, type: "module" })),
|
|
1548
|
-
path: prefix$1("/", getFileName({ id: p2, type: "module" }))
|
|
1549
|
-
}))
|
|
1550
|
-
};
|
|
1551
|
-
return update_;
|
|
1552
|
-
}
|
|
1553
|
-
default:
|
|
1554
|
-
return p;
|
|
1555
|
-
}
|
|
1556
|
-
}),
|
|
1557
|
-
filter((p) => {
|
|
1558
|
-
switch (p.type) {
|
|
1559
|
-
case "full-reload":
|
|
1560
|
-
return typeof p.path === "undefined";
|
|
1561
|
-
case "prune":
|
|
1562
|
-
return p.paths.length > 0;
|
|
1563
|
-
case "update":
|
|
1564
|
-
return p.updates.length > 0;
|
|
1565
|
-
default:
|
|
1566
|
-
return true;
|
|
1567
|
-
}
|
|
1568
|
-
}),
|
|
1768
|
+
mergeMap(prepareVitePayloadForCrx),
|
|
1769
|
+
filter(shouldForwardCrxPayload),
|
|
1569
1770
|
map((data) => {
|
|
1570
1771
|
debug$3(`hmr payload`, data);
|
|
1571
1772
|
return {
|
|
@@ -1597,6 +1798,23 @@ const crxRuntimeReload = {
|
|
|
1597
1798
|
type: "custom",
|
|
1598
1799
|
event: "crx:runtime-reload"
|
|
1599
1800
|
};
|
|
1801
|
+
function getChangedFilePath(root, file) {
|
|
1802
|
+
if (!file)
|
|
1803
|
+
return null;
|
|
1804
|
+
const normalizedRoot = normalize(root);
|
|
1805
|
+
const normalizedFile = normalize(file);
|
|
1806
|
+
const relativeFile = relative(normalizedRoot, normalizedFile);
|
|
1807
|
+
if (!relativeFile || relativeFile.startsWith("..") || isAbsolute(relativeFile)) {
|
|
1808
|
+
return null;
|
|
1809
|
+
}
|
|
1810
|
+
return prefix$1("/", relativeFile);
|
|
1811
|
+
}
|
|
1812
|
+
function stripTimestamp(id) {
|
|
1813
|
+
return id.replace(/([?&])t=\d+&/, "$1").replace(/[?&]t=\d+$/, "").replace(/\?$/, "");
|
|
1814
|
+
}
|
|
1815
|
+
function escapeRegExp(text) {
|
|
1816
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1817
|
+
}
|
|
1600
1818
|
const pluginHMR = () => {
|
|
1601
1819
|
let inputManifestFiles;
|
|
1602
1820
|
let decoratedSend;
|
|
@@ -1663,13 +1881,14 @@ const pluginHMR = () => {
|
|
|
1663
1881
|
// background changes require a full extension reload
|
|
1664
1882
|
handleHotUpdate({ file, modules, server }) {
|
|
1665
1883
|
const { root } = server.config;
|
|
1666
|
-
const changedFilePath =
|
|
1884
|
+
const changedFilePath = getChangedFilePath(root, file);
|
|
1667
1885
|
const relFiles = /* @__PURE__ */ new Set();
|
|
1668
1886
|
const fsFiles = /* @__PURE__ */ new Set();
|
|
1669
1887
|
const virtualModules = /* @__PURE__ */ new Set();
|
|
1670
1888
|
for (const m of modules) {
|
|
1671
|
-
|
|
1672
|
-
|
|
1889
|
+
const relFile = getChangedFilePath(root, m.id);
|
|
1890
|
+
if (relFile) {
|
|
1891
|
+
relFiles.add(relFile);
|
|
1673
1892
|
} else if (m.url?.startsWith("/@fs")) {
|
|
1674
1893
|
fsFiles.add(m.url);
|
|
1675
1894
|
} else if (m.id?.startsWith("\0") || m.url?.startsWith("/@id/__x00__")) {
|
|
@@ -1741,13 +1960,14 @@ const pluginHMR = () => {
|
|
|
1741
1960
|
},
|
|
1742
1961
|
renderCrxDevScript(code, { id: _id, type }) {
|
|
1743
1962
|
if (type === "module" && _id !== "/@vite/client" && code.includes("createHotContext")) {
|
|
1744
|
-
const id = _id
|
|
1745
|
-
const escaped = id
|
|
1963
|
+
const id = stripTimestamp(_id);
|
|
1964
|
+
const escaped = escapeRegExp(id);
|
|
1746
1965
|
const regexp = new RegExp(
|
|
1747
|
-
`(
|
|
1966
|
+
`(createHotContext\\(")${escaped}("\\))`,
|
|
1967
|
+
"g"
|
|
1748
1968
|
);
|
|
1749
1969
|
const fileUrl = prefix$1("/", getFileName({ id, type }));
|
|
1750
|
-
const replaced = code.replace(regexp, fileUrl);
|
|
1970
|
+
const replaced = code.replace(regexp, `$1${fileUrl}$2`);
|
|
1751
1971
|
return replaced;
|
|
1752
1972
|
} else {
|
|
1753
1973
|
return code;
|
|
@@ -1758,11 +1978,11 @@ const pluginHMR = () => {
|
|
|
1758
1978
|
};
|
|
1759
1979
|
|
|
1760
1980
|
function printStr(dir) {
|
|
1761
|
-
return ` ${
|
|
1762
|
-
${
|
|
1763
|
-
${
|
|
1981
|
+
return ` ${pc.magentaBright("B R O W S E R")}
|
|
1982
|
+
${pc.greenBright("E X T E N S I O N")}
|
|
1983
|
+
${pc.blueBright("T O O L S")}
|
|
1764
1984
|
|
|
1765
|
-
${
|
|
1985
|
+
${pc.green("\u279C")} ${pc.bold("CRXJS")}: ${pc.green(`Load ${pc.cyan(dir)} as unpacked extension`)}`;
|
|
1766
1986
|
}
|
|
1767
1987
|
const pluginPrint = () => {
|
|
1768
1988
|
let outDir = "dist";
|
|
@@ -1923,14 +2143,50 @@ const pluginHtmlInlineScripts = () => {
|
|
|
1923
2143
|
};
|
|
1924
2144
|
};
|
|
1925
2145
|
|
|
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=\"${
|
|
2146
|
+
var loadingPageScript = "const VITE_URL = \"%PROTO%://localhost:%PORT%\";\nconst READY_PATH = \"%READY_PATH%\";\nconst VITE_PAGE_URL = new URL(location.pathname + location.search, VITE_URL).href;\nconst RELOAD_DELAY = 100;\nfunction getReadyUrl() {\n const url = new URL(READY_PATH, VITE_URL);\n url.searchParams.set(\"path\", location.pathname + location.search);\n url.searchParams.set(\"t\", Date.now().toString());\n return url.href;\n}\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_PAGE_URL}\">${VITE_PAGE_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 const response = await fetch(getReadyUrl());\n if (!response.ok)\n throw new Error(`HTTP ${response.status}`);\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_PAGE_URL}`);\n console.log(`[CRXJS] Retrying in ${timeout}ms...`);\n await new Promise((resolve) => setTimeout(resolve, timeout));\n }\n} while (!ready);\nconsole.log(`[CRXJS] Vite Dev Server is ready on ${VITE_PAGE_URL}`);\nconsole.log(`[CRXJS] Reloading in ${RELOAD_DELAY}ms...`);\nawait new Promise((resolve) => setTimeout(resolve, RELOAD_DELAY));\nlocation.reload();\n";
|
|
1927
2147
|
|
|
1928
2148
|
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";
|
|
1929
2149
|
|
|
1930
2150
|
const { readFile } = promises;
|
|
2151
|
+
const loadingPageReadyPath = "/@crx/dev-ready";
|
|
2152
|
+
function normalizeHtmlPath(pathname) {
|
|
2153
|
+
let decoded;
|
|
2154
|
+
try {
|
|
2155
|
+
decoded = decodeURIComponent(pathname);
|
|
2156
|
+
} catch {
|
|
2157
|
+
return null;
|
|
2158
|
+
}
|
|
2159
|
+
const normalized = normalize(decoded.replace(/^\/+/, "") || "index.html");
|
|
2160
|
+
if (normalized.startsWith("..") || isAbsolute(normalized))
|
|
2161
|
+
return null;
|
|
2162
|
+
return normalized;
|
|
2163
|
+
}
|
|
2164
|
+
function getLoadingPageReadyHtmlPath(requestUrl) {
|
|
2165
|
+
if (!requestUrl)
|
|
2166
|
+
return null;
|
|
2167
|
+
let url;
|
|
2168
|
+
try {
|
|
2169
|
+
url = new URL(requestUrl, "http://crxjs.local");
|
|
2170
|
+
} catch {
|
|
2171
|
+
return null;
|
|
2172
|
+
}
|
|
2173
|
+
if (url.pathname !== loadingPageReadyPath)
|
|
2174
|
+
return void 0;
|
|
2175
|
+
const path = url.searchParams.get("path");
|
|
2176
|
+
if (!path)
|
|
2177
|
+
return null;
|
|
2178
|
+
let pageUrl;
|
|
2179
|
+
try {
|
|
2180
|
+
pageUrl = new URL(path, "http://crxjs.local");
|
|
2181
|
+
} catch {
|
|
2182
|
+
return null;
|
|
2183
|
+
}
|
|
2184
|
+
return normalizeHtmlPath(pageUrl.pathname);
|
|
2185
|
+
}
|
|
1931
2186
|
const pluginManifest = () => {
|
|
1932
2187
|
let manifest;
|
|
1933
2188
|
let plugins = [];
|
|
2189
|
+
let devHtmlFiles = /* @__PURE__ */ new Set();
|
|
1934
2190
|
let refId;
|
|
1935
2191
|
let config;
|
|
1936
2192
|
return [
|
|
@@ -1950,6 +2206,7 @@ const pluginManifest = () => {
|
|
|
1950
2206
|
background: sw,
|
|
1951
2207
|
html
|
|
1952
2208
|
} = await manifestFiles(manifest, { cwd: config2.root });
|
|
2209
|
+
devHtmlFiles = new Set(html.map(normalizeHtmlPath).filter(isString));
|
|
1953
2210
|
const { entries = [] } = config2.optimizeDeps ?? {};
|
|
1954
2211
|
let { input = [] } = config2.build?.rollupOptions ?? {};
|
|
1955
2212
|
if (typeof input === "string")
|
|
@@ -1983,6 +2240,25 @@ const pluginManifest = () => {
|
|
|
1983
2240
|
plugins = resolvedConfig.plugins;
|
|
1984
2241
|
}
|
|
1985
2242
|
},
|
|
2243
|
+
configureServer(server) {
|
|
2244
|
+
server.middlewares.use((req, res, next) => {
|
|
2245
|
+
const htmlPath = getLoadingPageReadyHtmlPath(req.url);
|
|
2246
|
+
if (typeof htmlPath === "undefined") {
|
|
2247
|
+
next();
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
2251
|
+
res.setHeader("Access-Control-Allow-Methods", "GET");
|
|
2252
|
+
if (!htmlPath) {
|
|
2253
|
+
res.statusCode = 400;
|
|
2254
|
+
res.end();
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
const exists = devHtmlFiles.has(htmlPath) && (existsSync(join(server.config.root, htmlPath)) || existsSync(join(server.config.publicDir, htmlPath)));
|
|
2258
|
+
res.statusCode = exists ? 204 : 404;
|
|
2259
|
+
res.end();
|
|
2260
|
+
});
|
|
2261
|
+
},
|
|
1986
2262
|
buildStart(options) {
|
|
1987
2263
|
if (options.plugins)
|
|
1988
2264
|
plugins = options.plugins;
|
|
@@ -2136,7 +2412,10 @@ const pluginManifest = () => {
|
|
|
2136
2412
|
const refId2 = this.emitFile({
|
|
2137
2413
|
type: "chunk",
|
|
2138
2414
|
id: id2,
|
|
2139
|
-
name: basename(file)
|
|
2415
|
+
name: basename(file),
|
|
2416
|
+
// Preserve content script entry exports so the build finalizer
|
|
2417
|
+
// can decide whether the script needs a loader wrapper.
|
|
2418
|
+
preserveSignature: "exports-only"
|
|
2140
2419
|
});
|
|
2141
2420
|
contentScripts.set(
|
|
2142
2421
|
file,
|
|
@@ -2206,6 +2485,7 @@ const pluginManifest = () => {
|
|
|
2206
2485
|
}
|
|
2207
2486
|
}
|
|
2208
2487
|
} else {
|
|
2488
|
+
finalizeBuildContentScripts(this, bundle);
|
|
2209
2489
|
if (manifest2.background && "service_worker" in manifest2.background) {
|
|
2210
2490
|
const ref = manifest2.background.service_worker;
|
|
2211
2491
|
const name = this.getFileName(ref);
|
|
@@ -2220,7 +2500,7 @@ const pluginManifest = () => {
|
|
|
2220
2500
|
({ js = [], ...rest }) => {
|
|
2221
2501
|
return {
|
|
2222
2502
|
js: js.map((id) => {
|
|
2223
|
-
const script = contentScripts.get(id);
|
|
2503
|
+
const script = contentScripts.get(id) ?? contentScripts.get(prefix$1("/", id));
|
|
2224
2504
|
const fileName = script?.loaderName ?? script?.fileName;
|
|
2225
2505
|
if (typeof fileName === "undefined")
|
|
2226
2506
|
throw new Error(
|
|
@@ -2242,11 +2522,11 @@ const pluginManifest = () => {
|
|
|
2242
2522
|
const name = `[${plugin.name}]`;
|
|
2243
2523
|
let message = error;
|
|
2244
2524
|
if (error instanceof Error) {
|
|
2245
|
-
message =
|
|
2525
|
+
message = pc.red(
|
|
2246
2526
|
`${name} ${error.stack ? error.stack : error.message}`
|
|
2247
2527
|
);
|
|
2248
2528
|
} else if (typeof error === "string") {
|
|
2249
|
-
message =
|
|
2529
|
+
message = pc.red(`${name} ${error}`);
|
|
2250
2530
|
}
|
|
2251
2531
|
console.log(message);
|
|
2252
2532
|
throw new Error(`Error in ${plugin.name}.renderCrxManifest`);
|
|
@@ -2260,6 +2540,11 @@ const pluginManifest = () => {
|
|
|
2260
2540
|
"webAccessibleResources"
|
|
2261
2541
|
];
|
|
2262
2542
|
const files = await manifestFiles(manifest2, { cwd: config.root });
|
|
2543
|
+
if (config.command === "serve") {
|
|
2544
|
+
devHtmlFiles = new Set(
|
|
2545
|
+
files.html.map(normalizeHtmlPath).filter(isString)
|
|
2546
|
+
);
|
|
2547
|
+
}
|
|
2263
2548
|
await Promise.all(
|
|
2264
2549
|
assetTypes.map((k) => files[k]).flat().map(async (f) => {
|
|
2265
2550
|
if (typeof bundle[f] === "undefined") {
|
|
@@ -2291,7 +2576,7 @@ Public dir: "${config.publicDir}"`
|
|
|
2291
2576
|
const refId2 = this.emitFile({
|
|
2292
2577
|
type: "asset",
|
|
2293
2578
|
name: "loading-page.js",
|
|
2294
|
-
source: loadingPageScript.replace("%PROTO%", config.server.https ? "https" : "http").replace("%PORT%", `${config.server.port ?? 0}`)
|
|
2579
|
+
source: loadingPageScript.replace("%PROTO%", config.server.https ? "https" : "http").replace("%PORT%", `${config.server.port ?? 0}`).replace("%READY_PATH%", loadingPageReadyPath)
|
|
2295
2580
|
});
|
|
2296
2581
|
const loadingPageScriptName = this.getFileName(refId2);
|
|
2297
2582
|
files.html.map(
|
|
@@ -2585,6 +2870,7 @@ const crx = (options) => {
|
|
|
2585
2870
|
contentScripts.clear();
|
|
2586
2871
|
return [
|
|
2587
2872
|
pluginOptionsProvider(options),
|
|
2873
|
+
pluginExtensionCors(),
|
|
2588
2874
|
pluginContentScriptsIife(),
|
|
2589
2875
|
// Must come early so isIifeModeEnabled is set before manifest plugin
|
|
2590
2876
|
pluginBackground(),
|