wxt 0.20.27 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +0 -1
  2. package/dist/cli/cli-utils.mjs +2 -2
  3. package/dist/core/builders/vite/index.mjs +85 -46
  4. package/dist/core/builders/vite/plugins/devHtmlPrerender.mjs +1 -1
  5. package/dist/core/builders/vite/plugins/index.mjs +0 -1
  6. package/dist/core/builders/vite/plugins/removeEntrypointMainFunction.mjs +1 -1
  7. package/dist/core/clean.d.mts +0 -11
  8. package/dist/core/clean.mjs +9 -2
  9. package/dist/core/create-server.mjs +12 -11
  10. package/dist/core/define-web-ext-config.d.mts +1 -3
  11. package/dist/core/define-web-ext-config.mjs +1 -7
  12. package/dist/core/generate-wxt-dir.mjs +27 -25
  13. package/dist/core/index.d.mts +3 -2
  14. package/dist/core/index.mjs +1 -0
  15. package/dist/core/initialize.mjs +5 -5
  16. package/dist/core/resolve-config.mjs +40 -24
  17. package/dist/core/runners/web-ext.mjs +6 -5
  18. package/dist/core/utils/building/detect-dev-changes.mjs +17 -7
  19. package/dist/core/utils/building/internal-build.mjs +1 -1
  20. package/dist/core/utils/create-file-reloader.mjs +82 -27
  21. package/dist/core/utils/entrypoints.d.mts +10 -0
  22. package/dist/core/utils/index.d.mts +2 -0
  23. package/dist/core/utils/index.mjs +1 -0
  24. package/dist/core/utils/time.mjs +1 -10
  25. package/dist/core/utils/transform.mjs +1 -1
  26. package/dist/core/zip.mjs +15 -11
  27. package/dist/index.d.mts +4 -3
  28. package/dist/index.mjs +3 -2
  29. package/dist/testing/wxt-vitest-plugin.mjs +0 -2
  30. package/dist/types.d.mts +102 -41
  31. package/dist/utils/app-config.d.mts +1 -0
  32. package/dist/utils/app-config.mjs +2 -0
  33. package/dist/utils/content-script-ui/shadow-root.mjs +1 -1
  34. package/dist/version.mjs +1 -1
  35. package/dist/virtual/mock-browser.d.mts +1 -1
  36. package/dist/virtual/mock-browser.mjs +1 -1
  37. package/package.json +22 -20
  38. package/dist/core/builders/vite/plugins/download.mjs +0 -30
  39. package/dist/core/runners/index.mjs +0 -15
  40. package/dist/core/utils/network.mjs +0 -36
  41. package/dist/core/utils/picomatch-multiple.mjs +0 -24
  42. package/dist/core/utils/wsl.mjs +0 -8
  43. package/dist/testing/index.d.mts +0 -3
  44. package/dist/testing/index.mjs +0 -3
@@ -5,15 +5,19 @@ import { createFsCache } from "./utils/cache.mjs";
5
5
  import { getEslintVersion } from "./utils/eslint.mjs";
6
6
  import { safeStringToNumber } from "./utils/number.mjs";
7
7
  import { loadEnv } from "./utils/env.mjs";
8
+ import { createSafariRunner } from "./runners/safari.mjs";
9
+ import { createWslRunner } from "./runners/wsl.mjs";
10
+ import { createManualRunner } from "./runners/manual.mjs";
8
11
  import { pathExists } from "./utils/fs.mjs";
9
12
  import { glob } from "tinyglobby";
10
13
  import path from "node:path";
11
- import consola, { LogLevels } from "consola";
12
14
  import { loadConfig } from "c12";
13
15
  import { resolve as resolve$1 } from "import-meta-resolve";
16
+ import consola, { LogLevels } from "consola";
14
17
  import defu from "defu";
15
18
  import { getPort } from "get-port-please";
16
19
  import { fileURLToPath, pathToFileURL } from "node:url";
20
+ import isWsl from "is-wsl";
17
21
  //#region src/core/resolve-config.ts
