wxt 0.19.15 → 0.19.17

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.
@@ -7,7 +7,7 @@ export default defineWxtModule({
7
7
  const options = wxt.config.imports;
8
8
  if (options === false) return;
9
9
  let unimport;
10
- wxt.hooks.hook("ready", () => {
10
+ wxt.hooks.hook("config:resolved", () => {
11
11
  const addModuleImports = (module) => {
12
12
  if (!module.imports) return;
13
13
  options.imports ??= [];
@@ -17,7 +17,7 @@ export default defineWxtModule({
17
17
  wxt.config.userModules.forEach(addModuleImports);
18
18
  });
19
19
  wxt.hooks.afterEach((event) => {
20
- if (event.name === "ready") {
20
+ if (event.name === "config:resolved") {
21
21
  unimport = createUnimport(options);
22
22
  }
23
23
  });
@@ -13,6 +13,7 @@ export function createIntegratedUi(ctx, options) {
13
13
  };
14
14
  const remove = () => {
15
15
  options.onRemove?.(mounted);
16
+ wrapper.replaceChildren();
16
17
  wrapper.remove();
17
18
  mounted = void 0;
18
19
  };
@@ -1,3 +1,3 @@
1
1
  import { ResolvedConfig, WxtBuilder, WxtDevServer, WxtHooks } from '../../../types';
2
2
  import { Hookable } from 'hookable';
3
- export declare function createViteBuilder(wxtConfig: ResolvedConfig, hooks: Hookable<WxtHooks>, server?: WxtDevServer): Promise<WxtBuilder>;
3
+ export declare function createViteBuilder(wxtConfig: ResolvedConfig, hooks: Hookable<WxtHooks>, getWxtDevServer?: () => WxtDevServer | undefined): Promise<WxtBuilder>;
@@ -9,7 +9,7 @@ import { importEntrypointFile } from "../../utils/building/index.mjs";
9
9
  import { ViteNodeServer } from "vite-node/server";
10
10
  import { ViteNodeRunner } from "vite-node/client";
11
11
  import { installSourcemapsSupport } from "vite-node/source-map";
12
- export async function createViteBuilder(wxtConfig, hooks, server) {
12
+ export async function createViteBuilder(wxtConfig, hooks, getWxtDevServer) {
13
13
  const vite = await import("vite");
14
14
  const getBaseConfig = async (baseConfigOptions) => {
15
15
  const config = await wxtConfig.vite(wxtConfig.env);
@@ -33,6 +33,7 @@ export async function createViteBuilder(wxtConfig, hooks, server) {
33
33
  config.server.watch = {
34
34
  ignored: [`${wxtConfig.outBaseDir}/**`, `${wxtConfig.wxtDir}/**`]
35
35
  };
36
+ const server = getWxtDevServer?.();
36
37
  config.plugins ??= [];
37
38
  config.plugins.push(
38
39
  wxtPlugins.download(wxtConfig),
@@ -180,18 +181,18 @@ export async function createViteBuilder(wxtConfig, hooks, server) {
180
181
  plugins: [wxtPlugins.removeEntrypointMainFunction(wxtConfig, path)]
181
182
  };
182
183
  const config = vite.mergeConfig(baseConfig, envConfig);
183
- const server2 = await vite.createServer(config);
184
- await server2.pluginContainer.buildStart({});
184
+ const server = await vite.createServer(config);
185
+ await server.pluginContainer.buildStart({});
185
186
  const node = new ViteNodeServer(
186
187
  // @ts-ignore: Some weird type error...
187
- server2
188
+ server
188
189
  );
189
190
  installSourcemapsSupport({
190
191
  getSourceMap: (source) => node.getSourceMap(source)
191
192
  });
192
193
  const runner = new ViteNodeRunner({
193
- root: server2.config.root,
194
- base: server2.config.base,
194
+ root: server.config.root,
195
+ base: server.config.base,
195
196
  // when having the server and runner in a different context,
196
197
  // you will need to handle the communication between them
197
198
  // and pass to this function
@@ -203,7 +204,7 @@ export async function createViteBuilder(wxtConfig, hooks, server) {
203
204
  }
204
205
  });
205
206
  const res = await runner.executeFile(path);
206
- await server2.close();
207
+ await server.close();
207
208
  return res.default;
208
209
  }
209
210
  }
@@ -239,7 +240,7 @@ export async function createViteBuilder(wxtConfig, hooks, server) {
239
240
  const finalConfig = vite.mergeConfig(baseConfig, serverConfig);
240
241
  await hooks.callHook("vite:devServer:extendConfig", finalConfig);
241
242
  const viteServer = await vite.createServer(finalConfig);
242
- const server2 = {
243
+ const server = {
243
244
  async listen() {
244
245
  await viteServer.listen(info.port);
245
246
  },
@@ -259,7 +260,7 @@ export async function createViteBuilder(wxtConfig, hooks, server) {
259
260
  },
260
261
  watcher: viteServer.watcher
261
262
  };
262
- return server2;
263
+ return server;
263
264
  }
264
265
  };
265
266
  }
@@ -14,66 +14,98 @@ import { createExtensionRunner } from "./runners/index.mjs";
14
14
  import { Mutex } from "async-mutex";
15
15
  import pc from "picocolors";
16
16
  import { relative } from "node:path";
17
- import { registerWxt, wxt } from "./wxt.mjs";
17
+ import { deinitWxtModules, initWxtModules, registerWxt, wxt } from "./wxt.mjs";
18
18
  import { unnormalizePath } from "./utils/paths.mjs";
19
19
  import {
20
20
  getContentScriptJs,
21
21
  mapWxtOptionsToRegisteredContentScript
22
22
  } from "./utils/content-scripts.mjs";
23
23
  export async function createServer(inlineConfig) {
24
- await registerWxt("serve", inlineConfig, async (config) => {
25
- const { port, hostname } = config.dev.server;
26
- const serverInfo = {
24
+ await registerWxt("serve", inlineConfig);
25
+ wxt.server = await createServerInternal();
26
+ await wxt.hooks.callHook("server:created", wxt, wxt.server);
27
+ return wxt.server;
28
+ }
29
+ async function createServerInternal() {
30
+ const getServerInfo = () => {
31
+ const { port, hostname } = wxt.config.dev.server;
32
+ return {
27
33
  port,
28
34
  hostname,
29
35
  origin: `http://${hostname}:${port}`
30
36
  };
31
- const server2 = {
32
- ...serverInfo,
33
- get watcher() {
34
- return builderServer.watcher;
35
- },
36
- get ws() {
37
- return builderServer.ws;
38
- },
39
- currentOutput: void 0,
40
- async start() {
41
- await builderServer.listen();
42
- wxt.logger.success(`Started dev server @ ${serverInfo.origin}`);
43
- await buildAndOpenBrowser();
44
- },
45
- async stop() {
46
- await runner.closeBrowser();
47
- await builderServer.close();
48
- },
49
- async restart() {
50
- await closeAndRecreateRunner();
51
- await buildAndOpenBrowser();
52
- },
53
- transformHtml(url, html, originalUrl) {
54
- return builderServer.transformHtml(url, html, originalUrl);
55
- },
56
- reloadContentScript(payload) {
57
- server2.ws.send("wxt:reload-content-script", payload);
58
- },
59
- reloadPage(path) {
60
- server2.ws.send("wxt:reload-page", path);
61
- },
62
- reloadExtension() {
63
- server2.ws.send("wxt:reload-extension");
64
- },
65
- async restartBrowser() {
66
- await closeAndRecreateRunner();
67
- await runner.openBrowser();
68
- }
69
- };
70
- return server2;
71
- });
72
- const server = wxt.server;
37
+ };
73
38
  let [runner, builderServer] = await Promise.all([
74
39
  createExtensionRunner(),
75
- wxt.builder.createServer(server)
40
+ wxt.builder.createServer(getServerInfo())
76
41
  ]);
42
+ let wasStopped = false;
43
+ const server = {
44
+ get hostname() {
45
+ return getServerInfo().hostname;
46
+ },
47
+ get port() {
48
+ return getServerInfo().port;
49
+ },
50
+ get origin() {
51
+ return getServerInfo().origin;
52
+ },
53
+ get watcher() {
54
+ return builderServer.watcher;
55
+ },
56
+ get ws() {
57
+ return builderServer.ws;
58
+ },
59
+ currentOutput: void 0,
60
+ async start() {
61
+ if (wasStopped) {
62
+ await wxt.reloadConfig();
63
+ runner = await createExtensionRunner();
64
+ builderServer = await wxt.builder.createServer(getServerInfo());
65
+ await initWxtModules();
66
+ }
67
+ await builderServer.listen();
68
+ wxt.logger.success(`Started dev server @ ${server.origin}`);
69
+ await wxt.hooks.callHook("server:started", wxt, server);
70
+ await buildAndOpenBrowser();
71
+ server.ws.on("wxt:background-initialized", () => {
72
+ if (server.currentOutput == null) return;
73
+ reloadContentScripts(server.currentOutput.steps, server);
74
+ });
75
+ const reloadOnChange = createFileReloader(server);
76
+ server.watcher.on("all", reloadOnChange);
77
+ },
78
+ async stop() {
79
+ wasStopped = true;
80
+ await runner.closeBrowser();
81
+ await builderServer.close();
82
+ await wxt.hooks.callHook("server:closed", wxt, server);
83
+ deinitWxtModules();
84
+ server.currentOutput = void 0;
85
+ },
86
+ async restart() {
87
+ await server.stop();
88
+ await server.start();
89
+ },
90
+ transformHtml(url, html, originalUrl) {
91
+ return builderServer.transformHtml(url, html, originalUrl);
92
+ },
93
+ reloadContentScript(payload) {
94
+ server.ws.send("wxt:reload-content-script", payload);
95
+ },
96
+ reloadPage(path) {
97
+ server.ws.send("wxt:reload-page", path);
98
+ },
99
+ reloadExtension() {
100
+ server.ws.send("wxt:reload-extension");
101
+ },
102
+ async restartBrowser() {
103
+ await runner.closeBrowser();
104
+ await wxt.reloadConfig();
105
+ runner = await createExtensionRunner();
106
+ await runner.openBrowser();
107
+ }
108
+ };
77
109
  const buildAndOpenBrowser = async () => {
78
110
  server.currentOutput = await internalBuild();
79
111
  try {
@@ -83,17 +115,6 @@ export async function createServer(inlineConfig) {
83
115
  }
84
116
  await runner.openBrowser();
85
117
  };
86
- const closeAndRecreateRunner = async () => {
87
- await runner.closeBrowser();
88
- await wxt.reloadConfig();
89
- runner = await createExtensionRunner();
90
- };
91
- server.ws.on("wxt:background-initialized", () => {
92
- if (server.currentOutput == null) return;
93
- reloadContentScripts(server.currentOutput.steps, server);
94
- });
95
- const reloadOnChange = createFileReloader(server);
96
- server.watcher.on("all", reloadOnChange);
97
118
  return server;
98
119
  }
99
120
  function createFileReloader(server) {
@@ -125,7 +125,7 @@ ${commentLines.map((line) => ` * ${line}`.trimEnd()).join("\n")}
125
125
  message.message
126
126
  )
127
127
  ),
128
- // Include a final union-based override so TS accepts valid string templates or concatinations
128
+ // Include a final union-based override so TS accepts valid string templates or concatenations
129
129
  // ie: browser.i18n.getMessage(`some_enum_${enumValue}`)
130
130
  renderGetMessageOverload(
131
131
  messages.map((message) => `"${message.name}"`).join(" | ")
@@ -10,6 +10,7 @@ import { builtinModules } from "../builtin-modules/index.mjs";
10
10
  import { getEslintVersion } from "./utils/eslint.mjs";
11
11
  import { safeStringToNumber } from "./utils/number.mjs";
12
12
  import { loadEnv } from "./utils/env.mjs";
13
+ import { getPort } from "get-port-please";
13
14
  export async function resolveConfig(inlineConfig, command) {
14
15
  let userConfig = {};
15
16
  let userConfigMetadata;
@@ -82,14 +83,19 @@ export async function resolveConfig(inlineConfig, command) {
82
83
  );
83
84
  let devServerConfig;
84
85
  if (command === "serve") {
86
+ const hostname = mergedConfig.dev?.server?.hostname ?? "localhost";
85
87
  let port = mergedConfig.dev?.server?.port;
86
88
  if (port == null || !isFinite(port)) {
87
- const { default: getPort, portNumbers } = await import("get-port");
88
- port = await getPort({ port: portNumbers(3e3, 3010) });
89
+ port = await getPort({
90
+ port: 3e3,
91
+ portRange: [3001, 3010],
92
+ // Passing host required for Mac, unsure of Windows/Linux
93
+ host: hostname
94
+ });
89
95
  }
90
96
  devServerConfig = {
91
97
  port,
92
- hostname: mergedConfig.dev?.server?.hostname ?? "localhost",
98
+ hostname,
93
99
  watchDebounce: safeStringToNumber(process.env.WXT_WATCH_DEBOUNCE) ?? 800
94
100
  };
95
101
  }
@@ -274,9 +280,10 @@ async function getUnimportEslintOptions(wxtDir, options) {
274
280
  case "auto":
275
281
  const version = await getEslintVersion();
276
282
  let major = parseInt(version[0]);
283
+ if (isNaN(major)) eslintEnabled = false;
277
284
  if (major <= 8) eslintEnabled = 8;
278
285
  else if (major >= 9) eslintEnabled = 9;
279
- else eslintEnabled = 8;
286
+ else eslintEnabled = false;
280
287
  break;
281
288
  case true:
282
289
  eslintEnabled = 8;
@@ -17,7 +17,11 @@ import { BuildOutput, BuildStepOutput, EntrypointGroup } from '../../../types';
17
17
  * - Background script is changed
18
18
  * - Manifest is different
19
19
  * - Restart browser
20
- * - Config file changed (wxt.config.ts, .env, web-ext.config.ts, etc)
20
+ * - web-ext.config.ts (runner config changes)
21
+ * - Full dev server restart
22
+ * - wxt.config.ts (main config file)
23
+ * - modules/* (any file related to WXT modules)
24
+ * - .env (environment variable changed could effect build)
21
25
  */
22
26
  export declare function detectDevChanges(changedFiles: string[], currentOutput: BuildOutput): DevModeChange;
23
27
  /**
@@ -7,6 +7,11 @@ export function detectDevChanges(changedFiles, currentOutput) {
7
7
  (file) => file === wxt.config.userConfigMetadata.configFile
8
8
  );
9
9
  if (isConfigChange) return { type: "full-restart" };
10
+ const isWxtModuleChange = some(
11
+ changedFiles,
12
+ (file) => file.startsWith(wxt.config.modulesDir)
13
+ );
14
+ if (isWxtModuleChange) return { type: "full-restart" };
10
15
  const isRunnerChange = some(
11
16
  changedFiles,
12
17
  (file) => file === wxt.config.runnerConfig.configFile
@@ -28,7 +28,7 @@ export function hashContentScriptOptions(options) {
28
28
  }
29
29
  export function mapWxtOptionsToContentScript(options, js, css) {
30
30
  return {
31
- matches: options.matches,
31
+ matches: options.matches ?? [],
32
32
  all_frames: options.allFrames,
33
33
  match_about_blank: options.matchAboutBlank,
34
34
  exclude_globs: options.excludeGlobs,
@@ -35,10 +35,10 @@ export declare function stripPathFromMatchPattern(pattern: string): string;
35
35
  /**
36
36
  * Converts all MV3 web accessible resources to their MV2 forms. MV3 web accessible resources are
37
37
  * generated in this file, and may be defined by the user in their manifest. In both cases, when
38
- * targetting MV2, automatically convert their definitions down to the basic MV2 array.
38
+ * targeting MV2, automatically convert their definitions down to the basic MV2 array.
39
39
  */
40
40
  export declare function convertWebAccessibleResourcesToMv2(manifest: Manifest.WebExtensionManifest): void;
41
41
  /**
42
42
  * Make sure all resources are in MV3 format. If not, add a wanring
43
43
  */
44
- export declare function validateMv3WebAccessbileResources(manifest: Manifest.WebExtensionManifest): void;
44
+ export declare function validateMv3WebAccessibleResources(manifest: Manifest.WebExtensionManifest): void;
@@ -70,10 +70,11 @@ export async function generateManifest(entrypoints, buildOutput) {
70
70
  if (wxt.config.manifestVersion === 2) {
71
71
  convertWebAccessibleResourcesToMv2(manifest);
72
72
  convertActionToMv2(manifest);
73
+ convertCspToMv2(manifest);
73
74
  moveHostPermissionsToPermissions(manifest);
74
75
  }
75
76
  if (wxt.config.manifestVersion === 3) {
76
- validateMv3WebAccessbileResources(manifest);
77
+ validateMv3WebAccessibleResources(manifest);
77
78
  }
78
79
  stripKeys(manifest);
79
80
  if (manifest.name == null)
@@ -262,7 +263,7 @@ function addEntrypoints(manifest, entrypoints, buildOutput) {
262
263
  const cssMap = getContentScriptsCssMap(buildOutput, contentScripts);
263
264
  if (wxt.config.command === "serve" && wxt.config.manifestVersion === 3) {
264
265
  contentScripts.forEach((script) => {
265
- script.options.matches.forEach((matchPattern) => {
266
+ script.options.matches?.forEach((matchPattern) => {
266
267
  addHostPermission(manifest, matchPattern);
267
268
  });
268
269
  });
@@ -297,7 +298,7 @@ function addEntrypoints(manifest, entrypoints, buildOutput) {
297
298
  );
298
299
  }
299
300
  runtimeContentScripts.forEach((script) => {
300
- script.options.matches.forEach((matchPattern) => {
301
+ script.options.matches?.forEach((matchPattern) => {
301
302
  addHostPermission(manifest, matchPattern);
302
303
  });
303
304
  });
@@ -353,28 +354,20 @@ function addDevModeCsp(manifest) {
353
354
  addPermission(manifest, permission);
354
355
  }
355
356
  const extensionPagesCsp = new ContentSecurityPolicy(
356
- manifest.manifest_version === 3 ? (
357
- // @ts-expect-error: extension_pages is not typed
358
- manifest.content_security_policy?.extension_pages ?? "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
359
- ) : manifest.content_security_policy ?? "script-src 'self'; object-src 'self';"
360
- // default CSP for MV2
357
+ // @ts-expect-error: extension_pages exists, we convert MV2 CSPs to this earlier in the process
358
+ manifest.content_security_policy?.extension_pages ?? (manifest.manifest_version === 3 ? DEFAULT_MV3_EXTENSION_PAGES_CSP : DEFAULT_MV2_CSP)
361
359
  );
362
360
  const sandboxCsp = new ContentSecurityPolicy(
363
361
  // @ts-expect-error: sandbox is not typed
364
- manifest.content_security_policy?.sandbox ?? "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';"
365
- // default sandbox CSP for MV3
362
+ manifest.content_security_policy?.sandbox ?? DEFAULT_MV3_SANDBOX_CSP
366
363
  );
367
- if (wxt.server) {
364
+ if (wxt.config.command === "serve") {
368
365
  extensionPagesCsp.add("script-src", allowedCsp);
369
366
  sandboxCsp.add("script-src", allowedCsp);
370
367
  }
371
- if (manifest.manifest_version === 3) {
372
- manifest.content_security_policy ??= {};
373
- manifest.content_security_policy.extension_pages = extensionPagesCsp.toString();
374
- manifest.content_security_policy.sandbox = sandboxCsp.toString();
375
- } else {
376
- manifest.content_security_policy = extensionPagesCsp.toString();
377
- }
368
+ manifest.content_security_policy ??= {};
369
+ manifest.content_security_policy.extension_pages = extensionPagesCsp.toString();
370
+ manifest.content_security_policy.sandbox = sandboxCsp.toString();
378
371
  }
379
372
  function addDevModePermissions(manifest) {
380
373
  addPermission(manifest, "tabs");
@@ -400,7 +393,7 @@ export function getContentScriptCssWebAccessibleResources(contentScripts, conten
400
393
  if (cssFile == null) return;
401
394
  resources.push({
402
395
  resources: [cssFile],
403
- matches: script.options.matches.map(
396
+ matches: script.options.matches?.map(
404
397
  (matchPattern) => stripPathFromMatchPattern(matchPattern)
405
398
  )
406
399
  });
@@ -457,7 +450,12 @@ function convertActionToMv2(manifest) {
457
450
  return;
458
451
  manifest.browser_action = manifest.action;
459
452
  }
460
- export function validateMv3WebAccessbileResources(manifest) {
453
+ function convertCspToMv2(manifest) {
454
+ if (typeof manifest.content_security_policy === "string" || manifest.content_security_policy?.extension_pages == null)
455
+ return;
456
+ manifest.content_security_policy = manifest.content_security_policy.extension_pages;
457
+ }
458
+ export function validateMv3WebAccessibleResources(manifest) {
461
459
  if (manifest.web_accessible_resources == null) return;
462
460
  const stringResources = manifest.web_accessible_resources.filter(
463
461
  (item) => typeof item === "string"
@@ -510,3 +508,6 @@ const mv3OnlyKeys = [
510
508
  "side_panel"
511
509
  ];
512
510
  const firefoxMv3OnlyKeys = ["host_permissions"];
511
+ const DEFAULT_MV3_EXTENSION_PAGES_CSP = "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';";
512
+ const DEFAULT_MV3_SANDBOX_CSP = "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';";
513
+ const DEFAULT_MV2_CSP = "script-src 'self'; object-src 'self';";