wxt 0.20.0-beta1 → 0.20.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.
Files changed (35) hide show
  1. package/dist/browser.d.ts +5 -3
  2. package/dist/browser.mjs +3 -7
  3. package/dist/builtin-modules/unimport.d.ts +0 -3
  4. package/dist/builtin-modules/unimport.mjs +2 -29
  5. package/dist/core/builders/vite/index.mjs +2 -0
  6. package/dist/core/create-server.mjs +27 -2
  7. package/dist/core/generate-wxt-dir.mjs +1 -2
  8. package/dist/core/resolve-config.d.ts +1 -1
  9. package/dist/core/resolve-config.mjs +12 -13
  10. package/dist/core/utils/building/detect-dev-changes.mjs +18 -7
  11. package/dist/core/utils/building/rebuild.d.ts +2 -1
  12. package/dist/core/utils/content-scripts.d.ts +2 -1
  13. package/dist/core/utils/env.d.ts +1 -1
  14. package/dist/core/utils/env.mjs +16 -13
  15. package/dist/core/utils/eslint.mjs +5 -3
  16. package/dist/core/utils/manifest.d.ts +4 -3
  17. package/dist/core/utils/manifest.mjs +7 -6
  18. package/dist/core/utils/syntax-errors.d.ts +11 -0
  19. package/dist/core/utils/syntax-errors.mjs +20 -0
  20. package/dist/core/utils/testing/fake-objects.d.ts +79 -78
  21. package/dist/core/utils/types.d.ts +3 -2
  22. package/dist/core/wxt.mjs +1 -1
  23. package/dist/testing/wxt-vitest-plugin.mjs +2 -5
  24. package/dist/types.d.ts +12 -12
  25. package/dist/utils/content-script-context.d.ts +2 -0
  26. package/dist/utils/content-script-context.mjs +11 -2
  27. package/dist/utils/content-script-ui/shared.mjs +3 -0
  28. package/dist/utils/content-script-ui/types.d.ts +1 -0
  29. package/dist/version.mjs +1 -1
  30. package/dist/virtual/background-entrypoint.mjs +2 -4
  31. package/dist/virtual/content-script-isolated-world-entrypoint.mjs +1 -2
  32. package/dist/virtual/content-script-main-world-entrypoint.mjs +1 -2
  33. package/dist/virtual/reload-html.mjs +2 -4
  34. package/dist/virtual/unlisted-script-entrypoint.mjs +1 -2
  35. package/package.json +19 -21
