@crxjs/vite-plugin 2.5.0 → 2.6.1
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 +517 -192
- package/package.json +16 -7
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { simple } from 'acorn-walk';
|
|
|
2
2
|
import { createHash } from 'crypto';
|
|
3
3
|
import debug$5 from 'debug';
|
|
4
4
|
import { join, normalize, dirname, basename, isAbsolute, relative, resolve, parse as parse$1 } from 'pathe';
|
|
5
|
-
import { Subject, filter, ReplaySubject, switchMap, of, startWith, map, BehaviorSubject, mergeMap, firstValueFrom, takeUntil, first, toArray, retry, concatWith, Subscription, buffer } from 'rxjs';
|
|
5
|
+
import { Subject, filter, ReplaySubject, switchMap, of, startWith, tap, debounceTime, map, share, BehaviorSubject, mergeMap, firstValueFrom, takeUntil, first, toArray, retry, concatWith, Subscription, buffer } from 'rxjs';
|
|
6
6
|
import fsx from 'fs-extra';
|
|
7
7
|
import { performance } from 'perf_hooks';
|
|
8
8
|
import { rollup } from 'rollup';
|
|
@@ -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);
|
|
@@ -462,11 +515,59 @@ fileWriterEvent$.pipe(
|
|
|
462
515
|
filter((e) => e.type === "build_start"),
|
|
463
516
|
switchMap((e) => of(e))
|
|
464
517
|
);
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
518
|
+
const allFilesReadyDebounceMs = 100;
|
|
519
|
+
let currentAllFilesReadyGeneration = 0;
|
|
520
|
+
let completedAllFilesReadyGeneration = 0;
|
|
521
|
+
let lastAllFilesReadyResults;
|
|
522
|
+
const allFilesReadyState$ = buildEnd$.pipe(
|
|
523
|
+
switchMap(
|
|
524
|
+
() => outputFiles.change$.pipe(
|
|
525
|
+
startWith({ type: "start" }),
|
|
526
|
+
tap(() => {
|
|
527
|
+
currentAllFilesReadyGeneration += 1;
|
|
528
|
+
}),
|
|
529
|
+
debounceTime(allFilesReadyDebounceMs)
|
|
530
|
+
)
|
|
531
|
+
),
|
|
532
|
+
map(() => ({
|
|
533
|
+
generation: currentAllFilesReadyGeneration,
|
|
534
|
+
files: [...outputFiles.values()]
|
|
535
|
+
})),
|
|
536
|
+
switchMap(async ({ generation, files }) => {
|
|
537
|
+
const seen = /* @__PURE__ */ new Set();
|
|
538
|
+
const results = await Promise.allSettled(
|
|
539
|
+
files.map((file) => waitForOutputFile(file, seen))
|
|
540
|
+
);
|
|
541
|
+
return { generation, results };
|
|
542
|
+
}),
|
|
543
|
+
tap(({ generation, results }) => {
|
|
544
|
+
completedAllFilesReadyGeneration = generation;
|
|
545
|
+
lastAllFilesReadyResults = results;
|
|
546
|
+
}),
|
|
547
|
+
share()
|
|
548
|
+
);
|
|
549
|
+
const allFilesReady$ = allFilesReadyState$.pipe(
|
|
550
|
+
map(({ results }) => results)
|
|
469
551
|
);
|
|
552
|
+
async function waitForOutputFile(file, seen = /* @__PURE__ */ new Set()) {
|
|
553
|
+
if (seen.has(file))
|
|
554
|
+
return;
|
|
555
|
+
seen.add(file);
|
|
556
|
+
const { deps } = await file.file;
|
|
557
|
+
await Promise.all(deps.map((dep) => waitForOutputFile(dep, seen)));
|
|
558
|
+
}
|
|
559
|
+
async function waitForAllFilesReadyResults() {
|
|
560
|
+
const targetGeneration = currentAllFilesReadyGeneration;
|
|
561
|
+
if (lastAllFilesReadyResults && completedAllFilesReadyGeneration >= targetGeneration) {
|
|
562
|
+
return lastAllFilesReadyResults;
|
|
563
|
+
}
|
|
564
|
+
const { results } = await firstValueFrom(
|
|
565
|
+
allFilesReadyState$.pipe(
|
|
566
|
+
filter(({ generation }) => generation >= targetGeneration)
|
|
567
|
+
)
|
|
568
|
+
);
|
|
569
|
+
return results;
|
|
570
|
+
}
|
|
470
571
|
const timestamp$ = new BehaviorSubject(Date.now());
|
|
471
572
|
allFilesReady$.subscribe(() => {
|
|
472
573
|
timestamp$.next(Date.now());
|
|
@@ -509,7 +610,14 @@ function prepScript(fileName, script) {
|
|
|
509
610
|
// get script contents from dev server
|
|
510
611
|
mergeMap(async ({ server }) => {
|
|
511
612
|
const target = getOutputPath(server, fileName);
|
|
512
|
-
const
|
|
613
|
+
const originalViteUrl = getViteUrl(script);
|
|
614
|
+
const isVueSfcQuery = script.id.includes("?vue");
|
|
615
|
+
const viteUrl = getViteUrl(script, { timestamp: isVueSfcQuery });
|
|
616
|
+
if (isVueSfcQuery) {
|
|
617
|
+
const module = await server.moduleGraph.getModuleByUrl(originalViteUrl);
|
|
618
|
+
if (module)
|
|
619
|
+
server.moduleGraph.invalidateModule(module);
|
|
620
|
+
}
|
|
513
621
|
const transformResult = await server.transformRequest(viteUrl);
|
|
514
622
|
if (!transformResult)
|
|
515
623
|
throw new TypeError(`Unable to load "${script.id}" from server.`);
|
|
@@ -662,11 +770,16 @@ function prepIifeScript(fileName, script) {
|
|
|
662
770
|
);
|
|
663
771
|
}
|
|
664
772
|
async function allFilesReady() {
|
|
665
|
-
await
|
|
773
|
+
await waitForAllFilesReadyResults();
|
|
666
774
|
}
|
|
667
775
|
|
|
668
776
|
const { outputFile } = fsx;
|
|
669
777
|
const debug$4 = _debug("file-writer");
|
|
778
|
+
function queueWrite(script, previous) {
|
|
779
|
+
if (!previous)
|
|
780
|
+
return write(script);
|
|
781
|
+
return previous.file.catch(() => void 0).then(() => write(script));
|
|
782
|
+
}
|
|
670
783
|
async function start({
|
|
671
784
|
server
|
|
672
785
|
}) {
|
|
@@ -708,18 +821,24 @@ function add(script) {
|
|
|
708
821
|
file = formatFileData({
|
|
709
822
|
...script,
|
|
710
823
|
fileName,
|
|
711
|
-
file:
|
|
824
|
+
file: queueWrite(script)
|
|
712
825
|
});
|
|
713
826
|
outputFiles.set(file.fileName, file);
|
|
714
827
|
debug$4("add: stored new file %s", file.fileName);
|
|
715
828
|
} else {
|
|
716
829
|
const isVirtualModule = script.id.startsWith("/@id/") || script.id.startsWith("/__");
|
|
717
|
-
|
|
830
|
+
const isTimestampedModule = script.type === "module" && /[?&]t=\d+/.test(script.id);
|
|
831
|
+
if (isVirtualModule || isTimestampedModule) {
|
|
718
832
|
debug$4(
|
|
719
|
-
"add:
|
|
833
|
+
"add: module already exists, triggering re-write for %s",
|
|
720
834
|
fileName
|
|
721
835
|
);
|
|
722
|
-
file
|
|
836
|
+
file = formatFileData({
|
|
837
|
+
...file,
|
|
838
|
+
...script,
|
|
839
|
+
fileName,
|
|
840
|
+
file: queueWrite(script, file)
|
|
841
|
+
});
|
|
723
842
|
outputFiles.set(fileName, file);
|
|
724
843
|
}
|
|
725
844
|
}
|
|
@@ -736,7 +855,7 @@ function update(_id) {
|
|
|
736
855
|
const scriptFile = outputFiles.get(fileName);
|
|
737
856
|
if (scriptFile) {
|
|
738
857
|
debug$4("update: found file, calling write()");
|
|
739
|
-
scriptFile.file =
|
|
858
|
+
scriptFile.file = queueWrite({ id, type }, scriptFile);
|
|
740
859
|
updatedFiles.push(scriptFile);
|
|
741
860
|
outputFiles.set(fileName, scriptFile);
|
|
742
861
|
}
|
|
@@ -794,7 +913,7 @@ const pluginContentScripts = () => {
|
|
|
794
913
|
});
|
|
795
914
|
if (worldMainIds.size) {
|
|
796
915
|
const name = `[${pluginName}]`;
|
|
797
|
-
const message =
|
|
916
|
+
const message = pc.yellow(
|
|
798
917
|
[
|
|
799
918
|
`${name} Some content-scripts don't support HMR because the world is MAIN:`,
|
|
800
919
|
...[...worldMainIds].map((id) => ` ${id}`)
|
|
@@ -823,7 +942,7 @@ const pluginContentScripts = () => {
|
|
|
823
942
|
try {
|
|
824
943
|
const react = await import('@vitejs/plugin-react');
|
|
825
944
|
preambleCode = react.default.preambleCode;
|
|
826
|
-
} catch
|
|
945
|
+
} catch {
|
|
827
946
|
preambleCode = false;
|
|
828
947
|
}
|
|
829
948
|
}
|
|
@@ -898,41 +1017,50 @@ const pluginContentScripts = () => {
|
|
|
898
1017
|
};
|
|
899
1018
|
},
|
|
900
1019
|
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
|
-
}
|
|
1020
|
+
finalizeBuildContentScripts(this, bundle, worldMainIds);
|
|
932
1021
|
}
|
|
933
1022
|
}
|
|
934
1023
|
];
|
|
935
1024
|
};
|
|
1025
|
+
function finalizeBuildContentScripts(context, bundle, worldMainIds = /* @__PURE__ */ new Set()) {
|
|
1026
|
+
const processed = /* @__PURE__ */ new Set();
|
|
1027
|
+
for (const [key, script] of contentScripts) {
|
|
1028
|
+
if (key !== script.refId || processed.has(script))
|
|
1029
|
+
continue;
|
|
1030
|
+
processed.add(script);
|
|
1031
|
+
if (script.type === "module") {
|
|
1032
|
+
script.fileName = script.fileName ?? context.getFileName(script.refId);
|
|
1033
|
+
} else if (script.type === "loader") {
|
|
1034
|
+
const fileName = script.fileName ?? context.getFileName(script.refId);
|
|
1035
|
+
script.fileName = fileName;
|
|
1036
|
+
const bundleFileInfo = bundle[fileName];
|
|
1037
|
+
if (bundleFileInfo?.type !== "chunk")
|
|
1038
|
+
continue;
|
|
1039
|
+
const shouldUseLoader = !(bundleFileInfo.imports.length === 0 && bundleFileInfo.dynamicImports.length === 0 && bundleFileInfo.exports.length === 0);
|
|
1040
|
+
if (shouldUseLoader) {
|
|
1041
|
+
if (typeof script.loaderName === "undefined") {
|
|
1042
|
+
const refId = context.emitFile({
|
|
1043
|
+
type: "asset",
|
|
1044
|
+
name: getFileName({
|
|
1045
|
+
type: "loader",
|
|
1046
|
+
id: basename(script.id)
|
|
1047
|
+
}),
|
|
1048
|
+
source: worldMainIds.has(script.id) ? createProMainLoader({
|
|
1049
|
+
fileName: `./${fileName.split("/").at(-1)}`
|
|
1050
|
+
}) : createProLoader({ fileName })
|
|
1051
|
+
});
|
|
1052
|
+
script.loaderName = context.getFileName(refId);
|
|
1053
|
+
}
|
|
1054
|
+
} else if (typeof script.loaderName === "undefined" && !bundleFileInfo.code.startsWith("(function(){")) {
|
|
1055
|
+
bundleFileInfo.code = `(function(){${bundleFileInfo.code}})()
|
|
1056
|
+
`;
|
|
1057
|
+
}
|
|
1058
|
+
} else if (script.type === "iife") {
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
contentScripts.set(script.refId, formatFileData(script));
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
936
1064
|
|
|
937
1065
|
const pluginContentScriptsCss = () => {
|
|
938
1066
|
let injectCss;
|
|
@@ -1027,45 +1155,22 @@ const pluginContentScriptsIife = () => {
|
|
|
1027
1155
|
name: pluginName,
|
|
1028
1156
|
apply: "build",
|
|
1029
1157
|
enforce: "post",
|
|
1030
|
-
async generateBundle(
|
|
1158
|
+
async generateBundle() {
|
|
1031
1159
|
const opts = await getOptions({ plugins: config.plugins });
|
|
1032
1160
|
const _manifest = opts.manifest;
|
|
1033
1161
|
const manifest = typeof _manifest === "function" ? await _manifest({ command: "build", mode: config.mode }) : await Promise.resolve(_manifest);
|
|
1034
1162
|
const standaloneFiles = (opts.contentScripts?.standaloneFiles || []).map(
|
|
1035
|
-
|
|
1163
|
+
normalizeContentScriptPath
|
|
1164
|
+
);
|
|
1165
|
+
const iifeEntries = collectIifeEntries(
|
|
1166
|
+
manifest,
|
|
1167
|
+
standaloneFiles,
|
|
1168
|
+
config.root
|
|
1036
1169
|
);
|
|
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
1170
|
if (iifeEntries.length === 0)
|
|
1066
1171
|
return;
|
|
1067
1172
|
console.log(
|
|
1068
|
-
|
|
1173
|
+
pc.cyan(`
|
|
1069
1174
|
[${pluginName}] Building ${iifeEntries.length} content script(s) as IIFE...`)
|
|
1070
1175
|
);
|
|
1071
1176
|
for (const entry of iifeEntries) {
|
|
@@ -1073,52 +1178,106 @@ const pluginContentScriptsIife = () => {
|
|
|
1073
1178
|
try {
|
|
1074
1179
|
const iifeConfig = createIifeConfig(config, entry.id, outputFileName);
|
|
1075
1180
|
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
|
-
}
|
|
1181
|
+
const outputs = getBuildOutputs(result, entry.file);
|
|
1182
|
+
emitIifeOutputs(this, outputs, entry.file, outputFileName);
|
|
1183
|
+
registerIifeContentScript(entry, outputFileName);
|
|
1184
|
+
console.log(
|
|
1185
|
+
pc.green(` \u2713 ${basename(entry.file)} \u2192 ${outputFileName}`)
|
|
1186
|
+
);
|
|
1108
1187
|
} catch (error) {
|
|
1109
1188
|
console.error(
|
|
1110
|
-
|
|
1189
|
+
pc.red(` \u2717 Failed to build ${entry.file}:`),
|
|
1111
1190
|
error
|
|
1112
1191
|
);
|
|
1113
1192
|
throw error;
|
|
1114
1193
|
}
|
|
1115
1194
|
}
|
|
1116
|
-
console.log(
|
|
1195
|
+
console.log(pc.cyan(`[${pluginName}] IIFE build complete
|
|
1117
1196
|
`));
|
|
1118
1197
|
}
|
|
1119
1198
|
}
|
|
1120
1199
|
];
|
|
1121
1200
|
};
|
|
1201
|
+
function normalizeContentScriptPath(file) {
|
|
1202
|
+
return file.replace(/^\//, "");
|
|
1203
|
+
}
|
|
1204
|
+
function isStandaloneFile(file, standaloneFiles) {
|
|
1205
|
+
return standaloneFiles.includes(normalizeContentScriptPath(file));
|
|
1206
|
+
}
|
|
1207
|
+
function collectIifeEntries(manifest, standaloneFiles, root) {
|
|
1208
|
+
const entries = [];
|
|
1209
|
+
const entryIds = /* @__PURE__ */ new Set();
|
|
1210
|
+
const addEntry = (entry) => {
|
|
1211
|
+
if (entryIds.has(entry.id))
|
|
1212
|
+
return;
|
|
1213
|
+
entryIds.add(entry.id);
|
|
1214
|
+
entries.push(entry);
|
|
1215
|
+
};
|
|
1216
|
+
for (const { js = [], matches = [] } of manifest.content_scripts ?? []) {
|
|
1217
|
+
for (const file of js) {
|
|
1218
|
+
if (isIifeContentScript(file) || isStandaloneFile(file, standaloneFiles)) {
|
|
1219
|
+
addEntry({ file, id: join(root, file), matches });
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
for (const [, script] of contentScripts.entries()) {
|
|
1224
|
+
if (script.type !== "iife" || !script.isDynamicScript)
|
|
1225
|
+
continue;
|
|
1226
|
+
addEntry({
|
|
1227
|
+
file: script.id,
|
|
1228
|
+
id: join(root, script.id),
|
|
1229
|
+
matches: script.matches ?? []
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
return entries;
|
|
1233
|
+
}
|
|
1234
|
+
function getBuildOutputs(result, entryFile) {
|
|
1235
|
+
if ("on" in result) {
|
|
1236
|
+
throw new Error(`Unexpected watcher result for "${entryFile}"`);
|
|
1237
|
+
}
|
|
1238
|
+
return Array.isArray(result) ? result.flatMap((r) => r.output) : result.output;
|
|
1239
|
+
}
|
|
1240
|
+
function emitIifeOutputs(context, outputs, entryFile, outputFileName) {
|
|
1241
|
+
const entryChunk = outputs.find(
|
|
1242
|
+
(output) => output.type === "chunk" && output.isEntry
|
|
1243
|
+
);
|
|
1244
|
+
if (!entryChunk) {
|
|
1245
|
+
throw new Error(`Unable to generate IIFE bundle for "${entryFile}"`);
|
|
1246
|
+
}
|
|
1247
|
+
context.emitFile({
|
|
1248
|
+
type: "asset",
|
|
1249
|
+
fileName: outputFileName,
|
|
1250
|
+
source: entryChunk.code
|
|
1251
|
+
});
|
|
1252
|
+
for (const output of outputs) {
|
|
1253
|
+
if (output.type === "asset") {
|
|
1254
|
+
if (output.fileName !== outputFileName && output.fileName !== "manifest.json" && !output.fileName.startsWith(".vite/")) {
|
|
1255
|
+
context.emitFile({
|
|
1256
|
+
type: "asset",
|
|
1257
|
+
fileName: output.fileName,
|
|
1258
|
+
source: output.source
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
} else if (!output.isEntry) {
|
|
1262
|
+
context.emitFile({
|
|
1263
|
+
type: "asset",
|
|
1264
|
+
fileName: output.fileName,
|
|
1265
|
+
source: output.code
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
function registerIifeContentScript(entry, outputFileName) {
|
|
1271
|
+
const existingScript = contentScripts.get(entry.file);
|
|
1272
|
+
const script = existingScript ? { ...existingScript, fileName: outputFileName } : {
|
|
1273
|
+
type: "iife",
|
|
1274
|
+
id: entry.file,
|
|
1275
|
+
refId: entry.file,
|
|
1276
|
+
matches: entry.matches,
|
|
1277
|
+
fileName: outputFileName
|
|
1278
|
+
};
|
|
1279
|
+
contentScripts.set(entry.file, formatFileData(script));
|
|
1280
|
+
}
|
|
1122
1281
|
function getIifeOutputPath(file) {
|
|
1123
1282
|
const normalizedFile = file.replace(/^\//, "");
|
|
1124
1283
|
const dir = dirname(normalizedFile);
|
|
@@ -1188,6 +1347,36 @@ const pluginDynamicContentScripts = () => {
|
|
|
1188
1347
|
);
|
|
1189
1348
|
}).catch(() => void 0);
|
|
1190
1349
|
},
|
|
1350
|
+
buildStart() {
|
|
1351
|
+
if (config.command === "build") {
|
|
1352
|
+
const dynamicScripts = [];
|
|
1353
|
+
for (const [key, script] of contentScripts) {
|
|
1354
|
+
if (script.isDynamicScript && key === script.scriptId) {
|
|
1355
|
+
dynamicScripts.push(script);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
contentScripts.clear();
|
|
1359
|
+
for (const script of dynamicScripts) {
|
|
1360
|
+
const absoluteId = script.id.startsWith("/") ? `${config.root}${script.id}` : `${config.root}/${script.id}`;
|
|
1361
|
+
const refId = this.emitFile({
|
|
1362
|
+
type: "chunk",
|
|
1363
|
+
id: absoluteId,
|
|
1364
|
+
name: basename(script.id)
|
|
1365
|
+
});
|
|
1366
|
+
contentScripts.set(
|
|
1367
|
+
script.id,
|
|
1368
|
+
formatFileData({
|
|
1369
|
+
type: script.type,
|
|
1370
|
+
id: script.id,
|
|
1371
|
+
isDynamicScript: true,
|
|
1372
|
+
refId,
|
|
1373
|
+
scriptId: script.scriptId,
|
|
1374
|
+
matches: script.matches
|
|
1375
|
+
})
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
},
|
|
1191
1380
|
configureServer(server) {
|
|
1192
1381
|
return () => {
|
|
1193
1382
|
server.middlewares.use(async (req, res, next) => {
|
|
@@ -1249,7 +1438,10 @@ const pluginDynamicContentScripts = () => {
|
|
|
1249
1438
|
refId = this.emitFile({
|
|
1250
1439
|
type: "chunk",
|
|
1251
1440
|
id,
|
|
1252
|
-
name: basename(id)
|
|
1441
|
+
name: basename(id),
|
|
1442
|
+
// Preserve content script entry exports so the build finalizer
|
|
1443
|
+
// can decide whether the script needs a loader wrapper.
|
|
1444
|
+
preserveSignature: "exports-only"
|
|
1253
1445
|
});
|
|
1254
1446
|
} else {
|
|
1255
1447
|
refId = scriptId;
|
|
@@ -1283,9 +1475,29 @@ const pluginDynamicContentScripts = () => {
|
|
|
1283
1475
|
const index = id.indexOf("?scriptId=");
|
|
1284
1476
|
if (index > -1) {
|
|
1285
1477
|
const scriptId = id.slice(index + "?scriptId=".length);
|
|
1286
|
-
|
|
1478
|
+
let script = contentScripts.get(scriptId);
|
|
1479
|
+
if (!script && config.command === "build") {
|
|
1480
|
+
const fileId = id.slice(0, index);
|
|
1481
|
+
const refId = this.emitFile({
|
|
1482
|
+
type: "chunk",
|
|
1483
|
+
id: fileId,
|
|
1484
|
+
name: basename(fileId)
|
|
1485
|
+
});
|
|
1486
|
+
script = formatFileData({
|
|
1487
|
+
type: "loader",
|
|
1488
|
+
id: relative(config.root, fileId),
|
|
1489
|
+
isDynamicScript: true,
|
|
1490
|
+
refId,
|
|
1491
|
+
scriptId,
|
|
1492
|
+
matches: []
|
|
1493
|
+
});
|
|
1494
|
+
contentScripts.set(script.id, script);
|
|
1495
|
+
}
|
|
1496
|
+
if (!script) {
|
|
1497
|
+
throw new Error(`Content script not found for scriptId: "${scriptId}"`);
|
|
1498
|
+
}
|
|
1287
1499
|
if (config.command === "build") {
|
|
1288
|
-
return `export default import.meta.CRX_DYNAMIC_SCRIPT_${script.
|
|
1500
|
+
return `export default import.meta.CRX_DYNAMIC_SCRIPT_${script.scriptId};`;
|
|
1289
1501
|
} else if (typeof script.fileName === "string") {
|
|
1290
1502
|
return `export default ${JSON.stringify(script.fileName)};`;
|
|
1291
1503
|
} else {
|
|
@@ -1493,6 +1705,89 @@ const isCustomPayload = (p) => {
|
|
|
1493
1705
|
return p.type === "custom";
|
|
1494
1706
|
};
|
|
1495
1707
|
const hmrPayload$ = new Subject();
|
|
1708
|
+
function withTimestamp(id, timestamp) {
|
|
1709
|
+
if (id.includes("?t=") || id.includes("&t="))
|
|
1710
|
+
return id;
|
|
1711
|
+
const t = `t=${timestamp}` + (id.includes("?") ? "&" : "");
|
|
1712
|
+
const parts = id.split("?");
|
|
1713
|
+
parts[1] = typeof parts[1] === "undefined" ? t : t + parts[1];
|
|
1714
|
+
return parts.join("?");
|
|
1715
|
+
}
|
|
1716
|
+
function getUpdatePayloadFileIds(p, { timestamp = false } = {}) {
|
|
1717
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1718
|
+
for (const u of p.updates) {
|
|
1719
|
+
for (const id of [u.path, u.acceptedPath]) {
|
|
1720
|
+
const isVirtualModule = id.startsWith("/@id/") || id.startsWith("/__");
|
|
1721
|
+
const isQueryModule = id.includes("?");
|
|
1722
|
+
if (isVirtualModule || isQueryModule)
|
|
1723
|
+
ids.add(timestamp ? withTimestamp(id, u.timestamp) : id);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
return [...ids];
|
|
1727
|
+
}
|
|
1728
|
+
function mapVitePayloadForCrx(p) {
|
|
1729
|
+
switch (p.type) {
|
|
1730
|
+
case "full-reload": {
|
|
1731
|
+
const fullReload = {
|
|
1732
|
+
type: "full-reload",
|
|
1733
|
+
path: p.path && getViteUrl({ id: p.path, type: "module" })
|
|
1734
|
+
};
|
|
1735
|
+
return fullReload;
|
|
1736
|
+
}
|
|
1737
|
+
case "prune": {
|
|
1738
|
+
const prune = {
|
|
1739
|
+
type: "prune",
|
|
1740
|
+
paths: p.paths.map((id) => getViteUrl({ id, type: "module" }))
|
|
1741
|
+
};
|
|
1742
|
+
return prune;
|
|
1743
|
+
}
|
|
1744
|
+
case "update": {
|
|
1745
|
+
debug$3("update payload with %d updates", p.updates.length);
|
|
1746
|
+
for (const u of p.updates) {
|
|
1747
|
+
debug$3(
|
|
1748
|
+
"update item: path=%s acceptedPath=%s type=%s",
|
|
1749
|
+
u.path,
|
|
1750
|
+
u.acceptedPath,
|
|
1751
|
+
u.type
|
|
1752
|
+
);
|
|
1753
|
+
}
|
|
1754
|
+
const update_ = {
|
|
1755
|
+
type: "update",
|
|
1756
|
+
updates: p.updates.map(({ acceptedPath: ap, path: p2, ...rest }) => ({
|
|
1757
|
+
...rest,
|
|
1758
|
+
acceptedPath: prefix$1("/", getFileName({ id: ap, type: "module" })),
|
|
1759
|
+
path: prefix$1("/", getFileName({ id: p2, type: "module" }))
|
|
1760
|
+
}))
|
|
1761
|
+
};
|
|
1762
|
+
return update_;
|
|
1763
|
+
}
|
|
1764
|
+
default:
|
|
1765
|
+
return p;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
async function prepareVitePayloadForCrx(p) {
|
|
1769
|
+
if (p.type === "update") {
|
|
1770
|
+
const pendingFiles = getUpdatePayloadFileIds(p, {
|
|
1771
|
+
timestamp: true
|
|
1772
|
+
}).flatMap((id) => {
|
|
1773
|
+
debug$3("updating payload module: %s", id);
|
|
1774
|
+
return update(id).map((file) => file.file);
|
|
1775
|
+
});
|
|
1776
|
+
await Promise.all(pendingFiles);
|
|
1777
|
+
await allFilesReady();
|
|
1778
|
+
}
|
|
1779
|
+
return mapVitePayloadForCrx(p);
|
|
1780
|
+
}
|
|
1781
|
+
function shouldForwardCrxPayload(p) {
|
|
1782
|
+
switch (p.type) {
|
|
1783
|
+
case "prune":
|
|
1784
|
+
return p.paths.length > 0;
|
|
1785
|
+
case "update":
|
|
1786
|
+
return p.updates.length > 0;
|
|
1787
|
+
default:
|
|
1788
|
+
return true;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1496
1791
|
const crxHMRPayload$ = hmrPayload$.pipe(
|
|
1497
1792
|
filter((p) => !isCustomPayload(p)),
|
|
1498
1793
|
buffer(allFilesReady$),
|
|
@@ -1509,63 +1804,8 @@ const crxHMRPayload$ = hmrPayload$.pipe(
|
|
|
1509
1804
|
payloads.push(fullReload);
|
|
1510
1805
|
return payloads;
|
|
1511
1806
|
}),
|
|
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
|
-
}),
|
|
1807
|
+
mergeMap(prepareVitePayloadForCrx),
|
|
1808
|
+
filter(shouldForwardCrxPayload),
|
|
1569
1809
|
map((data) => {
|
|
1570
1810
|
debug$3(`hmr payload`, data);
|
|
1571
1811
|
return {
|
|
@@ -1597,6 +1837,23 @@ const crxRuntimeReload = {
|
|
|
1597
1837
|
type: "custom",
|
|
1598
1838
|
event: "crx:runtime-reload"
|
|
1599
1839
|
};
|
|
1840
|
+
function getChangedFilePath(root, file) {
|
|
1841
|
+
if (!file)
|
|
1842
|
+
return null;
|
|
1843
|
+
const normalizedRoot = normalize(root);
|
|
1844
|
+
const normalizedFile = normalize(file);
|
|
1845
|
+
const relativeFile = relative(normalizedRoot, normalizedFile);
|
|
1846
|
+
if (!relativeFile || relativeFile.startsWith("..") || isAbsolute(relativeFile)) {
|
|
1847
|
+
return null;
|
|
1848
|
+
}
|
|
1849
|
+
return prefix$1("/", relativeFile);
|
|
1850
|
+
}
|
|
1851
|
+
function stripTimestamp(id) {
|
|
1852
|
+
return id.replace(/([?&])t=\d+&/, "$1").replace(/[?&]t=\d+$/, "").replace(/\?$/, "");
|
|
1853
|
+
}
|
|
1854
|
+
function escapeRegExp(text) {
|
|
1855
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1856
|
+
}
|
|
1600
1857
|
const pluginHMR = () => {
|
|
1601
1858
|
let inputManifestFiles;
|
|
1602
1859
|
let decoratedSend;
|
|
@@ -1663,13 +1920,14 @@ const pluginHMR = () => {
|
|
|
1663
1920
|
// background changes require a full extension reload
|
|
1664
1921
|
handleHotUpdate({ file, modules, server }) {
|
|
1665
1922
|
const { root } = server.config;
|
|
1666
|
-
const changedFilePath =
|
|
1923
|
+
const changedFilePath = getChangedFilePath(root, file);
|
|
1667
1924
|
const relFiles = /* @__PURE__ */ new Set();
|
|
1668
1925
|
const fsFiles = /* @__PURE__ */ new Set();
|
|
1669
1926
|
const virtualModules = /* @__PURE__ */ new Set();
|
|
1670
1927
|
for (const m of modules) {
|
|
1671
|
-
|
|
1672
|
-
|
|
1928
|
+
const relFile = getChangedFilePath(root, m.id);
|
|
1929
|
+
if (relFile) {
|
|
1930
|
+
relFiles.add(relFile);
|
|
1673
1931
|
} else if (m.url?.startsWith("/@fs")) {
|
|
1674
1932
|
fsFiles.add(m.url);
|
|
1675
1933
|
} else if (m.id?.startsWith("\0") || m.url?.startsWith("/@id/__x00__")) {
|
|
@@ -1741,13 +1999,14 @@ const pluginHMR = () => {
|
|
|
1741
1999
|
},
|
|
1742
2000
|
renderCrxDevScript(code, { id: _id, type }) {
|
|
1743
2001
|
if (type === "module" && _id !== "/@vite/client" && code.includes("createHotContext")) {
|
|
1744
|
-
const id = _id
|
|
1745
|
-
const escaped = id
|
|
2002
|
+
const id = stripTimestamp(_id);
|
|
2003
|
+
const escaped = escapeRegExp(id);
|
|
1746
2004
|
const regexp = new RegExp(
|
|
1747
|
-
`(
|
|
2005
|
+
`(createHotContext\\(")${escaped}("\\))`,
|
|
2006
|
+
"g"
|
|
1748
2007
|
);
|
|
1749
2008
|
const fileUrl = prefix$1("/", getFileName({ id, type }));
|
|
1750
|
-
const replaced = code.replace(regexp, fileUrl);
|
|
2009
|
+
const replaced = code.replace(regexp, `$1${fileUrl}$2`);
|
|
1751
2010
|
return replaced;
|
|
1752
2011
|
} else {
|
|
1753
2012
|
return code;
|
|
@@ -1758,11 +2017,11 @@ const pluginHMR = () => {
|
|
|
1758
2017
|
};
|
|
1759
2018
|
|
|
1760
2019
|
function printStr(dir) {
|
|
1761
|
-
return ` ${
|
|
1762
|
-
${
|
|
1763
|
-
${
|
|
2020
|
+
return ` ${pc.magentaBright("B R O W S E R")}
|
|
2021
|
+
${pc.greenBright("E X T E N S I O N")}
|
|
2022
|
+
${pc.blueBright("T O O L S")}
|
|
1764
2023
|
|
|
1765
|
-
${
|
|
2024
|
+
${pc.green("\u279C")} ${pc.bold("CRXJS")}: ${pc.green(`Load ${pc.cyan(dir)} as unpacked extension`)}`;
|
|
1766
2025
|
}
|
|
1767
2026
|
const pluginPrint = () => {
|
|
1768
2027
|
let outDir = "dist";
|
|
@@ -1923,14 +2182,50 @@ const pluginHtmlInlineScripts = () => {
|
|
|
1923
2182
|
};
|
|
1924
2183
|
};
|
|
1925
2184
|
|
|
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=\"${
|
|
2185
|
+
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
2186
|
|
|
1928
2187
|
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
2188
|
|
|
1930
2189
|
const { readFile } = promises;
|
|
2190
|
+
const loadingPageReadyPath = "/@crx/dev-ready";
|
|
2191
|
+
function normalizeHtmlPath(pathname) {
|
|
2192
|
+
let decoded;
|
|
2193
|
+
try {
|
|
2194
|
+
decoded = decodeURIComponent(pathname);
|
|
2195
|
+
} catch {
|
|
2196
|
+
return null;
|
|
2197
|
+
}
|
|
2198
|
+
const normalized = normalize(decoded.replace(/^\/+/, "") || "index.html");
|
|
2199
|
+
if (normalized.startsWith("..") || isAbsolute(normalized))
|
|
2200
|
+
return null;
|
|
2201
|
+
return normalized;
|
|
2202
|
+
}
|
|
2203
|
+
function getLoadingPageReadyHtmlPath(requestUrl) {
|
|
2204
|
+
if (!requestUrl)
|
|
2205
|
+
return null;
|
|
2206
|
+
let url;
|
|
2207
|
+
try {
|
|
2208
|
+
url = new URL(requestUrl, "http://crxjs.local");
|
|
2209
|
+
} catch {
|
|
2210
|
+
return null;
|
|
2211
|
+
}
|
|
2212
|
+
if (url.pathname !== loadingPageReadyPath)
|
|
2213
|
+
return void 0;
|
|
2214
|
+
const path = url.searchParams.get("path");
|
|
2215
|
+
if (!path)
|
|
2216
|
+
return null;
|
|
2217
|
+
let pageUrl;
|
|
2218
|
+
try {
|
|
2219
|
+
pageUrl = new URL(path, "http://crxjs.local");
|
|
2220
|
+
} catch {
|
|
2221
|
+
return null;
|
|
2222
|
+
}
|
|
2223
|
+
return normalizeHtmlPath(pageUrl.pathname);
|
|
2224
|
+
}
|
|
1931
2225
|
const pluginManifest = () => {
|
|
1932
2226
|
let manifest;
|
|
1933
2227
|
let plugins = [];
|
|
2228
|
+
let devHtmlFiles = /* @__PURE__ */ new Set();
|
|
1934
2229
|
let refId;
|
|
1935
2230
|
let config;
|
|
1936
2231
|
return [
|
|
@@ -1950,6 +2245,7 @@ const pluginManifest = () => {
|
|
|
1950
2245
|
background: sw,
|
|
1951
2246
|
html
|
|
1952
2247
|
} = await manifestFiles(manifest, { cwd: config2.root });
|
|
2248
|
+
devHtmlFiles = new Set(html.map(normalizeHtmlPath).filter(isString));
|
|
1953
2249
|
const { entries = [] } = config2.optimizeDeps ?? {};
|
|
1954
2250
|
let { input = [] } = config2.build?.rollupOptions ?? {};
|
|
1955
2251
|
if (typeof input === "string")
|
|
@@ -1983,6 +2279,25 @@ const pluginManifest = () => {
|
|
|
1983
2279
|
plugins = resolvedConfig.plugins;
|
|
1984
2280
|
}
|
|
1985
2281
|
},
|
|
2282
|
+
configureServer(server) {
|
|
2283
|
+
server.middlewares.use((req, res, next) => {
|
|
2284
|
+
const htmlPath = getLoadingPageReadyHtmlPath(req.url);
|
|
2285
|
+
if (typeof htmlPath === "undefined") {
|
|
2286
|
+
next();
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
2290
|
+
res.setHeader("Access-Control-Allow-Methods", "GET");
|
|
2291
|
+
if (!htmlPath) {
|
|
2292
|
+
res.statusCode = 400;
|
|
2293
|
+
res.end();
|
|
2294
|
+
return;
|
|
2295
|
+
}
|
|
2296
|
+
const exists = devHtmlFiles.has(htmlPath) && (existsSync(join(server.config.root, htmlPath)) || existsSync(join(server.config.publicDir, htmlPath)));
|
|
2297
|
+
res.statusCode = exists ? 204 : 404;
|
|
2298
|
+
res.end();
|
|
2299
|
+
});
|
|
2300
|
+
},
|
|
1986
2301
|
buildStart(options) {
|
|
1987
2302
|
if (options.plugins)
|
|
1988
2303
|
plugins = options.plugins;
|
|
@@ -2136,7 +2451,10 @@ const pluginManifest = () => {
|
|
|
2136
2451
|
const refId2 = this.emitFile({
|
|
2137
2452
|
type: "chunk",
|
|
2138
2453
|
id: id2,
|
|
2139
|
-
name: basename(file)
|
|
2454
|
+
name: basename(file),
|
|
2455
|
+
// Preserve content script entry exports so the build finalizer
|
|
2456
|
+
// can decide whether the script needs a loader wrapper.
|
|
2457
|
+
preserveSignature: "exports-only"
|
|
2140
2458
|
});
|
|
2141
2459
|
contentScripts.set(
|
|
2142
2460
|
file,
|
|
@@ -2206,6 +2524,7 @@ const pluginManifest = () => {
|
|
|
2206
2524
|
}
|
|
2207
2525
|
}
|
|
2208
2526
|
} else {
|
|
2527
|
+
finalizeBuildContentScripts(this, bundle);
|
|
2209
2528
|
if (manifest2.background && "service_worker" in manifest2.background) {
|
|
2210
2529
|
const ref = manifest2.background.service_worker;
|
|
2211
2530
|
const name = this.getFileName(ref);
|
|
@@ -2220,7 +2539,7 @@ const pluginManifest = () => {
|
|
|
2220
2539
|
({ js = [], ...rest }) => {
|
|
2221
2540
|
return {
|
|
2222
2541
|
js: js.map((id) => {
|
|
2223
|
-
const script = contentScripts.get(id);
|
|
2542
|
+
const script = contentScripts.get(id) ?? contentScripts.get(prefix$1("/", id));
|
|
2224
2543
|
const fileName = script?.loaderName ?? script?.fileName;
|
|
2225
2544
|
if (typeof fileName === "undefined")
|
|
2226
2545
|
throw new Error(
|
|
@@ -2242,11 +2561,11 @@ const pluginManifest = () => {
|
|
|
2242
2561
|
const name = `[${plugin.name}]`;
|
|
2243
2562
|
let message = error;
|
|
2244
2563
|
if (error instanceof Error) {
|
|
2245
|
-
message =
|
|
2564
|
+
message = pc.red(
|
|
2246
2565
|
`${name} ${error.stack ? error.stack : error.message}`
|
|
2247
2566
|
);
|
|
2248
2567
|
} else if (typeof error === "string") {
|
|
2249
|
-
message =
|
|
2568
|
+
message = pc.red(`${name} ${error}`);
|
|
2250
2569
|
}
|
|
2251
2570
|
console.log(message);
|
|
2252
2571
|
throw new Error(`Error in ${plugin.name}.renderCrxManifest`);
|
|
@@ -2260,6 +2579,11 @@ const pluginManifest = () => {
|
|
|
2260
2579
|
"webAccessibleResources"
|
|
2261
2580
|
];
|
|
2262
2581
|
const files = await manifestFiles(manifest2, { cwd: config.root });
|
|
2582
|
+
if (config.command === "serve") {
|
|
2583
|
+
devHtmlFiles = new Set(
|
|
2584
|
+
files.html.map(normalizeHtmlPath).filter(isString)
|
|
2585
|
+
);
|
|
2586
|
+
}
|
|
2263
2587
|
await Promise.all(
|
|
2264
2588
|
assetTypes.map((k) => files[k]).flat().map(async (f) => {
|
|
2265
2589
|
if (typeof bundle[f] === "undefined") {
|
|
@@ -2291,7 +2615,7 @@ Public dir: "${config.publicDir}"`
|
|
|
2291
2615
|
const refId2 = this.emitFile({
|
|
2292
2616
|
type: "asset",
|
|
2293
2617
|
name: "loading-page.js",
|
|
2294
|
-
source: loadingPageScript.replace("%PROTO%", config.server.https ? "https" : "http").replace("%PORT%", `${config.server.port ?? 0}`)
|
|
2618
|
+
source: loadingPageScript.replace("%PROTO%", config.server.https ? "https" : "http").replace("%PORT%", `${config.server.port ?? 0}`).replace("%READY_PATH%", loadingPageReadyPath)
|
|
2295
2619
|
});
|
|
2296
2620
|
const loadingPageScriptName = this.getFileName(refId2);
|
|
2297
2621
|
files.html.map(
|
|
@@ -2585,6 +2909,7 @@ const crx = (options) => {
|
|
|
2585
2909
|
contentScripts.clear();
|
|
2586
2910
|
return [
|
|
2587
2911
|
pluginOptionsProvider(options),
|
|
2912
|
+
pluginExtensionCors(),
|
|
2588
2913
|
pluginContentScriptsIife(),
|
|
2589
2914
|
// Must come early so isIifeModeEnabled is set before manifest plugin
|
|
2590
2915
|
pluginBackground(),
|