wxt 0.17.9 → 0.17.10

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.
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-VBXJIVYU.js";
4
4
 
5
5
  // package.json
6
- var version = "0.17.9";
6
+ var version = "0.17.10";
7
7
 
8
8
  // src/core/utils/paths.ts
9
9
  import systemPath from "node:path";
@@ -17,187 +17,15 @@ function unnormalizePath(path8) {
17
17
  var CSS_EXTENSIONS = ["css", "scss", "sass", "less", "styl", "stylus"];
18
18
  var CSS_EXTENSIONS_PATTERN = `+(${CSS_EXTENSIONS.join("|")})`;
19
19
 
20
- // src/core/utils/fs.ts
21
- import fs from "fs-extra";
22
- import glob from "fast-glob";
23
- async function writeFileIfDifferent(file, newContents) {
24
- const existingContents = await fs.readFile(file, "utf-8").catch(() => void 0);
25
- if (existingContents !== newContents) {
26
- await fs.writeFile(file, newContents);
27
- }
28
- }
29
- async function getPublicFiles() {
30
- if (!await fs.exists(wxt.config.publicDir))
31
- return [];
32
- const files = await glob("**/*", { cwd: wxt.config.publicDir });
33
- return files.map(unnormalizePath);
34
- }
35
-
36
- // src/core/utils/building/build-entrypoints.ts
37
- import fs2 from "fs-extra";
38
- import { dirname, resolve } from "path";
39
- import pc from "picocolors";
40
- async function buildEntrypoints(groups, spinner) {
41
- const steps = [];
42
- for (let i = 0; i < groups.length; i++) {
43
- const group = groups[i];
44
- const groupNames = [group].flat().map((e) => e.name);
45
- const groupNameColored = groupNames.join(pc.dim(", "));
46
- spinner.text = pc.dim(`[${i + 1}/${groups.length}]`) + ` ${groupNameColored}`;
47
- try {
48
- steps.push(await wxt.config.builder.build(group));
49
- } catch (err) {
50
- spinner.stop().clear();
51
- wxt.logger.error(err);
52
- throw Error(`Failed to build ${groupNames.join(", ")}`, { cause: err });
53
- }
54
- }
55
- const publicAssets = await copyPublicDirectory();
56
- return { publicAssets, steps };
57
- }
58
- async function copyPublicDirectory() {
59
- const files = await getPublicFiles();
60
- if (files.length === 0)
61
- return [];
62
- const publicAssets = [];
63
- for (const file of files) {
64
- const srcPath = resolve(wxt.config.publicDir, file);
65
- const outPath = resolve(wxt.config.outDir, file);
66
- await fs2.ensureDir(dirname(outPath));
67
- await fs2.copyFile(srcPath, outPath);
68
- publicAssets.push({
69
- type: "asset",
70
- fileName: file
71
- });
72
- }
73
- return publicAssets;
74
- }
75
-
76
- // src/core/utils/arrays.ts
77
- function every(array, predicate) {
78
- for (let i = 0; i < array.length; i++)
79
- if (!predicate(array[i], i))
80
- return false;
81
- return true;
82
- }
83
- function some(array, predicate) {
84
- for (let i = 0; i < array.length; i++)
85
- if (predicate(array[i], i))
86
- return true;
87
- return false;
88
- }
89
-
90
- // src/core/utils/building/detect-dev-changes.ts
91
- function detectDevChanges(changedFiles, currentOutput) {
92
- const isConfigChange = some(
93
- changedFiles,
94
- (file) => file === wxt.config.userConfigMetadata.configFile
95
- );
96
- if (isConfigChange)
97
- return { type: "full-restart" };
98
- const isRunnerChange = some(
99
- changedFiles,
100
- (file) => file === wxt.config.runnerConfig.configFile
101
- );
102
- if (isRunnerChange)
103
- return { type: "browser-restart" };
104
- const changedSteps = new Set(
105
- changedFiles.flatMap(
106
- (changedFile) => findEffectedSteps(changedFile, currentOutput)
107
- )
108
- );
109
- if (changedSteps.size === 0)
110
- return { type: "no-change" };
111
- const unchangedOutput = {
112
- manifest: currentOutput.manifest,
113
- steps: [],
114
- publicAssets: []
115
- };
116
- const changedOutput = {
117
- manifest: currentOutput.manifest,
118
- steps: [],
119
- publicAssets: []
120
- };
121
- for (const step of currentOutput.steps) {
122
- if (changedSteps.has(step)) {
123
- changedOutput.steps.push(step);
124
- } else {
125
- unchangedOutput.steps.push(step);
126
- }
127
- }
128
- for (const asset of currentOutput.publicAssets) {
129
- if (changedSteps.has(asset)) {
130
- changedOutput.publicAssets.push(asset);
131
- } else {
132
- unchangedOutput.publicAssets.push(asset);
133
- }
134
- }
135
- const isOnlyHtmlChanges = changedFiles.length > 0 && every(changedFiles, (file) => file.endsWith(".html"));
136
- if (isOnlyHtmlChanges) {
137
- return {
138
- type: "html-reload",
139
- cachedOutput: unchangedOutput,
140
- rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
141
- };
142
- }
143
- const isOnlyContentScripts = changedOutput.steps.length > 0 && every(
144
- changedOutput.steps.flatMap((step) => step.entrypoints),
145
- (entry) => entry.type === "content-script"
146
- );
147
- if (isOnlyContentScripts) {
148
- return {
149
- type: "content-script-reload",
150
- cachedOutput: unchangedOutput,
151
- changedSteps: changedOutput.steps,
152
- rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
153
- };
154
- }
155
- return {
156
- type: "extension-reload",
157
- cachedOutput: unchangedOutput,
158
- rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
159
- };
160
- }
161
- function findEffectedSteps(changedFile, currentOutput) {
162
- const changes = [];
163
- const changedPath = normalizePath(changedFile);
164
- const isChunkEffected = (chunk) => (
165
- // If it's an HTML file with the same path, is is effected because HTML files need to be re-rendered
166
- // - fileName is normalized, relative bundle path, "<entrypoint-name>.html"
167
- chunk.type === "asset" && changedPath.replace("/index.html", ".html").endsWith(chunk.fileName) || // If it's a chunk that depends on the changed file, it is effected
168
- // - moduleIds are absolute, normalized paths
169
- chunk.type === "chunk" && chunk.moduleIds.includes(changedPath)
170
- );
171
- for (const step of currentOutput.steps) {
172
- const effectedChunk = step.chunks.find((chunk) => isChunkEffected(chunk));
173
- if (effectedChunk)
174
- changes.push(step);
175
- }
176
- const effectedAsset = currentOutput.publicAssets.find(
177
- (chunk) => isChunkEffected(chunk)
178
- );
179
- if (effectedAsset)
180
- changes.push(effectedAsset);
181
- return changes;
182
- }
183
-
184
- // src/core/utils/building/find-entrypoints.ts
185
- import { relative as relative2, resolve as resolve3 } from "path";
186
- import fs3 from "fs-extra";
187
- import { minimatch } from "minimatch";
188
- import { parseHTML } from "linkedom";
189
- import JSON5 from "json5";
190
- import glob2 from "fast-glob";
191
-
192
20
  // src/core/utils/entrypoints.ts
193
- import path, { relative, resolve as resolve2 } from "node:path";
21
+ import path, { relative, resolve } from "node:path";
194
22
  function getEntrypointName(entrypointsDir, inputPath) {
195
23
  const relativePath = path.relative(entrypointsDir, inputPath);
196
24
  const name = relativePath.split(/[\.\/\\]/, 2)[0];
197
25
  return name;
198
26
  }
199
27
  function getEntrypointOutputFile(entrypoint, ext) {
200
- return resolve2(entrypoint.outputDir, `${entrypoint.name}${ext}`);
28
+ return resolve(entrypoint.outputDir, `${entrypoint.name}${ext}`);
201
29
  }
202
30
  function getEntrypointBundlePath(entrypoint, outDir, ext) {
203
31
  return normalizePath(
@@ -221,1439 +49,1382 @@ function isHtmlEntrypoint(entrypoint) {
221
49
  return entrypoint.inputPath.endsWith(".html");
222
50
  }
223
51
 
224
- // src/core/utils/constants.ts
225
- var VIRTUAL_NOOP_BACKGROUND_MODULE_ID = "virtual:user-background";
52
+ // src/core/utils/time.ts
53
+ function formatDuration(duration) {
54
+ if (duration < 1e3)
55
+ return `${duration} ms`;
56
+ if (duration < 1e4)
57
+ return `${(duration / 1e3).toFixed(3)} s`;
58
+ if (duration < 6e4)
59
+ return `${(duration / 1e3).toFixed(1)} s`;
60
+ return `${(duration / 1e3).toFixed(0)} s`;
61
+ }
62
+ function withTimeout(promise, duration) {
63
+ return new Promise((res, rej) => {
64
+ const timeout = setTimeout(() => {
65
+ rej(`Promise timed out after ${duration}ms`);
66
+ }, duration);
67
+ promise.then(res).catch(rej).finally(() => clearTimeout(timeout));
68
+ });
69
+ }
226
70
 
227
- // src/core/utils/building/find-entrypoints.ts
228
- import pc2 from "picocolors";
229
- async function findEntrypoints() {
230
- const relativePaths = await glob2(Object.keys(PATH_GLOB_TO_TYPE_MAP), {
231
- cwd: wxt.config.entrypointsDir
71
+ // src/core/utils/network.ts
72
+ import dns from "node:dns";
73
+ function isOffline() {
74
+ const isOffline2 = new Promise((res) => {
75
+ dns.resolve("google.com", (err) => {
76
+ if (err == null) {
77
+ res(false);
78
+ } else {
79
+ res(true);
80
+ }
81
+ });
232
82
  });
233
- relativePaths.sort();
234
- const pathGlobs = Object.keys(PATH_GLOB_TO_TYPE_MAP);
235
- const entrypointInfos = relativePaths.reduce((results, relativePath) => {
236
- const inputPath = resolve3(wxt.config.entrypointsDir, relativePath);
237
- const name = getEntrypointName(wxt.config.entrypointsDir, inputPath);
238
- const matchingGlob = pathGlobs.find(
239
- (glob4) => minimatch(relativePath, glob4)
83
+ return withTimeout(isOffline2, 1e3).catch(() => true);
84
+ }
85
+ async function isOnline() {
86
+ const offline = await isOffline();
87
+ return !offline;
88
+ }
89
+ async function fetchCached(url, config) {
90
+ let content = "";
91
+ if (await isOnline()) {
92
+ const res = await fetch(url);
93
+ if (res.status < 300) {
94
+ content = await res.text();
95
+ await config.fsCache.set(url, content);
96
+ } else {
97
+ config.logger.debug(
98
+ `Failed to download "${url}", falling back to cache...`
99
+ );
100
+ }
101
+ }
102
+ if (!content)
103
+ content = await config.fsCache.get(url) ?? "";
104
+ if (!content)
105
+ throw Error(
106
+ `Offline and "${url}" has not been cached. Try again when online.`
240
107
  );
241
- if (matchingGlob) {
242
- const type = PATH_GLOB_TO_TYPE_MAP[matchingGlob];
243
- results.push({
244
- name,
245
- inputPath,
246
- type,
247
- skipped: wxt.config.filterEntrypoints != null && !wxt.config.filterEntrypoints.has(name)
248
- });
108
+ return content;
109
+ }
110
+
111
+ // src/core/builders/vite/plugins/download.ts
112
+ function download(config) {
113
+ return {
114
+ name: "wxt:download",
115
+ resolveId(id) {
116
+ if (id.startsWith("url:"))
117
+ return "\0" + id;
118
+ },
119
+ async load(id) {
120
+ if (!id.startsWith("\0url:"))
121
+ return;
122
+ const url = id.replace("\0url:", "");
123
+ return await fetchCached(url, config);
249
124
  }
250
- return results;
251
- }, []);
252
- preventNoEntrypoints(entrypointInfos);
253
- preventDuplicateEntrypointNames(entrypointInfos);
254
- let hasBackground = false;
255
- const entrypoints = await Promise.all(
256
- entrypointInfos.map(async (info) => {
257
- const { type } = info;
258
- switch (type) {
259
- case "popup":
260
- return await getPopupEntrypoint(info);
261
- case "sidepanel":
262
- return await getSidepanelEntrypoint(info);
263
- case "options":
264
- return await getOptionsEntrypoint(info);
265
- case "background":
266
- hasBackground = true;
267
- return await getBackgroundEntrypoint(info);
268
- case "content-script":
269
- return await getContentScriptEntrypoint(info);
270
- case "unlisted-page":
271
- return await getUnlistedPageEntrypoint(info);
272
- case "unlisted-script":
273
- return await getUnlistedScriptEntrypoint(info);
274
- case "content-script-style":
275
- return {
276
- ...info,
277
- type,
278
- outputDir: resolve3(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
279
- options: {
280
- include: void 0,
281
- exclude: void 0
282
- }
283
- };
284
- default:
285
- return {
286
- ...info,
287
- type,
288
- outputDir: wxt.config.outDir,
289
- options: {
290
- include: void 0,
291
- exclude: void 0
292
- }
293
- };
294
- }
295
- })
296
- );
297
- if (wxt.config.command === "serve" && !hasBackground) {
298
- entrypoints.push(
299
- await getBackgroundEntrypoint({
300
- inputPath: VIRTUAL_NOOP_BACKGROUND_MODULE_ID,
301
- name: "background",
302
- type: "background",
303
- skipped: false
304
- })
305
- );
306
- }
307
- wxt.logger.debug("All entrypoints:", entrypoints);
308
- const skippedEntrypointNames = entrypointInfos.filter((item) => item.skipped).map((item) => item.name);
309
- if (skippedEntrypointNames.length) {
310
- wxt.logger.warn(
311
- `Filter excluded the following entrypoints:
312
- ${skippedEntrypointNames.map((item) => `${pc2.dim("-")} ${pc2.cyan(item)}`).join("\n")}`
313
- );
314
- }
315
- const targetEntrypoints = entrypoints.filter((entry) => {
316
- const { include, exclude } = entry.options;
317
- if (include?.length && exclude?.length) {
318
- wxt.logger.warn(
319
- `The ${entry.name} entrypoint lists both include and exclude, but only one can be used per entrypoint. Entrypoint ignored.`
320
- );
321
- return false;
322
- }
323
- if (exclude?.length && !include?.length) {
324
- return !exclude.includes(wxt.config.browser);
325
- }
326
- if (include?.length && !exclude?.length) {
327
- return include.includes(wxt.config.browser);
125
+ };
126
+ }
127
+
128
+ // src/core/builders/vite/plugins/unimport.ts
129
+ import { createUnimport } from "unimport";
130
+ import { extname } from "path";
131
+ var ENABLED_EXTENSIONS = /* @__PURE__ */ new Set([
132
+ ".js",
133
+ ".jsx",
134
+ ".ts",
135
+ ".tsx",
136
+ ".vue",
137
+ ".svelte"
138
+ ]);
139
+ function unimport(config) {
140
+ const options = config.imports;
141
+ if (options === false)
142
+ return [];
143
+ const unimport2 = createUnimport(options);
144
+ return {
145
+ name: "wxt:unimport",
146
+ async config() {
147
+ await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
148
+ },
149
+ async transform(code, id) {
150
+ if (id.includes("node_modules"))
151
+ return;
152
+ if (!ENABLED_EXTENSIONS.has(extname(id)))
153
+ return;
154
+ const injected = await unimport2.injectImports(code, id);
155
+ return {
156
+ code: injected.code,
157
+ map: injected.s.generateMap({ hires: "boundary", source: id })
158
+ };
328
159
  }
329
- if (skippedEntrypointNames.includes(entry.name)) {
330
- return false;
160
+ };
161
+ }
162
+
163
+ // src/core/builders/vite/plugins/tsconfigPaths.ts
164
+ function tsconfigPaths(config) {
165
+ return {
166
+ name: "wxt:aliases",
167
+ async config() {
168
+ return {
169
+ resolve: {
170
+ alias: config.alias
171
+ }
172
+ };
331
173
  }
332
- return true;
333
- });
334
- wxt.logger.debug(`${wxt.config.browser} entrypoints:`, targetEntrypoints);
335
- await wxt.hooks.callHook("entrypoints:resolved", wxt, targetEntrypoints);
336
- return targetEntrypoints;
174
+ };
337
175
  }
338
- function preventDuplicateEntrypointNames(files) {
339
- const namesToPaths = files.reduce(
340
- (map, { name, inputPath }) => {
341
- map[name] ??= [];
342
- map[name].push(inputPath);
343
- return map;
176
+
177
+ // src/core/utils/globals.ts
178
+ function getGlobals(config) {
179
+ return [
180
+ {
181
+ name: "MANIFEST_VERSION",
182
+ value: config.manifestVersion,
183
+ type: `2 | 3`
344
184
  },
345
- {}
346
- );
347
- const errorLines = Object.entries(namesToPaths).reduce(
348
- (lines, [name, absolutePaths]) => {
349
- if (absolutePaths.length > 1) {
350
- lines.push(`- ${name}`);
351
- absolutePaths.forEach((absolutePath) => {
352
- lines.push(` - ${relative2(wxt.config.root, absolutePath)}`);
353
- });
354
- }
355
- return lines;
185
+ {
186
+ name: "BROWSER",
187
+ value: config.browser,
188
+ type: `string`
356
189
  },
357
- []
358
- );
359
- if (errorLines.length > 0) {
360
- const errorContent = errorLines.join("\n");
361
- throw Error(
362
- `Multiple entrypoints with the same name detected, only one entrypoint for each name is allowed.
363
-
364
- ${errorContent}`
365
- );
366
- }
367
- }
368
- function preventNoEntrypoints(files) {
369
- if (files.length === 0) {
370
- throw Error(`No entrypoints found in ${wxt.config.entrypointsDir}`);
371
- }
372
- }
373
- async function getPopupEntrypoint(info) {
374
- const options = await getHtmlEntrypointOptions(
375
- info,
376
190
  {
377
- browserStyle: "browse_style",
378
- exclude: "exclude",
379
- include: "include",
380
- defaultIcon: "default_icon",
381
- defaultTitle: "default_title",
382
- mv2Key: "type"
191
+ name: "CHROME",
192
+ value: config.browser === "chrome",
193
+ type: `boolean`
383
194
  },
384
195
  {
385
- defaultTitle: (document) => document.querySelector("title")?.textContent || void 0
196
+ name: "FIREFOX",
197
+ value: config.browser === "firefox",
198
+ type: `boolean`
386
199
  },
387
200
  {
388
- defaultTitle: (content) => content,
389
- mv2Key: (content) => content === "page_action" ? "page_action" : "browser_action"
201
+ name: "SAFARI",
202
+ value: config.browser === "safari",
203
+ type: `boolean`
204
+ },
205
+ {
206
+ name: "EDGE",
207
+ value: config.browser === "edge",
208
+ type: `boolean`
209
+ },
210
+ {
211
+ name: "OPERA",
212
+ value: config.browser === "opera",
213
+ type: `boolean`
214
+ },
215
+ {
216
+ name: "COMMAND",
217
+ value: config.command,
218
+ type: `"build" | "serve"`
390
219
  }
391
- );
392
- return {
393
- type: "popup",
394
- name: "popup",
395
- options: resolvePerBrowserOptions(options, wxt.config.browser),
396
- inputPath: info.inputPath,
397
- outputDir: wxt.config.outDir,
398
- skipped: info.skipped
399
- };
220
+ ];
400
221
  }
401
- async function getOptionsEntrypoint(info) {
402
- const options = await getHtmlEntrypointOptions(
403
- info,
222
+ function getEntrypointGlobals(entrypointName) {
223
+ return [
404
224
  {
405
- browserStyle: "browse_style",
406
- chromeStyle: "chrome_style",
407
- exclude: "exclude",
408
- include: "include",
409
- openInTab: "open_in_tab"
225
+ name: "ENTRYPOINT",
226
+ value: entrypointName,
227
+ type: `string`
410
228
  }
411
- );
229
+ ];
230
+ }
231
+
232
+ // src/core/builders/vite/plugins/globals.ts
233
+ function globals(config) {
412
234
  return {
413
- type: "options",
414
- name: "options",
415
- options: resolvePerBrowserOptions(options, wxt.config.browser),
416
- inputPath: info.inputPath,
417
- outputDir: wxt.config.outDir,
418
- skipped: info.skipped
235
+ name: "wxt:globals",
236
+ config() {
237
+ const define = {};
238
+ for (const global of getGlobals(config)) {
239
+ define[`import.meta.env.${global.name}`] = JSON.stringify(global.value);
240
+ }
241
+ return {
242
+ define
243
+ };
244
+ }
419
245
  };
420
246
  }
421
- async function getUnlistedPageEntrypoint(info) {
422
- const options = await getHtmlEntrypointOptions(info, {
423
- exclude: "exclude",
424
- include: "include"
425
- });
426
- return {
427
- type: "unlisted-page",
428
- name: info.name,
429
- inputPath: info.inputPath,
430
- outputDir: wxt.config.outDir,
431
- options,
432
- skipped: info.skipped
433
- };
434
- }
435
- async function getUnlistedScriptEntrypoint({
436
- inputPath,
437
- name,
438
- skipped
439
- }) {
440
- const defaultExport = await importEntrypointFile(inputPath);
441
- if (defaultExport == null) {
442
- throw Error(
443
- `${name}: Default export not found, did you forget to call "export default defineUnlistedScript(...)"?`
444
- );
445
- }
446
- const { main: _, ...options } = defaultExport;
247
+
248
+ // src/core/builders/vite/plugins/webextensionPolyfillMock.ts
249
+ import path2 from "node:path";
250
+ function webextensionPolyfillMock(config) {
447
251
  return {
448
- type: "unlisted-script",
449
- name,
450
- inputPath,
451
- outputDir: wxt.config.outDir,
452
- options: resolvePerBrowserOptions(options, wxt.config.browser),
453
- skipped
454
- };
455
- }
456
- async function getBackgroundEntrypoint({
457
- inputPath,
458
- name,
459
- skipped
460
- }) {
461
- let options = {};
462
- if (inputPath !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) {
463
- const defaultExport = await importEntrypointFile(inputPath);
464
- if (defaultExport == null) {
465
- throw Error(
466
- `${name}: Default export not found, did you forget to call "export default defineBackground(...)"?`
467
- );
252
+ name: "wxt:testing-inline-deps",
253
+ config() {
254
+ return {
255
+ resolve: {
256
+ alias: {
257
+ // Alias to use a mocked version of the polyfill
258
+ "webextension-polyfill": path2.resolve(
259
+ config.wxtModuleDir,
260
+ "dist/virtual/mock-browser"
261
+ )
262
+ }
263
+ },
264
+ ssr: {
265
+ // Inline all WXT modules
266
+ noExternal: ["wxt"]
267
+ }
268
+ };
468
269
  }
469
- const { main: _, ...moduleOptions } = defaultExport;
470
- options = moduleOptions;
471
- }
472
- if (wxt.config.manifestVersion !== 3) {
473
- delete options.type;
474
- }
475
- return {
476
- type: "background",
477
- name,
478
- inputPath,
479
- outputDir: wxt.config.outDir,
480
- options: resolvePerBrowserOptions(options, wxt.config.browser),
481
- skipped
482
- };
483
- }
484
- async function getContentScriptEntrypoint({
485
- inputPath,
486
- name,
487
- skipped
488
- }) {
489
- const { main: _, ...options } = await importEntrypointFile(inputPath);
490
- if (options == null) {
491
- throw Error(
492
- `${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`
493
- );
494
- }
495
- return {
496
- type: "content-script",
497
- name,
498
- inputPath,
499
- outputDir: resolve3(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
500
- options: resolvePerBrowserOptions(options, wxt.config.browser),
501
- skipped
502
270
  };
503
271
  }
504
- async function getSidepanelEntrypoint(info) {
505
- const options = await getHtmlEntrypointOptions(
506
- info,
507
- {
508
- browserStyle: "browse_style",
509
- exclude: "exclude",
510
- include: "include",
511
- defaultIcon: "default_icon",
512
- defaultTitle: "default_title",
513
- openAtInstall: "open_at_install"
514
- },
272
+
273
+ // src/core/builders/vite/plugins/devHtmlPrerender.ts
274
+ import { parseHTML } from "linkedom";
275
+ import { dirname, relative as relative2, resolve as resolve2 } from "node:path";
276
+ var reactRefreshPreamble = "";
277
+ function devHtmlPrerender(config, server) {
278
+ const htmlReloadId = "@wxt/reload-html";
279
+ const resolvedHtmlReloadId = resolve2(
280
+ config.wxtModuleDir,
281
+ "dist/virtual/reload-html.js"
282
+ );
283
+ const virtualReactRefreshId = "@wxt/virtual-react-refresh";
284
+ const resolvedVirtualReactRefreshId = "\0" + virtualReactRefreshId;
285
+ return [
515
286
  {
516
- defaultTitle: (document) => document.querySelector("title")?.textContent || void 0
287
+ apply: "build",
288
+ name: "wxt:dev-html-prerender",
289
+ config() {
290
+ return {
291
+ resolve: {
292
+ alias: {
293
+ [htmlReloadId]: resolvedHtmlReloadId
294
+ }
295
+ }
296
+ };
297
+ },
298
+ // Convert scripts like src="./main.tsx" -> src="http://localhost:3000/entrypoints/popup/main.tsx"
299
+ // before the paths are replaced with their bundled path
300
+ transform(code, id) {
301
+ if (config.command !== "serve" || server == null || !id.endsWith(".html"))
302
+ return;
303
+ const { document } = parseHTML(code);
304
+ const _pointToDevServer = (querySelector, attr) => pointToDevServer(config, server, id, document, querySelector, attr);
305
+ _pointToDevServer("script[type=module]", "src");
306
+ _pointToDevServer("link[rel=stylesheet]", "href");
307
+ const reloader = document.createElement("script");
308
+ reloader.src = htmlReloadId;
309
+ reloader.type = "module";
310
+ document.head.appendChild(reloader);
311
+ const newHtml = document.toString();
312
+ config.logger.debug("transform " + id);
313
+ config.logger.debug("Old HTML:\n" + code);
314
+ config.logger.debug("New HTML:\n" + newHtml);
315
+ return newHtml;
316
+ },
317
+ // Pass the HTML through the dev server to add dev-mode specific code
318
+ async transformIndexHtml(html, ctx) {
319
+ if (config.command !== "serve" || server == null)
320
+ return;
321
+ const originalUrl = `${server.origin}${ctx.path}`;
322
+ const name = getEntrypointName(config.entrypointsDir, ctx.filename);
323
+ const url = `${server.origin}/${name}.html`;
324
+ const serverHtml = await server.transformHtml(url, html, originalUrl);
325
+ const { document } = parseHTML(serverHtml);
326
+ const reactRefreshScript = Array.from(
327
+ document.querySelectorAll("script[type=module]")
328
+ ).find((script) => script.innerHTML.includes("@react-refresh"));
329
+ if (reactRefreshScript) {
330
+ reactRefreshPreamble = reactRefreshScript.innerHTML;
331
+ const virtualScript = document.createElement("script");
332
+ virtualScript.type = "module";
333
+ virtualScript.src = `${server.origin}/${virtualReactRefreshId}`;
334
+ reactRefreshScript.replaceWith(virtualScript);
335
+ }
336
+ const viteClientScript = document.querySelector(
337
+ "script[src='/@vite/client']"
338
+ );
339
+ if (viteClientScript) {
340
+ viteClientScript.src = `${server.origin}${viteClientScript.src}`;
341
+ }
342
+ const newHtml = document.toString();
343
+ config.logger.debug("transformIndexHtml " + ctx.filename);
344
+ config.logger.debug("Old HTML:\n" + html);
345
+ config.logger.debug("New HTML:\n" + newHtml);
346
+ return newHtml;
347
+ }
517
348
  },
518
349
  {
519
- defaultTitle: (content) => content
350
+ name: "wxt:virtualize-react-refresh",
351
+ apply: "serve",
352
+ resolveId(id) {
353
+ if (id === `/${virtualReactRefreshId}`) {
354
+ return resolvedVirtualReactRefreshId;
355
+ }
356
+ if (id.startsWith("/chunks/")) {
357
+ return "\0noop";
358
+ }
359
+ },
360
+ load(id) {
361
+ if (id === resolvedVirtualReactRefreshId) {
362
+ return reactRefreshPreamble;
363
+ }
364
+ if (id === "\0noop") {
365
+ return "";
366
+ }
367
+ }
520
368
  }
521
- );
522
- return {
523
- type: "sidepanel",
524
- name: info.name,
525
- options: resolvePerBrowserOptions(options, wxt.config.browser),
526
- inputPath: info.inputPath,
527
- outputDir: wxt.config.outDir,
528
- skipped: info.skipped
529
- };
369
+ ];
530
370
  }
531
- async function getHtmlEntrypointOptions(info, keyMap, queries, parsers) {
532
- const content = await fs3.readFile(info.inputPath, "utf-8");
533
- const { document } = parseHTML(content);
534
- const options = {};
535
- const defaultQuery = (manifestKey) => document.querySelector(`meta[name='manifest.${manifestKey}']`)?.getAttribute("content");
536
- Object.entries(keyMap).forEach(([_key, manifestKey]) => {
537
- const key = _key;
538
- const content2 = queries?.[key] ? queries[key](document, manifestKey) : defaultQuery(manifestKey);
539
- if (content2) {
540
- try {
541
- options[key] = (parsers?.[key] ?? JSON5.parse)(content2);
542
- } catch (err) {
543
- wxt.logger.fatal(
544
- `Failed to parse meta tag content. Usually this means you have invalid JSON5 content (content=${content2})`,
545
- err
546
- );
371
+ function pointToDevServer(config, server, id, document, querySelector, attr) {
372
+ document.querySelectorAll(querySelector).forEach((element) => {
373
+ const src = element.getAttribute(attr);
374
+ if (!src || isUrl(src))
375
+ return;
376
+ let resolvedAbsolutePath;
377
+ const matchingAlias = Object.entries(config.alias).find(
378
+ ([key]) => src.startsWith(key)
379
+ );
380
+ if (matchingAlias) {
381
+ const [alias, replacement] = matchingAlias;
382
+ resolvedAbsolutePath = resolve2(
383
+ config.root,
384
+ src.replace(alias, replacement)
385
+ );
386
+ } else {
387
+ resolvedAbsolutePath = resolve2(dirname(id), src);
388
+ }
389
+ if (resolvedAbsolutePath) {
390
+ const relativePath = normalizePath(
391
+ relative2(config.root, resolvedAbsolutePath)
392
+ );
393
+ if (relativePath.startsWith(".")) {
394
+ let path8 = normalizePath(resolvedAbsolutePath);
395
+ if (!path8.startsWith("/"))
396
+ path8 = "/" + path8;
397
+ element.setAttribute(attr, `${server.origin}/@fs${path8}`);
398
+ } else {
399
+ const url = new URL(relativePath, server.origin);
400
+ element.setAttribute(attr, url.href);
547
401
  }
548
402
  }
549
403
  });
550
- return options;
551
404
  }
552
- var PATH_GLOB_TO_TYPE_MAP = {
553
- "sandbox.html": "sandbox",
554
- "sandbox/index.html": "sandbox",
555
- "*.sandbox.html": "sandbox",
556
- "*.sandbox/index.html": "sandbox",
557
- "bookmarks.html": "bookmarks",
558
- "bookmarks/index.html": "bookmarks",
559
- "history.html": "history",
560
- "history/index.html": "history",
561
- "newtab.html": "newtab",
562
- "newtab/index.html": "newtab",
563
- "sidepanel.html": "sidepanel",
564
- "sidepanel/index.html": "sidepanel",
565
- "*.sidepanel.html": "sidepanel",
566
- "*.sidepanel/index.html": "sidepanel",
567
- "devtools.html": "devtools",
568
- "devtools/index.html": "devtools",
569
- "background.[jt]s": "background",
570
- "background/index.[jt]s": "background",
571
- [VIRTUAL_NOOP_BACKGROUND_MODULE_ID]: "background",
572
- "content.[jt]s?(x)": "content-script",
573
- "content/index.[jt]s?(x)": "content-script",
574
- "*.content.[jt]s?(x)": "content-script",
575
- "*.content/index.[jt]s?(x)": "content-script",
576
- [`content.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
577
- [`*.content.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
578
- [`content/index.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
579
- [`*.content/index.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
580
- "popup.html": "popup",
581
- "popup/index.html": "popup",
582
- "options.html": "options",
583
- "options/index.html": "options",
584
- "*.html": "unlisted-page",
585
- "*/index.html": "unlisted-page",
586
- "*.[jt]s?(x)": "unlisted-script",
587
- "*/index.[jt]s?(x)": "unlisted-script",
588
- [`*.${CSS_EXTENSIONS_PATTERN}`]: "unlisted-style",
589
- [`*/index.${CSS_EXTENSIONS_PATTERN}`]: "unlisted-style"
590
- };
591
- var CONTENT_SCRIPT_OUT_DIR = "content-scripts";
592
-
593
- // src/core/utils/building/generate-wxt-dir.ts
594
- import { createUnimport } from "unimport";
595
- import fs4 from "fs-extra";
596
- import { relative as relative3, resolve as resolve4 } from "path";
405
+ function isUrl(str) {
406
+ try {
407
+ new URL(str);
408
+ return true;
409
+ } catch {
410
+ return false;
411
+ }
412
+ }
597
413
 
598
- // src/core/utils/globals.ts
599
- function getGlobals(config) {
600
- return [
601
- {
602
- name: "MANIFEST_VERSION",
603
- value: config.manifestVersion,
604
- type: `2 | 3`
605
- },
606
- {
607
- name: "BROWSER",
608
- value: config.browser,
609
- type: `string`
610
- },
611
- {
612
- name: "CHROME",
613
- value: config.browser === "chrome",
614
- type: `boolean`
615
- },
616
- {
617
- name: "FIREFOX",
618
- value: config.browser === "firefox",
619
- type: `boolean`
620
- },
621
- {
622
- name: "SAFARI",
623
- value: config.browser === "safari",
624
- type: `boolean`
625
- },
626
- {
627
- name: "EDGE",
628
- value: config.browser === "edge",
629
- type: `boolean`
630
- },
631
- {
632
- name: "OPERA",
633
- value: config.browser === "opera",
634
- type: `boolean`
635
- },
636
- {
637
- name: "COMMAND",
638
- value: config.command,
639
- type: `"build" | "serve"`
414
+ // src/core/builders/vite/plugins/devServerGlobals.ts
415
+ function devServerGlobals(config, server) {
416
+ return {
417
+ name: "wxt:dev-server-globals",
418
+ config() {
419
+ if (server == null || config.command == "build")
420
+ return;
421
+ return {
422
+ define: {
423
+ __DEV_SERVER_PROTOCOL__: JSON.stringify("ws:"),
424
+ __DEV_SERVER_HOSTNAME__: JSON.stringify(server.hostname),
425
+ __DEV_SERVER_PORT__: JSON.stringify(server.port)
426
+ }
427
+ };
640
428
  }
641
- ];
429
+ };
642
430
  }
643
- function getEntrypointGlobals(entrypointName) {
644
- return [
645
- {
646
- name: "ENTRYPOINT",
647
- value: entrypointName,
648
- type: `string`
431
+
432
+ // src/core/builders/vite/plugins/multipageMove.ts
433
+ import { dirname as dirname2, extname as extname2, resolve as resolve3, join } from "node:path";
434
+ import fs, { ensureDir } from "fs-extra";
435
+ function multipageMove(entrypoints, config) {
436
+ return {
437
+ name: "wxt:multipage-move",
438
+ async writeBundle(_, bundle) {
439
+ for (const oldBundlePath in bundle) {
440
+ const entrypoint = entrypoints.find(
441
+ (entry) => !!normalizePath(entry.inputPath).endsWith(oldBundlePath)
442
+ );
443
+ if (entrypoint == null) {
444
+ config.logger.debug(
445
+ `No entrypoint found for ${oldBundlePath}, leaving in chunks directory`
446
+ );
447
+ continue;
448
+ }
449
+ const newBundlePath = getEntrypointBundlePath(
450
+ entrypoint,
451
+ config.outDir,
452
+ extname2(oldBundlePath)
453
+ );
454
+ if (newBundlePath === oldBundlePath) {
455
+ config.logger.debug(
456
+ "HTML file is already in the correct location",
457
+ oldBundlePath
458
+ );
459
+ continue;
460
+ }
461
+ const oldAbsPath = resolve3(config.outDir, oldBundlePath);
462
+ const newAbsPath = resolve3(config.outDir, newBundlePath);
463
+ await ensureDir(dirname2(newAbsPath));
464
+ await fs.move(oldAbsPath, newAbsPath, { overwrite: true });
465
+ const renamedChunk = {
466
+ ...bundle[oldBundlePath],
467
+ fileName: newBundlePath
468
+ };
469
+ delete bundle[oldBundlePath];
470
+ bundle[newBundlePath] = renamedChunk;
471
+ }
472
+ removeEmptyDirs(config.outDir);
649
473
  }
650
- ];
474
+ };
651
475
  }
652
-
653
- // src/core/utils/building/generate-wxt-dir.ts
654
- import path2 from "node:path";
655
-
656
- // src/core/utils/i18n.ts
657
- var predefinedMessages = {
658
- "@@extension_id": {
659
- message: "<browser.runtime.id>",
660
- 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."
661
- },
662
- "@@ui_locale": {
663
- message: "<browser.i18n.getUiLocale()>",
664
- description: ""
665
- },
666
- "@@bidi_dir": {
667
- message: "<ltr|rtl>",
668
- 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.'
669
- },
670
- "@@bidi_reversed_dir": {
671
- message: "<rtl|ltr>",
672
- description: `If the @@bidi_dir is "ltr", then this is "rtl"; otherwise, it's "ltr".`
673
- },
674
- "@@bidi_start_edge": {
675
- message: "<left|right>",
676
- description: `If the @@bidi_dir is "ltr", then this is "left"; otherwise, it's "right".`
677
- },
678
- "@@bidi_end_edge": {
679
- message: "<right|left>",
680
- description: `If the @@bidi_dir is "ltr", then this is "right"; otherwise, it's "left".`
476
+ async function removeEmptyDirs(dir) {
477
+ const files = await fs.readdir(dir);
478
+ for (const file of files) {
479
+ const filePath = join(dir, file);
480
+ const stats = await fs.stat(filePath);
481
+ if (stats.isDirectory()) {
482
+ await removeEmptyDirs(filePath);
483
+ }
484
+ }
485
+ try {
486
+ await fs.rmdir(dir);
487
+ } catch {
681
488
  }
682
- };
683
- function parseI18nMessages(messagesJson) {
684
- return Object.entries({
685
- ...predefinedMessages,
686
- ...messagesJson
687
- }).map(([name, details]) => ({
688
- name,
689
- ...details
690
- }));
691
489
  }
692
490
 
693
- // src/core/utils/building/generate-wxt-dir.ts
694
- async function generateTypesDir(entrypoints) {
695
- await fs4.ensureDir(wxt.config.typesDir);
696
- const references = [];
697
- if (wxt.config.imports !== false) {
698
- const unimport2 = createUnimport(wxt.config.imports);
699
- references.push(await writeImportsDeclarationFile(unimport2));
700
- if (wxt.config.imports.eslintrc.enabled) {
701
- await writeImportsEslintFile(unimport2, wxt.config.imports);
491
+ // src/core/builders/vite/plugins/virtualEntrypoint.ts
492
+ import fs2 from "fs-extra";
493
+ import { resolve as resolve4 } from "path";
494
+ function virtualEntrypoint(type, config) {
495
+ const virtualId = `virtual:wxt-${type}?`;
496
+ const resolvedVirtualId = `\0${virtualId}`;
497
+ return {
498
+ name: `wxt:virtual-entrypoint`,
499
+ resolveId(id) {
500
+ const index = id.indexOf(virtualId);
501
+ if (index === -1)
502
+ return;
503
+ const inputPath = normalizePath(id.substring(index + virtualId.length));
504
+ return resolvedVirtualId + inputPath;
505
+ },
506
+ async load(id) {
507
+ if (!id.startsWith(resolvedVirtualId))
508
+ return;
509
+ const inputPath = id.replace(resolvedVirtualId, "");
510
+ const template = await fs2.readFile(
511
+ resolve4(config.wxtModuleDir, `dist/virtual/${type}-entrypoint.js`),
512
+ "utf-8"
513
+ );
514
+ return template.replace(`virtual:user-${type}`, inputPath);
702
515
  }
703
- }
704
- references.push(await writePathsDeclarationFile(entrypoints));
705
- references.push(await writeI18nDeclarationFile());
706
- references.push(await writeGlobalsDeclarationFile());
707
- const mainReference = await writeMainDeclarationFile(references);
708
- await writeTsConfigFile(mainReference);
709
- }
710
- async function writeImportsDeclarationFile(unimport2) {
711
- const filePath = resolve4(wxt.config.typesDir, "imports.d.ts");
712
- await unimport2.scanImportsFromDir(void 0, { cwd: wxt.config.srcDir });
713
- await writeFileIfDifferent(
714
- filePath,
715
- ["// Generated by wxt", await unimport2.generateTypeDeclarations()].join(
716
- "\n"
717
- ) + "\n"
718
- );
719
- return filePath;
720
- }
721
- async function writeImportsEslintFile(unimport2, options) {
722
- const globals2 = {};
723
- const eslintrc = { globals: globals2 };
724
- (await unimport2.getImports()).map((i) => i.as ?? i.name).filter(Boolean).sort().forEach((name) => {
725
- eslintrc.globals[name] = options.eslintrc.globalsPropValue;
726
- });
727
- await fs4.writeJson(options.eslintrc.filePath, eslintrc, { spaces: 2 });
516
+ };
728
517
  }
729
- async function writePathsDeclarationFile(entrypoints) {
730
- const filePath = resolve4(wxt.config.typesDir, "paths.d.ts");
731
- const unions = entrypoints.map(
732
- (entry) => getEntrypointBundlePath(
733
- entry,
734
- wxt.config.outDir,
735
- isHtmlEntrypoint(entry) ? ".html" : ".js"
736
- )
737
- ).concat(await getPublicFiles()).map(normalizePath).map((path8) => ` | "/${path8}"`).sort().join("\n");
738
- const template = `// Generated by wxt
739
- import "wxt/browser";
740
518
 
741
- declare module "wxt/browser" {
742
- export type PublicPath =
743
- {{ union }}
744
- type HtmlPublicPath = Extract<PublicPath, \`\${string}.html\`>
745
- export interface WxtRuntime extends Runtime.Static {
746
- getURL(path: PublicPath): string;
747
- getURL(path: \`\${HtmlPublicPath}\${string}\`): string;
748
- }
749
- }
750
- `;
751
- await writeFileIfDifferent(
752
- filePath,
753
- template.replace("{{ union }}", unions || " | never")
754
- );
755
- return filePath;
756
- }
757
- async function writeI18nDeclarationFile() {
758
- const filePath = resolve4(wxt.config.typesDir, "i18n.d.ts");
759
- const defaultLocale = wxt.config.manifest.default_locale;
760
- const template = `// Generated by wxt
761
- import "wxt/browser";
519
+ // src/core/utils/constants.ts
520
+ var VIRTUAL_NOOP_BACKGROUND_MODULE_ID = "virtual:user-background";
762
521
 
763
- declare module "wxt/browser" {
764
- /**
765
- * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
766
- */
767
- interface GetMessageOptions {
768
- /**
769
- * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
770
- */
771
- escapeLt?: boolean
772
- }
522
+ // src/core/builders/vite/plugins/noopBackground.ts
523
+ function noopBackground() {
524
+ const virtualModuleId = VIRTUAL_NOOP_BACKGROUND_MODULE_ID;
525
+ const resolvedVirtualModuleId = "\0" + virtualModuleId;
526
+ return {
527
+ name: "wxt:noop-background",
528
+ resolveId(id) {
529
+ if (id === virtualModuleId)
530
+ return resolvedVirtualModuleId;
531
+ },
532
+ load(id) {
533
+ if (id === resolvedVirtualModuleId) {
534
+ return `import { defineBackground } from 'wxt/sandbox';
535
+ export default defineBackground(() => void 0)`;
536
+ }
537
+ }
538
+ };
539
+ }
773
540
 
774
- export interface WxtI18n extends I18n.Static {
775
- {{ overrides }}
776
- }
541
+ // src/core/builders/vite/plugins/cssEntrypoints.ts
542
+ function cssEntrypoints(entrypoint, config) {
543
+ return {
544
+ name: "wxt:css-entrypoint",
545
+ config() {
546
+ return {
547
+ build: {
548
+ rollupOptions: {
549
+ output: {
550
+ assetFileNames: () => getEntrypointBundlePath(entrypoint, config.outDir, ".css")
551
+ }
552
+ }
553
+ }
554
+ };
555
+ },
556
+ generateBundle(_, bundle) {
557
+ Object.keys(bundle).forEach((file) => {
558
+ if (file.endsWith(".js"))
559
+ delete bundle[file];
560
+ });
561
+ }
562
+ };
777
563
  }
778
- `;
779
- let messages;
780
- if (defaultLocale) {
781
- const defaultLocalePath = path2.resolve(
782
- wxt.config.publicDir,
783
- "_locales",
784
- defaultLocale,
785
- "messages.json"
786
- );
787
- const content = JSON.parse(await fs4.readFile(defaultLocalePath, "utf-8"));
788
- messages = parseI18nMessages(content);
789
- } else {
790
- messages = parseI18nMessages({});
791
- }
792
- const overrides = messages.map((message) => {
793
- return ` /**
794
- * ${message.description || "No message description."}
795
- *
796
- * "${message.message}"
797
- */
798
- getMessage(
799
- messageName: "${message.name}",
800
- substitutions?: string | string[],
801
- options?: GetMessageOptions,
802
- ): string;`;
564
+
565
+ // src/core/builders/vite/plugins/bundleAnalysis.ts
566
+ import { visualizer } from "@aklinker1/rollup-plugin-visualizer";
567
+ import path3 from "node:path";
568
+ var increment = 0;
569
+ function bundleAnalysis(config) {
570
+ return visualizer({
571
+ template: "raw-data",
572
+ filename: path3.resolve(
573
+ config.analysis.outputDir,
574
+ `${config.analysis.outputName}-${increment++}.json`
575
+ )
803
576
  });
804
- await writeFileIfDifferent(
805
- filePath,
806
- template.replace("{{ overrides }}", overrides.join("\n"))
807
- );
808
- return filePath;
809
- }
810
- async function writeGlobalsDeclarationFile() {
811
- const filePath = resolve4(wxt.config.typesDir, "globals.d.ts");
812
- const globals2 = [...getGlobals(wxt.config), ...getEntrypointGlobals("")];
813
- await writeFileIfDifferent(
814
- filePath,
815
- [
816
- "// Generated by wxt",
817
- "export {}",
818
- "interface ImportMetaEnv {",
819
- ...globals2.map((global) => ` readonly ${global.name}: ${global.type};`),
820
- "}",
821
- "interface ImportMeta {",
822
- " readonly env: ImportMetaEnv",
823
- "}"
824
- ].join("\n") + "\n"
825
- );
826
- return filePath;
827
- }
828
- async function writeMainDeclarationFile(references) {
829
- const dir = wxt.config.wxtDir;
830
- const filePath = resolve4(dir, "wxt.d.ts");
831
- await writeFileIfDifferent(
832
- filePath,
833
- [
834
- "// Generated by wxt",
835
- `/// <reference types="wxt/vite-builder-env" />`,
836
- ...references.map(
837
- (ref) => `/// <reference types="./${normalizePath(relative3(dir, ref))}" />`
838
- )
839
- ].join("\n") + "\n"
840
- );
841
- return filePath;
842
577
  }
843
- async function writeTsConfigFile(mainReference) {
844
- const dir = wxt.config.wxtDir;
845
- const getTsconfigPath = (path8) => normalizePath(relative3(dir, path8));
846
- const paths = Object.entries(wxt.config.alias).flatMap(([alias, absolutePath]) => {
847
- const aliasPath = getTsconfigPath(absolutePath);
848
- return [
849
- ` "${alias}": ["${aliasPath}"]`,
850
- ` "${alias}/*": ["${aliasPath}/*"]`
851
- ];
852
- }).join(",\n");
853
- await writeFileIfDifferent(
854
- resolve4(dir, "tsconfig.json"),
855
- `{
856
- "compilerOptions": {
857
- "target": "ESNext",
858
- "module": "ESNext",
859
- "moduleResolution": "Bundler",
860
- "noEmit": true,
861
- "esModuleInterop": true,
862
- "forceConsistentCasingInFileNames": true,
863
- "resolveJsonModule": true,
864
- "strict": true,
865
- "skipLibCheck": true,
866
- "paths": {
867
- ${paths}
578
+
579
+ // src/core/builders/vite/plugins/excludeBrowserPolyfill.ts
580
+ function excludeBrowserPolyfill(config) {
581
+ const virtualId = "virtual:wxt-webextension-polyfill-disabled";
582
+ return {
583
+ name: "wxt:exclude-browser-polyfill",
584
+ config() {
585
+ if (config.experimental.includeBrowserPolyfill)
586
+ return;
587
+ return {
588
+ resolve: {
589
+ alias: {
590
+ "webextension-polyfill": virtualId
591
+ }
592
+ }
593
+ };
594
+ },
595
+ load(id) {
596
+ if (id === virtualId) {
597
+ return "export default chrome";
598
+ }
868
599
  }
869
- },
870
- "include": [
871
- "${getTsconfigPath(wxt.config.root)}/**/*",
872
- "./${getTsconfigPath(mainReference)}"
873
- ],
874
- "exclude": ["${getTsconfigPath(wxt.config.outBaseDir)}"]
875
- }`
876
- );
600
+ };
877
601
  }
878
602
 
879
- // src/core/utils/building/resolve-config.ts
880
- import { loadConfig } from "c12";
881
- import path5 from "node:path";
882
-
883
- // src/core/utils/cache.ts
884
- import fs5, { ensureDir } from "fs-extra";
885
- import { dirname as dirname2, resolve as resolve5 } from "path";
886
- function createFsCache(wxtDir) {
887
- const getPath = (key) => resolve5(wxtDir, "cache", encodeURIComponent(key));
603
+ // src/core/builders/vite/plugins/entrypointGroupGlobals.ts
604
+ function entrypointGroupGlobals(entrypointGroup) {
888
605
  return {
889
- async set(key, value) {
890
- const path8 = getPath(key);
891
- await ensureDir(dirname2(path8));
892
- await writeFileIfDifferent(path8, value);
893
- },
894
- async get(key) {
895
- const path8 = getPath(key);
896
- try {
897
- return await fs5.readFile(path8, "utf-8");
898
- } catch {
899
- return void 0;
606
+ name: "wxt:entrypoint-group-globals",
607
+ config() {
608
+ const define = {};
609
+ let name = Array.isArray(entrypointGroup) ? "html" : entrypointGroup.name;
610
+ for (const global of getEntrypointGlobals(name)) {
611
+ define[`import.meta.env.${global.name}`] = JSON.stringify(global.value);
900
612
  }
613
+ return {
614
+ define
615
+ };
901
616
  }
902
617
  };
903
618
  }
904
619
 
905
- // src/core/utils/building/resolve-config.ts
906
- import consola, { LogLevels } from "consola";
620
+ // src/core/builders/vite/plugins/defineImportMeta.ts
621
+ function defineImportMeta() {
622
+ return {
623
+ name: "wxt:define",
624
+ config() {
625
+ return {
626
+ define: {
627
+ // This works for all extension contexts, including background service worker
628
+ "import.meta.url": "self.location.href"
629
+ }
630
+ };
631
+ }
632
+ };
633
+ }
907
634
 
908
- // src/core/builders/vite/plugins/devHtmlPrerender.ts
909
- import { parseHTML as parseHTML2 } from "linkedom";
910
- import { dirname as dirname3, relative as relative4, resolve as resolve6 } from "node:path";
911
- var reactRefreshPreamble = "";
912
- function devHtmlPrerender(config) {
913
- const htmlReloadId = "@wxt/reload-html";
914
- const resolvedHtmlReloadId = resolve6(
915
- config.wxtModuleDir,
916
- "dist/virtual/reload-html.js"
917
- );
918
- const virtualReactRefreshId = "@wxt/virtual-react-refresh";
919
- const resolvedVirtualReactRefreshId = "\0" + virtualReactRefreshId;
920
- return [
921
- {
922
- apply: "build",
923
- name: "wxt:dev-html-prerender",
924
- config() {
925
- return {
926
- resolve: {
927
- alias: {
928
- [htmlReloadId]: resolvedHtmlReloadId
929
- }
930
- }
931
- };
932
- },
933
- // Convert scripts like src="./main.tsx" -> src="http://localhost:3000/entrypoints/popup/main.tsx"
934
- // before the paths are replaced with their bundled path
935
- transform(code, id) {
936
- const server = config.server;
937
- if (config.command !== "serve" || server == null || !id.endsWith(".html"))
938
- return;
939
- const { document } = parseHTML2(code);
940
- const _pointToDevServer = (querySelector, attr) => pointToDevServer(config, server, id, document, querySelector, attr);
941
- _pointToDevServer("script[type=module]", "src");
942
- _pointToDevServer("link[rel=stylesheet]", "href");
943
- const reloader = document.createElement("script");
944
- reloader.src = htmlReloadId;
945
- reloader.type = "module";
946
- document.head.appendChild(reloader);
947
- const newHtml = document.toString();
948
- config.logger.debug("transform " + id);
949
- config.logger.debug("Old HTML:\n" + code);
950
- config.logger.debug("New HTML:\n" + newHtml);
951
- return newHtml;
952
- },
953
- // Pass the HTML through the dev server to add dev-mode specific code
954
- async transformIndexHtml(html, ctx) {
955
- const server = config.server;
956
- if (config.command !== "serve" || server == null)
957
- return;
958
- const originalUrl = `${server.origin}${ctx.path}`;
959
- const name = getEntrypointName(config.entrypointsDir, ctx.filename);
960
- const url = `${server.origin}/${name}.html`;
961
- const serverHtml = await server.transformHtml(url, html, originalUrl);
962
- const { document } = parseHTML2(serverHtml);
963
- const reactRefreshScript = Array.from(
964
- document.querySelectorAll("script[type=module]")
965
- ).find((script) => script.innerHTML.includes("@react-refresh"));
966
- if (reactRefreshScript) {
967
- reactRefreshPreamble = reactRefreshScript.innerHTML;
968
- const virtualScript = document.createElement("script");
969
- virtualScript.type = "module";
970
- virtualScript.src = `${server.origin}/${virtualReactRefreshId}`;
971
- reactRefreshScript.replaceWith(virtualScript);
972
- }
973
- const viteClientScript = document.querySelector(
974
- "script[src='/@vite/client']"
975
- );
976
- if (viteClientScript) {
977
- viteClientScript.src = `${server.origin}${viteClientScript.src}`;
978
- }
979
- const newHtml = document.toString();
980
- config.logger.debug("transformIndexHtml " + ctx.filename);
981
- config.logger.debug("Old HTML:\n" + html);
982
- config.logger.debug("New HTML:\n" + newHtml);
983
- return newHtml;
984
- }
985
- },
986
- {
987
- name: "wxt:virtualize-react-refresh",
988
- apply: "serve",
989
- resolveId(id) {
990
- if (id === `/${virtualReactRefreshId}`) {
991
- return resolvedVirtualReactRefreshId;
992
- }
993
- if (id.startsWith("/chunks/")) {
994
- return "\0noop";
995
- }
996
- },
997
- load(id) {
998
- if (id === resolvedVirtualReactRefreshId) {
999
- return reactRefreshPreamble;
1000
- }
1001
- if (id === "\0noop") {
1002
- return "";
1003
- }
1004
- }
1005
- }
1006
- ];
1007
- }
1008
- function pointToDevServer(config, server, id, document, querySelector, attr) {
1009
- document.querySelectorAll(querySelector).forEach((element) => {
1010
- const src = element.getAttribute(attr);
1011
- if (!src || isUrl(src))
1012
- return;
1013
- let resolvedAbsolutePath;
1014
- const matchingAlias = Object.entries(config.alias).find(
1015
- ([key]) => src.startsWith(key)
1016
- );
1017
- if (matchingAlias) {
1018
- const [alias, replacement] = matchingAlias;
1019
- resolvedAbsolutePath = resolve6(
1020
- config.root,
1021
- src.replace(alias, replacement)
1022
- );
1023
- } else {
1024
- resolvedAbsolutePath = resolve6(dirname3(id), src);
1025
- }
1026
- if (resolvedAbsolutePath) {
1027
- const relativePath = normalizePath(
1028
- relative4(config.root, resolvedAbsolutePath)
1029
- );
1030
- if (relativePath.startsWith(".")) {
1031
- let path8 = normalizePath(resolvedAbsolutePath);
1032
- if (!path8.startsWith("/"))
1033
- path8 = "/" + path8;
1034
- element.setAttribute(attr, `${server.origin}/@fs${path8}`);
1035
- } else {
1036
- const url = new URL(relativePath, server.origin);
1037
- element.setAttribute(attr, url.href);
1038
- }
1039
- }
1040
- });
1041
- }
1042
- function isUrl(str) {
1043
- try {
1044
- new URL(str);
1045
- return true;
1046
- } catch {
1047
- return false;
635
+ // src/core/utils/fs.ts
636
+ import fs3 from "fs-extra";
637
+ import glob from "fast-glob";
638
+ async function writeFileIfDifferent(file, newContents) {
639
+ const existingContents = await fs3.readFile(file, "utf-8").catch(() => void 0);
640
+ if (existingContents !== newContents) {
641
+ await fs3.writeFile(file, newContents);
1048
642
  }
1049
643
  }
1050
-
1051
- // src/core/builders/vite/plugins/devServerGlobals.ts
1052
- function devServerGlobals(config) {
1053
- return {
1054
- name: "wxt:dev-server-globals",
1055
- config() {
1056
- if (config.server == null || config.command == "build")
1057
- return;
1058
- return {
1059
- define: {
1060
- __DEV_SERVER_PROTOCOL__: JSON.stringify("ws:"),
1061
- __DEV_SERVER_HOSTNAME__: JSON.stringify(config.server.hostname),
1062
- __DEV_SERVER_PORT__: JSON.stringify(config.server.port)
1063
- }
1064
- };
1065
- }
1066
- };
644
+ async function getPublicFiles() {
645
+ if (!await fs3.exists(wxt.config.publicDir))
646
+ return [];
647
+ const files = await glob("**/*", { cwd: wxt.config.publicDir });
648
+ return files.map(unnormalizePath);
1067
649
  }
1068
650
 
1069
- // src/core/utils/network.ts
1070
- import dns from "node:dns";
1071
-
1072
- // src/core/utils/time.ts
1073
- function formatDuration(duration) {
1074
- if (duration < 1e3)
1075
- return `${duration} ms`;
1076
- if (duration < 1e4)
1077
- return `${(duration / 1e3).toFixed(3)} s`;
1078
- if (duration < 6e4)
1079
- return `${(duration / 1e3).toFixed(1)} s`;
1080
- return `${(duration / 1e3).toFixed(0)} s`;
651
+ // src/core/utils/building/build-entrypoints.ts
652
+ import fs4 from "fs-extra";
653
+ import { dirname as dirname3, resolve as resolve5 } from "path";
654
+ import pc from "picocolors";
655
+ async function buildEntrypoints(groups, spinner) {
656
+ const steps = [];
657
+ for (let i = 0; i < groups.length; i++) {
658
+ const group = groups[i];
659
+ const groupNames = [group].flat().map((e) => e.name);
660
+ const groupNameColored = groupNames.join(pc.dim(", "));
661
+ spinner.text = pc.dim(`[${i + 1}/${groups.length}]`) + ` ${groupNameColored}`;
662
+ try {
663
+ steps.push(await wxt.builder.build(group));
664
+ } catch (err) {
665
+ spinner.stop().clear();
666
+ wxt.logger.error(err);
667
+ throw Error(`Failed to build ${groupNames.join(", ")}`, { cause: err });
668
+ }
669
+ }
670
+ const publicAssets = await copyPublicDirectory();
671
+ return { publicAssets, steps };
1081
672
  }
1082
- function withTimeout(promise, duration) {
1083
- return new Promise((res, rej) => {
1084
- const timeout = setTimeout(() => {
1085
- rej(`Promise timed out after ${duration}ms`);
1086
- }, duration);
1087
- promise.then(res).catch(rej).finally(() => clearTimeout(timeout));
1088
- });
673
+ async function copyPublicDirectory() {
674
+ const files = await getPublicFiles();
675
+ if (files.length === 0)
676
+ return [];
677
+ const publicAssets = [];
678
+ for (const file of files) {
679
+ const srcPath = resolve5(wxt.config.publicDir, file);
680
+ const outPath = resolve5(wxt.config.outDir, file);
681
+ await fs4.ensureDir(dirname3(outPath));
682
+ await fs4.copyFile(srcPath, outPath);
683
+ publicAssets.push({
684
+ type: "asset",
685
+ fileName: file
686
+ });
687
+ }
688
+ return publicAssets;
1089
689
  }
1090
690
 
1091
- // src/core/utils/network.ts
1092
- function isOffline() {
1093
- const isOffline2 = new Promise((res) => {
1094
- dns.resolve("google.com", (err) => {
1095
- if (err == null) {
1096
- res(false);
1097
- } else {
1098
- res(true);
1099
- }
1100
- });
1101
- });
1102
- return withTimeout(isOffline2, 1e3).catch(() => true);
691
+ // src/core/utils/arrays.ts
692
+ function every(array, predicate) {
693
+ for (let i = 0; i < array.length; i++)
694
+ if (!predicate(array[i], i))
695
+ return false;
696
+ return true;
1103
697
  }
1104
- async function isOnline() {
1105
- const offline = await isOffline();
1106
- return !offline;
698
+ function some(array, predicate) {
699
+ for (let i = 0; i < array.length; i++)
700
+ if (predicate(array[i], i))
701
+ return true;
702
+ return false;
1107
703
  }
1108
- async function fetchCached(url, config) {
1109
- let content = "";
1110
- if (await isOnline()) {
1111
- const res = await fetch(url);
1112
- if (res.status < 300) {
1113
- content = await res.text();
1114
- await config.fsCache.set(url, content);
704
+
705
+ // src/core/utils/building/detect-dev-changes.ts
706
+ function detectDevChanges(changedFiles, currentOutput) {
707
+ const isConfigChange = some(
708
+ changedFiles,
709
+ (file) => file === wxt.config.userConfigMetadata.configFile
710
+ );
711
+ if (isConfigChange)
712
+ return { type: "full-restart" };
713
+ const isRunnerChange = some(
714
+ changedFiles,
715
+ (file) => file === wxt.config.runnerConfig.configFile
716
+ );
717
+ if (isRunnerChange)
718
+ return { type: "browser-restart" };
719
+ const changedSteps = new Set(
720
+ changedFiles.flatMap(
721
+ (changedFile) => findEffectedSteps(changedFile, currentOutput)
722
+ )
723
+ );
724
+ if (changedSteps.size === 0)
725
+ return { type: "no-change" };
726
+ const unchangedOutput = {
727
+ manifest: currentOutput.manifest,
728
+ steps: [],
729
+ publicAssets: []
730
+ };
731
+ const changedOutput = {
732
+ manifest: currentOutput.manifest,
733
+ steps: [],
734
+ publicAssets: []
735
+ };
736
+ for (const step of currentOutput.steps) {
737
+ if (changedSteps.has(step)) {
738
+ changedOutput.steps.push(step);
1115
739
  } else {
1116
- config.logger.debug(
1117
- `Failed to download "${url}", falling back to cache...`
1118
- );
740
+ unchangedOutput.steps.push(step);
1119
741
  }
1120
742
  }
1121
- if (!content)
1122
- content = await config.fsCache.get(url) ?? "";
1123
- if (!content)
1124
- throw Error(
1125
- `Offline and "${url}" has not been cached. Try again when online.`
1126
- );
1127
- return content;
1128
- }
1129
-
1130
- // src/core/builders/vite/plugins/download.ts
1131
- function download(config) {
1132
- return {
1133
- name: "wxt:download",
1134
- resolveId(id) {
1135
- if (id.startsWith("url:"))
1136
- return "\0" + id;
1137
- },
1138
- async load(id) {
1139
- if (!id.startsWith("\0url:"))
1140
- return;
1141
- const url = id.replace("\0url:", "");
1142
- return await fetchCached(url, config);
1143
- }
1144
- };
1145
- }
1146
-
1147
- // src/core/builders/vite/plugins/multipageMove.ts
1148
- import { dirname as dirname4, extname, resolve as resolve7, join } from "node:path";
1149
- import fs6, { ensureDir as ensureDir2 } from "fs-extra";
1150
- function multipageMove(entrypoints, config) {
1151
- return {
1152
- name: "wxt:multipage-move",
1153
- async writeBundle(_, bundle) {
1154
- for (const oldBundlePath in bundle) {
1155
- const entrypoint = entrypoints.find(
1156
- (entry) => !!normalizePath(entry.inputPath).endsWith(oldBundlePath)
1157
- );
1158
- if (entrypoint == null) {
1159
- config.logger.debug(
1160
- `No entrypoint found for ${oldBundlePath}, leaving in chunks directory`
1161
- );
1162
- continue;
1163
- }
1164
- const newBundlePath = getEntrypointBundlePath(
1165
- entrypoint,
1166
- config.outDir,
1167
- extname(oldBundlePath)
1168
- );
1169
- if (newBundlePath === oldBundlePath) {
1170
- config.logger.debug(
1171
- "HTML file is already in the correct location",
1172
- oldBundlePath
1173
- );
1174
- continue;
1175
- }
1176
- const oldAbsPath = resolve7(config.outDir, oldBundlePath);
1177
- const newAbsPath = resolve7(config.outDir, newBundlePath);
1178
- await ensureDir2(dirname4(newAbsPath));
1179
- await fs6.move(oldAbsPath, newAbsPath, { overwrite: true });
1180
- const renamedChunk = {
1181
- ...bundle[oldBundlePath],
1182
- fileName: newBundlePath
1183
- };
1184
- delete bundle[oldBundlePath];
1185
- bundle[newBundlePath] = renamedChunk;
1186
- }
1187
- removeEmptyDirs(config.outDir);
1188
- }
1189
- };
1190
- }
1191
- async function removeEmptyDirs(dir) {
1192
- const files = await fs6.readdir(dir);
1193
- for (const file of files) {
1194
- const filePath = join(dir, file);
1195
- const stats = await fs6.stat(filePath);
1196
- if (stats.isDirectory()) {
1197
- await removeEmptyDirs(filePath);
743
+ for (const asset of currentOutput.publicAssets) {
744
+ if (changedSteps.has(asset)) {
745
+ changedOutput.publicAssets.push(asset);
746
+ } else {
747
+ unchangedOutput.publicAssets.push(asset);
1198
748
  }
1199
749
  }
1200
- try {
1201
- await fs6.rmdir(dir);
1202
- } catch {
750
+ const isOnlyHtmlChanges = changedFiles.length > 0 && every(changedFiles, (file) => file.endsWith(".html"));
751
+ if (isOnlyHtmlChanges) {
752
+ return {
753
+ type: "html-reload",
754
+ cachedOutput: unchangedOutput,
755
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
756
+ };
757
+ }
758
+ const isOnlyContentScripts = changedOutput.steps.length > 0 && every(
759
+ changedOutput.steps.flatMap((step) => step.entrypoints),
760
+ (entry) => entry.type === "content-script"
761
+ );
762
+ if (isOnlyContentScripts) {
763
+ return {
764
+ type: "content-script-reload",
765
+ cachedOutput: unchangedOutput,
766
+ changedSteps: changedOutput.steps,
767
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
768
+ };
1203
769
  }
1204
- }
1205
-
1206
- // src/core/builders/vite/plugins/unimport.ts
1207
- import { createUnimport as createUnimport2 } from "unimport";
1208
- import { extname as extname2 } from "path";
1209
- var ENABLED_EXTENSIONS = /* @__PURE__ */ new Set([
1210
- ".js",
1211
- ".jsx",
1212
- ".ts",
1213
- ".tsx",
1214
- ".vue",
1215
- ".svelte"
1216
- ]);
1217
- function unimport(config) {
1218
- const options = config.imports;
1219
- if (options === false)
1220
- return [];
1221
- const unimport2 = createUnimport2(options);
1222
- return {
1223
- name: "wxt:unimport",
1224
- async config() {
1225
- await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
1226
- },
1227
- async transform(code, id) {
1228
- if (id.includes("node_modules"))
1229
- return;
1230
- if (!ENABLED_EXTENSIONS.has(extname2(id)))
1231
- return;
1232
- const injected = await unimport2.injectImports(code, id);
1233
- return {
1234
- code: injected.code,
1235
- map: injected.s.generateMap({ hires: "boundary", source: id })
1236
- };
1237
- }
1238
- };
1239
- }
1240
-
1241
- // src/core/builders/vite/plugins/virtualEntrypoint.ts
1242
- import fs7 from "fs-extra";
1243
- import { resolve as resolve8 } from "path";
1244
- function virtualEntrypoint(type, config) {
1245
- const virtualId = `virtual:wxt-${type}?`;
1246
- const resolvedVirtualId = `\0${virtualId}`;
1247
- return {
1248
- name: `wxt:virtual-entrypoint`,
1249
- resolveId(id) {
1250
- const index = id.indexOf(virtualId);
1251
- if (index === -1)
1252
- return;
1253
- const inputPath = normalizePath(id.substring(index + virtualId.length));
1254
- return resolvedVirtualId + inputPath;
1255
- },
1256
- async load(id) {
1257
- if (!id.startsWith(resolvedVirtualId))
1258
- return;
1259
- const inputPath = id.replace(resolvedVirtualId, "");
1260
- const template = await fs7.readFile(
1261
- resolve8(config.wxtModuleDir, `dist/virtual/${type}-entrypoint.js`),
1262
- "utf-8"
1263
- );
1264
- return template.replace(`virtual:user-${type}`, inputPath);
1265
- }
1266
- };
1267
- }
1268
-
1269
- // src/core/builders/vite/plugins/tsconfigPaths.ts
1270
- function tsconfigPaths(config) {
1271
770
  return {
1272
- name: "wxt:aliases",
1273
- async config() {
1274
- return {
1275
- resolve: {
1276
- alias: config.alias
1277
- }
1278
- };
1279
- }
771
+ type: "extension-reload",
772
+ cachedOutput: unchangedOutput,
773
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
1280
774
  };
1281
775
  }
1282
-
1283
- // src/core/builders/vite/plugins/noopBackground.ts
1284
- function noopBackground() {
1285
- const virtualModuleId = VIRTUAL_NOOP_BACKGROUND_MODULE_ID;
1286
- const resolvedVirtualModuleId = "\0" + virtualModuleId;
1287
- return {
1288
- name: "wxt:noop-background",
1289
- resolveId(id) {
1290
- if (id === virtualModuleId)
1291
- return resolvedVirtualModuleId;
1292
- },
1293
- load(id) {
1294
- if (id === resolvedVirtualModuleId) {
1295
- return `import { defineBackground } from 'wxt/sandbox';
1296
- export default defineBackground(() => void 0)`;
1297
- }
1298
- }
1299
- };
776
+ function findEffectedSteps(changedFile, currentOutput) {
777
+ const changes = [];
778
+ const changedPath = normalizePath(changedFile);
779
+ const isChunkEffected = (chunk) => (
780
+ // If it's an HTML file with the same path, is is effected because HTML files need to be re-rendered
781
+ // - fileName is normalized, relative bundle path, "<entrypoint-name>.html"
782
+ chunk.type === "asset" && changedPath.replace("/index.html", ".html").endsWith(chunk.fileName) || // If it's a chunk that depends on the changed file, it is effected
783
+ // - moduleIds are absolute, normalized paths
784
+ chunk.type === "chunk" && chunk.moduleIds.includes(changedPath)
785
+ );
786
+ for (const step of currentOutput.steps) {
787
+ const effectedChunk = step.chunks.find((chunk) => isChunkEffected(chunk));
788
+ if (effectedChunk)
789
+ changes.push(step);
790
+ }
791
+ const effectedAsset = currentOutput.publicAssets.find(
792
+ (chunk) => isChunkEffected(chunk)
793
+ );
794
+ if (effectedAsset)
795
+ changes.push(effectedAsset);
796
+ return changes;
1300
797
  }
1301
798
 
1302
- // src/core/builders/vite/plugins/cssEntrypoints.ts
1303
- function cssEntrypoints(entrypoint, config) {
1304
- return {
1305
- name: "wxt:css-entrypoint",
1306
- config() {
1307
- return {
1308
- build: {
1309
- rollupOptions: {
1310
- output: {
1311
- assetFileNames: () => getEntrypointBundlePath(entrypoint, config.outDir, ".css")
1312
- }
1313
- }
1314
- }
1315
- };
1316
- },
1317
- generateBundle(_, bundle) {
1318
- Object.keys(bundle).forEach((file) => {
1319
- if (file.endsWith(".js"))
1320
- delete bundle[file];
799
+ // src/core/utils/building/find-entrypoints.ts
800
+ import { relative as relative3, resolve as resolve6 } from "path";
801
+ import fs5 from "fs-extra";
802
+ import { minimatch } from "minimatch";
803
+ import { parseHTML as parseHTML2 } from "linkedom";
804
+ import JSON5 from "json5";
805
+ import glob2 from "fast-glob";
806
+ import pc2 from "picocolors";
807
+ async function findEntrypoints() {
808
+ const relativePaths = await glob2(Object.keys(PATH_GLOB_TO_TYPE_MAP), {
809
+ cwd: wxt.config.entrypointsDir
810
+ });
811
+ relativePaths.sort();
812
+ const pathGlobs = Object.keys(PATH_GLOB_TO_TYPE_MAP);
813
+ const entrypointInfos = relativePaths.reduce((results, relativePath) => {
814
+ const inputPath = resolve6(wxt.config.entrypointsDir, relativePath);
815
+ const name = getEntrypointName(wxt.config.entrypointsDir, inputPath);
816
+ const matchingGlob = pathGlobs.find(
817
+ (glob4) => minimatch(relativePath, glob4)
818
+ );
819
+ if (matchingGlob) {
820
+ const type = PATH_GLOB_TO_TYPE_MAP[matchingGlob];
821
+ results.push({
822
+ name,
823
+ inputPath,
824
+ type,
825
+ skipped: wxt.config.filterEntrypoints != null && !wxt.config.filterEntrypoints.has(name)
1321
826
  });
1322
827
  }
1323
- };
1324
- }
1325
-
1326
- // src/core/builders/vite/plugins/bundleAnalysis.ts
1327
- import { visualizer } from "@aklinker1/rollup-plugin-visualizer";
1328
- import path3 from "node:path";
1329
- var increment = 0;
1330
- function bundleAnalysis(config) {
1331
- return visualizer({
1332
- template: "raw-data",
1333
- filename: path3.resolve(
1334
- config.analysis.outputDir,
1335
- `${config.analysis.outputName}-${increment++}.json`
1336
- )
1337
- });
1338
- }
1339
-
1340
- // src/core/builders/vite/plugins/globals.ts
1341
- function globals(config) {
1342
- return {
1343
- name: "wxt:globals",
1344
- config() {
1345
- const define = {};
1346
- for (const global of getGlobals(config)) {
1347
- define[`import.meta.env.${global.name}`] = JSON.stringify(global.value);
828
+ return results;
829
+ }, []);
830
+ preventNoEntrypoints(entrypointInfos);
831
+ preventDuplicateEntrypointNames(entrypointInfos);
832
+ let hasBackground = false;
833
+ const entrypoints = await Promise.all(
834
+ entrypointInfos.map(async (info) => {
835
+ const { type } = info;
836
+ switch (type) {
837
+ case "popup":
838
+ return await getPopupEntrypoint(info);
839
+ case "sidepanel":
840
+ return await getSidepanelEntrypoint(info);
841
+ case "options":
842
+ return await getOptionsEntrypoint(info);
843
+ case "background":
844
+ hasBackground = true;
845
+ return await getBackgroundEntrypoint(info);
846
+ case "content-script":
847
+ return await getContentScriptEntrypoint(info);
848
+ case "unlisted-page":
849
+ return await getUnlistedPageEntrypoint(info);
850
+ case "unlisted-script":
851
+ return await getUnlistedScriptEntrypoint(info);
852
+ case "content-script-style":
853
+ return {
854
+ ...info,
855
+ type,
856
+ outputDir: resolve6(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
857
+ options: {
858
+ include: void 0,
859
+ exclude: void 0
860
+ }
861
+ };
862
+ default:
863
+ return {
864
+ ...info,
865
+ type,
866
+ outputDir: wxt.config.outDir,
867
+ options: {
868
+ include: void 0,
869
+ exclude: void 0
870
+ }
871
+ };
1348
872
  }
1349
- return {
1350
- define
1351
- };
873
+ })
874
+ );
875
+ if (wxt.config.command === "serve" && !hasBackground) {
876
+ entrypoints.push(
877
+ await getBackgroundEntrypoint({
878
+ inputPath: VIRTUAL_NOOP_BACKGROUND_MODULE_ID,
879
+ name: "background",
880
+ type: "background",
881
+ skipped: false
882
+ })
883
+ );
884
+ }
885
+ wxt.logger.debug("All entrypoints:", entrypoints);
886
+ const skippedEntrypointNames = entrypointInfos.filter((item) => item.skipped).map((item) => item.name);
887
+ if (skippedEntrypointNames.length) {
888
+ wxt.logger.warn(
889
+ `Filter excluded the following entrypoints:
890
+ ${skippedEntrypointNames.map((item) => `${pc2.dim("-")} ${pc2.cyan(item)}`).join("\n")}`
891
+ );
892
+ }
893
+ const targetEntrypoints = entrypoints.filter((entry) => {
894
+ const { include, exclude } = entry.options;
895
+ if (include?.length && exclude?.length) {
896
+ wxt.logger.warn(
897
+ `The ${entry.name} entrypoint lists both include and exclude, but only one can be used per entrypoint. Entrypoint ignored.`
898
+ );
899
+ return false;
1352
900
  }
1353
- };
1354
- }
1355
-
1356
- // src/core/builders/vite/plugins/webextensionPolyfillMock.ts
1357
- import path4 from "node:path";
1358
- function webextensionPolyfillMock(config) {
1359
- return {
1360
- name: "wxt:testing-inline-deps",
1361
- config() {
1362
- return {
1363
- resolve: {
1364
- alias: {
1365
- // Alias to use a mocked version of the polyfill
1366
- "webextension-polyfill": path4.resolve(
1367
- config.wxtModuleDir,
1368
- "dist/virtual/mock-browser"
1369
- )
1370
- }
1371
- },
1372
- ssr: {
1373
- // Inline all WXT modules
1374
- noExternal: ["wxt"]
1375
- }
1376
- };
901
+ if (exclude?.length && !include?.length) {
902
+ return !exclude.includes(wxt.config.browser);
1377
903
  }
1378
- };
904
+ if (include?.length && !exclude?.length) {
905
+ return include.includes(wxt.config.browser);
906
+ }
907
+ if (skippedEntrypointNames.includes(entry.name)) {
908
+ return false;
909
+ }
910
+ return true;
911
+ });
912
+ wxt.logger.debug(`${wxt.config.browser} entrypoints:`, targetEntrypoints);
913
+ await wxt.hooks.callHook("entrypoints:resolved", wxt, targetEntrypoints);
914
+ return targetEntrypoints;
1379
915
  }
1380
-
1381
- // src/core/builders/vite/plugins/excludeBrowserPolyfill.ts
1382
- function excludeBrowserPolyfill(config) {
1383
- const virtualId = "virtual:wxt-webextension-polyfill-disabled";
1384
- return {
1385
- name: "wxt:exclude-browser-polyfill",
1386
- config() {
1387
- if (config.experimental.includeBrowserPolyfill)
1388
- return;
1389
- return {
1390
- resolve: {
1391
- alias: {
1392
- "webextension-polyfill": virtualId
1393
- }
1394
- }
1395
- };
916
+ function preventDuplicateEntrypointNames(files) {
917
+ const namesToPaths = files.reduce(
918
+ (map, { name, inputPath }) => {
919
+ map[name] ??= [];
920
+ map[name].push(inputPath);
921
+ return map;
1396
922
  },
1397
- load(id) {
1398
- if (id === virtualId) {
1399
- return "export default chrome";
923
+ {}
924
+ );
925
+ const errorLines = Object.entries(namesToPaths).reduce(
926
+ (lines, [name, absolutePaths]) => {
927
+ if (absolutePaths.length > 1) {
928
+ lines.push(`- ${name}`);
929
+ absolutePaths.forEach((absolutePath) => {
930
+ lines.push(` - ${relative3(wxt.config.root, absolutePath)}`);
931
+ });
1400
932
  }
933
+ return lines;
934
+ },
935
+ []
936
+ );
937
+ if (errorLines.length > 0) {
938
+ const errorContent = errorLines.join("\n");
939
+ throw Error(
940
+ `Multiple entrypoints with the same name detected, only one entrypoint for each name is allowed.
941
+
942
+ ${errorContent}`
943
+ );
944
+ }
945
+ }
946
+ function preventNoEntrypoints(files) {
947
+ if (files.length === 0) {
948
+ throw Error(`No entrypoints found in ${wxt.config.entrypointsDir}`);
949
+ }
950
+ }
951
+ async function getPopupEntrypoint(info) {
952
+ const options = await getHtmlEntrypointOptions(
953
+ info,
954
+ {
955
+ browserStyle: "browse_style",
956
+ exclude: "exclude",
957
+ include: "include",
958
+ defaultIcon: "default_icon",
959
+ defaultTitle: "default_title",
960
+ mv2Key: "type"
961
+ },
962
+ {
963
+ defaultTitle: (document) => document.querySelector("title")?.textContent || void 0
964
+ },
965
+ {
966
+ defaultTitle: (content) => content,
967
+ mv2Key: (content) => content === "page_action" ? "page_action" : "browser_action"
1401
968
  }
969
+ );
970
+ return {
971
+ type: "popup",
972
+ name: "popup",
973
+ options: resolvePerBrowserOptions(options, wxt.config.browser),
974
+ inputPath: info.inputPath,
975
+ outputDir: wxt.config.outDir,
976
+ skipped: info.skipped
1402
977
  };
1403
978
  }
1404
-
1405
- // src/core/builders/vite/plugins/entrypointGroupGlobals.ts
1406
- function entrypointGroupGlobals(entrypointGroup) {
1407
- return {
1408
- name: "wxt:entrypoint-group-globals",
1409
- config() {
1410
- const define = {};
1411
- let name = Array.isArray(entrypointGroup) ? "html" : entrypointGroup.name;
1412
- for (const global of getEntrypointGlobals(name)) {
1413
- define[`import.meta.env.${global.name}`] = JSON.stringify(global.value);
1414
- }
1415
- return {
1416
- define
1417
- };
979
+ async function getOptionsEntrypoint(info) {
980
+ const options = await getHtmlEntrypointOptions(
981
+ info,
982
+ {
983
+ browserStyle: "browse_style",
984
+ chromeStyle: "chrome_style",
985
+ exclude: "exclude",
986
+ include: "include",
987
+ openInTab: "open_in_tab"
1418
988
  }
989
+ );
990
+ return {
991
+ type: "options",
992
+ name: "options",
993
+ options: resolvePerBrowserOptions(options, wxt.config.browser),
994
+ inputPath: info.inputPath,
995
+ outputDir: wxt.config.outDir,
996
+ skipped: info.skipped
1419
997
  };
1420
998
  }
1421
-
1422
- // src/core/builders/vite/plugins/defineImportMeta.ts
1423
- function defineImportMeta() {
999
+ async function getUnlistedPageEntrypoint(info) {
1000
+ const options = await getHtmlEntrypointOptions(info, {
1001
+ exclude: "exclude",
1002
+ include: "include"
1003
+ });
1424
1004
  return {
1425
- name: "wxt:define",
1426
- config() {
1427
- return {
1428
- define: {
1429
- // This works for all extension contexts, including background service worker
1430
- "import.meta.url": "self.location.href"
1431
- }
1432
- };
1433
- }
1005
+ type: "unlisted-page",
1006
+ name: info.name,
1007
+ inputPath: info.inputPath,
1008
+ outputDir: wxt.config.outDir,
1009
+ options,
1010
+ skipped: info.skipped
1434
1011
  };
1435
1012
  }
1436
-
1437
- // src/core/builders/vite/index.ts
1438
- async function createViteBuilder(inlineConfig, userConfig, wxtConfig) {
1439
- const vite = await import("vite");
1440
- const getBaseConfig = async () => {
1441
- const resolvedInlineConfig = await inlineConfig.vite?.(wxtConfig.env) ?? {};
1442
- const resolvedUserConfig = await userConfig.vite?.(wxtConfig.env) ?? {};
1443
- const config = vite.mergeConfig(
1444
- resolvedUserConfig,
1445
- resolvedInlineConfig
1446
- );
1447
- config.root = wxtConfig.root;
1448
- config.configFile = false;
1449
- config.logLevel = "warn";
1450
- config.mode = wxtConfig.mode;
1451
- config.build ??= {};
1452
- config.build.outDir = wxtConfig.outDir;
1453
- config.build.emptyOutDir = false;
1454
- if (config.build.minify == null && wxtConfig.command === "serve") {
1455
- config.build.minify = false;
1456
- }
1457
- if (config.build.sourcemap == null && wxtConfig.command === "serve") {
1458
- config.build.sourcemap = "inline";
1459
- }
1460
- config.plugins ??= [];
1461
- config.plugins.push(
1462
- download(wxtConfig),
1463
- devHtmlPrerender(wxtConfig),
1464
- unimport(wxtConfig),
1465
- virtualEntrypoint("background", wxtConfig),
1466
- virtualEntrypoint("content-script-isolated-world", wxtConfig),
1467
- virtualEntrypoint("content-script-main-world", wxtConfig),
1468
- virtualEntrypoint("unlisted-script", wxtConfig),
1469
- devServerGlobals(wxtConfig),
1470
- tsconfigPaths(wxtConfig),
1471
- noopBackground(),
1472
- globals(wxtConfig),
1473
- excludeBrowserPolyfill(wxtConfig),
1474
- defineImportMeta()
1013
+ async function getUnlistedScriptEntrypoint({
1014
+ inputPath,
1015
+ name,
1016
+ skipped
1017
+ }) {
1018
+ const defaultExport = await importEntrypointFile(inputPath);
1019
+ if (defaultExport == null) {
1020
+ throw Error(
1021
+ `${name}: Default export not found, did you forget to call "export default defineUnlistedScript(...)"?`
1475
1022
  );
1476
- if (wxtConfig.analysis.enabled) {
1477
- config.plugins.push(bundleAnalysis(wxtConfig));
1478
- }
1479
- return config;
1023
+ }
1024
+ const { main: _, ...options } = defaultExport;
1025
+ return {
1026
+ type: "unlisted-script",
1027
+ name,
1028
+ inputPath,
1029
+ outputDir: wxt.config.outDir,
1030
+ options: resolvePerBrowserOptions(options, wxt.config.browser),
1031
+ skipped
1480
1032
  };
1481
- const getLibModeConfig = (entrypoint) => {
1482
- const entry = getRollupEntry(entrypoint);
1483
- const plugins = [
1484
- entrypointGroupGlobals(entrypoint)
1485
- ];
1486
- if (entrypoint.type === "content-script-style" || entrypoint.type === "unlisted-style") {
1487
- plugins.push(cssEntrypoints(entrypoint, wxtConfig));
1033
+ }
1034
+ async function getBackgroundEntrypoint({
1035
+ inputPath,
1036
+ name,
1037
+ skipped
1038
+ }) {
1039
+ let options = {};
1040
+ if (inputPath !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) {
1041
+ const defaultExport = await importEntrypointFile(inputPath);
1042
+ if (defaultExport == null) {
1043
+ throw Error(
1044
+ `${name}: Default export not found, did you forget to call "export default defineBackground(...)"?`
1045
+ );
1488
1046
  }
1489
- const libMode = {
1490
- mode: wxtConfig.mode,
1491
- plugins,
1492
- build: {
1493
- lib: {
1494
- entry,
1495
- formats: ["iife"],
1496
- name: "_",
1497
- fileName: entrypoint.name
1498
- },
1499
- rollupOptions: {
1500
- output: {
1501
- // There's only a single output for this build, so we use the desired bundle path for the
1502
- // entry output (like "content-scripts/overlay.js")
1503
- entryFileNames: getEntrypointBundlePath(
1504
- entrypoint,
1505
- wxtConfig.outDir,
1506
- ".js"
1507
- ),
1508
- // Output content script CSS to `content-scripts/`, but all other scripts are written to
1509
- // `assets/`.
1510
- assetFileNames: ({ name }) => {
1511
- if (entrypoint.type === "content-script" && name?.endsWith("css")) {
1512
- return `content-scripts/${entrypoint.name}.[ext]`;
1513
- } else {
1514
- return `assets/${entrypoint.name}.[ext]`;
1515
- }
1516
- }
1517
- }
1518
- }
1519
- },
1520
- define: {
1521
- // See https://github.com/aklinker1/vite-plugin-web-extension/issues/96
1522
- "process.env.NODE_ENV": JSON.stringify(wxtConfig.mode)
1523
- }
1524
- };
1525
- return libMode;
1047
+ const { main: _, ...moduleOptions } = defaultExport;
1048
+ options = moduleOptions;
1049
+ }
1050
+ if (wxt.config.manifestVersion !== 3) {
1051
+ delete options.type;
1052
+ }
1053
+ return {
1054
+ type: "background",
1055
+ name,
1056
+ inputPath,
1057
+ outputDir: wxt.config.outDir,
1058
+ options: resolvePerBrowserOptions(options, wxt.config.browser),
1059
+ skipped
1526
1060
  };
1527
- const getMultiPageConfig = (entrypoints) => {
1528
- const htmlEntrypoints = new Set(
1529
- entrypoints.filter(isHtmlEntrypoint).map((e) => e.name)
1061
+ }
1062
+ async function getContentScriptEntrypoint({
1063
+ inputPath,
1064
+ name,
1065
+ skipped
1066
+ }) {
1067
+ const { main: _, ...options } = await importEntrypointFile(inputPath);
1068
+ if (options == null) {
1069
+ throw Error(
1070
+ `${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`
1530
1071
  );
1531
- return {
1532
- mode: wxtConfig.mode,
1533
- plugins: [
1534
- multipageMove(entrypoints, wxtConfig),
1535
- entrypointGroupGlobals(entrypoints)
1536
- ],
1537
- build: {
1538
- rollupOptions: {
1539
- input: entrypoints.reduce((input, entry) => {
1540
- input[entry.name] = getRollupEntry(entry);
1541
- return input;
1542
- }, {}),
1543
- output: {
1544
- // Include a hash to prevent conflicts
1545
- chunkFileNames: "chunks/[name]-[hash].js",
1546
- entryFileNames: ({ name }) => {
1547
- if (htmlEntrypoints.has(name))
1548
- return "chunks/[name]-[hash].js";
1549
- return "[name].js";
1550
- },
1551
- // We can't control the "name", so we need a hash to prevent conflicts
1552
- assetFileNames: "assets/[name]-[hash].[ext]"
1553
- }
1554
- }
1555
- }
1556
- };
1557
- };
1558
- const getCssConfig = (entrypoint) => {
1559
- return {
1560
- mode: wxtConfig.mode,
1561
- plugins: [entrypointGroupGlobals(entrypoint)],
1562
- build: {
1563
- rollupOptions: {
1564
- input: {
1565
- [entrypoint.name]: entrypoint.inputPath
1566
- },
1567
- output: {
1568
- assetFileNames: () => {
1569
- if (entrypoint.type === "content-script-style") {
1570
- return `content-scripts/${entrypoint.name}.[ext]`;
1571
- } else {
1572
- return `assets/${entrypoint.name}.[ext]`;
1573
- }
1574
- }
1575
- }
1576
- }
1577
- }
1578
- };
1579
- };
1072
+ }
1580
1073
  return {
1581
- name: "Vite",
1582
- version: vite.version,
1583
- async build(group) {
1584
- let entryConfig;
1585
- if (Array.isArray(group))
1586
- entryConfig = getMultiPageConfig(group);
1587
- else if (group.inputPath.endsWith(".css"))
1588
- entryConfig = getCssConfig(group);
1589
- else
1590
- entryConfig = getLibModeConfig(group);
1591
- const buildConfig = vite.mergeConfig(await getBaseConfig(), entryConfig);
1592
- const result = await vite.build(buildConfig);
1593
- return {
1594
- entrypoints: group,
1595
- chunks: getBuildOutputChunks(result)
1596
- };
1074
+ type: "content-script",
1075
+ name,
1076
+ inputPath,
1077
+ outputDir: resolve6(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
1078
+ options: resolvePerBrowserOptions(options, wxt.config.browser),
1079
+ skipped
1080
+ };
1081
+ }
1082
+ async function getSidepanelEntrypoint(info) {
1083
+ const options = await getHtmlEntrypointOptions(
1084
+ info,
1085
+ {
1086
+ browserStyle: "browse_style",
1087
+ exclude: "exclude",
1088
+ include: "include",
1089
+ defaultIcon: "default_icon",
1090
+ defaultTitle: "default_title",
1091
+ openAtInstall: "open_at_install"
1597
1092
  },
1598
- async createServer(info) {
1599
- const serverConfig = {
1600
- server: {
1601
- port: info.port,
1602
- strictPort: true,
1603
- host: info.hostname,
1604
- origin: info.origin
1605
- }
1606
- };
1607
- const baseConfig = await getBaseConfig();
1608
- const viteServer = await vite.createServer(
1609
- vite.mergeConfig(baseConfig, serverConfig)
1610
- );
1611
- const server = {
1612
- async listen() {
1613
- await viteServer.listen(info.port);
1614
- },
1615
- async close() {
1616
- await viteServer.close();
1617
- },
1618
- transformHtml(...args) {
1619
- return viteServer.transformIndexHtml(...args);
1620
- },
1621
- ws: {
1622
- send(message, payload) {
1623
- return viteServer.ws.send(message, payload);
1624
- },
1625
- on(message, cb) {
1626
- viteServer.ws.on(message, cb);
1627
- }
1628
- },
1629
- watcher: viteServer.watcher
1630
- };
1631
- return server;
1093
+ {
1094
+ defaultTitle: (document) => document.querySelector("title")?.textContent || void 0
1095
+ },
1096
+ {
1097
+ defaultTitle: (content) => content
1098
+ }
1099
+ );
1100
+ return {
1101
+ type: "sidepanel",
1102
+ name: info.name,
1103
+ options: resolvePerBrowserOptions(options, wxt.config.browser),
1104
+ inputPath: info.inputPath,
1105
+ outputDir: wxt.config.outDir,
1106
+ skipped: info.skipped
1107
+ };
1108
+ }
1109
+ async function getHtmlEntrypointOptions(info, keyMap, queries, parsers) {
1110
+ const content = await fs5.readFile(info.inputPath, "utf-8");
1111
+ const { document } = parseHTML2(content);
1112
+ const options = {};
1113
+ const defaultQuery = (manifestKey) => document.querySelector(`meta[name='manifest.${manifestKey}']`)?.getAttribute("content");
1114
+ Object.entries(keyMap).forEach(([_key, manifestKey]) => {
1115
+ const key = _key;
1116
+ const content2 = queries?.[key] ? queries[key](document, manifestKey) : defaultQuery(manifestKey);
1117
+ if (content2) {
1118
+ try {
1119
+ options[key] = (parsers?.[key] ?? JSON5.parse)(content2);
1120
+ } catch (err) {
1121
+ wxt.logger.fatal(
1122
+ `Failed to parse meta tag content. Usually this means you have invalid JSON5 content (content=${content2})`,
1123
+ err
1124
+ );
1125
+ }
1126
+ }
1127
+ });
1128
+ return options;
1129
+ }
1130
+ var PATH_GLOB_TO_TYPE_MAP = {
1131
+ "sandbox.html": "sandbox",
1132
+ "sandbox/index.html": "sandbox",
1133
+ "*.sandbox.html": "sandbox",
1134
+ "*.sandbox/index.html": "sandbox",
1135
+ "bookmarks.html": "bookmarks",
1136
+ "bookmarks/index.html": "bookmarks",
1137
+ "history.html": "history",
1138
+ "history/index.html": "history",
1139
+ "newtab.html": "newtab",
1140
+ "newtab/index.html": "newtab",
1141
+ "sidepanel.html": "sidepanel",
1142
+ "sidepanel/index.html": "sidepanel",
1143
+ "*.sidepanel.html": "sidepanel",
1144
+ "*.sidepanel/index.html": "sidepanel",
1145
+ "devtools.html": "devtools",
1146
+ "devtools/index.html": "devtools",
1147
+ "background.[jt]s": "background",
1148
+ "background/index.[jt]s": "background",
1149
+ [VIRTUAL_NOOP_BACKGROUND_MODULE_ID]: "background",
1150
+ "content.[jt]s?(x)": "content-script",
1151
+ "content/index.[jt]s?(x)": "content-script",
1152
+ "*.content.[jt]s?(x)": "content-script",
1153
+ "*.content/index.[jt]s?(x)": "content-script",
1154
+ [`content.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
1155
+ [`*.content.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
1156
+ [`content/index.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
1157
+ [`*.content/index.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
1158
+ "popup.html": "popup",
1159
+ "popup/index.html": "popup",
1160
+ "options.html": "options",
1161
+ "options/index.html": "options",
1162
+ "*.html": "unlisted-page",
1163
+ "*/index.html": "unlisted-page",
1164
+ "*.[jt]s?(x)": "unlisted-script",
1165
+ "*/index.[jt]s?(x)": "unlisted-script",
1166
+ [`*.${CSS_EXTENSIONS_PATTERN}`]: "unlisted-style",
1167
+ [`*/index.${CSS_EXTENSIONS_PATTERN}`]: "unlisted-style"
1168
+ };
1169
+ var CONTENT_SCRIPT_OUT_DIR = "content-scripts";
1170
+
1171
+ // src/core/utils/building/generate-wxt-dir.ts
1172
+ import { createUnimport as createUnimport2 } from "unimport";
1173
+ import fs6 from "fs-extra";
1174
+ import { relative as relative4, resolve as resolve7 } from "path";
1175
+ import path4 from "node:path";
1176
+
1177
+ // src/core/utils/i18n.ts
1178
+ var predefinedMessages = {
1179
+ "@@extension_id": {
1180
+ message: "<browser.runtime.id>",
1181
+ 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."
1182
+ },
1183
+ "@@ui_locale": {
1184
+ message: "<browser.i18n.getUiLocale()>",
1185
+ description: ""
1186
+ },
1187
+ "@@bidi_dir": {
1188
+ message: "<ltr|rtl>",
1189
+ 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.'
1190
+ },
1191
+ "@@bidi_reversed_dir": {
1192
+ message: "<rtl|ltr>",
1193
+ description: `If the @@bidi_dir is "ltr", then this is "rtl"; otherwise, it's "ltr".`
1194
+ },
1195
+ "@@bidi_start_edge": {
1196
+ message: "<left|right>",
1197
+ description: `If the @@bidi_dir is "ltr", then this is "left"; otherwise, it's "right".`
1198
+ },
1199
+ "@@bidi_end_edge": {
1200
+ message: "<right|left>",
1201
+ description: `If the @@bidi_dir is "ltr", then this is "right"; otherwise, it's "left".`
1202
+ }
1203
+ };
1204
+ function parseI18nMessages(messagesJson) {
1205
+ return Object.entries({
1206
+ ...predefinedMessages,
1207
+ ...messagesJson
1208
+ }).map(([name, details]) => ({
1209
+ name,
1210
+ ...details
1211
+ }));
1212
+ }
1213
+
1214
+ // src/core/utils/building/generate-wxt-dir.ts
1215
+ async function generateTypesDir(entrypoints) {
1216
+ await fs6.ensureDir(wxt.config.typesDir);
1217
+ const references = [];
1218
+ if (wxt.config.imports !== false) {
1219
+ const unimport2 = createUnimport2(wxt.config.imports);
1220
+ references.push(await writeImportsDeclarationFile(unimport2));
1221
+ if (wxt.config.imports.eslintrc.enabled) {
1222
+ await writeImportsEslintFile(unimport2, wxt.config.imports);
1223
+ }
1224
+ }
1225
+ references.push(await writePathsDeclarationFile(entrypoints));
1226
+ references.push(await writeI18nDeclarationFile());
1227
+ references.push(await writeGlobalsDeclarationFile());
1228
+ const mainReference = await writeMainDeclarationFile(references);
1229
+ await writeTsConfigFile(mainReference);
1230
+ }
1231
+ async function writeImportsDeclarationFile(unimport2) {
1232
+ const filePath = resolve7(wxt.config.typesDir, "imports.d.ts");
1233
+ await unimport2.scanImportsFromDir(void 0, { cwd: wxt.config.srcDir });
1234
+ await writeFileIfDifferent(
1235
+ filePath,
1236
+ ["// Generated by wxt", await unimport2.generateTypeDeclarations()].join(
1237
+ "\n"
1238
+ ) + "\n"
1239
+ );
1240
+ return filePath;
1241
+ }
1242
+ async function writeImportsEslintFile(unimport2, options) {
1243
+ const globals2 = {};
1244
+ const eslintrc = { globals: globals2 };
1245
+ (await unimport2.getImports()).map((i) => i.as ?? i.name).filter(Boolean).sort().forEach((name) => {
1246
+ eslintrc.globals[name] = options.eslintrc.globalsPropValue;
1247
+ });
1248
+ await fs6.writeJson(options.eslintrc.filePath, eslintrc, { spaces: 2 });
1249
+ }
1250
+ async function writePathsDeclarationFile(entrypoints) {
1251
+ const filePath = resolve7(wxt.config.typesDir, "paths.d.ts");
1252
+ const unions = entrypoints.map(
1253
+ (entry) => getEntrypointBundlePath(
1254
+ entry,
1255
+ wxt.config.outDir,
1256
+ isHtmlEntrypoint(entry) ? ".html" : ".js"
1257
+ )
1258
+ ).concat(await getPublicFiles()).map(normalizePath).map((path8) => ` | "/${path8}"`).sort().join("\n");
1259
+ const template = `// Generated by wxt
1260
+ import "wxt/browser";
1261
+
1262
+ declare module "wxt/browser" {
1263
+ export type PublicPath =
1264
+ {{ union }}
1265
+ type HtmlPublicPath = Extract<PublicPath, \`\${string}.html\`>
1266
+ export interface WxtRuntime extends Runtime.Static {
1267
+ getURL(path: PublicPath): string;
1268
+ getURL(path: \`\${HtmlPublicPath}\${string}\`): string;
1269
+ }
1270
+ }
1271
+ `;
1272
+ await writeFileIfDifferent(
1273
+ filePath,
1274
+ template.replace("{{ union }}", unions || " | never")
1275
+ );
1276
+ return filePath;
1277
+ }
1278
+ async function writeI18nDeclarationFile() {
1279
+ const filePath = resolve7(wxt.config.typesDir, "i18n.d.ts");
1280
+ const defaultLocale = wxt.config.manifest.default_locale;
1281
+ const template = `// Generated by wxt
1282
+ import "wxt/browser";
1283
+
1284
+ declare module "wxt/browser" {
1285
+ /**
1286
+ * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
1287
+ */
1288
+ interface GetMessageOptions {
1289
+ /**
1290
+ * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
1291
+ */
1292
+ escapeLt?: boolean
1293
+ }
1294
+
1295
+ export interface WxtI18n extends I18n.Static {
1296
+ {{ overrides }}
1297
+ }
1298
+ }
1299
+ `;
1300
+ let messages;
1301
+ if (defaultLocale) {
1302
+ const defaultLocalePath = path4.resolve(
1303
+ wxt.config.publicDir,
1304
+ "_locales",
1305
+ defaultLocale,
1306
+ "messages.json"
1307
+ );
1308
+ const content = JSON.parse(await fs6.readFile(defaultLocalePath, "utf-8"));
1309
+ messages = parseI18nMessages(content);
1310
+ } else {
1311
+ messages = parseI18nMessages({});
1312
+ }
1313
+ const overrides = messages.map((message) => {
1314
+ return ` /**
1315
+ * ${message.description || "No message description."}
1316
+ *
1317
+ * "${message.message}"
1318
+ */
1319
+ getMessage(
1320
+ messageName: "${message.name}",
1321
+ substitutions?: string | string[],
1322
+ options?: GetMessageOptions,
1323
+ ): string;`;
1324
+ });
1325
+ await writeFileIfDifferent(
1326
+ filePath,
1327
+ template.replace("{{ overrides }}", overrides.join("\n"))
1328
+ );
1329
+ return filePath;
1330
+ }
1331
+ async function writeGlobalsDeclarationFile() {
1332
+ const filePath = resolve7(wxt.config.typesDir, "globals.d.ts");
1333
+ const globals2 = [...getGlobals(wxt.config), ...getEntrypointGlobals("")];
1334
+ await writeFileIfDifferent(
1335
+ filePath,
1336
+ [
1337
+ "// Generated by wxt",
1338
+ "export {}",
1339
+ "interface ImportMetaEnv {",
1340
+ ...globals2.map((global) => ` readonly ${global.name}: ${global.type};`),
1341
+ "}",
1342
+ "interface ImportMeta {",
1343
+ " readonly env: ImportMetaEnv",
1344
+ "}"
1345
+ ].join("\n") + "\n"
1346
+ );
1347
+ return filePath;
1348
+ }
1349
+ async function writeMainDeclarationFile(references) {
1350
+ const dir = wxt.config.wxtDir;
1351
+ const filePath = resolve7(dir, "wxt.d.ts");
1352
+ await writeFileIfDifferent(
1353
+ filePath,
1354
+ [
1355
+ "// Generated by wxt",
1356
+ `/// <reference types="wxt/vite-builder-env" />`,
1357
+ ...references.map(
1358
+ (ref) => `/// <reference types="./${normalizePath(relative4(dir, ref))}" />`
1359
+ )
1360
+ ].join("\n") + "\n"
1361
+ );
1362
+ return filePath;
1363
+ }
1364
+ async function writeTsConfigFile(mainReference) {
1365
+ const dir = wxt.config.wxtDir;
1366
+ const getTsconfigPath = (path8) => normalizePath(relative4(dir, path8));
1367
+ const paths = Object.entries(wxt.config.alias).flatMap(([alias, absolutePath]) => {
1368
+ const aliasPath = getTsconfigPath(absolutePath);
1369
+ return [
1370
+ ` "${alias}": ["${aliasPath}"]`,
1371
+ ` "${alias}/*": ["${aliasPath}/*"]`
1372
+ ];
1373
+ }).join(",\n");
1374
+ await writeFileIfDifferent(
1375
+ resolve7(dir, "tsconfig.json"),
1376
+ `{
1377
+ "compilerOptions": {
1378
+ "target": "ESNext",
1379
+ "module": "ESNext",
1380
+ "moduleResolution": "Bundler",
1381
+ "noEmit": true,
1382
+ "esModuleInterop": true,
1383
+ "forceConsistentCasingInFileNames": true,
1384
+ "resolveJsonModule": true,
1385
+ "strict": true,
1386
+ "skipLibCheck": true,
1387
+ "paths": {
1388
+ ${paths}
1389
+ }
1390
+ },
1391
+ "include": [
1392
+ "${getTsconfigPath(wxt.config.root)}/**/*",
1393
+ "./${getTsconfigPath(mainReference)}"
1394
+ ],
1395
+ "exclude": ["${getTsconfigPath(wxt.config.outBaseDir)}"]
1396
+ }`
1397
+ );
1398
+ }
1399
+
1400
+ // src/core/utils/building/resolve-config.ts
1401
+ import { loadConfig } from "c12";
1402
+ import path5 from "node:path";
1403
+
1404
+ // src/core/utils/cache.ts
1405
+ import fs7, { ensureDir as ensureDir2 } from "fs-extra";
1406
+ import { dirname as dirname4, resolve as resolve8 } from "path";
1407
+ function createFsCache(wxtDir) {
1408
+ const getPath = (key) => resolve8(wxtDir, "cache", encodeURIComponent(key));
1409
+ return {
1410
+ async set(key, value) {
1411
+ const path8 = getPath(key);
1412
+ await ensureDir2(dirname4(path8));
1413
+ await writeFileIfDifferent(path8, value);
1414
+ },
1415
+ async get(key) {
1416
+ const path8 = getPath(key);
1417
+ try {
1418
+ return await fs7.readFile(path8, "utf-8");
1419
+ } catch {
1420
+ return void 0;
1421
+ }
1632
1422
  }
1633
1423
  };
1634
1424
  }
1635
- function getBuildOutputChunks(result) {
1636
- if ("on" in result)
1637
- throw Error("wxt does not support vite watch mode.");
1638
- if (Array.isArray(result))
1639
- return result.flatMap(({ output }) => output);
1640
- return result.output;
1641
- }
1642
- function getRollupEntry(entrypoint) {
1643
- let virtualEntrypointType;
1644
- switch (entrypoint.type) {
1645
- case "background":
1646
- case "unlisted-script":
1647
- virtualEntrypointType = entrypoint.type;
1648
- break;
1649
- case "content-script":
1650
- virtualEntrypointType = entrypoint.options.world === "MAIN" ? "content-script-main-world" : "content-script-isolated-world";
1651
- break;
1652
- }
1653
- return virtualEntrypointType ? `virtual:wxt-${virtualEntrypointType}?${entrypoint.inputPath}` : entrypoint.inputPath;
1654
- }
1655
1425
 
1656
1426
  // src/core/utils/building/resolve-config.ts
1427
+ import consola, { LogLevels } from "consola";
1657
1428
  import defu from "defu";
1658
1429
 
1659
1430
  // src/core/utils/package.ts
@@ -1676,7 +1447,7 @@ function isModuleInstalled(name) {
1676
1447
 
1677
1448
  // src/core/utils/building/resolve-config.ts
1678
1449
  import fs9 from "fs-extra";
1679
- async function resolveConfig(inlineConfig, command, server) {
1450
+ async function resolveConfig(inlineConfig, command) {
1680
1451
  let userConfig = {};
1681
1452
  let userConfigMetadata;
1682
1453
  if (inlineConfig.configFile !== false) {
@@ -1692,7 +1463,7 @@ async function resolveConfig(inlineConfig, command, server) {
1692
1463
  userConfig = loadedConfig ?? {};
1693
1464
  userConfigMetadata = metadata;
1694
1465
  }
1695
- const mergedConfig = mergeInlineConfig(inlineConfig, userConfig);
1466
+ const mergedConfig = await mergeInlineConfig(inlineConfig, userConfig);
1696
1467
  const debug = mergedConfig.debug ?? false;
1697
1468
  const logger = mergedConfig.logger ?? consola;
1698
1469
  if (debug)
@@ -1740,7 +1511,19 @@ async function resolveConfig(inlineConfig, command, server) {
1740
1511
  "~~": root
1741
1512
  }).map(([key, value]) => [key, path5.resolve(root, value)])
1742
1513
  );
1743
- const finalConfig = {
1514
+ let devServerConfig;
1515
+ if (command === "serve") {
1516
+ let port = mergedConfig.dev?.server?.port;
1517
+ if (port == null || !isFinite(port)) {
1518
+ const { default: getPort, portNumbers } = await import("get-port");
1519
+ port = await getPort({ port: portNumbers(3e3, 3010) });
1520
+ }
1521
+ devServerConfig = {
1522
+ port,
1523
+ hostname: "localhost"
1524
+ };
1525
+ }
1526
+ return {
1744
1527
  browser,
1745
1528
  command,
1746
1529
  debug,
@@ -1770,26 +1553,18 @@ async function resolveConfig(inlineConfig, command, server) {
1770
1553
  experimental: defu(mergedConfig.experimental, {
1771
1554
  includeBrowserPolyfill: true
1772
1555
  }),
1773
- server,
1774
1556
  dev: {
1557
+ server: devServerConfig,
1775
1558
  reloadCommand
1776
1559
  },
1777
- hooks: mergedConfig.hooks ?? {}
1778
- };
1779
- const builder = await createViteBuilder(
1780
- inlineConfig,
1781
- userConfig,
1782
- finalConfig
1783
- );
1784
- return {
1785
- ...finalConfig,
1786
- builder
1560
+ hooks: mergedConfig.hooks ?? {},
1561
+ vite: mergedConfig.vite ?? (() => ({}))
1787
1562
  };
1788
1563
  }
1789
1564
  async function resolveManifestConfig(env, manifest) {
1790
1565
  return await (typeof manifest === "function" ? manifest(env) : manifest ?? {});
1791
1566
  }
1792
- function mergeInlineConfig(inlineConfig, userConfig) {
1567
+ async function mergeInlineConfig(inlineConfig, userConfig) {
1793
1568
  const imports = inlineConfig.imports === false || userConfig.imports === false ? false : userConfig.imports == null && inlineConfig.imports == null ? void 0 : defu(inlineConfig.imports ?? {}, userConfig.imports ?? {});
1794
1569
  const manifest = async (env) => {
1795
1570
  const user = await resolveManifestConfig(env, userConfig.manifest);
@@ -1800,14 +1575,14 @@ function mergeInlineConfig(inlineConfig, userConfig) {
1800
1575
  userConfig.transformManifest?.(manifest2);
1801
1576
  inlineConfig.transformManifest?.(manifest2);
1802
1577
  };
1578
+ const builderConfig = await mergeBuilderConfig(inlineConfig, userConfig);
1803
1579
  return {
1804
1580
  ...defu(inlineConfig, userConfig),
1805
1581
  // Custom merge values
1806
1582
  transformManifest,
1807
1583
  imports,
1808
1584
  manifest,
1809
- // Vite builder handles merging vite config internally
1810
- vite: void 0
1585
+ ...builderConfig
1811
1586
  };
1812
1587
  }
1813
1588
  function resolveZipConfig(root, mergedConfig) {
@@ -1908,6 +1683,19 @@ var COMMAND_MODES = {
1908
1683
  build: "production",
1909
1684
  serve: "development"
1910
1685
  };
1686
+ async function mergeBuilderConfig(inlineConfig, userConfig) {
1687
+ const vite = await import("vite").catch(() => void 0);
1688
+ if (vite) {
1689
+ return {
1690
+ vite: async (env) => {
1691
+ const resolvedInlineConfig = await inlineConfig.vite?.(env) ?? {};
1692
+ const resolvedUserConfig = await userConfig.vite?.(env) ?? {};
1693
+ return vite.mergeConfig(resolvedUserConfig, resolvedInlineConfig);
1694
+ }
1695
+ };
1696
+ }
1697
+ throw Error("Builder not found. Make sure vite is installed.");
1698
+ }
1911
1699
 
1912
1700
  // src/core/utils/building/group-entrypoints.ts
1913
1701
  function groupEntrypoints(entrypoints) {
@@ -2621,8 +2409,8 @@ function discoverIcons(buildOutput) {
2621
2409
  return icons.length > 0 ? Object.fromEntries(icons) : void 0;
2622
2410
  }
2623
2411
  function addDevModeCsp(manifest) {
2624
- const permission = `http://${wxt.config.server?.hostname ?? ""}/*`;
2625
- const allowedCsp = wxt.config.server?.origin ?? "http://localhost:*";
2412
+ const permission = `http://${wxt.server?.hostname ?? ""}/*`;
2413
+ const allowedCsp = wxt.server?.origin ?? "http://localhost:*";
2626
2414
  if (manifest.manifest_version === 3) {
2627
2415
  addHostPermission(manifest, permission);
2628
2416
  } else {
@@ -2635,7 +2423,7 @@ function addDevModeCsp(manifest) {
2635
2423
  ) : manifest.content_security_policy ?? "script-src 'self'; object-src 'self';"
2636
2424
  // default CSP for MV2
2637
2425
  );
2638
- if (wxt.config.server)
2426
+ if (wxt.server)
2639
2427
  csp.add("script-src", allowedCsp);
2640
2428
  if (manifest.manifest_version === 3) {
2641
2429
  manifest.content_security_policy ??= {};
@@ -2895,7 +2683,7 @@ async function internalBuild() {
2895
2683
  const target = `${wxt.config.browser}-mv${wxt.config.manifestVersion}`;
2896
2684
  wxt.logger.info(
2897
2685
  `${verb} ${pc5.cyan(target)} for ${pc5.cyan(wxt.config.mode)} with ${pc5.green(
2898
- `${wxt.config.builder.name} ${wxt.config.builder.version}`
2686
+ `${wxt.builder.name} ${wxt.builder.version}`
2899
2687
  )}`
2900
2688
  );
2901
2689
  const startTime = Date.now();
@@ -3193,11 +2981,227 @@ var packageManagers = {
3193
2981
  yarn
3194
2982
  };
3195
2983
 
2984
+ // src/core/builders/vite/index.ts
2985
+ async function createViteBuilder(wxtConfig, server) {
2986
+ const vite = await import("vite");
2987
+ const getBaseConfig = async () => {
2988
+ const config = await wxtConfig.vite(wxtConfig.env);
2989
+ config.root = wxtConfig.root;
2990
+ config.configFile = false;
2991
+ config.logLevel = "warn";
2992
+ config.mode = wxtConfig.mode;
2993
+ config.build ??= {};
2994
+ config.build.outDir = wxtConfig.outDir;
2995
+ config.build.emptyOutDir = false;
2996
+ if (config.build.minify == null && wxtConfig.command === "serve") {
2997
+ config.build.minify = false;
2998
+ }
2999
+ if (config.build.sourcemap == null && wxtConfig.command === "serve") {
3000
+ config.build.sourcemap = "inline";
3001
+ }
3002
+ config.plugins ??= [];
3003
+ config.plugins.push(
3004
+ download(wxtConfig),
3005
+ devHtmlPrerender(wxtConfig, server),
3006
+ unimport(wxtConfig),
3007
+ virtualEntrypoint("background", wxtConfig),
3008
+ virtualEntrypoint("content-script-isolated-world", wxtConfig),
3009
+ virtualEntrypoint("content-script-main-world", wxtConfig),
3010
+ virtualEntrypoint("unlisted-script", wxtConfig),
3011
+ devServerGlobals(wxtConfig, server),
3012
+ tsconfigPaths(wxtConfig),
3013
+ noopBackground(),
3014
+ globals(wxtConfig),
3015
+ excludeBrowserPolyfill(wxtConfig),
3016
+ defineImportMeta()
3017
+ );
3018
+ if (wxtConfig.analysis.enabled) {
3019
+ config.plugins.push(bundleAnalysis(wxtConfig));
3020
+ }
3021
+ return config;
3022
+ };
3023
+ const getLibModeConfig = (entrypoint) => {
3024
+ const entry = getRollupEntry(entrypoint);
3025
+ const plugins = [
3026
+ entrypointGroupGlobals(entrypoint)
3027
+ ];
3028
+ if (entrypoint.type === "content-script-style" || entrypoint.type === "unlisted-style") {
3029
+ plugins.push(cssEntrypoints(entrypoint, wxtConfig));
3030
+ }
3031
+ const libMode = {
3032
+ mode: wxtConfig.mode,
3033
+ plugins,
3034
+ build: {
3035
+ lib: {
3036
+ entry,
3037
+ formats: ["iife"],
3038
+ name: "_",
3039
+ fileName: entrypoint.name
3040
+ },
3041
+ rollupOptions: {
3042
+ output: {
3043
+ // There's only a single output for this build, so we use the desired bundle path for the
3044
+ // entry output (like "content-scripts/overlay.js")
3045
+ entryFileNames: getEntrypointBundlePath(
3046
+ entrypoint,
3047
+ wxtConfig.outDir,
3048
+ ".js"
3049
+ ),
3050
+ // Output content script CSS to `content-scripts/`, but all other scripts are written to
3051
+ // `assets/`.
3052
+ assetFileNames: ({ name }) => {
3053
+ if (entrypoint.type === "content-script" && name?.endsWith("css")) {
3054
+ return `content-scripts/${entrypoint.name}.[ext]`;
3055
+ } else {
3056
+ return `assets/${entrypoint.name}.[ext]`;
3057
+ }
3058
+ }
3059
+ }
3060
+ }
3061
+ },
3062
+ define: {
3063
+ // See https://github.com/aklinker1/vite-plugin-web-extension/issues/96
3064
+ "process.env.NODE_ENV": JSON.stringify(wxtConfig.mode)
3065
+ }
3066
+ };
3067
+ return libMode;
3068
+ };
3069
+ const getMultiPageConfig = (entrypoints) => {
3070
+ const htmlEntrypoints = new Set(
3071
+ entrypoints.filter(isHtmlEntrypoint).map((e) => e.name)
3072
+ );
3073
+ return {
3074
+ mode: wxtConfig.mode,
3075
+ plugins: [
3076
+ multipageMove(entrypoints, wxtConfig),
3077
+ entrypointGroupGlobals(entrypoints)
3078
+ ],
3079
+ build: {
3080
+ rollupOptions: {
3081
+ input: entrypoints.reduce((input, entry) => {
3082
+ input[entry.name] = getRollupEntry(entry);
3083
+ return input;
3084
+ }, {}),
3085
+ output: {
3086
+ // Include a hash to prevent conflicts
3087
+ chunkFileNames: "chunks/[name]-[hash].js",
3088
+ entryFileNames: ({ name }) => {
3089
+ if (htmlEntrypoints.has(name))
3090
+ return "chunks/[name]-[hash].js";
3091
+ return "[name].js";
3092
+ },
3093
+ // We can't control the "name", so we need a hash to prevent conflicts
3094
+ assetFileNames: "assets/[name]-[hash].[ext]"
3095
+ }
3096
+ }
3097
+ }
3098
+ };
3099
+ };
3100
+ const getCssConfig = (entrypoint) => {
3101
+ return {
3102
+ mode: wxtConfig.mode,
3103
+ plugins: [entrypointGroupGlobals(entrypoint)],
3104
+ build: {
3105
+ rollupOptions: {
3106
+ input: {
3107
+ [entrypoint.name]: entrypoint.inputPath
3108
+ },
3109
+ output: {
3110
+ assetFileNames: () => {
3111
+ if (entrypoint.type === "content-script-style") {
3112
+ return `content-scripts/${entrypoint.name}.[ext]`;
3113
+ } else {
3114
+ return `assets/${entrypoint.name}.[ext]`;
3115
+ }
3116
+ }
3117
+ }
3118
+ }
3119
+ }
3120
+ };
3121
+ };
3122
+ return {
3123
+ name: "Vite",
3124
+ version: vite.version,
3125
+ async build(group) {
3126
+ let entryConfig;
3127
+ if (Array.isArray(group))
3128
+ entryConfig = getMultiPageConfig(group);
3129
+ else if (group.inputPath.endsWith(".css"))
3130
+ entryConfig = getCssConfig(group);
3131
+ else
3132
+ entryConfig = getLibModeConfig(group);
3133
+ const buildConfig = vite.mergeConfig(await getBaseConfig(), entryConfig);
3134
+ const result = await vite.build(buildConfig);
3135
+ return {
3136
+ entrypoints: group,
3137
+ chunks: getBuildOutputChunks(result)
3138
+ };
3139
+ },
3140
+ async createServer(info) {
3141
+ const serverConfig = {
3142
+ server: {
3143
+ port: info.port,
3144
+ strictPort: true,
3145
+ host: info.hostname,
3146
+ origin: info.origin
3147
+ }
3148
+ };
3149
+ const baseConfig = await getBaseConfig();
3150
+ const viteServer = await vite.createServer(
3151
+ vite.mergeConfig(baseConfig, serverConfig)
3152
+ );
3153
+ const server2 = {
3154
+ async listen() {
3155
+ await viteServer.listen(info.port);
3156
+ },
3157
+ async close() {
3158
+ await viteServer.close();
3159
+ },
3160
+ transformHtml(...args) {
3161
+ return viteServer.transformIndexHtml(...args);
3162
+ },
3163
+ ws: {
3164
+ send(message, payload) {
3165
+ return viteServer.ws.send(message, payload);
3166
+ },
3167
+ on(message, cb) {
3168
+ viteServer.ws.on(message, cb);
3169
+ }
3170
+ },
3171
+ watcher: viteServer.watcher
3172
+ };
3173
+ return server2;
3174
+ }
3175
+ };
3176
+ }
3177
+ function getBuildOutputChunks(result) {
3178
+ if ("on" in result)
3179
+ throw Error("wxt does not support vite watch mode.");
3180
+ if (Array.isArray(result))
3181
+ return result.flatMap(({ output }) => output);
3182
+ return result.output;
3183
+ }
3184
+ function getRollupEntry(entrypoint) {
3185
+ let virtualEntrypointType;
3186
+ switch (entrypoint.type) {
3187
+ case "background":
3188
+ case "unlisted-script":
3189
+ virtualEntrypointType = entrypoint.type;
3190
+ break;
3191
+ case "content-script":
3192
+ virtualEntrypointType = entrypoint.options.world === "MAIN" ? "content-script-main-world" : "content-script-isolated-world";
3193
+ break;
3194
+ }
3195
+ return virtualEntrypointType ? `virtual:wxt-${virtualEntrypointType}?${entrypoint.inputPath}` : entrypoint.inputPath;
3196
+ }
3197
+
3196
3198
  // src/core/wxt.ts
3197
3199
  var wxt;
3198
- async function registerWxt(command, inlineConfig = {}, server) {
3199
- const config = await resolveConfig(inlineConfig, command, server);
3200
+ async function registerWxt(command, inlineConfig = {}, getServer) {
3200
3201
  const hooks = createHooks();
3202
+ const config = await resolveConfig(inlineConfig, command);
3203
+ const server = await getServer?.(config);
3204
+ const builder = await createViteBuilder(config, server);
3201
3205
  const pm = await createWxtPackageManager(config.root);
3202
3206
  wxt = {
3203
3207
  config,
@@ -3206,9 +3210,11 @@ async function registerWxt(command, inlineConfig = {}, server) {
3206
3210
  return config.logger;
3207
3211
  },
3208
3212
  async reloadConfig() {
3209
- wxt.config = await resolveConfig(inlineConfig, command, server);
3213
+ wxt.config = await resolveConfig(inlineConfig, command);
3210
3214
  },
3211
- pm
3215
+ pm,
3216
+ builder,
3217
+ server
3212
3218
  };
3213
3219
  wxt.hooks.addHooks(config.hooks);
3214
3220
  await wxt.hooks.callHook("ready", wxt);
@@ -3217,19 +3223,19 @@ async function registerWxt(command, inlineConfig = {}, server) {
3217
3223
  export {
3218
3224
  normalizePath,
3219
3225
  unnormalizePath,
3220
- wxt,
3221
- registerWxt,
3222
- detectDevChanges,
3223
3226
  getEntrypointBundlePath,
3224
3227
  isHtmlEntrypoint,
3225
- findEntrypoints,
3226
- generateTypesDir,
3227
3228
  formatDuration,
3228
3229
  download,
3229
3230
  unimport,
3230
3231
  tsconfigPaths,
3231
3232
  globals,
3232
3233
  webextensionPolyfillMock,
3234
+ wxt,
3235
+ registerWxt,
3236
+ detectDevChanges,
3237
+ findEntrypoints,
3238
+ generateTypesDir,
3233
3239
  getPackageJson,
3234
3240
  resolveConfig,
3235
3241
  kebabCaseAlphanumeric,