18
22
  /**
19
23
  * Given an inline config, discover the config file if necessary, merge the
@@ -69,14 +73,13 @@ async function resolveConfig(inlineConfig, command) {
69
73
  const outDirTemplate = (mergedConfig.outDirTemplate ?? `${browser}-mv${manifestVersion}${modeSuffix}`).replaceAll("{{browser}}", browser).replaceAll("{{manifestVersion}}", manifestVersion.toString()).replaceAll("{{modeSuffix}}", modeSuffix).replaceAll("{{mode}}", mode).replaceAll("{{command}}", command);
70
74
  const outDir = path.resolve(outBaseDir, outDirTemplate);
71
75
  const reloadCommand = mergedConfig.dev?.reloadCommand ?? "Alt+R";
72
- if (inlineConfig.runner != null || userConfig.runner != null) logger.warn("`InlineConfig#runner` is deprecated, use `InlineConfig#webExt` instead. See https://wxt.dev/guide/resources/upgrading.html#v0-19-0-rarr-v0-20-0");
73
- const runnerConfig = await loadConfig({
76
+ const webExt = await loadConfig({
74
77
  name: "web-ext",
75
78
  cwd: root,
76
79
  globalRc: true,
77
80
  rcFile: ".webextrc",
78
- overrides: inlineConfig.webExt ?? inlineConfig.runner,
79
- defaults: userConfig.webExt ?? userConfig.runner
81
+ overrides: inlineConfig.webExt,
82
+ defaults: userConfig.webExt
80
83
  });
81
84
  const alias = Object.fromEntries(Object.entries({
82
85
  ...mergedConfig.alias,
@@ -87,10 +90,9 @@ async function resolveConfig(inlineConfig, command) {
87
90
  }).map(([key, value]) => [key, path.resolve(root, value)]));
88
91
  let devServerConfig;
89
92
  if (command === "serve") {
90
- if (mergedConfig.dev?.server?.hostname) logger.warn(`The 'hostname' option is deprecated, please use 'host' or 'origin' depending on your circumstances.`);
91
- const host = mergedConfig.dev?.server?.host ?? mergedConfig.dev?.server?.hostname ?? "localhost";
93
+ const host = mergedConfig.dev?.server?.host ?? "localhost";
92
94
  let port = mergedConfig.dev?.server?.port;
93
- const origin = mergedConfig.dev?.server?.origin ?? mergedConfig.dev?.server?.hostname ?? "localhost";
95
+ const origin = mergedConfig.dev?.server?.origin ?? "localhost";
94
96
  const strictPort = mergedConfig.dev?.server?.strictPort ?? false;
95
97
  if (port == null || !isFinite(port)) port = await getPort({
96
98
  host,
@@ -139,7 +141,8 @@ async function resolveConfig(inlineConfig, command) {
139
141
  publicDir,
140
142
  wxtModuleDir,
141
143
  root,
142
- runnerConfig,
144
+ webExt,
145
+ runner: command === "serve" ? await resolveRunner(browser, logger, mergedConfig) : createManualRunner(),
143
146
  srcDir,
144
147
  typesDir,
145
148
  wxtDir,
@@ -149,6 +152,7 @@ async function resolveConfig(inlineConfig, command) {
149
152
  alias,
150
153
  experimental: defu(mergedConfig.experimental, {}),
151
154
  suppressWarnings: mergedConfig.suppressWarnings ?? {},
155
+ watchOptions: mergedConfig.watchOptions ?? {},
152
156
  dev: {
153
157
  server: devServerConfig,
154
158
  reloadCommand
@@ -187,10 +191,10 @@ function resolveZipConfig(root, browser, outBaseDir, mergedConfig) {
187
191
  const downloadedPackagesDir = path.resolve(root, ".wxt/local_modules");
188
192
  return {
189
193
  name: void 0,
190
- sourcesTemplate: "{{name}}-{{version}}-sources.zip",
191
- artifactTemplate: "{{name}}-{{version}}-{{browser}}.zip",
194
+ sourcesTemplate: "{{name}}-{{packageVersion}}-sources{{modeSuffix}}.zip",
195
+ artifactTemplate: "{{name}}-{{packageVersion}}-{{browser}}{{modeSuffix}}.zip",
192
196
  sourcesRoot: root,
193
- includeSources: [],
197
+ includeSources: mergedConfig.zip?.includeSources ?? ["**/*"],
194
198
  compressionLevel: 9,
195
199
  ...mergedConfig.zip,
196
200
  zipSources: mergedConfig.zip?.zipSources ?? ["firefox", "opera"].includes(browser),
@@ -198,12 +202,12 @@ function resolveZipConfig(root, browser, outBaseDir, mergedConfig) {
198
202
  excludeSources: [
199
203
  "**/node_modules",
200
204
  "**/web-ext.config.ts",
201
- "**/.*",
202
205
  "**/__tests__/**",
203
206
  "**/*.+(test|spec).?(c|m)+(j|t)s?(x)",
204
207
  `${path.relative(root, outBaseDir)}/**`,
205
208
  ...mergedConfig.zip?.excludeSources ?? []
206
209
  ],
210
+ dotSources: mergedConfig.zip?.dotSources ?? false,
207
211
  downloadPackages: mergedConfig.zip?.downloadPackages ?? [],
208
212
  downloadedPackagesDir
209
213
  };
@@ -224,7 +228,7 @@ function resolveAnalysisConfig(root, mergedConfig) {
224
228
  }
