wxt 0.21.1 → 0.21.3

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.
@@ -0,0 +1,34 @@
1
+ import { addViteConfig, defineWxtModule } from "../modules.mjs";
2
+ //#region src/builtin-modules/escape-unicode.ts
3
+ var escape_unicode_default = defineWxtModule({
4
+ name: "wxt:built-in:escape-unicode",
5
+ setup(wxt) {
6
+ if (!wxt.config.experimental.escapeUnicode) return;
7
+ addViteConfig(wxt, () => ({ plugins: [escapeUnicodePlugin(wxt)] }));
8
+ }
9
+ });
10
+ function escapeUnicodePlugin(wxt) {
11
+ return {
12
+ name: "wxt:escape-unicode",
13
+ generateBundle: (_, bundle) => {
14
+ const start = Date.now();
15
+ for (const key of Object.keys(bundle)) {
16
+ const chunk = bundle[key];
17
+ if (chunk.type === "chunk") chunk.code = escapeNonCharacterUnicode(chunk.code);
18
+ }
19
+ const end = Date.now();
20
+ wxt.logger.info("Escaped UTF8 non-characters in " + (end - start) + "ms");
21
+ }
22
+ };
23
+ }
24
+ /**
25
+ * See:
26
+ *
27
+ * - https://github.com/rolldown/rolldown/issues/8805#issuecomment-4103480096
28
+ * - https://github.com/rolldown/rolldown/issues/8805#issuecomment-5134001768
29
+ */
30
+ function escapeNonCharacterUnicode(text) {
31
+ return text.replace(/[\uFDD0-\uFDEF\uFFFE\uFFFF]|[\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/gu, (m) => "\\u" + m.codePointAt(0).toString(16));
32
+ }
33
+ //#endregion
34
+ export { escape_unicode_default as default };
@@ -1,6 +1,11 @@
1
1
  import favicon_permission_default from "./favicon-permission.mjs";
2
2
  import unimport_default from "./unimport.mjs";
3
+ import escape_unicode_default from "./escape-unicode.mjs";
3
4
  //#region src/builtin-modules/index.ts
4
- const builtinModules = [unimport_default, favicon_permission_default];
5
+ const builtinModules = [
6
+ unimport_default,
7
+ favicon_permission_default,
8
+ escape_unicode_default
9
+ ];
5
10
  //#endregion
6
11
  export { builtinModules };
@@ -1,8 +1,8 @@
1
1
  //#region src/core/initialize.d.ts
2
2
  declare function initialize(options: {
3
- directory: string;
4
- template: string;
5
- packageManager: string;
3
+ directory?: string;
4
+ template?: string;
5
+ packageManager?: string;
6
6
  }): Promise<void>;
7
7
  //#endregion
8
8
  export { initialize };
@@ -3,71 +3,57 @@ import { readdir, rename } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { consola as consola$1 } from "consola";
5
5
  import { styleText } from "node:util";
6
- import prompts from "prompts";
6
+ import { question, select } from "@topcli/prompts";
7
7
  import { downloadTemplate } from "giget";
8
8
  //#region src/core/initialize.ts
9
9
  async function initialize(options) {
10
10
  consola$1.info("Initializing new project");
11
11
  const templates = await listTemplates();
12
- const defaultTemplate = templates.find((template) => template.name === options.template?.toLowerCase().trim());
13
- const input = await prompts([
12
+ const inputTemplateName = options.template ? templates.find((template) => template.name === options.template.toLowerCase().trim())?.name : void 0;
13
+ const directory = options.directory ?? await question("Project Directory", { defaultValue: "." });
14
+ if (!directory) throw Error("Directory is required");
15
+ const templateName = inputTemplateName ?? await select("Choose a template", { choices: templates.map((template) => ({
16
+ label: TEMPLATE_COLORS[template.name] ? styleText(TEMPLATE_COLORS[template.name], template.name) : template.name,
17
+ value: template.name
18
+ })) });
19
+ const template = templates.find((t) => t.name === templateName);
20
+ if (!template) throw Error("Unknown template: " + templateName);
21
+ const packageManager = options.packageManager ?? await select("Package Manager", { choices: [
14
22
  {
15
- name: "directory",
16
- type: () => options.directory == null ? "text" : void 0,
17
- message: "Project Directory",
18
- initial: options.directory
23
+ label: styleText("magenta", "bun"),
24
+ value: "bun"
19
25
  },
20
26
  {
21
- name: "template",
22
- type: () => defaultTemplate == null ? "select" : void 0,
23
- message: "Choose a template",
24
- choices: templates.map((template) => ({
25
- title: TEMPLATE_COLORS[template.name] ? styleText(TEMPLATE_COLORS[template.name], template.name) : template.name,
26
- value: template
27
- }))
27
+ label: styleText("red", "npm"),
28
+ value: "npm"
28
29
  },
29
30
  {
30
- name: "packageManager",
31
- type: () => options.packageManager == null ? "select" : void 0,
32
- message: "Package Manager",
33
- choices: [
34
- {
35
- title: styleText("magenta", "bun"),
36
- value: "bun"
37
- },
38
- {
39
- title: styleText("red", "npm"),
40
- value: "npm"
41
- },
42
- {
43
- title: styleText("yellow", "pnpm"),
44
- value: "pnpm"
45
- },
46
- {
47
- title: styleText("cyan", "yarn"),
48
- value: "yarn"
49
- }
50
- ]
31
+ label: styleText("yellow", "pnpm"),
32
+ value: "pnpm"
33
+ },
34
+ {
35
+ label: styleText("cyan", "yarn"),
36
+ value: "yarn"
51
37
  }
52
- ], { onCancel: () => process.exit(1) });
53
- input.directory ||= options.directory || ".";
54
- input.template ??= defaultTemplate;
55
- input.packageManager ??= options.packageManager;
56
- if (await pathExists(input.directory)) {
57
- if (!((await readdir(input.directory)).filter((dir) => dir !== ".git").length === 0)) {
58
- consola$1.error(`The directory ${path.resolve(input.directory)} is not empty. Aborted.`);
38
+ ] });
39
+ if (await pathExists(directory)) {
40
+ if (!((await readdir(directory)).filter((dir) => dir !== ".git").length === 0)) {
41
+ consola$1.error(`The directory ${path.resolve(directory)} is not empty. Aborted.`);
59
42
  process.exit(1);
60
43
  }
61
44
  }
62
- await cloneProject(input);
63
- const cdPath = path.relative(process.cwd(), path.resolve(input.directory));
45
+ await cloneProject({
46
+ directory,
47
+ template
48
+ });
49
+ const cdPath = path.relative(process.cwd(), path.resolve(directory));
64
50
  console.log();
65
- consola$1.log(`✨ WXT project created with the ${TEMPLATE_COLORS[input.template.name] ? styleText(TEMPLATE_COLORS[input.template.name], input.template.name) : input.template.name} template.`);
51
+ consola$1.log(`✨ WXT project created with the ${TEMPLATE_COLORS[template.name] ? styleText(TEMPLATE_COLORS[template.name], template.name) : template.name} template.`);
66
52
  console.log();
67
53
  consola$1.log("Next steps:");
68
54
  let step = 0;
69
55
  if (cdPath !== "") consola$1.log(` ${++step}.`, styleText("cyan", `cd ${cdPath}`));
70
- consola$1.log(` ${++step}.`, styleText("cyan", `${input.packageManager} install`));
56
+ consola$1.log(` ${++step}.`, styleText("cyan", `${packageManager} install`));
71
57
  console.log();
72
58
  }
73
59
  async function listTemplates() {
@@ -12,7 +12,6 @@ import { pathExists } from "./utils/fs.mjs";
12
12
  import { glob } from "tinyglobby";
13
13
  import path from "node:path";
14
14
  import { loadConfig } from "c12";
15
- import { resolve as resolve$1 } from "import-meta-resolve";
16
15
  import consola, { LogLevels } from "consola";
17
16
  import defu from "defu";
18
17
  import { getPort } from "get-port-please";
@@ -150,7 +149,7 @@ async function resolveConfig(inlineConfig, command) {
150
149
  analysis: resolveAnalysisConfig(root, mergedConfig),
151
150
  userConfigMetadata: userConfigMetadata ?? {},
152
151
  alias,
153
- experimental: defu(mergedConfig.experimental, {}),
152
+ experimental: defu(mergedConfig.experimental, { escapeUnicode: false }),
154
153
  suppressWarnings: mergedConfig.suppressWarnings ?? {},
155
154
  watchOptions: mergedConfig.watchOptions ?? {},
156
155
  dev: {
@@ -366,7 +365,7 @@ async function getUnimportEslintOptions(logger, wxtDir, options) {
366
365
  }
367
366
  /** Returns the path to `node_modules/wxt`. */
368
367
  function resolveWxtModuleDir() {
369
- const url = resolve$1("wxt", import.meta.url);
368
+ const url = import.meta.resolve("wxt", import.meta.url);
370
369
  return path.resolve(fileURLToPath(url), "../..");
371
370
  }
372
371
  async function isDirMissing(dir) {
@@ -393,7 +392,7 @@ async function mergeBuilderConfig(logger, inlineConfig, userConfig) {
393
392
  async function resolveWxtUserModules(root, modulesDir, modules = []) {
394
393
  const importer = pathToFileURL(path.join(root, "index.js")).href;
395
394
  const npmModules = await Promise.all(modules.map(async (moduleId) => {
396
- const mod = await import(resolve$1(moduleId, importer));
395
+ const mod = await import(import.meta.resolve(moduleId, importer));
397
396
  if (mod.default == null) throw Error("Module missing default export: " + moduleId);
398
397
  return {
399
398
  ...mod.default,
@@ -1,4 +1,5 @@
1
1
  import { unnormalizePath } from "../paths.mjs";
2
+ import { isCI } from "../env.mjs";
2
3
  import { wxt } from "../../wxt.mjs";
3
4
  import { findEntrypoints } from "./find-entrypoints.mjs";
4
5
  import { groupEntrypoints } from "./group-entrypoints.mjs";
@@ -12,7 +13,7 @@ import { glob } from "tinyglobby";
12
13
  import { relative } from "node:path";
13
14
  import { mergeJsonOutputs } from "@aklinker1/rollup-plugin-visualizer";
14
15
  import { styleText } from "node:util";
15
- import { isCI } from "ci-info";
16
+ import open from "tiny-open";
16
17
  //#region src/core/utils/building/internal-build.ts
17
18
  /**
18
19
  * Builds the extension based on an internal config. No more config discovery is
@@ -51,10 +52,9 @@ async function internalBuild() {
51
52
  await combineAnalysisStats();
52
53
  const statsPath = relative(wxt.config.root, wxt.config.analysis.outputFile);
53
54
  wxt.logger.info(`Analysis complete:\n ${styleText("gray", "└─")} ${styleText("yellow", statsPath)}`);
54
- if (wxt.config.analysis.open) if (isCI) wxt.logger.debug(`Skipped opening ${styleText("yellow", statsPath)} in CI`);
55
+ if (wxt.config.analysis.open) if (isCI()) wxt.logger.debug(`Skipped opening ${styleText("yellow", statsPath)} in CI`);
55
56
  else {
56
57
  wxt.logger.info(`Opening ${styleText("yellow", statsPath)} in browser...`);
57
- const { default: open } = await import("open");
58
58
  await open(wxt.config.analysis.outputFile);
59
59
  }
60
60
  }
@@ -12,18 +12,18 @@ import "./building/index.mjs";
12
12
  import { isBabelSyntaxError, logBabelSyntaxError } from "./syntax-errors.mjs";
13
13
  import { relative } from "node:path";
14
14
  import { styleText } from "node:util";
15
- import { Mutex } from "async-mutex";
15
+ import { withLock } from "superlock";
16
16
  //#region src/core/utils/create-file-reloader.ts
17
17
  /**
18
18
  * Returns a function responsible for reloading different parts of the extension
19
19
  * when a file changes.
20
20
  */
21
21
  function createFileReloader(server) {
22
- const fileChangedMutex = new Mutex();
22
+ const fileChangedLock = withLock();
23
23
  const changeQueue = [];
24
24
  let processLoop;
25
25
  const processQueue = async () => {
26
- await fileChangedMutex.runExclusive(async () => {
26
+ await fileChangedLock(async () => {
27
27
  const fileChanges = changeQueue.splice(0, changeQueue.length).map(([_, file]) => file);
28
28
  if (fileChanges.length === 0) return;
29
29
  if (server.currentOutput == null) return;
@@ -2,6 +2,10 @@ import { expand } from "dotenv-expand";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { parseEnv } from "node:util";
4
4
  //#region src/core/utils/env.ts
5
+ /** Returns true when running in a CI environment. */
6
+ function isCI() {
7
+ return !!process.env.CI && process.env.CI !== "false";
8
+ }
5
9
  /** Load environment files based on the current mode and browser. */
6
10
  function loadEnv(mode, browser) {
7
11
  const envFiles = [
@@ -27,4 +31,4 @@ function loadEnv(mode, browser) {
27
31
  return parsed;
28
32
  }
29
33
  //#endregion
30
- export { loadEnv };
34
+ export { isCI, loadEnv };
package/dist/core/zip.mjs CHANGED
@@ -9,11 +9,10 @@ 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 { mkdir, readFile } from "node:fs/promises";
12
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
13
13
  import { glob } from "tinyglobby";
14
14
  import path from "node:path";
15
- import { createWriteStream } from "node:fs";
16
- import JSZip from "jszip";
15
+ import { createZip } from "@aklinker1/zero-zip";
17
16
  //#region src/core/zip.ts
18
17
  /**
19
18
  * Build and zip the extension for distribution.
@@ -67,7 +66,7 @@ async function zip(config) {
67
66
  return zipFiles;
68
67
  }
69
68
  async function zipDir(directory, outputPath, options) {
70
- const archive = new JSZip();
69
+ const archive = createZip({ level: wxt.config.zip.compressionLevel });
71
70
  const filesToZip = [...await glob(options?.include ?? ["**/*"], {
72
71
  cwd: directory,
73
72
  ignore: options?.exclude ?? [],
@@ -78,20 +77,14 @@ async function zipDir(directory, outputPath, options) {
78
77
  const absolutePath = path.resolve(directory, file);
79
78
  if (file.endsWith(".json")) {
80
79
  const content = await readFile(absolutePath, "utf-8");
81
- archive.file(file, await options?.transform?.(absolutePath, file, content) || content);
80
+ archive.addFile(file, await options?.transform?.(absolutePath, file, content) || content);
82
81
  } else {
83
82
  const content = await readFile(absolutePath);
84
- archive.file(file, content);
83
+ archive.addFile(file, content);
85
84
  }
86
85
  }
87
86
  await options?.additionalWork?.(archive);
88
- await new Promise((resolve, reject) => archive.generateNodeStream({
89
- type: "nodebuffer",
90
- ...wxt.config.zip.compressionLevel === 0 ? { compression: "STORE" } : {
91
- compression: "DEFLATE",
92
- compressionOptions: { level: wxt.config.zip.compressionLevel }
93
- }
94
- }).pipe(createWriteStream(outputPath)).on("error", reject).on("close", resolve));
87
+ await writeFile(outputPath, await archive.toBuffer());
95
88
  return filesToZip;
96
89
  }
97
90
  async function downloadPrivatePackages() {
package/dist/types.d.mts CHANGED
@@ -441,7 +441,23 @@ interface InlineConfig {
441
441
  */
442
442
  alias?: Record<string, string>;
443
443
  /** Experimental settings - use with caution. */
444
- experimental?: {};
444
+ experimental?: {
445
+ /**
446
+ * Some libraries include unicode characters Chrome does not allow in
447
+ * extensions. If you receive "Could not load content script... it is not
448
+ * UTF-8 encoded", enable this setting.
449
+ *
450
+ * It is not enabled by default because it will slow down your build, and
451
+ * because it's very rare to have the problematic characters in JS. So not
452
+ * every extension needs this flag enabled.
453
+ *
454
+ * For more details, see:
455
+ *
456
+ * - https://github.com/wxt-dev/wxt/issues/353
457
+ * - https://github.com/wxt-dev/wxt/issues/2535
458
+ */
459
+ escapeUnicode?: boolean;
460
+ };
445
461
  /** Config effecting dev mode only. */
446
462
  dev?: {
447
463
  server?: {
@@ -1449,7 +1465,9 @@ interface ResolvedConfig$1 {
1449
1465
  userConfigMetadata: Omit<ResolvedConfig<UserConfig>, 'config'>;
1450
1466
  /** Import aliases to absolute paths. */
1451
1467
  alias: Record<string, string>;
1452
- experimental: {};
1468
+ experimental: {
1469
+ escapeUnicode: boolean;
1470
+ };
1453
1471
  /** List of warning identifiers to suppress during the build process. */
1454
1472
  suppressWarnings: {
1455
1473
  firefoxDataCollection?: boolean;
package/dist/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region src/version.ts
2
- const version = "0.21.1";
2
+ const version = "0.21.3";
3
3
  //#endregion
4
4
  export { version };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "wxt",
3
3
  "type": "module",
4
- "version": "0.21.1",
4
+ "version": "0.21.3",
5
5
  "description": "⚡ Next-gen Web Extension Framework",
6
6
  "license": "MIT",
7
7
  "scripts": {
@@ -17,16 +17,16 @@
17
17
  "dependencies": {
18
18
  "@1natsu/wait-element": "^4.1.2",
19
19
  "@aklinker1/rollup-plugin-visualizer": "5.12.0",
20
+ "@aklinker1/zero-zip": "^1.0.1",
21
+ "@topcli/prompts": "^4.0.0",
20
22
  "@webext-core/fake-browser": "^2.0.1",
21
23
  "@webext-core/isolated-element": "^1.1.3 || ^2 || ^3",
22
24
  "@webext-core/match-patterns": "^2.0.0",
23
25
  "@wxt-dev/browser": "^0.2.2",
24
26
  "@wxt-dev/storage": "^1.0.0",
25
- "async-mutex": "^0.5.0",
26
27
  "c12": "^3.3.4",
27
28
  "cac": "^6.7.14 || ^7.0.0",
28
29
  "chokidar": "^5.0.0",
29
- "ci-info": "^4.4.0",
30
30
  "consola": "^3.4.2",
31
31
  "defu": "^6.1.4",
32
32
  "dotenv-expand": "^13.0.0",
@@ -34,10 +34,8 @@
34
34
  "get-port-please": "^3.2.0",
35
35
  "giget": "^1.2.3 || ^2.0.0 || ^3.0.0",
36
36
  "hookable": "^6.1.0",
37
- "import-meta-resolve": "^4.2.0",
38
37
  "is-wsl": "^3.1.1",
39
38
  "json5": "^2.2.3",
40
- "jszip": "^3.10.1",
41
39
  "linkedom": "^0.18.12",
42
40
  "magicast": "^0.5.2",
43
41
  "nano-spawn": "^2.0.0",
@@ -45,14 +43,13 @@
45
43
  "normalize-path": "^3.0.0",
46
44
  "nypm": "^0.6.5",
47
45
  "ohash": "^2.0.11",
48
- "open": "^11.0.0",
49
46
  "picomatch": "^4.0.3",
50
- "prompts": "^2.4.2",
51
- "publish-browser-extension": "^5.1.0",
47
+ "publish-browser-extension": "^5.1.0 || ^6.0.0",
52
48
  "scule": "^1.3.0",
49
+ "superlock": "^1.3.2",
50
+ "tiny-open": "^1.3.0",
53
51
  "tinyglobby": "^0.2.16",
54
- "unimport": "^3.13.1 || ^4.0.0 || ^5.0.0 || ^6.0.0",
55
- "web-ext-run": "^0.2.4"
52
+ "unimport": "^3.13.1 || ^4.0.0 || ^5.0.0 || ^6.0.0"
56
53
  },
57
54
  "peerDependencies": {
58
55
  "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
@@ -88,9 +85,9 @@
88
85
  "publint": "^0.3.18",
89
86
  "tsdown": "^0.21.0",
90
87
  "typescript": "^6.0.3",
91
- "vite": "^7.3.1",
92
- "vitest": "^4.1.5",
93
- "vitest-plugin-random-seed": "^1.1.2",
88
+ "vite": "^8.2.0",
89
+ "vitest": "^4.1.10",
90
+ "vitest-plugin-random-seed": "^2.0.2",
94
91
  "web-ext": "^10.5.0"
95
92
  },
96
93
  "repository": {