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.
@@ -1,22 +1,112 @@
1
1
  // package.json
2
- var version = "0.10.3";
2
+ var version = "0.11.0";
3
3
 
4
- // src/core/utils/entrypoints.ts
5
- import path, { relative, resolve } from "node:path";
4
+ // src/core/utils/arrays.ts
5
+ function every(array, predicate) {
6
+ for (let i = 0; i < array.length; i++)
7
+ if (!predicate(array[i], i))
8
+ return false;
9
+ return true;
10
+ }
6
11
 
7
12
  // src/core/utils/paths.ts
8
13
  import systemPath from "node:path";
9
14
  import normalize from "normalize-path";
10
- function normalizePath(path7) {
11
- return normalize(path7);
15
+ function normalizePath(path6) {
16
+ return normalize(path6);
12
17
  }
13
- function unnormalizePath(path7) {
14
- return systemPath.normalize(path7);
18
+ function unnormalizePath(path6) {
19
+ return systemPath.normalize(path6);
15
20
  }
16
21
  var CSS_EXTENSIONS = ["css", "scss", "sass", "less", "styl", "stylus"];
17
22
  var CSS_EXTENSIONS_PATTERN = `+(${CSS_EXTENSIONS.join("|")})`;
18
23
 
24
+ // src/core/utils/building/detect-dev-changes.ts
25
+ function detectDevChanges(changedFiles, currentOutput) {
26
+ if (currentOutput == null)
27
+ return { type: "no-change" };
28
+ const changedSteps = new Set(
29
+ changedFiles.flatMap(
30
+ (changedFile) => findEffectedSteps(changedFile, currentOutput)
31
+ )
32
+ );
33
+ if (changedSteps.size === 0)
34
+ return { type: "no-change" };
35
+ const unchangedOutput = {
36
+ manifest: currentOutput.manifest,
37
+ steps: [],
38
+ publicAssets: []
39
+ };
40
+ const changedOutput = {
41
+ manifest: currentOutput.manifest,
42
+ steps: [],
43
+ publicAssets: []
44
+ };
45
+ for (const step of currentOutput.steps) {
46
+ if (changedSteps.has(step)) {
47
+ changedOutput.steps.push(step);
48
+ } else {
49
+ unchangedOutput.steps.push(step);
50
+ }
51
+ }
52
+ for (const asset of currentOutput.publicAssets) {
53
+ if (changedSteps.has(asset)) {
54
+ changedOutput.publicAssets.push(asset);
55
+ } else {
56
+ unchangedOutput.publicAssets.push(asset);
57
+ }
58
+ }
59
+ const isOnlyHtmlChanges = changedFiles.length > 0 && every(changedFiles, ([_, file]) => file.endsWith(".html"));
60
+ if (isOnlyHtmlChanges) {
61
+ return {
62
+ type: "html-reload",
63
+ cachedOutput: unchangedOutput,
64
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
65
+ };
66
+ }
67
+ const isOnlyContentScripts = changedOutput.steps.length > 0 && every(
68
+ changedOutput.steps.flatMap((step) => step.entrypoints),
69
+ (entry) => entry.type === "content-script"
70
+ );
71
+ if (isOnlyContentScripts) {
72
+ return {
73
+ type: "content-script-reload",
74
+ cachedOutput: unchangedOutput,
75
+ changedSteps: changedOutput.steps,
76
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
77
+ };
78
+ }
79
+ return {
80
+ type: "extension-reload",
81
+ cachedOutput: unchangedOutput,
82
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
83
+ };
84
+ }
85
+ function findEffectedSteps(changedFile, currentOutput) {
86
+ const changes = [];
87
+ const changedPath = normalizePath(changedFile[1]);
88
+ const isChunkEffected = (chunk) => (
89
+ // If it's an HTML file with the same path, is is effected because HTML files need to be pre-rendered
90
+ // fileName is normalized, relative bundle path
91
+ chunk.type === "asset" && changedPath.endsWith(chunk.fileName) || // If it's a chunk that depends on the changed file, it is effected
92
+ // moduleIds are absolute, normalized paths
93
+ chunk.type === "chunk" && chunk.moduleIds.includes(changedPath)
94
+ );
95
+ for (const step of currentOutput.steps) {
96
+ const effectedChunk = step.chunks.find((chunk) => isChunkEffected(chunk));
97
+ if (effectedChunk)
98
+ changes.push(step);
99
+ }
100
+ const effectedAsset = currentOutput.publicAssets.find(
101
+ (chunk) => isChunkEffected(chunk)
102
+ );
103
+ if (effectedAsset)
104
+ changes.push(effectedAsset);
105
+ return changes;
106
+ }
107
+
19
108
  // src/core/utils/entrypoints.ts
109
+ import path, { relative, resolve } from "node:path";
20
110
  function getEntrypointName(entrypointsDir, inputPath) {
21
111
  const relativePath = path.relative(entrypointsDir, inputPath);
22
112
  const name = relativePath.split(/[\.\/\\]/, 2)[0];
@@ -36,87 +126,69 @@ function resolvePerBrowserOption(option, browser) {
36
126
  return option;
37
127
  }
38
128
 
39
- // src/core/utils/time.ts
40
- function formatDuration(duration) {
41
- if (duration < 1e3)
42
- return `${duration} ms`;
43
- if (duration < 1e4)
44
- return `${(duration / 1e3).toFixed(3)} s`;
45
- if (duration < 6e4)
46
- return `${(duration / 1e3).toFixed(1)} s`;
47
- return `${(duration / 1e3).toFixed(0)} s`;
48
- }
49
- function withTimeout(promise, duration) {
50
- return new Promise((res, rej) => {
51
- const timeout = setTimeout(() => {
52
- rej(`Promise timed out after ${duration}ms`);
53
- }, duration);
54
- promise.then(res).catch(rej).finally(() => clearTimeout(timeout));
55
- });
56
- }
129
+ // src/core/utils/building/find-entrypoints.ts
130
+ import { relative as relative4, resolve as resolve12 } from "path";
131
+ import fs12 from "fs-extra";
132
+ import { minimatch } from "minimatch";
133
+ import { parseHTML as parseHTML2 } from "linkedom";
134
+ import JSON5 from "json5";
57
135
 
58
- // src/core/utils/network.ts
59
- import dns from "node:dns";
60
- function isOffline() {
61
- const isOffline2 = new Promise((res) => {
62
- dns.resolve("google.com", (err) => {
63
- if (err == null) {
64
- res(false);
65
- } else {
66
- res(true);
67
- }
68
- });
69
- });
70
- return withTimeout(isOffline2, 1e3).catch(() => true);
136
+ // src/core/utils/fs.ts
137
+ import fs from "fs-extra";
138
+ import glob from "fast-glob";
139
+ async function writeFileIfDifferent(file, newContents) {
140
+ const existingContents = await fs.readFile(file, "utf-8").catch(() => void 0);
141
+ if (existingContents !== newContents) {
142
+ await fs.writeFile(file, newContents);
143
+ }
71
144
  }
72
- async function isOnline() {
73
- const offline = await isOffline();
74
- return !offline;
145
+ async function getPublicFiles(config) {
146
+ if (!await fs.exists(config.publicDir))
147
+ return [];
148
+ const files = await glob("**/*", { cwd: config.publicDir });
149
+ return files.map(unnormalizePath);
75
150
  }
76
- async function fetchCached(url, config) {
77
- let content = "";
78
- if (await isOnline()) {
79
- const res = await fetch(url);
80
- if (res.status < 300) {
81
- content = await res.text();
82
- await config.fsCache.set(url, content);
83
- } else {
84
- config.logger.debug(
85
- `Failed to download "${url}", falling back to cache...`
86
- );
87
- }
151
+
152
+ // src/core/utils/building/build-entrypoints.ts
153
+ import fs2 from "fs-extra";
154
+ import { dirname, resolve as resolve2 } from "path";
155
+ import pc from "picocolors";
156
+ async function buildEntrypoints(groups, config, spinner) {
157
+ const steps = [];
158
+ for (let i = 0; i < groups.length; i++) {
159
+ const group = groups[i];
160
+ const groupNames = [group].flat().map((e) => e.name).join(pc.dim(", "));
161
+ spinner.text = pc.dim(`[${i + 1}/${groups.length}]`) + ` ${groupNames}`;
162
+ steps.push(await config.builder.build(group));
88
163
  }
89
- if (!content)
90
- content = await config.fsCache.get(url) ?? "";
91
- if (!content)
92
- throw Error(
93
- `Offline and "${url}" has not been cached. Try again when online.`
94
- );
95
- return content;
164
+ const publicAssets = await copyPublicDirectory(config);
165
+ return { publicAssets, steps };
96
166
  }
97
-
98
- // src/core/vite-plugins/download.ts
99
- function download(config) {
100
- return {
101
- name: "wxt:download",
102
- resolveId(id) {
103
- if (id.startsWith("url:"))
104
- return "\0" + id;
105
- },
106
- async load(id) {
107
- if (!id.startsWith("\0url:"))
108
- return;
109
- const url = id.replace("\0url:", "");
110
- return await fetchCached(url, config);
111
- }
112
- };
167
+ async function copyPublicDirectory(config) {
168
+ const files = await getPublicFiles(config);
169
+ if (files.length === 0)
170
+ return [];
171
+ const publicAssets = [];
172
+ for (const file of files) {
173
+ const srcPath = resolve2(config.publicDir, file);
174
+ const outPath = resolve2(config.outDir, file);
175
+ await fs2.ensureDir(dirname(outPath));
176
+ await fs2.copyFile(srcPath, outPath);
177
+ publicAssets.push({
178
+ type: "asset",
179
+ fileName: file
180
+ });
181
+ }
182
+ return publicAssets;
113
183
  }
114
184
 
115
- // src/core/vite-plugins/unimport.ts
185
+ // src/core/utils/building/generate-wxt-dir.ts
116
186
  import { createUnimport } from "unimport";
187
+ import fs3 from "fs-extra";
188
+ import { relative as relative2, resolve as resolve3 } from "path";
117
189
 
118
190
  // src/core/utils/unimport.ts
119
- import { mergeConfig } from "vite";
191
+ import { defu } from "defu";
120
192
  function getUnimportOptions(config) {
121
193
  if (config.imports === false)
122
194
  return false;
@@ -135,54 +207,7 @@ function getUnimportOptions(config) {
135
207
  warn: config.logger.warn,
136
208
  dirs: ["components", "composables", "hooks", "utils"]
137
209
  };
138
- return mergeConfig(
139
- defaultOptions,
140
- config.imports
141
- );
142
- }
143
-
144
- // src/core/vite-plugins/unimport.ts
145
- import { extname } from "path";
146
- var ENABLED_EXTENSIONS = /* @__PURE__ */ new Set([
147
- ".js",
148
- ".jsx",
149
- ".ts",
150
- ".tsx",
151
- ".vue",
152
- ".svelte"
153
- ]);
154
- function unimport(config) {
155
- const options = getUnimportOptions(config);
156
- if (options === false)
157
- return [];
158
- const unimport2 = createUnimport(options);
159
- return {
160
- name: "wxt:unimport",
161
- async config() {
162
- await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
163
- },
164
- async transform(code, id) {
165
- if (id.includes("node_modules"))
166
- return;
167
- if (!ENABLED_EXTENSIONS.has(extname(id)))
168
- return;
169
- return unimport2.injectImports(code, id);
170
- }
171
- };
172
- }
173
-
174
- // src/core/vite-plugins/tsconfigPaths.ts
175
- function tsconfigPaths(config) {
176
- return {
177
- name: "wxt:aliases",
178
- async config() {
179
- return {
180
- resolve: {
181
- alias: config.alias
182
- }
183
- };
184
- }
185
- };
210
+ return defu(config.imports, defaultOptions);
186
211
  }
187
212
 
188
213
  // src/core/utils/globals.ts
@@ -230,7 +255,7 @@ function getGlobals(config) {
230
255
  }
231
256
  ];
232
257
  }
233
- function getEntrypointGlobals(config, entrypointName) {
258
+ function getEntrypointGlobals(entrypointName) {
234
259
  return [
235
260
  {
236
261
  name: surroundInUnderscore("ENTRYPOINT"),
@@ -243,68 +268,253 @@ function surroundInUnderscore(name) {
243
268
  return `__${name}__`;
244
269
  }
245
270
 
246
- // src/core/vite-plugins/globals.ts
247
- function globals(config) {
248
- return {
249
- name: "wxt:globals",
250
- config() {
251
- const define = {};
252
- for (const global of getGlobals(config)) {
253
- define[global.name] = JSON.stringify(global.value);
254
- }
255
- return {
256
- define
257
- };
258
- }
259
- };
260
- }
261
-
262
- // src/core/vite-plugins/webextensionPolyfillAlias.ts
271
+ // src/core/utils/building/generate-wxt-dir.ts
263
272
  import path2 from "node:path";
264
- function webextensionPolyfillAlias(config) {
265
- return {
266
- name: "wxt:webextension-polyfill-test-alias",
267
- config() {
268
- return {
269
- resolve: {
270
- alias: {
271
- "webextension-polyfill": path2.resolve(
272
- config.root,
273
- "node_modules/wxt/dist/virtual/mock-browser"
274
- )
275
- }
276
- }
277
- };
273
+
274
+ // src/core/utils/i18n.ts
275
+ var predefinedMessages = {
276
+ "@@extension_id": {
277
+ message: "<browser.runtime.id>",
278
+ 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."
279
+ },
280
+ "@@ui_locale": {
281
+ message: "<browser.i18n.getUiLocale()>",
282
+ description: ""
283
+ },
284
+ "@@bidi_dir": {
285
+ message: "<ltr|rtl>",
286
+ 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.'
287
+ },
288
+ "@@bidi_reversed_dir": {
289
+ message: "<rtl|ltr>",
290
+ description: `If the @@bidi_dir is "ltr", then this is "rtl"; otherwise, it's "ltr".`
291
+ },
292
+ "@@bidi_start_edge": {
293
+ message: "<left|right>",
294
+ description: `If the @@bidi_dir is "ltr", then this is "left"; otherwise, it's "right".`
295
+ },
296
+ "@@bidi_end_edge": {
297
+ message: "<right|left>",
298
+ description: `If the @@bidi_dir is "ltr", then this is "right"; otherwise, it's "left".`
299
+ }
300
+ };
301
+ function parseI18nMessages(messagesJson) {
302
+ return Object.entries({
303
+ ...predefinedMessages,
304
+ ...messagesJson
305
+ }).map(([name, details]) => ({
306
+ name,
307
+ ...details
308
+ }));
309
+ }
310
+
311
+ // src/core/utils/building/generate-wxt-dir.ts
312
+ async function generateTypesDir(entrypoints, config) {
313
+ await fs3.ensureDir(config.typesDir);
314
+ const references = [];
315
+ const imports = getUnimportOptions(config);
316
+ if (imports !== false) {
317
+ references.push(await writeImportsDeclarationFile(config, imports));
318
+ }
319
+ references.push(await writePathsDeclarationFile(entrypoints, config));
320
+ references.push(await writeI18nDeclarationFile(config));
321
+ references.push(await writeGlobalsDeclarationFile(config));
322
+ const mainReference = await writeMainDeclarationFile(references, config);
323
+ await writeTsConfigFile(mainReference, config);
324
+ }
325
+ async function writeImportsDeclarationFile(config, unimportOptions) {
326
+ const filePath = resolve3(config.typesDir, "imports.d.ts");
327
+ const unimport2 = createUnimport(unimportOptions);
328
+ await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
329
+ await writeFileIfDifferent(
330
+ filePath,
331
+ ["// Generated by wxt", await unimport2.generateTypeDeclarations()].join(
332
+ "\n"
333
+ ) + "\n"
334
+ );
335
+ return filePath;
336
+ }
337
+ async function writePathsDeclarationFile(entrypoints, config) {
338
+ const filePath = resolve3(config.typesDir, "paths.d.ts");
339
+ const unions = entrypoints.map(
340
+ (entry) => getEntrypointBundlePath(
341
+ entry,
342
+ config.outDir,
343
+ entry.inputPath.endsWith(".html") ? ".html" : ".js"
344
+ )
345
+ ).concat(await getPublicFiles(config)).map(normalizePath).map((path6) => ` | "/${path6}"`).sort().join("\n");
346
+ const template = `// Generated by wxt
347
+ import "wxt/browser";
348
+
349
+ declare module "wxt/browser" {
350
+ export type PublicPath =
351
+ {{ union }}
352
+ export interface WxtRuntime extends Runtime.Static {
353
+ getURL(path: PublicPath): string;
354
+ }
355
+ }
356
+ `;
357
+ await writeFileIfDifferent(
358
+ filePath,
359
+ template.replace("{{ union }}", unions || " | never")
360
+ );
361
+ return filePath;
362
+ }
363
+ async function writeI18nDeclarationFile(config) {
364
+ const filePath = resolve3(config.typesDir, "i18n.d.ts");
365
+ const defaultLocale = config.manifest.default_locale;
366
+ const template = `// Generated by wxt
367
+ import "wxt/browser";
368
+
369
+ declare module "wxt/browser" {
370
+ /**
371
+ * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
372
+ */
373
+ interface GetMessageOptions {
374
+ /**
375
+ * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
376
+ */
377
+ escapeLt?: boolean
378
+ }
379
+
380
+ export interface WxtI18n extends I18n.Static {
381
+ {{ overrides }}
382
+ }
383
+ }
384
+ `;
385
+ let messages;
386
+ if (defaultLocale) {
387
+ const defaultLocalePath = path2.resolve(
388
+ config.publicDir,
389
+ "_locales",
390
+ defaultLocale,
391
+ "messages.json"
392
+ );
393
+ const content = JSON.parse(await fs3.readFile(defaultLocalePath, "utf-8"));
394
+ messages = parseI18nMessages(content);
395
+ } else {
396
+ messages = parseI18nMessages({});
397
+ }
398
+ const overrides = messages.map((message) => {
399
+ return ` /**
400
+ * ${message.description ?? "No message description."}
401
+ *
402
+ * "${message.message}"
403
+ */
404
+ getMessage(
405
+ messageName: "${message.name}",
406
+ substitutions?: string | string[],
407
+ options?: GetMessageOptions,
408
+ ): string;`;
409
+ });
410
+ await writeFileIfDifferent(
411
+ filePath,
412
+ template.replace("{{ overrides }}", overrides.join("\n"))
413
+ );
414
+ return filePath;
415
+ }
416
+ async function writeGlobalsDeclarationFile(config) {
417
+ const filePath = resolve3(config.typesDir, "globals.d.ts");
418
+ const globals2 = [...getGlobals(config), ...getEntrypointGlobals("")];
419
+ await writeFileIfDifferent(
420
+ filePath,
421
+ [
422
+ "// Generated by wxt",
423
+ "export {}",
424
+ "declare global {",
425
+ ...globals2.map((global) => ` const ${global.name}: ${global.type};`),
426
+ "}"
427
+ ].join("\n") + "\n"
428
+ );
429
+ return filePath;
430
+ }
431
+ async function writeMainDeclarationFile(references, config) {
432
+ const dir = config.wxtDir;
433
+ const filePath = resolve3(dir, "wxt.d.ts");
434
+ await writeFileIfDifferent(
435
+ filePath,
436
+ [
437
+ "// Generated by wxt",
438
+ `/// <reference types="vite/client" />`,
439
+ ...references.map(
440
+ (ref) => `/// <reference types="./${normalizePath(relative2(dir, ref))}" />`
441
+ )
442
+ ].join("\n") + "\n"
443
+ );
444
+ return filePath;
445
+ }
446
+ async function writeTsConfigFile(mainReference, config) {
447
+ const dir = config.wxtDir;
448
+ const getTsconfigPath = (path6) => normalizePath(relative2(dir, path6));
449
+ const paths = Object.entries(config.alias).flatMap(([alias, absolutePath]) => {
450
+ const aliasPath = getTsconfigPath(absolutePath);
451
+ return [
452
+ ` "${alias}": ["${aliasPath}"]`,
453
+ ` "${alias}/*": ["${aliasPath}/*"]`
454
+ ];
455
+ }).join(",\n");
456
+ await writeFileIfDifferent(
457
+ resolve3(dir, "tsconfig.json"),
458
+ `{
459
+ "compilerOptions": {
460
+ "target": "ESNext",
461
+ "module": "ESNext",
462
+ "moduleResolution": "Bundler",
463
+ "noEmit": true,
464
+ "esModuleInterop": true,
465
+ "forceConsistentCasingInFileNames": true,
466
+ "resolveJsonModule": true,
467
+ "strict": true,
468
+ "skipLibCheck": true,
469
+ "paths": {
470
+ ${paths}
278
471
  }
279
- };
472
+ },
473
+ "include": [
474
+ "${getTsconfigPath(config.root)}/**/*",
475
+ "./${getTsconfigPath(mainReference)}"
476
+ ],
477
+ "exclude": ["${getTsconfigPath(config.outBaseDir)}"]
478
+ }`
479
+ );
280
480
  }
281
481
 
282
- // src/core/vite-plugins/webextensionPolyfillInlineDeps.ts
283
- function webextensionPolyfillInlineDeps() {
482
+ // src/core/utils/building/get-internal-config.ts
483
+ import { loadConfig } from "c12";
484
+ import path4 from "node:path";
485
+
486
+ // src/core/utils/cache.ts
487
+ import fs4, { ensureDir } from "fs-extra";
488
+ import { dirname as dirname2, resolve as resolve4 } from "path";
489
+ function createFsCache(wxtDir) {
490
+ const getPath = (key) => resolve4(wxtDir, "cache", encodeURIComponent(key));
284
491
  return {
285
- name: "wxt:testing-inline-deps",
286
- config() {
287
- const wxtModules = ["wxt/browser", "wxt/client"];
288
- return {
289
- test: {
290
- server: {
291
- deps: {
292
- inline: [...wxtModules]
293
- }
294
- }
295
- }
296
- };
492
+ async set(key, value) {
493
+ const path6 = getPath(key);
494
+ await ensureDir(dirname2(path6));
495
+ await writeFileIfDifferent(path6, value);
496
+ },
497
+ async get(key) {
498
+ const path6 = getPath(key);
499
+ try {
500
+ return await fs4.readFile(path6, "utf-8");
501
+ } catch {
502
+ return void 0;
503
+ }
297
504
  }
298
505
  };
299
506
  }
300
507
 
301
- // src/core/vite-plugins/devHtmlPrerender.ts
508
+ // src/core/utils/building/get-internal-config.ts
509
+ import consola, { LogLevels } from "consola";
510
+
511
+ // src/core/builders/vite/plugins/devHtmlPrerender.ts
302
512
  import { parseHTML } from "linkedom";
303
- import { dirname, isAbsolute, relative as relative2, resolve as resolve2 } from "path";
513
+ import { dirname as dirname3, isAbsolute, relative as relative3, resolve as resolve5 } from "node:path";
304
514
  var reactRefreshPreamble = "";
305
515
  function devHtmlPrerender(config) {
306
516
  const htmlReloadId = "@wxt/reload-html";
307
- const resolvedHtmlReloadId = resolve2(
517
+ const resolvedHtmlReloadId = resolve5(
308
518
  config.root,
309
519
  "node_modules/wxt/dist/virtual/reload-html.js"
310
520
  );
@@ -338,8 +548,8 @@ function devHtmlPrerender(config) {
338
548
  if (isAbsolute(src)) {
339
549
  element.setAttribute(attr, server.origin + src);
340
550
  } else if (src.startsWith(".")) {
341
- const abs = resolve2(dirname(id), src);
342
- const pathname = relative2(config.root, abs);
551
+ const abs = resolve5(dirname3(id), src);
552
+ const pathname = relative3(config.root, abs);
343
553
  element.setAttribute(attr, `${server.origin}/${pathname}`);
344
554
  }
345
555
  });
@@ -364,11 +574,7 @@ function devHtmlPrerender(config) {
364
574
  const originalUrl = `${server.origin}${ctx.path}`;
365
575
  const name = getEntrypointName(config.entrypointsDir, ctx.filename);
366
576
  const url = `${server.origin}/${name}.html`;
367
- const serverHtml = await server.transformIndexHtml(
368
- url,
369
- html,
370
- originalUrl
371
- );
577
+ const serverHtml = await server.transformHtml(url, html, originalUrl);
372
578
  const { document } = parseHTML(serverHtml);
373
579
  const reactRefreshScript = Array.from(
374
580
  document.querySelectorAll("script[type=module]")
@@ -416,59 +622,135 @@ function devHtmlPrerender(config) {
416
622
  ];
417
623
  }
418
624
 
419
- // src/core/vite-plugins/devServerGlobals.ts
420
- function devServerGlobals(internalConfig) {
625
+ // src/core/builders/vite/plugins/devServerGlobals.ts
626
+ function devServerGlobals(config) {
421
627
  return {
422
628
  name: "wxt:dev-server-globals",
423
629
  config() {
424
- if (internalConfig.server == null || internalConfig.command == "build")
630
+ if (config.server == null || config.command == "build")
425
631
  return;
426
632
  return {
427
633
  define: {
428
634
  __DEV_SERVER_PROTOCOL__: JSON.stringify("ws:"),
429
- __DEV_SERVER_HOSTNAME__: JSON.stringify(
430
- internalConfig.server.hostname
431
- ),
432
- __DEV_SERVER_PORT__: JSON.stringify(internalConfig.server.port)
635
+ __DEV_SERVER_HOSTNAME__: JSON.stringify(config.server.hostname),
636
+ __DEV_SERVER_PORT__: JSON.stringify(config.server.port)
433
637
  }
434
638
  };
435
639
  }
436
640
  };
437
641
  }
438
642
 
439
- // src/core/vite-plugins/multipageMove.ts
440
- import { dirname as dirname2, extname as extname2, resolve as resolve3 } from "node:path";
441
- import fs, { ensureDir } from "fs-extra";
442
- function multipageMove(entrypoints, config) {
443
- return {
444
- name: "wxt:multipage-move",
445
- async writeBundle(_, bundle) {
446
- for (const oldBundlePath in bundle) {
447
- const entrypoint = entrypoints.find(
448
- (entry) => !!normalizePath(entry.inputPath).endsWith(oldBundlePath)
449
- );
450
- if (entrypoint == null) {
451
- config.logger.debug(
452
- `No entrypoint found for ${oldBundlePath}, leaving in chunks directory`
453
- );
454
- continue;
455
- }
456
- const newBundlePath = getEntrypointBundlePath(
457
- entrypoint,
458
- config.outDir,
459
- extname2(oldBundlePath)
460
- );
461
- if (newBundlePath === oldBundlePath) {
462
- config.logger.debug(
643
+ // src/core/utils/network.ts
644
+ import dns from "node:dns";
645
+
646
+ // src/core/utils/time.ts
647
+ function formatDuration(duration) {
648
+ if (duration < 1e3)
649
+ return `${duration} ms`;
650
+ if (duration < 1e4)
651
+ return `${(duration / 1e3).toFixed(3)} s`;
652
+ if (duration < 6e4)
653
+ return `${(duration / 1e3).toFixed(1)} s`;
654
+ return `${(duration / 1e3).toFixed(0)} s`;
655
+ }
656
+ function withTimeout(promise, duration) {
657
+ return new Promise((res, rej) => {
658
+ const timeout = setTimeout(() => {
659
+ rej(`Promise timed out after ${duration}ms`);
660
+ }, duration);
661
+ promise.then(res).catch(rej).finally(() => clearTimeout(timeout));
662
+ });
663
+ }
664
+
665
+ // src/core/utils/network.ts
666
+ function isOffline() {
667
+ const isOffline2 = new Promise((res) => {
668
+ dns.resolve("google.com", (err) => {
669
+ if (err == null) {
670
+ res(false);
671
+ } else {
672
+ res(true);
673
+ }
674
+ });
675
+ });
676
+ return withTimeout(isOffline2, 1e3).catch(() => true);
677
+ }
678
+ async function isOnline() {
679
+ const offline = await isOffline();
680
+ return !offline;
681
+ }
682
+ async function fetchCached(url, config) {
683
+ let content = "";
684
+ if (await isOnline()) {
685
+ const res = await fetch(url);
686
+ if (res.status < 300) {
687
+ content = await res.text();
688
+ await config.fsCache.set(url, content);
689
+ } else {
690
+ config.logger.debug(
691
+ `Failed to download "${url}", falling back to cache...`
692
+ );
693
+ }
694
+ }
695
+ if (!content)
696
+ content = await config.fsCache.get(url) ?? "";
697
+ if (!content)
698
+ throw Error(
699
+ `Offline and "${url}" has not been cached. Try again when online.`
700
+ );
701
+ return content;
702
+ }
703
+
704
+ // src/core/builders/vite/plugins/download.ts
705
+ function download(config) {
706
+ return {
707
+ name: "wxt:download",
708
+ resolveId(id) {
709
+ if (id.startsWith("url:"))
710
+ return "\0" + id;
711
+ },
712
+ async load(id) {
713
+ if (!id.startsWith("\0url:"))
714
+ return;
715
+ const url = id.replace("\0url:", "");
716
+ return await fetchCached(url, config);
717
+ }
718
+ };
719
+ }
720
+
721
+ // src/core/builders/vite/plugins/multipageMove.ts
722
+ import { dirname as dirname4, extname, resolve as resolve6, join } from "node:path";
723
+ import fs5, { ensureDir as ensureDir2 } from "fs-extra";
724
+ function multipageMove(entrypoints, config) {
725
+ return {
726
+ name: "wxt:multipage-move",
727
+ async writeBundle(_, bundle) {
728
+ for (const oldBundlePath in bundle) {
729
+ const entrypoint = entrypoints.find(
730
+ (entry) => !!normalizePath(entry.inputPath).endsWith(oldBundlePath)
731
+ );
732
+ if (entrypoint == null) {
733
+ config.logger.debug(
734
+ `No entrypoint found for ${oldBundlePath}, leaving in chunks directory`
735
+ );
736
+ continue;
737
+ }
738
+ const newBundlePath = getEntrypointBundlePath(
739
+ entrypoint,
740
+ config.outDir,
741
+ extname(oldBundlePath)
742
+ );
743
+ if (newBundlePath === oldBundlePath) {
744
+ config.logger.debug(
463
745
  "HTML file is already in the correct location",
464
746
  oldBundlePath
465
747
  );
466
748
  continue;
467
749
  }
468
- const oldAbsPath = resolve3(config.outDir, oldBundlePath);
469
- const newAbsPath = resolve3(config.outDir, newBundlePath);
470
- await ensureDir(dirname2(newAbsPath));
471
- await fs.move(oldAbsPath, newAbsPath, { overwrite: true });
750
+ const oldAbsPath = resolve6(config.outDir, oldBundlePath);
751
+ const newAbsPath = resolve6(config.outDir, newBundlePath);
752
+ await ensureDir2(dirname4(newAbsPath));
753
+ await fs5.move(oldAbsPath, newAbsPath, { overwrite: true });
472
754
  const renamedChunk = {
473
755
  ...bundle[oldBundlePath],
474
756
  fileName: newBundlePath
@@ -476,13 +758,59 @@ function multipageMove(entrypoints, config) {
476
758
  delete bundle[oldBundlePath];
477
759
  bundle[newBundlePath] = renamedChunk;
478
760
  }
761
+ removeEmptyDirs(config.outDir);
479
762
  }
480
763
  };
481
764
  }
765
+ async function removeEmptyDirs(dir) {
766
+ const files = await fs5.readdir(dir);
767
+ for (const file of files) {
768
+ const filePath = join(dir, file);
769
+ const stats = await fs5.stat(filePath);
770
+ if (stats.isDirectory()) {
771
+ await removeEmptyDirs(filePath);
772
+ }
773
+ }
774
+ try {
775
+ await fs5.rmdir(dir);
776
+ } catch {
777
+ }
778
+ }
482
779
 
483
- // src/core/vite-plugins/virtualEntrypoint.ts
484
- import fs2 from "fs-extra";
485
- import { resolve as resolve4 } from "path";
780
+ // src/core/builders/vite/plugins/unimport.ts
781
+ import { createUnimport as createUnimport2 } from "unimport";
782
+ import { extname as extname2 } from "path";
783
+ var ENABLED_EXTENSIONS = /* @__PURE__ */ new Set([
784
+ ".js",
785
+ ".jsx",
786
+ ".ts",
787
+ ".tsx",
788
+ ".vue",
789
+ ".svelte"
790
+ ]);
791
+ function unimport(config) {
792
+ const options = getUnimportOptions(config);
793
+ if (options === false)
794
+ return [];
795
+ const unimport2 = createUnimport2(options);
796
+ return {
797
+ name: "wxt:unimport",
798
+ async config() {
799
+ await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
800
+ },
801
+ async transform(code, id) {
802
+ if (id.includes("node_modules"))
803
+ return;
804
+ if (!ENABLED_EXTENSIONS.has(extname2(id)))
805
+ return;
806
+ return unimport2.injectImports(code, id);
807
+ }
808
+ };
809
+ }
810
+
811
+ // src/core/builders/vite/plugins/virtualEntrypoint.ts
812
+ import fs6 from "fs-extra";
813
+ import { resolve as resolve7 } from "path";
486
814
  function virtualEntrypoint(type, config) {
487
815
  const virtualId = `virtual:wxt-${type}?`;
488
816
  const resolvedVirtualId = `\0${virtualId}`;
@@ -499,8 +827,8 @@ function virtualEntrypoint(type, config) {
499
827
  if (!id.startsWith(resolvedVirtualId))
500
828
  return;
501
829
  const inputPath = id.replace(resolvedVirtualId, "");
502
- const template = await fs2.readFile(
503
- resolve4(
830
+ const template = await fs6.readFile(
831
+ resolve7(
504
832
  config.root,
505
833
  `node_modules/wxt/dist/virtual/${type}-entrypoint.js`
506
834
  ),
@@ -511,7 +839,24 @@ function virtualEntrypoint(type, config) {
511
839
  };
512
840
  }
513
841
 
514
- // src/core/vite-plugins/noopBackground.ts
842
+ // src/core/builders/vite/plugins/tsconfigPaths.ts
843
+ function tsconfigPaths(config) {
844
+ return {
845
+ name: "wxt:aliases",
846
+ async config() {
847
+ return {
848
+ resolve: {
849
+ alias: config.alias
850
+ }
851
+ };
852
+ }
853
+ };
854
+ }
855
+
856
+ // src/core/utils/constants.ts
857
+ var VIRTUAL_NOOP_BACKGROUND_MODULE_ID = "virtual:user-background";
858
+
859
+ // src/core/builders/vite/plugins/noopBackground.ts
515
860
  function noopBackground() {
516
861
  const virtualModuleId = VIRTUAL_NOOP_BACKGROUND_MODULE_ID;
517
862
  const resolvedVirtualModuleId = "\0" + virtualModuleId;
@@ -529,9 +874,8 @@ export default defineBackground(() => void 0)`;
529
874
  }
530
875
  };
531
876
  }
532
- var VIRTUAL_NOOP_BACKGROUND_MODULE_ID = "virtual:user-background";
533
877
 
534
- // src/core/vite-plugins/cssEntrypoints.ts
878
+ // src/core/builders/vite/plugins/cssEntrypoints.ts
535
879
  function cssEntrypoints(entrypoint, config) {
536
880
  return {
537
881
  name: "wxt:css-entrypoint",
@@ -545,568 +889,320 @@ function cssEntrypoints(entrypoint, config) {
545
889
  }
546
890
  }
547
891
  };
548
- },
549
- generateBundle(_, bundle) {
550
- Object.keys(bundle).forEach((file) => {
551
- if (file.endsWith(".js"))
552
- delete bundle[file];
553
- });
554
- }
555
- };
556
- }
557
-
558
- // src/core/vite-plugins/bundleAnalysis.ts
559
- import { visualizer } from "rollup-plugin-visualizer";
560
- var increment = 0;
561
- function bundleAnalysis() {
562
- return visualizer({
563
- emitFile: true,
564
- template: "raw-data",
565
- filename: `stats-${increment++}.json`
566
- });
567
- }
568
-
569
- // src/core/vite-plugins/excludeBrowserPolyfill.ts
570
- function excludeBrowserPolyfill(config) {
571
- const virtualId = "virtual:wxt-webextension-polyfill-disabled";
572
- return {
573
- name: "wxt:exclude-browser-polyfill",
574
- config() {
575
- if (config.experimental.includeBrowserPolyfill)
576
- return;
577
- return {
578
- resolve: {
579
- alias: {
580
- "webextension-polyfill": virtualId
581
- }
582
- }
583
- };
584
- },
585
- load(id) {
586
- if (id === virtualId) {
587
- return "export default chrome";
588
- }
589
- }
590
- };
591
- }
592
-
593
- // src/core/utils/arrays.ts
594
- function every(array, predicate) {
595
- for (let i = 0; i < array.length; i++)
596
- if (!predicate(array[i], i))
597
- return false;
598
- return true;
599
- }
600
-
601
- // src/core/utils/building/detect-dev-changes.ts
602
- function detectDevChanges(changedFiles, currentOutput) {
603
- if (currentOutput == null)
604
- return { type: "no-change" };
605
- const changedSteps = new Set(
606
- changedFiles.flatMap(
607
- (changedFile) => findEffectedSteps(changedFile, currentOutput)
608
- )
609
- );
610
- if (changedSteps.size === 0)
611
- return { type: "no-change" };
612
- const unchangedOutput = {
613
- manifest: currentOutput.manifest,
614
- steps: [],
615
- publicAssets: []
616
- };
617
- const changedOutput = {
618
- manifest: currentOutput.manifest,
619
- steps: [],
620
- publicAssets: []
621
- };
622
- for (const step of currentOutput.steps) {
623
- if (changedSteps.has(step)) {
624
- changedOutput.steps.push(step);
625
- } else {
626
- unchangedOutput.steps.push(step);
627
- }
628
- }
629
- for (const asset of currentOutput.publicAssets) {
630
- if (changedSteps.has(asset)) {
631
- changedOutput.publicAssets.push(asset);
632
- } else {
633
- unchangedOutput.publicAssets.push(asset);
634
- }
635
- }
636
- const isOnlyHtmlChanges = changedFiles.length > 0 && every(changedFiles, ([_, file]) => file.endsWith(".html"));
637
- if (isOnlyHtmlChanges) {
638
- return {
639
- type: "html-reload",
640
- cachedOutput: unchangedOutput,
641
- rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
642
- };
643
- }
644
- const isOnlyContentScripts = changedOutput.steps.length > 0 && every(
645
- changedOutput.steps.flatMap((step) => step.entrypoints),
646
- (entry) => entry.type === "content-script"
647
- );
648
- if (isOnlyContentScripts) {
649
- return {
650
- type: "content-script-reload",
651
- cachedOutput: unchangedOutput,
652
- changedSteps: changedOutput.steps,
653
- rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
654
- };
655
- }
656
- return {
657
- type: "extension-reload",
658
- cachedOutput: unchangedOutput,
659
- rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
660
- };
661
- }
662
- function findEffectedSteps(changedFile, currentOutput) {
663
- const changes = [];
664
- const changedPath = normalizePath(changedFile[1]);
665
- const isChunkEffected = (chunk) => (
666
- // If it's an HTML file with the same path, is is effected because HTML files need to be pre-rendered
667
- // fileName is normalized, relative bundle path
668
- chunk.type === "asset" && changedPath.endsWith(chunk.fileName) || // If it's a chunk that depends on the changed file, it is effected
669
- // moduleIds are absolute, normalized paths
670
- chunk.type === "chunk" && chunk.moduleIds.includes(changedPath)
671
- );
672
- for (const step of currentOutput.steps) {
673
- const effectedChunk = step.chunks.find((chunk) => isChunkEffected(chunk));
674
- if (effectedChunk)
675
- changes.push(step);
676
- }
677
- const effectedAsset = currentOutput.publicAssets.find(
678
- (chunk) => isChunkEffected(chunk)
679
- );
680
- if (effectedAsset)
681
- changes.push(effectedAsset);
682
- return changes;
683
- }
684
-
685
- // src/core/utils/building/find-entrypoints.ts
686
- import { relative as relative4, resolve as resolve12 } from "path";
687
- import fs12 from "fs-extra";
688
- import { minimatch } from "minimatch";
689
- import { parseHTML as parseHTML2 } from "linkedom";
690
- import JSON5 from "json5";
691
-
692
- // src/core/utils/building/build-entrypoints.ts
693
- import * as vite from "vite";
694
-
695
- // src/core/utils/fs.ts
696
- import fs3 from "fs-extra";
697
- import glob from "fast-glob";
698
- import path3 from "node:path";
699
- async function writeFileIfDifferent(file, newContents) {
700
- const existingContents = await fs3.readFile(file, "utf-8").catch(() => void 0);
701
- if (existingContents !== newContents) {
702
- await fs3.writeFile(file, newContents);
703
- }
704
- }
705
- async function getPublicFiles(config) {
706
- if (!await fs3.exists(config.publicDir))
707
- return [];
708
- const files = await glob("**/*", { cwd: config.publicDir });
709
- return files.map(unnormalizePath);
710
- }
711
- async function removeEmptyDirs(dir) {
712
- const files = await fs3.readdir(dir);
713
- for (const file of files) {
714
- const filePath = path3.join(dir, file);
715
- const stats = await fs3.stat(filePath);
716
- if (stats.isDirectory()) {
717
- await removeEmptyDirs(filePath);
718
- }
719
- }
720
- try {
721
- await fs3.rmdir(dir);
722
- } catch {
723
- }
724
- }
725
-
726
- // src/core/utils/building/build-entrypoints.ts
727
- import fs4 from "fs-extra";
728
- import { dirname as dirname3, resolve as resolve5 } from "path";
729
- import pc from "picocolors";
730
- async function buildEntrypoints(groups, config, spinner) {
731
- const steps = [];
732
- for (let i = 0; i < groups.length; i++) {
733
- const group = groups[i];
734
- spinner.text = pc.dim(`[${i + 1}/${groups.length}]`) + ` ${[group].flat().map((e) => e.name).join(pc.dim(", "))}`;
735
- const step = Array.isArray(group) ? await buildMultipleEntrypoints(group, config) : await buildSingleEntrypoint(group, config);
736
- steps.push(step);
737
- }
738
- const publicAssets = await copyPublicDirectory(config);
739
- await removeEmptyDirs(config.outDir);
740
- return { publicAssets, steps };
741
- }
742
- async function buildSingleEntrypoint(entrypoint, config) {
743
- const isVirtual = [
744
- "background",
745
- "content-script",
746
- "unlisted-script"
747
- ].includes(entrypoint.type);
748
- const entry = isVirtual ? `virtual:wxt-${entrypoint.type}?${entrypoint.inputPath}` : entrypoint.inputPath;
749
- const plugins = [];
750
- if (entrypoint.type === "content-script-style" || entrypoint.type === "unlisted-style") {
751
- plugins.push(cssEntrypoints(entrypoint, config));
752
- }
753
- const libMode = {
754
- mode: config.mode,
755
- plugins,
756
- build: {
757
- lib: {
758
- entry,
759
- formats: ["iife"],
760
- name: "_",
761
- fileName: entrypoint.name
762
- },
763
- rollupOptions: {
764
- output: {
765
- // There's only a single output for this build, so we use the desired bundle path for the
766
- // entry output (like "content-scripts/overlay.js")
767
- entryFileNames: getEntrypointBundlePath(
768
- entrypoint,
769
- config.outDir,
770
- ".js"
771
- ),
772
- // Output content script CSS to `content-scripts/`, but all other scripts are written to
773
- // `assets/`.
774
- assetFileNames: ({ name }) => {
775
- if (entrypoint.type === "content-script" && name?.endsWith("css")) {
776
- return `content-scripts/${entrypoint.name}.[ext]`;
777
- } else {
778
- return `assets/${entrypoint.name}.[ext]`;
779
- }
780
- }
781
- }
782
- }
783
- },
784
- define: {
785
- // See https://github.com/aklinker1/vite-plugin-web-extension/issues/96
786
- "process.env.NODE_ENV": JSON.stringify(config.mode)
787
- }
788
- };
789
- for (const global of getEntrypointGlobals(config, entrypoint.name)) {
790
- libMode.define[global.name] = JSON.stringify(global.value);
791
- }
792
- const entryConfig = vite.mergeConfig(
793
- libMode,
794
- await config.vite(config.env)
795
- );
796
- const result = await vite.build(entryConfig);
797
- return {
798
- entrypoints: entrypoint,
799
- chunks: getBuildOutputChunks(result)
800
- };
801
- }
802
- async function buildMultipleEntrypoints(entrypoints, config) {
803
- const multiPage = {
804
- mode: config.mode,
805
- plugins: [multipageMove(entrypoints, config)],
806
- build: {
807
- rollupOptions: {
808
- input: entrypoints.reduce((input, entry) => {
809
- input[entry.name] = entry.inputPath;
810
- return input;
811
- }, {}),
812
- output: {
813
- // Include a hash to prevent conflicts
814
- chunkFileNames: "chunks/[name]-[hash].js",
815
- // Include a hash to prevent conflicts
816
- entryFileNames: "chunks/[name]-[hash].js",
817
- // We can't control the "name", so we need a hash to prevent conflicts
818
- assetFileNames: "assets/[name]-[hash].[ext]"
819
- }
820
- }
821
- },
822
- define: {}
823
- };
824
- for (const global of getEntrypointGlobals(config, "html")) {
825
- multiPage.define[global.name] = JSON.stringify(global.value);
826
- }
827
- const entryConfig = vite.mergeConfig(
828
- multiPage,
829
- await config.vite(config.env)
830
- );
831
- const result = await vite.build(entryConfig);
832
- return {
833
- entrypoints,
834
- chunks: getBuildOutputChunks(result)
835
- };
836
- }
837
- function getBuildOutputChunks(result) {
838
- if ("on" in result)
839
- throw Error("wxt does not support vite watch mode.");
840
- if (Array.isArray(result))
841
- return result.flatMap(({ output }) => output);
842
- return result.output;
843
- }
844
- async function copyPublicDirectory(config) {
845
- const files = await getPublicFiles(config);
846
- if (files.length === 0)
847
- return [];
848
- const publicAssets = [];
849
- for (const file of files) {
850
- const srcPath = resolve5(config.publicDir, file);
851
- const outPath = resolve5(config.outDir, file);
852
- await fs4.ensureDir(dirname3(outPath));
853
- await fs4.copyFile(srcPath, outPath);
854
- publicAssets.push({
855
- type: "asset",
856
- fileName: file,
857
- name: file,
858
- needsCodeReference: false,
859
- source: await fs4.readFile(srcPath)
860
- });
861
- }
862
- return publicAssets;
863
- }
864
-
865
- // src/core/utils/building/generate-wxt-dir.ts
866
- import { createUnimport as createUnimport2 } from "unimport";
867
- import fs5 from "fs-extra";
868
- import { relative as relative3, resolve as resolve6 } from "path";
869
- import path4 from "node:path";
870
-
871
- // src/core/utils/i18n.ts
872
- var predefinedMessages = {
873
- "@@extension_id": {
874
- message: "<browser.runtime.id>",
875
- 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."
876
- },
877
- "@@ui_locale": {
878
- message: "<browser.i18n.getUiLocale()>",
879
- description: ""
880
- },
881
- "@@bidi_dir": {
882
- message: "<ltr|rtl>",
883
- 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.'
884
- },
885
- "@@bidi_reversed_dir": {
886
- message: "<rtl|ltr>",
887
- description: `If the @@bidi_dir is "ltr", then this is "rtl"; otherwise, it's "ltr".`
888
- },
889
- "@@bidi_start_edge": {
890
- message: "<left|right>",
891
- description: `If the @@bidi_dir is "ltr", then this is "left"; otherwise, it's "right".`
892
- },
893
- "@@bidi_end_edge": {
894
- message: "<right|left>",
895
- description: `If the @@bidi_dir is "ltr", then this is "right"; otherwise, it's "left".`
896
- }
897
- };
898
- function parseI18nMessages(messagesJson) {
899
- return Object.entries({
900
- ...predefinedMessages,
901
- ...messagesJson
902
- }).map(([name, details]) => ({
903
- name,
904
- ...details
905
- }));
906
- }
907
-
908
- // src/core/utils/building/generate-wxt-dir.ts
909
- async function generateTypesDir(entrypoints, config) {
910
- await fs5.ensureDir(config.typesDir);
911
- const references = [];
912
- const imports = getUnimportOptions(config);
913
- if (imports !== false) {
914
- references.push(await writeImportsDeclarationFile(config, imports));
915
- }
916
- references.push(await writePathsDeclarationFile(entrypoints, config));
917
- references.push(await writeI18nDeclarationFile(config));
918
- references.push(await writeGlobalsDeclarationFile(config));
919
- const mainReference = await writeMainDeclarationFile(references, config);
920
- await writeTsConfigFile(mainReference, config);
921
- }
922
- async function writeImportsDeclarationFile(config, unimportOptions) {
923
- const filePath = resolve6(config.typesDir, "imports.d.ts");
924
- const unimport2 = createUnimport2(unimportOptions);
925
- await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
926
- await writeFileIfDifferent(
927
- filePath,
928
- ["// Generated by wxt", await unimport2.generateTypeDeclarations()].join(
929
- "\n"
930
- ) + "\n"
931
- );
932
- return filePath;
933
- }
934
- async function writePathsDeclarationFile(entrypoints, config) {
935
- const filePath = resolve6(config.typesDir, "paths.d.ts");
936
- const unions = entrypoints.map(
937
- (entry) => getEntrypointBundlePath(
938
- entry,
939
- config.outDir,
940
- entry.inputPath.endsWith(".html") ? ".html" : ".js"
941
- )
942
- ).concat(await getPublicFiles(config)).map(normalizePath).map((path7) => ` | "/${path7}"`).sort().join("\n");
943
- const template = `// Generated by wxt
944
- import "wxt/browser";
945
-
946
- declare module "wxt/browser" {
947
- export type PublicPath =
948
- {{ union }}
949
- export interface WxtRuntime extends Runtime.Static {
950
- getURL(path: PublicPath): string;
951
- }
952
- }
953
- `;
954
- await writeFileIfDifferent(
955
- filePath,
956
- template.replace("{{ union }}", unions || " | never")
957
- );
958
- return filePath;
959
- }
960
- async function writeI18nDeclarationFile(config) {
961
- const filePath = resolve6(config.typesDir, "i18n.d.ts");
962
- const defaultLocale = config.manifest.default_locale;
963
- const template = `// Generated by wxt
964
- import "wxt/browser";
965
-
966
- declare module "wxt/browser" {
967
- /**
968
- * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
969
- */
970
- interface GetMessageOptions {
971
- /**
972
- * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
973
- */
974
- escapeLt?: boolean
975
- }
976
-
977
- export interface WxtI18n extends I18n.Static {
978
- {{ overrides }}
979
- }
892
+ },
893
+ generateBundle(_, bundle) {
894
+ Object.keys(bundle).forEach((file) => {
895
+ if (file.endsWith(".js"))
896
+ delete bundle[file];
897
+ });
898
+ }
899
+ };
980
900
  }
981
- `;
982
- let messages;
983
- if (defaultLocale) {
984
- const defaultLocalePath = path4.resolve(
985
- config.publicDir,
986
- "_locales",
987
- defaultLocale,
988
- "messages.json"
989
- );
990
- const content = JSON.parse(await fs5.readFile(defaultLocalePath, "utf-8"));
991
- messages = parseI18nMessages(content);
992
- } else {
993
- messages = parseI18nMessages({});
994
- }
995
- const overrides = messages.map((message) => {
996
- return ` /**
997
- * ${message.description ?? "No message description."}
998
- *
999
- * "${message.message}"
1000
- */
1001
- getMessage(
1002
- messageName: "${message.name}",
1003
- substitutions?: string | string[],
1004
- options?: GetMessageOptions,
1005
- ): string;`;
901
+
902
+ // src/core/builders/vite/plugins/bundleAnalysis.ts
903
+ import { visualizer } from "rollup-plugin-visualizer";
904
+ var increment = 0;
905
+ function bundleAnalysis() {
906
+ return visualizer({
907
+ emitFile: true,
908
+ template: "raw-data",
909
+ filename: `stats-${increment++}.json`
1006
910
  });
1007
- await writeFileIfDifferent(
1008
- filePath,
1009
- template.replace("{{ overrides }}", overrides.join("\n"))
1010
- );
1011
- return filePath;
1012
- }
1013
- async function writeGlobalsDeclarationFile(config) {
1014
- const filePath = resolve6(config.typesDir, "globals.d.ts");
1015
- const globals2 = [...getGlobals(config), ...getEntrypointGlobals(config, "")];
1016
- await writeFileIfDifferent(
1017
- filePath,
1018
- [
1019
- "// Generated by wxt",
1020
- "export {}",
1021
- "declare global {",
1022
- ...globals2.map((global) => ` const ${global.name}: ${global.type};`),
1023
- "}"
1024
- ].join("\n") + "\n"
1025
- );
1026
- return filePath;
1027
911
  }
1028
- async function writeMainDeclarationFile(references, config) {
1029
- const dir = config.wxtDir;
1030
- const filePath = resolve6(dir, "wxt.d.ts");
1031
- await writeFileIfDifferent(
1032
- filePath,
1033
- [
1034
- "// Generated by wxt",
1035
- `/// <reference types="vite/client" />`,
1036
- ...references.map(
1037
- (ref) => `/// <reference types="./${normalizePath(relative3(dir, ref))}" />`
1038
- )
1039
- ].join("\n") + "\n"
1040
- );
1041
- return filePath;
912
+
913
+ // src/core/builders/vite/plugins/globals.ts
914
+ function globals(config) {
915
+ return {
916
+ name: "wxt:globals",
917
+ config() {
918
+ const define = {};
919
+ for (const global of getGlobals(config)) {
920
+ define[global.name] = JSON.stringify(global.value);
921
+ }
922
+ return {
923
+ define
924
+ };
925
+ }
926
+ };
1042
927
  }
1043
- async function writeTsConfigFile(mainReference, config) {
1044
- const dir = config.wxtDir;
1045
- const getTsconfigPath = (path7) => normalizePath(relative3(dir, path7));
1046
- const paths = Object.entries(config.alias).flatMap(([alias, absolutePath]) => {
1047
- const aliasPath = getTsconfigPath(absolutePath);
1048
- return [
1049
- ` "${alias}": ["${aliasPath}"]`,
1050
- ` "${alias}/*": ["${aliasPath}/*"]`
1051
- ];
1052
- }).join(",\n");
1053
- await writeFileIfDifferent(
1054
- resolve6(dir, "tsconfig.json"),
1055
- `{
1056
- "compilerOptions": {
1057
- "target": "ESNext",
1058
- "module": "ESNext",
1059
- "moduleResolution": "Bundler",
1060
- "noEmit": true,
1061
- "esModuleInterop": true,
1062
- "forceConsistentCasingInFileNames": true,
1063
- "resolveJsonModule": true,
1064
- "strict": true,
1065
- "skipLibCheck": true,
1066
- "paths": {
1067
- ${paths}
928
+
929
+ // src/core/builders/vite/plugins/webextensionPolyfillAlias.ts
930
+ import path3 from "node:path";
931
+ function webextensionPolyfillAlias(config) {
932
+ return {
933
+ name: "wxt:webextension-polyfill-test-alias",
934
+ config() {
935
+ return {
936
+ resolve: {
937
+ alias: {
938
+ "webextension-polyfill": path3.resolve(
939
+ config.root,
940
+ "node_modules/wxt/dist/virtual/mock-browser"
941
+ )
942
+ }
943
+ }
944
+ };
1068
945
  }
1069
- },
1070
- "include": [
1071
- "${getTsconfigPath(config.root)}/**/*",
1072
- "./${getTsconfigPath(mainReference)}"
1073
- ],
1074
- "exclude": ["${getTsconfigPath(config.outBaseDir)}"]
1075
- }`
1076
- );
946
+ };
1077
947
  }
1078
948
 
1079
- // src/core/utils/building/get-internal-config.ts
1080
- import { loadConfig } from "c12";
1081
- import path5 from "node:path";
1082
- import * as vite2 from "vite";
949
+ // src/core/builders/vite/plugins/webextensionPolyfillInlineDeps.ts
950
+ function webextensionPolyfillInlineDeps() {
951
+ return {
952
+ name: "wxt:testing-inline-deps",
953
+ config() {
954
+ const wxtModules = ["wxt/browser", "wxt/client"];
955
+ return {
956
+ test: {
957
+ server: {
958
+ deps: {
959
+ inline: [...wxtModules]
960
+ }
961
+ }
962
+ }
963
+ };
964
+ }
965
+ };
966
+ }
1083
967
 
1084
- // src/core/utils/cache.ts
1085
- import fs6, { ensureDir as ensureDir2 } from "fs-extra";
1086
- import { dirname as dirname4, resolve as resolve7 } from "path";
1087
- function createFsCache(wxtDir) {
1088
- const getPath = (key) => resolve7(wxtDir, "cache", encodeURIComponent(key));
968
+ // src/core/builders/vite/plugins/excludeBrowserPolyfill.ts
969
+ function excludeBrowserPolyfill(config) {
970
+ const virtualId = "virtual:wxt-webextension-polyfill-disabled";
1089
971
  return {
1090
- async set(key, value) {
1091
- const path7 = getPath(key);
1092
- await ensureDir2(dirname4(path7));
1093
- await writeFileIfDifferent(path7, value);
972
+ name: "wxt:exclude-browser-polyfill",
973
+ config() {
974
+ if (config.experimental.includeBrowserPolyfill)
975
+ return;
976
+ return {
977
+ resolve: {
978
+ alias: {
979
+ "webextension-polyfill": virtualId
980
+ }
981
+ }
982
+ };
1094
983
  },
1095
- async get(key) {
1096
- const path7 = getPath(key);
1097
- try {
1098
- return await fs6.readFile(path7, "utf-8");
1099
- } catch {
1100
- return void 0;
984
+ load(id) {
985
+ if (id === virtualId) {
986
+ return "export default chrome";
987
+ }
988
+ }
989
+ };
990
+ }
991
+
992
+ // src/core/builders/vite/plugins/entrypointGroupGlobals.ts
993
+ function entrypointGroupGlobals(entrypointGroup) {
994
+ return {
995
+ name: "wxt:entrypoint-group-globals",
996
+ config() {
997
+ const define = {};
998
+ let name = Array.isArray(entrypointGroup) ? "html" : entrypointGroup.name;
999
+ for (const global of getEntrypointGlobals(name)) {
1000
+ define[global.name] = JSON.stringify(global.value);
1001
+ }
1002
+ return {
1003
+ define
1004
+ };
1005
+ }
1006
+ };
1007
+ }
1008
+
1009
+ // src/core/builders/vite/index.ts
1010
+ async function craeteViteBuilder(inlineConfig, userConfig, wxtConfig) {
1011
+ const vite = await import("vite");
1012
+ const getBaseConfig = async () => {
1013
+ const resolvedInlineConfig = await inlineConfig.vite?.(wxtConfig.env) ?? {};
1014
+ const resolvedUserConfig = await userConfig.vite?.(wxtConfig.env) ?? {};
1015
+ const config = vite.mergeConfig(
1016
+ resolvedUserConfig,
1017
+ resolvedInlineConfig
1018
+ );
1019
+ config.root = wxtConfig.root;
1020
+ config.configFile = false;
1021
+ config.logLevel = "warn";
1022
+ config.mode = wxtConfig.mode;
1023
+ config.build ??= {};
1024
+ config.build.outDir = wxtConfig.outDir;
1025
+ config.build.emptyOutDir = false;
1026
+ config.plugins ??= [];
1027
+ config.plugins.push(
1028
+ download(wxtConfig),
1029
+ devHtmlPrerender(wxtConfig),
1030
+ unimport(wxtConfig),
1031
+ virtualEntrypoint("background", wxtConfig),
1032
+ virtualEntrypoint("content-script", wxtConfig),
1033
+ virtualEntrypoint("unlisted-script", wxtConfig),
1034
+ devServerGlobals(wxtConfig),
1035
+ tsconfigPaths(wxtConfig),
1036
+ noopBackground(),
1037
+ globals(wxtConfig),
1038
+ excludeBrowserPolyfill(wxtConfig)
1039
+ );
1040
+ if (wxtConfig.analysis.enabled) {
1041
+ config.plugins.push(bundleAnalysis());
1042
+ }
1043
+ return config;
1044
+ };
1045
+ const getLibModeConfig = (entrypoint) => {
1046
+ const isVirtual = [
1047
+ "background",
1048
+ "content-script",
1049
+ "unlisted-script"
1050
+ ].includes(entrypoint.type);
1051
+ const entry = isVirtual ? `virtual:wxt-${entrypoint.type}?${entrypoint.inputPath}` : entrypoint.inputPath;
1052
+ const plugins = [
1053
+ entrypointGroupGlobals(entrypoint)
1054
+ ];
1055
+ if (entrypoint.type === "content-script-style" || entrypoint.type === "unlisted-style") {
1056
+ plugins.push(cssEntrypoints(entrypoint, wxtConfig));
1057
+ }
1058
+ const libMode = {
1059
+ mode: wxtConfig.mode,
1060
+ plugins,
1061
+ build: {
1062
+ lib: {
1063
+ entry,
1064
+ formats: ["iife"],
1065
+ name: "_",
1066
+ fileName: entrypoint.name
1067
+ },
1068
+ rollupOptions: {
1069
+ output: {
1070
+ // There's only a single output for this build, so we use the desired bundle path for the
1071
+ // entry output (like "content-scripts/overlay.js")
1072
+ entryFileNames: getEntrypointBundlePath(
1073
+ entrypoint,
1074
+ wxtConfig.outDir,
1075
+ ".js"
1076
+ ),
1077
+ // Output content script CSS to `content-scripts/`, but all other scripts are written to
1078
+ // `assets/`.
1079
+ assetFileNames: ({ name }) => {
1080
+ if (entrypoint.type === "content-script" && name?.endsWith("css")) {
1081
+ return `content-scripts/${entrypoint.name}.[ext]`;
1082
+ } else {
1083
+ return `assets/${entrypoint.name}.[ext]`;
1084
+ }
1085
+ }
1086
+ }
1087
+ }
1088
+ },
1089
+ define: {
1090
+ // See https://github.com/aklinker1/vite-plugin-web-extension/issues/96
1091
+ "process.env.NODE_ENV": JSON.stringify(wxtConfig.mode)
1092
+ }
1093
+ };
1094
+ return libMode;
1095
+ };
1096
+ const getMultiPageConfig = (entrypoints) => {
1097
+ return {
1098
+ mode: wxtConfig.mode,
1099
+ plugins: [
1100
+ multipageMove(entrypoints, wxtConfig),
1101
+ entrypointGroupGlobals(entrypoints)
1102
+ ],
1103
+ build: {
1104
+ rollupOptions: {
1105
+ input: entrypoints.reduce((input, entry) => {
1106
+ input[entry.name] = entry.inputPath;
1107
+ return input;
1108
+ }, {}),
1109
+ output: {
1110
+ // Include a hash to prevent conflicts
1111
+ chunkFileNames: "chunks/[name]-[hash].js",
1112
+ // Include a hash to prevent conflicts
1113
+ entryFileNames: "chunks/[name]-[hash].js",
1114
+ // We can't control the "name", so we need a hash to prevent conflicts
1115
+ assetFileNames: "assets/[name]-[hash].[ext]"
1116
+ }
1117
+ }
1118
+ }
1119
+ };
1120
+ };
1121
+ const getCssConfig = (entrypoint) => {
1122
+ return {
1123
+ mode: wxtConfig.mode,
1124
+ plugins: [entrypointGroupGlobals(entrypoint)],
1125
+ build: {
1126
+ rollupOptions: {
1127
+ input: {
1128
+ [entrypoint.name]: entrypoint.inputPath
1129
+ },
1130
+ output: {
1131
+ assetFileNames: () => {
1132
+ if (entrypoint.type === "content-script-style") {
1133
+ return `content-scripts/${entrypoint.name}.[ext]`;
1134
+ } else {
1135
+ return `assets/${entrypoint.name}.[ext]`;
1136
+ }
1137
+ }
1138
+ }
1139
+ }
1101
1140
  }
1141
+ };
1142
+ };
1143
+ return {
1144
+ name: "Vite",
1145
+ version: vite.version,
1146
+ async build(group) {
1147
+ let entryConfig;
1148
+ if (Array.isArray(group))
1149
+ entryConfig = getMultiPageConfig(group);
1150
+ else if (group.inputPath.endsWith(".css"))
1151
+ entryConfig = getCssConfig(group);
1152
+ else
1153
+ entryConfig = getLibModeConfig(group);
1154
+ const buildConfig = vite.mergeConfig(await getBaseConfig(), entryConfig);
1155
+ const result = await vite.build(buildConfig);
1156
+ return {
1157
+ entrypoints: group,
1158
+ chunks: getBuildOutputChunks(result)
1159
+ };
1160
+ },
1161
+ async createServer(info) {
1162
+ const serverConfig = {
1163
+ server: {
1164
+ port: info.port,
1165
+ strictPort: true,
1166
+ host: info.hostname,
1167
+ origin: info.origin
1168
+ }
1169
+ };
1170
+ const baseConfig = await getBaseConfig();
1171
+ const viteServer = await vite.createServer(
1172
+ vite.mergeConfig(baseConfig, serverConfig)
1173
+ );
1174
+ const server = {
1175
+ async listen() {
1176
+ await viteServer.listen(info.port);
1177
+ },
1178
+ transformHtml(...args) {
1179
+ return viteServer.transformIndexHtml(...args);
1180
+ },
1181
+ ws: {
1182
+ send(message, payload) {
1183
+ return viteServer.ws.send(message, payload);
1184
+ },
1185
+ on(message, cb) {
1186
+ viteServer.ws.on(message, cb);
1187
+ }
1188
+ },
1189
+ watcher: viteServer.watcher
1190
+ };
1191
+ return server;
1102
1192
  }
1103
1193
  };
1104
1194
  }
1195
+ function getBuildOutputChunks(result) {
1196
+ if ("on" in result)
1197
+ throw Error("wxt does not support vite watch mode.");
1198
+ if (Array.isArray(result))
1199
+ return result.flatMap(({ output }) => output);
1200
+ return result.output;
1201
+ }
1105
1202
 
1106
1203
  // src/core/utils/building/get-internal-config.ts
1107
- import consola, { LogLevels } from "consola";
1108
- import defu from "defu";
1109
- async function getInternalConfig(inlineConfig, command) {
1204
+ import defu2 from "defu";
1205
+ async function getInternalConfig(inlineConfig, command, server) {
1110
1206
  let userConfig = {};
1111
1207
  let userConfigMetadata;
1112
1208
  if (inlineConfig.configFile !== false) {
@@ -1127,19 +1223,19 @@ async function getInternalConfig(inlineConfig, command) {
1127
1223
  const manifestVersion = mergedConfig.manifestVersion ?? (browser === "firefox" || browser === "safari" ? 2 : 3);
1128
1224
  const mode = mergedConfig.mode ?? (command === "build" ? "production" : "development");
1129
1225
  const env = { browser, command, manifestVersion, mode };
1130
- const root = path5.resolve(
1226
+ const root = path4.resolve(
1131
1227
  inlineConfig.root ?? userConfig.root ?? process.cwd()
1132
1228
  );
1133
- const wxtDir = path5.resolve(root, ".wxt");
1134
- const srcDir = path5.resolve(root, mergedConfig.srcDir ?? root);
1135
- const entrypointsDir = path5.resolve(
1229
+ const wxtDir = path4.resolve(root, ".wxt");
1230
+ const srcDir = path4.resolve(root, mergedConfig.srcDir ?? root);
1231
+ const entrypointsDir = path4.resolve(
1136
1232
  srcDir,
1137
1233
  mergedConfig.entrypointsDir ?? "entrypoints"
1138
1234
  );
1139
- const publicDir = path5.resolve(srcDir, mergedConfig.publicDir ?? "public");
1140
- const typesDir = path5.resolve(wxtDir, "types");
1141
- const outBaseDir = path5.resolve(root, ".output");
1142
- const outDir = path5.resolve(outBaseDir, `${browser}-mv${manifestVersion}`);
1235
+ const publicDir = path4.resolve(srcDir, mergedConfig.publicDir ?? "public");
1236
+ const typesDir = path4.resolve(wxtDir, "types");
1237
+ const outBaseDir = path4.resolve(root, mergedConfig.outDir ?? ".output");
1238
+ const outDir = path4.resolve(outBaseDir, `${browser}-mv${manifestVersion}`);
1143
1239
  const runnerConfig = await loadConfig({
1144
1240
  name: "web-ext",
1145
1241
  cwd: root,
@@ -1155,7 +1251,7 @@ async function getInternalConfig(inlineConfig, command) {
1155
1251
  "~": srcDir,
1156
1252
  "@@": root,
1157
1253
  "~~": root
1158
- }).map(([key, value]) => [key, path5.resolve(root, value)])
1254
+ }).map(([key, value]) => [key, path4.resolve(root, value)])
1159
1255
  );
1160
1256
  const finalConfig = {
1161
1257
  browser,
@@ -1176,8 +1272,6 @@ async function getInternalConfig(inlineConfig, command) {
1176
1272
  runnerConfig,
1177
1273
  srcDir,
1178
1274
  typesDir,
1179
- vite: () => ({}),
1180
- // Real value added after this object is initialized.
1181
1275
  wxtDir,
1182
1276
  zip: resolveInternalZipConfig(root, mergedConfig),
1183
1277
  transformManifest(manifest) {
@@ -1192,10 +1286,18 @@ async function getInternalConfig(inlineConfig, command) {
1192
1286
  alias,
1193
1287
  experimental: {
1194
1288
  includeBrowserPolyfill: mergedConfig.experimental?.includeBrowserPolyfill ?? true
1195
- }
1289
+ },
1290
+ server
1291
+ };
1292
+ const builder = await craeteViteBuilder(
1293
+ inlineConfig,
1294
+ userConfig,
1295
+ finalConfig
1296
+ );
1297
+ return {
1298
+ ...finalConfig,
1299
+ builder
1196
1300
  };
1197
- finalConfig.vite = (env2) => resolveInternalViteConfig(env2, mergedConfig, finalConfig);
1198
- return finalConfig;
1199
1301
  }
1200
1302
  async function resolveManifestConfig(env, manifest) {
1201
1303
  return await (typeof manifest === "function" ? manifest(env) : manifest ?? {});
@@ -1207,26 +1309,18 @@ function mergeInlineConfig(inlineConfig, userConfig) {
1207
1309
  } else if (userConfig.imports == null && inlineConfig.imports == null) {
1208
1310
  imports = void 0;
1209
1311
  } else {
1210
- imports = vite2.mergeConfig(
1211
- userConfig.imports ?? {},
1212
- inlineConfig.imports ?? {}
1213
- );
1312
+ imports = defu2(inlineConfig.imports ?? {}, userConfig.imports ?? {});
1214
1313
  }
1215
1314
  const manifest = async (env) => {
1216
1315
  const user = await resolveManifestConfig(env, userConfig.manifest);
1217
1316
  const inline = await resolveManifestConfig(env, inlineConfig.manifest);
1218
- return vite2.mergeConfig(user, inline);
1317
+ return defu2(inline, user);
1219
1318
  };
1220
- const viteConfig = async (env) => {
1221
- const user = await userConfig.vite?.(env);
1222
- const inline = await inlineConfig.vite?.(env);
1223
- return vite2.mergeConfig(user ?? {}, inline ?? {});
1224
- };
1225
- const runner = defu(
1319
+ const runner = defu2(
1226
1320
  inlineConfig.runner ?? {},
1227
1321
  userConfig.runner ?? {}
1228
1322
  );
1229
- const zip = defu(
1323
+ const zip = defu2(
1230
1324
  inlineConfig.zip ?? {},
1231
1325
  userConfig.zip ?? {}
1232
1326
  );
@@ -1244,7 +1338,7 @@ function mergeInlineConfig(inlineConfig, userConfig) {
1244
1338
  publicDir: inlineConfig.publicDir ?? userConfig.publicDir,
1245
1339
  runner,
1246
1340
  srcDir: inlineConfig.srcDir ?? userConfig.srcDir,
1247
- vite: viteConfig,
1341
+ outDir: inlineConfig.outDir ?? userConfig.outDir,
1248
1342
  zip,
1249
1343
  analysis: {
1250
1344
  enabled: inlineConfig.analysis?.enabled ?? userConfig.analysis?.enabled,
@@ -1257,11 +1351,14 @@ function mergeInlineConfig(inlineConfig, userConfig) {
1257
1351
  experimental: {
1258
1352
  ...userConfig.experimental,
1259
1353
  ...inlineConfig.experimental
1260
- }
1354
+ },
1355
+ vite: void 0,
1356
+ transformManifest: void 0
1261
1357
  };
1262
1358
  }
1263
1359
  function resolveInternalZipConfig(root, mergedConfig) {
1264
1360
  return {
1361
+ name: void 0,
1265
1362
  sourcesTemplate: "{{name}}-{{version}}-sources.zip",
1266
1363
  artifactTemplate: "{{name}}-{{version}}-{{browser}}.zip",
1267
1364
  sourcesRoot: root,
@@ -1280,38 +1377,6 @@ function resolveInternalZipConfig(root, mergedConfig) {
1280
1377
  ]
1281
1378
  };
1282
1379
  }
1283
- async function resolveInternalViteConfig(env, mergedConfig, finalConfig) {
1284
- const internalVite = await mergedConfig.vite?.(env) ?? {};
1285
- internalVite.root = finalConfig.root;
1286
- internalVite.configFile = false;
1287
- internalVite.logLevel = "warn";
1288
- internalVite.mode = env.mode;
1289
- internalVite.build ??= {};
1290
- internalVite.build.outDir = finalConfig.outDir;
1291
- internalVite.build.emptyOutDir = false;
1292
- internalVite.plugins ??= [];
1293
- internalVite.plugins.push(download(finalConfig));
1294
- internalVite.plugins.push(devHtmlPrerender(finalConfig));
1295
- internalVite.plugins.push(unimport(finalConfig));
1296
- internalVite.plugins.push(
1297
- virtualEntrypoint("background", finalConfig)
1298
- );
1299
- internalVite.plugins.push(
1300
- virtualEntrypoint("content-script", finalConfig)
1301
- );
1302
- internalVite.plugins.push(
1303
- virtualEntrypoint("unlisted-script", finalConfig)
1304
- );
1305
- internalVite.plugins.push(devServerGlobals(finalConfig));
1306
- internalVite.plugins.push(tsconfigPaths(finalConfig));
1307
- internalVite.plugins.push(noopBackground());
1308
- if (finalConfig.analysis.enabled) {
1309
- internalVite.plugins.push(bundleAnalysis());
1310
- }
1311
- internalVite.plugins.push(globals(finalConfig));
1312
- internalVite.plugins.push(excludeBrowserPolyfill(finalConfig));
1313
- return internalVite;
1314
- }
1315
1380
 
1316
1381
  // src/core/utils/building/group-entrypoints.ts
1317
1382
  function groupEntrypoints(entrypoints) {
@@ -1375,16 +1440,16 @@ ${noImports}`;
1375
1440
 
1376
1441
  // src/core/utils/building/import-entrypoint.ts
1377
1442
  import { transformSync } from "esbuild";
1378
- async function importEntrypointFile(path7, config) {
1379
- config.logger.debug("Loading file metadata:", path7);
1380
- const normalPath = normalizePath(path7);
1443
+ async function importEntrypointFile(path6, config) {
1444
+ config.logger.debug("Loading file metadata:", path6);
1445
+ const normalPath = normalizePath(path6);
1381
1446
  const unimport2 = createUnimport3({
1382
1447
  ...getUnimportOptions(config),
1383
1448
  // Only allow specific imports, not all from the project
1384
1449
  dirs: []
1385
1450
  });
1386
1451
  await unimport2.init();
1387
- const text = await fs7.readFile(path7, "utf-8");
1452
+ const text = await fs7.readFile(path6, "utf-8");
1388
1453
  const textNoImports = removeProjectImportStatements(text);
1389
1454
  const { code } = await unimport2.injectImports(textNoImports);
1390
1455
  config.logger.debug(
@@ -1415,7 +1480,7 @@ async function importEntrypointFile(path7, config) {
1415
1480
  }
1416
1481
  });
1417
1482
  try {
1418
- const res = await jiti(path7);
1483
+ const res = await jiti(path6);
1419
1484
  return res.default;
1420
1485
  } catch (err) {
1421
1486
  config.logger.error(err);
@@ -1433,14 +1498,13 @@ function getEsbuildOptions(opts) {
1433
1498
 
1434
1499
  // src/core/utils/building/internal-build.ts
1435
1500
  import pc4 from "picocolors";
1436
- import * as vite3 from "vite";
1437
1501
  import fs11 from "fs-extra";
1438
1502
 
1439
1503
  // src/core/utils/log/printBuildSummary.ts
1440
1504
  import { resolve as resolve9 } from "path";
1441
1505
 
1442
1506
  // src/core/utils/log/printFileList.ts
1443
- import path6 from "node:path";
1507
+ import path5 from "node:path";
1444
1508
  import pc2 from "picocolors";
1445
1509
  import fs8 from "fs-extra";
1446
1510
  import { filesize } from "filesize";
@@ -1478,8 +1542,8 @@ async function printFileList(log, header, baseDir, files) {
1478
1542
  const fileRows = await Promise.all(
1479
1543
  files.map(async (file, i) => {
1480
1544
  const parts = [
1481
- path6.relative(process.cwd(), baseDir) + path6.sep,
1482
- path6.relative(baseDir, file)
1545
+ path5.relative(process.cwd(), baseDir) + path5.sep,
1546
+ path5.relative(baseDir, file)
1483
1547
  ];
1484
1548
  const prefix = i === files.length - 1 ? " \u2514\u2500" : " \u251C\u2500";
1485
1549
  const color = getChunkColor(file);
@@ -1670,37 +1734,34 @@ async function getPackageJson(config) {
1670
1734
 
1671
1735
  // src/core/utils/manifest.ts
1672
1736
  import { produce } from "immer";
1673
- import defu2 from "defu";
1737
+ import defu3 from "defu";
1674
1738
  async function writeManifest(manifest, output, config) {
1675
1739
  const str = config.mode === "production" ? JSON.stringify(manifest) : JSON.stringify(manifest, null, 2);
1676
1740
  await fs10.ensureDir(config.outDir);
1677
1741
  await writeFileIfDifferent(resolve11(config.outDir, "manifest.json"), str);
1678
1742
  output.publicAssets.unshift({
1679
1743
  type: "asset",
1680
- fileName: "manifest.json",
1681
- name: "manifest",
1682
- needsCodeReference: false,
1683
- source: str
1744
+ fileName: "manifest.json"
1684
1745
  });
1685
1746
  }
1686
1747
  async function generateMainfest(entrypoints, buildOutput, config) {
1687
1748
  const pkg = await getPackageJson(config);
1688
1749
  const versionName = config.manifest.version_name ?? pkg?.version;
1689
- const version3 = config.manifest.version ?? simplifyVersion(pkg?.version);
1750
+ const version2 = config.manifest.version ?? simplifyVersion(pkg?.version);
1690
1751
  const baseManifest = {
1691
1752
  manifest_version: config.manifestVersion,
1692
1753
  name: pkg?.name,
1693
1754
  description: pkg?.description,
1694
- version: version3,
1755
+ version: version2,
1695
1756
  version_name: (
1696
1757
  // Firefox doesn't support version_name
1697
- config.browser === "firefox" || versionName === version3 ? void 0 : versionName
1758
+ config.browser === "firefox" || versionName === version2 ? void 0 : versionName
1698
1759
  ),
1699
1760
  short_name: pkg?.shortName,
1700
1761
  icons: discoverIcons(buildOutput)
1701
1762
  };
1702
1763
  const userManifest = config.manifest;
1703
- const manifest = defu2(
1764
+ const manifest = defu3(
1704
1765
  userManifest,
1705
1766
  baseManifest
1706
1767
  );
@@ -1722,14 +1783,14 @@ async function generateMainfest(entrypoints, buildOutput, config) {
1722
1783
  return finalManifest;
1723
1784
  }
1724
1785
  function simplifyVersion(versionName) {
1725
- const version3 = /^((0|[1-9][0-9]{0,8})([.](0|[1-9][0-9]{0,8})){0,3}).*$/.exec(
1786
+ const version2 = /^((0|[1-9][0-9]{0,8})([.](0|[1-9][0-9]{0,8})){0,3}).*$/.exec(
1726
1787
  versionName
1727
1788
  )?.[1];
1728
- if (version3 == null)
1789
+ if (version2 == null)
1729
1790
  throw Error(
1730
1791
  `Cannot simplify package.json version "${versionName}" to a valid extension version, "X.Y.Z"`
1731
1792
  );
1732
- return version3;
1793
+ return version2;
1733
1794
  }
1734
1795
  function addEntrypoints(manifest, entrypoints, buildOutput, config) {
1735
1796
  const entriesByType = entrypoints.reduce((map, entrypoint) => {
@@ -2101,7 +2162,7 @@ async function internalBuild(config) {
2101
2162
  const target = `${config.browser}-mv${config.manifestVersion}`;
2102
2163
  config.logger.info(
2103
2164
  `${verb} ${pc4.cyan(target)} for ${pc4.cyan(config.mode)} with ${pc4.green(
2104
- `Vite ${vite3.version}`
2165
+ `${config.builder.name} ${config.builder.version}`
2105
2166
  )}`
2106
2167
  );
2107
2168
  const startTime = Date.now();
@@ -2127,7 +2188,7 @@ async function internalBuild(config) {
2127
2188
  return output;
2128
2189
  }
2129
2190
  async function combineAnalysisStats(config) {
2130
- const { execaCommand } = await import("./execa-Z7B33P3C.js");
2191
+ const { execaCommand } = await import("./execa-4F7CCWCA.js");
2131
2192
  const unixFiles = await glob2(`stats-*.json`, {
2132
2193
  cwd: config.outDir,
2133
2194
  absolute: true
@@ -2150,7 +2211,7 @@ async function findEntrypoints(config) {
2150
2211
  let hasBackground = false;
2151
2212
  const possibleEntrypoints = await Promise.all(
2152
2213
  relativePaths.map(async (relativePath) => {
2153
- const path7 = resolve12(config.entrypointsDir, relativePath);
2214
+ const path6 = resolve12(config.entrypointsDir, relativePath);
2154
2215
  const matchingGlob = pathGlobs.find(
2155
2216
  (glob4) => minimatch(relativePath, glob4)
2156
2217
  );
@@ -2170,23 +2231,23 @@ ${JSON.stringify(
2170
2231
  return;
2171
2232
  switch (type) {
2172
2233
  case "popup":
2173
- return await getPopupEntrypoint(config, path7);
2234
+ return await getPopupEntrypoint(config, path6);
2174
2235
  case "options":
2175
- return await getOptionsEntrypoint(config, path7);
2236
+ return await getOptionsEntrypoint(config, path6);
2176
2237
  case "background":
2177
2238
  hasBackground = true;
2178
- return await getBackgroundEntrypoint(config, path7);
2239
+ return await getBackgroundEntrypoint(config, path6);
2179
2240
  case "content-script":
2180
- return await getContentScriptEntrypoint(config, path7);
2241
+ return await getContentScriptEntrypoint(config, path6);
2181
2242
  case "unlisted-page":
2182
- return await getUnlistedPageEntrypoint(config, path7);
2243
+ return await getUnlistedPageEntrypoint(config, path6);
2183
2244
  case "unlisted-script":
2184
- return await getUnlistedScriptEntrypoint(config, path7);
2245
+ return await getUnlistedScriptEntrypoint(config, path6);
2185
2246
  case "content-script-style":
2186
2247
  return {
2187
2248
  type,
2188
- name: getEntrypointName(config.entrypointsDir, path7),
2189
- inputPath: path7,
2249
+ name: getEntrypointName(config.entrypointsDir, path6),
2250
+ inputPath: path6,
2190
2251
  outputDir: resolve12(config.outDir, CONTENT_SCRIPT_OUT_DIR),
2191
2252
  options: {
2192
2253
  include: void 0,
@@ -2196,8 +2257,8 @@ ${JSON.stringify(
2196
2257
  default:
2197
2258
  return {
2198
2259
  type,
2199
- name: getEntrypointName(config.entrypointsDir, path7),
2200
- inputPath: path7,
2260
+ name: getEntrypointName(config.entrypointsDir, path6),
2261
+ inputPath: path6,
2201
2262
  outputDir: config.outDir,
2202
2263
  options: {
2203
2264
  include: void 0,
@@ -2260,8 +2321,8 @@ function getHtmlBaseOptions(document) {
2260
2321
  }
2261
2322
  return options;
2262
2323
  }
2263
- async function getPopupEntrypoint(config, path7) {
2264
- const content = await fs12.readFile(path7, "utf-8");
2324
+ async function getPopupEntrypoint(config, path6) {
2325
+ const content = await fs12.readFile(path6, "utf-8");
2265
2326
  const { document } = parseHTML2(content);
2266
2327
  const options = getHtmlBaseOptions(document);
2267
2328
  const title = document.querySelector("title");
@@ -2290,12 +2351,12 @@ async function getPopupEntrypoint(config, path7) {
2290
2351
  type: "popup",
2291
2352
  name: "popup",
2292
2353
  options,
2293
- inputPath: path7,
2354
+ inputPath: path6,
2294
2355
  outputDir: config.outDir
2295
2356
  };
2296
2357
  }
2297
- async function getOptionsEntrypoint(config, path7) {
2298
- const content = await fs12.readFile(path7, "utf-8");
2358
+ async function getOptionsEntrypoint(config, path6) {
2359
+ const content = await fs12.readFile(path6, "utf-8");
2299
2360
  const { document } = parseHTML2(content);
2300
2361
  const options = getHtmlBaseOptions(document);
2301
2362
  const openInTabContent = document.querySelector("meta[name='manifest.open_in_tab']")?.getAttribute("content");
@@ -2314,25 +2375,25 @@ async function getOptionsEntrypoint(config, path7) {
2314
2375
  type: "options",
2315
2376
  name: "options",
2316
2377
  options,
2317
- inputPath: path7,
2378
+ inputPath: path6,
2318
2379
  outputDir: config.outDir
2319
2380
  };
2320
2381
  }
2321
- async function getUnlistedPageEntrypoint(config, path7) {
2322
- const content = await fs12.readFile(path7, "utf-8");
2382
+ async function getUnlistedPageEntrypoint(config, path6) {
2383
+ const content = await fs12.readFile(path6, "utf-8");
2323
2384
  const { document } = parseHTML2(content);
2324
2385
  return {
2325
2386
  type: "unlisted-page",
2326
- name: getEntrypointName(config.entrypointsDir, path7),
2327
- inputPath: path7,
2387
+ name: getEntrypointName(config.entrypointsDir, path6),
2388
+ inputPath: path6,
2328
2389
  outputDir: config.outDir,
2329
2390
  options: getHtmlBaseOptions(document)
2330
2391
  };
2331
2392
  }
2332
- async function getUnlistedScriptEntrypoint(config, path7) {
2333
- const name = getEntrypointName(config.entrypointsDir, path7);
2393
+ async function getUnlistedScriptEntrypoint(config, path6) {
2394
+ const name = getEntrypointName(config.entrypointsDir, path6);
2334
2395
  const defaultExport = await importEntrypointFile(
2335
- path7,
2396
+ path6,
2336
2397
  config
2337
2398
  );
2338
2399
  if (defaultExport == null) {
@@ -2345,17 +2406,17 @@ async function getUnlistedScriptEntrypoint(config, path7) {
2345
2406
  return {
2346
2407
  type: "unlisted-script",
2347
2408
  name,
2348
- inputPath: path7,
2409
+ inputPath: path6,
2349
2410
  outputDir: config.outDir,
2350
2411
  options
2351
2412
  };
2352
2413
  }
2353
- async function getBackgroundEntrypoint(config, path7) {
2414
+ async function getBackgroundEntrypoint(config, path6) {
2354
2415
  const name = "background";
2355
2416
  let options = {};
2356
- if (path7 !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) {
2417
+ if (path6 !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) {
2357
2418
  const defaultExport = await importEntrypointFile(
2358
- path7,
2419
+ path6,
2359
2420
  config
2360
2421
  );
2361
2422
  if (defaultExport == null) {
@@ -2369,7 +2430,7 @@ async function getBackgroundEntrypoint(config, path7) {
2369
2430
  return {
2370
2431
  type: "background",
2371
2432
  name,
2372
- inputPath: path7,
2433
+ inputPath: path6,
2373
2434
  outputDir: config.outDir,
2374
2435
  options: {
2375
2436
  ...options,
@@ -2378,9 +2439,9 @@ async function getBackgroundEntrypoint(config, path7) {
2378
2439
  }
2379
2440
  };
2380
2441
  }
2381
- async function getContentScriptEntrypoint(config, path7) {
2382
- const name = getEntrypointName(config.entrypointsDir, path7);
2383
- const { main: _, ...options } = await importEntrypointFile(path7, config);
2442
+ async function getContentScriptEntrypoint(config, path6) {
2443
+ const name = getEntrypointName(config.entrypointsDir, path6);
2444
+ const { main: _, ...options } = await importEntrypointFile(path6, config);
2384
2445
  if (options == null) {
2385
2446
  throw Error(
2386
2447
  `${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`
@@ -2389,7 +2450,7 @@ async function getContentScriptEntrypoint(config, path7) {
2389
2450
  return {
2390
2451
  type: "content-script",
2391
2452
  name,
2392
- inputPath: path7,
2453
+ inputPath: path6,
2393
2454
  outputDir: resolve12(config.outDir, CONTENT_SCRIPT_OUT_DIR),
2394
2455
  options
2395
2456
  };
@@ -2438,9 +2499,12 @@ var PATH_GLOB_TO_TYPE_MAP = {
2438
2499
  var CONTENT_SCRIPT_OUT_DIR = "content-scripts";
2439
2500
 
2440
2501
  export {
2502
+ detectDevChanges,
2441
2503
  getEntrypointOutputFile,
2442
2504
  getEntrypointBundlePath,
2443
2505
  resolvePerBrowserOption,
2506
+ findEntrypoints,
2507
+ generateTypesDir,
2444
2508
  formatDuration,
2445
2509
  download,
2446
2510
  unimport,
@@ -2448,9 +2512,6 @@ export {
2448
2512
  globals,
2449
2513
  webextensionPolyfillAlias,
2450
2514
  webextensionPolyfillInlineDeps,
2451
- detectDevChanges,
2452
- findEntrypoints,
2453
- generateTypesDir,
2454
2515
  getInternalConfig,
2455
2516
  kebabCaseAlphanumeric,
2456
2517
  printFileList,