wxt 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/core/utils/getInternalConfig.ts
2
2
  import path2, { resolve as resolve7 } from "node:path";
3
- import * as vite from "vite";
3
+ import * as vite2 from "vite";
4
4
  import { consola as consola2 } from "consola";
5
5
 
6
6
  // src/core/utils/importTsFile.ts
@@ -11,7 +11,7 @@ import { resolve } from "path";
11
11
  import { scanExports } from "unimport";
12
12
  async function importTsFile(root, path5) {
13
13
  const clientImports = await scanExports(
14
- resolve(root, "node_modules/wxt/dist/client/index.js")
14
+ resolve(root, "node_modules/wxt/dist/client.js")
15
15
  );
16
16
  const jiti = createJITI(__filename, {
17
17
  cache: false,
@@ -38,6 +38,106 @@ async function importTsFile(root, path5) {
38
38
  }
39
39
  }
40
40
 
41
+ // src/core/vite-plugins/devHtmlPrerender.ts
42
+ import * as vite from "vite";
43
+
44
+ // src/core/utils/entrypoints.ts
45
+ import path, { relative, resolve as resolve2 } from "node:path";
46
+ function getEntrypointName(entrypointsDir, inputPath) {
47
+ const relativePath = path.relative(entrypointsDir, inputPath);
48
+ const name = relativePath.split(/[\.\/]/, 2)[0];
49
+ return name;
50
+ }
51
+ function getEntrypointOutputFile(entrypoint, ext) {
52
+ return resolve2(entrypoint.outputDir, `${entrypoint.name}${ext}`);
53
+ }
54
+ function getEntrypointBundlePath(entrypoint, outDir, ext) {
55
+ return relative(outDir, getEntrypointOutputFile(entrypoint, ext));
56
+ }
57
+
58
+ // src/core/vite-plugins/devHtmlPrerender.ts
59
+ import { parseHTML } from "linkedom";
60
+ import { dirname, isAbsolute, relative as relative2, resolve as resolve3 } from "path";
61
+ function devHtmlPrerender(config) {
62
+ return {
63
+ apply: "build",
64
+ name: "wxt:dev-html-prerender",
65
+ config(userConfig) {
66
+ return vite.mergeConfig(
67
+ {
68
+ resolve: {
69
+ alias: {
70
+ "@wxt/reload-html": resolve3(
71
+ config.root,
72
+ "node_modules/wxt/dist/virtual-modules/reload-html.js"
73
+ )
74
+ }
75
+ }
76
+ },
77
+ userConfig
78
+ );
79
+ },
80
+ async transform(html, id) {
81
+ const server = config.server;
82
+ if (config.command !== "serve" || server == null || !id.endsWith(".html"))
83
+ return;
84
+ const originalUrl = `${server.origin}${id}`;
85
+ const name = getEntrypointName(config.entrypointsDir, id);
86
+ const url = `${server.origin}/${name}.html`;
87
+ const serverHtml = await server.transformIndexHtml(
88
+ url,
89
+ html,
90
+ originalUrl
91
+ );
92
+ const { document } = parseHTML(serverHtml);
93
+ const pointToDevServer = (querySelector, attr) => {
94
+ document.querySelectorAll(querySelector).forEach((element) => {
95
+ const src = element.getAttribute(attr);
96
+ if (!src)
97
+ return;
98
+ if (isAbsolute(src)) {
99
+ element.setAttribute(attr, server.origin + src);
100
+ } else if (src.startsWith(".")) {
101
+ const abs = resolve3(dirname(id), src);
102
+ const pathname = relative2(config.root, abs);
103
+ element.setAttribute(attr, `${server.origin}/${pathname}`);
104
+ }
105
+ });
106
+ };
107
+ pointToDevServer("script[type=module]", "src");
108
+ pointToDevServer("link[rel=stylesheet]", "href");
109
+ const reloader = document.createElement("script");
110
+ reloader.src = "@wxt/reload-html";
111
+ reloader.type = "module";
112
+ document.head.appendChild(reloader);
113
+ const newHtml = document.toString();
114
+ config.logger.debug("Transformed " + id);
115
+ config.logger.debug("Old HTML:\n" + html);
116
+ config.logger.debug("New HTML:\n" + newHtml);
117
+ return newHtml;
118
+ }
119
+ };
120
+ }
121
+
122
+ // src/core/vite-plugins/devServerGlobals.ts
123
+ function devServerGlobals(internalConfig) {
124
+ return {
125
+ name: "wxt:dev-server-globals",
126
+ config(config) {
127
+ if (internalConfig.server == null || internalConfig.command == "build")
128
+ return;
129
+ config.define ??= {};
130
+ config.define.__DEV_SERVER_PROTOCOL__ = JSON.stringify("ws:");
131
+ config.define.__DEV_SERVER_HOSTNAME__ = JSON.stringify(
132
+ internalConfig.server.hostname
133
+ );
134
+ config.define.__DEV_SERVER_PORT__ = JSON.stringify(
135
+ internalConfig.server.port
136
+ );
137
+ }
138
+ };
139
+ }
140
+
41
141
  // src/core/utils/network.ts
42
142
  import dns from "node:dns";
43
143
 
@@ -107,61 +207,8 @@ function download(config) {
107
207
  };
108
208
  }
109
209
 
110
- // src/core/vite-plugins/unimport.ts
111
- import { createUnimport } from "unimport";
112
-
113
- // src/core/utils/auto-imports.ts
114
- import { mergeConfig } from "vite";
115
- function getUnimportOptions(config) {
116
- const defaultOptions = {
117
- debugLog: config.logger.debug,
118
- imports: [
119
- { name: "*", as: "browser", from: "webextension-polyfill" },
120
- { name: "defineConfig", from: "wxt" }
121
- ],
122
- presets: [{ package: "wxt/client" }],
123
- warn: config.logger.warn,
124
- dirs: ["components", "composables", "hooks", "utils"]
125
- };
126
- return mergeConfig(
127
- defaultOptions,
128
- config.imports
129
- );
130
- }
131
-
132
- // src/core/vite-plugins/unimport.ts
133
- function unimport(config) {
134
- const options = getUnimportOptions(config);
135
- const unimport2 = createUnimport(options);
136
- return {
137
- name: "wxt:unimport",
138
- async config() {
139
- await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
140
- },
141
- async transform(code, id) {
142
- return unimport2.injectImports(code, id);
143
- }
144
- };
145
- }
146
-
147
- // src/core/vite-plugins/multipageMove.ts
148
- import { dirname, extname, resolve as resolve3 } from "node:path";
149
-
150
- // src/core/utils/entrypoints.ts
151
- import path, { relative, resolve as resolve2 } from "node:path";
152
- function getEntrypointName(entrypointsDir, inputPath) {
153
- const relativePath = path.relative(entrypointsDir, inputPath);
154
- const name = relativePath.split(/[\.\/]/, 2)[0];
155
- return name;
156
- }
157
- function getEntrypointOutputFile(entrypoint, ext) {
158
- return resolve2(entrypoint.outputDir, `${entrypoint.name}${ext}`);
159
- }
160
- function getEntrypointBundlePath(entrypoint, outDir, ext) {
161
- return relative(outDir, getEntrypointOutputFile(entrypoint, ext));
162
- }
163
-
164
210
  // src/core/vite-plugins/multipageMove.ts
211
+ import { dirname as dirname2, extname, resolve as resolve4 } from "node:path";
165
212
  import fs, { ensureDir } from "fs-extra";
166
213
  function multipageMove(entrypoints, config) {
167
214
  return {
@@ -187,10 +234,10 @@ function multipageMove(entrypoints, config) {
187
234
  );
188
235
  continue;
189
236
  }
190
- const oldAbsPath = resolve3(config.outDir, oldBundlePath);
191
- const newAbsPath = resolve3(config.outDir, newBundlePath);
192
- await ensureDir(dirname(newAbsPath));
193
- await fs.move(oldAbsPath, newAbsPath);
237
+ const oldAbsPath = resolve4(config.outDir, oldBundlePath);
238
+ const newAbsPath = resolve4(config.outDir, newBundlePath);
239
+ await ensureDir(dirname2(newAbsPath));
240
+ await fs.move(oldAbsPath, newAbsPath, { overwrite: true });
194
241
  const renamedChunk = {
195
242
  ...bundle[oldBundlePath],
196
243
  fileName: newBundlePath
@@ -202,47 +249,39 @@ function multipageMove(entrypoints, config) {
202
249
  };
203
250
  }
204
251
 
205
- // src/core/vite-plugins/devHtmlPrerender.ts
206
- import { parseHTML } from "linkedom";
207
- import { dirname as dirname2, isAbsolute, relative as relative2, resolve as resolve4 } from "path";
208
- function devHtmlPrerender(config) {
252
+ // src/core/vite-plugins/unimport.ts
253
+ import { createUnimport } from "unimport";
254
+
255
+ // src/core/utils/auto-imports.ts
256
+ import { mergeConfig as mergeConfig2 } from "vite";
257
+ function getUnimportOptions(config) {
258
+ const defaultOptions = {
259
+ debugLog: config.logger.debug,
260
+ imports: [
261
+ { name: "*", as: "browser", from: "webextension-polyfill" },
262
+ { name: "defineConfig", from: "wxt" }
263
+ ],
264
+ presets: [{ package: "wxt/client" }],
265
+ warn: config.logger.warn,
266
+ dirs: ["components", "composables", "hooks", "utils"]
267
+ };
268
+ return mergeConfig2(
269
+ defaultOptions,
270
+ config.imports
271
+ );
272
+ }
273
+
274
+ // src/core/vite-plugins/unimport.ts
275
+ function unimport(config) {
276
+ const options = getUnimportOptions(config);
277
+ const unimport2 = createUnimport(options);
209
278
  return {
210
- apply: "build",
211
- name: "wxt:dev-html-prerender",
212
- async transform(html, id) {
213
- const server = config.server;
214
- if (config.command !== "serve" || server == null || !id.endsWith(".html"))
215
- return;
216
- const originalUrl = `${server.origin}${id}`;
217
- const name = getEntrypointName(config.entrypointsDir, id);
218
- const url = `${server.origin}/${name}.html`;
219
- const serverHtml = await server.transformIndexHtml(
220
- url,
221
- html,
222
- originalUrl
223
- );
224
- const { document } = parseHTML(serverHtml);
225
- const pointToDevServer = (querySelector, attr) => {
226
- document.querySelectorAll(querySelector).forEach((element) => {
227
- const src = element.getAttribute(attr);
228
- if (!src)
229
- return;
230
- if (isAbsolute(src)) {
231
- element.setAttribute(attr, server.origin + src);
232
- } else if (src.startsWith(".")) {
233
- const abs = resolve4(dirname2(id), src);
234
- const pathname = relative2(config.root, abs);
235
- element.setAttribute(attr, `${server.origin}/${pathname}`);
236
- }
237
- });
238
- };
239
- pointToDevServer("script[type=module]", "src");
240
- pointToDevServer("link[rel=stylesheet]", "href");
241
- const newHtml = document.toString();
242
- config.logger.debug("Transformed " + id);
243
- config.logger.debug("Old HTML:\n" + html);
244
- config.logger.debug("New HTML:\n" + newHtml);
245
- return newHtml;
279
+ name: "wxt:unimport",
280
+ async config() {
281
+ await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
282
+ },
283
+ async transform(code, id) {
284
+ return unimport2.injectImports(code, id);
246
285
  }
247
286
  };
248
287
  }
@@ -267,10 +306,13 @@ function virtualEntrypoin(type, config) {
267
306
  return;
268
307
  const inputPath = id.replace(resolvedVirtualId, "");
269
308
  const template = await fs2.readFile(
270
- resolve5(config.root, `node_modules/wxt/templates/virtual-${type}.ts`),
309
+ resolve5(
310
+ config.root,
311
+ `node_modules/wxt/dist/virtual-modules/${type}-entrypoint.js`
312
+ ),
271
313
  "utf-8"
272
314
  );
273
- return template.replaceAll("{{moduleId}}", inputPath);
315
+ return template.replace(`virtual:user-${type}`, inputPath);
274
316
  }
275
317
  };
276
318
  }
@@ -334,6 +376,11 @@ function getGlobals(config) {
334
376
  name: "__IS_OPERA__",
335
377
  value: config.browser === "opera",
336
378
  type: `boolean`
379
+ },
380
+ {
381
+ name: "__COMMAND__",
382
+ value: config.command,
383
+ type: `"build" | "serve"`
337
384
  }
338
385
  ];
339
386
  }
