wxt 0.17.8 → 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.8";
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
- }
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);
642
+ }
643
+ }
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);
649
+ }
650
+
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 });
1005
668
  }
1006
- ];
669
+ }
670
+ const publicAssets = await copyPublicDirectory();
671
+ return { publicAssets, steps };
1007
672
  }
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)
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;
689
+ }
690
+
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;
697
+ }
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;
703
+ }
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);
739
+ } else {
740
+ unchangedOutput.steps.push(step);
741
+ }
742
+ }
743
+ for (const asset of currentOutput.publicAssets) {
744
+ if (changedSteps.has(asset)) {
745
+ changedOutput.publicAssets.push(asset);
746
+ } else {
747
+ unchangedOutput.publicAssets.push(asset);
748
+ }
749
+ }
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
+ };
769
+ }
770
+ return {
771
+ type: "extension-reload",
772
+ cachedOutput: unchangedOutput,
773
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
774
+ };
775
+ }
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;
797
+ }
798
+
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)
826
+ });
827
+ }
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
+ };
872
+ }
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
+ })
1016
883
  );
1017
- if (matchingAlias) {
1018
- const [alias, replacement] = matchingAlias;
1019
- resolvedAbsolutePath = resolve6(
1020
- config.root,
1021
- src.replace(alias, replacement)
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.`
1022
898
  );
1023
- } else {
1024
- resolvedAbsolutePath = resolve6(dirname3(id), src);
899
+ return false;
1025
900
  }
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
- }
901
+ if (exclude?.length && !include?.length) {
902
+ return !exclude.includes(wxt.config.browser);
1039
903
  }
1040
- });
1041
- }
1042
- function isUrl(str) {
1043
- try {
1044
- new URL(str);
1045
- return true;
1046
- } catch {
1047
- return false;
1048
- }
1049
- }
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
- };
904
+ if (include?.length && !exclude?.length) {
905
+ return include.includes(wxt.config.browser);
1065
906
  }
1066
- };
1067
- }
1068
-
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`;
1081
- }
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));
907
+ if (skippedEntrypointNames.includes(entry.name)) {
908
+ return false;
909
+ }
910
+ return true;
1088
911
  });
912
+ wxt.logger.debug(`${wxt.config.browser} entrypoints:`, targetEntrypoints);
913
+ await wxt.hooks.callHook("entrypoints:resolved", wxt, targetEntrypoints);
914
+ return targetEntrypoints;
1089
915
  }
1090
-
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);
916
+ function preventDuplicateEntrypointNames(files) {
917
+ const namesToPaths = files.reduce(
918
+ (map, { name, inputPath }) => {
919
+ map[name] ??= [];
920
+ map[name].push(inputPath);
921
+ return map;
922
+ },
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
+ });
1099
932
  }
1100
- });
1101
- });
1102
- return withTimeout(isOffline2, 1e3).catch(() => true);
1103
- }
1104
- async function isOnline() {
1105
- const offline = await isOffline();
1106
- return !offline;
1107
- }
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);
1115
- } else {
1116
- config.logger.debug(
1117
- `Failed to download "${url}", falling back to cache...`
1118
- );
1119
- }
1120
- }
1121
- if (!content)
1122
- content = await config.fsCache.get(url) ?? "";
1123
- if (!content)
933
+ return lines;
934
+ },
935
+ []
936
+ );
937
+ if (errorLines.length > 0) {
938
+ const errorContent = errorLines.join("\n");
1124
939
  throw Error(
1125
- `Offline and "${url}" has not been cached. Try again when online.`
940
+ `Multiple entrypoints with the same name detected, only one entrypoint for each name is allowed.
941
+
942
+ ${errorContent}`
1126
943
  );
1127
- return content;
944
+ }
1128
945
  }
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;
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"
1137
961
  },
1138
- async load(id) {
1139
- if (!id.startsWith("\0url:"))
1140
- return;
1141
- const url = id.replace("\0url:", "");
1142
- return await fetchCached(url, config);
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"
1143
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
1144
977
  };
1145
978
  }
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);
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"
1188
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
997
+ };
998
+ }
999
+ async function getUnlistedPageEntrypoint(info) {
1000
+ const options = await getHtmlEntrypointOptions(info, {
1001
+ exclude: "exclude",
1002
+ include: "include"
1003
+ });
1004
+ return {
1005
+ type: "unlisted-page",
1006
+ name: info.name,
1007
+ inputPath: info.inputPath,
1008
+ outputDir: wxt.config.outDir,
1009
+ options,
1010
+ skipped: info.skipped
1011
+ };
1012
+ }
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(...)"?`
1022
+ );
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
1189
1032
  };