package/dist/browser.d.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  * ```
10
10
  * @module wxt/browser
11
11
  */
12
+ import { browser as _browser, type Browser } from '@wxt-dev/browser';
12
13
  /**
13
14
  * This interface is empty because it is generated per-project when running `wxt prepare`. See:
14
15
  * - `.wxt/types/paths.d.ts`
@@ -21,8 +22,9 @@ export interface WxtRuntime {
21
22
  */
22
23
  export interface WxtI18n {
23
24
  }
24
- export type WxtBrowser = Omit<typeof chrome, 'runtime' | 'i18n'> & {
25
- runtime: WxtRuntime & Omit<(typeof chrome)['runtime'], 'getURL'>;
26
- i18n: WxtI18n & Omit<(typeof chrome)['i18n'], 'getMessage'>;
25
+ export type WxtBrowser = Omit<typeof _browser, 'runtime' | 'i18n'> & {
26
+ runtime: WxtRuntime & Omit<(typeof _browser)['runtime'], 'getURL'>;
27
+ i18n: WxtI18n & Omit<(typeof _browser)['i18n'], 'getMessage'>;
27
28
  };
28
29
  export declare const browser: WxtBrowser;
30
+ export { Browser };
package/dist/browser.mjs CHANGED
@@ -1,7 +1,3 @@
1
- export const browser = (
2
- // @ts-expect-error
3
- globalThis.browser?.runtime?.id == null ? globalThis.chrome : (
4
- // @ts-expect-error
5
- globalThis.browser
6
- )
7
- );
1
+ import { browser as _browser } from "@wxt-dev/browser";
2
+ export const browser = _browser;
3
+ export {};
@@ -1,8 +1,5 @@
1
1
  import type { EslintGlobalsPropValue, WxtDirFileEntry, WxtModule, WxtResolvedUnimportOptions } from '../types';
2
- import { type Unimport } from 'unimport';
3
- import { Plugin } from 'vite';
4
2
  declare const _default: WxtModule<import("../types").WxtModuleOptions>;
5
3
  export default _default;
6
- export declare function vitePlugin(unimport: Unimport, autoImportsEnabled: boolean): Plugin;
7
4
  export declare function getEslint8ConfigEntry(options: WxtResolvedUnimportOptions, globals: Record<string, EslintGlobalsPropValue>): WxtDirFileEntry;
8
5
  export declare function getEslint9ConfigEntry(options: WxtResolvedUnimportOptions, globals: Record<string, EslintGlobalsPropValue>): WxtDirFileEntry;
@@ -1,6 +1,6 @@
1
1
  import { addViteConfig, defineWxtModule } from "../modules.mjs";
2
2
  import { createUnimport, toExports } from "unimport";
3
- import { extname } from "node:path";
3
+ import UnimportPlugin from "unimport/unplugin";
4
4
  export default defineWxtModule({
5
5
  name: "wxt:built-in:unimport",
6
6
  setup(wxt) {
@@ -35,37 +35,10 @@ export default defineWxtModule({
35
35
  );
36
36
  });
37
37
  addViteConfig(wxt, () => ({
38
- plugins: [vitePlugin(unimport, isEnabled())]
38
+ plugins: [UnimportPlugin.vite(wxt.config.imports)]
39
39
  }));
40
40
  }
41
41
  });
42
- export function vitePlugin(unimport, autoImportsEnabled) {
43
- const ENABLED_EXTENSIONS = /* @__PURE__ */ new Set([
44
- ".js",
45
- ".jsx",
46
- ".ts",
47
- ".tsx",
48
- ".vue",
49
- ".svelte"
50
- ]);
51
- return {
52
- name: "wxt:unimport",
53
- async transform(code, id) {
54
- if (id.includes("node_modules")) return;
55
- if (!ENABLED_EXTENSIONS.has(extname(id))) return;
56
- const injected = await unimport.injectImports(code, id, {
57
- // Only auto-import modules if enabled
58
- autoImport: autoImportsEnabled,
59
- // Always resolve the #imports module, even when auto-imports are disabled
60
- transformVirtualImports: true
61
- });
62
- return {
63
- code: injected.code,
64
- map: injected.s.generateMap({ hires: "boundary", source: id })
65
- };
66
- }
67
- };
68
- }
69
42
  async function getImportsDeclarationEntry(unimport) {
70
43
  return {
71
44
  path: "types/imports.d.ts",
@@ -34,6 +34,8 @@ export async function createViteBuilder(wxtConfig, hooks, getWxtDevServer) {
34
34
  config.server.watch = {
35
35
  ignored: [`${wxtConfig.outBaseDir}/**`, `${wxtConfig.wxtDir}/**`]
36
36
  };
37
+ config.legacy ??= {};
38
+ config.legacy.skipWebSocketTokenCheck = true;
37
39
  const server = getWxtDevServer?.();
38
40
  config.plugins ??= [];
39
41
  config.plugins.push(
@@ -1,4 +1,5 @@
1
1
  import { debounce } from "perfect-debounce";
2
+ import chokidar from "chokidar";
2
3
  import { getEntrypointBundlePath, isHtmlEntrypoint } from "./utils/entrypoints.mjs";
3
4
  import {
4
5
  getContentScriptCssFiles,
@@ -21,6 +22,7 @@ import {
21
22
  mapWxtOptionsToRegisteredContentScript
22
23
  } from "./utils/content-scripts.mjs";
23
24
  import { createKeyboardShortcuts } from "./keyboard-shortcuts.mjs";
25
+ import { isBabelSyntaxError, logBabelSyntaxError } from "./utils/syntax-errors.mjs";
24
26
  export async function createServer(inlineConfig) {
25
27
  await registerWxt("serve", inlineConfig);
26
28
  wxt.server = await createServerInternal();
@@ -116,7 +118,24 @@ async function createServerInternal() {
116
118
  };
117
119
  const keyboardShortcuts = createKeyboardShortcuts(server);
118
120
  const buildAndOpenBrowser = async () => {
119
- server.currentOutput = await internalBuild();
121
+ try {
122
+ server.currentOutput = await internalBuild();
123
+ } catch (err) {
124
+ if (!isBabelSyntaxError(err)) {
125
+ throw err;
126
+ }
127
+ logBabelSyntaxError(err);
128
+ wxt.logger.info("Waiting for syntax error to be fixed...");
129
+ await new Promise((resolve) => {
130
+ const watcher = chokidar.watch(err.id, { ignoreInitial: true });
131
+ watcher.on("all", () => {
132
+ watcher.close();
133
+ wxt.logger.info("Syntax error resolved, rebuilding...");
134
+ resolve();
135
+ });
136
+ });
137
+ return buildAndOpenBrowser();
138
+ }
120
139
  try {
121
140
  server.watcher.add(getExternalOutputDependencies(server));
122
141
  } catch (err) {
@@ -132,7 +151,7 @@ function createFileReloader(server) {
132
151
  const changeQueue = [];
133
152
  const cb = async (event, path) => {
134
153
  changeQueue.push([event, path]);
135
- await fileChangedMutex.runExclusive(async () => {
154
+ const reloading = fileChangedMutex.runExclusive(async () => {
136
155
  if (server.currentOutput == null) return;
137
156
  const fileChanges = changeQueue.splice(0, changeQueue.length).map(([_, file]) => file);
138
157
  if (fileChanges.length === 0) return;
@@ -182,6 +201,12 @@ function createFileReloader(server) {
182
201
  } catch {
183
202
  }
184
203
  });
204
+ await reloading.catch((error) => {
205
+ if (!isBabelSyntaxError(error)) {
206
+ throw error;
207
+ }
208
+ logBabelSyntaxError(error);
209
+ });
185
210
  };
186
211
  return debounce(cb, wxt.config.dev.server.watchDebounce, {
187
212
  leading: true,
@@ -19,7 +19,6 @@ export async function generateWxtDir(entrypoints) {
19
19
  entries.push(await getPathsDeclarationEntry(entrypoints));
20
20
  entries.push(await getI18nDeclarationEntry());
21
21
  entries.push(await getGlobalsDeclarationEntry());
22
- entries.push({ module: "@types/chrome" });
23
22
  entries.push(await getTsConfigEntry());
24
23
  await wxt.hooks.callHook("prepare:types", wxt, entries);
25
24
  entries.push(getMainDeclarationEntry(entries));
@@ -160,7 +159,7 @@ function getMainDeclarationEntry(references) {
160
159
  } else if (ref.tsReference) {
161
160
  const absolutePath = resolve(wxt.config.wxtDir, ref.path);
162
161
  const relativePath = relative(wxt.config.wxtDir, absolutePath);
163
- lines.push(`/// <reference types="./${normalizePath(relativePath)}" />`);
162
+ lines.push(`/// <reference path="./${normalizePath(relativePath)}" />`);
164
163
  }
165
164
  });
166
165
  return {
@@ -8,4 +8,4 @@ import { InlineConfig, ResolvedConfig, UserConfig, Logger, WxtCommand, WxtModule
8
8
  */