@@ -378,7 +425,7 @@ async function getInternalConfig(config, command) {
378
425
  path2.resolve(root, config.configFile ?? "wxt.config.ts")
379
426
  );
380
427
  }
381
- const merged = vite.mergeConfig(
428
+ const merged = vite2.mergeConfig(
382
429
  baseConfig,
383
430
  userConfig
384
431
  );
@@ -401,7 +448,7 @@ async function getInternalConfig(config, command) {
401
448
  };
402
449
  finalConfig.vite.root = root;
403
450
  finalConfig.vite.configFile = false;
404
- finalConfig.vite.logLevel = "silent";
451
+ finalConfig.vite.logLevel = "warn";
405
452
  finalConfig.vite.build ??= {};
406
453
  finalConfig.vite.build.outDir = outDir;
407
454
  finalConfig.vite.build.emptyOutDir = false;
@@ -415,6 +462,7 @@ async function getInternalConfig(config, command) {
415
462
  finalConfig.vite.plugins.push(
416
463
  virtualEntrypoin("content-script", finalConfig)
417
464
  );
465
+ finalConfig.vite.plugins.push(devServerGlobals(finalConfig));
418
466
  finalConfig.vite.define ??= {};
419
467
  getGlobals(finalConfig).forEach((global) => {
420
468
  finalConfig.vite.define[global.name] = JSON.stringify(global.value);
@@ -422,27 +470,258 @@ async function getInternalConfig(config, command) {
422
470
  return finalConfig;
423
471
  }
424
472
 
425
- // src/core/build/findEntrypoints.ts
426
- import { relative as relative3, resolve as resolve8 } from "path";
473
+ // src/index.ts
474
+ import pc3 from "picocolors";
475
+ import * as vite6 from "vite";
476
+
477
+ // src/core/utils/arrays.ts
478
+ function every(array, predicate) {
479
+ for (let i = 0; i < array.length; i++)
480
+ if (!predicate(array[i], i))
481
+ return false;
482
+ return true;
483
+ }
484
+
485
+ // src/core/utils/detectDevChanges.ts
486
+ function detectDevChanges(changedFiles, currentOutput) {
487
+ if (currentOutput == null)
488
+ return { type: "no-change" };
489
+ const changedSteps = new Set(
490
+ changedFiles.flatMap(
491
+ (changedFile) => findEffectedSteps(changedFile, currentOutput)
492
+ )
493
+ );
494
+ if (changedSteps.size === 0)
495
+ return { type: "no-change" };
496
+ const unchangedOutput = {
497
+ manifest: currentOutput.manifest,
498
+ steps: [],
499
+ publicAssets: []
500
+ };
501
+ const changedOutput = {
502
+ manifest: currentOutput.manifest,
503
+ steps: [],
504
+ publicAssets: []
505
+ };
506
+ for (const step of currentOutput.steps) {
507
+ if (changedSteps.has(step)) {
508
+ changedOutput.steps.push(step);
509
+ } else {
510
+ unchangedOutput.steps.push(step);
511
+ }
512
+ }
513
+ for (const asset of currentOutput.publicAssets) {
514
+ if (changedSteps.has(asset)) {
515
+ changedOutput.publicAssets.push(asset);
516
+ } else {
517
+ unchangedOutput.publicAssets.push(asset);
518
+ }
519
+ }
520
+ const isOnlyHtmlChanges = changedFiles.length > 0 && every(changedFiles, ([_, file]) => file.endsWith(".html"));
521
+ if (isOnlyHtmlChanges) {
522
+ return {
523
+ type: "html-reload",
524
+ cachedOutput: unchangedOutput,
525
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
526
+ };
527
+ }
528
+ const isOnlyContentScripts = changedOutput.steps.length > 0 && every(
529
+ changedOutput.steps.flatMap((step) => step.entrypoints),
530
+ (entry) => entry.type === "content-script"
531
+ );
532
+ if (isOnlyContentScripts) {
533
+ return {
534
+ type: "content-script-reload",
535
+ cachedOutput: unchangedOutput,
536
+ changedSteps: changedOutput.steps,
537
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
538
+ };
539
+ }
540
+ return {
541
+ type: "extension-reload",
542
+ cachedOutput: unchangedOutput,
543
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
544
+ };
545
+ }
546
+ function findEffectedSteps(changedFile, currentOutput) {
547
+ const changes = [];
548
+ const changedPath = changedFile[1];
549
+ const isChunkEffected = (chunk) => (
550
+ // If it's an HTML file with the same path, is is effected because HTML files need to be pre-rendered
551
+ // TODO: use bundle path to support `<name>/index.html`?
552
+ chunk.type === "asset" && changedPath.endsWith(chunk.fileName) || // If it's a chunk that depends on the changed file, it is effected
553
+ chunk.type === "chunk" && chunk.moduleIds.includes(changedPath)
554
+ );
555
+ for (const step of currentOutput.steps) {
556
+ const effectedChunk = step.chunks.find((chunk) => isChunkEffected(chunk));
557
+ if (effectedChunk)
558
+ changes.push(step);
559
+ }
560
+ const effectedAsset = currentOutput.publicAssets.find(
561
+ (chunk) => isChunkEffected(chunk)
562
+ );
563
+ if (effectedAsset)
564
+ changes.push(effectedAsset);
565
+ return changes;
566
+ }
567
+
568
+ // src/index.ts
569
+ import { Mutex } from "async-mutex";
570
+ import { consola as consola3 } from "consola";
571
+ import { relative as relative6 } from "node:path";
572
+
573
+ // src/core/build/buildEntrypoints.ts
574
+ import * as vite3 from "vite";
575
+
576
+ // src/core/utils/removeEmptyDirs.ts
427
577
  import fs4 from "fs-extra";
428
- import picomatch from "picomatch";
429
- import { parseHTML as parseHTML2 } from "linkedom";
430
- import JSON5 from "json5";
578
+ import path3 from "path";
579
+ async function removeEmptyDirs(dir) {
580
+ const files = await fs4.readdir(dir);
581
+ for (const file of files) {
582
+ const filePath = path3.join(dir, file);
583
+ const stats = await fs4.stat(filePath);
584
+ if (stats.isDirectory()) {
585
+ await removeEmptyDirs(filePath);
586
+ }
587
+ }
588
+ try {
589
+ await fs4.rmdir(dir);
590
+ } catch {
591
+ }
592
+ }
593
+
594
+ // src/core/build/buildEntrypoints.ts
431
595
  import glob from "fast-glob";
432
- async function findEntrypoints(config) {
433
- const relativePaths = await glob("**/*", {
434
- cwd: config.entrypointsDir
435
- });
436
- relativePaths.sort();
437
- const pathGlobs = Object.keys(PATH_GLOB_TO_TYPE_MAP);
438
- const existingNames = {};
439
- const entrypoints = [];
440
- await Promise.all(
441
- relativePaths.map(async (relativePath) => {
442
- const path5 = resolve8(config.entrypointsDir, relativePath);
443
- const matchingGlob = pathGlobs.find(
444
- (glob3) => picomatch.isMatch(relativePath, glob3)
445
- );
596
+ import fs5 from "fs-extra";
597
+ import { dirname as dirname4, resolve as resolve8 } from "path";
598
+ async function buildEntrypoints(groups, config) {
599
+ const steps = [];
600
+ for (const group of groups) {
601
+ const step = Array.isArray(group) ? await buildMultipleEntrypoints(group, config) : await buildSingleEntrypoint(group, config);
602
+ steps.push(step);
603
+ }
604
+ const publicAssets = await copyPublicDirectory(config);
605
+ await removeEmptyDirs(config.outDir);
606
+ return { publicAssets, steps };
607
+ }
608
+ async function buildSingleEntrypoint(entrypoint, config) {
609
+ const isVirtual = ["background", "content-script"].includes(entrypoint.type);
610
+ const entry = isVirtual ? `virtual:wxt-${entrypoint.type}?${entrypoint.inputPath}` : entrypoint.inputPath;
611
+ const libMode = {
612
+ build: {
613
+ lib: {
614
+ entry,
615
+ formats: ["iife"],
616
+ name: entrypoint.name,
617
+ fileName: entrypoint.name
618
+ },
619
+ rollupOptions: {
620
+ output: {
621
+ // There's only a single output for this build, so we use the desired bundle path for the
622
+ // entry output (like "content-scripts/overlay.js")
623
+ entryFileNames: getEntrypointBundlePath(
624
+ entrypoint,
625
+ config.outDir,
626
+ ".js"
627
+ ),
628
+ // Output content script CSS to assets/ with a hash to prevent conflicts. Defaults to
629
+ // "[name].[ext]" in lib mode, which usually results in "style.css". That means multiple
630
+ // content scripts with styles would overwrite each other if it weren't changed below.
631
+ assetFileNames: `assets/${entrypoint.name}.[ext]`
632
+ }
633
+ }
634
+ }
635
+ };
636
+ const entryConfig = vite3.mergeConfig(
637
+ libMode,
638
+ config.vite
639
+ );
640
+ const result = await vite3.build(entryConfig);
641
+ return {
642
+ entrypoints: entrypoint,
643
+ chunks: getBuildOutputChunks(result)
644
+ };
645
+ }
646
+ async function buildMultipleEntrypoints(entrypoints, config) {
647
+ const multiPage = {
648
+ plugins: [multipageMove(entrypoints, config)],
649
+ build: {
650
+ rollupOptions: {
651
+ input: entrypoints.reduce((input, entry) => {
652
+ input[entry.name] = entry.inputPath;
653
+ return input;
654
+ }, {}),
655
+ output: {
656
+ // Include a hash to prevent conflicts
657
+ chunkFileNames: "chunks/[name]-[hash].js",
658
+ // Include a hash to prevent conflicts
659
+ entryFileNames: "chunks/[name]-[hash].js",
660
+ // We can't control the "name", so we need a hash to prevent conflicts
661
+ assetFileNames: "assets/[name]-[hash].[ext]"
662
+ }
663
+ }
664
+ }
665
+ };
666
+ const entryConfig = vite3.mergeConfig(
667
+ multiPage,
668
+ config.vite
669
+ );
670
+ const result = await vite3.build(entryConfig);
671
+ return {
672
+ entrypoints,
673
+ chunks: getBuildOutputChunks(result)
674
+ };
675
+ }
676
+ function getBuildOutputChunks(result) {
677
+ if ("on" in result)
678
+ throw Error("wxt does not support vite watch mode.");
679
+ if (Array.isArray(result))
680
+ return result.flatMap(({ output }) => output);
681
+ return result.output;
682
+ }
683
+ async function copyPublicDirectory(config) {
684
+ const publicAssets = [];
685
+ if (!await fs5.exists(config.publicDir))
686
+ return publicAssets;
687
+ const files = await glob("**/*", { cwd: config.publicDir });
688
+ for (const file of files) {
689
+ const srcPath = resolve8(config.publicDir, file);
690
+ const outPath = resolve8(config.outDir, file);
691
+ await fs5.ensureDir(dirname4(outPath));
692
+ await fs5.copyFile(srcPath, outPath);
693
+ publicAssets.push({
694
+ type: "asset",
695
+ fileName: file,
696
+ name: file,
697
+ needsCodeReference: false,
698
+ source: await fs5.readFile(srcPath)
699
+ });
700
+ }
701
+ return publicAssets;
702
+ }
703
+
704
+ // src/core/build/findEntrypoints.ts
705
+ import { relative as relative3, resolve as resolve9 } from "path";
706
+ import fs6 from "fs-extra";
707
+ import picomatch from "picomatch";
708
+ import { parseHTML as parseHTML2 } from "linkedom";
709
+ import JSON5 from "json5";
710
+ import glob2 from "fast-glob";
711
+ async function findEntrypoints(config) {
712
+ const relativePaths = await glob2("**/*", {
713
+ cwd: config.entrypointsDir
714
+ });
715
+ relativePaths.sort();
716
+ const pathGlobs = Object.keys(PATH_GLOB_TO_TYPE_MAP);
717
+ const existingNames = {};
718
+ const entrypoints = [];
719
+ await Promise.all(
720
+ relativePaths.map(async (relativePath) => {
721
+ const path5 = resolve9(config.entrypointsDir, relativePath);
722
+ const matchingGlob = pathGlobs.find(
723
+ (glob3) => picomatch.isMatch(relativePath, glob3)
724
+ );
446
725
  if (matchingGlob == null) {
447
726
  return config.logger.warn(
448
727
  `${relativePath} does not match any known entrypoint. Known entrypoints:
@@ -499,7 +778,7 @@ ${JSON.stringify(
499
778
  }
500
779
  async function getPopupEntrypoint(config, path5) {
501
780
  const options = {};
502
- const content = await fs4.readFile(path5, "utf-8");
781
+ const content = await fs6.readFile(path5, "utf-8");
503
782
  const { document } = parseHTML2(content);
504
783
  const title = document.querySelector("title");
505
784
  if (title != null)
@@ -529,7 +808,7 @@ async function getPopupEntrypoint(config, path5) {
529
808
  }
530
809
  async function getOptionsEntrypoint(config, path5) {
531
810
  const options = {};
532
- const content = await fs4.readFile(path5, "utf-8");
811
+ const content = await fs6.readFile(path5, "utf-8");
533
812
  const { document } = parseHTML2(content);
534
813
  const openInTabContent = document.querySelector("meta[name='manifest.open_in_tab']")?.getAttribute("content");
535
814
  if (openInTabContent) {
@@ -576,7 +855,7 @@ async function getContentScriptEntrypoint(config, name, path5) {
576
855
  type: "content-script",
577
856
  name: getEntrypointName(config.entrypointsDir, path5),
578
857
  inputPath: path5,
579
- outputDir: resolve8(config.outDir, "content-scripts"),
858
+ outputDir: resolve9(config.outDir, "content-scripts"),
580
859
  options
581
860
  };
582
861
  }
@@ -611,160 +890,112 @@ var PATH_GLOB_TO_TYPE_MAP = {
611
890
  "*/*": "ignored"
612
891
  };
613
892
 
614
- // src/core/build/buildEntrypoints.ts
615
- import * as vite2 from "vite";
616
-
617
- // src/core/utils/groupEntrypoints.ts
618
- function groupEntrypoints(entrypoints) {
619
- const groupIndexMap = {};
620
- const groups = [];
621
- for (const entry of entrypoints) {
622
- const group = ENTRY_TYPE_TO_GROUP_MAP[entry.type];
623
- if (group === "no-group") {
624
- groups.push(entry);
625
- } else {
626
- let groupIndex = groupIndexMap[group];
627
- if (groupIndex == null) {
628
- groupIndex = groups.push([]) - 1;
629
- groupIndexMap[group] = groupIndex;
630
- }
631
- groups[groupIndex].push(entry);
632
- }
633
- }
634
- return groups;
635
- }
636
- var ENTRY_TYPE_TO_GROUP_MAP = {
637
- sandbox: "sandbox-page",
638
- popup: "extension-page",
639
- newtab: "extension-page",
640
- history: "extension-page",
641
- options: "extension-page",
642
- devtools: "extension-page",
643
- bookmarks: "extension-page",
644
- sidepanel: "extension-page",
645
- "unlisted-page": "extension-page",
646
- background: "no-group",
647
- "content-script": "no-group",
648
- "unlisted-script": "no-group"
649
- };
650
-
651
- // src/core/utils/removeEmptyDirs.ts
652
- import fs5 from "fs-extra";
653
- import path3 from "path";
654
- async function removeEmptyDirs(dir) {
655
- const files = await fs5.readdir(dir);
656
- for (const file of files) {
657
- const filePath = path3.join(dir, file);
658
- const stats = await fs5.stat(filePath);
659
- if (stats.isDirectory()) {
660
- await removeEmptyDirs(filePath);
661
- }
662
- }
663
- try {
664
- await fs5.rmdir(dir);
665
- } catch {
666
- }
893
+ // src/core/build/generateTypesDir.ts
894
+ import { createUnimport as createUnimport2 } from "unimport";
895
+ import fs7 from "fs-extra";
896
+ import { relative as relative4, resolve as resolve10 } from "path";
897
+ async function generateTypesDir(entrypoints, config) {
898
+ await fs7.ensureDir(config.typesDir);
899
+ const references = [];
900
+ references.push(await writeImportsDeclarationFile(config));
901
+ references.push(await writePathsDeclarationFile(entrypoints, config));
902
+ references.push(await writeGlobalsDeclarationFile(config));
903
+ const mainReference = await writeMainDeclarationFile(references, config);
904
+ await writeTsConfigFile(mainReference, config);
667
905
  }
668
-
669
- // src/core/build/buildEntrypoints.ts
670
- import glob2 from "fast-glob";
671
- import fs6 from "fs-extra";
672
- import { dirname as dirname4, resolve as resolve9 } from "path";
673
- async function buildEntrypoints(entrypoints, config) {
674
- const groups = groupEntrypoints(entrypoints);
675
- const outputs = [];
676
- for (const group of groups) {
677
- const output = Array.isArray(group) ? await buildMultipleEntrypoints(group, config) : await buildSingleEntrypoint(group, config);
678
- outputs.push(output);
679
- }
680
- const publicOutput = await copyPublicDirectory(config);
681
- outputs.push(publicOutput);
682
- await removeEmptyDirs(config.outDir);
683
- return outputs.flat();
906
+ async function writeImportsDeclarationFile(config) {
907
+ const filePath = resolve10(config.typesDir, "imports.d.ts");
908
+ const unimport2 = createUnimport2(getUnimportOptions(config));
909
+ await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
910
+ await fs7.writeFile(
911
+ filePath,
912
+ ["// Generated by wxt", await unimport2.generateTypeDeclarations()].join(
913
+ "\n"
914
+ ) + "\n"
915
+ );
916
+ return filePath;
684
917
  }
685
- async function buildSingleEntrypoint(entrypoint, config) {
686
- const isVirtual = ["background", "content-script"].includes(entrypoint.type);
687
- const entry = isVirtual ? `virtual:wxt-${entrypoint.type}?${entrypoint.inputPath}` : entrypoint.inputPath;
688
- const libMode = {
689
- build: {
690
- lib: {
691
- entry,
692
- formats: ["iife"],
693
- name: entrypoint.name,
694
- fileName: entrypoint.name
695
- },
696
- rollupOptions: {
697
- output: {
698
- entryFileNames: getEntrypointBundlePath(
699
- entrypoint,
700
- config.outDir,
701
- ".js"
702
- ),
703
- // Output content script CSS to assets/ with a hash to prevent conflicts. Defaults to
704
- // "[name].[ext]" in lib mode, which usually results in "style.css". That means multiple
705
- // content scripts with styles would overwrite each other if it weren't changed below.
706
- assetFileNames: `assets/${entrypoint.name}-[hash].[ext]`
707
- }
708
- }
709
- }
710
- };
711
- const entryConfig = vite2.mergeConfig(
712
- libMode,
713
- config.vite
918
+ async function writePathsDeclarationFile(entrypoints, config) {
919
+ const filePath = resolve10(config.typesDir, "paths.d.ts");
920
+ await fs7.writeFile(
921
+ filePath,
922
+ [
923
+ "// Generated by wxt",
924
+ "type EntrypointPath =",
925
+ ...entrypoints.map((entry) => {
926
+ const path5 = getEntrypointBundlePath(
927
+ entry,
928
+ config.outDir,
929
+ entry.inputPath.endsWith(".html") ? ".html" : ".js"
930
+ );
931
+ return ` | "/${path5}"`;
932
+ }).sort()
933
+ ].join("\n") + "\n"
714
934
  );
715
- const result = await vite2.build(entryConfig);
716
- return getBuildOutput(result);
935
+ return filePath;
717
936
  }
718
- async function buildMultipleEntrypoints(entrypoints, config) {
719
- const multiPage = {
720
- plugins: [multipageMove(entrypoints, config)],
721
- build: {
722
- rollupOptions: {
723
- input: entrypoints.reduce((input, entry) => {
724
- input[entry.name] = entry.inputPath;
725
- return input;
726
- }, {})
727
- }
728
- }
729
- };
730
- const entryConfig = vite2.mergeConfig(
731
- multiPage,
732
- config.vite
937
+ async function writeGlobalsDeclarationFile(config) {
938
+ const filePath = resolve10(config.typesDir, "globals.d.ts");
939
+ const globals = getGlobals(config);
940
+ await fs7.writeFile(
941
+ filePath,
942
+ [
943
+ "// Generated by wxt",
944
+ "export {}",
945
+ "declare global {",
946
+ ...globals.map((global) => ` const ${global.name}: ${global.type};`),
947
+ "}"
948
+ ].join("\n") + "\n",
949
+ "utf-8"
733
950
  );
734
- const result = await vite2.build(entryConfig);
735
- return getBuildOutput(result);
951
+ return filePath;
736
952
  }
737
- function getBuildOutput(result) {
738
- if ("on" in result)
739
- throw Error("wxt does not support vite watch mode.");
740
- if (Array.isArray(result))
741
- return result.flatMap(({ output }) => output);
742
- return result.output;
953
+ async function writeMainDeclarationFile(references, config) {
954
+ const dir = config.wxtDir;
955
+ const filePath = resolve10(dir, "wxt.d.ts");
956
+ await fs7.writeFile(
957
+ filePath,
958
+ [
959
+ "// Generated by wxt",
960
+ ...references.map(
961
+ (ref) => `/// <reference types="./${relative4(dir, ref)}" />`
962
+ )
963
+ ].join("\n") + "\n"
964
+ );
965
+ return filePath;
743
966
  }
744
- async function copyPublicDirectory(config) {
745
- if (!await fs6.exists(config.publicDir))
746
- return [];
747
- const files = await glob2("**/*", { cwd: config.publicDir });
748
- const outputs = [];
749
- for (const file of files) {
750
- const srcPath = resolve9(config.publicDir, file);
751
- const outPath = resolve9(config.outDir, file);
752
- await fs6.ensureDir(dirname4(outPath));
753
- await fs6.copyFile(srcPath, outPath);
754
- outputs.push({
755
- type: "asset",
756
- fileName: file,
757
- name: file,
758
- needsCodeReference: false,
759
- source: await fs6.readFile(srcPath)
760
- });
761
- }
762
- return outputs;
967
+ async function writeTsConfigFile(mainReference, config) {
968
+ const dir = config.wxtDir;
969
+ await fs7.writeFile(
970
+ resolve10(dir, "tsconfig.json"),
971
+ `{
972
+ "compilerOptions": {
973
+ "target": "ESNext",
974
+ "module": "ESNext",
975
+ "moduleResolution": "Bundler",
976
+ "noEmit": true,
977
+ "esModuleInterop": true,
978
+ "forceConsistentCasingInFileNames": true,
979
+ "resolveJsonModule": true,
980
+
981
+ /* Type Checking */
982
+ "strict": true,
983
+
984
+ /* Completeness */
985
+ "skipLibCheck": true
986
+ },
987
+ "include": [
988
+ "${relative4(dir, config.root)}/**/*",
989
+ "./${relative4(dir, mainReference)}"
990
+ ],
991
+ "exclude": ["${relative4(dir, config.outBaseDir)}"]
992
+ }`
993
+ );
763
994
  }
764
995
 
765
996
  // src/core/utils/manifest.ts
766
- import fs7 from "fs-extra";
767
- import { resolve as resolve10 } from "path";
997
+ import fs8 from "fs-extra";
998
+ import { resolve as resolve11 } from "path";
768
999
 
769
1000
  // src/core/utils/ContentSecurityPolicy.ts
770
1001
  var ContentSecurityPolicy = class _ContentSecurityPolicy {
@@ -812,9 +1043,9 @@ var ContentSecurityPolicy = class _ContentSecurityPolicy {
812
1043
  // src/core/utils/manifest.ts
813
1044
  async function writeManifest(manifest, output, config) {
814
1045
  const str = config.mode === "production" ? JSON.stringify(manifest) : JSON.stringify(manifest, null, 2);
815
- await fs7.ensureDir(config.outDir);
816
- await fs7.writeFile(resolve10(config.outDir, "manifest.json"), str, "utf-8");
817
- output.unshift({
1046
+ await fs8.ensureDir(config.outDir);
1047
+ await fs8.writeFile(resolve11(config.outDir, "manifest.json"), str, "utf-8");
1048
+ output.publicAssets.unshift({
818
1049
  type: "asset",
819
1050
  fileName: "manifest.json",
820
1051
  name: "manifest",
@@ -841,10 +1072,12 @@ async function generateMainfest(entrypoints, buildOutput, config) {
841
1072
  addEntrypoints(manifest, entrypoints, buildOutput, config);
842
1073
  if (config.command === "serve")
843
1074
  addDevModeCsp(manifest, config);
1075
+ if (config.command === "serve")
1076
+ addDevModePermissions(manifest, config);
844
1077
  return manifest;
845
1078
  }
846
1079
  async function getPackageJson(config) {
847
- return await fs7.readJson(resolve10(config.root, "package.json"));
1080
+ return await fs8.readJson(resolve11(config.root, "package.json"));
848
1081
  }
849
1082
  function simplifyVersion(versionName) {
850
1083
  const version3 = /^((0|[1-9][0-9]{0,8})([.](0|[1-9][0-9]{0,8})){0,3}).*$/.exec(
@@ -997,23 +1230,23 @@ function addEntrypoints(manifest, entrypoints, buildOutput, config) {
997
1230
  }
998
1231
  }
999
1232
  if (contentScripts?.length) {
1000
- if (config.command === "serve") {
1001
- const permissionsKey = config.manifestVersion === 2 ? "permissions" : "host_permissions";
1002
- const hostPermissions = new Set(manifest[permissionsKey] ?? []);
1233
+ if (config.command === "serve" && config.manifestVersion === 3) {
1234
+ const hostPermissions = new Set(manifest.host_permissions ?? []);
1003
1235
  contentScripts.forEach((script) => {
1004
1236
  script.options.matches.forEach((matchPattern) => {
1005
1237
  hostPermissions.add(matchPattern);
1006
1238
  });
1007
1239
  });
1008
- manifest[permissionsKey] = Array.from(hostPermissions).sort();
1240
+ hostPermissions.forEach(
1241
+ (permission) => addHostPermission(manifest, permission)
1242
+ );
1009
1243
  } else {
1010
1244
  const hashToEntrypointsMap = contentScripts.reduce((map, script) => {
1011
1245
  const hash = JSON.stringify(script.options);
1012
- if (!map.has(hash)) {
1013
- map.set(hash, [script]);
1014
- } else {
1246
+ if (map.has(hash))
1015
1247
  map.get(hash)?.push(script);
1016
- }
1248
+ else
1249
+ map.set(hash, [script]);
1017
1250
  return map;
1018
1251
  }, /* @__PURE__ */ new Map());
1019
1252
  manifest.content_scripts = Array.from(hashToEntrypointsMap.entries()).map(
@@ -1034,13 +1267,9 @@ function addDevModeCsp(manifest, config) {
1034
1267
  const permission = `http://${config.server?.hostname ?? ""}/*`;
1035
1268
  const allowedCsp = config.server?.origin ?? "http://localhost:*";
1036
1269
  if (manifest.manifest_version === 3) {
1037
- manifest.host_permissions ??= [];
1038
- if (!manifest.host_permissions.includes(permission))
1039
- manifest.host_permissions.push(permission);
1270
+ addHostPermission(manifest, permission);
1040
1271
  } else {
1041
- manifest.permissions ??= [];
1042
- if (!manifest.permissions.includes(permission))
1043
- manifest.permissions.push(permission);
1272
+ addPermission(manifest, permission);
1044
1273
  }
1045
1274
  const csp = new ContentSecurityPolicy(
1046
1275
  manifest.manifest_version === 3 ? (
@@ -1058,12 +1287,17 @@ function addDevModeCsp(manifest, config) {
1058
1287
  manifest.content_security_policy = csp.toString();
1059
1288
  }
1060
1289
  }
1290
+ function addDevModePermissions(manifest, config) {
1291
+ addPermission(manifest, "tabs");
1292
+ if (config.manifestVersion === 3)
1293
+ addPermission(manifest, "scripting");
1294
+ }
1061
1295
  function getContentScriptCssFiles(contentScripts, buildOutput) {
1062
1296
  const css = [];
1297
+ const allChunks = buildOutput.steps.flatMap((step) => step.chunks);
1063
1298
  contentScripts.forEach((script) => {
1064
- const cssRegex = new RegExp(`^assets/${script.name}-[a-f0-9]{8}.css$`);
1065
- const relatedCss = buildOutput.find(
1066
- (chunk) => chunk.fileName.match(cssRegex)
1299
+ const relatedCss = allChunks.find(
1300
+ (chunk) => chunk.fileName === `assets/${script.name}.css`
1067
1301
  );
1068
1302
  if (relatedCss)
1069
1303
  css.push(relatedCss.fileName);
@@ -1072,9 +1306,71 @@ function getContentScriptCssFiles(contentScripts, buildOutput) {
1072
1306
  return css;
1073
1307
  return void 0;
1074
1308
  }
1309
+ function addPermission(manifest, permission) {
1310
+ manifest.permissions ??= [];
1311
+ if (manifest.permissions.includes(permission))
1312
+ return;
1313
+ manifest.permissions.push(permission);
1314
+ }
1315
+ function addHostPermission(manifest, hostPermission) {
1316
+ manifest.host_permissions ??= [];
1317
+ if (manifest.host_permissions.includes(hostPermission))
1318
+ return;
1319
+ manifest.host_permissions.push(hostPermission);
1320
+ }
1321
+
1322
+ // src/core/build.ts
1323
+ import pc2 from "picocolors";
1324
+ import * as vite4 from "vite";
1325
+ import fs10 from "fs-extra";
1326
+
1327
+ // src/core/utils/groupEntrypoints.ts
1328
+ function groupEntrypoints(entrypoints) {
1329
+ const groupIndexMap = {};
1330
+ const groups = [];
1331
+ for (const entry of entrypoints) {
1332
+ const group = ENTRY_TYPE_TO_GROUP_MAP[entry.type];
1333
+ if (group === "no-group") {
1334
+ groups.push(entry);
1335
+ } else {
1336
+ let groupIndex = groupIndexMap[group];
1337
+ if (groupIndex == null) {
1338
+ groupIndex = groups.push([]) - 1;
1339
+ groupIndexMap[group] = groupIndex;
1340
+ }
1341
+ groups[groupIndex].push(entry);
1342
+ }
1343
+ }
1344
+ return groups;
1345
+ }
1346
+ var ENTRY_TYPE_TO_GROUP_MAP = {
1347
+ sandbox: "sandbox-page",
1348
+ popup: "extension-page",
1349
+ newtab: "extension-page",
1350
+ history: "extension-page",
1351
+ options: "extension-page",
1352
+ devtools: "extension-page",
1353
+ bookmarks: "extension-page",
1354
+ sidepanel: "extension-page",
1355
+ "unlisted-page": "extension-page",
1356
+ background: "no-group",
1357
+ "content-script": "no-group",
1358
+ "unlisted-script": "no-group"
1359
+ };
1360
+
1361
+ // src/core/utils/formatDuration.ts
1362
+ function formatDuration(duration) {
1363
+ if (duration < 1e3)
1364
+ return `${duration} ms`;
1365
+ if (duration < 1e4)
1366
+ return `${(duration / 1e3).toFixed(3)} s`;
1367
+ if (duration < 6e4)
1368
+ return `${(duration / 1e3).toFixed(1)} s`;
1369
+ return `${(duration / 1e3).toFixed(0)} s`;
1370
+ }
1075
1371
 
1076
1372
  // src/core/log/printBuildSummary.ts
1077
- import path4, { extname as extname2, relative as relative4, resolve as resolve11 } from "path";
1373
+ import path4, { extname as extname2, relative as relative5, resolve as resolve12 } from "path";
1078
1374
 
1079
1375
  // src/core/log/printTable.ts
1080
1376
  function printTable(log, rows, gap = 2) {
@@ -1104,10 +1400,13 @@ function printTable(log, rows, gap = 2) {
1104
1400
 
1105
1401
  // src/core/log/printBuildSummary.ts
1106
1402
  import pc from "picocolors";
1107
- import fs8 from "fs-extra";
1403
+ import fs9 from "fs-extra";
1108
1404
  import { filesize } from "filesize";
1109
1405
  async function printBuildSummary(output, config) {
1110
- const chunks = output.sort((l, r) => {
1406
+ const chunks = [
1407
+ ...output.steps.flatMap((step) => step.chunks),
1408
+ ...output.publicAssets
1409
+ ].sort((l, r) => {
1111
1410
  const lWeight = CHUNK_SORT_WEIGHTS[l.fileName] ?? CHUNK_SORT_WEIGHTS[extname2(l.fileName)] ?? DEFAULT_SORT_WEIGHT;
1112
1411
  const rWeight = CHUNK_SORT_WEIGHTS[r.fileName] ?? CHUNK_SORT_WEIGHTS[extname2(r.fileName)] ?? DEFAULT_SORT_WEIGHT;
1113
1412
  const diff = lWeight - rWeight;
@@ -1119,13 +1418,13 @@ async function printBuildSummary(output, config) {
1119
1418
  const chunkRows = await Promise.all(
1120
1419
  chunks.map(async (chunk, i) => {
1121
1420
  const file = [
1122
- relative4(process.cwd(), config.outDir) + path4.sep,
1421
+ relative5(process.cwd(), config.outDir) + path4.sep,
1123
1422
  chunk.fileName
1124
1423
  ];
1125
1424
  const ext = extname2(chunk.fileName);
1126
1425
  const prefix = i === chunks.length - 1 ? " \u2514\u2500" : " \u251C\u2500";
1127
1426
  const color = CHUNK_COLORS[ext] ?? DEFAULT_COLOR;
1128
- const stats = await fs8.lstat(resolve11(config.outDir, chunk.fileName));
1427
+ const stats = await fs9.lstat(resolve12(config.outDir, chunk.fileName));
1129
1428
  totalSize += stats.size;
1130
1429
  const size = String(filesize(stats.size));
1131
1430
  return [
@@ -1153,115 +1452,63 @@ var CHUNK_COLORS = {
1153
1452
  ".js": pc.cyan
1154
1453
  };
1155
1454
 
1156
- // src/index.ts
1157
- import fs10 from "fs-extra";
1158
-
1159
- // src/core/build/generateTypesDir.ts
1160
- import { createUnimport as createUnimport2 } from "unimport";
1161
- import fs9 from "fs-extra";
1162
- import { relative as relative5, resolve as resolve12 } from "path";
1163
- async function generateTypesDir(entrypoints, config) {
1164
- await fs9.ensureDir(config.typesDir);
1165
- const references = [];
1166
- references.push(await writeImportsDeclarationFile(config));
1167
- references.push(await writePathsDeclarationFile(entrypoints, config));
1168
- references.push(await writeGlobalsDeclarationFile(config));
1169
- const mainReference = await writeMainDeclarationFile(references, config);
1170
- await writeTsConfigFile(mainReference, config);
1171
- }
1172
- async function writeImportsDeclarationFile(config) {
1173
- const filePath = resolve12(config.typesDir, "imports.d.ts");
1174
- const unimport2 = createUnimport2(getUnimportOptions(config));
1175
- await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
1176
- await fs9.writeFile(
1177
- filePath,
1178
- ["// Generated by wxt", await unimport2.generateTypeDeclarations()].join(
1179
- "\n"
1180
- ) + "\n"
1181
- );
1182
- return filePath;
1183
- }
1184
- async function writePathsDeclarationFile(entrypoints, config) {
1185
- const filePath = resolve12(config.typesDir, "paths.d.ts");
1186
- await fs9.writeFile(
1187
- filePath,
1188
- [
1189
- "// Generated by wxt",
1190
- "type EntrypointPath =",
1191
- ...entrypoints.map((entry) => {
1192
- const path5 = getEntrypointBundlePath(
1193
- entry,
1194
- config.outDir,
1195
- entry.inputPath.endsWith(".html") ? ".html" : ".js"
1196
- );
1197
- return ` | "/${path5}"`;
1198
- }).sort()
1199
- ].join("\n") + "\n"
1200
- );
1201
- return filePath;
1202
- }
1203
- async function writeGlobalsDeclarationFile(config) {
1204
- const filePath = resolve12(config.typesDir, "globals.d.ts");
1205
- const globals = getGlobals(config);
1206
- await fs9.writeFile(
1207
- filePath,
1208
- [
1209
- "// Generated by wxt",
1210
- "export {}",
1211
- "declare global {",
1212
- ...globals.map((global) => ` const ${global.name}: ${global.type};`),
1213
- "}"
1214
- ].join("\n") + "\n",
1215
- "utf-8"
1455
+ // src/core/build.ts
1456
+ async function buildInternal(config) {
1457
+ const verb = config.command === "serve" ? "Pre-rendering" : "Building";
1458
+ const target = `${config.browser}-mv${config.manifestVersion}`;
1459
+ config.logger.info(
1460
+ `${verb} ${pc2.cyan(target)} for ${pc2.cyan(config.mode)} with ${pc2.green(
1461
+ `Vite ${vite4.version}`
1462
+ )}`
1216
1463
  );
1217
- return filePath;
1218
- }
1219
- async function writeMainDeclarationFile(references, config) {
1220
- const dir = config.wxtDir;
1221
- const filePath = resolve12(dir, "wxt.d.ts");
1222
- await fs9.writeFile(
1223
- filePath,
1224
- [
1225
- "// Generated by wxt",
1226
- ...references.map(
1227
- (ref) => `/// <reference types="./${relative5(dir, ref)}" />`
1228
- )
1229
- ].join("\n") + "\n"
1464
+ const startTime = Date.now();
1465
+ await fs10.rm(config.outDir, { recursive: true, force: true });
1466
+ await fs10.ensureDir(config.outDir);
1467
+ const entrypoints = await findEntrypoints(config);
1468
+ const groups = groupEntrypoints(entrypoints);
1469
+ const { output } = await rebuild(config, groups);
1470
+ config.logger.success(
1471
+ `Built extension in ${formatDuration(Date.now() - startTime)}`
1230
1472
  );
1231
- return filePath;
1473
+ await printBuildSummary(output, config);
1474
+ return output;
1232
1475
  }
1233
- async function writeTsConfigFile(mainReference, config) {
1234
- const dir = config.wxtDir;
1235
- await fs9.writeFile(
1236
- resolve12(dir, "tsconfig.json"),
1237
- `{
1238
- "compilerOptions": {
1239
- "target": "ESNext",
1240
- "module": "ESNext",
1241
- "moduleResolution": "Bundler",
1242
- "noEmit": true,
1243
- "esModuleInterop": true,
1244
- "forceConsistentCasingInFileNames": true,
1245
- "resolveJsonModule": true,
1246
-
1247
- /* Type Checking */
1248
- "strict": true,
1249
-
1250
- /* Completeness */
1251
- "skipLibCheck": true
1252
- },
1253
- "include": [
1254
- "${relative5(dir, config.root)}/**/*",
1255
- "./${relative5(dir, mainReference)}"
1256
- ],
1257
- "exclude": ["${relative5(dir, config.outBaseDir)}"]
1258
- }`
1476
+ async function rebuild(config, entrypointGroups, existingOutput = {
1477
+ steps: [],
1478
+ publicAssets: []
1479
+ }) {
1480
+ const allEntrypoints = await findEntrypoints(config);
1481
+ await generateTypesDir(allEntrypoints, config);
1482
+ const newOutput = await buildEntrypoints(entrypointGroups, config);
1483
+ const mergedOutput = {
1484
+ steps: [...existingOutput.steps, ...newOutput.steps],
1485
+ publicAssets: [...existingOutput.publicAssets, ...newOutput.publicAssets]
1486
+ };
1487
+ const newManifest = await generateMainfest(
1488
+ allEntrypoints,
1489
+ mergedOutput,
1490
+ config
1259
1491
  );
1492
+ const finalOutput = {
1493
+ manifest: newManifest,
1494
+ ...newOutput
1495
+ };
1496
+ await writeManifest(newManifest, finalOutput, config);
1497
+ return {
1498
+ output: {
1499
+ manifest: newManifest,
1500
+ steps: [...existingOutput.steps, ...finalOutput.steps],
1501
+ publicAssets: [
1502
+ ...existingOutput.publicAssets,
1503
+ ...finalOutput.publicAssets
1504
+ ]
1505
+ },
1506
+ manifest: newManifest
1507
+ };
1260
1508
  }
1261
1509
 
1262
- // src/index.ts
1263
- import pc2 from "picocolors";
1264
- import * as vite3 from "vite";
1510
+ // src/core/server.ts
1511
+ import * as vite5 from "vite";
1265
1512
 
1266
1513
  // src/core/utils/findOpenPort.ts
1267
1514
  import net from "node:net";
@@ -1286,17 +1533,6 @@ function findOpenPortRecursive(port, startPort, endPort) {
1286
1533
  });
1287
1534
  }
1288
1535
 
1289
- // src/core/utils/formatDuration.ts
1290
- function formatDuration(duration) {
1291
- if (duration < 1e3)
1292
- return `${duration} ms`;
1293
- if (duration < 1e4)
1294
- return `${(duration / 1e3).toFixed(3)} s`;
1295
- if (duration < 6e4)
1296
- return `${(duration / 1e3).toFixed(1)} s`;
1297
- return `${(duration / 1e3).toFixed(0)} s`;
1298
- }
1299
-
1300
1536
  // src/core/runners/createWebExtRunner.ts
1301
1537
  function createWebExtRunner() {
1302
1538
  let runner;
@@ -1354,8 +1590,93 @@ function createWebExtRunner() {
1354
1590
  var WARN_LOG_LEVEL = 40;
1355
1591
  var ERROR_LOG_LEVEL = 50;
1356
1592
 
1593
+ // src/core/server.ts
1594
+ async function getServerInfo() {
1595
+ const port = await findOpenPort(3e3, 3010);
1596
+ const hostname = "localhost";
1597
+ const origin = `http://${hostname}:${port}`;
1598
+ const serverConfig = {
1599
+ server: {
1600
+ origin
1601
+ }
1602
+ };
1603
+ return {
1604
+ port,
1605
+ hostname,
1606
+ origin,
1607
+ viteServerConfig: serverConfig
1608
+ };
1609
+ }
1610
+ async function setupServer(serverInfo, config) {
1611
+ const runner = createWebExtRunner();
1612
+ const viteServer = await vite5.createServer(
1613
+ vite5.mergeConfig(serverInfo, config.vite)
1614
+ );
1615
+ const start = async () => {
1616
+ await viteServer.listen(server.port);
1617
+ config.logger.success(`Started dev server @ ${serverInfo.origin}`);
1618
+ server.currentOutput = await buildInternal(config);
1619
+ config.logger.info("Opening browser...");
1620
+ await runner.openBrowser(config);
1621
+ config.logger.success("Opened!");
1622
+ };
1623
+ const reloadExtension = () => {
1624
+ viteServer.ws.send("wxt:reload-extension");
1625
+ };
1626
+ const reloadPage = (path5) => {
1627
+ viteServer.ws.send("wxt:reload-page", path5);
1628
+ };
1629
+ const reloadContentScript = (contentScript) => {
1630
+ viteServer.ws.send("wxt:reload-content-script", contentScript);
1631
+ };
1632
+ const server = {
1633
+ ...viteServer,
1634
+ start,
1635
+ currentOutput: {
1636
+ manifest: {
1637
+ manifest_version: 3,
1638
+ name: "",
1639
+ version: ""
1640
+ },
1641
+ publicAssets: [],
1642
+ steps: []
1643
+ },
1644
+ port: serverInfo.port,
1645
+ hostname: serverInfo.hostname,
1646
+ origin: serverInfo.origin,
1647
+ reloadExtension,
1648
+ reloadPage,
1649
+ reloadContentScript
1650
+ };
1651
+ return server;
1652
+ }
1653
+ function reloadContentScripts(steps, config, server) {
1654
+ if (config.manifestVersion === 3) {
1655
+ steps.forEach((step) => {
1656
+ const entry = step.entrypoints;
1657
+ if (Array.isArray(entry) || entry.type !== "content-script")
1658
+ return;
1659
+ const js = [getEntrypointBundlePath(entry, config.outDir, ".js")];
1660
+ const css = getContentScriptCssFiles([entry], server.currentOutput);
1661
+ server.reloadContentScript({
1662
+ js,
1663
+ css,
1664
+ ...entry.options
1665
+ });
1666
+ });
1667
+ } else {
1668
+ server.reloadExtension();
1669
+ }
1670
+ }
1671
+ function reloadHtmlPages(groups, server, config) {
1672
+ groups.flat().forEach((entry) => {
1673
+ const path5 = getEntrypointBundlePath(entry, config.outDir, ".html");
1674
+ server.reloadPage(path5);
1675
+ });
1676
+ }
1677
+
1357
1678
  // package.json
1358
- var version = "0.0.1";
1679
+ var version2 = "0.1.0";
1359
1680
 
1360
1681
  // src/core/utils/defineConfig.ts
1361
1682
  function defineConfig(config) {
@@ -1373,69 +1694,69 @@ async function build2(config) {
1373
1694
  return await buildInternal(internalConfig);
1374
1695
  }
1375
1696
  async function createServer2(config) {
1376
- const port = await findOpenPort(3e3, 3010);
1377
- const hostname = "localhost";
1378
- const origin = `http://${hostname}:${port}`;
1379
- const serverConfig = {
1380
- server: {
1381
- origin
1382
- }
1383
- };
1384
- const internalConfig = await getInternalConfig(
1385
- vite3.mergeConfig(serverConfig, config ?? {}),
1386
- "serve"
1387
- );
1388
- const runner = createWebExtRunner();
1389
- const viteServer = await vite3.createServer(internalConfig.vite);
1390
- const server = {
1391
- ...viteServer,
1392
- async listen(port2, isRestart) {
1393
- const res = await viteServer.listen(port2, isRestart);
1394
- if (!isRestart) {
1395
- internalConfig.logger.success(`Started dev server @ ${origin}`);
1396
- internalConfig.logger.info("Opening browser...");
1397
- await runner.openBrowser(internalConfig);
1398
- internalConfig.logger.success("Opened!");
1399
- }
1400
- return res;
1401
- },
1402
- logger: internalConfig.logger,
1403
- port,
1404
- hostname,
1405
- origin
1697
+ const serverInfo = await getServerInfo();
1698
+ const getLatestInternalConfig = () => {
1699
+ const viteConfig = vite6.mergeConfig(
1700
+ serverInfo.viteServerConfig,
1701
+ config?.vite ?? {}
1702
+ );
1703
+ return getInternalConfig({ ...config, vite: viteConfig }, "serve");
1406
1704
  };
1407
- internalConfig.logger.info("Created dev server");
1705
+ let internalConfig = await getLatestInternalConfig();
1706
+ const server = await setupServer(serverInfo, internalConfig);
1408
1707
  internalConfig.server = server;
1409
- await buildInternal(internalConfig);
1708
+ const fileChangedMutex = new Mutex();
1709
+ const changeQueue = [];
1710
+ server.ws.on("wxt:background-initialized", () => {
1711
+ reloadContentScripts(server.currentOutput.steps, internalConfig, server);
1712
+ });
1713
+ server.watcher.on("all", async (event, path5, _stats) => {
1714
+ if (path5.startsWith(internalConfig.outBaseDir))
1715
+ return;
1716
+ changeQueue.push([event, path5]);
1717
+ await fileChangedMutex.runExclusive(async () => {
1718
+ const fileChanges = changeQueue.splice(0, changeQueue.length);
1719
+ const changes = detectDevChanges(fileChanges, server.currentOutput);
1720
+ if (changes.type === "no-change")
1721
+ return;
1722
+ consola3.info(
1723
+ `Changed: ${Array.from(new Set(fileChanges.map((change) => change[1]))).map((file) => pc3.dim(relative6(internalConfig.root, file))).join(", ")}`
1724
+ );
1725
+ const rebuiltNames = changes.rebuildGroups.flat().map((entry) => {
1726
+ return pc3.cyan(
1727
+ relative6(internalConfig.outDir, getEntrypointOutputFile(entry, ""))
1728
+ );
1729
+ }).join(pc3.dim(", "));
1730
+ internalConfig = await getLatestInternalConfig();
1731
+ internalConfig.server = server;
1732
+ const { output: newOutput } = await rebuild(
1733
+ internalConfig,
1734
+ // TODO: this excludes new entrypoints, so they're not built until the dev command is restarted
1735
+ changes.rebuildGroups,
1736
+ changes.cachedOutput
1737
+ );
1738
+ server.currentOutput = newOutput;
1739
+ switch (changes.type) {
1740
+ case "extension-reload":
1741
+ server.reloadExtension();
1742
+ break;
1743
+ case "html-reload":
1744
+ reloadHtmlPages(changes.rebuildGroups, server, internalConfig);
1745
+ break;
1746
+ case "content-script-reload":
1747
+ reloadContentScripts(changes.changedSteps, internalConfig, server);
1748
+ break;
1749
+ }
1750
+ consola3.success(`Reloaded: ${rebuiltNames}`);
1751
+ });
1752
+ });
1410
1753
  return server;
1411
1754
  }
1412
- async function buildInternal(config) {
1413
- const verb = config.command === "serve" ? "Pre-rendering" : "Building";
1414
- const target = `${config.browser}-mv${config.manifestVersion}`;
1415
- config.logger.info(
1416
- `${verb} ${pc2.cyan(target)} for ${pc2.cyan(config.mode)} with ${pc2.green(
1417
- `Vite ${vite3.version}`
1418
- )}`
1419
- );
1420
- const startTime = Date.now();
1421
- await fs10.rm(config.outDir, { recursive: true, force: true });
1422
- await fs10.ensureDir(config.outDir);
1423
- const entrypoints = await findEntrypoints(config);
1424
- await generateTypesDir(entrypoints, config);
1425
- const output = await buildEntrypoints(entrypoints, config);
1426
- const manifest = await generateMainfest(entrypoints, output, config);
1427
- await writeManifest(manifest, output, config);
1428
- config.logger.success(
1429
- `Built extension in ${formatDuration(Date.now() - startTime)}`
1430
- );
1431
- await printBuildSummary(output, config);
1432
- return output;
1433
- }
1434
1755
  export {
1435
1756
  build2 as build,
1436
1757
  createServer2 as createServer,
1437
1758
  defineConfig,
1438
1759
  defineRunnerConfig,
1439
- version
1760
+ version2 as version
1440
1761
  };
1441
1762
  //# sourceMappingURL=index.js.map