1190
1033
  }
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);
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
+ );
1198
1046
  }
1047
+ const { main: _, ...moduleOptions } = defaultExport;
1048
+ options = moduleOptions;
1199
1049
  }
1200
- try {
1201
- await fs6.rmdir(dir);
1202
- } catch {
1050
+ if (wxt.config.manifestVersion !== 3) {
1051
+ delete options.type;
1203
1052
  }
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
1053
  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
- }
1054
+ type: "background",
1055
+ name,
1056
+ inputPath,
1057
+ outputDir: wxt.config.outDir,
1058
+ options: resolvePerBrowserOptions(options, wxt.config.browser),
1059
+ skipped
1238
1060
  };
1239
1061
  }
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}`;
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(...)"?`
1071
+ );
1072
+ }
1247
1073
  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
- }
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
1266
1080
  };
1267
1081
  }
1268
-
1269
- // src/core/builders/vite/plugins/tsconfigPaths.ts
1270
- function tsconfigPaths(config) {
1271
- return {
1272
- name: "wxt:aliases",
1273
- async config() {
1274
- return {
1275
- resolve: {
1276
- alias: config.alias
1277
- }
1278
- };
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"
1092
+ },
1093
+ {
1094
+ defaultTitle: (document) => document.querySelector("title")?.textContent || void 0
1095
+ },
1096
+ {
1097
+ defaultTitle: (content) => content
1279
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
1280
1107
  };
1281
1108
  }
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)`;
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
+ );
1297
1125
  }
1298
1126
  }
1299
- };
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
+ }));
1300
1212
  }
1301
1213
 
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];
1321
- });
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);
1322
1223
  }
1323
- };
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);
1324
1230
  }
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
- )
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;
1337
1247
  });
1248
+ await fs6.writeJson(options.eslintrc.filePath, eslintrc, { spaces: 2 });
1338
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";
1339
1261
 
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);
1348
- }
1349
- return {
1350
- define
1351
- };
1352
- }
1353
- };
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
+ }
1354
1270
  }
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
- };
1377
- }
1378
- };
1271
+ `;
1272
+ await writeFileIfDifferent(
1273
+ filePath,
1274
+ template.replace("{{ union }}", unions || " | never")
1275
+ );
1276
+ return filePath;
1379
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";
1380
1283
 
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
- };
1396
- },
1397
- load(id) {
1398
- if (id === virtualId) {
1399
- return "export default chrome";
1400
- }
1401
- }
1402
- };
1403
- }
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
+ }
1404
1294
 
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
- };
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}
1418
1389
  }
