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.cjs CHANGED
@@ -34,13 +34,13 @@ __export(src_exports, {
34
34
  createServer: () => createServer2,
35
35
  defineConfig: () => defineConfig,
36
36
  defineRunnerConfig: () => defineRunnerConfig,
37
- version: () => version
37
+ version: () => version2
38
38
  });
39
39
  module.exports = __toCommonJS(src_exports);
40
40
 
41
41
  // src/core/utils/getInternalConfig.ts
42
42
  var import_node_path3 = __toESM(require("path"), 1);
43
- var vite = __toESM(require("vite"), 1);
43
+ var vite2 = __toESM(require("vite"), 1);
44
44
  var import_consola2 = require("consola");
45
45
 
46
46
  // src/core/utils/importTsFile.ts
@@ -51,7 +51,7 @@ var import_path = require("path");
51
51
  var import_unimport = require("unimport");
52
52
  async function importTsFile(root, path5) {
53
53
  const clientImports = await (0, import_unimport.scanExports)(
54
- (0, import_path.resolve)(root, "node_modules/wxt/dist/client/index.js")
54
+ (0, import_path.resolve)(root, "node_modules/wxt/dist/client.js")
55
55
  );
56
56
  const jiti = (0, import_jiti.default)(__filename, {
57
57
  cache: false,
@@ -78,6 +78,106 @@ async function importTsFile(root, path5) {
78
78
  }
79
79
  }
80
80
 
81
+ // src/core/vite-plugins/devHtmlPrerender.ts
82
+ var vite = __toESM(require("vite"), 1);
83
+
84
+ // src/core/utils/entrypoints.ts
85
+ var import_node_path = __toESM(require("path"), 1);
86
+ function getEntrypointName(entrypointsDir, inputPath) {
87
+ const relativePath = import_node_path.default.relative(entrypointsDir, inputPath);
88
+ const name = relativePath.split(/[\.\/]/, 2)[0];
89
+ return name;
90
+ }
91
+ function getEntrypointOutputFile(entrypoint, ext) {
92
+ return (0, import_node_path.resolve)(entrypoint.outputDir, `${entrypoint.name}${ext}`);
93
+ }
94
+ function getEntrypointBundlePath(entrypoint, outDir, ext) {
95
+ return (0, import_node_path.relative)(outDir, getEntrypointOutputFile(entrypoint, ext));
96
+ }
97
+
98
+ // src/core/vite-plugins/devHtmlPrerender.ts
99
+ var import_linkedom = require("linkedom");
100
+ var import_path2 = require("path");
101
+ function devHtmlPrerender(config) {
102
+ return {
103
+ apply: "build",
104
+ name: "wxt:dev-html-prerender",
105
+ config(userConfig) {
106
+ return vite.mergeConfig(
107
+ {
108
+ resolve: {
109
+ alias: {
110
+ "@wxt/reload-html": (0, import_path2.resolve)(
111
+ config.root,
112
+ "node_modules/wxt/dist/virtual-modules/reload-html.js"
113
+ )
114
+ }
115
+ }
116
+ },
117
+ userConfig
118
+ );
119
+ },
120
+ async transform(html, id) {
121
+ const server = config.server;
122
+ if (config.command !== "serve" || server == null || !id.endsWith(".html"))
123
+ return;
124
+ const originalUrl = `${server.origin}${id}`;
125
+ const name = getEntrypointName(config.entrypointsDir, id);
126
+ const url = `${server.origin}/${name}.html`;
127
+ const serverHtml = await server.transformIndexHtml(
128
+ url,
129
+ html,
130
+ originalUrl
131
+ );
132
+ const { document } = (0, import_linkedom.parseHTML)(serverHtml);
133
+ const pointToDevServer = (querySelector, attr) => {
134
+ document.querySelectorAll(querySelector).forEach((element) => {
135
+ const src = element.getAttribute(attr);
136
+ if (!src)
137
+ return;
138
+ if ((0, import_path2.isAbsolute)(src)) {
139
+ element.setAttribute(attr, server.origin + src);
140
+ } else if (src.startsWith(".")) {
141
+ const abs = (0, import_path2.resolve)((0, import_path2.dirname)(id), src);
142
+ const pathname = (0, import_path2.relative)(config.root, abs);
143
+ element.setAttribute(attr, `${server.origin}/${pathname}`);
144
+ }
145
+ });
146
+ };
147
+ pointToDevServer("script[type=module]", "src");
148
+ pointToDevServer("link[rel=stylesheet]", "href");
149
+ const reloader = document.createElement("script");
150
+ reloader.src = "@wxt/reload-html";
151
+ reloader.type = "module";
152
+ document.head.appendChild(reloader);
153
+ const newHtml = document.toString();
154
+ config.logger.debug("Transformed " + id);
155
+ config.logger.debug("Old HTML:\n" + html);
156
+ config.logger.debug("New HTML:\n" + newHtml);
157
+ return newHtml;
158
+ }
159
+ };
160
+ }
161
+
162
+ // src/core/vite-plugins/devServerGlobals.ts
163
+ function devServerGlobals(internalConfig) {
164
+ return {
165
+ name: "wxt:dev-server-globals",
166
+ config(config) {
167
+ if (internalConfig.server == null || internalConfig.command == "build")
168
+ return;
169
+ config.define ??= {};
170
+ config.define.__DEV_SERVER_PROTOCOL__ = JSON.stringify("ws:");
171
+ config.define.__DEV_SERVER_HOSTNAME__ = JSON.stringify(
172
+ internalConfig.server.hostname
173
+ );
174
+ config.define.__DEV_SERVER_PORT__ = JSON.stringify(
175
+ internalConfig.server.port
176
+ );
177
+ }
178
+ };
179
+ }
180
+
81
181
  // src/core/utils/network.ts
82
182
  var import_node_dns = __toESM(require("dns"), 1);
83
183
 
@@ -147,61 +247,8 @@ function download(config) {
147
247
  };
148
248
  }
149
249
 
150
- // src/core/vite-plugins/unimport.ts
151
- var import_unimport2 = require("unimport");
152
-
153
- // src/core/utils/auto-imports.ts
154
- var import_vite = require("vite");
155
- function getUnimportOptions(config) {
156
- const defaultOptions = {
157
- debugLog: config.logger.debug,
158
- imports: [
159
- { name: "*", as: "browser", from: "webextension-polyfill" },
160
- { name: "defineConfig", from: "wxt" }
161
- ],
162
- presets: [{ package: "wxt/client" }],
163
- warn: config.logger.warn,
164
- dirs: ["components", "composables", "hooks", "utils"]
165
- };
166
- return (0, import_vite.mergeConfig)(
167
- defaultOptions,
168
- config.imports
169
- );
170
- }
171
-
172
- // src/core/vite-plugins/unimport.ts
173
- function unimport(config) {
174
- const options = getUnimportOptions(config);
175
- const unimport2 = (0, import_unimport2.createUnimport)(options);
176
- return {
177
- name: "wxt:unimport",
178
- async config() {
179
- await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
180
- },
181
- async transform(code, id) {
182
- return unimport2.injectImports(code, id);
183
- }
184
- };
185
- }
186
-
187
250
  // src/core/vite-plugins/multipageMove.ts
188
251
  var import_node_path2 = require("path");
189
-
190
- // src/core/utils/entrypoints.ts
191
- var import_node_path = __toESM(require("path"), 1);
192
- function getEntrypointName(entrypointsDir, inputPath) {
193
- const relativePath = import_node_path.default.relative(entrypointsDir, inputPath);
194
- const name = relativePath.split(/[\.\/]/, 2)[0];
195
- return name;
196
- }
197
- function getEntrypointOutputFile(entrypoint, ext) {
198
- return (0, import_node_path.resolve)(entrypoint.outputDir, `${entrypoint.name}${ext}`);
199
- }
200
- function getEntrypointBundlePath(entrypoint, outDir, ext) {
201
- return (0, import_node_path.relative)(outDir, getEntrypointOutputFile(entrypoint, ext));
202
- }
203
-
204
- // src/core/vite-plugins/multipageMove.ts
205
252
  var import_fs_extra = __toESM(require("fs-extra"), 1);
206
253
  function multipageMove(entrypoints, config) {
207
254
  return {
@@ -230,7 +277,7 @@ function multipageMove(entrypoints, config) {
230
277
  const oldAbsPath = (0, import_node_path2.resolve)(config.outDir, oldBundlePath);
231
278
  const newAbsPath = (0, import_node_path2.resolve)(config.outDir, newBundlePath);
232
279
  await (0, import_fs_extra.ensureDir)((0, import_node_path2.dirname)(newAbsPath));
233
- await import_fs_extra.default.move(oldAbsPath, newAbsPath);
280
+ await import_fs_extra.default.move(oldAbsPath, newAbsPath, { overwrite: true });
234
281
  const renamedChunk = {
235
282
  ...bundle[oldBundlePath],
236
283
  fileName: newBundlePath
@@ -242,47 +289,39 @@ function multipageMove(entrypoints, config) {
242
289
  };
243
290
  }
244
291
 
245
- // src/core/vite-plugins/devHtmlPrerender.ts
246
- var import_linkedom = require("linkedom");
247
- var import_path2 = require("path");
248
- function devHtmlPrerender(config) {
292
+ // src/core/vite-plugins/unimport.ts
293
+ var import_unimport2 = require("unimport");
294
+
295
+ // src/core/utils/auto-imports.ts
296
+ var import_vite = require("vite");
297
+ function getUnimportOptions(config) {
298
+ const defaultOptions = {
299
+ debugLog: config.logger.debug,
300
+ imports: [
301
+ { name: "*", as: "browser", from: "webextension-polyfill" },
302
+ { name: "defineConfig", from: "wxt" }
303
+ ],
304
+ presets: [{ package: "wxt/client" }],
305
+ warn: config.logger.warn,
306
+ dirs: ["components", "composables", "hooks", "utils"]
307
+ };
308
+ return (0, import_vite.mergeConfig)(
309
+ defaultOptions,
310
+ config.imports
311
+ );
312
+ }
313
+
314
+ // src/core/vite-plugins/unimport.ts
315
+ function unimport(config) {
316
+ const options = getUnimportOptions(config);
317
+ const unimport2 = (0, import_unimport2.createUnimport)(options);
249
318
  return {
250
- apply: "build",
251
- name: "wxt:dev-html-prerender",
252
- async transform(html, id) {
253
- const server = config.server;
254
- if (config.command !== "serve" || server == null || !id.endsWith(".html"))
255
- return;
256
- const originalUrl = `${server.origin}${id}`;
257
- const name = getEntrypointName(config.entrypointsDir, id);
258
- const url = `${server.origin}/${name}.html`;
259
- const serverHtml = await server.transformIndexHtml(
260
- url,
261
- html,
262
- originalUrl
263
- );
264
- const { document } = (0, import_linkedom.parseHTML)(serverHtml);
265
- const pointToDevServer = (querySelector, attr) => {
266
- document.querySelectorAll(querySelector).forEach((element) => {
267
- const src = element.getAttribute(attr);
268
- if (!src)
269
- return;
270
- if ((0, import_path2.isAbsolute)(src)) {
271
- element.setAttribute(attr, server.origin + src);
272
- } else if (src.startsWith(".")) {
273
- const abs = (0, import_path2.resolve)((0, import_path2.dirname)(id), src);
274
- const pathname = (0, import_path2.relative)(config.root, abs);
275
- element.setAttribute(attr, `${server.origin}/${pathname}`);
276
- }
277
- });
278
- };
279
- pointToDevServer("script[type=module]", "src");
280
- pointToDevServer("link[rel=stylesheet]", "href");
281
- const newHtml = document.toString();
282
- config.logger.debug("Transformed " + id);
283
- config.logger.debug("Old HTML:\n" + html);
284
- config.logger.debug("New HTML:\n" + newHtml);
285
- return newHtml;
319
+ name: "wxt:unimport",
320
+ async config() {
321
+ await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
322
+ },
323
+ async transform(code, id) {
324
+ return unimport2.injectImports(code, id);
286
325
  }
287
326
  };
288
327
  }
@@ -307,10 +346,13 @@ function virtualEntrypoin(type, config) {
307
346
  return;
308
347
  const inputPath = id.replace(resolvedVirtualId, "");
309
348
  const template = await import_fs_extra2.default.readFile(
310
- (0, import_path3.resolve)(config.root, `node_modules/wxt/templates/virtual-${type}.ts`),
349
+ (0, import_path3.resolve)(
350
+ config.root,
351
+ `node_modules/wxt/dist/virtual-modules/${type}-entrypoint.js`
352
+ ),
311
353
  "utf-8"
312
354
  );
313
- return template.replaceAll("{{moduleId}}", inputPath);
355
+ return template.replace(`virtual:user-${type}`, inputPath);
314
356
  }
315
357
  };
316
358
  }
@@ -374,6 +416,11 @@ function getGlobals(config) {
374
416
  name: "__IS_OPERA__",
375
417
  value: config.browser === "opera",
376
418
  type: `boolean`
419
+ },
420
+ {
421
+ name: "__COMMAND__",
422
+ value: config.command,
423
+ type: `"build" | "serve"`
377
424
  }
378
425
  ];
379
426
  }
@@ -418,7 +465,7 @@ async function getInternalConfig(config, command) {
418
465
  import_node_path3.default.resolve(root, config.configFile ?? "wxt.config.ts")
419
466
  );
420
467
  }
421
- const merged = vite.mergeConfig(
468
+ const merged = vite2.mergeConfig(
422
469
  baseConfig,
423
470
  userConfig
424
471
  );
@@ -441,7 +488,7 @@ async function getInternalConfig(config, command) {
441
488
  };
442
489
  finalConfig.vite.root = root;
443
490
  finalConfig.vite.configFile = false;
444
- finalConfig.vite.logLevel = "silent";
491
+ finalConfig.vite.logLevel = "warn";
445
492
  finalConfig.vite.build ??= {};
446
493
  finalConfig.vite.build.outDir = outDir;
447
494
  finalConfig.vite.build.emptyOutDir = false;
@@ -455,6 +502,7 @@ async function getInternalConfig(config, command) {
455
502
  finalConfig.vite.plugins.push(
456
503
  virtualEntrypoin("content-script", finalConfig)
457
504
  );
505
+ finalConfig.vite.plugins.push(devServerGlobals(finalConfig));
458
506
  finalConfig.vite.define ??= {};
459
507
  getGlobals(finalConfig).forEach((global) => {
460
508
  finalConfig.vite.define[global.name] = JSON.stringify(global.value);
@@ -462,24 +510,255 @@ async function getInternalConfig(config, command) {
462
510
  return finalConfig;
463
511
  }
464
512
 
465
- // src/core/build/findEntrypoints.ts
466
- var import_path5 = require("path");
513
+ // src/index.ts
514
+ var import_picocolors3 = __toESM(require("picocolors"), 1);
515
+ var vite6 = __toESM(require("vite"), 1);
516
+
517
+ // src/core/utils/arrays.ts
518
+ function every(array, predicate) {
519
+ for (let i = 0; i < array.length; i++)
520
+ if (!predicate(array[i], i))
521
+ return false;
522
+ return true;
523
+ }
524
+
525
+ // src/core/utils/detectDevChanges.ts
526
+ function detectDevChanges(changedFiles, currentOutput) {
527
+ if (currentOutput == null)
528
+ return { type: "no-change" };
529
+ const changedSteps = new Set(
530
+ changedFiles.flatMap(
531
+ (changedFile) => findEffectedSteps(changedFile, currentOutput)
532
+ )
533
+ );
534
+ if (changedSteps.size === 0)
535
+ return { type: "no-change" };
536
+ const unchangedOutput = {
537
+ manifest: currentOutput.manifest,
538
+ steps: [],
539
+ publicAssets: []
540
+ };
541
+ const changedOutput = {
542
+ manifest: currentOutput.manifest,
543
+ steps: [],
544
+ publicAssets: []
545
+ };
546
+ for (const step of currentOutput.steps) {
547
+ if (changedSteps.has(step)) {
548
+ changedOutput.steps.push(step);
549
+ } else {
550
+ unchangedOutput.steps.push(step);
551
+ }
552
+ }
553
+ for (const asset of currentOutput.publicAssets) {
554
+ if (changedSteps.has(asset)) {
555
+ changedOutput.publicAssets.push(asset);
556
+ } else {
557
+ unchangedOutput.publicAssets.push(asset);
558
+ }
559
+ }
560
+ const isOnlyHtmlChanges = changedFiles.length > 0 && every(changedFiles, ([_, file]) => file.endsWith(".html"));
561
+ if (isOnlyHtmlChanges) {
562
+ return {
563
+ type: "html-reload",
564
+ cachedOutput: unchangedOutput,
565
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
566
+ };
567
+ }
568
+ const isOnlyContentScripts = changedOutput.steps.length > 0 && every(
569
+ changedOutput.steps.flatMap((step) => step.entrypoints),
570
+ (entry) => entry.type === "content-script"
571
+ );
572
+ if (isOnlyContentScripts) {
573
+ return {
574
+ type: "content-script-reload",
575
+ cachedOutput: unchangedOutput,
576
+ changedSteps: changedOutput.steps,
577
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
578
+ };
579
+ }
580
+ return {
581
+ type: "extension-reload",
582
+ cachedOutput: unchangedOutput,
583
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
584
+ };
585
+ }
586
+ function findEffectedSteps(changedFile, currentOutput) {
587
+ const changes = [];
588
+ const changedPath = changedFile[1];
589
+ const isChunkEffected = (chunk) => (
590
+ // If it's an HTML file with the same path, is is effected because HTML files need to be pre-rendered
591
+ // TODO: use bundle path to support `<name>/index.html`?
592
+ chunk.type === "asset" && changedPath.endsWith(chunk.fileName) || // If it's a chunk that depends on the changed file, it is effected
593
+ chunk.type === "chunk" && chunk.moduleIds.includes(changedPath)
594
+ );
595
+ for (const step of currentOutput.steps) {
596
+ const effectedChunk = step.chunks.find((chunk) => isChunkEffected(chunk));
597
+ if (effectedChunk)
598
+ changes.push(step);
599
+ }
600
+ const effectedAsset = currentOutput.publicAssets.find(
601
+ (chunk) => isChunkEffected(chunk)
602
+ );
603
+ if (effectedAsset)
604
+ changes.push(effectedAsset);
605
+ return changes;
606
+ }
607
+
608
+ // src/index.ts
609
+ var import_async_mutex = require("async-mutex");
610
+ var import_consola3 = require("consola");
611
+ var import_node_path4 = require("path");
612
+
613
+ // src/core/build/buildEntrypoints.ts
614
+ var vite3 = __toESM(require("vite"), 1);
615
+
616
+ // src/core/utils/removeEmptyDirs.ts
467
617
  var import_fs_extra4 = __toESM(require("fs-extra"), 1);
468
- var import_picomatch = __toESM(require("picomatch"), 1);
469
- var import_linkedom2 = require("linkedom");
470
- var import_json5 = __toESM(require("json5"), 1);
618
+ var import_path5 = __toESM(require("path"), 1);
619
+ async function removeEmptyDirs(dir) {
620
+ const files = await import_fs_extra4.default.readdir(dir);
621
+ for (const file of files) {
622
+ const filePath = import_path5.default.join(dir, file);
623
+ const stats = await import_fs_extra4.default.stat(filePath);
624
+ if (stats.isDirectory()) {
625
+ await removeEmptyDirs(filePath);
626
+ }
627
+ }
628
+ try {
629
+ await import_fs_extra4.default.rmdir(dir);
630
+ } catch {
631
+ }
632
+ }
633
+
634
+ // src/core/build/buildEntrypoints.ts
471
635
  var import_fast_glob = __toESM(require("fast-glob"), 1);
472
- async function findEntrypoints(config) {
473
- const relativePaths = await (0, import_fast_glob.default)("**/*", {
474
- cwd: config.entrypointsDir
475
- });
476
- relativePaths.sort();
477
- const pathGlobs = Object.keys(PATH_GLOB_TO_TYPE_MAP);
478
- const existingNames = {};
636
+ var import_fs_extra5 = __toESM(require("fs-extra"), 1);
637
+ var import_path6 = require("path");
638
+ async function buildEntrypoints(groups, config) {
639
+ const steps = [];
640
+ for (const group of groups) {
641
+ const step = Array.isArray(group) ? await buildMultipleEntrypoints(group, config) : await buildSingleEntrypoint(group, config);
642
+ steps.push(step);
643
+ }
644
+ const publicAssets = await copyPublicDirectory(config);
645
+ await removeEmptyDirs(config.outDir);
646
+ return { publicAssets, steps };
647
+ }
648
+ async function buildSingleEntrypoint(entrypoint, config) {
649
+ const isVirtual = ["background", "content-script"].includes(entrypoint.type);
650
+ const entry = isVirtual ? `virtual:wxt-${entrypoint.type}?${entrypoint.inputPath}` : entrypoint.inputPath;
651
+ const libMode = {
652
+ build: {
653
+ lib: {
654
+ entry,
655
+ formats: ["iife"],
656
+ name: entrypoint.name,
657
+ fileName: entrypoint.name
658
+ },
659
+ rollupOptions: {
660
+ output: {
661
+ // There's only a single output for this build, so we use the desired bundle path for the
662
+ // entry output (like "content-scripts/overlay.js")
663
+ entryFileNames: getEntrypointBundlePath(
664
+ entrypoint,
665
+ config.outDir,
666
+ ".js"
667
+ ),
668
+ // Output content script CSS to assets/ with a hash to prevent conflicts. Defaults to
669
+ // "[name].[ext]" in lib mode, which usually results in "style.css". That means multiple
670
+ // content scripts with styles would overwrite each other if it weren't changed below.
671
+ assetFileNames: `assets/${entrypoint.name}.[ext]`
672
+ }
673
+ }
674
+ }
675
+ };
676
+ const entryConfig = vite3.mergeConfig(
677
+ libMode,
678
+ config.vite
679
+ );
680
+ const result = await vite3.build(entryConfig);
681
+ return {
682
+ entrypoints: entrypoint,
683
+ chunks: getBuildOutputChunks(result)
684
+ };
685
+ }
686
+ async function buildMultipleEntrypoints(entrypoints, config) {
687
+ const multiPage = {
688
+ plugins: [multipageMove(entrypoints, config)],
689
+ build: {
690
+ rollupOptions: {
691
+ input: entrypoints.reduce((input, entry) => {
692
+ input[entry.name] = entry.inputPath;
693
+ return input;
694
+ }, {}),
695
+ output: {
696
+ // Include a hash to prevent conflicts
697
+ chunkFileNames: "chunks/[name]-[hash].js",
698
+ // Include a hash to prevent conflicts
699
+ entryFileNames: "chunks/[name]-[hash].js",
700
+ // We can't control the "name", so we need a hash to prevent conflicts
701
+ assetFileNames: "assets/[name]-[hash].[ext]"
702
+ }
703
+ }
704
+ }
705
+ };
706
+ const entryConfig = vite3.mergeConfig(
707
+ multiPage,
708
+ config.vite
709
+ );
710
+ const result = await vite3.build(entryConfig);
711
+ return {
712
+ entrypoints,
713
+ chunks: getBuildOutputChunks(result)
714
+ };
715
+ }
716
+ function getBuildOutputChunks(result) {
717
+ if ("on" in result)
718
+ throw Error("wxt does not support vite watch mode.");
719
+ if (Array.isArray(result))
720
+ return result.flatMap(({ output }) => output);
721
+ return result.output;
722
+ }
723
+ async function copyPublicDirectory(config) {
724
+ const publicAssets = [];
725
+ if (!await import_fs_extra5.default.exists(config.publicDir))
726
+ return publicAssets;
727
+ const files = await (0, import_fast_glob.default)("**/*", { cwd: config.publicDir });
728
+ for (const file of files) {
729
+ const srcPath = (0, import_path6.resolve)(config.publicDir, file);
730
+ const outPath = (0, import_path6.resolve)(config.outDir, file);
731
+ await import_fs_extra5.default.ensureDir((0, import_path6.dirname)(outPath));
732
+ await import_fs_extra5.default.copyFile(srcPath, outPath);
733
+ publicAssets.push({
734
+ type: "asset",
735
+ fileName: file,
736
+ name: file,
737
+ needsCodeReference: false,
738
+ source: await import_fs_extra5.default.readFile(srcPath)
739
+ });
740
+ }
741
+ return publicAssets;
742
+ }
743
+
744
+ // src/core/build/findEntrypoints.ts
745
+ var import_path7 = require("path");
746
+ var import_fs_extra6 = __toESM(require("fs-extra"), 1);
747
+ var import_picomatch = __toESM(require("picomatch"), 1);
748
+ var import_linkedom2 = require("linkedom");
749
+ var import_json5 = __toESM(require("json5"), 1);
750
+ var import_fast_glob2 = __toESM(require("fast-glob"), 1);
751
+ async function findEntrypoints(config) {
752
+ const relativePaths = await (0, import_fast_glob2.default)("**/*", {
753
+ cwd: config.entrypointsDir
754
+ });
755
+ relativePaths.sort();
756
+ const pathGlobs = Object.keys(PATH_GLOB_TO_TYPE_MAP);
757
+ const existingNames = {};
479
758
  const entrypoints = [];
480
759
  await Promise.all(
481
760
  relativePaths.map(async (relativePath) => {
482
- const path5 = (0, import_path5.resolve)(config.entrypointsDir, relativePath);
761
+ const path5 = (0, import_path7.resolve)(config.entrypointsDir, relativePath);
483
762
  const matchingGlob = pathGlobs.find(
484
763
  (glob3) => import_picomatch.default.isMatch(relativePath, glob3)
485
764
  );
@@ -526,8 +805,8 @@ ${JSON.stringify(
526
805
  if (withSameName) {
527
806
  throw Error(
528
807
  `Multiple entrypoints with the name "${entrypoint.name}" detected, but only one is allowed: ${[
529
- (0, import_path5.relative)(config.root, withSameName.inputPath),
530
- (0, import_path5.relative)(config.root, entrypoint.inputPath)
808
+ (0, import_path7.relative)(config.root, withSameName.inputPath),
809
+ (0, import_path7.relative)(config.root, entrypoint.inputPath)
531
810
  ].join(", ")}`
532
811
  );
533
812
  }
@@ -539,7 +818,7 @@ ${JSON.stringify(
539
818
  }
540
819
  async function getPopupEntrypoint(config, path5) {
541
820
  const options = {};
542
- const content = await import_fs_extra4.default.readFile(path5, "utf-8");
821
+ const content = await import_fs_extra6.default.readFile(path5, "utf-8");
543
822
  const { document } = (0, import_linkedom2.parseHTML)(content);
544
823
  const title = document.querySelector("title");
545
824
  if (title != null)
@@ -569,7 +848,7 @@ async function getPopupEntrypoint(config, path5) {
569
848
  }
570
849
  async function getOptionsEntrypoint(config, path5) {
571
850
  const options = {};
572
- const content = await import_fs_extra4.default.readFile(path5, "utf-8");
851
+ const content = await import_fs_extra6.default.readFile(path5, "utf-8");
573
852
  const { document } = (0, import_linkedom2.parseHTML)(content);
574
853
  const openInTabContent = document.querySelector("meta[name='manifest.open_in_tab']")?.getAttribute("content");
575
854
  if (openInTabContent) {
@@ -616,7 +895,7 @@ async function getContentScriptEntrypoint(config, name, path5) {
616
895
  type: "content-script",
617
896
  name: getEntrypointName(config.entrypointsDir, path5),
618
897
  inputPath: path5,
619
- outputDir: (0, import_path5.resolve)(config.outDir, "content-scripts"),
898
+ outputDir: (0, import_path7.resolve)(config.outDir, "content-scripts"),
620
899
  options
621
900
  };
622
901
  }
@@ -651,160 +930,112 @@ var PATH_GLOB_TO_TYPE_MAP = {
651
930
  "*/*": "ignored"
652
931
  };
653
932
 
654
- // src/core/build/buildEntrypoints.ts
655
- var vite2 = __toESM(require("vite"), 1);
656
-
657
- // src/core/utils/groupEntrypoints.ts
658
- function groupEntrypoints(entrypoints) {
659
- const groupIndexMap = {};
660
- const groups = [];
661
- for (const entry of entrypoints) {
662
- const group = ENTRY_TYPE_TO_GROUP_MAP[entry.type];
663
- if (group === "no-group") {
664
- groups.push(entry);
665
- } else {
666
- let groupIndex = groupIndexMap[group];
667
- if (groupIndex == null) {
668
- groupIndex = groups.push([]) - 1;
669
- groupIndexMap[group] = groupIndex;
670
- }
671
- groups[groupIndex].push(entry);
672
- }
673
- }
674
- return groups;
675
- }
676
- var ENTRY_TYPE_TO_GROUP_MAP = {
677
- sandbox: "sandbox-page",
678
- popup: "extension-page",
679
- newtab: "extension-page",
680
- history: "extension-page",
681
- options: "extension-page",
682
- devtools: "extension-page",
683
- bookmarks: "extension-page",
684
- sidepanel: "extension-page",
685
- "unlisted-page": "extension-page",
686
- background: "no-group",
687
- "content-script": "no-group",
688
- "unlisted-script": "no-group"
689
- };
690
-
691
- // src/core/utils/removeEmptyDirs.ts
692
- var import_fs_extra5 = __toESM(require("fs-extra"), 1);
693
- var import_path6 = __toESM(require("path"), 1);
694
- async function removeEmptyDirs(dir) {
695
- const files = await import_fs_extra5.default.readdir(dir);
696
- for (const file of files) {
697
- const filePath = import_path6.default.join(dir, file);
698
- const stats = await import_fs_extra5.default.stat(filePath);
699
- if (stats.isDirectory()) {
700
- await removeEmptyDirs(filePath);
701
- }
702
- }
703
- try {
704
- await import_fs_extra5.default.rmdir(dir);
705
- } catch {
706
- }
933
+ // src/core/build/generateTypesDir.ts
934
+ var import_unimport3 = require("unimport");
935
+ var import_fs_extra7 = __toESM(require("fs-extra"), 1);
936
+ var import_path8 = require("path");
937
+ async function generateTypesDir(entrypoints, config) {
938
+ await import_fs_extra7.default.ensureDir(config.typesDir);
939
+ const references = [];
940
+ references.push(await writeImportsDeclarationFile(config));
941
+ references.push(await writePathsDeclarationFile(entrypoints, config));
942
+ references.push(await writeGlobalsDeclarationFile(config));
943
+ const mainReference = await writeMainDeclarationFile(references, config);
944
+ await writeTsConfigFile(mainReference, config);
707
945
  }
708
-
709
- // src/core/build/buildEntrypoints.ts
710
- var import_fast_glob2 = __toESM(require("fast-glob"), 1);
711
- var import_fs_extra6 = __toESM(require("fs-extra"), 1);
712
- var import_path7 = require("path");
713
- async function buildEntrypoints(entrypoints, config) {
714
- const groups = groupEntrypoints(entrypoints);
715
- const outputs = [];
716
- for (const group of groups) {
717
- const output = Array.isArray(group) ? await buildMultipleEntrypoints(group, config) : await buildSingleEntrypoint(group, config);
718
- outputs.push(output);
719
- }
720
- const publicOutput = await copyPublicDirectory(config);
721
- outputs.push(publicOutput);
722
- await removeEmptyDirs(config.outDir);
723
- return outputs.flat();
946
+ async function writeImportsDeclarationFile(config) {
947
+ const filePath = (0, import_path8.resolve)(config.typesDir, "imports.d.ts");
948
+ const unimport2 = (0, import_unimport3.createUnimport)(getUnimportOptions(config));
949
+ await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
950
+ await import_fs_extra7.default.writeFile(
951
+ filePath,
952
+ ["// Generated by wxt", await unimport2.generateTypeDeclarations()].join(
953
+ "\n"
954
+ ) + "\n"
955
+ );
956
+ return filePath;
724
957
  }
725
- async function buildSingleEntrypoint(entrypoint, config) {
726
- const isVirtual = ["background", "content-script"].includes(entrypoint.type);
727
- const entry = isVirtual ? `virtual:wxt-${entrypoint.type}?${entrypoint.inputPath}` : entrypoint.inputPath;
728
- const libMode = {
729
- build: {
730
- lib: {
731
- entry,
732
- formats: ["iife"],
733
- name: entrypoint.name,
734
- fileName: entrypoint.name
735
- },
736
- rollupOptions: {
737
- output: {
738
- entryFileNames: getEntrypointBundlePath(
739
- entrypoint,
740
- config.outDir,
741
- ".js"
742
- ),
743
- // Output content script CSS to assets/ with a hash to prevent conflicts. Defaults to
744
- // "[name].[ext]" in lib mode, which usually results in "style.css". That means multiple
745
- // content scripts with styles would overwrite each other if it weren't changed below.
746
- assetFileNames: `assets/${entrypoint.name}-[hash].[ext]`
747
- }
748
- }
749
- }
750
- };
751
- const entryConfig = vite2.mergeConfig(
752
- libMode,
753
- config.vite
958
+ async function writePathsDeclarationFile(entrypoints, config) {
959
+ const filePath = (0, import_path8.resolve)(config.typesDir, "paths.d.ts");
960
+ await import_fs_extra7.default.writeFile(
961
+ filePath,
962
+ [
963
+ "// Generated by wxt",
964
+ "type EntrypointPath =",
965
+ ...entrypoints.map((entry) => {
966
+ const path5 = getEntrypointBundlePath(
967
+ entry,
968
+ config.outDir,
969
+ entry.inputPath.endsWith(".html") ? ".html" : ".js"
970
+ );
971
+ return ` | "/${path5}"`;
972
+ }).sort()
973
+ ].join("\n") + "\n"
754
974
  );
755
- const result = await vite2.build(entryConfig);
756
- return getBuildOutput(result);
975
+ return filePath;
757
976
  }
758
- async function buildMultipleEntrypoints(entrypoints, config) {
759
- const multiPage = {
760
- plugins: [multipageMove(entrypoints, config)],
761
- build: {
762
- rollupOptions: {
763
- input: entrypoints.reduce((input, entry) => {
764
- input[entry.name] = entry.inputPath;
765
- return input;
766
- }, {})
767
- }
768
- }
769
- };
770
- const entryConfig = vite2.mergeConfig(
771
- multiPage,
772
- config.vite
977
+ async function writeGlobalsDeclarationFile(config) {
978
+ const filePath = (0, import_path8.resolve)(config.typesDir, "globals.d.ts");
979
+ const globals = getGlobals(config);
980
+ await import_fs_extra7.default.writeFile(
981
+ filePath,
982
+ [
983
+ "// Generated by wxt",
984
+ "export {}",
985
+ "declare global {",
986
+ ...globals.map((global) => ` const ${global.name}: ${global.type};`),
987
+ "}"
988
+ ].join("\n") + "\n",
989
+ "utf-8"
773
990
  );
774
- const result = await vite2.build(entryConfig);
775
- return getBuildOutput(result);
991
+ return filePath;
776
992
  }
777
- function getBuildOutput(result) {
778
- if ("on" in result)
779
- throw Error("wxt does not support vite watch mode.");
780
- if (Array.isArray(result))
781
- return result.flatMap(({ output }) => output);
782
- return result.output;
993
+ async function writeMainDeclarationFile(references, config) {
994
+ const dir = config.wxtDir;
995
+ const filePath = (0, import_path8.resolve)(dir, "wxt.d.ts");
996
+ await import_fs_extra7.default.writeFile(
997
+ filePath,
998
+ [
999
+ "// Generated by wxt",
1000
+ ...references.map(
1001
+ (ref) => `/// <reference types="./${(0, import_path8.relative)(dir, ref)}" />`
1002
+ )
1003
+ ].join("\n") + "\n"
1004
+ );
1005
+ return filePath;
783
1006
  }
784
- async function copyPublicDirectory(config) {
785
- if (!await import_fs_extra6.default.exists(config.publicDir))
786
- return [];
787
- const files = await (0, import_fast_glob2.default)("**/*", { cwd: config.publicDir });
788
- const outputs = [];
789
- for (const file of files) {
790
- const srcPath = (0, import_path7.resolve)(config.publicDir, file);
791
- const outPath = (0, import_path7.resolve)(config.outDir, file);
792
- await import_fs_extra6.default.ensureDir((0, import_path7.dirname)(outPath));
793
- await import_fs_extra6.default.copyFile(srcPath, outPath);
794
- outputs.push({
795
- type: "asset",
796
- fileName: file,
797
- name: file,
798
- needsCodeReference: false,
799
- source: await import_fs_extra6.default.readFile(srcPath)
800
- });
801
- }
802
- return outputs;
1007
+ async function writeTsConfigFile(mainReference, config) {
1008
+ const dir = config.wxtDir;
1009
+ await import_fs_extra7.default.writeFile(
1010
+ (0, import_path8.resolve)(dir, "tsconfig.json"),
1011
+ `{
1012
+ "compilerOptions": {
1013
+ "target": "ESNext",
1014
+ "module": "ESNext",
1015
+ "moduleResolution": "Bundler",
1016
+ "noEmit": true,
1017
+ "esModuleInterop": true,
1018
+ "forceConsistentCasingInFileNames": true,
1019
+ "resolveJsonModule": true,
1020
+
1021
+ /* Type Checking */
1022
+ "strict": true,
1023
+
1024
+ /* Completeness */
1025
+ "skipLibCheck": true
1026
+ },
1027
+ "include": [
1028
+ "${(0, import_path8.relative)(dir, config.root)}/**/*",
1029
+ "./${(0, import_path8.relative)(dir, mainReference)}"
1030
+ ],
1031
+ "exclude": ["${(0, import_path8.relative)(dir, config.outBaseDir)}"]
1032
+ }`
1033
+ );
803
1034
  }
804
1035
 
805
1036
  // src/core/utils/manifest.ts
806
- var import_fs_extra7 = __toESM(require("fs-extra"), 1);
807
- var import_path8 = require("path");
1037
+ var import_fs_extra8 = __toESM(require("fs-extra"), 1);
1038
+ var import_path9 = require("path");
808
1039
 
809
1040
  // src/core/utils/ContentSecurityPolicy.ts
810
1041
  var ContentSecurityPolicy = class _ContentSecurityPolicy {
@@ -852,9 +1083,9 @@ var ContentSecurityPolicy = class _ContentSecurityPolicy {
852
1083
  // src/core/utils/manifest.ts
853
1084
  async function writeManifest(manifest, output, config) {
854
1085
  const str = config.mode === "production" ? JSON.stringify(manifest) : JSON.stringify(manifest, null, 2);
855
- await import_fs_extra7.default.ensureDir(config.outDir);
856
- await import_fs_extra7.default.writeFile((0, import_path8.resolve)(config.outDir, "manifest.json"), str, "utf-8");
857
- output.unshift({
1086
+ await import_fs_extra8.default.ensureDir(config.outDir);
1087
+ await import_fs_extra8.default.writeFile((0, import_path9.resolve)(config.outDir, "manifest.json"), str, "utf-8");
1088
+ output.publicAssets.unshift({
858
1089
  type: "asset",
859
1090
  fileName: "manifest.json",
860
1091
  name: "manifest",
@@ -881,10 +1112,12 @@ async function generateMainfest(entrypoints, buildOutput, config) {
881
1112
  addEntrypoints(manifest, entrypoints, buildOutput, config);
882
1113
  if (config.command === "serve")
883
1114
  addDevModeCsp(manifest, config);
1115
+ if (config.command === "serve")
1116
+ addDevModePermissions(manifest, config);
884
1117
  return manifest;
885
1118
  }
886
1119
  async function getPackageJson(config) {
887
- return await import_fs_extra7.default.readJson((0, import_path8.resolve)(config.root, "package.json"));
1120
+ return await import_fs_extra8.default.readJson((0, import_path9.resolve)(config.root, "package.json"));
888
1121
  }
889
1122
  function simplifyVersion(versionName) {
890
1123
  const version3 = /^((0|[1-9][0-9]{0,8})([.](0|[1-9][0-9]{0,8})){0,3}).*$/.exec(
@@ -1037,23 +1270,23 @@ function addEntrypoints(manifest, entrypoints, buildOutput, config) {
1037
1270
  }
1038
1271
  }
1039
1272
  if (contentScripts?.length) {
1040
- if (config.command === "serve") {
1041
- const permissionsKey = config.manifestVersion === 2 ? "permissions" : "host_permissions";
1042
- const hostPermissions = new Set(manifest[permissionsKey] ?? []);
1273
+ if (config.command === "serve" && config.manifestVersion === 3) {
1274
+ const hostPermissions = new Set(manifest.host_permissions ?? []);
1043
1275
  contentScripts.forEach((script) => {
1044
1276
  script.options.matches.forEach((matchPattern) => {
1045
1277
  hostPermissions.add(matchPattern);
1046
1278
  });
1047
1279
  });
1048
- manifest[permissionsKey] = Array.from(hostPermissions).sort();
1280
+ hostPermissions.forEach(
1281
+ (permission) => addHostPermission(manifest, permission)
1282
+ );
1049
1283
  } else {
1050
1284
  const hashToEntrypointsMap = contentScripts.reduce((map, script) => {
1051
1285
  const hash = JSON.stringify(script.options);
1052
- if (!map.has(hash)) {
1053
- map.set(hash, [script]);
1054
- } else {
1286
+ if (map.has(hash))
1055
1287
  map.get(hash)?.push(script);
1056
- }
1288
+ else
1289
+ map.set(hash, [script]);
1057
1290
  return map;
1058
1291
  }, /* @__PURE__ */ new Map());
1059
1292
  manifest.content_scripts = Array.from(hashToEntrypointsMap.entries()).map(
@@ -1074,13 +1307,9 @@ function addDevModeCsp(manifest, config) {
1074
1307
  const permission = `http://${config.server?.hostname ?? ""}/*`;
1075
1308
  const allowedCsp = config.server?.origin ?? "http://localhost:*";
1076
1309
  if (manifest.manifest_version === 3) {
1077
- manifest.host_permissions ??= [];
1078
- if (!manifest.host_permissions.includes(permission))
1079
- manifest.host_permissions.push(permission);
1310
+ addHostPermission(manifest, permission);
1080
1311
  } else {
1081
- manifest.permissions ??= [];
1082
- if (!manifest.permissions.includes(permission))
1083
- manifest.permissions.push(permission);
1312
+ addPermission(manifest, permission);
1084
1313
  }
1085
1314
  const csp = new ContentSecurityPolicy(
1086
1315
  manifest.manifest_version === 3 ? (
@@ -1098,12 +1327,17 @@ function addDevModeCsp(manifest, config) {
1098
1327
  manifest.content_security_policy = csp.toString();
1099
1328
  }
1100
1329
  }
1330
+ function addDevModePermissions(manifest, config) {
1331
+ addPermission(manifest, "tabs");
1332
+ if (config.manifestVersion === 3)
1333
+ addPermission(manifest, "scripting");
1334
+ }
1101
1335
  function getContentScriptCssFiles(contentScripts, buildOutput) {
1102
1336
  const css = [];
1337
+ const allChunks = buildOutput.steps.flatMap((step) => step.chunks);
1103
1338
  contentScripts.forEach((script) => {
1104
- const cssRegex = new RegExp(`^assets/${script.name}-[a-f0-9]{8}.css$`);
1105
- const relatedCss = buildOutput.find(
1106
- (chunk) => chunk.fileName.match(cssRegex)
1339
+ const relatedCss = allChunks.find(
1340
+ (chunk) => chunk.fileName === `assets/${script.name}.css`
1107
1341
  );
1108
1342
  if (relatedCss)
1109
1343
  css.push(relatedCss.fileName);
@@ -1112,9 +1346,71 @@ function getContentScriptCssFiles(contentScripts, buildOutput) {
1112
1346
  return css;
1113
1347
  return void 0;
1114
1348
  }
1349
+ function addPermission(manifest, permission) {
1350
+ manifest.permissions ??= [];
1351
+ if (manifest.permissions.includes(permission))
1352
+ return;
1353
+ manifest.permissions.push(permission);
1354
+ }
1355
+ function addHostPermission(manifest, hostPermission) {
1356
+ manifest.host_permissions ??= [];
1357
+ if (manifest.host_permissions.includes(hostPermission))
1358
+ return;
1359
+ manifest.host_permissions.push(hostPermission);
1360
+ }
1361
+
1362
+ // src/core/build.ts
1363
+ var import_picocolors2 = __toESM(require("picocolors"), 1);
1364
+ var vite4 = __toESM(require("vite"), 1);
1365
+ var import_fs_extra10 = __toESM(require("fs-extra"), 1);
1366
+
1367
+ // src/core/utils/groupEntrypoints.ts
1368
+ function groupEntrypoints(entrypoints) {
1369
+ const groupIndexMap = {};
1370
+ const groups = [];
1371
+ for (const entry of entrypoints) {
1372
+ const group = ENTRY_TYPE_TO_GROUP_MAP[entry.type];
1373
+ if (group === "no-group") {
1374
+ groups.push(entry);
1375
+ } else {
1376
+ let groupIndex = groupIndexMap[group];
1377
+ if (groupIndex == null) {
1378
+ groupIndex = groups.push([]) - 1;
1379
+ groupIndexMap[group] = groupIndex;
1380
+ }
1381
+ groups[groupIndex].push(entry);
1382
+ }
1383
+ }
1384
+ return groups;
1385
+ }
1386
+ var ENTRY_TYPE_TO_GROUP_MAP = {
1387
+ sandbox: "sandbox-page",
1388
+ popup: "extension-page",
1389
+ newtab: "extension-page",
1390
+ history: "extension-page",
1391
+ options: "extension-page",
1392
+ devtools: "extension-page",
1393
+ bookmarks: "extension-page",
1394
+ sidepanel: "extension-page",
1395
+ "unlisted-page": "extension-page",
1396
+ background: "no-group",
1397
+ "content-script": "no-group",
1398
+ "unlisted-script": "no-group"
1399
+ };
1400
+
1401
+ // src/core/utils/formatDuration.ts
1402
+ function formatDuration(duration) {
1403
+ if (duration < 1e3)
1404
+ return `${duration} ms`;
1405
+ if (duration < 1e4)
1406
+ return `${(duration / 1e3).toFixed(3)} s`;
1407
+ if (duration < 6e4)
1408
+ return `${(duration / 1e3).toFixed(1)} s`;
1409
+ return `${(duration / 1e3).toFixed(0)} s`;
1410
+ }
1115
1411
 
1116
1412
  // src/core/log/printBuildSummary.ts
1117
- var import_path9 = __toESM(require("path"), 1);
1413
+ var import_path10 = __toESM(require("path"), 1);
1118
1414
 
1119
1415
  // src/core/log/printTable.ts
1120
1416
  function printTable(log, rows, gap = 2) {
@@ -1144,12 +1440,15 @@ function printTable(log, rows, gap = 2) {
1144
1440
 
1145
1441
  // src/core/log/printBuildSummary.ts
1146
1442
  var import_picocolors = __toESM(require("picocolors"), 1);
1147
- var import_fs_extra8 = __toESM(require("fs-extra"), 1);
1443
+ var import_fs_extra9 = __toESM(require("fs-extra"), 1);
1148
1444
  var import_filesize = require("filesize");
1149
1445
  async function printBuildSummary(output, config) {
1150
- const chunks = output.sort((l, r) => {
1151
- const lWeight = CHUNK_SORT_WEIGHTS[l.fileName] ?? CHUNK_SORT_WEIGHTS[(0, import_path9.extname)(l.fileName)] ?? DEFAULT_SORT_WEIGHT;
1152
- const rWeight = CHUNK_SORT_WEIGHTS[r.fileName] ?? CHUNK_SORT_WEIGHTS[(0, import_path9.extname)(r.fileName)] ?? DEFAULT_SORT_WEIGHT;
1446
+ const chunks = [
1447
+ ...output.steps.flatMap((step) => step.chunks),
1448
+ ...output.publicAssets
1449
+ ].sort((l, r) => {
1450
+ const lWeight = CHUNK_SORT_WEIGHTS[l.fileName] ?? CHUNK_SORT_WEIGHTS[(0, import_path10.extname)(l.fileName)] ?? DEFAULT_SORT_WEIGHT;
1451
+ const rWeight = CHUNK_SORT_WEIGHTS[r.fileName] ?? CHUNK_SORT_WEIGHTS[(0, import_path10.extname)(r.fileName)] ?? DEFAULT_SORT_WEIGHT;
1153
1452
  const diff = lWeight - rWeight;
1154
1453
  if (diff !== 0)
1155
1454
  return diff;
@@ -1159,13 +1458,13 @@ async function printBuildSummary(output, config) {
1159
1458
  const chunkRows = await Promise.all(
1160
1459
  chunks.map(async (chunk, i) => {
1161
1460
  const file = [
1162
- (0, import_path9.relative)(process.cwd(), config.outDir) + import_path9.default.sep,
1461
+ (0, import_path10.relative)(process.cwd(), config.outDir) + import_path10.default.sep,
1163
1462
  chunk.fileName
1164
1463
  ];
1165
- const ext = (0, import_path9.extname)(chunk.fileName);
1464
+ const ext = (0, import_path10.extname)(chunk.fileName);
1166
1465
  const prefix = i === chunks.length - 1 ? " \u2514\u2500" : " \u251C\u2500";
1167
1466
  const color = CHUNK_COLORS[ext] ?? DEFAULT_COLOR;
1168
- const stats = await import_fs_extra8.default.lstat((0, import_path9.resolve)(config.outDir, chunk.fileName));
1467
+ const stats = await import_fs_extra9.default.lstat((0, import_path10.resolve)(config.outDir, chunk.fileName));
1169
1468
  totalSize += stats.size;
1170
1469
  const size = String((0, import_filesize.filesize)(stats.size));
1171
1470
  return [
@@ -1193,115 +1492,63 @@ var CHUNK_COLORS = {
1193
1492
  ".js": import_picocolors.default.cyan
1194
1493
  };
1195
1494
 
1196
- // src/index.ts
1197
- var import_fs_extra10 = __toESM(require("fs-extra"), 1);
1198
-
1199
- // src/core/build/generateTypesDir.ts
1200
- var import_unimport3 = require("unimport");
1201
- var import_fs_extra9 = __toESM(require("fs-extra"), 1);
1202
- var import_path10 = require("path");
1203
- async function generateTypesDir(entrypoints, config) {
1204
- await import_fs_extra9.default.ensureDir(config.typesDir);
1205
- const references = [];
1206
- references.push(await writeImportsDeclarationFile(config));
1207
- references.push(await writePathsDeclarationFile(entrypoints, config));
1208
- references.push(await writeGlobalsDeclarationFile(config));
1209
- const mainReference = await writeMainDeclarationFile(references, config);
1210
- await writeTsConfigFile(mainReference, config);
1211
- }
1212
- async function writeImportsDeclarationFile(config) {
1213
- const filePath = (0, import_path10.resolve)(config.typesDir, "imports.d.ts");
1214
- const unimport2 = (0, import_unimport3.createUnimport)(getUnimportOptions(config));
1215
- await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
1216
- await import_fs_extra9.default.writeFile(
1217
- filePath,
1218
- ["// Generated by wxt", await unimport2.generateTypeDeclarations()].join(
1219
- "\n"
1220
- ) + "\n"
1221
- );
1222
- return filePath;
1223
- }
1224
- async function writePathsDeclarationFile(entrypoints, config) {
1225
- const filePath = (0, import_path10.resolve)(config.typesDir, "paths.d.ts");
1226
- await import_fs_extra9.default.writeFile(
1227
- filePath,
1228
- [
1229
- "// Generated by wxt",
1230
- "type EntrypointPath =",
1231
- ...entrypoints.map((entry) => {
1232
- const path5 = getEntrypointBundlePath(
1233
- entry,
1234
- config.outDir,
1235
- entry.inputPath.endsWith(".html") ? ".html" : ".js"
1236
- );
1237
- return ` | "/${path5}"`;
1238
- }).sort()
1239
- ].join("\n") + "\n"
1240
- );
1241
- return filePath;
1242
- }
1243
- async function writeGlobalsDeclarationFile(config) {
1244
- const filePath = (0, import_path10.resolve)(config.typesDir, "globals.d.ts");
1245
- const globals = getGlobals(config);
1246
- await import_fs_extra9.default.writeFile(
1247
- filePath,
1248
- [
1249
- "// Generated by wxt",
1250
- "export {}",
1251
- "declare global {",
1252
- ...globals.map((global) => ` const ${global.name}: ${global.type};`),
1253
- "}"
1254
- ].join("\n") + "\n",
1255
- "utf-8"
1495
+ // src/core/build.ts
1496
+ async function buildInternal(config) {
1497
+ const verb = config.command === "serve" ? "Pre-rendering" : "Building";
1498
+ const target = `${config.browser}-mv${config.manifestVersion}`;
1499
+ config.logger.info(
1500
+ `${verb} ${import_picocolors2.default.cyan(target)} for ${import_picocolors2.default.cyan(config.mode)} with ${import_picocolors2.default.green(
1501
+ `Vite ${vite4.version}`
1502
+ )}`
1256
1503
  );
1257
- return filePath;
1258
- }
1259
- async function writeMainDeclarationFile(references, config) {
1260
- const dir = config.wxtDir;
1261
- const filePath = (0, import_path10.resolve)(dir, "wxt.d.ts");
1262
- await import_fs_extra9.default.writeFile(
1263
- filePath,
1264
- [
1265
- "// Generated by wxt",
1266
- ...references.map(
1267
- (ref) => `/// <reference types="./${(0, import_path10.relative)(dir, ref)}" />`
1268
- )
1269
- ].join("\n") + "\n"
1504
+ const startTime = Date.now();
1505
+ await import_fs_extra10.default.rm(config.outDir, { recursive: true, force: true });
1506
+ await import_fs_extra10.default.ensureDir(config.outDir);
1507
+ const entrypoints = await findEntrypoints(config);
1508
+ const groups = groupEntrypoints(entrypoints);
1509
+ const { output } = await rebuild(config, groups);
1510
+ config.logger.success(
1511
+ `Built extension in ${formatDuration(Date.now() - startTime)}`
1270
1512
  );
1271
- return filePath;
1513
+ await printBuildSummary(output, config);
1514
+ return output;
1272
1515
  }
1273
- async function writeTsConfigFile(mainReference, config) {
1274
- const dir = config.wxtDir;
1275
- await import_fs_extra9.default.writeFile(
1276
- (0, import_path10.resolve)(dir, "tsconfig.json"),
1277
- `{
1278
- "compilerOptions": {
1279
- "target": "ESNext",
1280
- "module": "ESNext",
1281
- "moduleResolution": "Bundler",
1282
- "noEmit": true,
1283
- "esModuleInterop": true,
1284
- "forceConsistentCasingInFileNames": true,
1285
- "resolveJsonModule": true,
1286
-
1287
- /* Type Checking */
1288
- "strict": true,
1289
-
1290
- /* Completeness */
1291
- "skipLibCheck": true
1292
- },
1293
- "include": [
1294
- "${(0, import_path10.relative)(dir, config.root)}/**/*",
1295
- "./${(0, import_path10.relative)(dir, mainReference)}"
1296
- ],
1297
- "exclude": ["${(0, import_path10.relative)(dir, config.outBaseDir)}"]
1298
- }`
1516
+ async function rebuild(config, entrypointGroups, existingOutput = {
1517
+ steps: [],
1518
+ publicAssets: []
1519
+ }) {
1520
+ const allEntrypoints = await findEntrypoints(config);
1521
+ await generateTypesDir(allEntrypoints, config);
1522
+ const newOutput = await buildEntrypoints(entrypointGroups, config);
1523
+ const mergedOutput = {
1524
+ steps: [...existingOutput.steps, ...newOutput.steps],
1525
+ publicAssets: [...existingOutput.publicAssets, ...newOutput.publicAssets]
1526
+ };
1527
+ const newManifest = await generateMainfest(
1528
+ allEntrypoints,
1529
+ mergedOutput,
1530
+ config
1299
1531
  );
1532
+ const finalOutput = {
1533
+ manifest: newManifest,
1534
+ ...newOutput
1535
+ };
1536
+ await writeManifest(newManifest, finalOutput, config);
1537
+ return {
1538
+ output: {
1539
+ manifest: newManifest,
1540
+ steps: [...existingOutput.steps, ...finalOutput.steps],
1541
+ publicAssets: [
1542
+ ...existingOutput.publicAssets,
1543
+ ...finalOutput.publicAssets
1544
+ ]
1545
+ },
1546
+ manifest: newManifest
1547
+ };
1300
1548
  }
1301
1549
 
1302
- // src/index.ts
1303
- var import_picocolors2 = __toESM(require("picocolors"), 1);
1304
- var vite3 = __toESM(require("vite"), 1);
1550
+ // src/core/server.ts
1551
+ var vite5 = __toESM(require("vite"), 1);
1305
1552
 
1306
1553
  // src/core/utils/findOpenPort.ts
1307
1554
  var import_node_net = __toESM(require("net"), 1);
@@ -1326,17 +1573,6 @@ function findOpenPortRecursive(port, startPort, endPort) {
1326
1573
  });
1327
1574
  }
1328
1575
 
1329
- // src/core/utils/formatDuration.ts
1330
- function formatDuration(duration) {
1331
- if (duration < 1e3)
1332
- return `${duration} ms`;
1333
- if (duration < 1e4)
1334
- return `${(duration / 1e3).toFixed(3)} s`;
1335
- if (duration < 6e4)
1336
- return `${(duration / 1e3).toFixed(1)} s`;
1337
- return `${(duration / 1e3).toFixed(0)} s`;
1338
- }
1339
-
1340
1576
  // src/core/runners/createWebExtRunner.ts
1341
1577
  function createWebExtRunner() {
1342
1578
  let runner;
@@ -1394,8 +1630,93 @@ function createWebExtRunner() {
1394
1630
  var WARN_LOG_LEVEL = 40;
1395
1631
  var ERROR_LOG_LEVEL = 50;
1396
1632
 
1633
+ // src/core/server.ts
1634
+ async function getServerInfo() {
1635
+ const port = await findOpenPort(3e3, 3010);
1636
+ const hostname = "localhost";
1637
+ const origin = `http://${hostname}:${port}`;
1638
+ const serverConfig = {
1639
+ server: {
1640
+ origin
1641
+ }
1642
+ };
1643
+ return {
1644
+ port,
1645
+ hostname,
1646
+ origin,
1647
+ viteServerConfig: serverConfig
1648
+ };
1649
+ }
1650
+ async function setupServer(serverInfo, config) {
1651
+ const runner = createWebExtRunner();
1652
+ const viteServer = await vite5.createServer(
1653
+ vite5.mergeConfig(serverInfo, config.vite)
1654
+ );
1655
+ const start = async () => {
1656
+ await viteServer.listen(server.port);
1657
+ config.logger.success(`Started dev server @ ${serverInfo.origin}`);
1658
+ server.currentOutput = await buildInternal(config);
1659
+ config.logger.info("Opening browser...");
1660
+ await runner.openBrowser(config);
1661
+ config.logger.success("Opened!");
1662
+ };
1663
+ const reloadExtension = () => {
1664
+ viteServer.ws.send("wxt:reload-extension");
1665
+ };
1666
+ const reloadPage = (path5) => {
1667
+ viteServer.ws.send("wxt:reload-page", path5);
1668
+ };
1669
+ const reloadContentScript = (contentScript) => {
1670
+ viteServer.ws.send("wxt:reload-content-script", contentScript);
1671
+ };
1672
+ const server = {
1673
+ ...viteServer,
1674
+ start,
1675
+ currentOutput: {
1676
+ manifest: {
1677
+ manifest_version: 3,
1678
+ name: "",
1679
+ version: ""
1680
+ },
1681
+ publicAssets: [],
1682
+ steps: []
1683
+ },
1684
+ port: serverInfo.port,
1685
+ hostname: serverInfo.hostname,
1686
+ origin: serverInfo.origin,
1687
+ reloadExtension,
1688
+ reloadPage,
1689
+ reloadContentScript
1690
+ };
1691
+ return server;
1692
+ }
1693
+ function reloadContentScripts(steps, config, server) {
1694
+ if (config.manifestVersion === 3) {
1695
+ steps.forEach((step) => {
1696
+ const entry = step.entrypoints;
1697
+ if (Array.isArray(entry) || entry.type !== "content-script")
1698
+ return;
1699
+ const js = [getEntrypointBundlePath(entry, config.outDir, ".js")];
1700
+ const css = getContentScriptCssFiles([entry], server.currentOutput);
1701
+ server.reloadContentScript({
1702
+ js,
1703
+ css,
1704
+ ...entry.options
1705
+ });
1706
+ });
1707
+ } else {
1708
+ server.reloadExtension();
1709
+ }
1710
+ }
1711
+ function reloadHtmlPages(groups, server, config) {
1712
+ groups.flat().forEach((entry) => {
1713
+ const path5 = getEntrypointBundlePath(entry, config.outDir, ".html");
1714
+ server.reloadPage(path5);
1715
+ });
1716
+ }
1717
+
1397
1718
  // package.json
1398
- var version = "0.0.1";
1719
+ var version2 = "0.1.0";
1399
1720
 
1400
1721
  // src/core/utils/defineConfig.ts
1401
1722
  function defineConfig(config) {
@@ -1413,64 +1734,64 @@ async function build2(config) {
1413
1734
  return await buildInternal(internalConfig);
1414
1735
  }
1415
1736
  async function createServer2(config) {
1416
- const port = await findOpenPort(3e3, 3010);
1417
- const hostname = "localhost";
1418
- const origin = `http://${hostname}:${port}`;
1419
- const serverConfig = {
1420
- server: {
1421
- origin
1422
- }
1423
- };
1424
- const internalConfig = await getInternalConfig(
1425
- vite3.mergeConfig(serverConfig, config ?? {}),
1426
- "serve"
1427
- );
1428
- const runner = createWebExtRunner();
1429
- const viteServer = await vite3.createServer(internalConfig.vite);
1430
- const server = {
1431
- ...viteServer,
1432
- async listen(port2, isRestart) {
1433
- const res = await viteServer.listen(port2, isRestart);
1434
- if (!isRestart) {
1435
- internalConfig.logger.success(`Started dev server @ ${origin}`);
1436
- internalConfig.logger.info("Opening browser...");
1437
- await runner.openBrowser(internalConfig);
1438
- internalConfig.logger.success("Opened!");
1439
- }
1440
- return res;
1441
- },
1442
- logger: internalConfig.logger,
1443
- port,
1444
- hostname,
1445
- origin
1737
+ const serverInfo = await getServerInfo();
1738
+ const getLatestInternalConfig = () => {
1739
+ const viteConfig = vite6.mergeConfig(
1740
+ serverInfo.viteServerConfig,
1741
+ config?.vite ?? {}
1742
+ );
1743
+ return getInternalConfig({ ...config, vite: viteConfig }, "serve");
1446
1744
  };
1447
- internalConfig.logger.info("Created dev server");
1745
+ let internalConfig = await getLatestInternalConfig();
1746
+ const server = await setupServer(serverInfo, internalConfig);
1448
1747
  internalConfig.server = server;
1449
- await buildInternal(internalConfig);
1748
+ const fileChangedMutex = new import_async_mutex.Mutex();
1749
+ const changeQueue = [];
1750
+ server.ws.on("wxt:background-initialized", () => {
1751
+ reloadContentScripts(server.currentOutput.steps, internalConfig, server);
1752
+ });
1753
+ server.watcher.on("all", async (event, path5, _stats) => {
1754
+ if (path5.startsWith(internalConfig.outBaseDir))
1755
+ return;
1756
+ changeQueue.push([event, path5]);
1757
+ await fileChangedMutex.runExclusive(async () => {
1758
+ const fileChanges = changeQueue.splice(0, changeQueue.length);
1759
+ const changes = detectDevChanges(fileChanges, server.currentOutput);
1760
+ if (changes.type === "no-change")
1761
+ return;
1762
+ import_consola3.consola.info(
1763
+ `Changed: ${Array.from(new Set(fileChanges.map((change) => change[1]))).map((file) => import_picocolors3.default.dim((0, import_node_path4.relative)(internalConfig.root, file))).join(", ")}`
1764
+ );
1765
+ const rebuiltNames = changes.rebuildGroups.flat().map((entry) => {
1766
+ return import_picocolors3.default.cyan(
1767
+ (0, import_node_path4.relative)(internalConfig.outDir, getEntrypointOutputFile(entry, ""))
1768
+ );
1769
+ }).join(import_picocolors3.default.dim(", "));
1770
+ internalConfig = await getLatestInternalConfig();
1771
+ internalConfig.server = server;
1772
+ const { output: newOutput } = await rebuild(
1773
+ internalConfig,
1774
+ // TODO: this excludes new entrypoints, so they're not built until the dev command is restarted
1775
+ changes.rebuildGroups,
1776
+ changes.cachedOutput
1777
+ );
1778
+ server.currentOutput = newOutput;
1779
+ switch (changes.type) {
1780
+ case "extension-reload":
1781
+ server.reloadExtension();
1782
+ break;
1783
+ case "html-reload":
1784
+ reloadHtmlPages(changes.rebuildGroups, server, internalConfig);
1785
+ break;
1786
+ case "content-script-reload":
1787
+ reloadContentScripts(changes.changedSteps, internalConfig, server);
1788
+ break;
1789
+ }
1790
+ import_consola3.consola.success(`Reloaded: ${rebuiltNames}`);
1791
+ });
1792
+ });
1450
1793
  return server;
1451
1794
  }
1452
- async function buildInternal(config) {
1453
- const verb = config.command === "serve" ? "Pre-rendering" : "Building";
1454
- const target = `${config.browser}-mv${config.manifestVersion}`;
1455
- config.logger.info(
1456
- `${verb} ${import_picocolors2.default.cyan(target)} for ${import_picocolors2.default.cyan(config.mode)} with ${import_picocolors2.default.green(
1457
- `Vite ${vite3.version}`
1458
- )}`
1459
- );
1460
- const startTime = Date.now();
1461
- await import_fs_extra10.default.rm(config.outDir, { recursive: true, force: true });
1462
- await import_fs_extra10.default.ensureDir(config.outDir);
1463
- const entrypoints = await findEntrypoints(config);
1464
- await generateTypesDir(entrypoints, config);
1465
- const output = await buildEntrypoints(entrypoints, config);
1466
- const manifest = await generateMainfest(entrypoints, output, config);
1467
- await writeManifest(manifest, output, config);
1468
- config.logger.success(
1469
- `Built extension in ${formatDuration(Date.now() - startTime)}`
1470
- );
1471
- await printBuildSummary(output, config);
1472
- return output;
1473
- }
1474
1795
  // Annotate the CommonJS export names for ESM import in node:
1475
1796
  0 && (module.exports = {
1476
1797
  build,