9
9
  export declare function resolveConfig(inlineConfig: InlineConfig, command: WxtCommand): Promise<ResolvedConfig>;
10
10
  export declare function mergeBuilderConfig(logger: Logger, inlineConfig: InlineConfig, userConfig: UserConfig): Promise<Pick<InlineConfig, 'vite'>>;
11
- export declare function resolveWxtUserModules(modulesDir: string, modules?: string[]): Promise<WxtModuleWithMetadata<any>[]>;
11
+ export declare function resolveWxtUserModules(root: string, modulesDir: string, modules?: string[]): Promise<WxtModuleWithMetadata<any>[]>;
@@ -1,4 +1,5 @@
1
1
  import { loadConfig } from "c12";
2
+ import { resolve as esmResolve } from "import-meta-resolve";
2
3
  import path from "node:path";
3
4
  import { createFsCache } from "./utils/cache.mjs";
4
5
  import consola, { LogLevels } from "consola";
@@ -11,7 +12,7 @@ import { getEslintVersion } from "./utils/eslint.mjs";
11
12
  import { safeStringToNumber } from "./utils/number.mjs";
12
13
  import { loadEnv } from "./utils/env.mjs";
13
14
  import { getPort } from "get-port-please";
14
- import { fileURLToPath } from "node:url";
15
+ import { fileURLToPath, pathToFileURL } from "node:url";
15
16
  export async function resolveConfig(inlineConfig, command) {
16
17
  let userConfig = {};
17
18
  let userConfigMetadata;
@@ -41,7 +42,7 @@ export async function resolveConfig(inlineConfig, command) {
41
42
  inlineConfig.root ?? userConfig.root ?? process.cwd()
42
43
  );
43
44
  const wxtDir = path.resolve(root, ".wxt");
44
- const wxtModuleDir = await resolveWxtModuleDir();
45
+ const wxtModuleDir = resolveWxtModuleDir();
45
46
  const srcDir = path.resolve(root, mergedConfig.srcDir ?? root);
46
47
  const entrypointsDir = path.resolve(
47
48
  srcDir,
@@ -104,6 +105,7 @@ export async function resolveConfig(inlineConfig, command) {
104
105
  };
105
106
  }
106
107
  const userModules = await resolveWxtUserModules(
108
+ root,
107
109
  modulesDir,
108
110
  mergedConfig.modules
109
111
  );
@@ -241,7 +243,7 @@ async function getUnimportOptions(wxtDir, srcDir, logger, config) {
241
243
  presets: [
242
244
  {
243
245
  from: "wxt/browser",
244
- imports: defineImportsAndTypes(["browser"], [])
246
+ imports: defineImportsAndTypes(["browser"], ["Browser"])
245
247
  },
246
248
  {
247
249
  from: "wxt/utils/storage",
@@ -391,8 +393,9 @@ async function getUnimportEslintOptions(wxtDir, options) {
391
393
  globalsPropValue: true
392
394
  };
393
395
  }
394
- async function resolveWxtModuleDir() {
395
- const url = import.meta.resolve("wxt", import.meta.url);
396
+ function resolveWxtModuleDir() {
397
+ const importer = typeof __filename === "string" ? pathToFileURL(__filename).href : import.meta.url;
398
+ const url = esmResolve("wxt", importer);
396
399
  return path.resolve(fileURLToPath(url), "../..");
397
400
  }
398
401
  async function isDirMissing(dir) {
@@ -424,12 +427,14 @@ export async function mergeBuilderConfig(logger, inlineConfig, userConfig) {
424
427
  }
425
428
  throw Error("Builder not found. Make sure vite is installed.");
426
429
  }
427
- export async function resolveWxtUserModules(modulesDir, modules = []) {
430
+ export async function resolveWxtUserModules(root, modulesDir, modules = []) {
431
+ const importer = pathToFileURL(path.join(root, "index.js")).href;
428
432
  const npmModules = await Promise.all(
429
433
  modules.map(async (moduleId) => {
434
+ const resolvedModulePath = esmResolve(moduleId, importer);
430
435
  const mod = await import(
431
436
  /* @vite-ignore */
432
- moduleId
437
+ resolvedModulePath
433
438
  );
434
439
  if (mod.default == null) {
435
440
  throw Error("Module missing default export: " + moduleId);
@@ -471,9 +476,3 @@ export async function resolveWxtUserModules(modulesDir, modules = []) {
471
476
  );
472
477
  return [...npmModules, ...localModules];
473
478
  }
474
- if (false) {
475
- (void 0).resolve = (path2) => (
476
- // @ts-expect-error: vitestCreateRequire defined in vitest.setup.ts
477
- "file://" + vitestCreateRequire(import.meta.url).resolve(path2)
478
- );
479
- }
@@ -83,13 +83,24 @@ export function detectDevChanges(changedFiles, currentOutput) {
83
83
  function findEffectedSteps(changedFile, currentOutput) {
84
84
  const changes = [];
85
85
  const changedPath = normalizePath(changedFile);
86
- const isChunkEffected = (chunk) => (
87
- // If it's an HTML file with the same path, is is effected because HTML files need to be re-rendered
88
- // - fileName is normalized, relative bundle path, "<entrypoint-name>.html"
89
- chunk.type === "asset" && changedPath.replace("/index.html", ".html").endsWith(chunk.fileName) || // If it's a chunk that depends on the changed file, it is effected
90
- // - moduleIds are absolute, normalized paths
91
- chunk.type === "chunk" && chunk.moduleIds.includes(changedPath)
92
- );
86
+ const isChunkEffected = (chunk) => {
87
+ switch (chunk.type) {
88
+ // If it's an HTML file with the same path, is is effected because HTML files need to be re-rendered
89
+ // - fileName is normalized, relative bundle path, "<entrypoint-name>.html"
90
+ case "asset": {
91
+ return changedPath.replace("/index.html", ".html").endsWith(chunk.fileName);
92
+ }
93
+ // If it's a chunk that depends on the changed file, it is effected
94
+ // - moduleIds are absolute, normalized paths
95
+ case "chunk": {
96
+ const modulePaths = chunk.moduleIds.map((path) => path.split("?")[0]);
97
+ return modulePaths.includes(changedPath);
98
+ }
99
+ default: {
100
+ return false;
101
+ }
102
+ }
103
+ };
93
104
  for (const step of currentOutput.steps) {
94
105
  const effectedChunk = step.chunks.find((chunk) => isChunkEffected(chunk));
95
106
  if (effectedChunk) changes.push(step);
@@ -1,4 +1,5 @@
1
1
  import { BuildOutput, Entrypoint, EntrypointGroup } from '../../../types';
2
+ import type { Browser } from '@wxt-dev/browser';
2
3
  /**
3
4
  * Given a configuration, list of entrypoints, and an existing, partial output, build the
4
5
  * entrypoints and merge the new output with the existing output.
@@ -17,6 +18,6 @@ import { BuildOutput, Entrypoint, EntrypointGroup } from '../../../types';
17
18
  */
18
19
  export declare function rebuild(allEntrypoints: Entrypoint[], entrypointGroups: EntrypointGroup[], existingOutput?: Omit<BuildOutput, 'manifest'>): Promise<{
19
20
  output: BuildOutput;
20
- manifest: chrome.runtime.Manifest;
21
+ manifest: Browser.runtime.Manifest;
21
22
  warnings: any[][];
22
23
  }>;
@@ -1,3 +1,4 @@
1
+ import type { Browser } from '@wxt-dev/browser';
1
2
  import { ContentScriptEntrypoint, ResolvedConfig } from '../../types';
2
3
  import { ManifestContentScript } from './types';
3
4
  /**
@@ -7,5 +8,5 @@ import { ManifestContentScript } from './types';
7
8
  */
8
9
  export declare function hashContentScriptOptions(options: ContentScriptEntrypoint['options']): string;
9
10
  export declare function mapWxtOptionsToContentScript(options: ContentScriptEntrypoint['options'], js: string[] | undefined, css: string[] | undefined): ManifestContentScript;
10
- export declare function mapWxtOptionsToRegisteredContentScript(options: ContentScriptEntrypoint['options'], js: string[] | undefined, css: string[] | undefined): Omit<chrome.scripting.RegisteredContentScript, 'id'>;
11
+ export declare function mapWxtOptionsToRegisteredContentScript(options: ContentScriptEntrypoint['options'], js: string[] | undefined, css: string[] | undefined): Omit<Browser.scripting.RegisteredContentScript, 'id'>;
11
12
  export declare function getContentScriptJs(config: ResolvedConfig, entrypoint: ContentScriptEntrypoint): string[];
@@ -2,4 +2,4 @@ import type { TargetBrowser } from '../../types';
2
2
  /**
3
3
  * Load environment files based on the current mode and browser.
4
4
  */
5
- export declare function loadEnv(mode: string, browser: TargetBrowser): import("dotenv").DotenvConfigOutput;
5
+ export declare function loadEnv(mode: string, browser: TargetBrowser): import("dotenv-expand").DotenvExpandOutput;
@@ -1,16 +1,19 @@
1
1
  import { config } from "dotenv";
2
+ import { expand } from "dotenv-expand";
2
3
  export function loadEnv(mode, browser) {
3
- return config({
4
- // Files on top override files below
5
- path: [
6
- `.env.${mode}.${browser}.local`,
7
- `.env.${mode}.${browser}`,
8
- `.env.${browser}.local`,
9
- `.env.${browser}`,
10
- `.env.${mode}.local`,
11
- `.env.${mode}`,
12
- `.env.local`,
13
- `.env`
14
- ]
15
- });
4
+ return expand(
5
+ config({
6
+ // Files on top override files below
7
+ path: [
8
+ `.env.${mode}.${browser}.local`,
9
+ `.env.${mode}.${browser}`,
10
+ `.env.${browser}.local`,
11
+ `.env.${browser}`,
12
+ `.env.${mode}.local`,
13
+ `.env.${mode}`,
14
+ `.env.local`,
15
+ `.env`
16
+ ]
17
+ })
18
+ );
16
19
  }
@@ -1,8 +1,10 @@
1
- const ESLINT_PACKAGE_NAME = "eslint";
2
1
  export async function getEslintVersion() {
3
2
  try {
4
- const { version } = await import(ESLINT_PACKAGE_NAME);
5
- return version.split(".") ?? [];
3
+ const require = (await import("node:module")).default.createRequire(
4
+ import.meta.url
5
+ );
6
+ const { ESLint } = require("eslint");
7
+ return ESLint.version?.split(".") ?? [];
6
8
  } catch {
7
9
  return [];
8
10
  }
@@ -1,13 +1,14 @@
1
1
  import { Entrypoint, BuildOutput, ContentScriptEntrypoint } from '../../types';
2
+ import type { Browser } from '@wxt-dev/browser';
2
3
  /**
3
4
  * Writes the manifest to the output directory and the build output.
4
5
  */
5
- export declare function writeManifest(manifest: chrome.runtime.Manifest, output: BuildOutput): Promise<void>;
6
+ export declare function writeManifest(manifest: Browser.runtime.Manifest, output: BuildOutput): Promise<void>;
6
7
  /**
7
8
  * Generates the manifest based on the config and entrypoints.
8
9
  */
9
10
  export declare function generateManifest(allEntrypoints: Entrypoint[], buildOutput: Omit<BuildOutput, 'manifest'>): Promise<{
10
- manifest: chrome.runtime.Manifest;
11
+ manifest: Browser.runtime.Manifest;
11
12
  warnings: any[][];
12
13
  }>;
13
14
  /**
@@ -36,4 +37,4 @@ export declare function stripPathFromMatchPattern(pattern: string): string;
36
37
  * generated in this file, and may be defined by the user in their manifest. In both cases, when
37
38
  * targeting MV2, automatically convert their definitions down to the basic MV2 array.
38
39
  */
39
- export declare function convertWebAccessibleResourcesToMv2(manifest: chrome.runtime.Manifest): void;
40
+ export declare function convertWebAccessibleResourcesToMv2(manifest: Browser.runtime.Manifest): void;
@@ -41,6 +41,12 @@ export async function generateManifest(allEntrypoints, buildOutput) {
41
41
  icons: discoverIcons(buildOutput)
42
42
  };
43
43
  const userManifest = wxt.config.manifest;
44
+ if (userManifest.manifest_version) {
45
+ delete userManifest.manifest_version;
46
+ wxt.logger.warn(
47
+ "`manifest.manifest_version` config was set, but ignored. To change the target manifest version, use the `manifestVersion` option or the `--mv2`/`--mv3` CLI flags.\nSee https://wxt.dev/guide/essentials/target-different-browsers.html#target-a-manifest-version"
48
+ );
49
+ }
44
50
  let manifest = defu(userManifest, baseManifest);
45
51
  if (wxt.config.command === "serve" && wxt.config.dev.reloadCommand) {
46
52
  if (manifest.commands && // If the following limit is exceeded, Chrome will fail to load the extension.
@@ -216,7 +222,7 @@ function addEntrypoints(manifest, entrypoints, buildOutput) {
216
222
  const page = getEntrypointBundlePath(options, wxt.config.outDir, ".html");
217
223
  manifest.options_ui = {
218
224
  open_in_tab: options.options.openInTab,
219
- // @ts-expect-error: Not typed by @types/chrome, but supported by Firefox
225
+ // @ts-expect-error: Not typed by @wxt-dev/browser, but supported by Firefox
220
226
  browser_style: wxt.config.browser === "firefox" ? options.options.browserStyle : void 0,
221
227
  chrome_style: wxt.config.browser !== "firefox" ? options.options.chromeStyle : void 0,
222
228
  page
@@ -294,11 +300,6 @@ function addEntrypoints(manifest, entrypoints, buildOutput) {
294
300
  const runtimeContentScripts = contentScripts.filter(
295
301
  (cs) => cs.options.registration === "runtime"
296
302
  );
297
- if (runtimeContentScripts.length > 0 && wxt.config.manifestVersion === 2) {
298
- throw Error(
299
- 'Cannot use `registration: "runtime"` with MV2 content scripts, it is a MV3-only feature.'
300
- );
301
- }
302
303
  runtimeContentScripts.forEach((script) => {
303
304
  script.options.matches?.forEach((matchPattern) => {
304
305
  addHostPermission(manifest, matchPattern);
@@ -0,0 +1,11 @@
1
+ export interface BabelSyntaxError extends SyntaxError {
2
+ code: 'BABEL_PARSER_SYNTAX_ERROR';
3
+ frame?: string;
4
+ id: string;
5
+ loc: {
6
+ line: number;
7
+ column: number;
8
+ };
9
+ }
10
+ export declare function isBabelSyntaxError(error: unknown): error is BabelSyntaxError;
11
+ export declare function logBabelSyntaxError(error: BabelSyntaxError): void;
@@ -0,0 +1,20 @@
1
+ import { relative } from "node:path";
2
+ import pc from "picocolors";
3
+ import { wxt } from "../wxt.mjs";
4
+ export function isBabelSyntaxError(error) {
5
+ return error instanceof SyntaxError && error.code === "BABEL_PARSER_SYNTAX_ERROR";
6
+ }
7
+ export function logBabelSyntaxError(error) {
8
+ let filename = relative(wxt.config.root, error.id);
9
+ if (filename.startsWith("..")) {
10
+ filename = error.id;
11
+ }
12
+ let message = error.message.replace(
13
+ /\(\d+:\d+\)$/,
14
+ `(${filename}:${error.loc.line}:${error.loc.column + 1})`
15
+ );
16
+ if (error.frame) {
17
+ message += "\n\n" + pc.red(error.frame);
18
+ }
19
+ wxt.logger.error(message);
20
+ }