1419
- };
1390
+ },
1391
+ "include": [
1392
+ "${getTsconfigPath(wxt.config.root)}/**/*",
1393
+ "./${getTsconfigPath(mainReference)}"
1394
+ ],
1395
+ "exclude": ["${getTsconfigPath(wxt.config.outBaseDir)}"]
1396
+ }`
1397
+ );
1420
1398
  }
1421
1399
 
1422
- // src/core/builders/vite/plugins/defineImportMeta.ts
1423
- function defineImportMeta() {
1424
- 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
- }
1434
- };
1435
- }
1400
+ // src/core/utils/building/resolve-config.ts
1401
+ import { loadConfig } from "c12";
1402
+ import path5 from "node:path";
1436
1403
 
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()
1475
- );
1476
- if (wxtConfig.analysis.enabled) {
1477
- config.plugins.push(bundleAnalysis(wxtConfig));
1478
- }
1479
- return config;
1480
- };
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));
1488
- }
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;
1526
- };
1527
- const getMultiPageConfig = (entrypoints) => {
1528
- const htmlEntrypoints = new Set(
1529
- entrypoints.filter(isHtmlEntrypoint).map((e) => e.name)
1530
- );
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
- };
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));
1580
1409
  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
- };
1410
+ async set(key, value) {
1411
+ const path8 = getPath(key);
1412
+ await ensureDir2(dirname4(path8));
1413
+ await writeFileIfDifferent(path8, value);
1597
1414
  },
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;
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,14 +1463,14 @@ 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)
1699
1470
  logger.level = LogLevels.debug;
1700
1471
  const browser = mergedConfig.browser ?? "chrome";
1701
1472
  const manifestVersion = mergedConfig.manifestVersion ?? (browser === "firefox" || browser === "safari" ? 2 : 3);
1702
- const mode = mergedConfig.mode ?? (command === "build" ? "production" : "development");
1473
+ const mode = mergedConfig.mode ?? COMMAND_MODES[command];
1703
1474
  const env = { browser, command, manifestVersion, mode };
1704
1475
  const root = path5.resolve(
1705
1476
  inlineConfig.root ?? userConfig.root ?? process.cwd()
@@ -1738,15 +1509,21 @@ async function resolveConfig(inlineConfig, command, server) {
1738
1509
  "~": srcDir,
1739
1510
  "@@": root,
1740
1511
  "~~": root
1741
- }).map(([key, value]) => [key, path5.resolve(root, value)])
1742
- );
1743
- const analysisOutputFile = path5.resolve(
1744
- root,
1745
- mergedConfig.analysis?.outputFile ?? "stats.html"
1512
+ }).map(([key, value]) => [key, path5.resolve(root, value)])
1746
1513
  );
1747
- const analysisOutputDir = path5.dirname(analysisOutputFile);
1748
- const analysisOutputName = path5.parse(analysisOutputFile).name;
1749
- 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 {
1750
1527
  browser,
1751
1528
  command,
1752
1529
  debug,
@@ -1768,109 +1545,47 @@ async function resolveConfig(inlineConfig, command, server) {
1768
1545
  srcDir,
1769
1546
  typesDir,
1770
1547
  wxtDir,
1771
- zip: resolveInternalZipConfig(root, mergedConfig),
1772
- transformManifest(manifest) {
1773
- userConfig.transformManifest?.(manifest);
1774
- inlineConfig.transformManifest?.(manifest);
1775
- },
1776
- analysis: {
1777
- enabled: mergedConfig.analysis?.enabled ?? false,
1778
- open: mergedConfig.analysis?.open ?? false,
1779
- template: mergedConfig.analysis?.template ?? "treemap",
1780
- outputFile: analysisOutputFile,
1781
- outputDir: analysisOutputDir,
1782
- outputName: analysisOutputName,
1783
- keepArtifacts: mergedConfig.analysis?.keepArtifacts ?? false
1784
- },
1548
+ zip: resolveZipConfig(root, mergedConfig),
1549
+ transformManifest: mergedConfig.transformManifest,
1550
+ analysis: resolveAnalysisConfig(root, mergedConfig),
1785
1551
  userConfigMetadata: userConfigMetadata ?? {},
1786
1552
  alias,
1787
- experimental: {
1788
- includeBrowserPolyfill: mergedConfig.experimental?.includeBrowserPolyfill ?? true
1789
- },
1790
- server,
1553
+ experimental: defu(mergedConfig.experimental, {
1554
+ includeBrowserPolyfill: true
1555
+ }),
1791
1556
  dev: {
1557
+ server: devServerConfig,
1792
1558
  reloadCommand
1793
1559
  },
1794
- hooks: mergedConfig.hooks ?? {}
1795
- };
1796
- const builder = await createViteBuilder(
1797
- inlineConfig,
1798
- userConfig,
1799
- finalConfig
1800
- );
1801
- return {
1802
- ...finalConfig,
1803
- builder
1560
+ hooks: mergedConfig.hooks ?? {},
1561
+ vite: mergedConfig.vite ?? (() => ({}))
1804
1562
  };
1805
1563
  }
1806
1564
  async function resolveManifestConfig(env, manifest) {
1807
1565
  return await (typeof manifest === "function" ? manifest(env) : manifest ?? {});
1808
1566
  }
1809
- function mergeInlineConfig(inlineConfig, userConfig) {
1810
- let imports;
1811
- if (inlineConfig.imports === false || userConfig.imports === false) {
1812
- imports = false;
1813
- } else if (userConfig.imports == null && inlineConfig.imports == null) {
1814
- imports = void 0;
1815
- } else {
1816
- imports = defu(inlineConfig.imports ?? {}, userConfig.imports ?? {});
1817
- }
1567
+ async function mergeInlineConfig(inlineConfig, userConfig) {
1568
+ const imports = inlineConfig.imports === false || userConfig.imports === false ? false : userConfig.imports == null && inlineConfig.imports == null ? void 0 : defu(inlineConfig.imports ?? {}, userConfig.imports ?? {});
1818
1569
  const manifest = async (env) => {
1819
1570
  const user = await resolveManifestConfig(env, userConfig.manifest);
1820
1571
  const inline = await resolveManifestConfig(env, inlineConfig.manifest);
1821
1572
  return defu(inline, user);
1822
1573
  };
1823
- const runner = defu(
1824
- inlineConfig.runner ?? {},
1825
- userConfig.runner ?? {}
1826
- );
1827
- const zip = defu(
1828
- inlineConfig.zip ?? {},
1829
- userConfig.zip ?? {}
1830
- );
1831
- const hooks = defu(
1832
- inlineConfig.hooks ?? {},
1833
- userConfig.hooks ?? {}
1834
- );
1574
+ const transformManifest = (manifest2) => {
1575
+ userConfig.transformManifest?.(manifest2);
1576
+ inlineConfig.transformManifest?.(manifest2);
1577
+ };
1578
+ const builderConfig = await mergeBuilderConfig(inlineConfig, userConfig);
1835
1579
  return {
1836
- root: inlineConfig.root ?? userConfig.root,
1837
- browser: inlineConfig.browser ?? userConfig.browser,
1838
- manifestVersion: inlineConfig.manifestVersion ?? userConfig.manifestVersion,
1839
- configFile: inlineConfig.configFile,
1840
- debug: inlineConfig.debug ?? userConfig.debug,
1841
- entrypointsDir: inlineConfig.entrypointsDir ?? userConfig.entrypointsDir,
1842
- filterEntrypoints: inlineConfig.filterEntrypoints ?? userConfig.filterEntrypoints,
1580
+ ...defu(inlineConfig, userConfig),
1581
+ // Custom merge values
1582
+ transformManifest,
1843
1583
  imports,
1844
- logger: inlineConfig.logger ?? userConfig.logger,
1845
1584
  manifest,
1846
- mode: inlineConfig.mode ?? userConfig.mode,
1847
- publicDir: inlineConfig.publicDir ?? userConfig.publicDir,
1848
- runner,
1849
- srcDir: inlineConfig.srcDir ?? userConfig.srcDir,
1850
- outDir: inlineConfig.outDir ?? userConfig.outDir,
1851
- zip,
1852
- analysis: {
1853
- ...userConfig.analysis,
1854
- ...inlineConfig.analysis
1855
- },
1856
- alias: {
1857
- ...userConfig.alias,
1858
- ...inlineConfig.alias
1859
- },
1860
- experimental: {
1861
- ...userConfig.experimental,
1862
- ...inlineConfig.experimental
1863
- },
1864
- vite: void 0,
1865
- transformManifest: void 0,
1866
- dev: {
1867
- ...userConfig.dev,
1868
- ...inlineConfig.dev
1869
- },
1870
- hooks
1585
+ ...builderConfig
1871
1586
  };
1872
1587
  }
1873
- function resolveInternalZipConfig(root, mergedConfig) {
1588
+ function resolveZipConfig(root, mergedConfig) {
1874
1589
  const downloadedPackagesDir = path5.resolve(root, ".wxt/local_modules");
1875
1590
  return {
1876
1591
  name: void 0,
@@ -1895,6 +1610,23 @@ function resolveInternalZipConfig(root, mergedConfig) {
1895
1610
  downloadedPackagesDir
1896
1611
  };
1897
1612
  }
1613
+ function resolveAnalysisConfig(root, mergedConfig) {
1614
+ const analysisOutputFile = path5.resolve(
1615
+ root,
1616
+ mergedConfig.analysis?.outputFile ?? "stats.html"
1617
+ );
1618
+ const analysisOutputDir = path5.dirname(analysisOutputFile);
1619
+ const analysisOutputName = path5.parse(analysisOutputFile).name;
1620
+ return {
1621
+ enabled: mergedConfig.analysis?.enabled ?? false,
1622
+ open: mergedConfig.analysis?.open ?? false,
1623
+ template: mergedConfig.analysis?.template ?? "treemap",
1624
+ outputFile: analysisOutputFile,
1625
+ outputDir: analysisOutputDir,
1626
+ outputName: analysisOutputName,
1627
+ keepArtifacts: mergedConfig.analysis?.keepArtifacts ?? false
1628
+ };
1629
+ }
1898
1630
  async function getUnimportOptions(wxtDir, logger, config) {
1899
1631
  if (config.imports === false)
1900
1632
  return false;
@@ -1947,6 +1679,23 @@ function logMissingDir(logger, name, expected) {
1947
1679
  )}`