225
229
  async function getUnimportOptions(wxtDir, srcDir, logger, config) {
226
230
  const disabled = config.imports === false;
227
- const eslintrc = await getUnimportEslintOptions(wxtDir, config.imports);
231
+ const eslintrc = await getUnimportEslintOptions(logger, wxtDir, config.imports);
228
232
  const invalidExports = ["options"];
229
233
  const defineImportsAndTypes = (imports, typeImports) => [...imports, ...typeImports.map((name) => ({
230
234
  name,
@@ -233,7 +237,7 @@ async function getUnimportOptions(wxtDir, srcDir, logger, config) {
233
237
  const defaultOptions = {
234
238
  imports: [{
235
239
  name: "fakeBrowser",
236
- from: "wxt/testing"
240
+ from: "wxt/testing/fake-browser"
237
241
  }],
238
242
  presets: [
239
243
  {
@@ -337,26 +341,26 @@ async function getUnimportOptions(wxtDir, srcDir, logger, config) {
337
341
  };
338
342
  return defu(config.imports ?? {}, defaultOptions);
339
343
  }
340
- async function getUnimportEslintOptions(wxtDir, options) {
344
+ async function getUnimportEslintOptions(logger, wxtDir, options) {
341
345
  const inlineEnabled = options === false ? false : options?.eslintrc?.enabled ?? "auto";
346
+ const version = await getEslintVersion();
347
+ const major = parseInt(version[0]);
342
348
  let enabled;
343
349
  switch (inlineEnabled) {
344
350
  case "auto":
345
- const version = await getEslintVersion();
346
- let major = parseInt(version[0]);
347
- if (isNaN(major)) enabled = false;
348
- if (major <= 8) enabled = 8;
351
+ case true:
352
+ if (isNaN(major)) {
353
+ if (inlineEnabled === true) logger.warn("Could not determine installed ESLint version, `eslint-auto-imports.mjs` not generated");
354
+ enabled = false;
355
+ } else if (major <= 8) enabled = 8;
349
356
  else if (major >= 9) enabled = 9;
350
357
  else enabled = false;
351
358
  break;
352
- case true:
353
- enabled = 8;
354
- break;
355
359
  default: enabled = inlineEnabled;
356
360
  }
357
361
  return {
358
362
  enabled,
359
- filePath: path.resolve(wxtDir, enabled === 9 ? "eslint-auto-imports.mjs" : "eslintrc-auto-import.json"),
363
+ filePath: path.resolve(wxtDir, enabled === 8 ? "eslintrc-auto-import.json" : "eslint-auto-imports.mjs"),
360
364
  globalsPropValue: true
361
365
  };
362
366
  }
@@ -423,5 +427,17 @@ async function resolveWxtUserModules(root, modulesDir, modules = []) {
423
427
  }));
424
428
  return [...npmModules, ...localModules];
425
429
  }
430
+ async function resolveRunner(browser, logger, mergedConfig) {
431
+ if (browser === "safari") return createSafariRunner();
432
+ if (isWsl) return createWslRunner();
433
+ try {
434
+ const { createWebExtRunner } = await import("./runners/web-ext.mjs");
435
+ return mergedConfig.webExt?.disabled ? createManualRunner() : createWebExtRunner();
436
+ } catch (err) {
437
+ if (err?.code !== "ERR_MODULE_NOT_FOUND") throw err;
438
+ logger.debug("Error loading the web-ext runner", err);
439
+ }
440
+ return createManualRunner();
441
+ }
426
442
  //#endregion
427
443
  export { resolveConfig };
@@ -1,6 +1,8 @@
1
- import { formatDuration } from "../utils/time.mjs";
2
1
  import { wxt } from "../wxt.mjs";
2
+ import { formatDuration } from "../utils/time.mjs";
3
3
  import defu from "defu";
4
+ import webExt from "web-ext";
5
+ import { consoleStream } from "web-ext/util/logger";
4
6
  //#region src/core/runners/web-ext.ts
5
7
  /** Create an `ExtensionRunner` backed by `web-ext`. */
6
8
  function createWebExtRunner() {
@@ -11,12 +13,11 @@ function createWebExtRunner() {
11
13
  },
12
14
  async openBrowser() {
13
15
  const startTime = Date.now();
14
- const webExtLogger = await import("web-ext-run/util/logger");
15
- webExtLogger.consoleStream.write = ({ level, msg, name }) => {
16
+ consoleStream.write = ({ level, msg, name }) => {
16
17
  if (level >= ERROR_LOG_LEVEL) wxt.logger.error(name, msg);
17
18
  if (level >= WARN_LOG_LEVEL) wxt.logger.warn(msg);
18
19
  };
19
- const wxtUserConfig = wxt.config.runnerConfig.config;
20
+ const wxtUserConfig = wxt.config.webExt.config;
20
21
  const finalConfig = {
21
22
  browserConsole: wxtUserConfig?.openConsole,
22
23
  devtools: wxtUserConfig?.openDevtools,
@@ -43,7 +44,7 @@ function createWebExtRunner() {
43
44
  const options = { shouldExitProgram: false };
44
45
  wxt.logger.debug("web-ext config:", finalConfig);
45
46
  wxt.logger.debug("web-ext options:", options);
46
- runner = await (await import("web-ext-run")).default.cmd.run(finalConfig, options);
47
+ runner = await webExt.cmd.run(finalConfig, options);
47
48
  const duration = Date.now() - startTime;
48
49
  wxt.logger.success(`Opened browser in ${formatDuration(duration)}`);
49
50
  },
@@ -36,11 +36,12 @@ import { wxt } from "../../wxt.mjs";
36
36
  * - .env (environment variable changed could effect build)
37
37
  */
38
38
  function detectDevChanges(changedFiles, currentOutput) {
39
- if (some(changedFiles, (file) => file === wxt.config.userConfigMetadata.configFile)) return { type: "full-restart" };
40
- if (some(changedFiles, (file) => file.startsWith(wxt.config.modulesDir))) return { type: "full-restart" };
41
- if (some(changedFiles, (file) => file === wxt.config.runnerConfig.configFile)) return { type: "browser-restart" };
42
- const changedSteps = new Set(changedFiles.flatMap((changedFile) => findEffectedSteps(changedFile, currentOutput)));
43
- if (changedSteps.size === 0) if (some(changedFiles, (file) => file.startsWith(wxt.config.publicDir))) return {
39
+ const relevantChangedFiles = getRelevantDevChangedFiles(changedFiles, currentOutput);
40
+ if (some(relevantChangedFiles, (file) => file === wxt.config.userConfigMetadata.configFile)) return { type: "full-restart" };
41
+ if (some(relevantChangedFiles, (file) => file.startsWith(wxt.config.modulesDir))) return { type: "full-restart" };
42
+ if (some(relevantChangedFiles, (file) => file === wxt.config.webExt.configFile)) return { type: "browser-restart" };
43
+ const changedSteps = new Set(relevantChangedFiles.flatMap((changedFile) => findEffectedSteps(changedFile, currentOutput)));
44
+ if (changedSteps.size === 0) if (some(relevantChangedFiles, (file) => file.startsWith(wxt.config.publicDir))) return {
44
45
  type: "extension-reload",
45
46
  rebuildGroups: [],
46
47
  cachedOutput: currentOutput
@@ -58,7 +59,7 @@ function detectDevChanges(changedFiles, currentOutput) {
58
59
  };
59
60
  for (const step of currentOutput.steps) if (changedSteps.has(step)) changedOutput.steps.push(step);
60
61
  else unchangedOutput.steps.push(step);
61
- if (changedFiles.length > 0 && every(changedFiles, (file) => file.endsWith(".html"))) return {
62
+ if (relevantChangedFiles.length > 0 && every(relevantChangedFiles, (file) => file.endsWith(".html"))) return {
62
63
  type: "html-reload",
63
64
  cachedOutput: unchangedOutput,
64
65
  rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
@@ -75,6 +76,15 @@ function detectDevChanges(changedFiles, currentOutput) {
75
76
  rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
76
77
  };
77
78
  }
79
+ function getRelevantDevChangedFiles(changedFiles, currentOutput) {
80
+ return Array.from(new Set(changedFiles)).filter((changedFile) => {
81
+ if (changedFile === wxt.config.userConfigMetadata.configFile) return true;
82
+ if (changedFile.startsWith(wxt.config.modulesDir)) return true;
83
+ if (changedFile === wxt.config.webExt.configFile) return true;
84
+ if (changedFile.startsWith(wxt.config.publicDir)) return true;
85
+ return findEffectedSteps(changedFile, currentOutput).length > 0;
86
+ });
87
+ }
78
88
  /**
79
89
  * For a single change, return all the step of the build output that were
80
90
  * effected by it.
@@ -93,4 +103,4 @@ function findEffectedSteps(changedFile, currentOutput) {
93
103
  return changes;
94
104
  }
95
105
  //#endregion
96
- export { detectDevChanges };
106
+ export { detectDevChanges, getRelevantDevChangedFiles };
@@ -1,8 +1,8 @@
1
1
  import { unnormalizePath } from "../paths.mjs";
2
- import { formatDuration } from "../time.mjs";
3
2
  import { wxt } from "../../wxt.mjs";
4
3
  import { findEntrypoints } from "./find-entrypoints.mjs";
5
4
  import { groupEntrypoints } from "./group-entrypoints.mjs";
5
+ import { formatDuration } from "../time.mjs";
6
6
  import { printBuildSummary } from "../log/printBuildSummary.mjs";
7
7
  import "../log/index.mjs";
8
8
  import { rebuild } from "./rebuild.mjs";
@@ -1,7 +1,10 @@
1
+ import { normalizePath } from "./paths.mjs";
1
2
  import { getEntrypointBundlePath, isHtmlEntrypoint } from "./entrypoints.mjs";
3
+ import { filterTruthy, toArray } from "./arrays.mjs";
2
4
  import { wxt } from "../wxt.mjs";
3
- import { detectDevChanges } from "./building/detect-dev-changes.mjs";
5
+ import { detectDevChanges, getRelevantDevChangedFiles } from "./building/detect-dev-changes.mjs";
4
6
  import { findEntrypoints } from "./building/find-entrypoints.mjs";
7
+ import { groupEntrypoints } from "./building/group-entrypoints.mjs";
5
8
  import { getContentScriptJs, mapWxtOptionsToRegisteredContentScript } from "./content-scripts.mjs";
6
9
  import { getContentScriptCssFiles, getContentScriptsCssMap } from "./manifest.mjs";
7
10
  import { rebuild } from "./building/rebuild.mjs";
@@ -9,7 +12,6 @@ import "./building/index.mjs";
9
12
  import { isBabelSyntaxError, logBabelSyntaxError } from "./syntax-errors.mjs";
10
13
  import { relative } from "node:path";
11
14
  import { styleText } from "node:util";
12
- import { debounce } from "perfect-debounce";
13
15
  import { Mutex } from "async-mutex";
14
16
  //#region src/core/utils/create-file-reloader.ts
15
17
  /**
@@ -19,15 +21,23 @@ import { Mutex } from "async-mutex";
19
21
  function createFileReloader(server) {
20
22
  const fileChangedMutex = new Mutex();
21
23
  const changeQueue = [];
22
- const cb = async (event, path) => {
23
- changeQueue.push([event, path]);
24
+ let processLoop;
25
+ const processQueue = async () => {
24
26
  await fileChangedMutex.runExclusive(async () => {
25
- if (server.currentOutput == null) return;
26
27
  const fileChanges = changeQueue.splice(0, changeQueue.length).map(([_, file]) => file);
27
28
  if (fileChanges.length === 0) return;
29
+ if (server.currentOutput == null) return;
30
+ const normalizedFileChanges = fileChanges.map(normalizePath);
31
+ const relevantFileChanges = getRelevantDevChangedFiles(fileChanges, server.currentOutput);
32
+ const normalizedEntrypointsDir = normalizePath(wxt.config.entrypointsDir);
33
+ const hasEntrypointDirChange = normalizedFileChanges.some((file) => file === normalizedEntrypointsDir || file.startsWith(`${normalizedEntrypointsDir}/`));
34
+ if (relevantFileChanges.length === 0 && !hasEntrypointDirChange) return;
28
35
  await wxt.reloadConfig();
29
- const changes = detectDevChanges(fileChanges, server.currentOutput);
30
- if (changes.type === "no-change") return;
36
+ const allEntrypoints = await findEntrypoints();
37
+ const allEntrypointGroups = groupEntrypoints(allEntrypoints);
38
+ const newEntrypointGroups = getNewEntrypointGroups(normalizedFileChanges, allEntrypointGroups, server.currentOutput);
39
+ if (relevantFileChanges.length === 0 && newEntrypointGroups.length === 0) return;
40
+ const changes = detectDevChanges(relevantFileChanges, server.currentOutput);
31
41
  if (changes.type === "full-restart") {
32
42
  wxt.logger.info("Config changed, restarting server...");
33
43
  server.restart();
@@ -38,25 +48,24 @@ function createFileReloader(server) {
38
48
  server.restartBrowser();
39
49
  return;
40
50
  }
41
- wxt.logger.info(`Changed: ${Array.from(new Set(fileChanges)).map((file) => styleText("dim", relative(wxt.config.root, file))).join(", ")}`);
42
- const allEntrypoints = await findEntrypoints();
51
+ const hasNewEntrypoints = newEntrypointGroups.length > 0;
52
+ if (changes.type === "no-change" && !hasNewEntrypoints) return;
53
+ const changedFilesToLog = Array.from(new Set([...relevantFileChanges, ...newEntrypointGroups.flatMap((group) => toArray(group)).map((entry) => entry.inputPath)]));
54
+ wxt.logger.info(`Changed: ${changedFilesToLog.map((file) => styleText("dim", relative(wxt.config.root, file))).join(", ")}`);
55
+ const rebuildGroups = changes.type === "no-change" ? newEntrypointGroups : mergeEntrypointGroups(getLatestRebuildGroups(changes.rebuildGroups, allEntrypointGroups), newEntrypointGroups);
43
56
  try {
44
- const { output: newOutput } = await rebuild(allEntrypoints, changes.rebuildGroups, changes.cachedOutput);
57
+ const { output: newOutput } = await rebuild(allEntrypoints, rebuildGroups, changes.type === "no-change" ? server.currentOutput : changes.cachedOutput);
45
58
  server.currentOutput = newOutput;
46
- switch (changes.type) {
47
- case "extension-reload":
48
- server.reloadExtension();
49
- wxt.logger.success(`Reloaded extension`);
50
- break;
51
- case "html-reload":
52
- const { reloadedNames } = reloadHtmlPages(changes.rebuildGroups, server);
53
- wxt.logger.success(`Reloaded: ${getFilenameList(reloadedNames)}`);
54
- break;
55
- case "content-script-reload":
56
- reloadContentScripts(changes.changedSteps, server);
57
- const rebuiltNames = changes.rebuildGroups.flat().map((entry) => entry.name);
58
- wxt.logger.success(`Reloaded: ${getFilenameList(rebuiltNames)}`);
59
- break;
59
+ if (hasNewEntrypoints || changes.type === "extension-reload") {
60
+ server.reloadExtension();
61
+ wxt.logger.success(`Reloaded extension`);
62
+ } else if (changes.type === "html-reload") {
63
+ const { reloadedNames } = reloadHtmlPages(rebuildGroups, server);
64
+ wxt.logger.success(`Reloaded: ${getFilenameList(reloadedNames)}`);
65
+ } else if (changes.type === "content-script-reload") {
66
+ reloadContentScripts(changes.changedSteps, server);
67
+ const rebuiltNames = rebuildGroups.flat().map((entry) => entry.name);
68
+ wxt.logger.success(`Reloaded: ${getFilenameList(rebuiltNames)}`);
60
69
  }
61
70
  } catch {}
62
71
  }).catch((error) => {
@@ -64,11 +73,57 @@ function createFileReloader(server) {
64
73
  logBabelSyntaxError(error);
65
74
  });
66
75
  };
67
- return debounce(cb, wxt.config.dev.server.watchDebounce, {
68
- leading: true,
69
- trailing: false
76
+ const waitForDebounceWindow = async () => {
77
+ await new Promise((resolve) => {
78
+ setTimeout(resolve, wxt.config.dev.server.watchDebounce);
79
+ });
80
+ };
81
+ const queueWorker = async () => {
82
+ while (true) {
83
+ await processQueue();
84
+ await waitForDebounceWindow();
85
+ if (changeQueue.length === 0) break;
86
+ }
87
+ };
88
+ return async (event, path) => {
89
+ changeQueue.push([event, path]);
90
+ processLoop ??= queueWorker().finally(() => {
91
+ processLoop = void 0;
92
+ });
93
+ await processLoop;
94
+ };
95
+ }
96
+ function getNewEntrypointGroups(normalizedFileChanges, allEntrypointGroups, currentOutput) {
97
+ const changedFiles = new Set(normalizedFileChanges);
98
+ const builtEntrypointPaths = new Set(currentOutput.steps.flatMap((step) => toArray(step.entrypoints).map((entry) => normalizePath(entry.inputPath))));
99
+ return allEntrypointGroups.filter((group) => {
100
+ const groupEntrypoints = toArray(group);
101
+ const hasNewEntrypoint = groupEntrypoints.some((entry) => !builtEntrypointPaths.has(normalizePath(entry.inputPath)));
102
+ const changedEntrypoint = groupEntrypoints.some((entry) => changedFiles.has(normalizePath(entry.inputPath)));
103
+ return hasNewEntrypoint && changedEntrypoint;
70
104
  });
71
105
  }
106
+ function getLatestRebuildGroups(rebuildGroups, allEntrypointGroups) {
107
+ const groupByEntrypointPath = /* @__PURE__ */ new Map();
108
+ allEntrypointGroups.forEach((group) => {
109
+ toArray(group).forEach((entry) => {
110
+ groupByEntrypointPath.set(normalizePath(entry.inputPath), group);
111
+ });
112
+ });
113
+ return mergeEntrypointGroups(rebuildGroups.flatMap((group) => {
114
+ return filterTruthy(toArray(group).map((entry) => groupByEntrypointPath.get(normalizePath(entry.inputPath))));
115
+ }));
116
+ }
117
+ function mergeEntrypointGroups(...groups) {
118
+ const deduped = /* @__PURE__ */ new Map();
119
+ groups.flat().forEach((group) => {
120
+ deduped.set(getEntrypointGroupKey(group), group);
121
+ });
122
+ return [...deduped.values()];
123
+ }
124
+ function getEntrypointGroupKey(group) {
125
+ return toArray(group).map((entry) => normalizePath(entry.inputPath)).sort().join("|");
126
+ }
72
127
  /**
73
128
  * From the server, tell the client to reload content scripts from the provided
74
129
  * build step outputs.
@@ -0,0 +1,10 @@
1
+ import { Entrypoint } from "../../types.mjs";
2
+
3
+ //#region src/core/utils/entrypoints.d.ts
4
+ /**
5
+ * Return's the entrypoint's output path relative to the output directory. Used
6
+ * for paths in the manifest and rollup's bundle.
7
+ */
8
+ declare function getEntrypointBundlePath(entrypoint: Entrypoint, outDir: string, ext: string): string;
9
+ //#endregion
10
+ export { getEntrypointBundlePath };
@@ -0,0 +1,2 @@
1
+ import { normalizePath } from "./paths.mjs";
2
+ import { getEntrypointBundlePath } from "./entrypoints.mjs";
@@ -1,2 +1,3 @@
1
1
  import "./paths.mjs";
2
+ import "./entrypoints.mjs";
2
3
  export {};
@@ -5,14 +5,5 @@ function formatDuration(duration) {
5
5
  if (duration < 6e4) return `${(duration / 1e3).toFixed(1)} s`;
6
6
  return `${(duration / 1e3).toFixed(0)} s`;
7
7
  }
8
- /** Add a timeout to a promise. */
9
- function withTimeout(promise, duration) {
10
- return new Promise((res, rej) => {
11
- const timeout = setTimeout(() => {
12
- rej(`Promise timed out after ${duration}ms`);
13
- }, duration);
14
- promise.then(res).catch(rej).finally(() => clearTimeout(timeout));
15
- });
16
- }
17
8
  //#endregion
18
- export { formatDuration, withTimeout };
9
+ export { formatDuration };
@@ -28,7 +28,7 @@ function removeMainFunctionCode(code) {
28
28
  function emptyMainFunction(mod) {
29
29
  if (mod.exports?.default?.$type === "function-call") {
30
30
  if (mod.exports.default.$ast?.arguments?.[0]?.body) delete mod.exports.default.$ast.arguments[0];
31
- else if (mod.exports.default.$ast?.arguments?.[0]?.properties) mod.exports.default.$ast.arguments[0].properties = mod.exports.default.$ast.arguments[0].properties.filter((prop) => prop.key.name !== "main");
31
+ else if (mod.exports.default.$ast?.arguments?.[0]?.properties) mod.exports.default.$ast.arguments[0].properties = mod.exports.default.$ast.arguments[0].properties.filter((prop) => prop.key?.name !== "main");
32
32
  }
33
33
  }
34
34
  function removeUnusedTopLevelVariables(mod) {
package/dist/core/zip.mjs CHANGED
@@ -1,15 +1,14 @@
1
1
  import { normalizePath } from "./utils/paths.mjs";
2
2
  import "./utils/index.mjs";
3
3
  import { safeFilename } from "./utils/strings.mjs";
4
- import { formatDuration } from "./utils/time.mjs";
5
4
  import { registerWxt, wxt } from "./wxt.mjs";
6
5
  import { findEntrypoints } from "./utils/building/find-entrypoints.mjs";
6
+ import { formatDuration } from "./utils/time.mjs";
7
7
  import { printFileList } from "./utils/log/printFileList.mjs";
8
8
  import "./utils/log/index.mjs";
9
9
  import { getPackageJson } from "./utils/package.mjs";
10
10
  import { internalBuild } from "./utils/building/internal-build.mjs";
11
11
  import "./utils/building/index.mjs";
12
- import { picomatchMultiple } from "./utils/picomatch-multiple.mjs";
13
12
  import { mkdir, readFile } from "node:fs/promises";
14
13
  import { glob } from "tinyglobby";
15
14
  import path from "node:path";
@@ -31,7 +30,11 @@ async function zip(config) {
31
30
  const zipFiles = [];
32
31
  const packageJson = await getPackageJson();
33
32
  const projectName = wxt.config.zip.name ?? safeFilename(packageJson?.name || path.basename(process.cwd()));
34
- const applyTemplate = (template) => template.replaceAll("{{name}}", projectName).replaceAll("{{browser}}", wxt.config.browser).replaceAll("{{version}}", output.manifest.version_name ?? output.manifest.version).replaceAll("{{packageVersion}}", packageJson?.version).replaceAll("{{mode}}", wxt.config.mode).replaceAll("{{manifestVersion}}", `mv${wxt.config.manifestVersion}`);
33
+ const modeSuffix = {
34
+ production: "",
35
+ development: "-dev"
36
+ }[wxt.config.mode] ?? `-${wxt.config.mode}`;
37
+ const applyTemplate = (template) => template.replaceAll("{{name}}", projectName).replaceAll("{{browser}}", wxt.config.browser).replaceAll("{{version}}", output.manifest.version).replaceAll("{{versionName}}", output.manifest.version_name ?? output.manifest.version).replaceAll("{{packageVersion}}", packageJson?.version).replaceAll("{{modeSuffix}}", modeSuffix).replaceAll("{{mode}}", wxt.config.mode).replaceAll("{{manifestVersion}}", `mv${wxt.config.manifestVersion}`);
35
38
  await mkdir(wxt.config.outBaseDir, { recursive: true });
36
39
  await wxt.hooks.callHook("zip:extension:start", wxt);
37
40
  const outZipFilename = applyTemplate(wxt.config.zip.artifactTemplate);
@@ -46,16 +49,18 @@ async function zip(config) {
46
49
  const { overrides, files: downloadedPackages } = await downloadPrivatePackages();
47
50
  const sourcesZipFilename = applyTemplate(wxt.config.zip.sourcesTemplate);
48
51
  const sourcesZipPath = path.resolve(wxt.config.outBaseDir, sourcesZipFilename);
49
- await zipDir(wxt.config.zip.sourcesRoot, sourcesZipPath, {
52
+ const files = await zipDir(wxt.config.zip.sourcesRoot, sourcesZipPath, {
50
53
  include: wxt.config.zip.includeSources,
51
54
  exclude: excludeSources,
52
55
  transform(absolutePath, zipPath, content) {
53
56
  if (zipPath.endsWith("package.json")) return addOverridesToPackageJson(absolutePath, content, overrides);
54
57
  },
55
- additionalFiles: downloadedPackages
58
+ additionalFiles: downloadedPackages,
59
+ dot: wxt.config.zip.dotSources
56
60
  });
57
61
  zipFiles.push(sourcesZipPath);
58
62
  await wxt.hooks.callHook("zip:sources:done", wxt, sourcesZipPath);
63
+ await printFileList(wxt.logger.info, `Sources included in \`${sourcesZipFilename}\``, wxt.config.zip.sourcesRoot, files);
59
64
  }
60
65
  await printFileList(wxt.logger.success, `Zipped extension in ${formatDuration(Date.now() - start)}`, wxt.config.outBaseDir, zipFiles);
61
66
  await wxt.hooks.callHook("zip:done", wxt, zipFiles);
@@ -63,14 +68,12 @@ async function zip(config) {
63
68
  }
64
69
  async function zipDir(directory, outputPath, options) {
65
70
  const archive = new JSZip();
66
- const filesToZip = [...(await glob(["**/*", ...options?.include || []], {
71
+ const filesToZip = [...await glob(options?.include ?? ["**/*"], {
67
72
  cwd: directory,
68
- ignore: ["**/node_modules"],
73
+ ignore: options?.exclude ?? [],
69
74
  onlyFiles: true,
70
- expandDirectories: false
71
- })).filter((relativePath) => {
72
- return picomatchMultiple(relativePath, options?.include) || !picomatchMultiple(relativePath, options?.exclude);
73
- }), ...(options?.additionalFiles ?? []).map((file) => path.relative(directory, file))];
75
+ dot: options?.dot
76
+ }), ...(options?.additionalFiles ?? []).map((file) => path.relative(directory, file))].sort();
74
77
  for (const file of filesToZip) {
75
78
  const absolutePath = path.resolve(directory, file);
76
79
  if (file.endsWith(".json")) {
@@ -89,6 +92,7 @@ async function zipDir(directory, outputPath, options) {
89
92
  compressionOptions: { level: wxt.config.zip.compressionLevel }
90
93
  }
91
94
  }).pipe(createWriteStream(outputPath)).on("error", reject).on("close", resolve));
95
+ return filesToZip;
92
96
  }
93
97
  async function downloadPrivatePackages() {
94
98
  const overrides = {};
package/dist/index.d.mts CHANGED
@@ -1,12 +1,13 @@
1
- import { BackgroundDefinition, BackgroundEntrypoint, BackgroundEntrypointOptions, BaseContentScriptEntrypointOptions, BaseEntrypoint, BaseEntrypointOptions, BaseScriptEntrypointOptions, BuildOutput, BuildStepOutput, ConfigEnv, ContentScriptDefinition, ContentScriptEntrypoint, CopiedPublicFile, Dependency, Entrypoint, EntrypointGroup, EntrypointInfo, EslintGlobalsPropValue, Eslintrc, ExtensionRunner, ExtensionRunnerConfig, FirefoxDataCollectionPermissions, FirefoxDataCollectionType, FsCache, GeneratedPublicFile, GenericEntrypoint, HookResult, InlineConfig, IsolatedWorldContentScriptDefinition, IsolatedWorldContentScriptEntrypointOptions, Logger, MainWorldContentScriptDefinition, MainWorldContentScriptEntrypointOptions, OnContentScriptStopped, OptionsEntrypoint, OptionsEntrypointOptions, OutputAsset, OutputChunk, OutputFile, PerBrowserMap, PerBrowserOption, PopupEntrypoint, PopupEntrypointOptions, PublicPathEntry, ReloadContentScriptPayload, ResolvedBasePublicFile, ResolvedConfig, ResolvedEslintrc, ResolvedPerBrowserOptions, ResolvedPublicFile, ServerInfo, SidepanelEntrypoint, SidepanelEntrypointOptions, TargetBrowser, TargetManifestVersion, ThemeIcon, UnlistedScriptDefinition, UnlistedScriptEntrypoint, UserConfig, UserManifest, UserManifestFn, WebExtConfig, Wxt, WxtBuilder, WxtBuilderServer, WxtCommand, WxtDevServer, WxtDirEntry, WxtDirFileEntry, WxtDirTypeReferenceEntry, WxtHooks, WxtModule, WxtModuleOptions, WxtModuleSetup, WxtModuleWithMetadata, WxtPackageManager, WxtPlugin, WxtResolvedUnimportOptions, WxtUnimportOptions, WxtViteConfig } from "./types.mjs";
1
+ import { BackgroundDefinition, BackgroundEntrypoint, BackgroundEntrypointOptions, BaseContentScriptEntrypointOptions, BaseEntrypoint, BaseEntrypointOptions, BaseScriptEntrypointOptions, BuildOutput, BuildStepOutput, ConfigEnv, ContentScriptDefinition, ContentScriptEntrypoint, CopiedPublicFile, Dependency, Entrypoint, EntrypointGroup, EntrypointInfo, EslintGlobalsPropValue, Eslintrc, ExtensionRunner, FirefoxDataCollectionPermissions, FirefoxDataCollectionType, FsCache, GeneratedPublicFile, GenericEntrypoint, HookResult, InlineConfig, IsolatedWorldContentScriptDefinition, IsolatedWorldContentScriptEntrypointOptions, Logger, MainWorldContentScriptDefinition, MainWorldContentScriptEntrypointOptions, OnContentScriptStopped, OptionsEntrypoint, OptionsEntrypointOptions, OutputAsset, OutputChunk, OutputFile, PerBrowserMap, PerBrowserOption, PopupEntrypoint, PopupEntrypointOptions, PrepareTsconfigs, PublicPathEntry, ReloadContentScriptPayload, ResolvedBasePublicFile, ResolvedConfig, ResolvedEslintrc, ResolvedPerBrowserOptions, ResolvedPublicFile, ServerInfo, SidepanelEntrypoint, SidepanelEntrypointOptions, TargetBrowser, TargetManifestVersion, ThemeIcon, UnlistedScriptDefinition, UnlistedScriptEntrypoint, UserConfig, UserManifest, UserManifestFn, WebExtConfig, Wxt, WxtBuilder, WxtBuilderServer, WxtCommand, WxtDevServer, WxtDirEntry, WxtDirFileEntry, WxtDirTypeReferenceEntry, WxtHooks, WxtModule, WxtModuleOptions, WxtModuleSetup, WxtModuleWithMetadata, WxtPackageManager, WxtPlugin, WxtResolvedUnimportOptions, WxtUnimportOptions, WxtViteConfig } from "./types.mjs";
2
2
  import { build } from "./core/build.mjs";
3
3
  import { clean } from "./core/clean.mjs";
4
4
  import { defineConfig } from "./core/define-config.mjs";
5
- import { defineRunnerConfig, defineWebExtConfig } from "./core/define-web-ext-config.mjs";
5
+ import { defineWebExtConfig } from "./core/define-web-ext-config.mjs";
6
6
  import { createServer } from "./core/create-server.mjs";
7
7
  import { initialize } from "./core/initialize.mjs";
8
8
  import { prepare } from "./core/prepare.mjs";
9
9
  import { zip } from "./core/zip.mjs";
10
10
  import { normalizePath } from "./core/utils/paths.mjs";
11
+ import { getEntrypointBundlePath } from "./core/utils/entrypoints.mjs";
11
12
  import { version } from "./version.mjs";
12
- export { BackgroundDefinition, BackgroundEntrypoint, BackgroundEntrypointOptions, BaseContentScriptEntrypointOptions, BaseEntrypoint, BaseEntrypointOptions, BaseScriptEntrypointOptions, BuildOutput, BuildStepOutput, ConfigEnv, ContentScriptDefinition, ContentScriptEntrypoint, CopiedPublicFile, Dependency, Entrypoint, EntrypointGroup, EntrypointInfo, EslintGlobalsPropValue, Eslintrc, ExtensionRunner, ExtensionRunnerConfig, FirefoxDataCollectionPermissions, FirefoxDataCollectionType, FsCache, GeneratedPublicFile, GenericEntrypoint, HookResult, InlineConfig, IsolatedWorldContentScriptDefinition, IsolatedWorldContentScriptEntrypointOptions, Logger, MainWorldContentScriptDefinition, MainWorldContentScriptEntrypointOptions, OnContentScriptStopped, OptionsEntrypoint, OptionsEntrypointOptions, OutputAsset, OutputChunk, OutputFile, PerBrowserMap, PerBrowserOption, PopupEntrypoint, PopupEntrypointOptions, PublicPathEntry, ReloadContentScriptPayload, ResolvedBasePublicFile, ResolvedConfig, ResolvedEslintrc, ResolvedPerBrowserOptions, ResolvedPublicFile, ServerInfo, SidepanelEntrypoint, SidepanelEntrypointOptions, TargetBrowser, TargetManifestVersion, ThemeIcon, UnlistedScriptDefinition, UnlistedScriptEntrypoint, UserConfig, UserManifest, UserManifestFn, WebExtConfig, Wxt, WxtBuilder, WxtBuilderServer, WxtCommand, WxtDevServer, WxtDirEntry, WxtDirFileEntry, WxtDirTypeReferenceEntry, WxtHooks, WxtModule, WxtModuleOptions, WxtModuleSetup, WxtModuleWithMetadata, WxtPackageManager, WxtPlugin, WxtResolvedUnimportOptions, WxtUnimportOptions, WxtViteConfig, build, clean, createServer, defineConfig, defineRunnerConfig, defineWebExtConfig, initialize, normalizePath, prepare, version, zip };
13
+ export { BackgroundDefinition, BackgroundEntrypoint, BackgroundEntrypointOptions, BaseContentScriptEntrypointOptions, BaseEntrypoint, BaseEntrypointOptions, BaseScriptEntrypointOptions, BuildOutput, BuildStepOutput, ConfigEnv, ContentScriptDefinition, ContentScriptEntrypoint, CopiedPublicFile, Dependency, Entrypoint, EntrypointGroup, EntrypointInfo, EslintGlobalsPropValue, Eslintrc, ExtensionRunner, FirefoxDataCollectionPermissions, FirefoxDataCollectionType, FsCache, GeneratedPublicFile, GenericEntrypoint, HookResult, InlineConfig, IsolatedWorldContentScriptDefinition, IsolatedWorldContentScriptEntrypointOptions, Logger, MainWorldContentScriptDefinition, MainWorldContentScriptEntrypointOptions, OnContentScriptStopped, OptionsEntrypoint, OptionsEntrypointOptions, OutputAsset, OutputChunk, OutputFile, PerBrowserMap, PerBrowserOption, PopupEntrypoint, PopupEntrypointOptions, PrepareTsconfigs, PublicPathEntry, ReloadContentScriptPayload, ResolvedBasePublicFile, ResolvedConfig, ResolvedEslintrc, ResolvedPerBrowserOptions, ResolvedPublicFile, ServerInfo, SidepanelEntrypoint, SidepanelEntrypointOptions, TargetBrowser, TargetManifestVersion, ThemeIcon, UnlistedScriptDefinition, UnlistedScriptEntrypoint, UserConfig, UserManifest, UserManifestFn, WebExtConfig, Wxt, WxtBuilder, WxtBuilderServer, WxtCommand, WxtDevServer, WxtDirEntry, WxtDirFileEntry, WxtDirTypeReferenceEntry, WxtHooks, WxtModule, WxtModuleOptions, WxtModuleSetup, WxtModuleWithMetadata, WxtPackageManager, WxtPlugin, WxtResolvedUnimportOptions, WxtUnimportOptions, WxtViteConfig, build, clean, createServer, defineConfig, defineWebExtConfig, getEntrypointBundlePath, initialize, normalizePath, prepare, version, zip };
package/dist/index.mjs CHANGED
@@ -1,12 +1,13 @@
1
1
  import { normalizePath } from "./core/utils/paths.mjs";
2
+ import { getEntrypointBundlePath } from "./core/utils/entrypoints.mjs";
2
3
  import { version } from "./version.mjs";
3
4
  import { build } from "./core/build.mjs";
4
5
  import { clean } from "./core/clean.mjs";
5
6
  import { defineConfig } from "./core/define-config.mjs";
6
- import { defineRunnerConfig, defineWebExtConfig } from "./core/define-web-ext-config.mjs";
7
+ import { defineWebExtConfig } from "./core/define-web-ext-config.mjs";
7
8
  import { createServer } from "./core/create-server.mjs";
8
9
  import { initialize } from "./core/initialize.mjs";
9
10
  import { prepare } from "./core/prepare.mjs";
10
11
  import { zip } from "./core/zip.mjs";
11
12
  import "./core/index.mjs";
12
- export { build, clean, createServer, defineConfig, defineRunnerConfig, defineWebExtConfig, initialize, normalizePath, prepare, version, zip };
13
+ export { build, clean, createServer, defineConfig, defineWebExtConfig, getEntrypointBundlePath, initialize, normalizePath, prepare, version, zip };
@@ -1,4 +1,3 @@
1
- import { download } from "../core/builders/vite/plugins/download.mjs";
2
1
  import { tsconfigPaths } from "../core/builders/vite/plugins/tsconfigPaths.mjs";
3
2
  import { globals } from "../core/builders/vite/plugins/globals.mjs";
4
3
  import { extensionApiMock } from "../core/builders/vite/plugins/extensionApiMock.mjs";
@@ -28,7 +27,6 @@ async function WxtVitest(inlineConfig) {
28
27
  await registerWxt("serve", inlineConfig ?? {});
29
28
  const plugins = [
30
29
  globals(wxt.config),
31
- download(wxt.config),
32
30
  tsconfigPaths(wxt.config),
33
31
  resolveAppConfig(wxt.config),
34
32
  extensionApiMock(wxt.config)