wxt 0.10.3 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -39,7 +39,7 @@ var require_windows = __commonJS({
39
39
  module2.exports = isexe;
40
40
  isexe.sync = sync;
41
41
  var fs16 = require("fs");
42
- function checkPathExt(path11, options) {
42
+ function checkPathExt(path10, options) {
43
43
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
44
44
  if (!pathext) {
45
45
  return true;
@@ -50,25 +50,25 @@ var require_windows = __commonJS({
50
50
  }
51
51
  for (var i = 0; i < pathext.length; i++) {
52
52
  var p = pathext[i].toLowerCase();
53
- if (p && path11.substr(-p.length).toLowerCase() === p) {
53
+ if (p && path10.substr(-p.length).toLowerCase() === p) {
54
54
  return true;
55
55
  }
56
56
  }
57
57
  return false;
58
58
  }
59
- function checkStat(stat, path11, options) {
59
+ function checkStat(stat, path10, options) {
60
60
  if (!stat.isSymbolicLink() && !stat.isFile()) {
61
61
  return false;
62
62
  }
63
- return checkPathExt(path11, options);
63
+ return checkPathExt(path10, options);
64
64
  }
65
- function isexe(path11, options, cb) {
66
- fs16.stat(path11, function(er, stat) {
67
- cb(er, er ? false : checkStat(stat, path11, options));
65
+ function isexe(path10, options, cb) {
66
+ fs16.stat(path10, function(er, stat) {
67
+ cb(er, er ? false : checkStat(stat, path10, options));
68
68
  });
69
69
  }
70
- function sync(path11, options) {
71
- return checkStat(fs16.statSync(path11), path11, options);
70
+ function sync(path10, options) {
71
+ return checkStat(fs16.statSync(path10), path10, options);
72
72
  }
73
73
  }
74
74
  });
@@ -80,13 +80,13 @@ var require_mode = __commonJS({
80
80
  module2.exports = isexe;
81
81
  isexe.sync = sync;
82
82
  var fs16 = require("fs");
83
- function isexe(path11, options, cb) {
84
- fs16.stat(path11, function(er, stat) {
83
+ function isexe(path10, options, cb) {
84
+ fs16.stat(path10, function(er, stat) {
85
85
  cb(er, er ? false : checkStat(stat, options));
86
86
  });
87
87
  }
88
- function sync(path11, options) {
89
- return checkStat(fs16.statSync(path11), options);
88
+ function sync(path10, options) {
89
+ return checkStat(fs16.statSync(path10), options);
90
90
  }
91
91
  function checkStat(stat, options) {
92
92
  return stat.isFile() && checkMode(stat, options);
@@ -120,7 +120,7 @@ var require_isexe = __commonJS({
120
120
  }
121
121
  module2.exports = isexe;
122
122
  isexe.sync = sync;
123
- function isexe(path11, options, cb) {
123
+ function isexe(path10, options, cb) {
124
124
  if (typeof options === "function") {
125
125
  cb = options;
126
126
  options = {};
@@ -130,7 +130,7 @@ var require_isexe = __commonJS({
130
130
  throw new TypeError("callback not provided");
131
131
  }
132
132
  return new Promise(function(resolve14, reject) {
133
- isexe(path11, options || {}, function(er, is) {
133
+ isexe(path10, options || {}, function(er, is) {
134
134
  if (er) {
135
135
  reject(er);
136
136
  } else {
@@ -139,7 +139,7 @@ var require_isexe = __commonJS({
139
139
  });
140
140
  });
141
141
  }
142
- core(path11, options || {}, function(er, is) {
142
+ core(path10, options || {}, function(er, is) {
143
143
  if (er) {
144
144
  if (er.code === "EACCES" || options && options.ignoreErrors) {
145
145
  er = null;
@@ -149,9 +149,9 @@ var require_isexe = __commonJS({
149
149
  cb(er, is);
150
150
  });
151
151
  }
152
- function sync(path11, options) {
152
+ function sync(path10, options) {
153
153
  try {
154
- return core.sync(path11, options || {});
154
+ return core.sync(path10, options || {});
155
155
  } catch (er) {
156
156
  if (options && options.ignoreErrors || er.code === "EACCES") {
157
157
  return false;
@@ -168,7 +168,7 @@ var require_which = __commonJS({
168
168
  "node_modules/.pnpm/which@2.0.2/node_modules/which/which.js"(exports, module2) {
169
169
  "use strict";
170
170
  var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
171
- var path11 = require("path");
171
+ var path10 = require("path");
172
172
  var COLON = isWindows ? ";" : ":";
173
173
  var isexe = require_isexe();
174
174
  var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
@@ -206,7 +206,7 @@ var require_which = __commonJS({
206
206
  return opt.all && found.length ? resolve14(found) : reject(getNotFoundError(cmd));
207
207
  const ppRaw = pathEnv[i];
208
208
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
209
- const pCmd = path11.join(pathPart, cmd);
209
+ const pCmd = path10.join(pathPart, cmd);
210
210
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
211
211
  resolve14(subStep(p, i, 0));
212
212
  });
@@ -233,7 +233,7 @@ var require_which = __commonJS({
233
233
  for (let i = 0; i < pathEnv.length; i++) {
234
234
  const ppRaw = pathEnv[i];
235
235
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
236
- const pCmd = path11.join(pathPart, cmd);
236
+ const pCmd = path10.join(pathPart, cmd);
237
237
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
238
238
  for (let j = 0; j < pathExt.length; j++) {
239
239
  const cur = p + pathExt[j];
@@ -281,7 +281,7 @@ var require_path_key = __commonJS({
281
281
  var require_resolveCommand = __commonJS({
282
282
  "node_modules/.pnpm/cross-spawn@7.0.3/node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module2) {
283
283
  "use strict";
284
- var path11 = require("path");
284
+ var path10 = require("path");
285
285
  var which = require_which();
286
286
  var getPathKey = require_path_key();
287
287
  function resolveCommandAttempt(parsed, withoutPathExt) {
@@ -299,7 +299,7 @@ var require_resolveCommand = __commonJS({
299
299
  try {
300
300
  resolved = which.sync(parsed.command, {
301
301
  path: env[getPathKey({ env })],
302
- pathExt: withoutPathExt ? path11.delimiter : void 0
302
+ pathExt: withoutPathExt ? path10.delimiter : void 0
303
303
  });
304
304
  } catch (e) {
305
305
  } finally {
@@ -308,7 +308,7 @@ var require_resolveCommand = __commonJS({
308
308
  }
309
309
  }
310
310
  if (resolved) {
311
- resolved = path11.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
311
+ resolved = path10.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
312
312
  }
313
313
  return resolved;
314
314
  }
@@ -362,8 +362,8 @@ var require_shebang_command = __commonJS({
362
362
  if (!match) {
363
363
  return null;
364
364
  }
365
- const [path11, argument] = match[0].replace(/#! ?/, "").split(" ");
366
- const binary = path11.split("/").pop();
365
+ const [path10, argument] = match[0].replace(/#! ?/, "").split(" ");
366
+ const binary = path10.split("/").pop();
367
367
  if (binary === "env") {
368
368
  return argument;
369
369
  }
@@ -398,7 +398,7 @@ var require_readShebang = __commonJS({
398
398
  var require_parse = __commonJS({
399
399
  "node_modules/.pnpm/cross-spawn@7.0.3/node_modules/cross-spawn/lib/parse.js"(exports, module2) {
400
400
  "use strict";
401
- var path11 = require("path");
401
+ var path10 = require("path");
402
402
  var resolveCommand = require_resolveCommand();
403
403
  var escape = require_escape();
404
404
  var readShebang = require_readShebang();
@@ -423,7 +423,7 @@ var require_parse = __commonJS({
423
423
  const needsShell = !isExecutableRegExp.test(commandFile);
424
424
  if (parsed.options.forceShell || needsShell) {
425
425
  const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
426
- parsed.command = path11.normalize(parsed.command);
426
+ parsed.command = path10.normalize(parsed.command);
427
427
  parsed.command = escape.command(parsed.command);
428
428
  parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
429
429
  const shellCommand = [parsed.command].concat(parsed.args).join(" ");
@@ -589,9 +589,9 @@ function npmRunPath(options = {}) {
589
589
  }
590
590
  function npmRunPathEnv({ env = import_node_process.default.env, ...options } = {}) {
591
591
  env = { ...env };
592
- const path11 = pathKey({ env });
593
- options.path = env[path11];
594
- env[path11] = npmRunPath(options);
592
+ const path10 = pathKey({ env });
593
+ options.path = env[path10];
594
+ env[path10] = npmRunPath(options);
595
595
  return env;
596
596
  }
597
597
  var import_node_process, import_node_path10, import_node_url;
@@ -2000,8 +2000,8 @@ var nativePromisePrototype, descriptors, mergePromise, getSpawnedPromise;
2000
2000
  var init_promise = __esm({
2001
2001
  "node_modules/.pnpm/execa@8.0.1/node_modules/execa/lib/promise.js"() {
2002
2002
  "use strict";
2003
- nativePromisePrototype = (/* @__PURE__ */ (async () => {
2004
- })()).constructor.prototype;
2003
+ nativePromisePrototype = (async () => {
2004
+ })().constructor.prototype;
2005
2005
  descriptors = ["then", "catch", "finally"].map((property) => [
2006
2006
  property,
2007
2007
  Reflect.getOwnPropertyDescriptor(nativePromisePrototype, property)
@@ -2415,27 +2415,173 @@ var init_execa = __esm({
2415
2415
  var import_cac = __toESM(require("cac"), 1);
2416
2416
 
2417
2417
  // package.json
2418
- var version = "0.10.3";
2418
+ var version = "0.11.0";
2419
2419
 
2420
- // src/core/utils/building/build-entrypoints.ts
2421
- var vite = __toESM(require("vite"), 1);
2422
-
2423
- // src/core/utils/entrypoints.ts
2424
- var import_node_path2 = __toESM(require("path"), 1);
2420
+ // src/core/utils/fs.ts
2421
+ var import_fs_extra = __toESM(require("fs-extra"), 1);
2422
+ var import_fast_glob = __toESM(require("fast-glob"), 1);
2425
2423
 
2426
2424
  // src/core/utils/paths.ts
2427
2425
  var import_node_path = __toESM(require("path"), 1);
2428
2426
  var import_normalize_path = __toESM(require("normalize-path"), 1);
2429
- function normalizePath(path11) {
2430
- return (0, import_normalize_path.default)(path11);
2427
+ function normalizePath(path10) {
2428
+ return (0, import_normalize_path.default)(path10);
2431
2429
  }
2432
- function unnormalizePath(path11) {
2433
- return import_node_path.default.normalize(path11);
2430
+ function unnormalizePath(path10) {
2431
+ return import_node_path.default.normalize(path10);
2434
2432
  }
2435
2433
  var CSS_EXTENSIONS = ["css", "scss", "sass", "less", "styl", "stylus"];
2436
2434
  var CSS_EXTENSIONS_PATTERN = `+(${CSS_EXTENSIONS.join("|")})`;
2437
2435
 
2436
+ // src/core/utils/fs.ts
2437
+ async function writeFileIfDifferent(file, newContents) {
2438
+ const existingContents = await import_fs_extra.default.readFile(file, "utf-8").catch(() => void 0);
2439
+ if (existingContents !== newContents) {
2440
+ await import_fs_extra.default.writeFile(file, newContents);
2441
+ }
2442
+ }
2443
+ async function getPublicFiles(config) {
2444
+ if (!await import_fs_extra.default.exists(config.publicDir))
2445
+ return [];
2446
+ const files = await (0, import_fast_glob.default)("**/*", { cwd: config.publicDir });
2447
+ return files.map(unnormalizePath);
2448
+ }
2449
+
2450
+ // src/core/utils/building/build-entrypoints.ts
2451
+ var import_fs_extra2 = __toESM(require("fs-extra"), 1);
2452
+ var import_path = require("path");
2453
+ var import_picocolors = __toESM(require("picocolors"), 1);
2454
+ async function buildEntrypoints(groups, config, spinner) {
2455
+ const steps = [];
2456
+ for (let i = 0; i < groups.length; i++) {
2457
+ const group = groups[i];
2458
+ const groupNames = [group].flat().map((e) => e.name).join(import_picocolors.default.dim(", "));
2459
+ spinner.text = import_picocolors.default.dim(`[${i + 1}/${groups.length}]`) + ` ${groupNames}`;
2460
+ steps.push(await config.builder.build(group));
2461
+ }
2462
+ const publicAssets = await copyPublicDirectory(config);
2463
+ return { publicAssets, steps };
2464
+ }
2465
+ async function copyPublicDirectory(config) {
2466
+ const files = await getPublicFiles(config);
2467
+ if (files.length === 0)
2468
+ return [];
2469
+ const publicAssets = [];
2470
+ for (const file of files) {
2471
+ const srcPath = (0, import_path.resolve)(config.publicDir, file);
2472
+ const outPath = (0, import_path.resolve)(config.outDir, file);
2473
+ await import_fs_extra2.default.ensureDir((0, import_path.dirname)(outPath));
2474
+ await import_fs_extra2.default.copyFile(srcPath, outPath);
2475
+ publicAssets.push({
2476
+ type: "asset",
2477
+ fileName: file
2478
+ });
2479
+ }
2480
+ return publicAssets;
2481
+ }
2482
+
2483
+ // src/core/utils/arrays.ts
2484
+ function every(array, predicate) {
2485
+ for (let i = 0; i < array.length; i++)
2486
+ if (!predicate(array[i], i))
2487
+ return false;
2488
+ return true;
2489
+ }
2490
+
2491
+ // src/core/utils/building/detect-dev-changes.ts
2492
+ function detectDevChanges(changedFiles, currentOutput) {
2493
+ if (currentOutput == null)
2494
+ return { type: "no-change" };
2495
+ const changedSteps = new Set(
2496
+ changedFiles.flatMap(
2497
+ (changedFile) => findEffectedSteps(changedFile, currentOutput)
2498
+ )
2499
+ );
2500
+ if (changedSteps.size === 0)
2501
+ return { type: "no-change" };
2502
+ const unchangedOutput = {
2503
+ manifest: currentOutput.manifest,
2504
+ steps: [],
2505
+ publicAssets: []
2506
+ };
2507
+ const changedOutput = {
2508
+ manifest: currentOutput.manifest,
2509
+ steps: [],
2510
+ publicAssets: []
2511
+ };
2512
+ for (const step of currentOutput.steps) {
2513
+ if (changedSteps.has(step)) {
2514
+ changedOutput.steps.push(step);
2515
+ } else {
2516
+ unchangedOutput.steps.push(step);
2517
+ }
2518
+ }
2519
+ for (const asset of currentOutput.publicAssets) {
2520
+ if (changedSteps.has(asset)) {
2521
+ changedOutput.publicAssets.push(asset);
2522
+ } else {
2523
+ unchangedOutput.publicAssets.push(asset);
2524
+ }
2525
+ }
2526
+ const isOnlyHtmlChanges = changedFiles.length > 0 && every(changedFiles, ([_, file]) => file.endsWith(".html"));
2527
+ if (isOnlyHtmlChanges) {
2528
+ return {
2529
+ type: "html-reload",
2530
+ cachedOutput: unchangedOutput,
2531
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
2532
+ };
2533
+ }
2534
+ const isOnlyContentScripts = changedOutput.steps.length > 0 && every(
2535
+ changedOutput.steps.flatMap((step) => step.entrypoints),
2536
+ (entry) => entry.type === "content-script"
2537
+ );
2538
+ if (isOnlyContentScripts) {
2539
+ return {
2540
+ type: "content-script-reload",
2541
+ cachedOutput: unchangedOutput,
2542
+ changedSteps: changedOutput.steps,
2543
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
2544
+ };
2545
+ }
2546
+ return {
2547
+ type: "extension-reload",
2548
+ cachedOutput: unchangedOutput,
2549
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
2550
+ };
2551
+ }
2552
+ function findEffectedSteps(changedFile, currentOutput) {
2553
+ const changes = [];
2554
+ const changedPath = normalizePath(changedFile[1]);
2555
+ const isChunkEffected = (chunk) => (
2556
+ // If it's an HTML file with the same path, is is effected because HTML files need to be pre-rendered
2557
+ // fileName is normalized, relative bundle path
2558
+ chunk.type === "asset" && changedPath.endsWith(chunk.fileName) || // If it's a chunk that depends on the changed file, it is effected
2559
+ // moduleIds are absolute, normalized paths
2560
+ chunk.type === "chunk" && chunk.moduleIds.includes(changedPath)
2561
+ );
2562
+ for (const step of currentOutput.steps) {
2563
+ const effectedChunk = step.chunks.find((chunk) => isChunkEffected(chunk));
2564
+ if (effectedChunk)
2565
+ changes.push(step);
2566
+ }
2567
+ const effectedAsset = currentOutput.publicAssets.find(
2568
+ (chunk) => isChunkEffected(chunk)
2569
+ );
2570
+ if (effectedAsset)
2571
+ changes.push(effectedAsset);
2572
+ return changes;
2573
+ }
2574
+
2575
+ // src/core/utils/building/find-entrypoints.ts
2576
+ var import_path2 = require("path");
2577
+ var import_fs_extra3 = __toESM(require("fs-extra"), 1);
2578
+ var import_minimatch = require("minimatch");
2579
+ var import_linkedom = require("linkedom");
2580
+ var import_json5 = __toESM(require("json5"), 1);
2581
+ var import_fast_glob2 = __toESM(require("fast-glob"), 1);
2582
+
2438
2583
  // src/core/utils/entrypoints.ts
2584
+ var import_node_path2 = __toESM(require("path"), 1);
2439
2585
  function getEntrypointName(entrypointsDir, inputPath) {
2440
2586
  const relativePath = import_node_path2.default.relative(entrypointsDir, inputPath);
2441
2587
  const name = relativePath.split(/[\.\/\\]/, 2)[0];
@@ -2455,423 +2601,332 @@ function resolvePerBrowserOption(option, browser) {
2455
2601
  return option;
2456
2602
  }
2457
2603
 
2458
- // src/core/vite-plugins/devHtmlPrerender.ts
2459
- var import_linkedom = require("linkedom");
2460
- var import_path = require("path");
2461
- var reactRefreshPreamble = "";
2462
- function devHtmlPrerender(config) {
2463
- const htmlReloadId = "@wxt/reload-html";
2464
- const resolvedHtmlReloadId = (0, import_path.resolve)(
2465
- config.root,
2466
- "node_modules/wxt/dist/virtual/reload-html.js"
2467
- );
2468
- const virtualReactRefreshId = "@wxt/virtual-react-refresh";
2469
- const resolvedVirtualReactRefreshId = "\0" + virtualReactRefreshId;
2470
- return [
2471
- {
2472
- apply: "build",
2473
- name: "wxt:dev-html-prerender",
2474
- config() {
2475
- return {
2476
- resolve: {
2477
- alias: {
2478
- [htmlReloadId]: resolvedHtmlReloadId
2479
- }
2480
- }
2481
- };
2482
- },
2483
- // Convert scripts like src="./main.tsx" -> src="http://localhost:3000/entrypoints/popup/main.tsx"
2484
- // before the paths are replaced with their bundled path
2485
- transform(code, id) {
2486
- const server = config.server;
2487
- if (config.command !== "serve" || server == null || !id.endsWith(".html"))
2488
- return;
2489
- const { document } = (0, import_linkedom.parseHTML)(code);
2490
- const pointToDevServer = (querySelector, attr) => {
2491
- document.querySelectorAll(querySelector).forEach((element) => {
2492
- const src = element.getAttribute(attr);
2493
- if (!src)
2494
- return;
2495
- if ((0, import_path.isAbsolute)(src)) {
2496
- element.setAttribute(attr, server.origin + src);
2497
- } else if (src.startsWith(".")) {
2498
- const abs = (0, import_path.resolve)((0, import_path.dirname)(id), src);
2499
- const pathname = (0, import_path.relative)(config.root, abs);
2500
- element.setAttribute(attr, `${server.origin}/${pathname}`);
2501
- }
2502
- });
2503
- };
2504
- pointToDevServer("script[type=module]", "src");
2505
- pointToDevServer("link[rel=stylesheet]", "href");
2506
- const reloader = document.createElement("script");
2507
- reloader.src = htmlReloadId;
2508
- reloader.type = "module";
2509
- document.head.appendChild(reloader);
2510
- const newHtml = document.toString();
2511
- config.logger.debug("transform " + id);
2512
- config.logger.debug("Old HTML:\n" + code);
2513
- config.logger.debug("New HTML:\n" + newHtml);
2514
- return newHtml;
2515
- },
2516
- // Pass the HTML through the dev server to add dev-mode specific code
2517
- async transformIndexHtml(html, ctx) {
2518
- const server = config.server;
2519
- if (config.command !== "serve" || server == null)
2520
- return;
2521
- const originalUrl = `${server.origin}${ctx.path}`;
2522
- const name = getEntrypointName(config.entrypointsDir, ctx.filename);
2523
- const url2 = `${server.origin}/${name}.html`;
2524
- const serverHtml = await server.transformIndexHtml(
2525
- url2,
2526
- html,
2527
- originalUrl
2528
- );
2529
- const { document } = (0, import_linkedom.parseHTML)(serverHtml);
2530
- const reactRefreshScript = Array.from(
2531
- document.querySelectorAll("script[type=module]")
2532
- ).find((script) => script.innerHTML.includes("@react-refresh"));
2533
- if (reactRefreshScript) {
2534
- reactRefreshPreamble = reactRefreshScript.innerHTML;
2535
- const virtualScript = document.createElement("script");
2536
- virtualScript.type = "module";
2537
- virtualScript.src = `${server.origin}/${virtualReactRefreshId}`;
2538
- reactRefreshScript.replaceWith(virtualScript);
2539
- }
2540
- const viteClientScript = document.querySelector(
2541
- "script[src='/@vite/client']"
2604
+ // src/core/utils/constants.ts
2605
+ var VIRTUAL_NOOP_BACKGROUND_MODULE_ID = "virtual:user-background";
2606
+
2607
+ // src/core/utils/building/find-entrypoints.ts
2608
+ async function findEntrypoints(config) {
2609
+ const relativePaths = await (0, import_fast_glob2.default)("**/*", {
2610
+ cwd: config.entrypointsDir
2611
+ });
2612
+ relativePaths.sort();
2613
+ const pathGlobs = Object.keys(PATH_GLOB_TO_TYPE_MAP);
2614
+ let hasBackground = false;
2615
+ const possibleEntrypoints = await Promise.all(
2616
+ relativePaths.map(async (relativePath) => {
2617
+ const path10 = (0, import_path2.resolve)(config.entrypointsDir, relativePath);
2618
+ const matchingGlob = pathGlobs.find(
2619
+ (glob5) => (0, import_minimatch.minimatch)(relativePath, glob5)
2620
+ );
2621
+ if (matchingGlob == null) {
2622
+ config.logger.warn(
2623
+ `${relativePath} does not match any known entrypoint. Known entrypoints:
2624
+ ${JSON.stringify(
2625
+ PATH_GLOB_TO_TYPE_MAP,
2626
+ null,
2627
+ 2
2628
+ )}`
2542
2629
  );
2543
- if (viteClientScript) {
2544
- viteClientScript.src = `${server.origin}${viteClientScript.src}`;
2545
- }
2546
- const newHtml = document.toString();
2547
- config.logger.debug("transformIndexHtml " + ctx.filename);
2548
- config.logger.debug("Old HTML:\n" + html);
2549
- config.logger.debug("New HTML:\n" + newHtml);
2550
- return newHtml;
2630
+ return;
2551
2631
  }
2552
- },
2553
- {
2554
- name: "wxt:virtualize-react-refresh",
2555
- apply: "serve",
2556
- resolveId(id) {
2557
- if (id === `/${virtualReactRefreshId}`) {
2558
- return resolvedVirtualReactRefreshId;
2559
- }
2560
- if (id.startsWith("/chunks/")) {
2561
- return "\0noop";
2562
- }
2563
- },
2564
- load(id) {
2565
- if (id === resolvedVirtualReactRefreshId) {
2566
- return reactRefreshPreamble;
2567
- }
2568
- if (id === "\0noop") {
2569
- return "";
2570
- }
2571
- }
2572
- }
2573
- ];
2574
- }
2575
-
2576
- // src/core/vite-plugins/devServerGlobals.ts
2577
- function devServerGlobals(internalConfig) {
2578
- return {
2579
- name: "wxt:dev-server-globals",
2580
- config() {
2581
- if (internalConfig.server == null || internalConfig.command == "build")
2632
+ const type = PATH_GLOB_TO_TYPE_MAP[matchingGlob];
2633
+ if (type === "ignored")
2582
2634
  return;
2583
- return {
2584
- define: {
2585
- __DEV_SERVER_PROTOCOL__: JSON.stringify("ws:"),
2586
- __DEV_SERVER_HOSTNAME__: JSON.stringify(
2587
- internalConfig.server.hostname
2588
- ),
2589
- __DEV_SERVER_PORT__: JSON.stringify(internalConfig.server.port)
2590
- }
2591
- };
2635
+ switch (type) {
2636
+ case "popup":
2637
+ return await getPopupEntrypoint(config, path10);
2638
+ case "options":
2639
+ return await getOptionsEntrypoint(config, path10);
2640
+ case "background":
2641
+ hasBackground = true;
2642
+ return await getBackgroundEntrypoint(config, path10);
2643
+ case "content-script":
2644
+ return await getContentScriptEntrypoint(config, path10);
2645
+ case "unlisted-page":
2646
+ return await getUnlistedPageEntrypoint(config, path10);
2647
+ case "unlisted-script":
2648
+ return await getUnlistedScriptEntrypoint(config, path10);
2649
+ case "content-script-style":
2650
+ return {
2651
+ type,
2652
+ name: getEntrypointName(config.entrypointsDir, path10),
2653
+ inputPath: path10,
2654
+ outputDir: (0, import_path2.resolve)(config.outDir, CONTENT_SCRIPT_OUT_DIR),
2655
+ options: {
2656
+ include: void 0,
2657
+ exclude: void 0
2658
+ }
2659
+ };
2660
+ default:
2661
+ return {
2662
+ type,
2663
+ name: getEntrypointName(config.entrypointsDir, path10),
2664
+ inputPath: path10,
2665
+ outputDir: config.outDir,
2666
+ options: {
2667
+ include: void 0,
2668
+ exclude: void 0
2669
+ }
2670
+ };
2671
+ }
2672
+ })
2673
+ );
2674
+ const entrypoints = possibleEntrypoints.filter(
2675
+ (entry) => !!entry
2676
+ );
2677
+ const existingNames = {};
2678
+ entrypoints.forEach((entrypoint) => {
2679
+ const withSameName = existingNames[entrypoint.name];
2680
+ if (withSameName) {
2681
+ throw Error(
2682
+ `Multiple entrypoints with the name "${entrypoint.name}" detected, but only one is allowed: ${[
2683
+ (0, import_path2.relative)(config.root, withSameName.inputPath),
2684
+ (0, import_path2.relative)(config.root, entrypoint.inputPath)
2685
+ ].join(", ")}`
2686
+ );
2592
2687
  }
2593
- };
2594
- }
2595
-
2596
- // src/core/utils/network.ts
2597
- var import_node_dns = __toESM(require("dns"), 1);
2598
-
2599
- // src/core/utils/time.ts
2600
- function formatDuration(duration) {
2601
- if (duration < 1e3)
2602
- return `${duration} ms`;
2603
- if (duration < 1e4)
2604
- return `${(duration / 1e3).toFixed(3)} s`;
2605
- if (duration < 6e4)
2606
- return `${(duration / 1e3).toFixed(1)} s`;
2607
- return `${(duration / 1e3).toFixed(0)} s`;
2608
- }
2609
- function withTimeout(promise, duration) {
2610
- return new Promise((res, rej) => {
2611
- const timeout = setTimeout(() => {
2612
- rej(`Promise timed out after ${duration}ms`);
2613
- }, duration);
2614
- promise.then(res).catch(rej).finally(() => clearTimeout(timeout));
2688
+ existingNames[entrypoint.name] = entrypoint;
2615
2689
  });
2616
- }
2617
-
2618
- // src/core/utils/network.ts
2619
- function isOffline() {
2620
- const isOffline2 = new Promise((res) => {
2621
- import_node_dns.default.resolve("google.com", (err) => {
2622
- if (err == null) {
2623
- res(false);
2624
- } else {
2625
- res(true);
2626
- }
2627
- });
2690
+ if (config.command === "serve" && !hasBackground) {
2691
+ entrypoints.push(
2692
+ await getBackgroundEntrypoint(config, VIRTUAL_NOOP_BACKGROUND_MODULE_ID)
2693
+ );
2694
+ }
2695
+ config.logger.debug("All entrypoints:", entrypoints);
2696
+ const targetEntrypoints = entrypoints.filter((entry) => {
2697
+ const { include, exclude } = entry.options;
2698
+ if (include?.length && exclude?.length) {
2699
+ config.logger.warn(
2700
+ `The ${entry.name} entrypoint lists both include and exclude, but only one can be used per entrypoint. Entrypoint ignored.`
2701
+ );
2702
+ return false;
2703
+ }
2704
+ if (exclude?.length && !include?.length) {
2705
+ return !exclude.includes(config.browser);
2706
+ }
2707
+ if (include?.length && !exclude?.length) {
2708
+ return include.includes(config.browser);
2709
+ }
2710
+ return true;
2628
2711
  });
2629
- return withTimeout(isOffline2, 1e3).catch(() => true);
2712
+ config.logger.debug(`${config.browser} entrypoints:`, targetEntrypoints);
2713
+ return targetEntrypoints;
2630
2714
  }
2631
- async function isOnline() {
2632
- const offline = await isOffline();
2633
- return !offline;
2715
+ function getHtmlBaseOptions(document) {
2716
+ const options = {};
2717
+ const includeContent = document.querySelector("meta[name='manifest.include']")?.getAttribute("content");
2718
+ if (includeContent) {
2719
+ options.include = import_json5.default.parse(includeContent);
2720
+ }
2721
+ const excludeContent = document.querySelector("meta[name='manifest.exclude']")?.getAttribute("content");
2722
+ if (excludeContent) {
2723
+ options.exclude = import_json5.default.parse(excludeContent);
2724
+ }
2725
+ return options;
2634
2726
  }
2635
- async function fetchCached(url2, config) {
2636
- let content = "";
2637
- if (await isOnline()) {
2638
- const res = await fetch(url2);
2639
- if (res.status < 300) {
2640
- content = await res.text();
2641
- await config.fsCache.set(url2, content);
2642
- } else {
2643
- config.logger.debug(
2644
- `Failed to download "${url2}", falling back to cache...`
2727
+ async function getPopupEntrypoint(config, path10) {
2728
+ const content = await import_fs_extra3.default.readFile(path10, "utf-8");
2729
+ const { document } = (0, import_linkedom.parseHTML)(content);
2730
+ const options = getHtmlBaseOptions(document);
2731
+ const title = document.querySelector("title");
2732
+ if (title != null)
2733
+ options.defaultTitle = title.textContent ?? void 0;
2734
+ const defaultIconContent = document.querySelector("meta[name='manifest.default_icon']")?.getAttribute("content");
2735
+ if (defaultIconContent) {
2736
+ try {
2737
+ options.defaultIcon = import_json5.default.parse(defaultIconContent);
2738
+ } catch (err) {
2739
+ config.logger.fatal(
2740
+ `Failed to parse default_icon meta tag content as JSON5. content=${defaultIconContent}`,
2741
+ err
2645
2742
  );
2646
2743
  }
2647
2744
  }
2648
- if (!content)
2649
- content = await config.fsCache.get(url2) ?? "";
2650
- if (!content)
2651
- throw Error(
2652
- `Offline and "${url2}" has not been cached. Try again when online.`
2653
- );
2654
- return content;
2745
+ const mv2TypeContent = document.querySelector("meta[name='manifest.type']")?.getAttribute("content");
2746
+ if (mv2TypeContent) {
2747
+ options.mv2Key = mv2TypeContent === "page_action" ? "page_action" : "browser_action";
2748
+ }
2749
+ const browserStyleContent = document.querySelector("meta[name='manifest.browser_style']")?.getAttribute("content");
2750
+ if (browserStyleContent) {
2751
+ options.browserStyle = browserStyleContent === "true";
2752
+ }
2753
+ return {
2754
+ type: "popup",
2755
+ name: "popup",
2756
+ options,
2757
+ inputPath: path10,
2758
+ outputDir: config.outDir
2759
+ };
2655
2760
  }
2656
-
2657
- // src/core/vite-plugins/download.ts
2658
- function download(config) {
2761
+ async function getOptionsEntrypoint(config, path10) {
2762
+ const content = await import_fs_extra3.default.readFile(path10, "utf-8");
2763
+ const { document } = (0, import_linkedom.parseHTML)(content);
2764
+ const options = getHtmlBaseOptions(document);
2765
+ const openInTabContent = document.querySelector("meta[name='manifest.open_in_tab']")?.getAttribute("content");
2766
+ if (openInTabContent) {
2767
+ options.openInTab = openInTabContent === "true";
2768
+ }
2769
+ const chromeStyleContent = document.querySelector("meta[name='manifest.chrome_style']")?.getAttribute("content");
2770
+ if (chromeStyleContent) {
2771
+ options.chromeStyle = chromeStyleContent === "true";
2772
+ }
2773
+ const browserStyleContent = document.querySelector("meta[name='manifest.browser_style']")?.getAttribute("content");
2774
+ if (browserStyleContent) {
2775
+ options.browserStyle = browserStyleContent === "true";
2776
+ }
2659
2777
  return {
2660
- name: "wxt:download",
2661
- resolveId(id) {
2662
- if (id.startsWith("url:"))
2663
- return "\0" + id;
2664
- },
2665
- async load(id) {
2666
- if (!id.startsWith("\0url:"))
2667
- return;
2668
- const url2 = id.replace("\0url:", "");
2669
- return await fetchCached(url2, config);
2670
- }
2778
+ type: "options",
2779
+ name: "options",
2780
+ options,
2781
+ inputPath: path10,
2782
+ outputDir: config.outDir
2671
2783
  };
2672
2784
  }
2673
-
2674
- // src/core/vite-plugins/multipageMove.ts
2675
- var import_node_path3 = require("path");
2676
- var import_fs_extra = __toESM(require("fs-extra"), 1);
2677
- function multipageMove(entrypoints, config) {
2785
+ async function getUnlistedPageEntrypoint(config, path10) {
2786
+ const content = await import_fs_extra3.default.readFile(path10, "utf-8");
2787
+ const { document } = (0, import_linkedom.parseHTML)(content);
2678
2788
  return {
2679
- name: "wxt:multipage-move",
2680
- async writeBundle(_, bundle) {
2681
- for (const oldBundlePath in bundle) {
2682
- const entrypoint = entrypoints.find(
2683
- (entry) => !!normalizePath(entry.inputPath).endsWith(oldBundlePath)
2684
- );
2685
- if (entrypoint == null) {
2686
- config.logger.debug(
2687
- `No entrypoint found for ${oldBundlePath}, leaving in chunks directory`
2688
- );
2689
- continue;
2690
- }
2691
- const newBundlePath = getEntrypointBundlePath(
2692
- entrypoint,
2693
- config.outDir,
2694
- (0, import_node_path3.extname)(oldBundlePath)
2695
- );
2696
- if (newBundlePath === oldBundlePath) {
2697
- config.logger.debug(
2698
- "HTML file is already in the correct location",
2699
- oldBundlePath
2700
- );
2701
- continue;
2702
- }
2703
- const oldAbsPath = (0, import_node_path3.resolve)(config.outDir, oldBundlePath);
2704
- const newAbsPath = (0, import_node_path3.resolve)(config.outDir, newBundlePath);
2705
- await (0, import_fs_extra.ensureDir)((0, import_node_path3.dirname)(newAbsPath));
2706
- await import_fs_extra.default.move(oldAbsPath, newAbsPath, { overwrite: true });
2707
- const renamedChunk = {
2708
- ...bundle[oldBundlePath],
2709
- fileName: newBundlePath
2710
- };
2711
- delete bundle[oldBundlePath];
2712
- bundle[newBundlePath] = renamedChunk;
2713
- }
2714
- }
2789
+ type: "unlisted-page",
2790
+ name: getEntrypointName(config.entrypointsDir, path10),
2791
+ inputPath: path10,
2792
+ outputDir: config.outDir,
2793
+ options: getHtmlBaseOptions(document)
2715
2794
  };
2716
2795
  }
2717
-
2718
- // src/core/vite-plugins/unimport.ts
2719
- var import_unimport = require("unimport");
2720
-
2721
- // src/core/utils/unimport.ts
2722
- var import_vite = require("vite");
2723
- function getUnimportOptions(config) {
2724
- if (config.imports === false)
2725
- return false;
2726
- const defaultOptions = {
2727
- debugLog: config.logger.debug,
2728
- imports: [
2729
- { name: "defineConfig", from: "wxt" },
2730
- { name: "fakeBrowser", from: "wxt/testing" }
2731
- ],
2732
- presets: [
2733
- { package: "wxt/client" },
2734
- { package: "wxt/browser" },
2735
- { package: "wxt/sandbox" },
2736
- { package: "wxt/storage" }
2737
- ],
2738
- warn: config.logger.warn,
2739
- dirs: ["components", "composables", "hooks", "utils"]
2740
- };
2741
- return (0, import_vite.mergeConfig)(
2742
- defaultOptions,
2743
- config.imports
2796
+ async function getUnlistedScriptEntrypoint(config, path10) {
2797
+ const name = getEntrypointName(config.entrypointsDir, path10);
2798
+ const defaultExport = await importEntrypointFile(
2799
+ path10,
2800
+ config
2744
2801
  );
2745
- }
2746
-
2747
- // src/core/vite-plugins/unimport.ts
2748
- var import_path2 = require("path");
2749
- var ENABLED_EXTENSIONS = /* @__PURE__ */ new Set([
2750
- ".js",
2751
- ".jsx",
2752
- ".ts",
2753
- ".tsx",
2754
- ".vue",
2755
- ".svelte"
2756
- ]);
2757
- function unimport(config) {
2758
- const options = getUnimportOptions(config);
2759
- if (options === false)
2760
- return [];
2761
- const unimport2 = (0, import_unimport.createUnimport)(options);
2802
+ if (defaultExport == null) {
2803
+ throw Error(
2804
+ `${name}: Default export not found, did you forget to call "export default defineUnlistedScript(...)"?`
2805
+ );
2806
+ }
2807
+ const { main: _, ...moduleOptions } = defaultExport;
2808
+ const options = moduleOptions;
2762
2809
  return {
2763
- name: "wxt:unimport",
2764
- async config() {
2765
- await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
2766
- },
2767
- async transform(code, id) {
2768
- if (id.includes("node_modules"))
2769
- return;
2770
- if (!ENABLED_EXTENSIONS.has((0, import_path2.extname)(id)))
2771
- return;
2772
- return unimport2.injectImports(code, id);
2773
- }
2810
+ type: "unlisted-script",
2811
+ name,
2812
+ inputPath: path10,
2813
+ outputDir: config.outDir,
2814
+ options
2774
2815
  };
2775
2816
  }
2776
-
2777
- // src/core/vite-plugins/virtualEntrypoint.ts
2778
- var import_fs_extra2 = __toESM(require("fs-extra"), 1);
2779
- var import_path3 = require("path");
2780
- function virtualEntrypoint(type, config) {
2781
- const virtualId = `virtual:wxt-${type}?`;
2782
- const resolvedVirtualId = `\0${virtualId}`;
2783
- return {
2784
- name: `wxt:virtual-entrypoint`,
2785
- resolveId(id) {
2786
- const index = id.indexOf(virtualId);
2787
- if (index === -1)
2788
- return;
2789
- const inputPath = normalizePath(id.substring(index + virtualId.length));
2790
- return resolvedVirtualId + inputPath;
2791
- },
2792
- async load(id) {
2793
- if (!id.startsWith(resolvedVirtualId))
2794
- return;
2795
- const inputPath = id.replace(resolvedVirtualId, "");
2796
- const template = await import_fs_extra2.default.readFile(
2797
- (0, import_path3.resolve)(
2798
- config.root,
2799
- `node_modules/wxt/dist/virtual/${type}-entrypoint.js`
2800
- ),
2801
- "utf-8"
2817
+ async function getBackgroundEntrypoint(config, path10) {
2818
+ const name = "background";
2819
+ let options = {};
2820
+ if (path10 !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) {
2821
+ const defaultExport = await importEntrypointFile(
2822
+ path10,
2823
+ config
2824
+ );
2825
+ if (defaultExport == null) {
2826
+ throw Error(
2827
+ `${name}: Default export not found, did you forget to call "export default defineBackground(...)"?`
2802
2828
  );
2803
- return template.replace(`virtual:user-${type}`, inputPath);
2804
2829
  }
2805
- };
2806
- }
2807
-
2808
- // src/core/vite-plugins/tsconfigPaths.ts
2809
- function tsconfigPaths(config) {
2830
+ const { main: _, ...moduleOptions } = defaultExport;
2831
+ options = moduleOptions;
2832
+ }
2810
2833
  return {
2811
- name: "wxt:aliases",
2812
- async config() {
2813
- return {
2814
- resolve: {
2815
- alias: config.alias
2816
- }
2817
- };
2834
+ type: "background",
2835
+ name,
2836
+ inputPath: path10,
2837
+ outputDir: config.outDir,
2838
+ options: {
2839
+ ...options,
2840
+ type: resolvePerBrowserOption(options.type, config.browser),
2841
+ persistent: resolvePerBrowserOption(options.persistent, config.browser)
2818
2842
  }
2819
2843
  };
2820
2844
  }
2821
-
2822
- // src/core/vite-plugins/noopBackground.ts
2823
- function noopBackground() {
2824
- const virtualModuleId = VIRTUAL_NOOP_BACKGROUND_MODULE_ID;
2825
- const resolvedVirtualModuleId = "\0" + virtualModuleId;
2845
+ async function getContentScriptEntrypoint(config, path10) {
2846
+ const name = getEntrypointName(config.entrypointsDir, path10);
2847
+ const { main: _, ...options } = await importEntrypointFile(path10, config);
2848
+ if (options == null) {
2849
+ throw Error(
2850
+ `${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`
2851
+ );
2852
+ }
2826
2853
  return {
2827
- name: "wxt:noop-background",
2828
- resolveId(id) {
2829
- if (id === virtualModuleId)
2830
- return resolvedVirtualModuleId;
2831
- },
2832
- load(id) {
2833
- if (id === resolvedVirtualModuleId) {
2834
- return `import { defineBackground } from 'wxt/client';
2835
- export default defineBackground(() => void 0)`;
2836
- }
2837
- }
2854
+ type: "content-script",
2855
+ name,
2856
+ inputPath: path10,
2857
+ outputDir: (0, import_path2.resolve)(config.outDir, CONTENT_SCRIPT_OUT_DIR),
2858
+ options
2838
2859
  };
2839
2860
  }
2840
- var VIRTUAL_NOOP_BACKGROUND_MODULE_ID = "virtual:user-background";
2861
+ var PATH_GLOB_TO_TYPE_MAP = {
2862
+ "sandbox.html": "sandbox",
2863
+ "sandbox/index.html": "sandbox",
2864
+ "*.sandbox.html": "sandbox",
2865
+ "*.sandbox/index.html": "sandbox",
2866
+ "bookmarks.html": "bookmarks",
2867
+ "bookmarks/index.html": "bookmarks",
2868
+ "history.html": "history",
2869
+ "history/index.html": "history",
2870
+ "newtab.html": "newtab",
2871
+ "newtab/index.html": "newtab",
2872
+ "sidepanel.html": "sidepanel",
2873
+ "sidepanel/index.html": "sidepanel",
2874
+ "*.sidepanel.html": "sidepanel",
2875
+ "*.sidepanel/index.html": "sidepanel",
2876
+ "devtools.html": "devtools",
2877
+ "devtools/index.html": "devtools",
2878
+ "background.[jt]s": "background",
2879
+ "background/index.[jt]s": "background",
2880
+ [VIRTUAL_NOOP_BACKGROUND_MODULE_ID]: "background",
2881
+ "content.[jt]s?(x)": "content-script",
2882
+ "content/index.[jt]s?(x)": "content-script",
2883
+ "*.content.[jt]s?(x)": "content-script",
2884
+ "*.content/index.[jt]s?(x)": "content-script",
2885
+ [`content.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
2886
+ [`*.content.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
2887
+ [`content/index.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
2888
+ [`*.content/index.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
2889
+ "popup.html": "popup",
2890
+ "popup/index.html": "popup",
2891
+ "options.html": "options",
2892
+ "options/index.html": "options",
2893
+ "*.html": "unlisted-page",
2894
+ "*/index.html": "unlisted-page",
2895
+ "*.[jt]s": "unlisted-script",
2896
+ "*/index.ts": "unlisted-script",
2897
+ [`*.${CSS_EXTENSIONS_PATTERN}`]: "unlisted-style",
2898
+ [`*/index.${CSS_EXTENSIONS_PATTERN}`]: "unlisted-style",
2899
+ // Don't warn about any files in subdirectories, like CSS or JS entrypoints for HTML files or tests
2900
+ "*/**": "ignored"
2901
+ };
2902
+ var CONTENT_SCRIPT_OUT_DIR = "content-scripts";
2841
2903
 
2842
- // src/core/vite-plugins/cssEntrypoints.ts
2843
- function cssEntrypoints(entrypoint, config) {
2844
- return {
2845
- name: "wxt:css-entrypoint",
2846
- config() {
2847
- return {
2848
- build: {
2849
- rollupOptions: {
2850
- output: {
2851
- assetFileNames: () => getEntrypointBundlePath(entrypoint, config.outDir, ".css")
2852
- }
2853
- }
2854
- }
2855
- };
2856
- },
2857
- generateBundle(_, bundle) {
2858
- Object.keys(bundle).forEach((file) => {
2859
- if (file.endsWith(".js"))
2860
- delete bundle[file];
2861
- });
2862
- }
2863
- };
2864
- }
2904
+ // src/core/utils/building/generate-wxt-dir.ts
2905
+ var import_unimport = require("unimport");
2906
+ var import_fs_extra4 = __toESM(require("fs-extra"), 1);
2907
+ var import_path3 = require("path");
2865
2908
 
2866
- // src/core/vite-plugins/bundleAnalysis.ts
2867
- var import_rollup_plugin_visualizer = require("rollup-plugin-visualizer");
2868
- var increment = 0;
2869
- function bundleAnalysis() {
2870
- return (0, import_rollup_plugin_visualizer.visualizer)({
2871
- emitFile: true,
2872
- template: "raw-data",
2873
- filename: `stats-${increment++}.json`
2874
- });
2909
+ // src/core/utils/unimport.ts
2910
+ var import_defu = require("defu");
2911
+ function getUnimportOptions(config) {
2912
+ if (config.imports === false)
2913
+ return false;
2914
+ const defaultOptions = {
2915
+ debugLog: config.logger.debug,
2916
+ imports: [
2917
+ { name: "defineConfig", from: "wxt" },
2918
+ { name: "fakeBrowser", from: "wxt/testing" }
2919
+ ],
2920
+ presets: [
2921
+ { package: "wxt/client" },
2922
+ { package: "wxt/browser" },
2923
+ { package: "wxt/sandbox" },
2924
+ { package: "wxt/storage" }
2925
+ ],
2926
+ warn: config.logger.warn,
2927
+ dirs: ["components", "composables", "hooks", "utils"]
2928
+ };
2929
+ return (0, import_defu.defu)(config.imports, defaultOptions);
2875
2930
  }
2876
2931
 
2877
2932
  // src/core/utils/globals.ts
@@ -2919,7 +2974,7 @@ function getGlobals(config) {
2919
2974
  }
2920
2975
  ];
2921
2976
  }
2922
- function getEntrypointGlobals(config, entrypointName) {
2977
+ function getEntrypointGlobals(entrypointName) {
2923
2978
  return [
2924
2979
  {
2925
2980
  name: surroundInUnderscore("ENTRYPOINT"),
@@ -2932,859 +2987,902 @@ function surroundInUnderscore(name) {
2932
2987
  return `__${name}__`;
2933
2988
  }
2934
2989
 
2935
- // src/core/vite-plugins/globals.ts
2936
- function globals(config) {
2937
- return {
2938
- name: "wxt:globals",
2939
- config() {
2940
- const define = {};
2941
- for (const global3 of getGlobals(config)) {
2942
- define[global3.name] = JSON.stringify(global3.value);
2943
- }
2944
- return {
2945
- define
2946
- };
2947
- }
2948
- };
2949
- }
2950
-
2951
- // src/core/vite-plugins/webextensionPolyfillAlias.ts
2952
- var import_node_path4 = __toESM(require("path"), 1);
2990
+ // src/core/utils/building/generate-wxt-dir.ts
2991
+ var import_node_path3 = __toESM(require("path"), 1);
2953
2992
 
2954
- // src/core/vite-plugins/excludeBrowserPolyfill.ts
2955
- function excludeBrowserPolyfill(config) {
2956
- const virtualId = "virtual:wxt-webextension-polyfill-disabled";
2957
- return {
2958
- name: "wxt:exclude-browser-polyfill",
2959
- config() {
2960
- if (config.experimental.includeBrowserPolyfill)
2961
- return;
2962
- return {
2963
- resolve: {
2964
- alias: {
2965
- "webextension-polyfill": virtualId
2966
- }
2967
- }
2968
- };
2969
- },
2970
- load(id) {
2971
- if (id === virtualId) {
2972
- return "export default chrome";
2973
- }
2974
- }
2975
- };
2993
+ // src/core/utils/i18n.ts
2994
+ var predefinedMessages = {
2995
+ "@@extension_id": {
2996
+ message: "<browser.runtime.id>",
2997
+ description: "The extension or app ID; you might use this string to construct URLs for resources inside the extension. Even unlocalized extensions can use this message.\nNote: You can't use this message in a manifest file."
2998
+ },
2999
+ "@@ui_locale": {
3000
+ message: "<browser.i18n.getUiLocale()>",
3001
+ description: ""
3002
+ },
3003
+ "@@bidi_dir": {
3004
+ message: "<ltr|rtl>",
3005
+ description: 'The text direction for the current locale, either "ltr" for left-to-right languages such as English or "rtl" for right-to-left languages such as Japanese.'
3006
+ },
3007
+ "@@bidi_reversed_dir": {
3008
+ message: "<rtl|ltr>",
3009
+ description: `If the @@bidi_dir is "ltr", then this is "rtl"; otherwise, it's "ltr".`
3010
+ },
3011
+ "@@bidi_start_edge": {
3012
+ message: "<left|right>",
3013
+ description: `If the @@bidi_dir is "ltr", then this is "left"; otherwise, it's "right".`
3014
+ },
3015
+ "@@bidi_end_edge": {
3016
+ message: "<right|left>",
3017
+ description: `If the @@bidi_dir is "ltr", then this is "right"; otherwise, it's "left".`
3018
+ }
3019
+ };
3020
+ function parseI18nMessages(messagesJson) {
3021
+ return Object.entries({
3022
+ ...predefinedMessages,
3023
+ ...messagesJson
3024
+ }).map(([name, details]) => ({
3025
+ name,
3026
+ ...details
3027
+ }));
2976
3028
  }
2977
3029
 
2978
- // src/core/utils/fs.ts
2979
- var import_fs_extra3 = __toESM(require("fs-extra"), 1);
2980
- var import_fast_glob = __toESM(require("fast-glob"), 1);
2981
- var import_node_path5 = __toESM(require("path"), 1);
2982
- async function writeFileIfDifferent(file, newContents) {
2983
- const existingContents = await import_fs_extra3.default.readFile(file, "utf-8").catch(() => void 0);
2984
- if (existingContents !== newContents) {
2985
- await import_fs_extra3.default.writeFile(file, newContents);
3030
+ // src/core/utils/building/generate-wxt-dir.ts
3031
+ async function generateTypesDir(entrypoints, config) {
3032
+ await import_fs_extra4.default.ensureDir(config.typesDir);
3033
+ const references = [];
3034
+ const imports = getUnimportOptions(config);
3035
+ if (imports !== false) {
3036
+ references.push(await writeImportsDeclarationFile(config, imports));
2986
3037
  }
3038
+ references.push(await writePathsDeclarationFile(entrypoints, config));
3039
+ references.push(await writeI18nDeclarationFile(config));
3040
+ references.push(await writeGlobalsDeclarationFile(config));
3041
+ const mainReference = await writeMainDeclarationFile(references, config);
3042
+ await writeTsConfigFile(mainReference, config);
2987
3043
  }
2988
- async function getPublicFiles(config) {
2989
- if (!await import_fs_extra3.default.exists(config.publicDir))
2990
- return [];
2991
- const files = await (0, import_fast_glob.default)("**/*", { cwd: config.publicDir });
2992
- return files.map(unnormalizePath);
3044
+ async function writeImportsDeclarationFile(config, unimportOptions) {
3045
+ const filePath = (0, import_path3.resolve)(config.typesDir, "imports.d.ts");
3046
+ const unimport2 = (0, import_unimport.createUnimport)(unimportOptions);
3047
+ await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
3048
+ await writeFileIfDifferent(
3049
+ filePath,
3050
+ ["// Generated by wxt", await unimport2.generateTypeDeclarations()].join(
3051
+ "\n"
3052
+ ) + "\n"
3053
+ );
3054
+ return filePath;
2993
3055
  }
2994
- async function removeEmptyDirs(dir) {
2995
- const files = await import_fs_extra3.default.readdir(dir);
2996
- for (const file of files) {
2997
- const filePath = import_node_path5.default.join(dir, file);
2998
- const stats = await import_fs_extra3.default.stat(filePath);
2999
- if (stats.isDirectory()) {
3000
- await removeEmptyDirs(filePath);
3001
- }
3002
- }
3003
- try {
3004
- await import_fs_extra3.default.rmdir(dir);
3005
- } catch {
3056
+ async function writePathsDeclarationFile(entrypoints, config) {
3057
+ const filePath = (0, import_path3.resolve)(config.typesDir, "paths.d.ts");
3058
+ const unions = entrypoints.map(
3059
+ (entry) => getEntrypointBundlePath(
3060
+ entry,
3061
+ config.outDir,
3062
+ entry.inputPath.endsWith(".html") ? ".html" : ".js"
3063
+ )
3064
+ ).concat(await getPublicFiles(config)).map(normalizePath).map((path10) => ` | "/${path10}"`).sort().join("\n");
3065
+ const template = `// Generated by wxt
3066
+ import "wxt/browser";
3067
+
3068
+ declare module "wxt/browser" {
3069
+ export type PublicPath =
3070
+ {{ union }}
3071
+ export interface WxtRuntime extends Runtime.Static {
3072
+ getURL(path: PublicPath): string;
3006
3073
  }
3007
3074
  }
3075
+ `;
3076
+ await writeFileIfDifferent(
3077
+ filePath,
3078
+ template.replace("{{ union }}", unions || " | never")
3079
+ );
3080
+ return filePath;
3081
+ }
3082
+ async function writeI18nDeclarationFile(config) {
3083
+ const filePath = (0, import_path3.resolve)(config.typesDir, "i18n.d.ts");
3084
+ const defaultLocale = config.manifest.default_locale;
3085
+ const template = `// Generated by wxt
3086
+ import "wxt/browser";
3008
3087
 
3009
- // src/core/utils/building/build-entrypoints.ts
3010
- var import_fs_extra4 = __toESM(require("fs-extra"), 1);
3011
- var import_path4 = require("path");
3012
- var import_picocolors = __toESM(require("picocolors"), 1);
3013
- async function buildEntrypoints(groups, config, spinner) {
3014
- const steps = [];
3015
- for (let i = 0; i < groups.length; i++) {
3016
- const group = groups[i];
3017
- spinner.text = import_picocolors.default.dim(`[${i + 1}/${groups.length}]`) + ` ${[group].flat().map((e) => e.name).join(import_picocolors.default.dim(", "))}`;
3018
- const step = Array.isArray(group) ? await buildMultipleEntrypoints(group, config) : await buildSingleEntrypoint(group, config);
3019
- steps.push(step);
3088
+ declare module "wxt/browser" {
3089
+ /**
3090
+ * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
3091
+ */
3092
+ interface GetMessageOptions {
3093
+ /**
3094
+ * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
3095
+ */
3096
+ escapeLt?: boolean
3020
3097
  }
3021
- const publicAssets = await copyPublicDirectory(config);
3022
- await removeEmptyDirs(config.outDir);
3023
- return { publicAssets, steps };
3024
- }
3025
- async function buildSingleEntrypoint(entrypoint, config) {
3026
- const isVirtual = [
3027
- "background",
3028
- "content-script",
3029
- "unlisted-script"
3030
- ].includes(entrypoint.type);
3031
- const entry = isVirtual ? `virtual:wxt-${entrypoint.type}?${entrypoint.inputPath}` : entrypoint.inputPath;
3032
- const plugins = [];
3033
- if (entrypoint.type === "content-script-style" || entrypoint.type === "unlisted-style") {
3034
- plugins.push(cssEntrypoints(entrypoint, config));
3098
+
3099
+ export interface WxtI18n extends I18n.Static {
3100
+ {{ overrides }}
3035
3101
  }
3036
- const libMode = {
3037
- mode: config.mode,
3038
- plugins,
3039
- build: {
3040
- lib: {
3041
- entry,
3042
- formats: ["iife"],
3043
- name: "_",
3044
- fileName: entrypoint.name
3045
- },
3046
- rollupOptions: {
3047
- output: {
3048
- // There's only a single output for this build, so we use the desired bundle path for the
3049
- // entry output (like "content-scripts/overlay.js")
3050
- entryFileNames: getEntrypointBundlePath(
3051
- entrypoint,
3052
- config.outDir,
3053
- ".js"
3054
- ),
3055
- // Output content script CSS to `content-scripts/`, but all other scripts are written to
3056
- // `assets/`.
3057
- assetFileNames: ({ name }) => {
3058
- if (entrypoint.type === "content-script" && name?.endsWith("css")) {
3059
- return `content-scripts/${entrypoint.name}.[ext]`;
3060
- } else {
3061
- return `assets/${entrypoint.name}.[ext]`;
3062
- }
3063
- }
3064
- }
3065
- }
3066
- },
3067
- define: {
3068
- // See https://github.com/aklinker1/vite-plugin-web-extension/issues/96
3069
- "process.env.NODE_ENV": JSON.stringify(config.mode)
3070
- }
3071
- };
3072
- for (const global3 of getEntrypointGlobals(config, entrypoint.name)) {
3073
- libMode.define[global3.name] = JSON.stringify(global3.value);
3102
+ }
3103
+ `;
3104
+ let messages;
3105
+ if (defaultLocale) {
3106
+ const defaultLocalePath = import_node_path3.default.resolve(
3107
+ config.publicDir,
3108
+ "_locales",
3109
+ defaultLocale,
3110
+ "messages.json"
3111
+ );
3112
+ const content = JSON.parse(await import_fs_extra4.default.readFile(defaultLocalePath, "utf-8"));
3113
+ messages = parseI18nMessages(content);
3114
+ } else {
3115
+ messages = parseI18nMessages({});
3074
3116
  }
3075
- const entryConfig = vite.mergeConfig(
3076
- libMode,
3077
- await config.vite(config.env)
3117
+ const overrides = messages.map((message) => {
3118
+ return ` /**
3119
+ * ${message.description ?? "No message description."}
3120
+ *
3121
+ * "${message.message}"
3122
+ */
3123
+ getMessage(
3124
+ messageName: "${message.name}",
3125
+ substitutions?: string | string[],
3126
+ options?: GetMessageOptions,
3127
+ ): string;`;
3128
+ });
3129
+ await writeFileIfDifferent(
3130
+ filePath,
3131
+ template.replace("{{ overrides }}", overrides.join("\n"))
3078
3132
  );
3079
- const result = await vite.build(entryConfig);
3080
- return {
3081
- entrypoints: entrypoint,
3082
- chunks: getBuildOutputChunks(result)
3083
- };
3133
+ return filePath;
3084
3134
  }
3085
- async function buildMultipleEntrypoints(entrypoints, config) {
3086
- const multiPage = {
3087
- mode: config.mode,
3088
- plugins: [multipageMove(entrypoints, config)],
3089
- build: {
3090
- rollupOptions: {
3091
- input: entrypoints.reduce((input, entry) => {
3092
- input[entry.name] = entry.inputPath;
3093
- return input;
3094
- }, {}),
3095
- output: {
3096
- // Include a hash to prevent conflicts
3097
- chunkFileNames: "chunks/[name]-[hash].js",
3098
- // Include a hash to prevent conflicts
3099
- entryFileNames: "chunks/[name]-[hash].js",
3100
- // We can't control the "name", so we need a hash to prevent conflicts
3101
- assetFileNames: "assets/[name]-[hash].[ext]"
3102
- }
3103
- }
3104
- },
3105
- define: {}
3106
- };
3107
- for (const global3 of getEntrypointGlobals(config, "html")) {
3108
- multiPage.define[global3.name] = JSON.stringify(global3.value);
3109
- }
3110
- const entryConfig = vite.mergeConfig(
3111
- multiPage,
3112
- await config.vite(config.env)
3135
+ async function writeGlobalsDeclarationFile(config) {
3136
+ const filePath = (0, import_path3.resolve)(config.typesDir, "globals.d.ts");
3137
+ const globals2 = [...getGlobals(config), ...getEntrypointGlobals("")];
3138
+ await writeFileIfDifferent(
3139
+ filePath,
3140
+ [
3141
+ "// Generated by wxt",
3142
+ "export {}",
3143
+ "declare global {",
3144
+ ...globals2.map((global3) => ` const ${global3.name}: ${global3.type};`),
3145
+ "}"
3146
+ ].join("\n") + "\n"
3113
3147
  );
3114
- const result = await vite.build(entryConfig);
3115
- return {
3116
- entrypoints,
3117
- chunks: getBuildOutputChunks(result)
3118
- };
3148
+ return filePath;
3119
3149
  }
3120
- function getBuildOutputChunks(result) {
3121
- if ("on" in result)
3122
- throw Error("wxt does not support vite watch mode.");
3123
- if (Array.isArray(result))
3124
- return result.flatMap(({ output }) => output);
3125
- return result.output;
3150
+ async function writeMainDeclarationFile(references, config) {
3151
+ const dir = config.wxtDir;
3152
+ const filePath = (0, import_path3.resolve)(dir, "wxt.d.ts");
3153
+ await writeFileIfDifferent(
3154
+ filePath,
3155
+ [
3156
+ "// Generated by wxt",
3157
+ `/// <reference types="vite/client" />`,
3158
+ ...references.map(
3159
+ (ref) => `/// <reference types="./${normalizePath((0, import_path3.relative)(dir, ref))}" />`
3160
+ )
3161
+ ].join("\n") + "\n"
3162
+ );
3163
+ return filePath;
3126
3164
  }
3127
- async function copyPublicDirectory(config) {
3128
- const files = await getPublicFiles(config);
3129
- if (files.length === 0)
3130
- return [];
3131
- const publicAssets = [];
3132
- for (const file of files) {
3133
- const srcPath = (0, import_path4.resolve)(config.publicDir, file);
3134
- const outPath = (0, import_path4.resolve)(config.outDir, file);
3135
- await import_fs_extra4.default.ensureDir((0, import_path4.dirname)(outPath));
3136
- await import_fs_extra4.default.copyFile(srcPath, outPath);
3137
- publicAssets.push({
3138
- type: "asset",
3139
- fileName: file,
3140
- name: file,
3141
- needsCodeReference: false,
3142
- source: await import_fs_extra4.default.readFile(srcPath)
3143
- });
3144
- }
3145
- return publicAssets;
3165
+ async function writeTsConfigFile(mainReference, config) {
3166
+ const dir = config.wxtDir;
3167
+ const getTsconfigPath = (path10) => normalizePath((0, import_path3.relative)(dir, path10));
3168
+ const paths = Object.entries(config.alias).flatMap(([alias, absolutePath]) => {
3169
+ const aliasPath = getTsconfigPath(absolutePath);
3170
+ return [
3171
+ ` "${alias}": ["${aliasPath}"]`,
3172
+ ` "${alias}/*": ["${aliasPath}/*"]`
3173
+ ];
3174
+ }).join(",\n");
3175
+ await writeFileIfDifferent(
3176
+ (0, import_path3.resolve)(dir, "tsconfig.json"),
3177
+ `{
3178
+ "compilerOptions": {
3179
+ "target": "ESNext",
3180
+ "module": "ESNext",
3181
+ "moduleResolution": "Bundler",
3182
+ "noEmit": true,
3183
+ "esModuleInterop": true,
3184
+ "forceConsistentCasingInFileNames": true,
3185
+ "resolveJsonModule": true,
3186
+ "strict": true,
3187
+ "skipLibCheck": true,
3188
+ "paths": {
3189
+ ${paths}
3190
+ }
3191
+ },
3192
+ "include": [
3193
+ "${getTsconfigPath(config.root)}/**/*",
3194
+ "./${getTsconfigPath(mainReference)}"
3195
+ ],
3196
+ "exclude": ["${getTsconfigPath(config.outBaseDir)}"]
3197
+ }`
3198
+ );
3146
3199
  }
3147
3200
 
3148
- // src/core/utils/arrays.ts
3149
- function every(array, predicate) {
3150
- for (let i = 0; i < array.length; i++)
3151
- if (!predicate(array[i], i))
3152
- return false;
3153
- return true;
3154
- }
3201
+ // src/core/utils/building/get-internal-config.ts
3202
+ var import_c12 = require("c12");
3203
+ var import_node_path7 = __toESM(require("path"), 1);
3155
3204
 
3156
- // src/core/utils/building/detect-dev-changes.ts
3157
- function detectDevChanges(changedFiles, currentOutput) {
3158
- if (currentOutput == null)
3159
- return { type: "no-change" };
3160
- const changedSteps = new Set(
3161
- changedFiles.flatMap(
3162
- (changedFile) => findEffectedSteps(changedFile, currentOutput)
3163
- )
3164
- );
3165
- if (changedSteps.size === 0)
3166
- return { type: "no-change" };
3167
- const unchangedOutput = {
3168
- manifest: currentOutput.manifest,
3169
- steps: [],
3170
- publicAssets: []
3171
- };
3172
- const changedOutput = {
3173
- manifest: currentOutput.manifest,
3174
- steps: [],
3175
- publicAssets: []
3176
- };
3177
- for (const step of currentOutput.steps) {
3178
- if (changedSteps.has(step)) {
3179
- changedOutput.steps.push(step);
3180
- } else {
3181
- unchangedOutput.steps.push(step);
3182
- }
3183
- }
3184
- for (const asset of currentOutput.publicAssets) {
3185
- if (changedSteps.has(asset)) {
3186
- changedOutput.publicAssets.push(asset);
3187
- } else {
3188
- unchangedOutput.publicAssets.push(asset);
3189
- }
3190
- }
3191
- const isOnlyHtmlChanges = changedFiles.length > 0 && every(changedFiles, ([_, file]) => file.endsWith(".html"));
3192
- if (isOnlyHtmlChanges) {
3193
- return {
3194
- type: "html-reload",
3195
- cachedOutput: unchangedOutput,
3196
- rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
3197
- };
3198
- }
3199
- const isOnlyContentScripts = changedOutput.steps.length > 0 && every(
3200
- changedOutput.steps.flatMap((step) => step.entrypoints),
3201
- (entry) => entry.type === "content-script"
3202
- );
3203
- if (isOnlyContentScripts) {
3204
- return {
3205
- type: "content-script-reload",
3206
- cachedOutput: unchangedOutput,
3207
- changedSteps: changedOutput.steps,
3208
- rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
3209
- };
3210
- }
3211
- return {
3212
- type: "extension-reload",
3213
- cachedOutput: unchangedOutput,
3214
- rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
3215
- };
3216
- }
3217
- function findEffectedSteps(changedFile, currentOutput) {
3218
- const changes = [];
3219
- const changedPath = normalizePath(changedFile[1]);
3220
- const isChunkEffected = (chunk) => (
3221
- // If it's an HTML file with the same path, is is effected because HTML files need to be pre-rendered
3222
- // fileName is normalized, relative bundle path
3223
- chunk.type === "asset" && changedPath.endsWith(chunk.fileName) || // If it's a chunk that depends on the changed file, it is effected
3224
- // moduleIds are absolute, normalized paths
3225
- chunk.type === "chunk" && chunk.moduleIds.includes(changedPath)
3226
- );
3227
- for (const step of currentOutput.steps) {
3228
- const effectedChunk = step.chunks.find((chunk) => isChunkEffected(chunk));
3229
- if (effectedChunk)
3230
- changes.push(step);
3231
- }
3232
- const effectedAsset = currentOutput.publicAssets.find(
3233
- (chunk) => isChunkEffected(chunk)
3234
- );
3235
- if (effectedAsset)
3236
- changes.push(effectedAsset);
3237
- return changes;
3238
- }
3239
-
3240
- // src/core/utils/building/find-entrypoints.ts
3241
- var import_path5 = require("path");
3205
+ // src/core/utils/cache.ts
3242
3206
  var import_fs_extra5 = __toESM(require("fs-extra"), 1);
3243
- var import_minimatch = require("minimatch");
3244
- var import_linkedom2 = require("linkedom");
3245
- var import_json5 = __toESM(require("json5"), 1);
3246
- var import_fast_glob2 = __toESM(require("fast-glob"), 1);
3247
- async function findEntrypoints(config) {
3248
- const relativePaths = await (0, import_fast_glob2.default)("**/*", {
3249
- cwd: config.entrypointsDir
3250
- });
3251
- relativePaths.sort();
3252
- const pathGlobs = Object.keys(PATH_GLOB_TO_TYPE_MAP);
3253
- let hasBackground = false;
3254
- const possibleEntrypoints = await Promise.all(
3255
- relativePaths.map(async (relativePath) => {
3256
- const path11 = (0, import_path5.resolve)(config.entrypointsDir, relativePath);
3257
- const matchingGlob = pathGlobs.find(
3258
- (glob5) => (0, import_minimatch.minimatch)(relativePath, glob5)
3259
- );
3260
- if (matchingGlob == null) {
3261
- config.logger.warn(
3262
- `${relativePath} does not match any known entrypoint. Known entrypoints:
3263
- ${JSON.stringify(
3264
- PATH_GLOB_TO_TYPE_MAP,
3265
- null,
3266
- 2
3267
- )}`
3268
- );
3269
- return;
3270
- }
3271
- const type = PATH_GLOB_TO_TYPE_MAP[matchingGlob];
3272
- if (type === "ignored")
3273
- return;
3274
- switch (type) {
3275
- case "popup":
3276
- return await getPopupEntrypoint(config, path11);
3277
- case "options":
3278
- return await getOptionsEntrypoint(config, path11);
3279
- case "background":
3280
- hasBackground = true;
3281
- return await getBackgroundEntrypoint(config, path11);
3282
- case "content-script":
3283
- return await getContentScriptEntrypoint(config, path11);
3284
- case "unlisted-page":
3285
- return await getUnlistedPageEntrypoint(config, path11);
3286
- case "unlisted-script":
3287
- return await getUnlistedScriptEntrypoint(config, path11);
3288
- case "content-script-style":
3289
- return {
3290
- type,
3291
- name: getEntrypointName(config.entrypointsDir, path11),
3292
- inputPath: path11,
3293
- outputDir: (0, import_path5.resolve)(config.outDir, CONTENT_SCRIPT_OUT_DIR),
3294
- options: {
3295
- include: void 0,
3296
- exclude: void 0
3297
- }
3298
- };
3299
- default:
3300
- return {
3301
- type,
3302
- name: getEntrypointName(config.entrypointsDir, path11),
3303
- inputPath: path11,
3304
- outputDir: config.outDir,
3305
- options: {
3306
- include: void 0,
3307
- exclude: void 0
3308
- }
3309
- };
3310
- }
3311
- })
3312
- );
3313
- const entrypoints = possibleEntrypoints.filter(
3314
- (entry) => !!entry
3315
- );
3316
- const existingNames = {};
3317
- entrypoints.forEach((entrypoint) => {
3318
- const withSameName = existingNames[entrypoint.name];
3319
- if (withSameName) {
3320
- throw Error(
3321
- `Multiple entrypoints with the name "${entrypoint.name}" detected, but only one is allowed: ${[
3322
- (0, import_path5.relative)(config.root, withSameName.inputPath),
3323
- (0, import_path5.relative)(config.root, entrypoint.inputPath)
3324
- ].join(", ")}`
3325
- );
3326
- }
3327
- existingNames[entrypoint.name] = entrypoint;
3328
- });
3329
- if (config.command === "serve" && !hasBackground) {
3330
- entrypoints.push(
3331
- await getBackgroundEntrypoint(config, VIRTUAL_NOOP_BACKGROUND_MODULE_ID)
3332
- );
3333
- }
3334
- config.logger.debug("All entrypoints:", entrypoints);
3335
- const targetEntrypoints = entrypoints.filter((entry) => {
3336
- const { include, exclude } = entry.options;
3337
- if (include?.length && exclude?.length) {
3338
- config.logger.warn(
3339
- `The ${entry.name} entrypoint lists both include and exclude, but only one can be used per entrypoint. Entrypoint ignored.`
3340
- );
3341
- return false;
3342
- }
3343
- if (exclude?.length && !include?.length) {
3344
- return !exclude.includes(config.browser);
3345
- }
3346
- if (include?.length && !exclude?.length) {
3347
- return include.includes(config.browser);
3348
- }
3349
- return true;
3350
- });
3351
- config.logger.debug(`${config.browser} entrypoints:`, targetEntrypoints);
3352
- return targetEntrypoints;
3353
- }
3354
- function getHtmlBaseOptions(document) {
3355
- const options = {};
3356
- const includeContent = document.querySelector("meta[name='manifest.include']")?.getAttribute("content");
3357
- if (includeContent) {
3358
- options.include = import_json5.default.parse(includeContent);
3359
- }
3360
- const excludeContent = document.querySelector("meta[name='manifest.exclude']")?.getAttribute("content");
3361
- if (excludeContent) {
3362
- options.exclude = import_json5.default.parse(excludeContent);
3363
- }
3364
- return options;
3365
- }
3366
- async function getPopupEntrypoint(config, path11) {
3367
- const content = await import_fs_extra5.default.readFile(path11, "utf-8");
3368
- const { document } = (0, import_linkedom2.parseHTML)(content);
3369
- const options = getHtmlBaseOptions(document);
3370
- const title = document.querySelector("title");
3371
- if (title != null)
3372
- options.defaultTitle = title.textContent ?? void 0;
3373
- const defaultIconContent = document.querySelector("meta[name='manifest.default_icon']")?.getAttribute("content");
3374
- if (defaultIconContent) {
3375
- try {
3376
- options.defaultIcon = import_json5.default.parse(defaultIconContent);
3377
- } catch (err) {
3378
- config.logger.fatal(
3379
- `Failed to parse default_icon meta tag content as JSON5. content=${defaultIconContent}`,
3380
- err
3381
- );
3382
- }
3383
- }
3384
- const mv2TypeContent = document.querySelector("meta[name='manifest.type']")?.getAttribute("content");
3385
- if (mv2TypeContent) {
3386
- options.mv2Key = mv2TypeContent === "page_action" ? "page_action" : "browser_action";
3387
- }
3388
- const browserStyleContent = document.querySelector("meta[name='manifest.browser_style']")?.getAttribute("content");
3389
- if (browserStyleContent) {
3390
- options.browserStyle = browserStyleContent === "true";
3391
- }
3392
- return {
3393
- type: "popup",
3394
- name: "popup",
3395
- options,
3396
- inputPath: path11,
3397
- outputDir: config.outDir
3398
- };
3399
- }
3400
- async function getOptionsEntrypoint(config, path11) {
3401
- const content = await import_fs_extra5.default.readFile(path11, "utf-8");
3402
- const { document } = (0, import_linkedom2.parseHTML)(content);
3403
- const options = getHtmlBaseOptions(document);
3404
- const openInTabContent = document.querySelector("meta[name='manifest.open_in_tab']")?.getAttribute("content");
3405
- if (openInTabContent) {
3406
- options.openInTab = openInTabContent === "true";
3407
- }
3408
- const chromeStyleContent = document.querySelector("meta[name='manifest.chrome_style']")?.getAttribute("content");
3409
- if (chromeStyleContent) {
3410
- options.chromeStyle = chromeStyleContent === "true";
3411
- }
3412
- const browserStyleContent = document.querySelector("meta[name='manifest.browser_style']")?.getAttribute("content");
3413
- if (browserStyleContent) {
3414
- options.browserStyle = browserStyleContent === "true";
3415
- }
3416
- return {
3417
- type: "options",
3418
- name: "options",
3419
- options,
3420
- inputPath: path11,
3421
- outputDir: config.outDir
3422
- };
3423
- }
3424
- async function getUnlistedPageEntrypoint(config, path11) {
3425
- const content = await import_fs_extra5.default.readFile(path11, "utf-8");
3426
- const { document } = (0, import_linkedom2.parseHTML)(content);
3427
- return {
3428
- type: "unlisted-page",
3429
- name: getEntrypointName(config.entrypointsDir, path11),
3430
- inputPath: path11,
3431
- outputDir: config.outDir,
3432
- options: getHtmlBaseOptions(document)
3433
- };
3434
- }
3435
- async function getUnlistedScriptEntrypoint(config, path11) {
3436
- const name = getEntrypointName(config.entrypointsDir, path11);
3437
- const defaultExport = await importEntrypointFile(
3438
- path11,
3439
- config
3440
- );
3441
- if (defaultExport == null) {
3442
- throw Error(
3443
- `${name}: Default export not found, did you forget to call "export default defineUnlistedScript(...)"?`
3444
- );
3445
- }
3446
- const { main: _, ...moduleOptions } = defaultExport;
3447
- const options = moduleOptions;
3448
- return {
3449
- type: "unlisted-script",
3450
- name,
3451
- inputPath: path11,
3452
- outputDir: config.outDir,
3453
- options
3454
- };
3455
- }
3456
- async function getBackgroundEntrypoint(config, path11) {
3457
- const name = "background";
3458
- let options = {};
3459
- if (path11 !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) {
3460
- const defaultExport = await importEntrypointFile(
3461
- path11,
3462
- config
3463
- );
3464
- if (defaultExport == null) {
3465
- throw Error(
3466
- `${name}: Default export not found, did you forget to call "export default defineBackground(...)"?`
3467
- );
3468
- }
3469
- const { main: _, ...moduleOptions } = defaultExport;
3470
- options = moduleOptions;
3471
- }
3207
+ var import_path4 = require("path");
3208
+ function createFsCache(wxtDir) {
3209
+ const getPath = (key) => (0, import_path4.resolve)(wxtDir, "cache", encodeURIComponent(key));
3472
3210
  return {
3473
- type: "background",
3474
- name,
3475
- inputPath: path11,
3476
- outputDir: config.outDir,
3477
- options: {
3478
- ...options,
3479
- type: resolvePerBrowserOption(options.type, config.browser),
3480
- persistent: resolvePerBrowserOption(options.persistent, config.browser)
3211
+ async set(key, value) {
3212
+ const path10 = getPath(key);
3213
+ await (0, import_fs_extra5.ensureDir)((0, import_path4.dirname)(path10));
3214
+ await writeFileIfDifferent(path10, value);
3215
+ },
3216
+ async get(key) {
3217
+ const path10 = getPath(key);
3218
+ try {
3219
+ return await import_fs_extra5.default.readFile(path10, "utf-8");
3220
+ } catch {
3221
+ return void 0;
3222
+ }
3481
3223
  }
3482
3224
  };
3483
3225
  }
3484
- async function getContentScriptEntrypoint(config, path11) {
3485
- const name = getEntrypointName(config.entrypointsDir, path11);
3486
- const { main: _, ...options } = await importEntrypointFile(path11, config);
3487
- if (options == null) {
3488
- throw Error(
3489
- `${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`
3490
- );
3491
- }
3226
+
3227
+ // src/core/utils/building/get-internal-config.ts
3228
+ var import_consola = __toESM(require("consola"), 1);
3229
+
3230
+ // src/core/builders/vite/plugins/devHtmlPrerender.ts
3231
+ var import_linkedom2 = require("linkedom");
3232
+ var import_node_path4 = require("path");
3233
+ var reactRefreshPreamble = "";
3234
+ function devHtmlPrerender(config) {
3235
+ const htmlReloadId = "@wxt/reload-html";
3236
+ const resolvedHtmlReloadId = (0, import_node_path4.resolve)(
3237
+ config.root,
3238
+ "node_modules/wxt/dist/virtual/reload-html.js"
3239
+ );
3240
+ const virtualReactRefreshId = "@wxt/virtual-react-refresh";
3241
+ const resolvedVirtualReactRefreshId = "\0" + virtualReactRefreshId;
3242
+ return [
3243
+ {
3244
+ apply: "build",
3245
+ name: "wxt:dev-html-prerender",
3246
+ config() {
3247
+ return {
3248
+ resolve: {
3249
+ alias: {
3250
+ [htmlReloadId]: resolvedHtmlReloadId
3251
+ }
3252
+ }
3253
+ };
3254
+ },
3255
+ // Convert scripts like src="./main.tsx" -> src="http://localhost:3000/entrypoints/popup/main.tsx"
3256
+ // before the paths are replaced with their bundled path
3257
+ transform(code, id) {
3258
+ const server = config.server;
3259
+ if (config.command !== "serve" || server == null || !id.endsWith(".html"))
3260
+ return;
3261
+ const { document } = (0, import_linkedom2.parseHTML)(code);
3262
+ const pointToDevServer = (querySelector, attr) => {
3263
+ document.querySelectorAll(querySelector).forEach((element) => {
3264
+ const src = element.getAttribute(attr);
3265
+ if (!src)
3266
+ return;
3267
+ if ((0, import_node_path4.isAbsolute)(src)) {
3268
+ element.setAttribute(attr, server.origin + src);
3269
+ } else if (src.startsWith(".")) {
3270
+ const abs = (0, import_node_path4.resolve)((0, import_node_path4.dirname)(id), src);
3271
+ const pathname = (0, import_node_path4.relative)(config.root, abs);
3272
+ element.setAttribute(attr, `${server.origin}/${pathname}`);
3273
+ }
3274
+ });
3275
+ };
3276
+ pointToDevServer("script[type=module]", "src");
3277
+ pointToDevServer("link[rel=stylesheet]", "href");
3278
+ const reloader = document.createElement("script");
3279
+ reloader.src = htmlReloadId;
3280
+ reloader.type = "module";
3281
+ document.head.appendChild(reloader);
3282
+ const newHtml = document.toString();
3283
+ config.logger.debug("transform " + id);
3284
+ config.logger.debug("Old HTML:\n" + code);
3285
+ config.logger.debug("New HTML:\n" + newHtml);
3286
+ return newHtml;
3287
+ },
3288
+ // Pass the HTML through the dev server to add dev-mode specific code
3289
+ async transformIndexHtml(html, ctx) {
3290
+ const server = config.server;
3291
+ if (config.command !== "serve" || server == null)
3292
+ return;
3293
+ const originalUrl = `${server.origin}${ctx.path}`;
3294
+ const name = getEntrypointName(config.entrypointsDir, ctx.filename);
3295
+ const url2 = `${server.origin}/${name}.html`;
3296
+ const serverHtml = await server.transformHtml(url2, html, originalUrl);
3297
+ const { document } = (0, import_linkedom2.parseHTML)(serverHtml);
3298
+ const reactRefreshScript = Array.from(
3299
+ document.querySelectorAll("script[type=module]")
3300
+ ).find((script) => script.innerHTML.includes("@react-refresh"));
3301
+ if (reactRefreshScript) {
3302
+ reactRefreshPreamble = reactRefreshScript.innerHTML;
3303
+ const virtualScript = document.createElement("script");
3304
+ virtualScript.type = "module";
3305
+ virtualScript.src = `${server.origin}/${virtualReactRefreshId}`;
3306
+ reactRefreshScript.replaceWith(virtualScript);
3307
+ }
3308
+ const viteClientScript = document.querySelector(
3309
+ "script[src='/@vite/client']"
3310
+ );
3311
+ if (viteClientScript) {
3312
+ viteClientScript.src = `${server.origin}${viteClientScript.src}`;
3313
+ }
3314
+ const newHtml = document.toString();
3315
+ config.logger.debug("transformIndexHtml " + ctx.filename);
3316
+ config.logger.debug("Old HTML:\n" + html);
3317
+ config.logger.debug("New HTML:\n" + newHtml);
3318
+ return newHtml;
3319
+ }
3320
+ },
3321
+ {
3322
+ name: "wxt:virtualize-react-refresh",
3323
+ apply: "serve",
3324
+ resolveId(id) {
3325
+ if (id === `/${virtualReactRefreshId}`) {
3326
+ return resolvedVirtualReactRefreshId;
3327
+ }
3328
+ if (id.startsWith("/chunks/")) {
3329
+ return "\0noop";
3330
+ }
3331
+ },
3332
+ load(id) {
3333
+ if (id === resolvedVirtualReactRefreshId) {
3334
+ return reactRefreshPreamble;
3335
+ }
3336
+ if (id === "\0noop") {
3337
+ return "";
3338
+ }
3339
+ }
3340
+ }
3341
+ ];
3342
+ }
3343
+
3344
+ // src/core/builders/vite/plugins/devServerGlobals.ts
3345
+ function devServerGlobals(config) {
3492
3346
  return {
3493
- type: "content-script",
3494
- name,
3495
- inputPath: path11,
3496
- outputDir: (0, import_path5.resolve)(config.outDir, CONTENT_SCRIPT_OUT_DIR),
3497
- options
3347
+ name: "wxt:dev-server-globals",
3348
+ config() {
3349
+ if (config.server == null || config.command == "build")
3350
+ return;
3351
+ return {
3352
+ define: {
3353
+ __DEV_SERVER_PROTOCOL__: JSON.stringify("ws:"),
3354
+ __DEV_SERVER_HOSTNAME__: JSON.stringify(config.server.hostname),
3355
+ __DEV_SERVER_PORT__: JSON.stringify(config.server.port)
3356
+ }
3357
+ };
3358
+ }
3498
3359
  };
3499
3360
  }
3500
- var PATH_GLOB_TO_TYPE_MAP = {
3501
- "sandbox.html": "sandbox",
3502
- "sandbox/index.html": "sandbox",
3503
- "*.sandbox.html": "sandbox",
3504
- "*.sandbox/index.html": "sandbox",
3505
- "bookmarks.html": "bookmarks",
3506
- "bookmarks/index.html": "bookmarks",
3507
- "history.html": "history",
3508
- "history/index.html": "history",
3509
- "newtab.html": "newtab",
3510
- "newtab/index.html": "newtab",
3511
- "sidepanel.html": "sidepanel",
3512
- "sidepanel/index.html": "sidepanel",
3513
- "*.sidepanel.html": "sidepanel",
3514
- "*.sidepanel/index.html": "sidepanel",
3515
- "devtools.html": "devtools",
3516
- "devtools/index.html": "devtools",
3517
- "background.[jt]s": "background",
3518
- "background/index.[jt]s": "background",
3519
- [VIRTUAL_NOOP_BACKGROUND_MODULE_ID]: "background",
3520
- "content.[jt]s?(x)": "content-script",
3521
- "content/index.[jt]s?(x)": "content-script",
3522
- "*.content.[jt]s?(x)": "content-script",
3523
- "*.content/index.[jt]s?(x)": "content-script",
3524
- [`content.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
3525
- [`*.content.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
3526
- [`content/index.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
3527
- [`*.content/index.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
3528
- "popup.html": "popup",
3529
- "popup/index.html": "popup",
3530
- "options.html": "options",
3531
- "options/index.html": "options",
3532
- "*.html": "unlisted-page",
3533
- "*/index.html": "unlisted-page",
3534
- "*.[jt]s": "unlisted-script",
3535
- "*/index.ts": "unlisted-script",
3536
- [`*.${CSS_EXTENSIONS_PATTERN}`]: "unlisted-style",
3537
- [`*/index.${CSS_EXTENSIONS_PATTERN}`]: "unlisted-style",
3538
- // Don't warn about any files in subdirectories, like CSS or JS entrypoints for HTML files or tests
3539
- "*/**": "ignored"
3540
- };
3541
- var CONTENT_SCRIPT_OUT_DIR = "content-scripts";
3542
3361
 
3543
- // src/core/utils/building/generate-wxt-dir.ts
3544
- var import_unimport3 = require("unimport");
3545
- var import_fs_extra6 = __toESM(require("fs-extra"), 1);
3546
- var import_path6 = require("path");
3547
- var import_node_path6 = __toESM(require("path"), 1);
3362
+ // src/core/utils/network.ts
3363
+ var import_node_dns = __toESM(require("dns"), 1);
3548
3364
 
3549
- // src/core/utils/i18n.ts
3550
- var predefinedMessages = {
3551
- "@@extension_id": {
3552
- message: "<browser.runtime.id>",
3553
- description: "The extension or app ID; you might use this string to construct URLs for resources inside the extension. Even unlocalized extensions can use this message.\nNote: You can't use this message in a manifest file."
3554
- },
3555
- "@@ui_locale": {
3556
- message: "<browser.i18n.getUiLocale()>",
3557
- description: ""
3558
- },
3559
- "@@bidi_dir": {
3560
- message: "<ltr|rtl>",
3561
- description: 'The text direction for the current locale, either "ltr" for left-to-right languages such as English or "rtl" for right-to-left languages such as Japanese.'
3562
- },
3563
- "@@bidi_reversed_dir": {
3564
- message: "<rtl|ltr>",
3565
- description: `If the @@bidi_dir is "ltr", then this is "rtl"; otherwise, it's "ltr".`
3566
- },
3567
- "@@bidi_start_edge": {
3568
- message: "<left|right>",
3569
- description: `If the @@bidi_dir is "ltr", then this is "left"; otherwise, it's "right".`
3570
- },
3571
- "@@bidi_end_edge": {
3572
- message: "<right|left>",
3573
- description: `If the @@bidi_dir is "ltr", then this is "right"; otherwise, it's "left".`
3574
- }
3575
- };
3576
- function parseI18nMessages(messagesJson) {
3577
- return Object.entries({
3578
- ...predefinedMessages,
3579
- ...messagesJson
3580
- }).map(([name, details]) => ({
3581
- name,
3582
- ...details
3583
- }));
3365
+ // src/core/utils/time.ts
3366
+ function formatDuration(duration) {
3367
+ if (duration < 1e3)
3368
+ return `${duration} ms`;
3369
+ if (duration < 1e4)
3370
+ return `${(duration / 1e3).toFixed(3)} s`;
3371
+ if (duration < 6e4)
3372
+ return `${(duration / 1e3).toFixed(1)} s`;
3373
+ return `${(duration / 1e3).toFixed(0)} s`;
3374
+ }
3375
+ function withTimeout(promise, duration) {
3376
+ return new Promise((res, rej) => {
3377
+ const timeout = setTimeout(() => {
3378
+ rej(`Promise timed out after ${duration}ms`);
3379
+ }, duration);
3380
+ promise.then(res).catch(rej).finally(() => clearTimeout(timeout));
3381
+ });
3382
+ }
3383
+
3384
+ // src/core/utils/network.ts
3385
+ function isOffline() {
3386
+ const isOffline2 = new Promise((res) => {
3387
+ import_node_dns.default.resolve("google.com", (err) => {
3388
+ if (err == null) {
3389
+ res(false);
3390
+ } else {
3391
+ res(true);
3392
+ }
3393
+ });
3394
+ });
3395
+ return withTimeout(isOffline2, 1e3).catch(() => true);
3396
+ }
3397
+ async function isOnline() {
3398
+ const offline = await isOffline();
3399
+ return !offline;
3584
3400
  }
3585
-
3586
- // src/core/utils/building/generate-wxt-dir.ts
3587
- async function generateTypesDir(entrypoints, config) {
3588
- await import_fs_extra6.default.ensureDir(config.typesDir);
3589
- const references = [];
3590
- const imports = getUnimportOptions(config);
3591
- if (imports !== false) {
3592
- references.push(await writeImportsDeclarationFile(config, imports));
3401
+ async function fetchCached(url2, config) {
3402
+ let content = "";
3403
+ if (await isOnline()) {
3404
+ const res = await fetch(url2);
3405
+ if (res.status < 300) {
3406
+ content = await res.text();
3407
+ await config.fsCache.set(url2, content);
3408
+ } else {
3409
+ config.logger.debug(
3410
+ `Failed to download "${url2}", falling back to cache...`
3411
+ );
3412
+ }
3593
3413
  }
3594
- references.push(await writePathsDeclarationFile(entrypoints, config));
3595
- references.push(await writeI18nDeclarationFile(config));
3596
- references.push(await writeGlobalsDeclarationFile(config));
3597
- const mainReference = await writeMainDeclarationFile(references, config);
3598
- await writeTsConfigFile(mainReference, config);
3414
+ if (!content)
3415
+ content = await config.fsCache.get(url2) ?? "";
3416
+ if (!content)
3417
+ throw Error(
3418
+ `Offline and "${url2}" has not been cached. Try again when online.`
3419
+ );
3420
+ return content;
3599
3421
  }
3600
- async function writeImportsDeclarationFile(config, unimportOptions) {
3601
- const filePath = (0, import_path6.resolve)(config.typesDir, "imports.d.ts");
3602
- const unimport2 = (0, import_unimport3.createUnimport)(unimportOptions);
3603
- await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
3604
- await writeFileIfDifferent(
3605
- filePath,
3606
- ["// Generated by wxt", await unimport2.generateTypeDeclarations()].join(
3607
- "\n"
3608
- ) + "\n"
3609
- );
3610
- return filePath;
3422
+
3423
+ // src/core/builders/vite/plugins/download.ts
3424
+ function download(config) {
3425
+ return {
3426
+ name: "wxt:download",
3427
+ resolveId(id) {
3428
+ if (id.startsWith("url:"))
3429
+ return "\0" + id;
3430
+ },
3431
+ async load(id) {
3432
+ if (!id.startsWith("\0url:"))
3433
+ return;
3434
+ const url2 = id.replace("\0url:", "");
3435
+ return await fetchCached(url2, config);
3436
+ }
3437
+ };
3611
3438
  }
3612
- async function writePathsDeclarationFile(entrypoints, config) {
3613
- const filePath = (0, import_path6.resolve)(config.typesDir, "paths.d.ts");
3614
- const unions = entrypoints.map(
3615
- (entry) => getEntrypointBundlePath(
3616
- entry,
3617
- config.outDir,
3618
- entry.inputPath.endsWith(".html") ? ".html" : ".js"
3619
- )
3620
- ).concat(await getPublicFiles(config)).map(normalizePath).map((path11) => ` | "/${path11}"`).sort().join("\n");
3621
- const template = `// Generated by wxt
3622
- import "wxt/browser";
3623
3439
 
3624
- declare module "wxt/browser" {
3625
- export type PublicPath =
3626
- {{ union }}
3627
- export interface WxtRuntime extends Runtime.Static {
3628
- getURL(path: PublicPath): string;
3629
- }
3440
+ // src/core/builders/vite/plugins/multipageMove.ts
3441
+ var import_node_path5 = require("path");
3442
+ var import_fs_extra6 = __toESM(require("fs-extra"), 1);
3443
+ function multipageMove(entrypoints, config) {
3444
+ return {
3445
+ name: "wxt:multipage-move",
3446
+ async writeBundle(_, bundle) {
3447
+ for (const oldBundlePath in bundle) {
3448
+ const entrypoint = entrypoints.find(
3449
+ (entry) => !!normalizePath(entry.inputPath).endsWith(oldBundlePath)
3450
+ );
3451
+ if (entrypoint == null) {
3452
+ config.logger.debug(
3453
+ `No entrypoint found for ${oldBundlePath}, leaving in chunks directory`
3454
+ );
3455
+ continue;
3456
+ }
3457
+ const newBundlePath = getEntrypointBundlePath(
3458
+ entrypoint,
3459
+ config.outDir,
3460
+ (0, import_node_path5.extname)(oldBundlePath)
3461
+ );
3462
+ if (newBundlePath === oldBundlePath) {
3463
+ config.logger.debug(
3464
+ "HTML file is already in the correct location",
3465
+ oldBundlePath
3466
+ );
3467
+ continue;
3468
+ }
3469
+ const oldAbsPath = (0, import_node_path5.resolve)(config.outDir, oldBundlePath);
3470
+ const newAbsPath = (0, import_node_path5.resolve)(config.outDir, newBundlePath);
3471
+ await (0, import_fs_extra6.ensureDir)((0, import_node_path5.dirname)(newAbsPath));
3472
+ await import_fs_extra6.default.move(oldAbsPath, newAbsPath, { overwrite: true });
3473
+ const renamedChunk = {
3474
+ ...bundle[oldBundlePath],
3475
+ fileName: newBundlePath
3476
+ };
3477
+ delete bundle[oldBundlePath];
3478
+ bundle[newBundlePath] = renamedChunk;
3479
+ }
3480
+ removeEmptyDirs(config.outDir);
3481
+ }
3482
+ };
3630
3483
  }
3631
- `;
3632
- await writeFileIfDifferent(
3633
- filePath,
3634
- template.replace("{{ union }}", unions || " | never")
3635
- );
3636
- return filePath;
3484
+ async function removeEmptyDirs(dir) {
3485
+ const files = await import_fs_extra6.default.readdir(dir);
3486
+ for (const file of files) {
3487
+ const filePath = (0, import_node_path5.join)(dir, file);
3488
+ const stats = await import_fs_extra6.default.stat(filePath);
3489
+ if (stats.isDirectory()) {
3490
+ await removeEmptyDirs(filePath);
3491
+ }
3492
+ }
3493
+ try {
3494
+ await import_fs_extra6.default.rmdir(dir);
3495
+ } catch {
3496
+ }
3637
3497
  }
3638
- async function writeI18nDeclarationFile(config) {
3639
- const filePath = (0, import_path6.resolve)(config.typesDir, "i18n.d.ts");
3640
- const defaultLocale = config.manifest.default_locale;
3641
- const template = `// Generated by wxt
3642
- import "wxt/browser";
3643
3498
 
3644
- declare module "wxt/browser" {
3645
- /**
3646
- * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
3647
- */
3648
- interface GetMessageOptions {
3649
- /**
3650
- * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
3651
- */
3652
- escapeLt?: boolean
3653
- }
3499
+ // src/core/builders/vite/plugins/unimport.ts
3500
+ var import_unimport3 = require("unimport");
3501
+ var import_path5 = require("path");
3502
+ var ENABLED_EXTENSIONS = /* @__PURE__ */ new Set([
3503
+ ".js",
3504
+ ".jsx",
3505
+ ".ts",
3506
+ ".tsx",
3507
+ ".vue",
3508
+ ".svelte"
3509
+ ]);
3510
+ function unimport(config) {
3511
+ const options = getUnimportOptions(config);
3512
+ if (options === false)
3513
+ return [];
3514
+ const unimport2 = (0, import_unimport3.createUnimport)(options);
3515
+ return {
3516
+ name: "wxt:unimport",
3517
+ async config() {
3518
+ await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
3519
+ },
3520
+ async transform(code, id) {
3521
+ if (id.includes("node_modules"))
3522
+ return;
3523
+ if (!ENABLED_EXTENSIONS.has((0, import_path5.extname)(id)))
3524
+ return;
3525
+ return unimport2.injectImports(code, id);
3526
+ }
3527
+ };
3528
+ }
3654
3529
 
3655
- export interface WxtI18n extends I18n.Static {
3656
- {{ overrides }}
3657
- }
3530
+ // src/core/builders/vite/plugins/virtualEntrypoint.ts
3531
+ var import_fs_extra7 = __toESM(require("fs-extra"), 1);
3532
+ var import_path6 = require("path");
3533
+ function virtualEntrypoint(type, config) {
3534
+ const virtualId = `virtual:wxt-${type}?`;
3535
+ const resolvedVirtualId = `\0${virtualId}`;
3536
+ return {
3537
+ name: `wxt:virtual-entrypoint`,
3538
+ resolveId(id) {
3539
+ const index = id.indexOf(virtualId);
3540
+ if (index === -1)
3541
+ return;
3542
+ const inputPath = normalizePath(id.substring(index + virtualId.length));
3543
+ return resolvedVirtualId + inputPath;
3544
+ },
3545
+ async load(id) {
3546
+ if (!id.startsWith(resolvedVirtualId))
3547
+ return;
3548
+ const inputPath = id.replace(resolvedVirtualId, "");
3549
+ const template = await import_fs_extra7.default.readFile(
3550
+ (0, import_path6.resolve)(
3551
+ config.root,
3552
+ `node_modules/wxt/dist/virtual/${type}-entrypoint.js`
3553
+ ),
3554
+ "utf-8"
3555
+ );
3556
+ return template.replace(`virtual:user-${type}`, inputPath);
3557
+ }
3558
+ };
3658
3559
  }
3659
- `;
3660
- let messages;
3661
- if (defaultLocale) {
3662
- const defaultLocalePath = import_node_path6.default.resolve(
3663
- config.publicDir,
3664
- "_locales",
3665
- defaultLocale,
3666
- "messages.json"
3667
- );
3668
- const content = JSON.parse(await import_fs_extra6.default.readFile(defaultLocalePath, "utf-8"));
3669
- messages = parseI18nMessages(content);
3670
- } else {
3671
- messages = parseI18nMessages({});
3672
- }
3673
- const overrides = messages.map((message) => {
3674
- return ` /**
3675
- * ${message.description ?? "No message description."}
3676
- *
3677
- * "${message.message}"
3678
- */
3679
- getMessage(
3680
- messageName: "${message.name}",
3681
- substitutions?: string | string[],
3682
- options?: GetMessageOptions,
3683
- ): string;`;
3684
- });
3685
- await writeFileIfDifferent(
3686
- filePath,
3687
- template.replace("{{ overrides }}", overrides.join("\n"))
3688
- );
3689
- return filePath;
3560
+
3561
+ // src/core/builders/vite/plugins/tsconfigPaths.ts
3562
+ function tsconfigPaths(config) {
3563
+ return {
3564
+ name: "wxt:aliases",
3565
+ async config() {
3566
+ return {
3567
+ resolve: {
3568
+ alias: config.alias
3569
+ }
3570
+ };
3571
+ }
3572
+ };
3690
3573
  }
3691
- async function writeGlobalsDeclarationFile(config) {
3692
- const filePath = (0, import_path6.resolve)(config.typesDir, "globals.d.ts");
3693
- const globals2 = [...getGlobals(config), ...getEntrypointGlobals(config, "")];
3694
- await writeFileIfDifferent(
3695
- filePath,
3696
- [
3697
- "// Generated by wxt",
3698
- "export {}",
3699
- "declare global {",
3700
- ...globals2.map((global3) => ` const ${global3.name}: ${global3.type};`),
3701
- "}"
3702
- ].join("\n") + "\n"
3703
- );
3704
- return filePath;
3574
+
3575
+ // src/core/builders/vite/plugins/noopBackground.ts
3576
+ function noopBackground() {
3577
+ const virtualModuleId = VIRTUAL_NOOP_BACKGROUND_MODULE_ID;
3578
+ const resolvedVirtualModuleId = "\0" + virtualModuleId;
3579
+ return {
3580
+ name: "wxt:noop-background",
3581
+ resolveId(id) {
3582
+ if (id === virtualModuleId)
3583
+ return resolvedVirtualModuleId;
3584
+ },
3585
+ load(id) {
3586
+ if (id === resolvedVirtualModuleId) {
3587
+ return `import { defineBackground } from 'wxt/client';
3588
+ export default defineBackground(() => void 0)`;
3589
+ }
3590
+ }
3591
+ };
3705
3592
  }
3706
- async function writeMainDeclarationFile(references, config) {
3707
- const dir = config.wxtDir;
3708
- const filePath = (0, import_path6.resolve)(dir, "wxt.d.ts");
3709
- await writeFileIfDifferent(
3710
- filePath,
3711
- [
3712
- "// Generated by wxt",
3713
- `/// <reference types="vite/client" />`,
3714
- ...references.map(
3715
- (ref) => `/// <reference types="./${normalizePath((0, import_path6.relative)(dir, ref))}" />`
3716
- )
3717
- ].join("\n") + "\n"
3718
- );
3719
- return filePath;
3593
+
3594
+ // src/core/builders/vite/plugins/cssEntrypoints.ts
3595
+ function cssEntrypoints(entrypoint, config) {
3596
+ return {
3597
+ name: "wxt:css-entrypoint",
3598
+ config() {
3599
+ return {
3600
+ build: {
3601
+ rollupOptions: {
3602
+ output: {
3603
+ assetFileNames: () => getEntrypointBundlePath(entrypoint, config.outDir, ".css")
3604
+ }
3605
+ }
3606
+ }
3607
+ };
3608
+ },
3609
+ generateBundle(_, bundle) {
3610
+ Object.keys(bundle).forEach((file) => {
3611
+ if (file.endsWith(".js"))
3612
+ delete bundle[file];
3613
+ });
3614
+ }
3615
+ };
3720
3616
  }
3721
- async function writeTsConfigFile(mainReference, config) {
3722
- const dir = config.wxtDir;
3723
- const getTsconfigPath = (path11) => normalizePath((0, import_path6.relative)(dir, path11));
3724
- const paths = Object.entries(config.alias).flatMap(([alias, absolutePath]) => {
3725
- const aliasPath = getTsconfigPath(absolutePath);
3726
- return [
3727
- ` "${alias}": ["${aliasPath}"]`,
3728
- ` "${alias}/*": ["${aliasPath}/*"]`
3729
- ];
3730
- }).join(",\n");
3731
- await writeFileIfDifferent(
3732
- (0, import_path6.resolve)(dir, "tsconfig.json"),
3733
- `{
3734
- "compilerOptions": {
3735
- "target": "ESNext",
3736
- "module": "ESNext",
3737
- "moduleResolution": "Bundler",
3738
- "noEmit": true,
3739
- "esModuleInterop": true,
3740
- "forceConsistentCasingInFileNames": true,
3741
- "resolveJsonModule": true,
3742
- "strict": true,
3743
- "skipLibCheck": true,
3744
- "paths": {
3745
- ${paths}
3617
+
3618
+ // src/core/builders/vite/plugins/bundleAnalysis.ts
3619
+ var import_rollup_plugin_visualizer = require("rollup-plugin-visualizer");
3620
+ var increment = 0;
3621
+ function bundleAnalysis() {
3622
+ return (0, import_rollup_plugin_visualizer.visualizer)({
3623
+ emitFile: true,
3624
+ template: "raw-data",
3625
+ filename: `stats-${increment++}.json`
3626
+ });
3627
+ }
3628
+
3629
+ // src/core/builders/vite/plugins/globals.ts
3630
+ function globals(config) {
3631
+ return {
3632
+ name: "wxt:globals",
3633
+ config() {
3634
+ const define = {};
3635
+ for (const global3 of getGlobals(config)) {
3636
+ define[global3.name] = JSON.stringify(global3.value);
3637
+ }
3638
+ return {
3639
+ define
3640
+ };
3746
3641
  }
3747
- },
3748
- "include": [
3749
- "${getTsconfigPath(config.root)}/**/*",
3750
- "./${getTsconfigPath(mainReference)}"
3751
- ],
3752
- "exclude": ["${getTsconfigPath(config.outBaseDir)}"]
3753
- }`
3754
- );
3642
+ };
3755
3643
  }
3756
3644
 
3757
- // src/core/utils/building/get-internal-config.ts
3758
- var import_c12 = require("c12");
3759
- var import_node_path7 = __toESM(require("path"), 1);
3760
- var vite2 = __toESM(require("vite"), 1);
3645
+ // src/core/builders/vite/plugins/webextensionPolyfillAlias.ts
3646
+ var import_node_path6 = __toESM(require("path"), 1);
3761
3647
 
3762
- // src/core/utils/cache.ts
3763
- var import_fs_extra7 = __toESM(require("fs-extra"), 1);
3764
- var import_path7 = require("path");
3765
- function createFsCache(wxtDir) {
3766
- const getPath = (key) => (0, import_path7.resolve)(wxtDir, "cache", encodeURIComponent(key));
3648
+ // src/core/builders/vite/plugins/excludeBrowserPolyfill.ts
3649
+ function excludeBrowserPolyfill(config) {
3650
+ const virtualId = "virtual:wxt-webextension-polyfill-disabled";
3767
3651
  return {
3768
- async set(key, value) {
3769
- const path11 = getPath(key);
3770
- await (0, import_fs_extra7.ensureDir)((0, import_path7.dirname)(path11));
3771
- await writeFileIfDifferent(path11, value);
3652
+ name: "wxt:exclude-browser-polyfill",
3653
+ config() {
3654
+ if (config.experimental.includeBrowserPolyfill)
3655
+ return;
3656
+ return {
3657
+ resolve: {
3658
+ alias: {
3659
+ "webextension-polyfill": virtualId
3660
+ }
3661
+ }
3662
+ };
3772
3663
  },
3773
- async get(key) {
3774
- const path11 = getPath(key);
3775
- try {
3776
- return await import_fs_extra7.default.readFile(path11, "utf-8");
3777
- } catch {
3778
- return void 0;
3664
+ load(id) {
3665
+ if (id === virtualId) {
3666
+ return "export default chrome";
3667
+ }
3668
+ }
3669
+ };
3670
+ }
3671
+
3672
+ // src/core/builders/vite/plugins/entrypointGroupGlobals.ts
3673
+ function entrypointGroupGlobals(entrypointGroup) {
3674
+ return {
3675
+ name: "wxt:entrypoint-group-globals",
3676
+ config() {
3677
+ const define = {};
3678
+ let name = Array.isArray(entrypointGroup) ? "html" : entrypointGroup.name;
3679
+ for (const global3 of getEntrypointGlobals(name)) {
3680
+ define[global3.name] = JSON.stringify(global3.value);
3681
+ }
3682
+ return {
3683
+ define
3684
+ };
3685
+ }
3686
+ };
3687
+ }
3688
+
3689
+ // src/core/builders/vite/index.ts
3690
+ async function craeteViteBuilder(inlineConfig, userConfig, wxtConfig) {
3691
+ const vite = await import("vite");
3692
+ const getBaseConfig = async () => {
3693
+ const resolvedInlineConfig = await inlineConfig.vite?.(wxtConfig.env) ?? {};
3694
+ const resolvedUserConfig = await userConfig.vite?.(wxtConfig.env) ?? {};
3695
+ const config = vite.mergeConfig(
3696
+ resolvedUserConfig,
3697
+ resolvedInlineConfig
3698
+ );
3699
+ config.root = wxtConfig.root;
3700
+ config.configFile = false;
3701
+ config.logLevel = "warn";
3702
+ config.mode = wxtConfig.mode;
3703
+ config.build ??= {};
3704
+ config.build.outDir = wxtConfig.outDir;
3705
+ config.build.emptyOutDir = false;
3706
+ config.plugins ??= [];
3707
+ config.plugins.push(
3708
+ download(wxtConfig),
3709
+ devHtmlPrerender(wxtConfig),
3710
+ unimport(wxtConfig),
3711
+ virtualEntrypoint("background", wxtConfig),
3712
+ virtualEntrypoint("content-script", wxtConfig),
3713
+ virtualEntrypoint("unlisted-script", wxtConfig),
3714
+ devServerGlobals(wxtConfig),
3715
+ tsconfigPaths(wxtConfig),
3716
+ noopBackground(),
3717
+ globals(wxtConfig),
3718
+ excludeBrowserPolyfill(wxtConfig)
3719
+ );
3720
+ if (wxtConfig.analysis.enabled) {
3721
+ config.plugins.push(bundleAnalysis());
3722
+ }
3723
+ return config;
3724
+ };
3725
+ const getLibModeConfig = (entrypoint) => {
3726
+ const isVirtual = [
3727
+ "background",
3728
+ "content-script",
3729
+ "unlisted-script"
3730
+ ].includes(entrypoint.type);
3731
+ const entry = isVirtual ? `virtual:wxt-${entrypoint.type}?${entrypoint.inputPath}` : entrypoint.inputPath;
3732
+ const plugins = [
3733
+ entrypointGroupGlobals(entrypoint)
3734
+ ];
3735
+ if (entrypoint.type === "content-script-style" || entrypoint.type === "unlisted-style") {
3736
+ plugins.push(cssEntrypoints(entrypoint, wxtConfig));
3737
+ }
3738
+ const libMode = {
3739
+ mode: wxtConfig.mode,
3740
+ plugins,
3741
+ build: {
3742
+ lib: {
3743
+ entry,
3744
+ formats: ["iife"],
3745
+ name: "_",
3746
+ fileName: entrypoint.name
3747
+ },
3748
+ rollupOptions: {
3749
+ output: {
3750
+ // There's only a single output for this build, so we use the desired bundle path for the
3751
+ // entry output (like "content-scripts/overlay.js")
3752
+ entryFileNames: getEntrypointBundlePath(
3753
+ entrypoint,
3754
+ wxtConfig.outDir,
3755
+ ".js"
3756
+ ),
3757
+ // Output content script CSS to `content-scripts/`, but all other scripts are written to
3758
+ // `assets/`.
3759
+ assetFileNames: ({ name }) => {
3760
+ if (entrypoint.type === "content-script" && name?.endsWith("css")) {
3761
+ return `content-scripts/${entrypoint.name}.[ext]`;
3762
+ } else {
3763
+ return `assets/${entrypoint.name}.[ext]`;
3764
+ }
3765
+ }
3766
+ }
3767
+ }
3768
+ },
3769
+ define: {
3770
+ // See https://github.com/aklinker1/vite-plugin-web-extension/issues/96
3771
+ "process.env.NODE_ENV": JSON.stringify(wxtConfig.mode)
3772
+ }
3773
+ };
3774
+ return libMode;
3775
+ };
3776
+ const getMultiPageConfig = (entrypoints) => {
3777
+ return {
3778
+ mode: wxtConfig.mode,
3779
+ plugins: [
3780
+ multipageMove(entrypoints, wxtConfig),
3781
+ entrypointGroupGlobals(entrypoints)
3782
+ ],
3783
+ build: {
3784
+ rollupOptions: {
3785
+ input: entrypoints.reduce((input, entry) => {
3786
+ input[entry.name] = entry.inputPath;
3787
+ return input;
3788
+ }, {}),
3789
+ output: {
3790
+ // Include a hash to prevent conflicts
3791
+ chunkFileNames: "chunks/[name]-[hash].js",
3792
+ // Include a hash to prevent conflicts
3793
+ entryFileNames: "chunks/[name]-[hash].js",
3794
+ // We can't control the "name", so we need a hash to prevent conflicts
3795
+ assetFileNames: "assets/[name]-[hash].[ext]"
3796
+ }
3797
+ }
3798
+ }
3799
+ };
3800
+ };
3801
+ const getCssConfig = (entrypoint) => {
3802
+ return {
3803
+ mode: wxtConfig.mode,
3804
+ plugins: [entrypointGroupGlobals(entrypoint)],
3805
+ build: {
3806
+ rollupOptions: {
3807
+ input: {
3808
+ [entrypoint.name]: entrypoint.inputPath
3809
+ },
3810
+ output: {
3811
+ assetFileNames: () => {
3812
+ if (entrypoint.type === "content-script-style") {
3813
+ return `content-scripts/${entrypoint.name}.[ext]`;
3814
+ } else {
3815
+ return `assets/${entrypoint.name}.[ext]`;
3816
+ }
3817
+ }
3818
+ }
3819
+ }
3779
3820
  }
3821
+ };
3822
+ };
3823
+ return {
3824
+ name: "Vite",
3825
+ version: vite.version,
3826
+ async build(group) {
3827
+ let entryConfig;
3828
+ if (Array.isArray(group))
3829
+ entryConfig = getMultiPageConfig(group);
3830
+ else if (group.inputPath.endsWith(".css"))
3831
+ entryConfig = getCssConfig(group);
3832
+ else
3833
+ entryConfig = getLibModeConfig(group);
3834
+ const buildConfig = vite.mergeConfig(await getBaseConfig(), entryConfig);
3835
+ const result = await vite.build(buildConfig);
3836
+ return {
3837
+ entrypoints: group,
3838
+ chunks: getBuildOutputChunks(result)
3839
+ };
3840
+ },
3841
+ async createServer(info) {
3842
+ const serverConfig = {
3843
+ server: {
3844
+ port: info.port,
3845
+ strictPort: true,
3846
+ host: info.hostname,
3847
+ origin: info.origin
3848
+ }
3849
+ };
3850
+ const baseConfig = await getBaseConfig();
3851
+ const viteServer = await vite.createServer(
3852
+ vite.mergeConfig(baseConfig, serverConfig)
3853
+ );
3854
+ const server = {
3855
+ async listen() {
3856
+ await viteServer.listen(info.port);
3857
+ },
3858
+ transformHtml(...args) {
3859
+ return viteServer.transformIndexHtml(...args);
3860
+ },
3861
+ ws: {
3862
+ send(message, payload) {
3863
+ return viteServer.ws.send(message, payload);
3864
+ },
3865
+ on(message, cb) {
3866
+ viteServer.ws.on(message, cb);
3867
+ }
3868
+ },
3869
+ watcher: viteServer.watcher
3870
+ };
3871
+ return server;
3780
3872
  }
3781
3873
  };
3782
3874
  }
3875
+ function getBuildOutputChunks(result) {
3876
+ if ("on" in result)
3877
+ throw Error("wxt does not support vite watch mode.");
3878
+ if (Array.isArray(result))
3879
+ return result.flatMap(({ output }) => output);
3880
+ return result.output;
3881
+ }
3783
3882
 
3784
3883
  // src/core/utils/building/get-internal-config.ts
3785
- var import_consola = __toESM(require("consola"), 1);
3786
- var import_defu = __toESM(require("defu"), 1);
3787
- async function getInternalConfig(inlineConfig, command) {
3884
+ var import_defu2 = __toESM(require("defu"), 1);
3885
+ async function getInternalConfig(inlineConfig, command, server) {
3788
3886
  let userConfig = {};
3789
3887
  let userConfigMetadata;
3790
3888
  if (inlineConfig.configFile !== false) {
@@ -3816,7 +3914,7 @@ async function getInternalConfig(inlineConfig, command) {
3816
3914
  );
3817
3915
  const publicDir = import_node_path7.default.resolve(srcDir, mergedConfig.publicDir ?? "public");
3818
3916
  const typesDir = import_node_path7.default.resolve(wxtDir, "types");
3819
- const outBaseDir = import_node_path7.default.resolve(root, ".output");
3917
+ const outBaseDir = import_node_path7.default.resolve(root, mergedConfig.outDir ?? ".output");
3820
3918
  const outDir = import_node_path7.default.resolve(outBaseDir, `${browser}-mv${manifestVersion}`);
3821
3919
  const runnerConfig = await (0, import_c12.loadConfig)({
3822
3920
  name: "web-ext",
@@ -3854,8 +3952,6 @@ async function getInternalConfig(inlineConfig, command) {
3854
3952
  runnerConfig,
3855
3953
  srcDir,
3856
3954
  typesDir,
3857
- vite: () => ({}),
3858
- // Real value added after this object is initialized.
3859
3955
  wxtDir,
3860
3956
  zip: resolveInternalZipConfig(root, mergedConfig),
3861
3957
  transformManifest(manifest) {
@@ -3870,10 +3966,18 @@ async function getInternalConfig(inlineConfig, command) {
3870
3966
  alias,
3871
3967
  experimental: {
3872
3968
  includeBrowserPolyfill: mergedConfig.experimental?.includeBrowserPolyfill ?? true
3873
- }
3969
+ },
3970
+ server
3971
+ };
3972
+ const builder = await craeteViteBuilder(
3973
+ inlineConfig,
3974
+ userConfig,
3975
+ finalConfig
3976
+ );
3977
+ return {
3978
+ ...finalConfig,
3979
+ builder
3874
3980
  };
3875
- finalConfig.vite = (env2) => resolveInternalViteConfig(env2, mergedConfig, finalConfig);
3876
- return finalConfig;
3877
3981
  }
3878
3982
  async function resolveManifestConfig(env, manifest) {
3879
3983
  return await (typeof manifest === "function" ? manifest(env) : manifest ?? {});
@@ -3885,26 +3989,18 @@ function mergeInlineConfig(inlineConfig, userConfig) {
3885
3989
  } else if (userConfig.imports == null && inlineConfig.imports == null) {
3886
3990
  imports = void 0;
3887
3991
  } else {
3888
- imports = vite2.mergeConfig(
3889
- userConfig.imports ?? {},
3890
- inlineConfig.imports ?? {}
3891
- );
3992
+ imports = (0, import_defu2.default)(inlineConfig.imports ?? {}, userConfig.imports ?? {});
3892
3993
  }
3893
3994
  const manifest = async (env) => {
3894
3995
  const user = await resolveManifestConfig(env, userConfig.manifest);
3895
3996
  const inline = await resolveManifestConfig(env, inlineConfig.manifest);
3896
- return vite2.mergeConfig(user, inline);
3897
- };
3898
- const viteConfig = async (env) => {
3899
- const user = await userConfig.vite?.(env);
3900
- const inline = await inlineConfig.vite?.(env);
3901
- return vite2.mergeConfig(user ?? {}, inline ?? {});
3997
+ return (0, import_defu2.default)(inline, user);
3902
3998
  };
3903
- const runner = (0, import_defu.default)(
3999
+ const runner = (0, import_defu2.default)(
3904
4000
  inlineConfig.runner ?? {},
3905
4001
  userConfig.runner ?? {}
3906
4002
  );
3907
- const zip2 = (0, import_defu.default)(
4003
+ const zip2 = (0, import_defu2.default)(
3908
4004
  inlineConfig.zip ?? {},
3909
4005
  userConfig.zip ?? {}
3910
4006
  );
@@ -3922,7 +4018,7 @@ function mergeInlineConfig(inlineConfig, userConfig) {
3922
4018
  publicDir: inlineConfig.publicDir ?? userConfig.publicDir,
3923
4019
  runner,
3924
4020
  srcDir: inlineConfig.srcDir ?? userConfig.srcDir,
3925
- vite: viteConfig,
4021
+ outDir: inlineConfig.outDir ?? userConfig.outDir,
3926
4022
  zip: zip2,
3927
4023
  analysis: {
3928
4024
  enabled: inlineConfig.analysis?.enabled ?? userConfig.analysis?.enabled,
@@ -3935,11 +4031,14 @@ function mergeInlineConfig(inlineConfig, userConfig) {
3935
4031
  experimental: {
3936
4032
  ...userConfig.experimental,
3937
4033
  ...inlineConfig.experimental
3938
- }
4034
+ },
4035
+ vite: void 0,
4036
+ transformManifest: void 0
3939
4037
  };
3940
4038
  }
3941
4039
  function resolveInternalZipConfig(root, mergedConfig) {
3942
4040
  return {
4041
+ name: void 0,
3943
4042
  sourcesTemplate: "{{name}}-{{version}}-sources.zip",
3944
4043
  artifactTemplate: "{{name}}-{{version}}-{{browser}}.zip",
3945
4044
  sourcesRoot: root,
@@ -3958,38 +4057,6 @@ function resolveInternalZipConfig(root, mergedConfig) {
3958
4057
  ]
3959
4058
  };
3960
4059
  }
3961
- async function resolveInternalViteConfig(env, mergedConfig, finalConfig) {
3962
- const internalVite = await mergedConfig.vite?.(env) ?? {};
3963
- internalVite.root = finalConfig.root;
3964
- internalVite.configFile = false;
3965
- internalVite.logLevel = "warn";
3966
- internalVite.mode = env.mode;
3967
- internalVite.build ??= {};
3968
- internalVite.build.outDir = finalConfig.outDir;
3969
- internalVite.build.emptyOutDir = false;
3970
- internalVite.plugins ??= [];
3971
- internalVite.plugins.push(download(finalConfig));
3972
- internalVite.plugins.push(devHtmlPrerender(finalConfig));
3973
- internalVite.plugins.push(unimport(finalConfig));
3974
- internalVite.plugins.push(
3975
- virtualEntrypoint("background", finalConfig)
3976
- );
3977
- internalVite.plugins.push(
3978
- virtualEntrypoint("content-script", finalConfig)
3979
- );
3980
- internalVite.plugins.push(
3981
- virtualEntrypoint("unlisted-script", finalConfig)
3982
- );
3983
- internalVite.plugins.push(devServerGlobals(finalConfig));
3984
- internalVite.plugins.push(tsconfigPaths(finalConfig));
3985
- internalVite.plugins.push(noopBackground());
3986
- if (finalConfig.analysis.enabled) {
3987
- internalVite.plugins.push(bundleAnalysis());
3988
- }
3989
- internalVite.plugins.push(globals(finalConfig));
3990
- internalVite.plugins.push(excludeBrowserPolyfill(finalConfig));
3991
- return internalVite;
3992
- }
3993
4060
 
3994
4061
  // src/core/utils/building/group-entrypoints.ts
3995
4062
  function groupEntrypoints(entrypoints) {
@@ -4031,7 +4098,7 @@ var ENTRY_TYPE_TO_GROUP_MAP = {
4031
4098
  var import_jiti = __toESM(require("jiti"), 1);
4032
4099
  var import_unimport5 = require("unimport");
4033
4100
  var import_fs_extra8 = __toESM(require("fs-extra"), 1);
4034
- var import_path8 = require("path");
4101
+ var import_path7 = require("path");
4035
4102
 
4036
4103
  // src/core/utils/strings.ts
4037
4104
  function kebabCaseAlphanumeric(str) {
@@ -4053,16 +4120,16 @@ ${noImports}`;
4053
4120
 
4054
4121
  // src/core/utils/building/import-entrypoint.ts
4055
4122
  var import_esbuild = require("esbuild");
4056
- async function importEntrypointFile(path11, config) {
4057
- config.logger.debug("Loading file metadata:", path11);
4058
- const normalPath = normalizePath(path11);
4123
+ async function importEntrypointFile(path10, config) {
4124
+ config.logger.debug("Loading file metadata:", path10);
4125
+ const normalPath = normalizePath(path10);
4059
4126
  const unimport2 = (0, import_unimport5.createUnimport)({
4060
4127
  ...getUnimportOptions(config),
4061
4128
  // Only allow specific imports, not all from the project
4062
4129
  dirs: []
4063
4130
  });
4064
4131
  await unimport2.init();
4065
- const text = await import_fs_extra8.default.readFile(path11, "utf-8");
4132
+ const text = await import_fs_extra8.default.readFile(path10, "utf-8");
4066
4133
  const textNoImports = removeProjectImportStatements(text);
4067
4134
  const { code } = await unimport2.injectImports(textNoImports);
4068
4135
  config.logger.debug(
@@ -4073,7 +4140,7 @@ async function importEntrypointFile(path11, config) {
4073
4140
  debug: config.debug,
4074
4141
  esmResolve: true,
4075
4142
  alias: {
4076
- "webextension-polyfill": (0, import_path8.resolve)(
4143
+ "webextension-polyfill": (0, import_path7.resolve)(
4077
4144
  config.root,
4078
4145
  "node_modules/wxt/dist/virtual/mock-browser.js"
4079
4146
  )
@@ -4093,7 +4160,7 @@ async function importEntrypointFile(path11, config) {
4093
4160
  }
4094
4161
  });
4095
4162
  try {
4096
- const res = await jiti(path11);
4163
+ const res = await jiti(path10);
4097
4164
  return res.default;
4098
4165
  } catch (err) {
4099
4166
  config.logger.error(err);
@@ -4111,11 +4178,10 @@ function getEsbuildOptions(opts) {
4111
4178
 
4112
4179
  // src/core/utils/building/internal-build.ts
4113
4180
  var import_picocolors4 = __toESM(require("picocolors"), 1);
4114
- var vite3 = __toESM(require("vite"), 1);
4115
4181
  var import_fs_extra12 = __toESM(require("fs-extra"), 1);
4116
4182
 
4117
4183
  // src/core/utils/log/printBuildSummary.ts
4118
- var import_path9 = require("path");
4184
+ var import_path8 = require("path");
4119
4185
 
4120
4186
  // src/core/utils/log/printFileList.ts
4121
4187
  var import_node_path8 = __toESM(require("path"), 1);
@@ -4202,7 +4268,7 @@ async function printBuildSummary(log, header, output, config) {
4202
4268
  return diff;
4203
4269
  return l.fileName.localeCompare(r.fileName);
4204
4270
  });
4205
- const files = chunks.map((chunk) => (0, import_path9.resolve)(config.outDir, chunk.fileName));
4271
+ const files = chunks.map((chunk) => (0, import_path8.resolve)(config.outDir, chunk.fileName));
4206
4272
  await printFileList(log, header, config.outDir, files);
4207
4273
  }
4208
4274
  var DEFAULT_SORT_WEIGHT = 100;
@@ -4232,7 +4298,7 @@ var import_fast_glob3 = __toESM(require("fast-glob"), 1);
4232
4298
 
4233
4299
  // src/core/utils/manifest.ts
4234
4300
  var import_fs_extra11 = __toESM(require("fs-extra"), 1);
4235
- var import_path10 = require("path");
4301
+ var import_path9 = require("path");
4236
4302
 
4237
4303
  // src/core/utils/content-security-policy.ts
4238
4304
  var ContentSecurityPolicy = class _ContentSecurityPolicy {
@@ -4352,37 +4418,34 @@ async function getPackageJson(config) {
4352
4418
 
4353
4419
  // src/core/utils/manifest.ts
4354
4420
  var import_immer = require("immer");
4355
- var import_defu2 = __toESM(require("defu"), 1);
4421
+ var import_defu3 = __toESM(require("defu"), 1);
4356
4422
  async function writeManifest(manifest, output, config) {
4357
4423
  const str = config.mode === "production" ? JSON.stringify(manifest) : JSON.stringify(manifest, null, 2);
4358
4424
  await import_fs_extra11.default.ensureDir(config.outDir);
4359
- await writeFileIfDifferent((0, import_path10.resolve)(config.outDir, "manifest.json"), str);
4425
+ await writeFileIfDifferent((0, import_path9.resolve)(config.outDir, "manifest.json"), str);
4360
4426
  output.publicAssets.unshift({
4361
4427
  type: "asset",
4362
- fileName: "manifest.json",
4363
- name: "manifest",
4364
- needsCodeReference: false,
4365
- source: str
4428
+ fileName: "manifest.json"
4366
4429
  });
4367
4430
  }
4368
4431
  async function generateMainfest(entrypoints, buildOutput, config) {
4369
4432
  const pkg = await getPackageJson(config);
4370
4433
  const versionName = config.manifest.version_name ?? pkg?.version;
4371
- const version3 = config.manifest.version ?? simplifyVersion(pkg?.version);
4434
+ const version2 = config.manifest.version ?? simplifyVersion(pkg?.version);
4372
4435
  const baseManifest = {
4373
4436
  manifest_version: config.manifestVersion,
4374
4437
  name: pkg?.name,
4375
4438
  description: pkg?.description,
4376
- version: version3,
4439
+ version: version2,
4377
4440
  version_name: (
4378
4441
  // Firefox doesn't support version_name
4379
- config.browser === "firefox" || versionName === version3 ? void 0 : versionName
4442
+ config.browser === "firefox" || versionName === version2 ? void 0 : versionName
4380
4443
  ),
4381
4444
  short_name: pkg?.shortName,
4382
4445
  icons: discoverIcons(buildOutput)
4383
4446
  };
4384
4447
  const userManifest = config.manifest;
4385
- const manifest = (0, import_defu2.default)(
4448
+ const manifest = (0, import_defu3.default)(
4386
4449
  userManifest,
4387
4450
  baseManifest
4388
4451
  );
@@ -4404,14 +4467,14 @@ async function generateMainfest(entrypoints, buildOutput, config) {
4404
4467
  return finalManifest;
4405
4468
  }
4406
4469
  function simplifyVersion(versionName) {
4407
- const version3 = /^((0|[1-9][0-9]{0,8})([.](0|[1-9][0-9]{0,8})){0,3}).*$/.exec(
4470
+ const version2 = /^((0|[1-9][0-9]{0,8})([.](0|[1-9][0-9]{0,8})){0,3}).*$/.exec(
4408
4471
  versionName
4409
4472
  )?.[1];
4410
- if (version3 == null)
4473
+ if (version2 == null)
4411
4474
  throw Error(
4412
4475
  `Cannot simplify package.json version "${versionName}" to a valid extension version, "X.Y.Z"`
4413
4476
  );
4414
- return version3;
4477
+ return version2;
4415
4478
  }
4416
4479
  function addEntrypoints(manifest, entrypoints, buildOutput, config) {
4417
4480
  const entriesByType = entrypoints.reduce((map, entrypoint) => {
@@ -4783,7 +4846,7 @@ async function internalBuild(config) {
4783
4846
  const target = `${config.browser}-mv${config.manifestVersion}`;
4784
4847
  config.logger.info(
4785
4848
  `${verb} ${import_picocolors4.default.cyan(target)} for ${import_picocolors4.default.cyan(config.mode)} with ${import_picocolors4.default.green(
4786
- `Vite ${vite3.version}`
4849
+ `${config.builder.name} ${config.builder.version}`
4787
4850
  )}`
4788
4851
  );
4789
4852
  const startTime = Date.now();
@@ -4822,7 +4885,7 @@ async function combineAnalysisStats(config) {
4822
4885
  }
4823
4886
 
4824
4887
  // src/core/build.ts
4825
- async function build2(config) {
4888
+ async function build(config) {
4826
4889
  const internalConfig = await getInternalConfig(config ?? {}, "build");
4827
4890
  return await internalBuild(internalConfig);
4828
4891
  }
@@ -4862,9 +4925,6 @@ async function clean(root = process.cwd()) {
4862
4925
  }
4863
4926
  }
4864
4927
 
4865
- // src/core/create-server.ts
4866
- var vite4 = __toESM(require("vite"), 1);
4867
-
4868
4928
  // src/core/runners/wsl.ts
4869
4929
  var import_node_path13 = require("path");
4870
4930
  function createWslRunner() {
@@ -4998,29 +5058,77 @@ var import_consola4 = require("consola");
4998
5058
  var import_async_mutex = require("async-mutex");
4999
5059
  var import_picocolors6 = __toESM(require("picocolors"), 1);
5000
5060
  var import_node_path16 = require("path");
5001
- async function createServer2(config) {
5002
- const serverInfo = await getServerInfo();
5003
- const getLatestInternalConfig = async () => {
5004
- return getInternalConfig(
5005
- {
5006
- ...config,
5007
- vite: () => serverInfo.viteServerConfig
5008
- },
5009
- "serve"
5010
- );
5061
+ async function createServer(inlineConfig) {
5062
+ const port = await getPort();
5063
+ const hostname = "localhost";
5064
+ const origin = `http://${hostname}:${port}`;
5065
+ const serverInfo = {
5066
+ port,
5067
+ hostname,
5068
+ origin
5011
5069
  };
5012
- let internalConfig = await getLatestInternalConfig();
5013
- const server = await setupServer(serverInfo, internalConfig);
5014
- internalConfig.server = server;
5015
- const fileChangedMutex = new import_async_mutex.Mutex();
5016
- const changeQueue = [];
5070
+ const server = {
5071
+ ...serverInfo,
5072
+ watcher: void 0,
5073
+ // Filled out later down below
5074
+ ws: void 0,
5075
+ // Filled out later down below
5076
+ currentOutput: void 0,
5077
+ // Filled out later down below
5078
+ async start() {
5079
+ await builderServer.listen();
5080
+ config.logger.success(`Started dev server @ ${serverInfo.origin}`);
5081
+ server.currentOutput = await internalBuild(config);
5082
+ await runner.openBrowser(config);
5083
+ },
5084
+ transformHtml(url2, html, originalUrl) {
5085
+ return builderServer.transformHtml(url2, html, originalUrl);
5086
+ },
5087
+ reloadContentScript(contentScript) {
5088
+ server.ws.send("wxt:reload-content-script", contentScript);
5089
+ },
5090
+ reloadPage(path10) {
5091
+ server.ws.send("wxt:reload-page", path10);
5092
+ },
5093
+ reloadExtension() {
5094
+ server.ws.send("wxt:reload-extension");
5095
+ }
5096
+ };
5097
+ const getLatestConfig = () => getInternalConfig(inlineConfig ?? {}, "serve", server);
5098
+ let config = await getLatestConfig();
5099
+ const [runner, builderServer] = await Promise.all([
5100
+ createExtensionRunner(config),
5101
+ config.builder.createServer(server)
5102
+ ]);
5103
+ server.watcher = builderServer.watcher;
5104
+ server.ws = builderServer.ws;
5017
5105
  server.ws.on("wxt:background-initialized", () => {
5018
- reloadContentScripts(server.currentOutput.steps, internalConfig, server);
5106
+ reloadContentScripts(server.currentOutput.steps, config, server);
5107
+ });
5108
+ const reloadOnChange = createFileReloader({
5109
+ server,
5110
+ getLatestConfig,
5111
+ updateConfig(newConfig) {
5112
+ config = newConfig;
5113
+ }
5019
5114
  });
5020
- server.watcher.on("all", async (event, path11, _stats) => {
5021
- if (path11.startsWith(internalConfig.outBaseDir))
5115
+ server.watcher.on("all", reloadOnChange);
5116
+ return server;
5117
+ }
5118
+ async function getPort() {
5119
+ const { default: getPort2, portNumbers } = await import("get-port");
5120
+ return await getPort2({ port: portNumbers(3e3, 3010) });
5121
+ }
5122
+ function createFileReloader(options) {
5123
+ const { server, getLatestConfig, updateConfig } = options;
5124
+ const fileChangedMutex = new import_async_mutex.Mutex();
5125
+ const changeQueue = [];
5126
+ return async (event, path10) => {
5127
+ const config = await getLatestConfig();
5128
+ updateConfig(config);
5129
+ if (path10.startsWith(config.outBaseDir))
5022
5130
  return;
5023
- changeQueue.push([event, path11]);
5131
+ changeQueue.push([event, path10]);
5024
5132
  await fileChangedMutex.runExclusive(async () => {
5025
5133
  const fileChanges = changeQueue.splice(0, changeQueue.length);
5026
5134
  if (fileChanges.length === 0)
@@ -5028,18 +5136,16 @@ async function createServer2(config) {
5028
5136
  const changes = detectDevChanges(fileChanges, server.currentOutput);
5029
5137
  if (changes.type === "no-change")
5030
5138
  return;
5031
- internalConfig.logger.info(
5032
- `Changed: ${Array.from(new Set(fileChanges.map((change) => change[1]))).map((file) => import_picocolors6.default.dim((0, import_node_path16.relative)(internalConfig.root, file))).join(", ")}`
5139
+ config.logger.info(
5140
+ `Changed: ${Array.from(new Set(fileChanges.map((change) => change[1]))).map((file) => import_picocolors6.default.dim((0, import_node_path16.relative)(config.root, file))).join(", ")}`
5033
5141
  );
5034
5142
  const rebuiltNames = changes.rebuildGroups.flat().map((entry) => {
5035
5143
  return import_picocolors6.default.cyan(
5036
- (0, import_node_path16.relative)(internalConfig.outDir, getEntrypointOutputFile(entry, ""))
5144
+ (0, import_node_path16.relative)(config.outDir, getEntrypointOutputFile(entry, ""))
5037
5145
  );
5038
5146
  }).join(import_picocolors6.default.dim(", "));
5039
- internalConfig = await getLatestInternalConfig();
5040
- internalConfig.server = server;
5041
5147
  const { output: newOutput } = await rebuild(
5042
- internalConfig,
5148
+ config,
5043
5149
  // TODO: this excludes new entrypoints, so they're not built until the dev command is restarted
5044
5150
  changes.rebuildGroups,
5045
5151
  changes.cachedOutput
@@ -5050,74 +5156,15 @@ async function createServer2(config) {
5050
5156
  server.reloadExtension();
5051
5157
  break;
5052
5158
  case "html-reload":
5053
- reloadHtmlPages(changes.rebuildGroups, server, internalConfig);
5159
+ reloadHtmlPages(changes.rebuildGroups, server, config);
5054
5160
  break;
5055
5161
  case "content-script-reload":
5056
- reloadContentScripts(changes.changedSteps, internalConfig, server);
5162
+ reloadContentScripts(changes.changedSteps, config, server);
5057
5163
  break;
5058
5164
  }
5059
5165
  import_consola4.consola.success(`Reloaded: ${rebuiltNames}`);
5060
5166
  });
5061
- });
5062
- return server;
5063
- }
5064
- async function getServerInfo() {
5065
- const { default: getPort, portNumbers } = await import("get-port");
5066
- const port = await getPort({ port: portNumbers(3e3, 3010) });
5067
- const hostname = "localhost";
5068
- const origin = `http://${hostname}:${port}`;
5069
- const serverConfig = {
5070
- server: {
5071
- origin
5072
- }
5073
- };
5074
- return {
5075
- port,
5076
- hostname,
5077
- origin,
5078
- viteServerConfig: serverConfig
5079
- };
5080
- }
5081
- async function setupServer(serverInfo, config) {
5082
- const runner = await createExtensionRunner(config);
5083
- const viteServer = await vite4.createServer(
5084
- vite4.mergeConfig(serverInfo, await config.vite(config.env))
5085
- );
5086
- const start = async () => {
5087
- await viteServer.listen(server.port);
5088
- config.logger.success(`Started dev server @ ${serverInfo.origin}`);
5089
- server.currentOutput = await internalBuild(config);
5090
- await runner.openBrowser(config);
5091
5167
  };
5092
- const reloadExtension = () => {
5093
- viteServer.ws.send("wxt:reload-extension");
5094
- };
5095
- const reloadPage = (path11) => {
5096
- viteServer.ws.send("wxt:reload-page", path11);
5097
- };
5098
- const reloadContentScript = (contentScript) => {
5099
- viteServer.ws.send("wxt:reload-content-script", contentScript);
5100
- };
5101
- const server = {
5102
- ...viteServer,
5103
- start,
5104
- currentOutput: {
5105
- manifest: {
5106
- manifest_version: 3,
5107
- name: "",
5108
- version: ""
5109
- },
5110
- publicAssets: [],
5111
- steps: []
5112
- },
5113
- port: serverInfo.port,
5114
- hostname: serverInfo.hostname,
5115
- origin: serverInfo.origin,
5116
- reloadExtension,
5117
- reloadPage,
5118
- reloadContentScript
5119
- };
5120
- return server;
5121
5168
  }
5122
5169
  function reloadContentScripts(steps, config, server) {
5123
5170
  if (config.manifestVersion === 3) {
@@ -5151,8 +5198,8 @@ function reloadContentScripts(steps, config, server) {
5151
5198
  }
5152
5199
  function reloadHtmlPages(groups, server, config) {
5153
5200
  groups.flat().forEach((entry) => {
5154
- const path11 = getEntrypointBundlePath(entry, config.outDir, ".html");
5155
- server.reloadPage(path11);
5201
+ const path10 = getEntrypointBundlePath(entry, config.outDir, ".html");
5202
+ server.reloadPage(path10);
5156
5203
  });
5157
5204
  }
5158
5205
 
@@ -5334,8 +5381,8 @@ async function zip(config) {
5334
5381
  );
5335
5382
  await (0, import_zip_dir.default)(internalConfig.zip.sourcesRoot, {
5336
5383
  saveTo: sourcesZipPath,
5337
- filter(path11) {
5338
- const relativePath = (0, import_node_path18.relative)(internalConfig.zip.sourcesRoot, path11);
5384
+ filter(path10) {
5385
+ const relativePath = (0, import_node_path18.relative)(internalConfig.zip.sourcesRoot, path10);
5339
5386
  const matchedPattern = internalConfig.zip.ignoredSources.find(
5340
5387
  (pattern) => (0, import_minimatch2.minimatch)(relativePath, pattern)
5341
5388
  );
@@ -5361,7 +5408,7 @@ cli.version(version);
5361
5408
  cli.option("--debug", "enable debug mode");
5362
5409
  cli.command("[root]", "start dev server").option("-c, --config <file>", "use specified config file").option("-m, --mode <mode>", "set env mode").option("-b, --browser <browser>", "specify a browser").option("--mv3", "target manifest v3").option("--mv2", "target manifest v2").action(
5363
5410
  wrapAction(async (root, flags) => {
5364
- const server = await createServer2({
5411
+ const server = await createServer({
5365
5412
  root,
5366
5413
  mode: flags.mode,
5367
5414
  browser: flags.browser,
@@ -5375,7 +5422,7 @@ cli.command("[root]", "start dev server").option("-c, --config <file>", "use spe
5375
5422
  );
5376
5423
  cli.command("build [root]", "build for production").option("-c, --config <file>", "use specified config file").option("-m, --mode <mode>", "set env mode").option("-b, --browser <browser>", "specify a browser").option("--mv3", "target manifest v3").option("--mv2", "target manifest v2").option("--analyze", "visualize extension bundle").action(
5377
5424
  wrapAction(async (root, flags) => {
5378
- await build2({
5425
+ await build({
5379
5426
  root,
5380
5427
  mode: flags.mode,
5381
5428
  browser: flags.browser,