1948
1680
  );
1949
1681
  }
1682
+ var COMMAND_MODES = {
1683
+ build: "production",
1684
+ serve: "development"
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
+ }
1950
1699
 
1951
1700
  // src/core/utils/building/group-entrypoints.ts
1952
1701
  function groupEntrypoints(entrypoints) {
@@ -2379,7 +2128,7 @@ async function generateManifest(entrypoints, buildOutput) {
2379
2128
  addDevModeCsp(manifest);
2380
2129
  if (wxt.config.command === "serve")
2381
2130
  addDevModePermissions(manifest);
2382
- wxt.config.transformManifest(manifest);
2131
+ wxt.config.transformManifest?.(manifest);
2383
2132
  await wxt.hooks.callHook("build:manifestGenerated", wxt, manifest);
2384
2133
  if (wxt.config.manifestVersion === 2) {
2385
2134
  convertWebAccessibleResourcesToMv2(manifest);
@@ -2660,8 +2409,8 @@ function discoverIcons(buildOutput) {
2660
2409
  return icons.length > 0 ? Object.fromEntries(icons) : void 0;
2661
2410
  }
2662
2411
  function addDevModeCsp(manifest) {
2663
- const permission = `http://${wxt.config.server?.hostname ?? ""}/*`;
2664
- const allowedCsp = wxt.config.server?.origin ?? "http://localhost:*";
2412
+ const permission = `http://${wxt.server?.hostname ?? ""}/*`;
2413
+ const allowedCsp = wxt.server?.origin ?? "http://localhost:*";
2665
2414
  if (manifest.manifest_version === 3) {
2666
2415
  addHostPermission(manifest, permission);
2667
2416
  } else {
@@ -2674,7 +2423,7 @@ function addDevModeCsp(manifest) {
2674
2423
  ) : manifest.content_security_policy ?? "script-src 'self'; object-src 'self';"
2675
2424
  // default CSP for MV2
2676
2425
  );
2677
- if (wxt.config.server)
2426
+ if (wxt.server)
2678
2427
  csp.add("script-src", allowedCsp);
2679
2428
  if (manifest.manifest_version === 3) {
2680
2429
  manifest.content_security_policy ??= {};
@@ -2934,7 +2683,7 @@ async function internalBuild() {
2934
2683
  const target = `${wxt.config.browser}-mv${wxt.config.manifestVersion}`;
2935
2684
  wxt.logger.info(
2936
2685
  `${verb} ${pc5.cyan(target)} for ${pc5.cyan(wxt.config.mode)} with ${pc5.green(
2937
- `${wxt.config.builder.name} ${wxt.config.builder.version}`
2686
+ `${wxt.builder.name} ${wxt.builder.version}`
2938
2687
  )}`
2939
2688
  );
2940
2689
  const startTime = Date.now();
@@ -3232,11 +2981,227 @@ var packageManagers = {
3232
2981
  yarn
3233
2982
  };
3234
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
+
3235
3198
  // src/core/wxt.ts
3236
3199
  var wxt;
3237
- async function registerWxt(command, inlineConfig = {}, server) {
3238
- const config = await resolveConfig(inlineConfig, command, server);
3200
+ async function registerWxt(command, inlineConfig = {}, getServer) {
3239
3201
  const hooks = createHooks();
3202
+ const config = await resolveConfig(inlineConfig, command);
3203
+ const server = await getServer?.(config);
3204
+ const builder = await createViteBuilder(config, server);
3240
3205
  const pm = await createWxtPackageManager(config.root);
3241
3206
  wxt = {
3242
3207
  config,
@@ -3245,9 +3210,11 @@ async function registerWxt(command, inlineConfig = {}, server) {
3245
3210
  return config.logger;
3246
3211
  },
3247
3212
  async reloadConfig() {
3248
- wxt.config = await resolveConfig(inlineConfig, command, server);
3213
+ wxt.config = await resolveConfig(inlineConfig, command);
3249
3214
  },
3250
- pm
3215
+ pm,
3216
+ builder,
3217
+ server
3251
3218
  };
3252
3219
  wxt.hooks.addHooks(config.hooks);
3253
3220
  await wxt.hooks.callHook("ready", wxt);
@@ -3256,19 +3223,19 @@ async function registerWxt(command, inlineConfig = {}, server) {
3256
3223
  export {
3257
3224
  normalizePath,
3258
3225
  unnormalizePath,
3259
- wxt,
3260
- registerWxt,
3261
- detectDevChanges,
3262
3226
  getEntrypointBundlePath,
3263
3227
  isHtmlEntrypoint,
3264
- findEntrypoints,
3265
- generateTypesDir,
3266
3228
  formatDuration,
3267
3229
  download,
3268
3230
  unimport,
3269
3231
  tsconfigPaths,
3270
3232
  globals,
3271
3233
  webextensionPolyfillMock,
3234
+ wxt,
3235
+ registerWxt,
3236
+ detectDevChanges,
3237
+ findEntrypoints,
3238
+ generateTypesDir,
3272
3239
  getPackageJson,
3273
3240
  resolveConfig,
3274
3241
  kebabCaseAlphanumeric,