wxt 0.0.2 → 0.1.1-alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8,25 +8,27 @@ import { consola } from "consola";
8
8
  import createJITI from "jiti";
9
9
  import transform from "jiti/dist/babel";
10
10
  import { resolve } from "path";
11
- import { scanExports } from "unimport";
11
+ import { createUnimport } from "unimport";
12
+ import fs from "fs-extra";
12
13
  async function importTsFile(root, path5) {
13
- const clientImports = await scanExports(
14
+ const unimport2 = createUnimport({});
15
+ await unimport2.scanImportsFromFile(
14
16
  resolve(root, "node_modules/wxt/dist/client.js")
15
17
  );
18
+ const text = await fs.readFile(path5, "utf-8");
19
+ const res = await unimport2.injectImports(text, path5);
20
+ const transformedText = res.code;
16
21
  const jiti = createJITI(__filename, {
17
22
  cache: false,
18
23
  esmResolve: true,
19
24
  interopDefault: true,
25
+ alias: {
26
+ "webextension-polyfill": "wxt"
27
+ },
20
28
  transform(opts) {
29
+ if (opts.filename === path5)
30
+ opts.source = transformedText;
21
31
  opts.source = opts.source.replace(/^import ['"].*\.css['"];?$/gm, "");
22
- opts.source = opts.source.replace(
23
- /^import\s+.*\s+from ['"]webextension-polyfill['"];?$/gm,
24
- ""
25
- );
26
- if (opts.filename === path5) {
27
- const imports = clientImports.map((i) => `import { ${i.name} } from "${i.from}";`).join("\n") + "\n";
28
- opts.source = imports + opts.source;
29
- }
30
32
  return transform(opts);
31
33
  }
32
34
  });
@@ -209,7 +211,7 @@ function download(config) {
209
211
 
210
212
  // src/core/vite-plugins/multipageMove.ts
211
213
  import { dirname as dirname2, extname, resolve as resolve4 } from "node:path";
212
- import fs, { ensureDir } from "fs-extra";
214
+ import fs2, { ensureDir } from "fs-extra";
213
215
  function multipageMove(entrypoints, config) {
214
216
  return {
215
217
  name: "wxt:multipage-move",
@@ -237,7 +239,7 @@ function multipageMove(entrypoints, config) {
237
239
  const oldAbsPath = resolve4(config.outDir, oldBundlePath);
238
240
  const newAbsPath = resolve4(config.outDir, newBundlePath);
239
241
  await ensureDir(dirname2(newAbsPath));
240
- await fs.move(oldAbsPath, newAbsPath, { overwrite: true });
242
+ await fs2.move(oldAbsPath, newAbsPath, { overwrite: true });
241
243
  const renamedChunk = {
242
244
  ...bundle[oldBundlePath],
243
245
  fileName: newBundlePath
@@ -250,7 +252,7 @@ function multipageMove(entrypoints, config) {
250
252
  }
251
253
 
252
254
  // src/core/vite-plugins/unimport.ts
253
- import { createUnimport } from "unimport";
255
+ import { createUnimport as createUnimport2 } from "unimport";
254
256
 
255
257
  // src/core/utils/auto-imports.ts
256
258
  import { mergeConfig as mergeConfig2 } from "vite";
@@ -274,7 +276,7 @@ function getUnimportOptions(config) {
274
276
  // src/core/vite-plugins/unimport.ts
275
277
  function unimport(config) {
276
278
  const options = getUnimportOptions(config);
277
- const unimport2 = createUnimport(options);
279
+ const unimport2 = createUnimport2(options);
278
280
  return {
279
281
  name: "wxt:unimport",
280
282
  async config() {
@@ -287,7 +289,7 @@ function unimport(config) {
287
289
  }
288
290
 
289
291
  // src/core/vite-plugins/virtualEntrypoint.ts
290
- import fs2 from "fs-extra";
292
+ import fs3 from "fs-extra";
291
293
  import { resolve as resolve5 } from "path";
292
294
  function virtualEntrypoin(type, config) {
293
295
  const virtualId = `virtual:wxt-${type}?`;
@@ -305,7 +307,7 @@ function virtualEntrypoin(type, config) {
305
307
  if (!id.startsWith(resolvedVirtualId))
306
308
  return;
307
309
  const inputPath = id.replace(resolvedVirtualId, "");
308
- const template = await fs2.readFile(
310
+ const template = await fs3.readFile(
309
311
  resolve5(
310
312
  config.root,
311
313
  `node_modules/wxt/dist/virtual-modules/${type}-entrypoint.js`
@@ -318,7 +320,7 @@ function virtualEntrypoin(type, config) {
318
320
  }
319
321
 
320
322
  // src/core/utils/createFsCache.ts
321
- import fs3, { ensureDir as ensureDir2 } from "fs-extra";
323
+ import fs4, { ensureDir as ensureDir2 } from "fs-extra";
322
324
  import { dirname as dirname3, resolve as resolve6 } from "path";
323
325
  function createFsCache(wxtDir) {
324
326
  const getPath = (key) => resolve6(wxtDir, "cache", encodeURIComponent(key));
@@ -326,12 +328,12 @@ function createFsCache(wxtDir) {
326
328
  async set(key, value) {
327
329
  const path5 = getPath(key);
328
330
  await ensureDir2(dirname3(path5));
329
- await fs3.writeFile(path5, value, "utf-8");
331
+ await fs4.writeFile(path5, value, "utf-8");
330
332
  },
331
333
  async get(key) {
332
334
  const path5 = getPath(key);
333
335
  try {
334
- return await fs3.readFile(path5, "utf-8");
336
+ return await fs4.readFile(path5, "utf-8");
335
337
  } catch {
336
338
  return void 0;
337
339
  }
@@ -470,15 +472,246 @@ async function getInternalConfig(config, command) {
470
472
  return finalConfig;
471
473
  }
472
474
 
475
+ // src/index.ts
476
+ import pc3 from "picocolors";
477
+ import * as vite6 from "vite";
478
+
479
+ // src/core/utils/arrays.ts
480
+ function every(array, predicate) {
481
+ for (let i = 0; i < array.length; i++)
482
+ if (!predicate(array[i], i))
483
+ return false;
484
+ return true;
485
+ }
486
+
487
+ // src/core/utils/detectDevChanges.ts
488
+ function detectDevChanges(changedFiles, currentOutput) {
489
+ if (currentOutput == null)
490
+ return { type: "no-change" };
491
+ const changedSteps = new Set(
492
+ changedFiles.flatMap(
493
+ (changedFile) => findEffectedSteps(changedFile, currentOutput)
494
+ )
495
+ );
496
+ if (changedSteps.size === 0)
497
+ return { type: "no-change" };
498
+ const unchangedOutput = {
499
+ manifest: currentOutput.manifest,
500
+ steps: [],
501
+ publicAssets: []
502
+ };
503
+ const changedOutput = {
504
+ manifest: currentOutput.manifest,
505
+ steps: [],
506
+ publicAssets: []
507
+ };
508
+ for (const step of currentOutput.steps) {
509
+ if (changedSteps.has(step)) {
510
+ changedOutput.steps.push(step);
511
+ } else {
512
+ unchangedOutput.steps.push(step);
513
+ }
514
+ }
515
+ for (const asset of currentOutput.publicAssets) {
516
+ if (changedSteps.has(asset)) {
517
+ changedOutput.publicAssets.push(asset);
518
+ } else {
519
+ unchangedOutput.publicAssets.push(asset);
520
+ }
521
+ }
522
+ const isOnlyHtmlChanges = changedFiles.length > 0 && every(changedFiles, ([_, file]) => file.endsWith(".html"));
523
+ if (isOnlyHtmlChanges) {
524
+ return {
525
+ type: "html-reload",
526
+ cachedOutput: unchangedOutput,
527
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
528
+ };
529
+ }
530
+ const isOnlyContentScripts = changedOutput.steps.length > 0 && every(
531
+ changedOutput.steps.flatMap((step) => step.entrypoints),
532
+ (entry) => entry.type === "content-script"
533
+ );
534
+ if (isOnlyContentScripts) {
535
+ return {
536
+ type: "content-script-reload",
537
+ cachedOutput: unchangedOutput,
538
+ changedSteps: changedOutput.steps,
539
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
540
+ };
541
+ }
542
+ return {
543
+ type: "extension-reload",
544
+ cachedOutput: unchangedOutput,
545
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
546
+ };
547
+ }
548
+ function findEffectedSteps(changedFile, currentOutput) {
549
+ const changes = [];
550
+ const changedPath = changedFile[1];
551
+ const isChunkEffected = (chunk) => (
552
+ // If it's an HTML file with the same path, is is effected because HTML files need to be pre-rendered
553
+ // TODO: use bundle path to support `<name>/index.html`?
554
+ chunk.type === "asset" && changedPath.endsWith(chunk.fileName) || // If it's a chunk that depends on the changed file, it is effected
555
+ chunk.type === "chunk" && chunk.moduleIds.includes(changedPath)
556
+ );
557
+ for (const step of currentOutput.steps) {
558
+ const effectedChunk = step.chunks.find((chunk) => isChunkEffected(chunk));
559
+ if (effectedChunk)
560
+ changes.push(step);
561
+ }
562
+ const effectedAsset = currentOutput.publicAssets.find(
563
+ (chunk) => isChunkEffected(chunk)
564
+ );
565
+ if (effectedAsset)
566
+ changes.push(effectedAsset);
567
+ return changes;
568
+ }
569
+
570
+ // src/index.ts
571
+ import { Mutex } from "async-mutex";
572
+ import { consola as consola3 } from "consola";
573
+ import { relative as relative6 } from "node:path";
574
+
575
+ // src/core/build/buildEntrypoints.ts
576
+ import * as vite3 from "vite";
577
+
578
+ // src/core/utils/removeEmptyDirs.ts
579
+ import fs5 from "fs-extra";
580
+ import path3 from "path";
581
+ async function removeEmptyDirs(dir) {
582
+ const files = await fs5.readdir(dir);
583
+ for (const file of files) {
584
+ const filePath = path3.join(dir, file);
585
+ const stats = await fs5.stat(filePath);
586
+ if (stats.isDirectory()) {
587
+ await removeEmptyDirs(filePath);
588
+ }
589
+ }
590
+ try {
591
+ await fs5.rmdir(dir);
592
+ } catch {
593
+ }
594
+ }
595
+
596
+ // src/core/build/buildEntrypoints.ts
597
+ import glob from "fast-glob";
598
+ import fs6 from "fs-extra";
599
+ import { dirname as dirname4, resolve as resolve8 } from "path";
600
+ async function buildEntrypoints(groups, config) {
601
+ const steps = [];
602
+ for (const group of groups) {
603
+ const step = Array.isArray(group) ? await buildMultipleEntrypoints(group, config) : await buildSingleEntrypoint(group, config);
604
+ steps.push(step);
605
+ }
606
+ const publicAssets = await copyPublicDirectory(config);
607
+ await removeEmptyDirs(config.outDir);
608
+ return { publicAssets, steps };
609
+ }
610
+ async function buildSingleEntrypoint(entrypoint, config) {
611
+ const isVirtual = ["background", "content-script"].includes(entrypoint.type);
612
+ const entry = isVirtual ? `virtual:wxt-${entrypoint.type}?${entrypoint.inputPath}` : entrypoint.inputPath;
613
+ const libMode = {
614
+ build: {
615
+ lib: {
616
+ entry,
617
+ formats: ["iife"],
618
+ name: entrypoint.name,
619
+ fileName: entrypoint.name
620
+ },
621
+ rollupOptions: {
622
+ output: {
623
+ // There's only a single output for this build, so we use the desired bundle path for the
624
+ // entry output (like "content-scripts/overlay.js")
625
+ entryFileNames: getEntrypointBundlePath(
626
+ entrypoint,
627
+ config.outDir,
628
+ ".js"
629
+ ),
630
+ // Output content script CSS to assets/ with a hash to prevent conflicts. Defaults to
631
+ // "[name].[ext]" in lib mode, which usually results in "style.css". That means multiple
632
+ // content scripts with styles would overwrite each other if it weren't changed below.
633
+ assetFileNames: `assets/${entrypoint.name}.[ext]`
634
+ }
635
+ }
636
+ }
637
+ };
638
+ const entryConfig = vite3.mergeConfig(
639
+ libMode,
640
+ config.vite
641
+ );
642
+ const result = await vite3.build(entryConfig);
643
+ return {
644
+ entrypoints: entrypoint,
645
+ chunks: getBuildOutputChunks(result)
646
+ };
647
+ }
648
+ async function buildMultipleEntrypoints(entrypoints, config) {
649
+ const multiPage = {
650
+ plugins: [multipageMove(entrypoints, config)],
651
+ build: {
652
+ rollupOptions: {
653
+ input: entrypoints.reduce((input, entry) => {
654
+ input[entry.name] = entry.inputPath;
655
+ return input;
656
+ }, {}),
657
+ output: {
658
+ // Include a hash to prevent conflicts
659
+ chunkFileNames: "chunks/[name]-[hash].js",
660
+ // Include a hash to prevent conflicts
661
+ entryFileNames: "chunks/[name]-[hash].js",
662
+ // We can't control the "name", so we need a hash to prevent conflicts
663
+ assetFileNames: "assets/[name]-[hash].[ext]"
664
+ }
665
+ }
666
+ }
667
+ };
668
+ const entryConfig = vite3.mergeConfig(
669
+ multiPage,
670
+ config.vite
671
+ );
672
+ const result = await vite3.build(entryConfig);
673
+ return {
674
+ entrypoints,
675
+ chunks: getBuildOutputChunks(result)
676
+ };
677
+ }
678
+ function getBuildOutputChunks(result) {
679
+ if ("on" in result)
680
+ throw Error("wxt does not support vite watch mode.");
681
+ if (Array.isArray(result))
682
+ return result.flatMap(({ output }) => output);
683
+ return result.output;
684
+ }
685
+ async function copyPublicDirectory(config) {
686
+ const publicAssets = [];
687
+ if (!await fs6.exists(config.publicDir))
688
+ return publicAssets;
689
+ const files = await glob("**/*", { cwd: config.publicDir });
690
+ for (const file of files) {
691
+ const srcPath = resolve8(config.publicDir, file);
692
+ const outPath = resolve8(config.outDir, file);
693
+ await fs6.ensureDir(dirname4(outPath));
694
+ await fs6.copyFile(srcPath, outPath);
695
+ publicAssets.push({
696
+ type: "asset",
697
+ fileName: file,
698
+ name: file,
699
+ needsCodeReference: false,
700
+ source: await fs6.readFile(srcPath)
701
+ });
702
+ }
703
+ return publicAssets;
704
+ }
705
+
473
706
  // src/core/build/findEntrypoints.ts
474
- import { relative as relative3, resolve as resolve8 } from "path";
475
- import fs4 from "fs-extra";
707
+ import { relative as relative3, resolve as resolve9 } from "path";
708
+ import fs7 from "fs-extra";
476
709
  import picomatch from "picomatch";
477
710
  import { parseHTML as parseHTML2 } from "linkedom";
478
711
  import JSON5 from "json5";
479
- import glob from "fast-glob";
712
+ import glob2 from "fast-glob";
480
713
  async function findEntrypoints(config) {
481
- const relativePaths = await glob("**/*", {
714
+ const relativePaths = await glob2("**/*", {
482
715
  cwd: config.entrypointsDir
483
716
  });
484
717
  relativePaths.sort();
@@ -487,7 +720,7 @@ async function findEntrypoints(config) {
487
720
  const entrypoints = [];
488
721
  await Promise.all(
489
722
  relativePaths.map(async (relativePath) => {
490
- const path5 = resolve8(config.entrypointsDir, relativePath);
723
+ const path5 = resolve9(config.entrypointsDir, relativePath);
491
724
  const matchingGlob = pathGlobs.find(
492
725
  (glob3) => picomatch.isMatch(relativePath, glob3)
493
726
  );
@@ -547,7 +780,7 @@ ${JSON.stringify(
547
780
  }
548
781
  async function getPopupEntrypoint(config, path5) {
549
782
  const options = {};
550
- const content = await fs4.readFile(path5, "utf-8");
783
+ const content = await fs7.readFile(path5, "utf-8");
551
784
  const { document } = parseHTML2(content);
552
785
  const title = document.querySelector("title");
553
786
  if (title != null)
@@ -577,7 +810,7 @@ async function getPopupEntrypoint(config, path5) {
577
810
  }
578
811
  async function getOptionsEntrypoint(config, path5) {
579
812
  const options = {};
580
- const content = await fs4.readFile(path5, "utf-8");
813
+ const content = await fs7.readFile(path5, "utf-8");
581
814
  const { document } = parseHTML2(content);
582
815
  const openInTabContent = document.querySelector("meta[name='manifest.open_in_tab']")?.getAttribute("content");
583
816
  if (openInTabContent) {
@@ -624,7 +857,7 @@ async function getContentScriptEntrypoint(config, name, path5) {
624
857
  type: "content-script",
625
858
  name: getEntrypointName(config.entrypointsDir, path5),
626
859
  inputPath: path5,
627
- outputDir: resolve8(config.outDir, "content-scripts"),
860
+ outputDir: resolve9(config.outDir, "content-scripts"),
628
861
  options
629
862
  };
630
863
  }
@@ -659,140 +892,112 @@ var PATH_GLOB_TO_TYPE_MAP = {
659
892
  "*/*": "ignored"
660
893
  };
661
894
 
662
- // src/core/build/buildEntrypoints.ts
663
- import * as vite3 from "vite";
664
-
665
- // src/core/utils/removeEmptyDirs.ts
666
- import fs5 from "fs-extra";
667
- import path3 from "path";
668
- async function removeEmptyDirs(dir) {
669
- const files = await fs5.readdir(dir);
670
- for (const file of files) {
671
- const filePath = path3.join(dir, file);
672
- const stats = await fs5.stat(filePath);
673
- if (stats.isDirectory()) {
674
- await removeEmptyDirs(filePath);
675
- }
676
- }
677
- try {
678
- await fs5.rmdir(dir);
679
- } catch {
680
- }
895
+ // src/core/build/generateTypesDir.ts
896
+ import { createUnimport as createUnimport3 } from "unimport";
897
+ import fs8 from "fs-extra";
898
+ import { relative as relative4, resolve as resolve10 } from "path";
899
+ async function generateTypesDir(entrypoints, config) {
900
+ await fs8.ensureDir(config.typesDir);
901
+ const references = [];
902
+ references.push(await writeImportsDeclarationFile(config));
903
+ references.push(await writePathsDeclarationFile(entrypoints, config));
904
+ references.push(await writeGlobalsDeclarationFile(config));
905
+ const mainReference = await writeMainDeclarationFile(references, config);
906
+ await writeTsConfigFile(mainReference, config);
681
907
  }
682
-
683
- // src/core/build/buildEntrypoints.ts
684
- import glob2 from "fast-glob";
685
- import fs6 from "fs-extra";
686
- import { dirname as dirname4, resolve as resolve9 } from "path";
687
- async function buildEntrypoints(groups, config) {
688
- const steps = [];
689
- for (const group of groups) {
690
- const step = Array.isArray(group) ? await buildMultipleEntrypoints(group, config) : await buildSingleEntrypoint(group, config);
691
- steps.push(step);
692
- }
693
- const publicAssets = await copyPublicDirectory(config);
694
- await removeEmptyDirs(config.outDir);
695
- return { publicAssets, steps };
908
+ async function writeImportsDeclarationFile(config) {
909
+ const filePath = resolve10(config.typesDir, "imports.d.ts");
910
+ const unimport2 = createUnimport3(getUnimportOptions(config));
911
+ await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
912
+ await fs8.writeFile(
913
+ filePath,
914
+ ["// Generated by wxt", await unimport2.generateTypeDeclarations()].join(
915
+ "\n"
916
+ ) + "\n"
917
+ );
918
+ return filePath;
696
919
  }
697
- async function buildSingleEntrypoint(entrypoint, config) {
698
- const isVirtual = ["background", "content-script"].includes(entrypoint.type);
699
- const entry = isVirtual ? `virtual:wxt-${entrypoint.type}?${entrypoint.inputPath}` : entrypoint.inputPath;
700
- const libMode = {
701
- build: {
702
- lib: {
703
- entry,
704
- formats: ["iife"],
705
- name: entrypoint.name,
706
- fileName: entrypoint.name
707
- },
708
- rollupOptions: {
709
- output: {
710
- // There's only a single output for this build, so we use the desired bundle path for the
711
- // entry output (like "content-scripts/overlay.js")
712
- entryFileNames: getEntrypointBundlePath(
713
- entrypoint,
714
- config.outDir,
715
- ".js"
716
- ),
717
- // Output content script CSS to assets/ with a hash to prevent conflicts. Defaults to
718
- // "[name].[ext]" in lib mode, which usually results in "style.css". That means multiple
719
- // content scripts with styles would overwrite each other if it weren't changed below.
720
- assetFileNames: `assets/${entrypoint.name}.[ext]`
721
- }
722
- }
723
- }
724
- };
725
- const entryConfig = vite3.mergeConfig(
726
- libMode,
727
- config.vite
920
+ async function writePathsDeclarationFile(entrypoints, config) {
921
+ const filePath = resolve10(config.typesDir, "paths.d.ts");
922
+ await fs8.writeFile(
923
+ filePath,
924
+ [
925
+ "// Generated by wxt",
926
+ "type EntrypointPath =",
927
+ ...entrypoints.map((entry) => {
928
+ const path5 = getEntrypointBundlePath(
929
+ entry,
930
+ config.outDir,
931
+ entry.inputPath.endsWith(".html") ? ".html" : ".js"
932
+ );
933
+ return ` | "/${path5}"`;
934
+ }).sort()
935
+ ].join("\n") + "\n"
728
936
  );
729
- const result = await vite3.build(entryConfig);
730
- return {
731
- entrypoints: entrypoint,
732
- chunks: getBuildOutputChunks(result)
733
- };
937
+ return filePath;
734
938
  }
735
- async function buildMultipleEntrypoints(entrypoints, config) {
736
- const multiPage = {
737
- plugins: [multipageMove(entrypoints, config)],
738
- build: {
739
- rollupOptions: {
740
- input: entrypoints.reduce((input, entry) => {
741
- input[entry.name] = entry.inputPath;
742
- return input;
743
- }, {}),
744
- output: {
745
- // Include a hash to prevent conflicts
746
- chunkFileNames: "chunks/[name]-[hash].js",
747
- // Include a hash to prevent conflicts
748
- entryFileNames: "chunks/[name]-[hash].js",
749
- // We can't control the "name", so we need a hash to prevent conflicts
750
- assetFileNames: "assets/[name]-[hash].[ext]"
751
- }
752
- }
753
- }
754
- };
755
- const entryConfig = vite3.mergeConfig(
756
- multiPage,
757
- config.vite
939
+ async function writeGlobalsDeclarationFile(config) {
940
+ const filePath = resolve10(config.typesDir, "globals.d.ts");
941
+ const globals = getGlobals(config);
942
+ await fs8.writeFile(
943
+ filePath,
944
+ [
945
+ "// Generated by wxt",
946
+ "export {}",
947
+ "declare global {",
948
+ ...globals.map((global) => ` const ${global.name}: ${global.type};`),
949
+ "}"
950
+ ].join("\n") + "\n",
951
+ "utf-8"
758
952
  );
759
- const result = await vite3.build(entryConfig);
760
- return {
761
- entrypoints,
762
- chunks: getBuildOutputChunks(result)
763
- };
953
+ return filePath;
764
954
  }
765
- function getBuildOutputChunks(result) {
766
- if ("on" in result)
767
- throw Error("wxt does not support vite watch mode.");
768
- if (Array.isArray(result))
769
- return result.flatMap(({ output }) => output);
770
- return result.output;
955
+ async function writeMainDeclarationFile(references, config) {
956
+ const dir = config.wxtDir;
957
+ const filePath = resolve10(dir, "wxt.d.ts");
958
+ await fs8.writeFile(
959
+ filePath,
960
+ [
961
+ "// Generated by wxt",
962
+ ...references.map(
963
+ (ref) => `/// <reference types="./${relative4(dir, ref)}" />`
964
+ )
965
+ ].join("\n") + "\n"
966
+ );
967
+ return filePath;
771
968
  }
772
- async function copyPublicDirectory(config) {
773
- const publicAssets = [];
774
- if (!await fs6.exists(config.publicDir))
775
- return publicAssets;
776
- const files = await glob2("**/*", { cwd: config.publicDir });
777
- for (const file of files) {
778
- const srcPath = resolve9(config.publicDir, file);
779
- const outPath = resolve9(config.outDir, file);
780
- await fs6.ensureDir(dirname4(outPath));
781
- await fs6.copyFile(srcPath, outPath);
782
- publicAssets.push({
783
- type: "asset",
784
- fileName: file,
785
- name: file,
786
- needsCodeReference: false,
787
- source: await fs6.readFile(srcPath)
788
- });
789
- }
790
- return publicAssets;
969
+ async function writeTsConfigFile(mainReference, config) {
970
+ const dir = config.wxtDir;
971
+ await fs8.writeFile(
972
+ resolve10(dir, "tsconfig.json"),
973
+ `{
974
+ "compilerOptions": {
975
+ "target": "ESNext",
976
+ "module": "ESNext",
977
+ "moduleResolution": "Bundler",
978
+ "noEmit": true,
979
+ "esModuleInterop": true,
980
+ "forceConsistentCasingInFileNames": true,
981
+ "resolveJsonModule": true,
982
+
983
+ /* Type Checking */
984
+ "strict": true,
985
+
986
+ /* Completeness */
987
+ "skipLibCheck": true
988
+ },
989
+ "include": [
990
+ "${relative4(dir, config.root)}/**/*",
991
+ "./${relative4(dir, mainReference)}"
992
+ ],
993
+ "exclude": ["${relative4(dir, config.outBaseDir)}"]
994
+ }`
995
+ );
791
996
  }
792
997
 
793
998
  // src/core/utils/manifest.ts
794
- import fs7 from "fs-extra";
795
- import { resolve as resolve10 } from "path";
999
+ import fs9 from "fs-extra";
1000
+ import { resolve as resolve11 } from "path";
796
1001
 
797
1002
  // src/core/utils/ContentSecurityPolicy.ts
798
1003
  var ContentSecurityPolicy = class _ContentSecurityPolicy {
@@ -840,8 +1045,8 @@ var ContentSecurityPolicy = class _ContentSecurityPolicy {
840
1045
  // src/core/utils/manifest.ts
841
1046
  async function writeManifest(manifest, output, config) {
842
1047
  const str = config.mode === "production" ? JSON.stringify(manifest) : JSON.stringify(manifest, null, 2);
843
- await fs7.ensureDir(config.outDir);
844
- await fs7.writeFile(resolve10(config.outDir, "manifest.json"), str, "utf-8");
1048
+ await fs9.ensureDir(config.outDir);
1049
+ await fs9.writeFile(resolve11(config.outDir, "manifest.json"), str, "utf-8");
845
1050
  output.publicAssets.unshift({
846
1051
  type: "asset",
847
1052
  fileName: "manifest.json",
@@ -869,10 +1074,12 @@ async function generateMainfest(entrypoints, buildOutput, config) {
869
1074
  addEntrypoints(manifest, entrypoints, buildOutput, config);
870
1075
  if (config.command === "serve")
871
1076
  addDevModeCsp(manifest, config);
1077
+ if (config.command === "serve")
1078
+ addDevModePermissions(manifest, config);
872
1079
  return manifest;
873
1080
  }
874
1081
  async function getPackageJson(config) {
875
- return await fs7.readJson(resolve10(config.root, "package.json"));
1082
+ return await fs9.readJson(resolve11(config.root, "package.json"));
876
1083
  }
877
1084
  function simplifyVersion(versionName) {
878
1085
  const version3 = /^((0|[1-9][0-9]{0,8})([.](0|[1-9][0-9]{0,8})){0,3}).*$/.exec(
@@ -1025,23 +1232,23 @@ function addEntrypoints(manifest, entrypoints, buildOutput, config) {
1025
1232
  }
1026
1233
  }
1027
1234
  if (contentScripts?.length) {
1028
- if (config.command === "serve") {
1029
- const permissionsKey = config.manifestVersion === 2 ? "permissions" : "host_permissions";
1030
- const hostPermissions = new Set(manifest[permissionsKey] ?? []);
1235
+ if (config.command === "serve" && config.manifestVersion === 3) {
1236
+ const hostPermissions = new Set(manifest.host_permissions ?? []);
1031
1237
  contentScripts.forEach((script) => {
1032
1238
  script.options.matches.forEach((matchPattern) => {
1033
1239
  hostPermissions.add(matchPattern);
1034
1240
  });
1035
1241
  });
1036
- manifest[permissionsKey] = Array.from(hostPermissions).sort();
1242
+ hostPermissions.forEach(
1243
+ (permission) => addHostPermission(manifest, permission)
1244
+ );
1037
1245
  } else {
1038
1246
  const hashToEntrypointsMap = contentScripts.reduce((map, script) => {
1039
1247
  const hash = JSON.stringify(script.options);
1040
- if (!map.has(hash)) {
1041
- map.set(hash, [script]);
1042
- } else {
1248
+ if (map.has(hash))
1043
1249
  map.get(hash)?.push(script);
1044
- }
1250
+ else
1251
+ map.set(hash, [script]);
1045
1252
  return map;
1046
1253
  }, /* @__PURE__ */ new Map());
1047
1254
  manifest.content_scripts = Array.from(hashToEntrypointsMap.entries()).map(
@@ -1062,13 +1269,9 @@ function addDevModeCsp(manifest, config) {
1062
1269
  const permission = `http://${config.server?.hostname ?? ""}/*`;
1063
1270
  const allowedCsp = config.server?.origin ?? "http://localhost:*";
1064
1271
  if (manifest.manifest_version === 3) {
1065
- manifest.host_permissions ??= [];
1066
- if (!manifest.host_permissions.includes(permission))
1067
- manifest.host_permissions.push(permission);
1272
+ addHostPermission(manifest, permission);
1068
1273
  } else {
1069
- manifest.permissions ??= [];
1070
- if (!manifest.permissions.includes(permission))
1071
- manifest.permissions.push(permission);
1274
+ addPermission(manifest, permission);
1072
1275
  }
1073
1276
  const csp = new ContentSecurityPolicy(
1074
1277
  manifest.manifest_version === 3 ? (
@@ -1086,6 +1289,11 @@ function addDevModeCsp(manifest, config) {
1086
1289
  manifest.content_security_policy = csp.toString();
1087
1290
  }
1088
1291
  }
1292
+ function addDevModePermissions(manifest, config) {
1293
+ addPermission(manifest, "tabs");
1294
+ if (config.manifestVersion === 3)
1295
+ addPermission(manifest, "scripting");
1296
+ }
1089
1297
  function getContentScriptCssFiles(contentScripts, buildOutput) {
1090
1298
  const css = [];
1091
1299
  const allChunks = buildOutput.steps.flatMap((step) => step.chunks);
@@ -1100,9 +1308,71 @@ function getContentScriptCssFiles(contentScripts, buildOutput) {
1100
1308
  return css;
1101
1309
  return void 0;
1102
1310
  }
1311
+ function addPermission(manifest, permission) {
1312
+ manifest.permissions ??= [];
1313
+ if (manifest.permissions.includes(permission))
1314
+ return;
1315
+ manifest.permissions.push(permission);
1316
+ }
1317
+ function addHostPermission(manifest, hostPermission) {
1318
+ manifest.host_permissions ??= [];
1319
+ if (manifest.host_permissions.includes(hostPermission))
1320
+ return;
1321
+ manifest.host_permissions.push(hostPermission);
1322
+ }
1323
+
1324
+ // src/core/build.ts
1325
+ import pc2 from "picocolors";
1326
+ import * as vite4 from "vite";
1327
+ import fs11 from "fs-extra";
1328
+
1329
+ // src/core/utils/groupEntrypoints.ts
1330
+ function groupEntrypoints(entrypoints) {
1331
+ const groupIndexMap = {};
1332
+ const groups = [];
1333
+ for (const entry of entrypoints) {
1334
+ const group = ENTRY_TYPE_TO_GROUP_MAP[entry.type];
1335
+ if (group === "no-group") {
1336
+ groups.push(entry);
1337
+ } else {
1338
+ let groupIndex = groupIndexMap[group];
1339
+ if (groupIndex == null) {
1340
+ groupIndex = groups.push([]) - 1;
1341
+ groupIndexMap[group] = groupIndex;
1342
+ }
1343
+ groups[groupIndex].push(entry);
1344
+ }
1345
+ }
1346
+ return groups;
1347
+ }
1348
+ var ENTRY_TYPE_TO_GROUP_MAP = {
1349
+ sandbox: "sandbox-page",
1350
+ popup: "extension-page",
1351
+ newtab: "extension-page",
1352
+ history: "extension-page",
1353
+ options: "extension-page",
1354
+ devtools: "extension-page",
1355
+ bookmarks: "extension-page",
1356
+ sidepanel: "extension-page",
1357
+ "unlisted-page": "extension-page",
1358
+ background: "no-group",
1359
+ "content-script": "no-group",
1360
+ "unlisted-script": "no-group"
1361
+ };
1362
+
1363
+ // src/core/utils/formatDuration.ts
1364
+ function formatDuration(duration) {
1365
+ if (duration < 1e3)
1366
+ return `${duration} ms`;
1367
+ if (duration < 1e4)
1368
+ return `${(duration / 1e3).toFixed(3)} s`;
1369
+ if (duration < 6e4)
1370
+ return `${(duration / 1e3).toFixed(1)} s`;
1371
+ return `${(duration / 1e3).toFixed(0)} s`;
1372
+ }
1103
1373
 
1104
1374
  // src/core/log/printBuildSummary.ts
1105
- import path4, { extname as extname2, relative as relative4, resolve as resolve11 } from "path";
1375
+ import path4, { extname as extname2, relative as relative5, resolve as resolve12 } from "path";
1106
1376
 
1107
1377
  // src/core/log/printTable.ts
1108
1378
  function printTable(log, rows, gap = 2) {
@@ -1132,7 +1402,7 @@ function printTable(log, rows, gap = 2) {
1132
1402
 
1133
1403
  // src/core/log/printBuildSummary.ts
1134
1404
  import pc from "picocolors";
1135
- import fs8 from "fs-extra";
1405
+ import fs10 from "fs-extra";
1136
1406
  import { filesize } from "filesize";
1137
1407
  async function printBuildSummary(output, config) {
1138
1408
  const chunks = [
@@ -1150,13 +1420,13 @@ async function printBuildSummary(output, config) {
1150
1420
  const chunkRows = await Promise.all(
1151
1421
  chunks.map(async (chunk, i) => {
1152
1422
  const file = [
1153
- relative4(process.cwd(), config.outDir) + path4.sep,
1423
+ relative5(process.cwd(), config.outDir) + path4.sep,
1154
1424
  chunk.fileName
1155
1425
  ];
1156
1426
  const ext = extname2(chunk.fileName);
1157
1427
  const prefix = i === chunks.length - 1 ? " \u2514\u2500" : " \u251C\u2500";
1158
1428
  const color = CHUNK_COLORS[ext] ?? DEFAULT_COLOR;
1159
- const stats = await fs8.lstat(resolve11(config.outDir, chunk.fileName));
1429
+ const stats = await fs10.lstat(resolve12(config.outDir, chunk.fileName));
1160
1430
  totalSize += stats.size;
1161
1431
  const size = String(filesize(stats.size));
1162
1432
  return [
@@ -1184,115 +1454,63 @@ var CHUNK_COLORS = {
1184
1454
  ".js": pc.cyan
1185
1455
  };
1186
1456
 
1187
- // src/index.ts
1188
- import fs10 from "fs-extra";
1189
-
1190
- // src/core/build/generateTypesDir.ts
1191
- import { createUnimport as createUnimport2 } from "unimport";
1192
- import fs9 from "fs-extra";
1193
- import { relative as relative5, resolve as resolve12 } from "path";
1194
- async function generateTypesDir(entrypoints, config) {
1195
- await fs9.ensureDir(config.typesDir);
1196
- const references = [];
1197
- references.push(await writeImportsDeclarationFile(config));
1198
- references.push(await writePathsDeclarationFile(entrypoints, config));
1199
- references.push(await writeGlobalsDeclarationFile(config));
1200
- const mainReference = await writeMainDeclarationFile(references, config);
1201
- await writeTsConfigFile(mainReference, config);
1202
- }
1203
- async function writeImportsDeclarationFile(config) {
1204
- const filePath = resolve12(config.typesDir, "imports.d.ts");
1205
- const unimport2 = createUnimport2(getUnimportOptions(config));
1206
- await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
1207
- await fs9.writeFile(
1208
- filePath,
1209
- ["// Generated by wxt", await unimport2.generateTypeDeclarations()].join(
1210
- "\n"
1211
- ) + "\n"
1457
+ // src/core/build.ts
1458
+ async function buildInternal(config) {
1459
+ const verb = config.command === "serve" ? "Pre-rendering" : "Building";
1460
+ const target = `${config.browser}-mv${config.manifestVersion}`;
1461
+ config.logger.info(
1462
+ `${verb} ${pc2.cyan(target)} for ${pc2.cyan(config.mode)} with ${pc2.green(
1463
+ `Vite ${vite4.version}`
1464
+ )}`
1212
1465
  );
1213
- return filePath;
1214
- }
1215
- async function writePathsDeclarationFile(entrypoints, config) {
1216
- const filePath = resolve12(config.typesDir, "paths.d.ts");
1217
- await fs9.writeFile(
1218
- filePath,
1219
- [
1220
- "// Generated by wxt",
1221
- "type EntrypointPath =",
1222
- ...entrypoints.map((entry) => {
1223
- const path5 = getEntrypointBundlePath(
1224
- entry,
1225
- config.outDir,
1226
- entry.inputPath.endsWith(".html") ? ".html" : ".js"
1227
- );
1228
- return ` | "/${path5}"`;
1229
- }).sort()
1230
- ].join("\n") + "\n"
1231
- );
1232
- return filePath;
1233
- }
1234
- async function writeGlobalsDeclarationFile(config) {
1235
- const filePath = resolve12(config.typesDir, "globals.d.ts");
1236
- const globals = getGlobals(config);
1237
- await fs9.writeFile(
1238
- filePath,
1239
- [
1240
- "// Generated by wxt",
1241
- "export {}",
1242
- "declare global {",
1243
- ...globals.map((global) => ` const ${global.name}: ${global.type};`),
1244
- "}"
1245
- ].join("\n") + "\n",
1246
- "utf-8"
1247
- );
1248
- return filePath;
1249
- }
1250
- async function writeMainDeclarationFile(references, config) {
1251
- const dir = config.wxtDir;
1252
- const filePath = resolve12(dir, "wxt.d.ts");
1253
- await fs9.writeFile(
1254
- filePath,
1255
- [
1256
- "// Generated by wxt",
1257
- ...references.map(
1258
- (ref) => `/// <reference types="./${relative5(dir, ref)}" />`
1259
- )
1260
- ].join("\n") + "\n"
1466
+ const startTime = Date.now();
1467
+ await fs11.rm(config.outDir, { recursive: true, force: true });
1468
+ await fs11.ensureDir(config.outDir);
1469
+ const entrypoints = await findEntrypoints(config);
1470
+ const groups = groupEntrypoints(entrypoints);
1471
+ const { output } = await rebuild(config, groups);
1472
+ config.logger.success(
1473
+ `Built extension in ${formatDuration(Date.now() - startTime)}`
1261
1474
  );
1262
- return filePath;
1475
+ await printBuildSummary(output, config);
1476
+ return output;
1263
1477
  }
1264
- async function writeTsConfigFile(mainReference, config) {
1265
- const dir = config.wxtDir;
1266
- await fs9.writeFile(
1267
- resolve12(dir, "tsconfig.json"),
1268
- `{
1269
- "compilerOptions": {
1270
- "target": "ESNext",
1271
- "module": "ESNext",
1272
- "moduleResolution": "Bundler",
1273
- "noEmit": true,
1274
- "esModuleInterop": true,
1275
- "forceConsistentCasingInFileNames": true,
1276
- "resolveJsonModule": true,
1277
-
1278
- /* Type Checking */
1279
- "strict": true,
1280
-
1281
- /* Completeness */
1282
- "skipLibCheck": true
1283
- },
1284
- "include": [
1285
- "${relative5(dir, config.root)}/**/*",
1286
- "./${relative5(dir, mainReference)}"
1287
- ],
1288
- "exclude": ["${relative5(dir, config.outBaseDir)}"]
1289
- }`
1478
+ async function rebuild(config, entrypointGroups, existingOutput = {
1479
+ steps: [],
1480
+ publicAssets: []
1481
+ }) {
1482
+ const allEntrypoints = await findEntrypoints(config);
1483
+ await generateTypesDir(allEntrypoints, config);
1484
+ const newOutput = await buildEntrypoints(entrypointGroups, config);
1485
+ const mergedOutput = {
1486
+ steps: [...existingOutput.steps, ...newOutput.steps],
1487
+ publicAssets: [...existingOutput.publicAssets, ...newOutput.publicAssets]
1488
+ };
1489
+ const newManifest = await generateMainfest(
1490
+ allEntrypoints,
1491
+ mergedOutput,
1492
+ config
1290
1493
  );
1494
+ const finalOutput = {
1495
+ manifest: newManifest,
1496
+ ...newOutput
1497
+ };
1498
+ await writeManifest(newManifest, finalOutput, config);
1499
+ return {
1500
+ output: {
1501
+ manifest: newManifest,
1502
+ steps: [...existingOutput.steps, ...finalOutput.steps],
1503
+ publicAssets: [
1504
+ ...existingOutput.publicAssets,
1505
+ ...finalOutput.publicAssets
1506
+ ]
1507
+ },
1508
+ manifest: newManifest
1509
+ };
1291
1510
  }
1292
1511
 
1293
- // src/index.ts
1294
- import pc2 from "picocolors";
1295
- import * as vite4 from "vite";
1512
+ // src/core/server.ts
1513
+ import * as vite5 from "vite";
1296
1514
 
1297
1515
  // src/core/utils/findOpenPort.ts
1298
1516
  import net from "node:net";
@@ -1317,17 +1535,6 @@ function findOpenPortRecursive(port, startPort, endPort) {
1317
1535
  });
1318
1536
  }
1319
1537
 
1320
- // src/core/utils/formatDuration.ts
1321
- function formatDuration(duration) {
1322
- if (duration < 1e3)
1323
- return `${duration} ms`;
1324
- if (duration < 1e4)
1325
- return `${(duration / 1e3).toFixed(3)} s`;
1326
- if (duration < 6e4)
1327
- return `${(duration / 1e3).toFixed(1)} s`;
1328
- return `${(duration / 1e3).toFixed(0)} s`;
1329
- }
1330
-
1331
1538
  // src/core/runners/createWebExtRunner.ts
1332
1539
  function createWebExtRunner() {
1333
1540
  let runner;
@@ -1385,120 +1592,93 @@ function createWebExtRunner() {
1385
1592
  var WARN_LOG_LEVEL = 40;
1386
1593
  var ERROR_LOG_LEVEL = 50;
1387
1594
 
1388
- // src/core/utils/groupEntrypoints.ts
1389
- function groupEntrypoints(entrypoints) {
1390
- const groupIndexMap = {};
1391
- const groups = [];
1392
- for (const entry of entrypoints) {
1393
- const group = ENTRY_TYPE_TO_GROUP_MAP[entry.type];
1394
- if (group === "no-group") {
1395
- groups.push(entry);
1396
- } else {
1397
- let groupIndex = groupIndexMap[group];
1398
- if (groupIndex == null) {
1399
- groupIndex = groups.push([]) - 1;
1400
- groupIndexMap[group] = groupIndex;
1401
- }
1402
- groups[groupIndex].push(entry);
1595
+ // src/core/server.ts
1596
+ async function getServerInfo() {
1597
+ const port = await findOpenPort(3e3, 3010);
1598
+ const hostname = "localhost";
1599
+ const origin = `http://${hostname}:${port}`;
1600
+ const serverConfig = {
1601
+ server: {
1602
+ origin
1403
1603
  }
1404
- }
1405
- return groups;
1604
+ };
1605
+ return {
1606
+ port,
1607
+ hostname,
1608
+ origin,
1609
+ viteServerConfig: serverConfig
1610
+ };
1406
1611
  }
1407
- var ENTRY_TYPE_TO_GROUP_MAP = {
1408
- sandbox: "sandbox-page",
1409
- popup: "extension-page",
1410
- newtab: "extension-page",
1411
- history: "extension-page",
1412
- options: "extension-page",
1413
- devtools: "extension-page",
1414
- bookmarks: "extension-page",
1415
- sidepanel: "extension-page",
1416
- "unlisted-page": "extension-page",
1417
- background: "no-group",
1418
- "content-script": "no-group",
1419
- "unlisted-script": "no-group"
1420
- };
1421
-
1422
- // src/core/utils/detectDevChanges.ts
1423
- function detectDevChanges(changedFiles, currentOutput) {
1424
- if (currentOutput == null)
1425
- return { type: "no-change" };
1426
- const changedSteps = new Set(
1427
- changedFiles.flatMap(
1428
- (changedFile) => findEffectedSteps(changedFile, currentOutput)
1429
- )
1612
+ async function setupServer(serverInfo, config) {
1613
+ const runner = createWebExtRunner();
1614
+ const viteServer = await vite5.createServer(
1615
+ vite5.mergeConfig(serverInfo, config.vite)
1430
1616
  );
1431
- if (changedSteps.size === 0)
1432
- return { type: "no-change" };
1433
- const unchangedOutput = {
1434
- manifest: currentOutput.manifest,
1435
- steps: [],
1436
- publicAssets: []
1617
+ const start = async () => {
1618
+ await viteServer.listen(server.port);
1619
+ config.logger.success(`Started dev server @ ${serverInfo.origin}`);
1620
+ server.currentOutput = await buildInternal(config);
1621
+ config.logger.info("Opening browser...");
1622
+ await runner.openBrowser(config);
1623
+ config.logger.success("Opened!");
1437
1624
  };
1438
- const changedOutput = {
1439
- manifest: currentOutput.manifest,
1440
- steps: [],
1441
- publicAssets: []
1625
+ const reloadExtension = () => {
1626
+ viteServer.ws.send("wxt:reload-extension");
1442
1627
  };
1443
- for (const step of currentOutput.steps) {
1444
- if (changedSteps.has(step)) {
1445
- changedOutput.steps.push(step);
1446
- } else {
1447
- unchangedOutput.steps.push(step);
1448
- }
1449
- }
1450
- for (const asset of currentOutput.publicAssets) {
1451
- if (changedSteps.has(asset)) {
1452
- changedOutput.publicAssets.push(asset);
1453
- } else {
1454
- unchangedOutput.publicAssets.push(asset);
1455
- }
1456
- }
1457
- const isOnlyHtmlChanges = !changedFiles.find(
1458
- ([_, file]) => !file.endsWith(".html")
1459
- );
1460
- if (isOnlyHtmlChanges) {
1461
- return {
1462
- type: "html-reload",
1463
- cachedOutput: unchangedOutput,
1464
- rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
1465
- };
1466
- }
1467
- return {
1468
- type: "extension-reload",
1469
- cachedOutput: unchangedOutput,
1470
- rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
1628
+ const reloadPage = (path5) => {
1629
+ viteServer.ws.send("wxt:reload-page", path5);
1471
1630
  };
1631
+ const reloadContentScript = (contentScript) => {
1632
+ viteServer.ws.send("wxt:reload-content-script", contentScript);
1633
+ };
1634
+ const server = {
1635
+ ...viteServer,
1636
+ start,
1637
+ currentOutput: {
1638
+ manifest: {
1639
+ manifest_version: 3,
1640
+ name: "",
1641
+ version: ""
1642
+ },
1643
+ publicAssets: [],
1644
+ steps: []
1645
+ },
1646
+ port: serverInfo.port,
1647
+ hostname: serverInfo.hostname,
1648
+ origin: serverInfo.origin,
1649
+ reloadExtension,
1650
+ reloadPage,
1651
+ reloadContentScript
1652
+ };
1653
+ return server;
1472
1654
  }
1473
- function findEffectedSteps(changedFile, currentOutput) {
1474
- const changes = [];
1475
- const changedPath = changedFile[1];
1476
- const isChunkEffected = (chunk) => (
1477
- // If it's an HTML file with the same path, is is effected because HTML files need to be pre-rendered
1478
- // TODO: use bundle path to support `<name>/index.html`?
1479
- chunk.type === "asset" && changedPath.endsWith(chunk.fileName) || // If it's a chunk that depends on the changed file, it is effected
1480
- chunk.type === "chunk" && chunk.moduleIds.includes(changedPath)
1481
- );
1482
- for (const step of currentOutput.steps) {
1483
- const effectedChunk = step.chunks.find((chunk) => isChunkEffected(chunk));
1484
- if (effectedChunk)
1485
- changes.push(step);
1655
+ function reloadContentScripts(steps, config, server) {
1656
+ if (config.manifestVersion === 3) {
1657
+ steps.forEach((step) => {
1658
+ const entry = step.entrypoints;
1659
+ if (Array.isArray(entry) || entry.type !== "content-script")
1660
+ return;
1661
+ const js = [getEntrypointBundlePath(entry, config.outDir, ".js")];
1662
+ const css = getContentScriptCssFiles([entry], server.currentOutput);
1663
+ server.reloadContentScript({
1664
+ js,
1665
+ css,
1666
+ ...entry.options
1667
+ });
1668
+ });
1669
+ } else {
1670
+ server.reloadExtension();
1486
1671
  }
1487
- const effectedAsset = currentOutput.publicAssets.find(
1488
- (chunk) => isChunkEffected(chunk)
1489
- );
1490
- if (effectedAsset)
1491
- changes.push(effectedAsset);
1492
- return changes;
1493
1672
  }
1494
-
1495
- // src/index.ts
1496
- import { Mutex } from "async-mutex";
1497
- import { consola as consola3 } from "consola";
1498
- import { relative as relative6 } from "node:path";
1673
+ function reloadHtmlPages(groups, server, config) {
1674
+ groups.flat().forEach((entry) => {
1675
+ const path5 = getEntrypointBundlePath(entry, config.outDir, ".html");
1676
+ server.reloadPage(path5);
1677
+ });
1678
+ }
1499
1679
 
1500
1680
  // package.json
1501
- var version = "0.0.2";
1681
+ var version2 = "0.1.1-alpha1";
1502
1682
 
1503
1683
  // src/core/utils/defineConfig.ts
1504
1684
  function defineConfig(config) {
@@ -1516,45 +1696,40 @@ async function build2(config) {
1516
1696
  return await buildInternal(internalConfig);
1517
1697
  }
1518
1698
  async function createServer2(config) {
1519
- const port = await findOpenPort(3e3, 3010);
1520
- const hostname = "localhost";
1521
- const origin = `http://${hostname}:${port}`;
1522
- const serverConfig = {
1523
- server: {
1524
- origin
1525
- }
1699
+ const serverInfo = await getServerInfo();
1700
+ const getLatestInternalConfig = () => {
1701
+ const viteConfig = vite6.mergeConfig(
1702
+ serverInfo.viteServerConfig,
1703
+ config?.vite ?? {}
1704
+ );
1705
+ return getInternalConfig({ ...config, vite: viteConfig }, "serve");
1526
1706
  };
1527
- let internalConfig = await getInternalConfig(
1528
- vite4.mergeConfig(serverConfig, config ?? {}),
1529
- "serve"
1530
- );
1531
- const runner = createWebExtRunner();
1532
- let hasBuiltOnce = false;
1533
- let currentOutput;
1707
+ let internalConfig = await getLatestInternalConfig();
1708
+ const server = await setupServer(serverInfo, internalConfig);
1709
+ internalConfig.server = server;
1534
1710
  const fileChangedMutex = new Mutex();
1535
1711
  const changeQueue = [];
1536
- const viteServer = await vite4.createServer(internalConfig.vite);
1537
- viteServer.watcher.on("all", async (event, path5, stats) => {
1538
- if (!hasBuiltOnce || path5.startsWith(internalConfig.outBaseDir))
1712
+ server.ws.on("wxt:background-initialized", () => {
1713
+ reloadContentScripts(server.currentOutput.steps, internalConfig, server);
1714
+ });
1715
+ server.watcher.on("all", async (event, path5, _stats) => {
1716
+ if (path5.startsWith(internalConfig.outBaseDir))
1539
1717
  return;
1540
1718
  changeQueue.push([event, path5]);
1541
1719
  await fileChangedMutex.runExclusive(async () => {
1542
1720
  const fileChanges = changeQueue.splice(0, changeQueue.length);
1543
- const changes = detectDevChanges(fileChanges, currentOutput);
1721
+ const changes = detectDevChanges(fileChanges, server.currentOutput);
1544
1722
  if (changes.type === "no-change")
1545
1723
  return;
1546
1724
  consola3.info(
1547
- `Changed: ${Array.from(new Set(fileChanges.map((change) => change[1]))).map((file) => pc2.dim(relative6(internalConfig.root, file))).join(", ")}`
1725
+ `Changed: ${Array.from(new Set(fileChanges.map((change) => change[1]))).map((file) => pc3.dim(relative6(internalConfig.root, file))).join(", ")}`
1548
1726
  );
1549
1727
  const rebuiltNames = changes.rebuildGroups.flat().map((entry) => {
1550
- return pc2.cyan(
1728
+ return pc3.cyan(
1551
1729
  relative6(internalConfig.outDir, getEntrypointOutputFile(entry, ""))
1552
1730
  );
1553
- }).join(pc2.dim(", "));
1554
- internalConfig = await getInternalConfig(
1555
- vite4.mergeConfig(serverConfig, config ?? {}),
1556
- "serve"
1557
- );
1731
+ }).join(pc3.dim(", "));
1732
+ internalConfig = await getLatestInternalConfig();
1558
1733
  internalConfig.server = server;
1559
1734
  const { output: newOutput } = await rebuild(
1560
1735
  internalConfig,
@@ -1562,102 +1737,28 @@ async function createServer2(config) {
1562
1737
  changes.rebuildGroups,
1563
1738
  changes.cachedOutput
1564
1739
  );
1565
- currentOutput = newOutput;
1740
+ server.currentOutput = newOutput;
1566
1741
  switch (changes.type) {
1567
1742
  case "extension-reload":
1568
1743
  server.reloadExtension();
1569
- consola3.success(`Reloaded extension: ${rebuiltNames}`);
1570
1744
  break;
1571
1745
  case "html-reload":
1572
- changes.rebuildGroups.flat().forEach((entry) => {
1573
- const path6 = getEntrypointBundlePath(
1574
- entry,
1575
- internalConfig.outDir,
1576
- ".html"
1577
- );
1578
- server.reloadPage(path6);
1579
- });
1580
- consola3.success(`Reloaded pages: ${rebuiltNames}`);
1746
+ reloadHtmlPages(changes.rebuildGroups, server, internalConfig);
1747
+ break;
1748
+ case "content-script-reload":
1749
+ reloadContentScripts(changes.changedSteps, internalConfig, server);
1581
1750
  break;
1582
1751
  }
1752
+ consola3.success(`Reloaded: ${rebuiltNames}`);
1583
1753
  });
1584
1754
  });
1585
- const server = {
1586
- ...viteServer,
1587
- async listen(port2, isRestart) {
1588
- const res = await viteServer.listen(port2, isRestart);
1589
- if (!isRestart) {
1590
- internalConfig.logger.success(`Started dev server @ ${origin}`);
1591
- internalConfig.logger.info("Opening browser...");
1592
- await runner.openBrowser(internalConfig);
1593
- internalConfig.logger.success("Opened!");
1594
- }
1595
- return res;
1596
- },
1597
- port,
1598
- hostname,
1599
- origin,
1600
- reloadExtension: () => {
1601
- server.ws.send("wxt:reload-extension");
1602
- },
1603
- reloadPage: (path5) => {
1604
- server.ws.send("wxt:reload-page", path5);
1605
- }
1606
- };
1607
- internalConfig.logger.info("Created dev server");
1608
- internalConfig.server = server;
1609
- currentOutput = await buildInternal(internalConfig);
1610
- hasBuiltOnce = true;
1611
1755
  return server;
1612
1756
  }
1613
- async function buildInternal(config) {
1614
- const verb = config.command === "serve" ? "Pre-rendering" : "Building";
1615
- const target = `${config.browser}-mv${config.manifestVersion}`;
1616
- config.logger.info(
1617
- `${verb} ${pc2.cyan(target)} for ${pc2.cyan(config.mode)} with ${pc2.green(
1618
- `Vite ${vite4.version}`
1619
- )}`
1620
- );
1621
- const startTime = Date.now();
1622
- await fs10.rm(config.outDir, { recursive: true, force: true });
1623
- await fs10.ensureDir(config.outDir);
1624
- const entrypoints = await findEntrypoints(config);
1625
- const groups = groupEntrypoints(entrypoints);
1626
- const { output } = await rebuild(config, groups);
1627
- config.logger.success(
1628
- `Built extension in ${formatDuration(Date.now() - startTime)}`
1629
- );
1630
- await printBuildSummary(output, config);
1631
- return output;
1632
- }
1633
- async function rebuild(config, entrypointGroups, existingOutput = {
1634
- steps: [],
1635
- publicAssets: []
1636
- }) {
1637
- const allEntrypoints = await findEntrypoints(config);
1638
- await generateTypesDir(allEntrypoints, config);
1639
- const buildOutput = await buildEntrypoints(entrypointGroups, config);
1640
- const manifest = await generateMainfest(allEntrypoints, buildOutput, config);
1641
- const output = {
1642
- manifest,
1643
- ...buildOutput
1644
- };
1645
- await writeManifest(manifest, output, config);
1646
- return {
1647
- output: {
1648
- manifest,
1649
- steps: [...existingOutput.steps, ...output.steps],
1650
- publicAssets: [...existingOutput.publicAssets, ...output.publicAssets]
1651
- },
1652
- manifest
1653
- };
1654
- }
1655
1757
  export {
1656
1758
  build2 as build,
1657
1759
  createServer2 as createServer,
1658
1760
  defineConfig,
1659
1761
  defineRunnerConfig,
1660
- rebuild,
1661
- version
1762
+ version2 as version
1662
1763
  };
1663
1764
  //# sourceMappingURL=index.js.map