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.
package/dist/index.cjs CHANGED
@@ -2428,7 +2428,7 @@ __export(src_exports, {
2428
2428
  module.exports = __toCommonJS(src_exports);
2429
2429
 
2430
2430
  // src/core/utils/fs.ts
2431
- var import_fs_extra2 = __toESM(require("fs-extra"), 1);
2431
+ var import_fs_extra4 = __toESM(require("fs-extra"), 1);
2432
2432
  var import_fast_glob = __toESM(require("fast-glob"), 1);
2433
2433
 
2434
2434
  // src/core/utils/paths.ts
@@ -2644,197 +2644,6 @@ var packageManagers = {
2644
2644
  yarn
2645
2645
  };
2646
2646
 
2647
- // src/core/wxt.ts
2648
- var wxt;
2649
- async function registerWxt(command, inlineConfig = {}, server) {
2650
- const config = await resolveConfig(inlineConfig, command, server);
2651
- const hooks = (0, import_hookable.createHooks)();
2652
- const pm = await createWxtPackageManager(config.root);
2653
- wxt = {
2654
- config,
2655
- hooks,
2656
- get logger() {
2657
- return config.logger;
2658
- },
2659
- async reloadConfig() {
2660
- wxt.config = await resolveConfig(inlineConfig, command, server);
2661
- },
2662
- pm
2663
- };
2664
- wxt.hooks.addHooks(config.hooks);
2665
- await wxt.hooks.callHook("ready", wxt);
2666
- }
2667
-
2668
- // src/core/utils/fs.ts
2669
- async function writeFileIfDifferent(file, newContents) {
2670
- const existingContents = await import_fs_extra2.default.readFile(file, "utf-8").catch(() => void 0);
2671
- if (existingContents !== newContents) {
2672
- await import_fs_extra2.default.writeFile(file, newContents);
2673
- }
2674
- }
2675
- async function getPublicFiles() {
2676
- if (!await import_fs_extra2.default.exists(wxt.config.publicDir))
2677
- return [];
2678
- const files = await (0, import_fast_glob.default)("**/*", { cwd: wxt.config.publicDir });
2679
- return files.map(unnormalizePath);
2680
- }
2681
-
2682
- // src/core/utils/building/build-entrypoints.ts
2683
- var import_fs_extra3 = __toESM(require("fs-extra"), 1);
2684
- var import_path = require("path");
2685
- var import_picocolors = __toESM(require("picocolors"), 1);
2686
- async function buildEntrypoints(groups, spinner) {
2687
- const steps = [];
2688
- for (let i = 0; i < groups.length; i++) {
2689
- const group = groups[i];
2690
- const groupNames = [group].flat().map((e) => e.name);
2691
- const groupNameColored = groupNames.join(import_picocolors.default.dim(", "));
2692
- spinner.text = import_picocolors.default.dim(`[${i + 1}/${groups.length}]`) + ` ${groupNameColored}`;
2693
- try {
2694
- steps.push(await wxt.config.builder.build(group));
2695
- } catch (err) {
2696
- spinner.stop().clear();
2697
- wxt.logger.error(err);
2698
- throw Error(`Failed to build ${groupNames.join(", ")}`, { cause: err });
2699
- }
2700
- }
2701
- const publicAssets = await copyPublicDirectory();
2702
- return { publicAssets, steps };
2703
- }
2704
- async function copyPublicDirectory() {
2705
- const files = await getPublicFiles();
2706
- if (files.length === 0)
2707
- return [];
2708
- const publicAssets = [];
2709
- for (const file of files) {
2710
- const srcPath = (0, import_path.resolve)(wxt.config.publicDir, file);
2711
- const outPath = (0, import_path.resolve)(wxt.config.outDir, file);
2712
- await import_fs_extra3.default.ensureDir((0, import_path.dirname)(outPath));
2713
- await import_fs_extra3.default.copyFile(srcPath, outPath);
2714
- publicAssets.push({
2715
- type: "asset",
2716
- fileName: file
2717
- });
2718
- }
2719
- return publicAssets;
2720
- }
2721
-
2722
- // src/core/utils/arrays.ts
2723
- function every(array, predicate) {
2724
- for (let i = 0; i < array.length; i++)
2725
- if (!predicate(array[i], i))
2726
- return false;
2727
- return true;
2728
- }
2729
- function some(array, predicate) {
2730
- for (let i = 0; i < array.length; i++)
2731
- if (predicate(array[i], i))
2732
- return true;
2733
- return false;
2734
- }
2735
-
2736
- // src/core/utils/building/detect-dev-changes.ts
2737
- function detectDevChanges(changedFiles, currentOutput) {
2738
- const isConfigChange = some(
2739
- changedFiles,
2740
- (file) => file === wxt.config.userConfigMetadata.configFile
2741
- );
2742
- if (isConfigChange)
2743
- return { type: "full-restart" };
2744
- const isRunnerChange = some(
2745
- changedFiles,
2746
- (file) => file === wxt.config.runnerConfig.configFile
2747
- );
2748
- if (isRunnerChange)
2749
- return { type: "browser-restart" };
2750
- const changedSteps = new Set(
2751
- changedFiles.flatMap(
2752
- (changedFile) => findEffectedSteps(changedFile, currentOutput)
2753
- )
2754
- );
2755
- if (changedSteps.size === 0)
2756
- return { type: "no-change" };
2757
- const unchangedOutput = {
2758
- manifest: currentOutput.manifest,
2759
- steps: [],
2760
- publicAssets: []
2761
- };
2762
- const changedOutput = {
2763
- manifest: currentOutput.manifest,
2764
- steps: [],
2765
- publicAssets: []
2766
- };
2767
- for (const step of currentOutput.steps) {
2768
- if (changedSteps.has(step)) {
2769
- changedOutput.steps.push(step);
2770
- } else {
2771
- unchangedOutput.steps.push(step);
2772
- }
2773
- }
2774
- for (const asset of currentOutput.publicAssets) {
2775
- if (changedSteps.has(asset)) {
2776
- changedOutput.publicAssets.push(asset);
2777
- } else {
2778
- unchangedOutput.publicAssets.push(asset);
2779
- }
2780
- }
2781
- const isOnlyHtmlChanges = changedFiles.length > 0 && every(changedFiles, (file) => file.endsWith(".html"));
2782
- if (isOnlyHtmlChanges) {
2783
- return {
2784
- type: "html-reload",
2785
- cachedOutput: unchangedOutput,
2786
- rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
2787
- };
2788
- }
2789
- const isOnlyContentScripts = changedOutput.steps.length > 0 && every(
2790
- changedOutput.steps.flatMap((step) => step.entrypoints),
2791
- (entry) => entry.type === "content-script"
2792
- );
2793
- if (isOnlyContentScripts) {
2794
- return {
2795
- type: "content-script-reload",
2796
- cachedOutput: unchangedOutput,
2797
- changedSteps: changedOutput.steps,
2798
- rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
2799
- };
2800
- }
2801
- return {
2802
- type: "extension-reload",
2803
- cachedOutput: unchangedOutput,
2804
- rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
2805
- };
2806
- }
2807
- function findEffectedSteps(changedFile, currentOutput) {
2808
- const changes = [];
2809
- const changedPath = normalizePath(changedFile);
2810
- const isChunkEffected = (chunk) => (
2811
- // If it's an HTML file with the same path, is is effected because HTML files need to be re-rendered
2812
- // - fileName is normalized, relative bundle path, "<entrypoint-name>.html"
2813
- 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
2814
- // - moduleIds are absolute, normalized paths
2815
- chunk.type === "chunk" && chunk.moduleIds.includes(changedPath)
2816
- );
2817
- for (const step of currentOutput.steps) {
2818
- const effectedChunk = step.chunks.find((chunk) => isChunkEffected(chunk));
2819
- if (effectedChunk)
2820
- changes.push(step);
2821
- }
2822
- const effectedAsset = currentOutput.publicAssets.find(
2823
- (chunk) => isChunkEffected(chunk)
2824
- );
2825
- if (effectedAsset)
2826
- changes.push(effectedAsset);
2827
- return changes;
2828
- }
2829
-
2830
- // src/core/utils/building/find-entrypoints.ts
2831
- var import_path2 = require("path");
2832
- var import_fs_extra4 = __toESM(require("fs-extra"), 1);
2833
- var import_minimatch = require("minimatch");
2834
- var import_linkedom = require("linkedom");
2835
- var import_json5 = __toESM(require("json5"), 1);
2836
- var import_fast_glob2 = __toESM(require("fast-glob"), 1);
2837
-
2838
2647
  // src/core/utils/entrypoints.ts
2839
2648
  var import_node_path5 = __toESM(require("path"), 1);
2840
2649
  function getEntrypointName(entrypointsDir, inputPath) {
@@ -2867,384 +2676,443 @@ function isHtmlEntrypoint(entrypoint) {
2867
2676
  return entrypoint.inputPath.endsWith(".html");
2868
2677
  }
2869
2678
 
2870
- // src/core/utils/constants.ts
2871
- var VIRTUAL_NOOP_BACKGROUND_MODULE_ID = "virtual:user-background";
2872
-
2873
- // src/core/utils/building/find-entrypoints.ts
2874
- var import_picocolors2 = __toESM(require("picocolors"), 1);
2875
- async function findEntrypoints() {
2876
- const relativePaths = await (0, import_fast_glob2.default)(Object.keys(PATH_GLOB_TO_TYPE_MAP), {
2877
- cwd: wxt.config.entrypointsDir
2878
- });
2879
- relativePaths.sort();
2880
- const pathGlobs = Object.keys(PATH_GLOB_TO_TYPE_MAP);
2881
- const entrypointInfos = relativePaths.reduce((results, relativePath) => {
2882
- const inputPath = (0, import_path2.resolve)(wxt.config.entrypointsDir, relativePath);
2883
- const name = getEntrypointName(wxt.config.entrypointsDir, inputPath);
2884
- const matchingGlob = pathGlobs.find(
2885
- (glob6) => (0, import_minimatch.minimatch)(relativePath, glob6)
2886
- );
2887
- if (matchingGlob) {
2888
- const type = PATH_GLOB_TO_TYPE_MAP[matchingGlob];
2889
- results.push({
2890
- name,
2891
- inputPath,
2892
- type,
2893
- skipped: wxt.config.filterEntrypoints != null && !wxt.config.filterEntrypoints.has(name)
2894
- });
2895
- }
2896
- return results;
2897
- }, []);
2898
- preventNoEntrypoints(entrypointInfos);
2899
- preventDuplicateEntrypointNames(entrypointInfos);
2900
- let hasBackground = false;
2901
- const entrypoints = await Promise.all(
2902
- entrypointInfos.map(async (info) => {
2903
- const { type } = info;
2904
- switch (type) {
2905
- case "popup":
2906
- return await getPopupEntrypoint(info);
2907
- case "sidepanel":
2908
- return await getSidepanelEntrypoint(info);
2909
- case "options":
2910
- return await getOptionsEntrypoint(info);
2911
- case "background":
2912
- hasBackground = true;
2913
- return await getBackgroundEntrypoint(info);
2914
- case "content-script":
2915
- return await getContentScriptEntrypoint(info);
2916
- case "unlisted-page":
2917
- return await getUnlistedPageEntrypoint(info);
2918
- case "unlisted-script":
2919
- return await getUnlistedScriptEntrypoint(info);
2920
- case "content-script-style":
2921
- return {
2922
- ...info,
2923
- type,
2924
- outputDir: (0, import_path2.resolve)(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
2925
- options: {
2926
- include: void 0,
2927
- exclude: void 0
2679
+ // src/core/builders/vite/plugins/devHtmlPrerender.ts
2680
+ var import_linkedom = require("linkedom");
2681
+ var import_node_path6 = require("path");
2682
+ var reactRefreshPreamble = "";
2683
+ function devHtmlPrerender(config, server) {
2684
+ const htmlReloadId = "@wxt/reload-html";
2685
+ const resolvedHtmlReloadId = (0, import_node_path6.resolve)(
2686
+ config.wxtModuleDir,
2687
+ "dist/virtual/reload-html.js"
2688
+ );
2689
+ const virtualReactRefreshId = "@wxt/virtual-react-refresh";
2690
+ const resolvedVirtualReactRefreshId = "\0" + virtualReactRefreshId;
2691
+ return [
2692
+ {
2693
+ apply: "build",
2694
+ name: "wxt:dev-html-prerender",
2695
+ config() {
2696
+ return {
2697
+ resolve: {
2698
+ alias: {
2699
+ [htmlReloadId]: resolvedHtmlReloadId
2928
2700
  }
2929
- };
2930
- default:
2931
- return {
2932
- ...info,
2933
- type,
2934
- outputDir: wxt.config.outDir,
2935
- options: {
2936
- include: void 0,
2937
- exclude: void 0
2938
- }
2939
- };
2701
+ }
2702
+ };
2703
+ },
2704
+ // Convert scripts like src="./main.tsx" -> src="http://localhost:3000/entrypoints/popup/main.tsx"
2705
+ // before the paths are replaced with their bundled path
2706
+ transform(code, id) {
2707
+ if (config.command !== "serve" || server == null || !id.endsWith(".html"))
2708
+ return;
2709
+ const { document } = (0, import_linkedom.parseHTML)(code);
2710
+ const _pointToDevServer = (querySelector, attr) => pointToDevServer(config, server, id, document, querySelector, attr);
2711
+ _pointToDevServer("script[type=module]", "src");
2712
+ _pointToDevServer("link[rel=stylesheet]", "href");
2713
+ const reloader = document.createElement("script");
2714
+ reloader.src = htmlReloadId;
2715
+ reloader.type = "module";
2716
+ document.head.appendChild(reloader);
2717
+ const newHtml = document.toString();
2718
+ config.logger.debug("transform " + id);
2719
+ config.logger.debug("Old HTML:\n" + code);
2720
+ config.logger.debug("New HTML:\n" + newHtml);
2721
+ return newHtml;
2722
+ },
2723
+ // Pass the HTML through the dev server to add dev-mode specific code
2724
+ async transformIndexHtml(html, ctx) {
2725
+ if (config.command !== "serve" || server == null)
2726
+ return;
2727
+ const originalUrl = `${server.origin}${ctx.path}`;
2728
+ const name = getEntrypointName(config.entrypointsDir, ctx.filename);
2729
+ const url2 = `${server.origin}/${name}.html`;
2730
+ const serverHtml = await server.transformHtml(url2, html, originalUrl);
2731
+ const { document } = (0, import_linkedom.parseHTML)(serverHtml);
2732
+ const reactRefreshScript = Array.from(
2733
+ document.querySelectorAll("script[type=module]")
2734
+ ).find((script) => script.innerHTML.includes("@react-refresh"));
2735
+ if (reactRefreshScript) {
2736
+ reactRefreshPreamble = reactRefreshScript.innerHTML;
2737
+ const virtualScript = document.createElement("script");
2738
+ virtualScript.type = "module";
2739
+ virtualScript.src = `${server.origin}/${virtualReactRefreshId}`;
2740
+ reactRefreshScript.replaceWith(virtualScript);
2741
+ }
2742
+ const viteClientScript = document.querySelector(
2743
+ "script[src='/@vite/client']"
2744
+ );
2745
+ if (viteClientScript) {
2746
+ viteClientScript.src = `${server.origin}${viteClientScript.src}`;
2747
+ }
2748
+ const newHtml = document.toString();
2749
+ config.logger.debug("transformIndexHtml " + ctx.filename);
2750
+ config.logger.debug("Old HTML:\n" + html);
2751
+ config.logger.debug("New HTML:\n" + newHtml);
2752
+ return newHtml;
2940
2753
  }
2941
- })
2942
- );
2943
- if (wxt.config.command === "serve" && !hasBackground) {
2944
- entrypoints.push(
2945
- await getBackgroundEntrypoint({
2946
- inputPath: VIRTUAL_NOOP_BACKGROUND_MODULE_ID,
2947
- name: "background",
2948
- type: "background",
2949
- skipped: false
2950
- })
2951
- );
2952
- }
2953
- wxt.logger.debug("All entrypoints:", entrypoints);
2954
- const skippedEntrypointNames = entrypointInfos.filter((item) => item.skipped).map((item) => item.name);
2955
- if (skippedEntrypointNames.length) {
2956
- wxt.logger.warn(
2957
- `Filter excluded the following entrypoints:
2958
- ${skippedEntrypointNames.map((item) => `${import_picocolors2.default.dim("-")} ${import_picocolors2.default.cyan(item)}`).join("\n")}`
2754
+ },
2755
+ {
2756
+ name: "wxt:virtualize-react-refresh",
2757
+ apply: "serve",
2758
+ resolveId(id) {
2759
+ if (id === `/${virtualReactRefreshId}`) {
2760
+ return resolvedVirtualReactRefreshId;
2761
+ }
2762
+ if (id.startsWith("/chunks/")) {
2763
+ return "\0noop";
2764
+ }
2765
+ },
2766
+ load(id) {
2767
+ if (id === resolvedVirtualReactRefreshId) {
2768
+ return reactRefreshPreamble;
2769
+ }
2770
+ if (id === "\0noop") {
2771
+ return "";
2772
+ }
2773
+ }
2774
+ }
2775
+ ];
2776
+ }
2777
+ function pointToDevServer(config, server, id, document, querySelector, attr) {
2778
+ document.querySelectorAll(querySelector).forEach((element) => {
2779
+ const src = element.getAttribute(attr);
2780
+ if (!src || isUrl(src))
2781
+ return;
2782
+ let resolvedAbsolutePath;
2783
+ const matchingAlias = Object.entries(config.alias).find(
2784
+ ([key]) => src.startsWith(key)
2959
2785
  );
2960
- }
2961
- const targetEntrypoints = entrypoints.filter((entry) => {
2962
- const { include, exclude } = entry.options;
2963
- if (include?.length && exclude?.length) {
2964
- wxt.logger.warn(
2965
- `The ${entry.name} entrypoint lists both include and exclude, but only one can be used per entrypoint. Entrypoint ignored.`
2786
+ if (matchingAlias) {
2787
+ const [alias, replacement] = matchingAlias;
2788
+ resolvedAbsolutePath = (0, import_node_path6.resolve)(
2789
+ config.root,
2790
+ src.replace(alias, replacement)
2966
2791
  );
2967
- return false;
2968
- }
2969
- if (exclude?.length && !include?.length) {
2970
- return !exclude.includes(wxt.config.browser);
2971
- }
2972
- if (include?.length && !exclude?.length) {
2973
- return include.includes(wxt.config.browser);
2792
+ } else {
2793
+ resolvedAbsolutePath = (0, import_node_path6.resolve)((0, import_node_path6.dirname)(id), src);
2974
2794
  }
2975
- if (skippedEntrypointNames.includes(entry.name)) {
2976
- return false;
2795
+ if (resolvedAbsolutePath) {
2796
+ const relativePath = normalizePath(
2797
+ (0, import_node_path6.relative)(config.root, resolvedAbsolutePath)
2798
+ );
2799
+ if (relativePath.startsWith(".")) {
2800
+ let path13 = normalizePath(resolvedAbsolutePath);
2801
+ if (!path13.startsWith("/"))
2802
+ path13 = "/" + path13;
2803
+ element.setAttribute(attr, `${server.origin}/@fs${path13}`);
2804
+ } else {
2805
+ const url2 = new URL(relativePath, server.origin);
2806
+ element.setAttribute(attr, url2.href);
2807
+ }
2977
2808
  }
2978
- return true;
2979
2809
  });
2980
- wxt.logger.debug(`${wxt.config.browser} entrypoints:`, targetEntrypoints);
2981
- await wxt.hooks.callHook("entrypoints:resolved", wxt, targetEntrypoints);
2982
- return targetEntrypoints;
2983
- }
2984
- function preventDuplicateEntrypointNames(files) {
2985
- const namesToPaths = files.reduce(
2986
- (map, { name, inputPath }) => {
2987
- map[name] ??= [];
2988
- map[name].push(inputPath);
2989
- return map;
2990
- },
2991
- {}
2992
- );
2993
- const errorLines = Object.entries(namesToPaths).reduce(
2994
- (lines, [name, absolutePaths]) => {
2995
- if (absolutePaths.length > 1) {
2996
- lines.push(`- ${name}`);
2997
- absolutePaths.forEach((absolutePath) => {
2998
- lines.push(` - ${(0, import_path2.relative)(wxt.config.root, absolutePath)}`);
2999
- });
3000
- }
3001
- return lines;
3002
- },
3003
- []
3004
- );
3005
- if (errorLines.length > 0) {
3006
- const errorContent = errorLines.join("\n");
3007
- throw Error(
3008
- `Multiple entrypoints with the same name detected, only one entrypoint for each name is allowed.
3009
-
3010
- ${errorContent}`
3011
- );
3012
- }
3013
2810
  }
3014
- function preventNoEntrypoints(files) {
3015
- if (files.length === 0) {
3016
- throw Error(`No entrypoints found in ${wxt.config.entrypointsDir}`);
2811
+ function isUrl(str) {
2812
+ try {
2813
+ new URL(str);
2814
+ return true;
2815
+ } catch {
2816
+ return false;
3017
2817
  }
3018
2818
  }
3019
- async function getPopupEntrypoint(info) {
3020
- const options = await getHtmlEntrypointOptions(
3021
- info,
3022
- {
3023
- browserStyle: "browse_style",
3024
- exclude: "exclude",
3025
- include: "include",
3026
- defaultIcon: "default_icon",
3027
- defaultTitle: "default_title",
3028
- mv2Key: "type"
3029
- },
3030
- {
3031
- defaultTitle: (document) => document.querySelector("title")?.textContent || void 0
3032
- },
3033
- {
3034
- defaultTitle: (content) => content,
3035
- mv2Key: (content) => content === "page_action" ? "page_action" : "browser_action"
3036
- }
3037
- );
2819
+
2820
+ // src/core/builders/vite/plugins/devServerGlobals.ts
2821
+ function devServerGlobals(config, server) {
3038
2822
  return {
3039
- type: "popup",
3040
- name: "popup",
3041
- options: resolvePerBrowserOptions(options, wxt.config.browser),
3042
- inputPath: info.inputPath,
3043
- outputDir: wxt.config.outDir,
3044
- skipped: info.skipped
3045
- };
3046
- }
3047
- async function getOptionsEntrypoint(info) {
3048
- const options = await getHtmlEntrypointOptions(
3049
- info,
3050
- {
3051
- browserStyle: "browse_style",
3052
- chromeStyle: "chrome_style",
3053
- exclude: "exclude",
3054
- include: "include",
3055
- openInTab: "open_in_tab"
2823
+ name: "wxt:dev-server-globals",
2824
+ config() {
2825
+ if (server == null || config.command == "build")
2826
+ return;
2827
+ return {
2828
+ define: {
2829
+ __DEV_SERVER_PROTOCOL__: JSON.stringify("ws:"),
2830
+ __DEV_SERVER_HOSTNAME__: JSON.stringify(server.hostname),
2831
+ __DEV_SERVER_PORT__: JSON.stringify(server.port)
2832
+ }
2833
+ };
3056
2834
  }
3057
- );
3058
- return {
3059
- type: "options",
3060
- name: "options",
3061
- options: resolvePerBrowserOptions(options, wxt.config.browser),
3062
- inputPath: info.inputPath,
3063
- outputDir: wxt.config.outDir,
3064
- skipped: info.skipped
3065
- };
3066
- }
3067
- async function getUnlistedPageEntrypoint(info) {
3068
- const options = await getHtmlEntrypointOptions(info, {
3069
- exclude: "exclude",
3070
- include: "include"
3071
- });
3072
- return {
3073
- type: "unlisted-page",
3074
- name: info.name,
3075
- inputPath: info.inputPath,
3076
- outputDir: wxt.config.outDir,
3077
- options,
3078
- skipped: info.skipped
3079
2835
  };
3080
2836
  }
3081
- async function getUnlistedScriptEntrypoint({
3082
- inputPath,
3083
- name,
3084
- skipped
3085
- }) {
3086
- const defaultExport = await importEntrypointFile(inputPath);
3087
- if (defaultExport == null) {
3088
- throw Error(
3089
- `${name}: Default export not found, did you forget to call "export default defineUnlistedScript(...)"?`
3090
- );
3091
- }
3092
- const { main: _, ...options } = defaultExport;
3093
- return {
3094
- type: "unlisted-script",
3095
- name,
3096
- inputPath,
3097
- outputDir: wxt.config.outDir,
3098
- options: resolvePerBrowserOptions(options, wxt.config.browser),
3099
- skipped
3100
- };
2837
+
2838
+ // src/core/utils/network.ts
2839
+ var import_node_dns = __toESM(require("dns"), 1);
2840
+
2841
+ // src/core/utils/time.ts
2842
+ function formatDuration(duration) {
2843
+ if (duration < 1e3)
2844
+ return `${duration} ms`;
2845
+ if (duration < 1e4)
2846
+ return `${(duration / 1e3).toFixed(3)} s`;
2847
+ if (duration < 6e4)
2848
+ return `${(duration / 1e3).toFixed(1)} s`;
2849
+ return `${(duration / 1e3).toFixed(0)} s`;
3101
2850
  }
3102
- async function getBackgroundEntrypoint({
3103
- inputPath,
3104
- name,
3105
- skipped
3106
- }) {
3107
- let options = {};
3108
- if (inputPath !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) {
3109
- const defaultExport = await importEntrypointFile(inputPath);
3110
- if (defaultExport == null) {
3111
- throw Error(
3112
- `${name}: Default export not found, did you forget to call "export default defineBackground(...)"?`
2851
+ function withTimeout(promise, duration) {
2852
+ return new Promise((res, rej) => {
2853
+ const timeout = setTimeout(() => {
2854
+ rej(`Promise timed out after ${duration}ms`);
2855
+ }, duration);
2856
+ promise.then(res).catch(rej).finally(() => clearTimeout(timeout));
2857
+ });
2858
+ }
2859
+
2860
+ // src/core/utils/network.ts
2861
+ function isOffline() {
2862
+ const isOffline2 = new Promise((res) => {
2863
+ import_node_dns.default.resolve("google.com", (err) => {
2864
+ if (err == null) {
2865
+ res(false);
2866
+ } else {
2867
+ res(true);
2868
+ }
2869
+ });
2870
+ });
2871
+ return withTimeout(isOffline2, 1e3).catch(() => true);
2872
+ }
2873
+ async function isOnline() {
2874
+ const offline = await isOffline();
2875
+ return !offline;
2876
+ }
2877
+ async function fetchCached(url2, config) {
2878
+ let content = "";
2879
+ if (await isOnline()) {
2880
+ const res = await fetch(url2);
2881
+ if (res.status < 300) {
2882
+ content = await res.text();
2883
+ await config.fsCache.set(url2, content);
2884
+ } else {
2885
+ config.logger.debug(
2886
+ `Failed to download "${url2}", falling back to cache...`
3113
2887
  );
3114
2888
  }
3115
- const { main: _, ...moduleOptions } = defaultExport;
3116
- options = moduleOptions;
3117
- }
3118
- if (wxt.config.manifestVersion !== 3) {
3119
- delete options.type;
3120
2889
  }
3121
- return {
3122
- type: "background",
3123
- name,
3124
- inputPath,
3125
- outputDir: wxt.config.outDir,
3126
- options: resolvePerBrowserOptions(options, wxt.config.browser),
3127
- skipped
3128
- };
3129
- }
3130
- async function getContentScriptEntrypoint({
3131
- inputPath,
3132
- name,
3133
- skipped
3134
- }) {
3135
- const { main: _, ...options } = await importEntrypointFile(inputPath);
3136
- if (options == null) {
2890
+ if (!content)
2891
+ content = await config.fsCache.get(url2) ?? "";
2892
+ if (!content)
3137
2893
  throw Error(
3138
- `${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`
2894
+ `Offline and "${url2}" has not been cached. Try again when online.`
3139
2895
  );
3140
- }
3141
- return {
3142
- type: "content-script",
3143
- name,
3144
- inputPath,
3145
- outputDir: (0, import_path2.resolve)(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
3146
- options: resolvePerBrowserOptions(options, wxt.config.browser),
3147
- skipped
3148
- };
2896
+ return content;
3149
2897
  }
3150
- async function getSidepanelEntrypoint(info) {
3151
- const options = await getHtmlEntrypointOptions(
3152
- info,
3153
- {
3154
- browserStyle: "browse_style",
3155
- exclude: "exclude",
3156
- include: "include",
3157
- defaultIcon: "default_icon",
3158
- defaultTitle: "default_title",
3159
- openAtInstall: "open_at_install"
3160
- },
3161
- {
3162
- defaultTitle: (document) => document.querySelector("title")?.textContent || void 0
2898
+
2899
+ // src/core/builders/vite/plugins/download.ts
2900
+ function download(config) {
2901
+ return {
2902
+ name: "wxt:download",
2903
+ resolveId(id) {
2904
+ if (id.startsWith("url:"))
2905
+ return "\0" + id;
3163
2906
  },
3164
- {
3165
- defaultTitle: (content) => content
2907
+ async load(id) {
2908
+ if (!id.startsWith("\0url:"))
2909
+ return;
2910
+ const url2 = id.replace("\0url:", "");
2911
+ return await fetchCached(url2, config);
3166
2912
  }
3167
- );
3168
- return {
3169
- type: "sidepanel",
3170
- name: info.name,
3171
- options: resolvePerBrowserOptions(options, wxt.config.browser),
3172
- inputPath: info.inputPath,
3173
- outputDir: wxt.config.outDir,
3174
- skipped: info.skipped
3175
2913
  };
3176
2914
  }
3177
- async function getHtmlEntrypointOptions(info, keyMap, queries, parsers) {
3178
- const content = await import_fs_extra4.default.readFile(info.inputPath, "utf-8");
3179
- const { document } = (0, import_linkedom.parseHTML)(content);
3180
- const options = {};
3181
- const defaultQuery = (manifestKey) => document.querySelector(`meta[name='manifest.${manifestKey}']`)?.getAttribute("content");
3182
- Object.entries(keyMap).forEach(([_key, manifestKey]) => {
3183
- const key = _key;
3184
- const content2 = queries?.[key] ? queries[key](document, manifestKey) : defaultQuery(manifestKey);
3185
- if (content2) {
3186
- try {
3187
- options[key] = (parsers?.[key] ?? import_json5.default.parse)(content2);
3188
- } catch (err) {
3189
- wxt.logger.fatal(
3190
- `Failed to parse meta tag content. Usually this means you have invalid JSON5 content (content=${content2})`,
3191
- err
2915
+
2916
+ // src/core/builders/vite/plugins/multipageMove.ts
2917
+ var import_node_path7 = require("path");
2918
+ var import_fs_extra2 = __toESM(require("fs-extra"), 1);
2919
+ function multipageMove(entrypoints, config) {
2920
+ return {
2921
+ name: "wxt:multipage-move",
2922
+ async writeBundle(_, bundle) {
2923
+ for (const oldBundlePath in bundle) {
2924
+ const entrypoint = entrypoints.find(
2925
+ (entry) => !!normalizePath(entry.inputPath).endsWith(oldBundlePath)
2926
+ );
2927
+ if (entrypoint == null) {
2928
+ config.logger.debug(
2929
+ `No entrypoint found for ${oldBundlePath}, leaving in chunks directory`
2930
+ );
2931
+ continue;
2932
+ }
2933
+ const newBundlePath = getEntrypointBundlePath(
2934
+ entrypoint,
2935
+ config.outDir,
2936
+ (0, import_node_path7.extname)(oldBundlePath)
3192
2937
  );
2938
+ if (newBundlePath === oldBundlePath) {
2939
+ config.logger.debug(
2940
+ "HTML file is already in the correct location",
2941
+ oldBundlePath
2942
+ );
2943
+ continue;
2944
+ }
2945
+ const oldAbsPath = (0, import_node_path7.resolve)(config.outDir, oldBundlePath);
2946
+ const newAbsPath = (0, import_node_path7.resolve)(config.outDir, newBundlePath);
2947
+ await (0, import_fs_extra2.ensureDir)((0, import_node_path7.dirname)(newAbsPath));
2948
+ await import_fs_extra2.default.move(oldAbsPath, newAbsPath, { overwrite: true });
2949
+ const renamedChunk = {
2950
+ ...bundle[oldBundlePath],
2951
+ fileName: newBundlePath
2952
+ };
2953
+ delete bundle[oldBundlePath];
2954
+ bundle[newBundlePath] = renamedChunk;
3193
2955
  }
2956
+ removeEmptyDirs(config.outDir);
3194
2957
  }
3195
- });
3196
- return options;
2958
+ };
2959
+ }
2960
+ async function removeEmptyDirs(dir) {
2961
+ const files = await import_fs_extra2.default.readdir(dir);
2962
+ for (const file of files) {
2963
+ const filePath = (0, import_node_path7.join)(dir, file);
2964
+ const stats = await import_fs_extra2.default.stat(filePath);
2965
+ if (stats.isDirectory()) {
2966
+ await removeEmptyDirs(filePath);
2967
+ }
2968
+ }
2969
+ try {
2970
+ await import_fs_extra2.default.rmdir(dir);
2971
+ } catch {
2972
+ }
3197
2973
  }
3198
- var PATH_GLOB_TO_TYPE_MAP = {
3199
- "sandbox.html": "sandbox",
3200
- "sandbox/index.html": "sandbox",
3201
- "*.sandbox.html": "sandbox",
3202
- "*.sandbox/index.html": "sandbox",
3203
- "bookmarks.html": "bookmarks",
3204
- "bookmarks/index.html": "bookmarks",
3205
- "history.html": "history",
3206
- "history/index.html": "history",
3207
- "newtab.html": "newtab",
3208
- "newtab/index.html": "newtab",
3209
- "sidepanel.html": "sidepanel",
3210
- "sidepanel/index.html": "sidepanel",
3211
- "*.sidepanel.html": "sidepanel",
3212
- "*.sidepanel/index.html": "sidepanel",
3213
- "devtools.html": "devtools",
3214
- "devtools/index.html": "devtools",
3215
- "background.[jt]s": "background",
3216
- "background/index.[jt]s": "background",
3217
- [VIRTUAL_NOOP_BACKGROUND_MODULE_ID]: "background",
3218
- "content.[jt]s?(x)": "content-script",
3219
- "content/index.[jt]s?(x)": "content-script",
3220
- "*.content.[jt]s?(x)": "content-script",
3221
- "*.content/index.[jt]s?(x)": "content-script",
3222
- [`content.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
3223
- [`*.content.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
3224
- [`content/index.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
3225
- [`*.content/index.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
3226
- "popup.html": "popup",
3227
- "popup/index.html": "popup",
3228
- "options.html": "options",
3229
- "options/index.html": "options",
3230
- "*.html": "unlisted-page",
3231
- "*/index.html": "unlisted-page",
3232
- "*.[jt]s?(x)": "unlisted-script",
3233
- "*/index.[jt]s?(x)": "unlisted-script",
3234
- [`*.${CSS_EXTENSIONS_PATTERN}`]: "unlisted-style",
3235
- [`*/index.${CSS_EXTENSIONS_PATTERN}`]: "unlisted-style"
3236
- };
3237
- var CONTENT_SCRIPT_OUT_DIR = "content-scripts";
3238
2974
 
3239
- // src/core/utils/building/generate-wxt-dir.ts
2975
+ // src/core/builders/vite/plugins/unimport.ts
3240
2976
  var import_unimport = require("unimport");
3241
- var import_fs_extra5 = __toESM(require("fs-extra"), 1);
3242
- var import_path3 = require("path");
3243
-
3244
- // src/core/utils/globals.ts
3245
- function getGlobals(config) {
3246
- return [
3247
- {
2977
+ var import_path = require("path");
2978
+ var ENABLED_EXTENSIONS = /* @__PURE__ */ new Set([
2979
+ ".js",
2980
+ ".jsx",
2981
+ ".ts",
2982
+ ".tsx",
2983
+ ".vue",
2984
+ ".svelte"
2985
+ ]);
2986
+ function unimport(config) {
2987
+ const options = config.imports;
2988
+ if (options === false)
2989
+ return [];
2990
+ const unimport2 = (0, import_unimport.createUnimport)(options);
2991
+ return {
2992
+ name: "wxt:unimport",
2993
+ async config() {
2994
+ await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
2995
+ },
2996
+ async transform(code, id) {
2997
+ if (id.includes("node_modules"))
2998
+ return;
2999
+ if (!ENABLED_EXTENSIONS.has((0, import_path.extname)(id)))
3000
+ return;
3001
+ const injected = await unimport2.injectImports(code, id);
3002
+ return {
3003
+ code: injected.code,
3004
+ map: injected.s.generateMap({ hires: "boundary", source: id })
3005
+ };
3006
+ }
3007
+ };
3008
+ }
3009
+
3010
+ // src/core/builders/vite/plugins/virtualEntrypoint.ts
3011
+ var import_fs_extra3 = __toESM(require("fs-extra"), 1);
3012
+ var import_path2 = require("path");
3013
+ function virtualEntrypoint(type, config) {
3014
+ const virtualId = `virtual:wxt-${type}?`;
3015
+ const resolvedVirtualId = `\0${virtualId}`;
3016
+ return {
3017
+ name: `wxt:virtual-entrypoint`,
3018
+ resolveId(id) {
3019
+ const index = id.indexOf(virtualId);
3020
+ if (index === -1)
3021
+ return;
3022
+ const inputPath = normalizePath(id.substring(index + virtualId.length));
3023
+ return resolvedVirtualId + inputPath;
3024
+ },
3025
+ async load(id) {
3026
+ if (!id.startsWith(resolvedVirtualId))
3027
+ return;
3028
+ const inputPath = id.replace(resolvedVirtualId, "");
3029
+ const template = await import_fs_extra3.default.readFile(
3030
+ (0, import_path2.resolve)(config.wxtModuleDir, `dist/virtual/${type}-entrypoint.js`),
3031
+ "utf-8"
3032
+ );
3033
+ return template.replace(`virtual:user-${type}`, inputPath);
3034
+ }
3035
+ };
3036
+ }
3037
+
3038
+ // src/core/builders/vite/plugins/tsconfigPaths.ts
3039
+ function tsconfigPaths(config) {
3040
+ return {
3041
+ name: "wxt:aliases",
3042
+ async config() {
3043
+ return {
3044
+ resolve: {
3045
+ alias: config.alias
3046
+ }
3047
+ };
3048
+ }
3049
+ };
3050
+ }
3051
+
3052
+ // src/core/utils/constants.ts
3053
+ var VIRTUAL_NOOP_BACKGROUND_MODULE_ID = "virtual:user-background";
3054
+
3055
+ // src/core/builders/vite/plugins/noopBackground.ts
3056
+ function noopBackground() {
3057
+ const virtualModuleId = VIRTUAL_NOOP_BACKGROUND_MODULE_ID;
3058
+ const resolvedVirtualModuleId = "\0" + virtualModuleId;
3059
+ return {
3060
+ name: "wxt:noop-background",
3061
+ resolveId(id) {
3062
+ if (id === virtualModuleId)
3063
+ return resolvedVirtualModuleId;
3064
+ },
3065
+ load(id) {
3066
+ if (id === resolvedVirtualModuleId) {
3067
+ return `import { defineBackground } from 'wxt/sandbox';
3068
+ export default defineBackground(() => void 0)`;
3069
+ }
3070
+ }
3071
+ };
3072
+ }
3073
+
3074
+ // src/core/builders/vite/plugins/cssEntrypoints.ts
3075
+ function cssEntrypoints(entrypoint, config) {
3076
+ return {
3077
+ name: "wxt:css-entrypoint",
3078
+ config() {
3079
+ return {
3080
+ build: {
3081
+ rollupOptions: {
3082
+ output: {
3083
+ assetFileNames: () => getEntrypointBundlePath(entrypoint, config.outDir, ".css")
3084
+ }
3085
+ }
3086
+ }
3087
+ };
3088
+ },
3089
+ generateBundle(_, bundle) {
3090
+ Object.keys(bundle).forEach((file) => {
3091
+ if (file.endsWith(".js"))
3092
+ delete bundle[file];
3093
+ });
3094
+ }
3095
+ };
3096
+ }
3097
+
3098
+ // src/core/builders/vite/plugins/bundleAnalysis.ts
3099
+ var import_rollup_plugin_visualizer = require("@aklinker1/rollup-plugin-visualizer");
3100
+ var import_node_path8 = __toESM(require("path"), 1);
3101
+ var increment = 0;
3102
+ function bundleAnalysis(config) {
3103
+ return (0, import_rollup_plugin_visualizer.visualizer)({
3104
+ template: "raw-data",
3105
+ filename: import_node_path8.default.resolve(
3106
+ config.analysis.outputDir,
3107
+ `${config.analysis.outputName}-${increment++}.json`
3108
+ )
3109
+ });
3110
+ }
3111
+
3112
+ // src/core/utils/globals.ts
3113
+ function getGlobals(config) {
3114
+ return [
3115
+ {
3248
3116
  name: "MANIFEST_VERSION",
3249
3117
  value: config.manifestVersion,
3250
3118
  type: `2 | 3`
@@ -3296,988 +3164,1111 @@ function getEntrypointGlobals(entrypointName) {
3296
3164
  ];
3297
3165
  }
3298
3166
 
3299
- // src/core/utils/building/generate-wxt-dir.ts
3300
- var import_node_path6 = __toESM(require("path"), 1);
3301
-
3302
- // src/core/utils/i18n.ts
3303
- var predefinedMessages = {
3304
- "@@extension_id": {
3305
- message: "<browser.runtime.id>",
3306
- 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."
3307
- },
3308
- "@@ui_locale": {
3309
- message: "<browser.i18n.getUiLocale()>",
3310
- description: ""
3311
- },
3312
- "@@bidi_dir": {
3313
- message: "<ltr|rtl>",
3314
- 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.'
3315
- },
3316
- "@@bidi_reversed_dir": {
3317
- message: "<rtl|ltr>",
3318
- description: `If the @@bidi_dir is "ltr", then this is "rtl"; otherwise, it's "ltr".`
3319
- },
3320
- "@@bidi_start_edge": {
3321
- message: "<left|right>",
3322
- description: `If the @@bidi_dir is "ltr", then this is "left"; otherwise, it's "right".`
3323
- },
3324
- "@@bidi_end_edge": {
3325
- message: "<right|left>",
3326
- description: `If the @@bidi_dir is "ltr", then this is "right"; otherwise, it's "left".`
3327
- }
3328
- };
3329
- function parseI18nMessages(messagesJson) {
3330
- return Object.entries({
3331
- ...predefinedMessages,
3332
- ...messagesJson
3333
- }).map(([name, details]) => ({
3334
- name,
3335
- ...details
3336
- }));
3167
+ // src/core/builders/vite/plugins/globals.ts
3168
+ function globals(config) {
3169
+ return {
3170
+ name: "wxt:globals",
3171
+ config() {
3172
+ const define = {};
3173
+ for (const global3 of getGlobals(config)) {
3174
+ define[`import.meta.env.${global3.name}`] = JSON.stringify(global3.value);
3175
+ }
3176
+ return {
3177
+ define
3178
+ };
3179
+ }
3180
+ };
3337
3181
  }
3338
3182
 
3339
- // src/core/utils/building/generate-wxt-dir.ts
3340
- async function generateTypesDir(entrypoints) {
3341
- await import_fs_extra5.default.ensureDir(wxt.config.typesDir);
3342
- const references = [];
3343
- if (wxt.config.imports !== false) {
3344
- const unimport2 = (0, import_unimport.createUnimport)(wxt.config.imports);
3345
- references.push(await writeImportsDeclarationFile(unimport2));
3346
- if (wxt.config.imports.eslintrc.enabled) {
3347
- await writeImportsEslintFile(unimport2, wxt.config.imports);
3183
+ // src/core/builders/vite/plugins/webextensionPolyfillMock.ts
3184
+ var import_node_path9 = __toESM(require("path"), 1);
3185
+
3186
+ // src/core/builders/vite/plugins/excludeBrowserPolyfill.ts
3187
+ function excludeBrowserPolyfill(config) {
3188
+ const virtualId = "virtual:wxt-webextension-polyfill-disabled";
3189
+ return {
3190
+ name: "wxt:exclude-browser-polyfill",
3191
+ config() {
3192
+ if (config.experimental.includeBrowserPolyfill)
3193
+ return;
3194
+ return {
3195
+ resolve: {
3196
+ alias: {
3197
+ "webextension-polyfill": virtualId
3198
+ }
3199
+ }
3200
+ };
3201
+ },
3202
+ load(id) {
3203
+ if (id === virtualId) {
3204
+ return "export default chrome";
3205
+ }
3348
3206
  }
3349
- }
3350
- references.push(await writePathsDeclarationFile(entrypoints));
3351
- references.push(await writeI18nDeclarationFile());
3352
- references.push(await writeGlobalsDeclarationFile());
3353
- const mainReference = await writeMainDeclarationFile(references);
3354
- await writeTsConfigFile(mainReference);
3355
- }
3356
- async function writeImportsDeclarationFile(unimport2) {
3357
- const filePath = (0, import_path3.resolve)(wxt.config.typesDir, "imports.d.ts");
3358
- await unimport2.scanImportsFromDir(void 0, { cwd: wxt.config.srcDir });
3359
- await writeFileIfDifferent(
3360
- filePath,
3361
- ["// Generated by wxt", await unimport2.generateTypeDeclarations()].join(
3362
- "\n"
3363
- ) + "\n"
3364
- );
3365
- return filePath;
3366
- }
3367
- async function writeImportsEslintFile(unimport2, options) {
3368
- const globals2 = {};
3369
- const eslintrc = { globals: globals2 };
3370
- (await unimport2.getImports()).map((i) => i.as ?? i.name).filter(Boolean).sort().forEach((name) => {
3371
- eslintrc.globals[name] = options.eslintrc.globalsPropValue;
3372
- });
3373
- await import_fs_extra5.default.writeJson(options.eslintrc.filePath, eslintrc, { spaces: 2 });
3207
+ };
3374
3208
  }
3375
- async function writePathsDeclarationFile(entrypoints) {
3376
- const filePath = (0, import_path3.resolve)(wxt.config.typesDir, "paths.d.ts");
3377
- const unions = entrypoints.map(
3378
- (entry) => getEntrypointBundlePath(
3379
- entry,
3380
- wxt.config.outDir,
3381
- isHtmlEntrypoint(entry) ? ".html" : ".js"
3382
- )
3383
- ).concat(await getPublicFiles()).map(normalizePath).map((path13) => ` | "/${path13}"`).sort().join("\n");
3384
- const template = `// Generated by wxt
3385
- import "wxt/browser";
3386
3209
 
3387
- declare module "wxt/browser" {
3388
- export type PublicPath =
3389
- {{ union }}
3390
- type HtmlPublicPath = Extract<PublicPath, \`\${string}.html\`>
3391
- export interface WxtRuntime extends Runtime.Static {
3392
- getURL(path: PublicPath): string;
3393
- getURL(path: \`\${HtmlPublicPath}\${string}\`): string;
3394
- }
3395
- }
3396
- `;
3397
- await writeFileIfDifferent(
3398
- filePath,
3399
- template.replace("{{ union }}", unions || " | never")
3400
- );
3401
- return filePath;
3402
- }
3403
- async function writeI18nDeclarationFile() {
3404
- const filePath = (0, import_path3.resolve)(wxt.config.typesDir, "i18n.d.ts");
3405
- const defaultLocale = wxt.config.manifest.default_locale;
3406
- const template = `// Generated by wxt
3407
- import "wxt/browser";
3408
-
3409
- declare module "wxt/browser" {
3410
- /**
3411
- * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
3412
- */
3413
- interface GetMessageOptions {
3414
- /**
3415
- * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
3416
- */
3417
- escapeLt?: boolean
3418
- }
3419
-
3420
- export interface WxtI18n extends I18n.Static {
3421
- {{ overrides }}
3422
- }
3423
- }
3424
- `;
3425
- let messages;
3426
- if (defaultLocale) {
3427
- const defaultLocalePath = import_node_path6.default.resolve(
3428
- wxt.config.publicDir,
3429
- "_locales",
3430
- defaultLocale,
3431
- "messages.json"
3432
- );
3433
- const content = JSON.parse(await import_fs_extra5.default.readFile(defaultLocalePath, "utf-8"));
3434
- messages = parseI18nMessages(content);
3435
- } else {
3436
- messages = parseI18nMessages({});
3437
- }
3438
- const overrides = messages.map((message) => {
3439
- return ` /**
3440
- * ${message.description || "No message description."}
3441
- *
3442
- * "${message.message}"
3443
- */
3444
- getMessage(
3445
- messageName: "${message.name}",
3446
- substitutions?: string | string[],
3447
- options?: GetMessageOptions,
3448
- ): string;`;
3449
- });
3450
- await writeFileIfDifferent(
3451
- filePath,
3452
- template.replace("{{ overrides }}", overrides.join("\n"))
3453
- );
3454
- return filePath;
3455
- }
3456
- async function writeGlobalsDeclarationFile() {
3457
- const filePath = (0, import_path3.resolve)(wxt.config.typesDir, "globals.d.ts");
3458
- const globals2 = [...getGlobals(wxt.config), ...getEntrypointGlobals("")];
3459
- await writeFileIfDifferent(
3460
- filePath,
3461
- [
3462
- "// Generated by wxt",
3463
- "export {}",
3464
- "interface ImportMetaEnv {",
3465
- ...globals2.map((global3) => ` readonly ${global3.name}: ${global3.type};`),
3466
- "}",
3467
- "interface ImportMeta {",
3468
- " readonly env: ImportMetaEnv",
3469
- "}"
3470
- ].join("\n") + "\n"
3471
- );
3472
- return filePath;
3473
- }
3474
- async function writeMainDeclarationFile(references) {
3475
- const dir = wxt.config.wxtDir;
3476
- const filePath = (0, import_path3.resolve)(dir, "wxt.d.ts");
3477
- await writeFileIfDifferent(
3478
- filePath,
3479
- [
3480
- "// Generated by wxt",
3481
- `/// <reference types="wxt/vite-builder-env" />`,
3482
- ...references.map(
3483
- (ref) => `/// <reference types="./${normalizePath((0, import_path3.relative)(dir, ref))}" />`
3484
- )
3485
- ].join("\n") + "\n"
3486
- );
3487
- return filePath;
3488
- }
3489
- async function writeTsConfigFile(mainReference) {
3490
- const dir = wxt.config.wxtDir;
3491
- const getTsconfigPath = (path13) => normalizePath((0, import_path3.relative)(dir, path13));
3492
- const paths = Object.entries(wxt.config.alias).flatMap(([alias, absolutePath]) => {
3493
- const aliasPath = getTsconfigPath(absolutePath);
3494
- return [
3495
- ` "${alias}": ["${aliasPath}"]`,
3496
- ` "${alias}/*": ["${aliasPath}/*"]`
3497
- ];
3498
- }).join(",\n");
3499
- await writeFileIfDifferent(
3500
- (0, import_path3.resolve)(dir, "tsconfig.json"),
3501
- `{
3502
- "compilerOptions": {
3503
- "target": "ESNext",
3504
- "module": "ESNext",
3505
- "moduleResolution": "Bundler",
3506
- "noEmit": true,
3507
- "esModuleInterop": true,
3508
- "forceConsistentCasingInFileNames": true,
3509
- "resolveJsonModule": true,
3510
- "strict": true,
3511
- "skipLibCheck": true,
3512
- "paths": {
3513
- ${paths}
3210
+ // src/core/builders/vite/plugins/entrypointGroupGlobals.ts
3211
+ function entrypointGroupGlobals(entrypointGroup) {
3212
+ return {
3213
+ name: "wxt:entrypoint-group-globals",
3214
+ config() {
3215
+ const define = {};
3216
+ let name = Array.isArray(entrypointGroup) ? "html" : entrypointGroup.name;
3217
+ for (const global3 of getEntrypointGlobals(name)) {
3218
+ define[`import.meta.env.${global3.name}`] = JSON.stringify(global3.value);
3219
+ }
3220
+ return {
3221
+ define
3222
+ };
3514
3223
  }
3515
- },
3516
- "include": [
3517
- "${getTsconfigPath(wxt.config.root)}/**/*",
3518
- "./${getTsconfigPath(mainReference)}"
3519
- ],
3520
- "exclude": ["${getTsconfigPath(wxt.config.outBaseDir)}"]
3521
- }`
3522
- );
3224
+ };
3523
3225
  }
3524
3226
 
3525
- // src/core/utils/building/resolve-config.ts
3526
- var import_c12 = require("c12");
3527
- var import_node_path12 = __toESM(require("path"), 1);
3528
-
3529
- // src/core/utils/cache.ts
3530
- var import_fs_extra6 = __toESM(require("fs-extra"), 1);
3531
- var import_path4 = require("path");
3532
- function createFsCache(wxtDir) {
3533
- const getPath = (key) => (0, import_path4.resolve)(wxtDir, "cache", encodeURIComponent(key));
3227
+ // src/core/builders/vite/plugins/defineImportMeta.ts
3228
+ function defineImportMeta() {
3534
3229
  return {
3535
- async set(key, value) {
3536
- const path13 = getPath(key);
3537
- await (0, import_fs_extra6.ensureDir)((0, import_path4.dirname)(path13));
3538
- await writeFileIfDifferent(path13, value);
3539
- },
3540
- async get(key) {
3541
- const path13 = getPath(key);
3542
- try {
3543
- return await import_fs_extra6.default.readFile(path13, "utf-8");
3544
- } catch {
3545
- return void 0;
3546
- }
3230
+ name: "wxt:define",
3231
+ config() {
3232
+ return {
3233
+ define: {
3234
+ // This works for all extension contexts, including background service worker
3235
+ "import.meta.url": "self.location.href"
3236
+ }
3237
+ };
3547
3238
  }
3548
3239
  };
3549
3240
  }
3550
3241
 
3551
- // src/core/utils/building/resolve-config.ts
3552
- var import_consola = __toESM(require("consola"), 1);
3553
-
3554
- // src/core/builders/vite/plugins/devHtmlPrerender.ts
3555
- var import_linkedom2 = require("linkedom");
3556
- var import_node_path7 = require("path");
3557
- var reactRefreshPreamble = "";
3558
- function devHtmlPrerender(config) {
3559
- const htmlReloadId = "@wxt/reload-html";
3560
- const resolvedHtmlReloadId = (0, import_node_path7.resolve)(
3561
- config.wxtModuleDir,
3562
- "dist/virtual/reload-html.js"
3563
- );
3564
- const virtualReactRefreshId = "@wxt/virtual-react-refresh";
3565
- const resolvedVirtualReactRefreshId = "\0" + virtualReactRefreshId;
3566
- return [
3567
- {
3568
- apply: "build",
3569
- name: "wxt:dev-html-prerender",
3570
- config() {
3571
- return {
3572
- resolve: {
3573
- alias: {
3574
- [htmlReloadId]: resolvedHtmlReloadId
3242
+ // src/core/builders/vite/index.ts
3243
+ async function createViteBuilder(wxtConfig, server) {
3244
+ const vite = await import("vite");
3245
+ const getBaseConfig = async () => {
3246
+ const config = await wxtConfig.vite(wxtConfig.env);
3247
+ config.root = wxtConfig.root;
3248
+ config.configFile = false;
3249
+ config.logLevel = "warn";
3250
+ config.mode = wxtConfig.mode;
3251
+ config.build ??= {};
3252
+ config.build.outDir = wxtConfig.outDir;
3253
+ config.build.emptyOutDir = false;
3254
+ if (config.build.minify == null && wxtConfig.command === "serve") {
3255
+ config.build.minify = false;
3256
+ }
3257
+ if (config.build.sourcemap == null && wxtConfig.command === "serve") {
3258
+ config.build.sourcemap = "inline";
3259
+ }
3260
+ config.plugins ??= [];
3261
+ config.plugins.push(
3262
+ download(wxtConfig),
3263
+ devHtmlPrerender(wxtConfig, server),
3264
+ unimport(wxtConfig),
3265
+ virtualEntrypoint("background", wxtConfig),
3266
+ virtualEntrypoint("content-script-isolated-world", wxtConfig),
3267
+ virtualEntrypoint("content-script-main-world", wxtConfig),
3268
+ virtualEntrypoint("unlisted-script", wxtConfig),
3269
+ devServerGlobals(wxtConfig, server),
3270
+ tsconfigPaths(wxtConfig),
3271
+ noopBackground(),
3272
+ globals(wxtConfig),
3273
+ excludeBrowserPolyfill(wxtConfig),
3274
+ defineImportMeta()
3275
+ );
3276
+ if (wxtConfig.analysis.enabled) {
3277
+ config.plugins.push(bundleAnalysis(wxtConfig));
3278
+ }
3279
+ return config;
3280
+ };
3281
+ const getLibModeConfig = (entrypoint) => {
3282
+ const entry = getRollupEntry(entrypoint);
3283
+ const plugins = [
3284
+ entrypointGroupGlobals(entrypoint)
3285
+ ];
3286
+ if (entrypoint.type === "content-script-style" || entrypoint.type === "unlisted-style") {
3287
+ plugins.push(cssEntrypoints(entrypoint, wxtConfig));
3288
+ }
3289
+ const libMode = {
3290
+ mode: wxtConfig.mode,
3291
+ plugins,
3292
+ build: {
3293
+ lib: {
3294
+ entry,
3295
+ formats: ["iife"],
3296
+ name: "_",
3297
+ fileName: entrypoint.name
3298
+ },
3299
+ rollupOptions: {
3300
+ output: {
3301
+ // There's only a single output for this build, so we use the desired bundle path for the
3302
+ // entry output (like "content-scripts/overlay.js")
3303
+ entryFileNames: getEntrypointBundlePath(
3304
+ entrypoint,
3305
+ wxtConfig.outDir,
3306
+ ".js"
3307
+ ),
3308
+ // Output content script CSS to `content-scripts/`, but all other scripts are written to
3309
+ // `assets/`.
3310
+ assetFileNames: ({ name }) => {
3311
+ if (entrypoint.type === "content-script" && name?.endsWith("css")) {
3312
+ return `content-scripts/${entrypoint.name}.[ext]`;
3313
+ } else {
3314
+ return `assets/${entrypoint.name}.[ext]`;
3315
+ }
3575
3316
  }
3576
3317
  }
3577
- };
3318
+ }
3578
3319
  },
3579
- // Convert scripts like src="./main.tsx" -> src="http://localhost:3000/entrypoints/popup/main.tsx"
3580
- // before the paths are replaced with their bundled path
3581
- transform(code, id) {
3582
- const server = config.server;
3583
- if (config.command !== "serve" || server == null || !id.endsWith(".html"))
3584
- return;
3585
- const { document } = (0, import_linkedom2.parseHTML)(code);
3586
- const _pointToDevServer = (querySelector, attr) => pointToDevServer(config, server, id, document, querySelector, attr);
3587
- _pointToDevServer("script[type=module]", "src");
3588
- _pointToDevServer("link[rel=stylesheet]", "href");
3589
- const reloader = document.createElement("script");
3590
- reloader.src = htmlReloadId;
3591
- reloader.type = "module";
3592
- document.head.appendChild(reloader);
3593
- const newHtml = document.toString();
3594
- config.logger.debug("transform " + id);
3595
- config.logger.debug("Old HTML:\n" + code);
3596
- config.logger.debug("New HTML:\n" + newHtml);
3597
- return newHtml;
3598
- },
3599
- // Pass the HTML through the dev server to add dev-mode specific code
3600
- async transformIndexHtml(html, ctx) {
3601
- const server = config.server;
3602
- if (config.command !== "serve" || server == null)
3603
- return;
3604
- const originalUrl = `${server.origin}${ctx.path}`;
3605
- const name = getEntrypointName(config.entrypointsDir, ctx.filename);
3606
- const url2 = `${server.origin}/${name}.html`;
3607
- const serverHtml = await server.transformHtml(url2, html, originalUrl);
3608
- const { document } = (0, import_linkedom2.parseHTML)(serverHtml);
3609
- const reactRefreshScript = Array.from(
3610
- document.querySelectorAll("script[type=module]")
3611
- ).find((script) => script.innerHTML.includes("@react-refresh"));
3612
- if (reactRefreshScript) {
3613
- reactRefreshPreamble = reactRefreshScript.innerHTML;
3614
- const virtualScript = document.createElement("script");
3615
- virtualScript.type = "module";
3616
- virtualScript.src = `${server.origin}/${virtualReactRefreshId}`;
3617
- reactRefreshScript.replaceWith(virtualScript);
3618
- }
3619
- const viteClientScript = document.querySelector(
3620
- "script[src='/@vite/client']"
3621
- );
3622
- if (viteClientScript) {
3623
- viteClientScript.src = `${server.origin}${viteClientScript.src}`;
3624
- }
3625
- const newHtml = document.toString();
3626
- config.logger.debug("transformIndexHtml " + ctx.filename);
3627
- config.logger.debug("Old HTML:\n" + html);
3628
- config.logger.debug("New HTML:\n" + newHtml);
3629
- return newHtml;
3320
+ define: {
3321
+ // See https://github.com/aklinker1/vite-plugin-web-extension/issues/96
3322
+ "process.env.NODE_ENV": JSON.stringify(wxtConfig.mode)
3630
3323
  }
3631
- },
3632
- {
3633
- name: "wxt:virtualize-react-refresh",
3634
- apply: "serve",
3635
- resolveId(id) {
3636
- if (id === `/${virtualReactRefreshId}`) {
3637
- return resolvedVirtualReactRefreshId;
3638
- }
3639
- if (id.startsWith("/chunks/")) {
3640
- return "\0noop";
3641
- }
3642
- },
3643
- load(id) {
3644
- if (id === resolvedVirtualReactRefreshId) {
3645
- return reactRefreshPreamble;
3646
- }
3647
- if (id === "\0noop") {
3648
- return "";
3324
+ };
3325
+ return libMode;
3326
+ };
3327
+ const getMultiPageConfig = (entrypoints) => {
3328
+ const htmlEntrypoints = new Set(
3329
+ entrypoints.filter(isHtmlEntrypoint).map((e) => e.name)
3330
+ );
3331
+ return {
3332
+ mode: wxtConfig.mode,
3333
+ plugins: [
3334
+ multipageMove(entrypoints, wxtConfig),
3335
+ entrypointGroupGlobals(entrypoints)
3336
+ ],
3337
+ build: {
3338
+ rollupOptions: {
3339
+ input: entrypoints.reduce((input, entry) => {
3340
+ input[entry.name] = getRollupEntry(entry);
3341
+ return input;
3342
+ }, {}),
3343
+ output: {
3344
+ // Include a hash to prevent conflicts
3345
+ chunkFileNames: "chunks/[name]-[hash].js",
3346
+ entryFileNames: ({ name }) => {
3347
+ if (htmlEntrypoints.has(name))
3348
+ return "chunks/[name]-[hash].js";
3349
+ return "[name].js";
3350
+ },
3351
+ // We can't control the "name", so we need a hash to prevent conflicts
3352
+ assetFileNames: "assets/[name]-[hash].[ext]"
3353
+ }
3649
3354
  }
3650
3355
  }
3651
- }
3652
- ];
3653
- }
3654
- function pointToDevServer(config, server, id, document, querySelector, attr) {
3655
- document.querySelectorAll(querySelector).forEach((element) => {
3656
- const src = element.getAttribute(attr);
3657
- if (!src || isUrl(src))
3658
- return;
3659
- let resolvedAbsolutePath;
3660
- const matchingAlias = Object.entries(config.alias).find(
3661
- ([key]) => src.startsWith(key)
3662
- );
3663
- if (matchingAlias) {
3664
- const [alias, replacement] = matchingAlias;
3665
- resolvedAbsolutePath = (0, import_node_path7.resolve)(
3666
- config.root,
3667
- src.replace(alias, replacement)
3668
- );
3669
- } else {
3670
- resolvedAbsolutePath = (0, import_node_path7.resolve)((0, import_node_path7.dirname)(id), src);
3671
- }
3672
- if (resolvedAbsolutePath) {
3673
- const relativePath = normalizePath(
3674
- (0, import_node_path7.relative)(config.root, resolvedAbsolutePath)
3675
- );
3676
- if (relativePath.startsWith(".")) {
3677
- let path13 = normalizePath(resolvedAbsolutePath);
3678
- if (!path13.startsWith("/"))
3679
- path13 = "/" + path13;
3680
- element.setAttribute(attr, `${server.origin}/@fs${path13}`);
3681
- } else {
3682
- const url2 = new URL(relativePath, server.origin);
3683
- element.setAttribute(attr, url2.href);
3356
+ };
3357
+ };
3358
+ const getCssConfig = (entrypoint) => {
3359
+ return {
3360
+ mode: wxtConfig.mode,
3361
+ plugins: [entrypointGroupGlobals(entrypoint)],
3362
+ build: {
3363
+ rollupOptions: {
3364
+ input: {
3365
+ [entrypoint.name]: entrypoint.inputPath
3366
+ },
3367
+ output: {
3368
+ assetFileNames: () => {
3369
+ if (entrypoint.type === "content-script-style") {
3370
+ return `content-scripts/${entrypoint.name}.[ext]`;
3371
+ } else {
3372
+ return `assets/${entrypoint.name}.[ext]`;
3373
+ }
3374
+ }
3375
+ }
3376
+ }
3684
3377
  }
3685
- }
3686
- });
3687
- }
3688
- function isUrl(str) {
3689
- try {
3690
- new URL(str);
3691
- return true;
3692
- } catch {
3693
- return false;
3694
- }
3695
- }
3696
-
3697
- // src/core/builders/vite/plugins/devServerGlobals.ts
3698
- function devServerGlobals(config) {
3378
+ };
3379
+ };
3699
3380
  return {
3700
- name: "wxt:dev-server-globals",
3701
- config() {
3702
- if (config.server == null || config.command == "build")
3703
- return;
3381
+ name: "Vite",
3382
+ version: vite.version,
3383
+ async build(group) {
3384
+ let entryConfig;
3385
+ if (Array.isArray(group))
3386
+ entryConfig = getMultiPageConfig(group);
3387
+ else if (group.inputPath.endsWith(".css"))
3388
+ entryConfig = getCssConfig(group);
3389
+ else
3390
+ entryConfig = getLibModeConfig(group);
3391
+ const buildConfig = vite.mergeConfig(await getBaseConfig(), entryConfig);
3392
+ const result = await vite.build(buildConfig);
3704
3393
  return {
3705
- define: {
3706
- __DEV_SERVER_PROTOCOL__: JSON.stringify("ws:"),
3707
- __DEV_SERVER_HOSTNAME__: JSON.stringify(config.server.hostname),
3708
- __DEV_SERVER_PORT__: JSON.stringify(config.server.port)
3394
+ entrypoints: group,
3395
+ chunks: getBuildOutputChunks(result)
3396
+ };
3397
+ },
3398
+ async createServer(info) {
3399
+ const serverConfig = {
3400
+ server: {
3401
+ port: info.port,
3402
+ strictPort: true,
3403
+ host: info.hostname,
3404
+ origin: info.origin
3709
3405
  }
3710
3406
  };
3407
+ const baseConfig = await getBaseConfig();
3408
+ const viteServer = await vite.createServer(
3409
+ vite.mergeConfig(baseConfig, serverConfig)
3410
+ );
3411
+ const server2 = {
3412
+ async listen() {
3413
+ await viteServer.listen(info.port);
3414
+ },
3415
+ async close() {
3416
+ await viteServer.close();
3417
+ },
3418
+ transformHtml(...args) {
3419
+ return viteServer.transformIndexHtml(...args);
3420
+ },
3421
+ ws: {
3422
+ send(message, payload) {
3423
+ return viteServer.ws.send(message, payload);
3424
+ },
3425
+ on(message, cb) {
3426
+ viteServer.ws.on(message, cb);
3427
+ }
3428
+ },
3429
+ watcher: viteServer.watcher
3430
+ };
3431
+ return server2;
3711
3432
  }
3712
3433
  };
3713
3434
  }
3714
-
3715
- // src/core/utils/network.ts
3716
- var import_node_dns = __toESM(require("dns"), 1);
3717
-
3718
- // src/core/utils/time.ts
3719
- function formatDuration(duration) {
3720
- if (duration < 1e3)
3721
- return `${duration} ms`;
3722
- if (duration < 1e4)
3723
- return `${(duration / 1e3).toFixed(3)} s`;
3724
- if (duration < 6e4)
3725
- return `${(duration / 1e3).toFixed(1)} s`;
3726
- return `${(duration / 1e3).toFixed(0)} s`;
3435
+ function getBuildOutputChunks(result) {
3436
+ if ("on" in result)
3437
+ throw Error("wxt does not support vite watch mode.");
3438
+ if (Array.isArray(result))
3439
+ return result.flatMap(({ output }) => output);
3440
+ return result.output;
3727
3441
  }
3728
- function withTimeout(promise, duration) {
3729
- return new Promise((res, rej) => {
3730
- const timeout = setTimeout(() => {
3731
- rej(`Promise timed out after ${duration}ms`);
3732
- }, duration);
3733
- promise.then(res).catch(rej).finally(() => clearTimeout(timeout));
3734
- });
3442
+ function getRollupEntry(entrypoint) {
3443
+ let virtualEntrypointType;
3444
+ switch (entrypoint.type) {
3445
+ case "background":
3446
+ case "unlisted-script":
3447
+ virtualEntrypointType = entrypoint.type;
3448
+ break;
3449
+ case "content-script":
3450
+ virtualEntrypointType = entrypoint.options.world === "MAIN" ? "content-script-main-world" : "content-script-isolated-world";
3451
+ break;
3452
+ }
3453
+ return virtualEntrypointType ? `virtual:wxt-${virtualEntrypointType}?${entrypoint.inputPath}` : entrypoint.inputPath;
3735
3454
  }
3736
3455
 
3737
- // src/core/utils/network.ts
3738
- function isOffline() {
3739
- const isOffline2 = new Promise((res) => {
3740
- import_node_dns.default.resolve("google.com", (err) => {
3741
- if (err == null) {
3742
- res(false);
3743
- } else {
3744
- res(true);
3745
- }
3746
- });
3747
- });
3748
- return withTimeout(isOffline2, 1e3).catch(() => true);
3749
- }
3750
- async function isOnline() {
3751
- const offline = await isOffline();
3752
- return !offline;
3753
- }
3754
- async function fetchCached(url2, config) {
3755
- let content = "";
3756
- if (await isOnline()) {
3757
- const res = await fetch(url2);
3758
- if (res.status < 300) {
3759
- content = await res.text();
3760
- await config.fsCache.set(url2, content);
3761
- } else {
3762
- config.logger.debug(
3763
- `Failed to download "${url2}", falling back to cache...`
3764
- );
3765
- }
3766
- }
3767
- if (!content)
3768
- content = await config.fsCache.get(url2) ?? "";
3769
- if (!content)
3770
- throw Error(
3771
- `Offline and "${url2}" has not been cached. Try again when online.`
3772
- );
3773
- return content;
3774
- }
3775
-
3776
- // src/core/builders/vite/plugins/download.ts
3777
- function download(config) {
3778
- return {
3779
- name: "wxt:download",
3780
- resolveId(id) {
3781
- if (id.startsWith("url:"))
3782
- return "\0" + id;
3456
+ // src/core/wxt.ts
3457
+ var wxt;
3458
+ async function registerWxt(command, inlineConfig = {}, getServer) {
3459
+ const hooks = (0, import_hookable.createHooks)();
3460
+ const config = await resolveConfig(inlineConfig, command);
3461
+ const server = await getServer?.(config);
3462
+ const builder = await createViteBuilder(config, server);
3463
+ const pm = await createWxtPackageManager(config.root);
3464
+ wxt = {
3465
+ config,
3466
+ hooks,
3467
+ get logger() {
3468
+ return config.logger;
3783
3469
  },
3784
- async load(id) {
3785
- if (!id.startsWith("\0url:"))
3786
- return;
3787
- const url2 = id.replace("\0url:", "");
3788
- return await fetchCached(url2, config);
3789
- }
3470
+ async reloadConfig() {
3471
+ wxt.config = await resolveConfig(inlineConfig, command);
3472
+ },
3473
+ pm,
3474
+ builder,
3475
+ server
3790
3476
  };
3477
+ wxt.hooks.addHooks(config.hooks);
3478
+ await wxt.hooks.callHook("ready", wxt);
3791
3479
  }
3792
3480
 
3793
- // src/core/builders/vite/plugins/multipageMove.ts
3794
- var import_node_path8 = require("path");
3795
- var import_fs_extra7 = __toESM(require("fs-extra"), 1);
3796
- function multipageMove(entrypoints, config) {
3797
- return {
3798
- name: "wxt:multipage-move",
3799
- async writeBundle(_, bundle) {
3800
- for (const oldBundlePath in bundle) {
3801
- const entrypoint = entrypoints.find(
3802
- (entry) => !!normalizePath(entry.inputPath).endsWith(oldBundlePath)
3803
- );
3804
- if (entrypoint == null) {
3805
- config.logger.debug(
3806
- `No entrypoint found for ${oldBundlePath}, leaving in chunks directory`
3807
- );
3808
- continue;
3809
- }
3810
- const newBundlePath = getEntrypointBundlePath(
3811
- entrypoint,
3812
- config.outDir,
3813
- (0, import_node_path8.extname)(oldBundlePath)
3814
- );
3815
- if (newBundlePath === oldBundlePath) {
3816
- config.logger.debug(
3817
- "HTML file is already in the correct location",
3818
- oldBundlePath
3819
- );
3820
- continue;
3821
- }
3822
- const oldAbsPath = (0, import_node_path8.resolve)(config.outDir, oldBundlePath);
3823
- const newAbsPath = (0, import_node_path8.resolve)(config.outDir, newBundlePath);
3824
- await (0, import_fs_extra7.ensureDir)((0, import_node_path8.dirname)(newAbsPath));
3825
- await import_fs_extra7.default.move(oldAbsPath, newAbsPath, { overwrite: true });
3826
- const renamedChunk = {
3827
- ...bundle[oldBundlePath],
3828
- fileName: newBundlePath
3829
- };
3830
- delete bundle[oldBundlePath];
3831
- bundle[newBundlePath] = renamedChunk;
3832
- }
3833
- removeEmptyDirs(config.outDir);
3834
- }
3835
- };
3836
- }
3837
- async function removeEmptyDirs(dir) {
3838
- const files = await import_fs_extra7.default.readdir(dir);
3839
- for (const file of files) {
3840
- const filePath = (0, import_node_path8.join)(dir, file);
3841
- const stats = await import_fs_extra7.default.stat(filePath);
3842
- if (stats.isDirectory()) {
3843
- await removeEmptyDirs(filePath);
3844
- }
3845
- }
3846
- try {
3847
- await import_fs_extra7.default.rmdir(dir);
3848
- } catch {
3481
+ // src/core/utils/fs.ts
3482
+ async function writeFileIfDifferent(file, newContents) {
3483
+ const existingContents = await import_fs_extra4.default.readFile(file, "utf-8").catch(() => void 0);
3484
+ if (existingContents !== newContents) {
3485
+ await import_fs_extra4.default.writeFile(file, newContents);
3849
3486
  }
3850
3487
  }
3851
-
3852
- // src/core/builders/vite/plugins/unimport.ts
3853
- var import_unimport2 = require("unimport");
3854
- var import_path5 = require("path");
3855
- var ENABLED_EXTENSIONS = /* @__PURE__ */ new Set([
3856
- ".js",
3857
- ".jsx",
3858
- ".ts",
3859
- ".tsx",
3860
- ".vue",
3861
- ".svelte"
3862
- ]);
3863
- function unimport(config) {
3864
- const options = config.imports;
3865
- if (options === false)
3488
+ async function getPublicFiles() {
3489
+ if (!await import_fs_extra4.default.exists(wxt.config.publicDir))
3866
3490
  return [];
3867
- const unimport2 = (0, import_unimport2.createUnimport)(options);
3868
- return {
3869
- name: "wxt:unimport",
3870
- async config() {
3871
- await unimport2.scanImportsFromDir(void 0, { cwd: config.srcDir });
3872
- },
3873
- async transform(code, id) {
3874
- if (id.includes("node_modules"))
3875
- return;
3876
- if (!ENABLED_EXTENSIONS.has((0, import_path5.extname)(id)))
3877
- return;
3878
- const injected = await unimport2.injectImports(code, id);
3879
- return {
3880
- code: injected.code,
3881
- map: injected.s.generateMap({ hires: "boundary", source: id })
3882
- };
3883
- }
3884
- };
3491
+ const files = await (0, import_fast_glob.default)("**/*", { cwd: wxt.config.publicDir });
3492
+ return files.map(unnormalizePath);
3885
3493
  }
3886
3494
 
3887
- // src/core/builders/vite/plugins/virtualEntrypoint.ts
3888
- var import_fs_extra8 = __toESM(require("fs-extra"), 1);
3889
- var import_path6 = require("path");
3890
- function virtualEntrypoint(type, config) {
3891
- const virtualId = `virtual:wxt-${type}?`;
3892
- const resolvedVirtualId = `\0${virtualId}`;
3893
- return {
3894
- name: `wxt:virtual-entrypoint`,
3895
- resolveId(id) {
3896
- const index = id.indexOf(virtualId);
3897
- if (index === -1)
3898
- return;
3899
- const inputPath = normalizePath(id.substring(index + virtualId.length));
3900
- return resolvedVirtualId + inputPath;
3901
- },
3902
- async load(id) {
3903
- if (!id.startsWith(resolvedVirtualId))
3904
- return;
3905
- const inputPath = id.replace(resolvedVirtualId, "");
3906
- const template = await import_fs_extra8.default.readFile(
3907
- (0, import_path6.resolve)(config.wxtModuleDir, `dist/virtual/${type}-entrypoint.js`),
3908
- "utf-8"
3909
- );
3910
- return template.replace(`virtual:user-${type}`, inputPath);
3495
+ // src/core/utils/building/build-entrypoints.ts
3496
+ var import_fs_extra5 = __toESM(require("fs-extra"), 1);
3497
+ var import_path3 = require("path");
3498
+ var import_picocolors = __toESM(require("picocolors"), 1);
3499
+ async function buildEntrypoints(groups, spinner) {
3500
+ const steps = [];
3501
+ for (let i = 0; i < groups.length; i++) {
3502
+ const group = groups[i];
3503
+ const groupNames = [group].flat().map((e) => e.name);
3504
+ const groupNameColored = groupNames.join(import_picocolors.default.dim(", "));
3505
+ spinner.text = import_picocolors.default.dim(`[${i + 1}/${groups.length}]`) + ` ${groupNameColored}`;
3506
+ try {
3507
+ steps.push(await wxt.builder.build(group));
3508
+ } catch (err) {
3509
+ spinner.stop().clear();
3510
+ wxt.logger.error(err);
3511
+ throw Error(`Failed to build ${groupNames.join(", ")}`, { cause: err });
3911
3512
  }
3912
- };
3513
+ }
3514
+ const publicAssets = await copyPublicDirectory();
3515
+ return { publicAssets, steps };
3913
3516
  }
3914
-
3915
- // src/core/builders/vite/plugins/tsconfigPaths.ts
3916
- function tsconfigPaths(config) {
3917
- return {
3918
- name: "wxt:aliases",
3919
- async config() {
3920
- return {
3921
- resolve: {
3922
- alias: config.alias
3923
- }
3924
- };
3925
- }
3926
- };
3517
+ async function copyPublicDirectory() {
3518
+ const files = await getPublicFiles();
3519
+ if (files.length === 0)
3520
+ return [];
3521
+ const publicAssets = [];
3522
+ for (const file of files) {
3523
+ const srcPath = (0, import_path3.resolve)(wxt.config.publicDir, file);
3524
+ const outPath = (0, import_path3.resolve)(wxt.config.outDir, file);
3525
+ await import_fs_extra5.default.ensureDir((0, import_path3.dirname)(outPath));
3526
+ await import_fs_extra5.default.copyFile(srcPath, outPath);
3527
+ publicAssets.push({
3528
+ type: "asset",
3529
+ fileName: file
3530
+ });
3531
+ }
3532
+ return publicAssets;
3927
3533
  }
3928
3534
 
3929
- // src/core/builders/vite/plugins/noopBackground.ts
3930
- function noopBackground() {
3931
- const virtualModuleId = VIRTUAL_NOOP_BACKGROUND_MODULE_ID;
3932
- const resolvedVirtualModuleId = "\0" + virtualModuleId;
3933
- return {
3934
- name: "wxt:noop-background",
3935
- resolveId(id) {
3936
- if (id === virtualModuleId)
3937
- return resolvedVirtualModuleId;
3938
- },
3939
- load(id) {
3940
- if (id === resolvedVirtualModuleId) {
3941
- return `import { defineBackground } from 'wxt/sandbox';
3942
- export default defineBackground(() => void 0)`;
3943
- }
3944
- }
3945
- };
3535
+ // src/core/utils/arrays.ts
3536
+ function every(array, predicate) {
3537
+ for (let i = 0; i < array.length; i++)
3538
+ if (!predicate(array[i], i))
3539
+ return false;
3540
+ return true;
3541
+ }
3542
+ function some(array, predicate) {
3543
+ for (let i = 0; i < array.length; i++)
3544
+ if (predicate(array[i], i))
3545
+ return true;
3546
+ return false;
3946
3547
  }
3947
3548
 
3948
- // src/core/builders/vite/plugins/cssEntrypoints.ts
3949
- function cssEntrypoints(entrypoint, config) {
3950
- return {
3951
- name: "wxt:css-entrypoint",
3952
- config() {
3953
- return {
3954
- build: {
3955
- rollupOptions: {
3956
- output: {
3957
- assetFileNames: () => getEntrypointBundlePath(entrypoint, config.outDir, ".css")
3958
- }
3959
- }
3960
- }
3961
- };
3962
- },
3963
- generateBundle(_, bundle) {
3964
- Object.keys(bundle).forEach((file) => {
3965
- if (file.endsWith(".js"))
3966
- delete bundle[file];
3967
- });
3968
- }
3969
- };
3970
- }
3971
-
3972
- // src/core/builders/vite/plugins/bundleAnalysis.ts
3973
- var import_rollup_plugin_visualizer = require("@aklinker1/rollup-plugin-visualizer");
3974
- var import_node_path9 = __toESM(require("path"), 1);
3975
- var increment = 0;
3976
- function bundleAnalysis(config) {
3977
- return (0, import_rollup_plugin_visualizer.visualizer)({
3978
- template: "raw-data",
3979
- filename: import_node_path9.default.resolve(
3980
- config.analysis.outputDir,
3981
- `${config.analysis.outputName}-${increment++}.json`
3549
+ // src/core/utils/building/detect-dev-changes.ts
3550
+ function detectDevChanges(changedFiles, currentOutput) {
3551
+ const isConfigChange = some(
3552
+ changedFiles,
3553
+ (file) => file === wxt.config.userConfigMetadata.configFile
3554
+ );
3555
+ if (isConfigChange)
3556
+ return { type: "full-restart" };
3557
+ const isRunnerChange = some(
3558
+ changedFiles,
3559
+ (file) => file === wxt.config.runnerConfig.configFile
3560
+ );
3561
+ if (isRunnerChange)
3562
+ return { type: "browser-restart" };
3563
+ const changedSteps = new Set(
3564
+ changedFiles.flatMap(
3565
+ (changedFile) => findEffectedSteps(changedFile, currentOutput)
3982
3566
  )
3983
- });
3984
- }
3985
-
3986
- // src/core/builders/vite/plugins/globals.ts
3987
- function globals(config) {
3988
- return {
3989
- name: "wxt:globals",
3990
- config() {
3991
- const define = {};
3992
- for (const global3 of getGlobals(config)) {
3993
- define[`import.meta.env.${global3.name}`] = JSON.stringify(global3.value);
3994
- }
3995
- return {
3996
- define
3997
- };
3998
- }
3567
+ );
3568
+ if (changedSteps.size === 0)
3569
+ return { type: "no-change" };
3570
+ const unchangedOutput = {
3571
+ manifest: currentOutput.manifest,
3572
+ steps: [],
3573
+ publicAssets: []
3999
3574
  };
4000
- }
4001
-
4002
- // src/core/builders/vite/plugins/webextensionPolyfillMock.ts
4003
- var import_node_path10 = __toESM(require("path"), 1);
4004
-
4005
- // src/core/builders/vite/plugins/excludeBrowserPolyfill.ts
4006
- function excludeBrowserPolyfill(config) {
4007
- const virtualId = "virtual:wxt-webextension-polyfill-disabled";
4008
- return {
4009
- name: "wxt:exclude-browser-polyfill",
4010
- config() {
4011
- if (config.experimental.includeBrowserPolyfill)
4012
- return;
4013
- return {
4014
- resolve: {
4015
- alias: {
4016
- "webextension-polyfill": virtualId
4017
- }
4018
- }
4019
- };
4020
- },
4021
- load(id) {
4022
- if (id === virtualId) {
4023
- return "export default chrome";
4024
- }
4025
- }
3575
+ const changedOutput = {
3576
+ manifest: currentOutput.manifest,
3577
+ steps: [],
3578
+ publicAssets: []
4026
3579
  };
4027
- }
4028
-
4029
- // src/core/builders/vite/plugins/entrypointGroupGlobals.ts
4030
- function entrypointGroupGlobals(entrypointGroup) {
4031
- return {
4032
- name: "wxt:entrypoint-group-globals",
4033
- config() {
4034
- const define = {};
4035
- let name = Array.isArray(entrypointGroup) ? "html" : entrypointGroup.name;
4036
- for (const global3 of getEntrypointGlobals(name)) {
4037
- define[`import.meta.env.${global3.name}`] = JSON.stringify(global3.value);
4038
- }
4039
- return {
4040
- define
4041
- };
3580
+ for (const step of currentOutput.steps) {
3581
+ if (changedSteps.has(step)) {
3582
+ changedOutput.steps.push(step);
3583
+ } else {
3584
+ unchangedOutput.steps.push(step);
4042
3585
  }
4043
- };
4044
- }
4045
-
4046
- // src/core/builders/vite/plugins/defineImportMeta.ts
4047
- function defineImportMeta() {
4048
- return {
4049
- name: "wxt:define",
4050
- config() {
4051
- return {
4052
- define: {
4053
- // This works for all extension contexts, including background service worker
4054
- "import.meta.url": "self.location.href"
4055
- }
4056
- };
3586
+ }
3587
+ for (const asset of currentOutput.publicAssets) {
3588
+ if (changedSteps.has(asset)) {
3589
+ changedOutput.publicAssets.push(asset);
3590
+ } else {
3591
+ unchangedOutput.publicAssets.push(asset);
4057
3592
  }
3593
+ }
3594
+ const isOnlyHtmlChanges = changedFiles.length > 0 && every(changedFiles, (file) => file.endsWith(".html"));
3595
+ if (isOnlyHtmlChanges) {
3596
+ return {
3597
+ type: "html-reload",
3598
+ cachedOutput: unchangedOutput,
3599
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
3600
+ };
3601
+ }
3602
+ const isOnlyContentScripts = changedOutput.steps.length > 0 && every(
3603
+ changedOutput.steps.flatMap((step) => step.entrypoints),
3604
+ (entry) => entry.type === "content-script"
3605
+ );
3606
+ if (isOnlyContentScripts) {
3607
+ return {
3608
+ type: "content-script-reload",
3609
+ cachedOutput: unchangedOutput,
3610
+ changedSteps: changedOutput.steps,
3611
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
3612
+ };
3613
+ }
3614
+ return {
3615
+ type: "extension-reload",
3616
+ cachedOutput: unchangedOutput,
3617
+ rebuildGroups: changedOutput.steps.map((step) => step.entrypoints)
4058
3618
  };
4059
3619
  }
3620
+ function findEffectedSteps(changedFile, currentOutput) {
3621
+ const changes = [];
3622
+ const changedPath = normalizePath(changedFile);
3623
+ const isChunkEffected = (chunk) => (
3624
+ // If it's an HTML file with the same path, is is effected because HTML files need to be re-rendered
3625
+ // - fileName is normalized, relative bundle path, "<entrypoint-name>.html"
3626
+ 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
3627
+ // - moduleIds are absolute, normalized paths
3628
+ chunk.type === "chunk" && chunk.moduleIds.includes(changedPath)
3629
+ );
3630
+ for (const step of currentOutput.steps) {
3631
+ const effectedChunk = step.chunks.find((chunk) => isChunkEffected(chunk));
3632
+ if (effectedChunk)
3633
+ changes.push(step);
3634
+ }
3635
+ const effectedAsset = currentOutput.publicAssets.find(
3636
+ (chunk) => isChunkEffected(chunk)
3637
+ );
3638
+ if (effectedAsset)
3639
+ changes.push(effectedAsset);
3640
+ return changes;
3641
+ }
4060
3642
 
4061
- // src/core/builders/vite/index.ts
4062
- async function createViteBuilder(inlineConfig, userConfig, wxtConfig) {
4063
- const vite = await import("vite");
4064
- const getBaseConfig = async () => {
4065
- const resolvedInlineConfig = await inlineConfig.vite?.(wxtConfig.env) ?? {};
4066
- const resolvedUserConfig = await userConfig.vite?.(wxtConfig.env) ?? {};
4067
- const config = vite.mergeConfig(
4068
- resolvedUserConfig,
4069
- resolvedInlineConfig
4070
- );
4071
- config.root = wxtConfig.root;
4072
- config.configFile = false;
4073
- config.logLevel = "warn";
4074
- config.mode = wxtConfig.mode;
4075
- config.build ??= {};
4076
- config.build.outDir = wxtConfig.outDir;
4077
- config.build.emptyOutDir = false;
4078
- if (config.build.minify == null && wxtConfig.command === "serve") {
4079
- config.build.minify = false;
4080
- }
4081
- if (config.build.sourcemap == null && wxtConfig.command === "serve") {
4082
- config.build.sourcemap = "inline";
4083
- }
4084
- config.plugins ??= [];
4085
- config.plugins.push(
4086
- download(wxtConfig),
4087
- devHtmlPrerender(wxtConfig),
4088
- unimport(wxtConfig),
4089
- virtualEntrypoint("background", wxtConfig),
4090
- virtualEntrypoint("content-script-isolated-world", wxtConfig),
4091
- virtualEntrypoint("content-script-main-world", wxtConfig),
4092
- virtualEntrypoint("unlisted-script", wxtConfig),
4093
- devServerGlobals(wxtConfig),
4094
- tsconfigPaths(wxtConfig),
4095
- noopBackground(),
4096
- globals(wxtConfig),
4097
- excludeBrowserPolyfill(wxtConfig),
4098
- defineImportMeta()
3643
+ // src/core/utils/building/find-entrypoints.ts
3644
+ var import_path4 = require("path");
3645
+ var import_fs_extra6 = __toESM(require("fs-extra"), 1);
3646
+ var import_minimatch = require("minimatch");
3647
+ var import_linkedom2 = require("linkedom");
3648
+ var import_json5 = __toESM(require("json5"), 1);
3649
+ var import_fast_glob2 = __toESM(require("fast-glob"), 1);
3650
+ var import_picocolors2 = __toESM(require("picocolors"), 1);
3651
+ async function findEntrypoints() {
3652
+ const relativePaths = await (0, import_fast_glob2.default)(Object.keys(PATH_GLOB_TO_TYPE_MAP), {
3653
+ cwd: wxt.config.entrypointsDir
3654
+ });
3655
+ relativePaths.sort();
3656
+ const pathGlobs = Object.keys(PATH_GLOB_TO_TYPE_MAP);
3657
+ const entrypointInfos = relativePaths.reduce((results, relativePath) => {
3658
+ const inputPath = (0, import_path4.resolve)(wxt.config.entrypointsDir, relativePath);
3659
+ const name = getEntrypointName(wxt.config.entrypointsDir, inputPath);
3660
+ const matchingGlob = pathGlobs.find(
3661
+ (glob6) => (0, import_minimatch.minimatch)(relativePath, glob6)
4099
3662
  );
4100
- if (wxtConfig.analysis.enabled) {
4101
- config.plugins.push(bundleAnalysis(wxtConfig));
4102
- }
4103
- return config;
4104
- };
4105
- const getLibModeConfig = (entrypoint) => {
4106
- const entry = getRollupEntry(entrypoint);
4107
- const plugins = [
4108
- entrypointGroupGlobals(entrypoint)
4109
- ];
4110
- if (entrypoint.type === "content-script-style" || entrypoint.type === "unlisted-style") {
4111
- plugins.push(cssEntrypoints(entrypoint, wxtConfig));
3663
+ if (matchingGlob) {
3664
+ const type = PATH_GLOB_TO_TYPE_MAP[matchingGlob];
3665
+ results.push({
3666
+ name,
3667
+ inputPath,
3668
+ type,
3669
+ skipped: wxt.config.filterEntrypoints != null && !wxt.config.filterEntrypoints.has(name)
3670
+ });
4112
3671
  }
4113
- const libMode = {
4114
- mode: wxtConfig.mode,
4115
- plugins,
4116
- build: {
4117
- lib: {
4118
- entry,
4119
- formats: ["iife"],
4120
- name: "_",
4121
- fileName: entrypoint.name
4122
- },
4123
- rollupOptions: {
4124
- output: {
4125
- // There's only a single output for this build, so we use the desired bundle path for the
4126
- // entry output (like "content-scripts/overlay.js")
4127
- entryFileNames: getEntrypointBundlePath(
4128
- entrypoint,
4129
- wxtConfig.outDir,
4130
- ".js"
4131
- ),
4132
- // Output content script CSS to `content-scripts/`, but all other scripts are written to
4133
- // `assets/`.
4134
- assetFileNames: ({ name }) => {
4135
- if (entrypoint.type === "content-script" && name?.endsWith("css")) {
4136
- return `content-scripts/${entrypoint.name}.[ext]`;
4137
- } else {
4138
- return `assets/${entrypoint.name}.[ext]`;
4139
- }
3672
+ return results;
3673
+ }, []);
3674
+ preventNoEntrypoints(entrypointInfos);
3675
+ preventDuplicateEntrypointNames(entrypointInfos);
3676
+ let hasBackground = false;
3677
+ const entrypoints = await Promise.all(
3678
+ entrypointInfos.map(async (info) => {
3679
+ const { type } = info;
3680
+ switch (type) {
3681
+ case "popup":
3682
+ return await getPopupEntrypoint(info);
3683
+ case "sidepanel":
3684
+ return await getSidepanelEntrypoint(info);
3685
+ case "options":
3686
+ return await getOptionsEntrypoint(info);
3687
+ case "background":
3688
+ hasBackground = true;
3689
+ return await getBackgroundEntrypoint(info);
3690
+ case "content-script":
3691
+ return await getContentScriptEntrypoint(info);
3692
+ case "unlisted-page":
3693
+ return await getUnlistedPageEntrypoint(info);
3694
+ case "unlisted-script":
3695
+ return await getUnlistedScriptEntrypoint(info);
3696
+ case "content-script-style":
3697
+ return {
3698
+ ...info,
3699
+ type,
3700
+ outputDir: (0, import_path4.resolve)(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
3701
+ options: {
3702
+ include: void 0,
3703
+ exclude: void 0
4140
3704
  }
4141
- }
4142
- }
4143
- },
4144
- define: {
4145
- // See https://github.com/aklinker1/vite-plugin-web-extension/issues/96
4146
- "process.env.NODE_ENV": JSON.stringify(wxtConfig.mode)
3705
+ };
3706
+ default:
3707
+ return {
3708
+ ...info,
3709
+ type,
3710
+ outputDir: wxt.config.outDir,
3711
+ options: {
3712
+ include: void 0,
3713
+ exclude: void 0
3714
+ }
3715
+ };
4147
3716
  }
4148
- };
4149
- return libMode;
4150
- };
4151
- const getMultiPageConfig = (entrypoints) => {
4152
- const htmlEntrypoints = new Set(
4153
- entrypoints.filter(isHtmlEntrypoint).map((e) => e.name)
3717
+ })
3718
+ );
3719
+ if (wxt.config.command === "serve" && !hasBackground) {
3720
+ entrypoints.push(
3721
+ await getBackgroundEntrypoint({
3722
+ inputPath: VIRTUAL_NOOP_BACKGROUND_MODULE_ID,
3723
+ name: "background",
3724
+ type: "background",
3725
+ skipped: false
3726
+ })
4154
3727
  );
4155
- return {
4156
- mode: wxtConfig.mode,
4157
- plugins: [
4158
- multipageMove(entrypoints, wxtConfig),
4159
- entrypointGroupGlobals(entrypoints)
4160
- ],
4161
- build: {
4162
- rollupOptions: {
4163
- input: entrypoints.reduce((input, entry) => {
4164
- input[entry.name] = getRollupEntry(entry);
4165
- return input;
4166
- }, {}),
4167
- output: {
4168
- // Include a hash to prevent conflicts
4169
- chunkFileNames: "chunks/[name]-[hash].js",
4170
- entryFileNames: ({ name }) => {
4171
- if (htmlEntrypoints.has(name))
4172
- return "chunks/[name]-[hash].js";
4173
- return "[name].js";
4174
- },
4175
- // We can't control the "name", so we need a hash to prevent conflicts
4176
- assetFileNames: "assets/[name]-[hash].[ext]"
4177
- }
4178
- }
3728
+ }
3729
+ wxt.logger.debug("All entrypoints:", entrypoints);
3730
+ const skippedEntrypointNames = entrypointInfos.filter((item) => item.skipped).map((item) => item.name);
3731
+ if (skippedEntrypointNames.length) {
3732
+ wxt.logger.warn(
3733
+ `Filter excluded the following entrypoints:
3734
+ ${skippedEntrypointNames.map((item) => `${import_picocolors2.default.dim("-")} ${import_picocolors2.default.cyan(item)}`).join("\n")}`
3735
+ );
3736
+ }
3737
+ const targetEntrypoints = entrypoints.filter((entry) => {
3738
+ const { include, exclude } = entry.options;
3739
+ if (include?.length && exclude?.length) {
3740
+ wxt.logger.warn(
3741
+ `The ${entry.name} entrypoint lists both include and exclude, but only one can be used per entrypoint. Entrypoint ignored.`
3742
+ );
3743
+ return false;
3744
+ }
3745
+ if (exclude?.length && !include?.length) {
3746
+ return !exclude.includes(wxt.config.browser);
3747
+ }
3748
+ if (include?.length && !exclude?.length) {
3749
+ return include.includes(wxt.config.browser);
3750
+ }
3751
+ if (skippedEntrypointNames.includes(entry.name)) {
3752
+ return false;
3753
+ }
3754
+ return true;
3755
+ });
3756
+ wxt.logger.debug(`${wxt.config.browser} entrypoints:`, targetEntrypoints);
3757
+ await wxt.hooks.callHook("entrypoints:resolved", wxt, targetEntrypoints);
3758
+ return targetEntrypoints;
3759
+ }
3760
+ function preventDuplicateEntrypointNames(files) {
3761
+ const namesToPaths = files.reduce(
3762
+ (map, { name, inputPath }) => {
3763
+ map[name] ??= [];
3764
+ map[name].push(inputPath);
3765
+ return map;
3766
+ },
3767
+ {}
3768
+ );
3769
+ const errorLines = Object.entries(namesToPaths).reduce(
3770
+ (lines, [name, absolutePaths]) => {
3771
+ if (absolutePaths.length > 1) {
3772
+ lines.push(`- ${name}`);
3773
+ absolutePaths.forEach((absolutePath) => {
3774
+ lines.push(` - ${(0, import_path4.relative)(wxt.config.root, absolutePath)}`);
3775
+ });
4179
3776
  }
4180
- };
3777
+ return lines;
3778
+ },
3779
+ []
3780
+ );
3781
+ if (errorLines.length > 0) {
3782
+ const errorContent = errorLines.join("\n");
3783
+ throw Error(
3784
+ `Multiple entrypoints with the same name detected, only one entrypoint for each name is allowed.
3785
+
3786
+ ${errorContent}`
3787
+ );
3788
+ }
3789
+ }
3790
+ function preventNoEntrypoints(files) {
3791
+ if (files.length === 0) {
3792
+ throw Error(`No entrypoints found in ${wxt.config.entrypointsDir}`);
3793
+ }
3794
+ }
3795
+ async function getPopupEntrypoint(info) {
3796
+ const options = await getHtmlEntrypointOptions(
3797
+ info,
3798
+ {
3799
+ browserStyle: "browse_style",
3800
+ exclude: "exclude",
3801
+ include: "include",
3802
+ defaultIcon: "default_icon",
3803
+ defaultTitle: "default_title",
3804
+ mv2Key: "type"
3805
+ },
3806
+ {
3807
+ defaultTitle: (document) => document.querySelector("title")?.textContent || void 0
3808
+ },
3809
+ {
3810
+ defaultTitle: (content) => content,
3811
+ mv2Key: (content) => content === "page_action" ? "page_action" : "browser_action"
3812
+ }
3813
+ );
3814
+ return {
3815
+ type: "popup",
3816
+ name: "popup",
3817
+ options: resolvePerBrowserOptions(options, wxt.config.browser),
3818
+ inputPath: info.inputPath,
3819
+ outputDir: wxt.config.outDir,
3820
+ skipped: info.skipped
4181
3821
  };
4182
- const getCssConfig = (entrypoint) => {
4183
- return {
4184
- mode: wxtConfig.mode,
4185
- plugins: [entrypointGroupGlobals(entrypoint)],
4186
- build: {
4187
- rollupOptions: {
4188
- input: {
4189
- [entrypoint.name]: entrypoint.inputPath
4190
- },
4191
- output: {
4192
- assetFileNames: () => {
4193
- if (entrypoint.type === "content-script-style") {
4194
- return `content-scripts/${entrypoint.name}.[ext]`;
4195
- } else {
4196
- return `assets/${entrypoint.name}.[ext]`;
4197
- }
4198
- }
4199
- }
4200
- }
4201
- }
4202
- };
3822
+ }
3823
+ async function getOptionsEntrypoint(info) {
3824
+ const options = await getHtmlEntrypointOptions(
3825
+ info,
3826
+ {
3827
+ browserStyle: "browse_style",
3828
+ chromeStyle: "chrome_style",
3829
+ exclude: "exclude",
3830
+ include: "include",
3831
+ openInTab: "open_in_tab"
3832
+ }
3833
+ );
3834
+ return {
3835
+ type: "options",
3836
+ name: "options",
3837
+ options: resolvePerBrowserOptions(options, wxt.config.browser),
3838
+ inputPath: info.inputPath,
3839
+ outputDir: wxt.config.outDir,
3840
+ skipped: info.skipped
3841
+ };
3842
+ }
3843
+ async function getUnlistedPageEntrypoint(info) {
3844
+ const options = await getHtmlEntrypointOptions(info, {
3845
+ exclude: "exclude",
3846
+ include: "include"
3847
+ });
3848
+ return {
3849
+ type: "unlisted-page",
3850
+ name: info.name,
3851
+ inputPath: info.inputPath,
3852
+ outputDir: wxt.config.outDir,
3853
+ options,
3854
+ skipped: info.skipped
4203
3855
  };
3856
+ }
3857
+ async function getUnlistedScriptEntrypoint({
3858
+ inputPath,
3859
+ name,
3860
+ skipped
3861
+ }) {
3862
+ const defaultExport = await importEntrypointFile(inputPath);
3863
+ if (defaultExport == null) {
3864
+ throw Error(
3865
+ `${name}: Default export not found, did you forget to call "export default defineUnlistedScript(...)"?`
3866
+ );
3867
+ }
3868
+ const { main: _, ...options } = defaultExport;
3869
+ return {
3870
+ type: "unlisted-script",
3871
+ name,
3872
+ inputPath,
3873
+ outputDir: wxt.config.outDir,
3874
+ options: resolvePerBrowserOptions(options, wxt.config.browser),
3875
+ skipped
3876
+ };
3877
+ }
3878
+ async function getBackgroundEntrypoint({
3879
+ inputPath,
3880
+ name,
3881
+ skipped
3882
+ }) {
3883
+ let options = {};
3884
+ if (inputPath !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) {
3885
+ const defaultExport = await importEntrypointFile(inputPath);
3886
+ if (defaultExport == null) {
3887
+ throw Error(
3888
+ `${name}: Default export not found, did you forget to call "export default defineBackground(...)"?`
3889
+ );
3890
+ }
3891
+ const { main: _, ...moduleOptions } = defaultExport;
3892
+ options = moduleOptions;
3893
+ }
3894
+ if (wxt.config.manifestVersion !== 3) {
3895
+ delete options.type;
3896
+ }
3897
+ return {
3898
+ type: "background",
3899
+ name,
3900
+ inputPath,
3901
+ outputDir: wxt.config.outDir,
3902
+ options: resolvePerBrowserOptions(options, wxt.config.browser),
3903
+ skipped
3904
+ };
3905
+ }
3906
+ async function getContentScriptEntrypoint({
3907
+ inputPath,
3908
+ name,
3909
+ skipped
3910
+ }) {
3911
+ const { main: _, ...options } = await importEntrypointFile(inputPath);
3912
+ if (options == null) {
3913
+ throw Error(
3914
+ `${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`
3915
+ );
3916
+ }
3917
+ return {
3918
+ type: "content-script",
3919
+ name,
3920
+ inputPath,
3921
+ outputDir: (0, import_path4.resolve)(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
3922
+ options: resolvePerBrowserOptions(options, wxt.config.browser),
3923
+ skipped
3924
+ };
3925
+ }
3926
+ async function getSidepanelEntrypoint(info) {
3927
+ const options = await getHtmlEntrypointOptions(
3928
+ info,
3929
+ {
3930
+ browserStyle: "browse_style",
3931
+ exclude: "exclude",
3932
+ include: "include",
3933
+ defaultIcon: "default_icon",
3934
+ defaultTitle: "default_title",
3935
+ openAtInstall: "open_at_install"
3936
+ },
3937
+ {
3938
+ defaultTitle: (document) => document.querySelector("title")?.textContent || void 0
3939
+ },
3940
+ {
3941
+ defaultTitle: (content) => content
3942
+ }
3943
+ );
3944
+ return {
3945
+ type: "sidepanel",
3946
+ name: info.name,
3947
+ options: resolvePerBrowserOptions(options, wxt.config.browser),
3948
+ inputPath: info.inputPath,
3949
+ outputDir: wxt.config.outDir,
3950
+ skipped: info.skipped
3951
+ };
3952
+ }
3953
+ async function getHtmlEntrypointOptions(info, keyMap, queries, parsers) {
3954
+ const content = await import_fs_extra6.default.readFile(info.inputPath, "utf-8");
3955
+ const { document } = (0, import_linkedom2.parseHTML)(content);
3956
+ const options = {};
3957
+ const defaultQuery = (manifestKey) => document.querySelector(`meta[name='manifest.${manifestKey}']`)?.getAttribute("content");
3958
+ Object.entries(keyMap).forEach(([_key, manifestKey]) => {
3959
+ const key = _key;
3960
+ const content2 = queries?.[key] ? queries[key](document, manifestKey) : defaultQuery(manifestKey);
3961
+ if (content2) {
3962
+ try {
3963
+ options[key] = (parsers?.[key] ?? import_json5.default.parse)(content2);
3964
+ } catch (err) {
3965
+ wxt.logger.fatal(
3966
+ `Failed to parse meta tag content. Usually this means you have invalid JSON5 content (content=${content2})`,
3967
+ err
3968
+ );
3969
+ }
3970
+ }
3971
+ });
3972
+ return options;
3973
+ }
3974
+ var PATH_GLOB_TO_TYPE_MAP = {
3975
+ "sandbox.html": "sandbox",
3976
+ "sandbox/index.html": "sandbox",
3977
+ "*.sandbox.html": "sandbox",
3978
+ "*.sandbox/index.html": "sandbox",
3979
+ "bookmarks.html": "bookmarks",
3980
+ "bookmarks/index.html": "bookmarks",
3981
+ "history.html": "history",
3982
+ "history/index.html": "history",
3983
+ "newtab.html": "newtab",
3984
+ "newtab/index.html": "newtab",
3985
+ "sidepanel.html": "sidepanel",
3986
+ "sidepanel/index.html": "sidepanel",
3987
+ "*.sidepanel.html": "sidepanel",
3988
+ "*.sidepanel/index.html": "sidepanel",
3989
+ "devtools.html": "devtools",
3990
+ "devtools/index.html": "devtools",
3991
+ "background.[jt]s": "background",
3992
+ "background/index.[jt]s": "background",
3993
+ [VIRTUAL_NOOP_BACKGROUND_MODULE_ID]: "background",
3994
+ "content.[jt]s?(x)": "content-script",
3995
+ "content/index.[jt]s?(x)": "content-script",
3996
+ "*.content.[jt]s?(x)": "content-script",
3997
+ "*.content/index.[jt]s?(x)": "content-script",
3998
+ [`content.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
3999
+ [`*.content.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
4000
+ [`content/index.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
4001
+ [`*.content/index.${CSS_EXTENSIONS_PATTERN}`]: "content-script-style",
4002
+ "popup.html": "popup",
4003
+ "popup/index.html": "popup",
4004
+ "options.html": "options",
4005
+ "options/index.html": "options",
4006
+ "*.html": "unlisted-page",
4007
+ "*/index.html": "unlisted-page",
4008
+ "*.[jt]s?(x)": "unlisted-script",
4009
+ "*/index.[jt]s?(x)": "unlisted-script",
4010
+ [`*.${CSS_EXTENSIONS_PATTERN}`]: "unlisted-style",
4011
+ [`*/index.${CSS_EXTENSIONS_PATTERN}`]: "unlisted-style"
4012
+ };
4013
+ var CONTENT_SCRIPT_OUT_DIR = "content-scripts";
4014
+
4015
+ // src/core/utils/building/generate-wxt-dir.ts
4016
+ var import_unimport2 = require("unimport");
4017
+ var import_fs_extra7 = __toESM(require("fs-extra"), 1);
4018
+ var import_path5 = require("path");
4019
+ var import_node_path10 = __toESM(require("path"), 1);
4020
+
4021
+ // src/core/utils/i18n.ts
4022
+ var predefinedMessages = {
4023
+ "@@extension_id": {
4024
+ message: "<browser.runtime.id>",
4025
+ 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."
4026
+ },
4027
+ "@@ui_locale": {
4028
+ message: "<browser.i18n.getUiLocale()>",
4029
+ description: ""
4030
+ },
4031
+ "@@bidi_dir": {
4032
+ message: "<ltr|rtl>",
4033
+ 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.'
4034
+ },
4035
+ "@@bidi_reversed_dir": {
4036
+ message: "<rtl|ltr>",
4037
+ description: `If the @@bidi_dir is "ltr", then this is "rtl"; otherwise, it's "ltr".`
4038
+ },
4039
+ "@@bidi_start_edge": {
4040
+ message: "<left|right>",
4041
+ description: `If the @@bidi_dir is "ltr", then this is "left"; otherwise, it's "right".`
4042
+ },
4043
+ "@@bidi_end_edge": {
4044
+ message: "<right|left>",
4045
+ description: `If the @@bidi_dir is "ltr", then this is "right"; otherwise, it's "left".`
4046
+ }
4047
+ };
4048
+ function parseI18nMessages(messagesJson) {
4049
+ return Object.entries({
4050
+ ...predefinedMessages,
4051
+ ...messagesJson
4052
+ }).map(([name, details]) => ({
4053
+ name,
4054
+ ...details
4055
+ }));
4056
+ }
4057
+
4058
+ // src/core/utils/building/generate-wxt-dir.ts
4059
+ async function generateTypesDir(entrypoints) {
4060
+ await import_fs_extra7.default.ensureDir(wxt.config.typesDir);
4061
+ const references = [];
4062
+ if (wxt.config.imports !== false) {
4063
+ const unimport2 = (0, import_unimport2.createUnimport)(wxt.config.imports);
4064
+ references.push(await writeImportsDeclarationFile(unimport2));
4065
+ if (wxt.config.imports.eslintrc.enabled) {
4066
+ await writeImportsEslintFile(unimport2, wxt.config.imports);
4067
+ }
4068
+ }
4069
+ references.push(await writePathsDeclarationFile(entrypoints));
4070
+ references.push(await writeI18nDeclarationFile());
4071
+ references.push(await writeGlobalsDeclarationFile());
4072
+ const mainReference = await writeMainDeclarationFile(references);
4073
+ await writeTsConfigFile(mainReference);
4074
+ }
4075
+ async function writeImportsDeclarationFile(unimport2) {
4076
+ const filePath = (0, import_path5.resolve)(wxt.config.typesDir, "imports.d.ts");
4077
+ await unimport2.scanImportsFromDir(void 0, { cwd: wxt.config.srcDir });
4078
+ await writeFileIfDifferent(
4079
+ filePath,
4080
+ ["// Generated by wxt", await unimport2.generateTypeDeclarations()].join(
4081
+ "\n"
4082
+ ) + "\n"
4083
+ );
4084
+ return filePath;
4085
+ }
4086
+ async function writeImportsEslintFile(unimport2, options) {
4087
+ const globals2 = {};
4088
+ const eslintrc = { globals: globals2 };
4089
+ (await unimport2.getImports()).map((i) => i.as ?? i.name).filter(Boolean).sort().forEach((name) => {
4090
+ eslintrc.globals[name] = options.eslintrc.globalsPropValue;
4091
+ });
4092
+ await import_fs_extra7.default.writeJson(options.eslintrc.filePath, eslintrc, { spaces: 2 });
4093
+ }
4094
+ async function writePathsDeclarationFile(entrypoints) {
4095
+ const filePath = (0, import_path5.resolve)(wxt.config.typesDir, "paths.d.ts");
4096
+ const unions = entrypoints.map(
4097
+ (entry) => getEntrypointBundlePath(
4098
+ entry,
4099
+ wxt.config.outDir,
4100
+ isHtmlEntrypoint(entry) ? ".html" : ".js"
4101
+ )
4102
+ ).concat(await getPublicFiles()).map(normalizePath).map((path13) => ` | "/${path13}"`).sort().join("\n");
4103
+ const template = `// Generated by wxt
4104
+ import "wxt/browser";
4105
+
4106
+ declare module "wxt/browser" {
4107
+ export type PublicPath =
4108
+ {{ union }}
4109
+ type HtmlPublicPath = Extract<PublicPath, \`\${string}.html\`>
4110
+ export interface WxtRuntime extends Runtime.Static {
4111
+ getURL(path: PublicPath): string;
4112
+ getURL(path: \`\${HtmlPublicPath}\${string}\`): string;
4113
+ }
4114
+ }
4115
+ `;
4116
+ await writeFileIfDifferent(
4117
+ filePath,
4118
+ template.replace("{{ union }}", unions || " | never")
4119
+ );
4120
+ return filePath;
4121
+ }
4122
+ async function writeI18nDeclarationFile() {
4123
+ const filePath = (0, import_path5.resolve)(wxt.config.typesDir, "i18n.d.ts");
4124
+ const defaultLocale = wxt.config.manifest.default_locale;
4125
+ const template = `// Generated by wxt
4126
+ import "wxt/browser";
4127
+
4128
+ declare module "wxt/browser" {
4129
+ /**
4130
+ * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
4131
+ */
4132
+ interface GetMessageOptions {
4133
+ /**
4134
+ * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
4135
+ */
4136
+ escapeLt?: boolean
4137
+ }
4138
+
4139
+ export interface WxtI18n extends I18n.Static {
4140
+ {{ overrides }}
4141
+ }
4142
+ }
4143
+ `;
4144
+ let messages;
4145
+ if (defaultLocale) {
4146
+ const defaultLocalePath = import_node_path10.default.resolve(
4147
+ wxt.config.publicDir,
4148
+ "_locales",
4149
+ defaultLocale,
4150
+ "messages.json"
4151
+ );
4152
+ const content = JSON.parse(await import_fs_extra7.default.readFile(defaultLocalePath, "utf-8"));
4153
+ messages = parseI18nMessages(content);
4154
+ } else {
4155
+ messages = parseI18nMessages({});
4156
+ }
4157
+ const overrides = messages.map((message) => {
4158
+ return ` /**
4159
+ * ${message.description || "No message description."}
4160
+ *
4161
+ * "${message.message}"
4162
+ */
4163
+ getMessage(
4164
+ messageName: "${message.name}",
4165
+ substitutions?: string | string[],
4166
+ options?: GetMessageOptions,
4167
+ ): string;`;
4168
+ });
4169
+ await writeFileIfDifferent(
4170
+ filePath,
4171
+ template.replace("{{ overrides }}", overrides.join("\n"))
4172
+ );
4173
+ return filePath;
4174
+ }
4175
+ async function writeGlobalsDeclarationFile() {
4176
+ const filePath = (0, import_path5.resolve)(wxt.config.typesDir, "globals.d.ts");
4177
+ const globals2 = [...getGlobals(wxt.config), ...getEntrypointGlobals("")];
4178
+ await writeFileIfDifferent(
4179
+ filePath,
4180
+ [
4181
+ "// Generated by wxt",
4182
+ "export {}",
4183
+ "interface ImportMetaEnv {",
4184
+ ...globals2.map((global3) => ` readonly ${global3.name}: ${global3.type};`),
4185
+ "}",
4186
+ "interface ImportMeta {",
4187
+ " readonly env: ImportMetaEnv",
4188
+ "}"
4189
+ ].join("\n") + "\n"
4190
+ );
4191
+ return filePath;
4192
+ }
4193
+ async function writeMainDeclarationFile(references) {
4194
+ const dir = wxt.config.wxtDir;
4195
+ const filePath = (0, import_path5.resolve)(dir, "wxt.d.ts");
4196
+ await writeFileIfDifferent(
4197
+ filePath,
4198
+ [
4199
+ "// Generated by wxt",
4200
+ `/// <reference types="wxt/vite-builder-env" />`,
4201
+ ...references.map(
4202
+ (ref) => `/// <reference types="./${normalizePath((0, import_path5.relative)(dir, ref))}" />`
4203
+ )
4204
+ ].join("\n") + "\n"
4205
+ );
4206
+ return filePath;
4207
+ }
4208
+ async function writeTsConfigFile(mainReference) {
4209
+ const dir = wxt.config.wxtDir;
4210
+ const getTsconfigPath = (path13) => normalizePath((0, import_path5.relative)(dir, path13));
4211
+ const paths = Object.entries(wxt.config.alias).flatMap(([alias, absolutePath]) => {
4212
+ const aliasPath = getTsconfigPath(absolutePath);
4213
+ return [
4214
+ ` "${alias}": ["${aliasPath}"]`,
4215
+ ` "${alias}/*": ["${aliasPath}/*"]`
4216
+ ];
4217
+ }).join(",\n");
4218
+ await writeFileIfDifferent(
4219
+ (0, import_path5.resolve)(dir, "tsconfig.json"),
4220
+ `{
4221
+ "compilerOptions": {
4222
+ "target": "ESNext",
4223
+ "module": "ESNext",
4224
+ "moduleResolution": "Bundler",
4225
+ "noEmit": true,
4226
+ "esModuleInterop": true,
4227
+ "forceConsistentCasingInFileNames": true,
4228
+ "resolveJsonModule": true,
4229
+ "strict": true,
4230
+ "skipLibCheck": true,
4231
+ "paths": {
4232
+ ${paths}
4233
+ }
4234
+ },
4235
+ "include": [
4236
+ "${getTsconfigPath(wxt.config.root)}/**/*",
4237
+ "./${getTsconfigPath(mainReference)}"
4238
+ ],
4239
+ "exclude": ["${getTsconfigPath(wxt.config.outBaseDir)}"]
4240
+ }`
4241
+ );
4242
+ }
4243
+
4244
+ // src/core/utils/building/resolve-config.ts
4245
+ var import_c12 = require("c12");
4246
+ var import_node_path12 = __toESM(require("path"), 1);
4247
+
4248
+ // src/core/utils/cache.ts
4249
+ var import_fs_extra8 = __toESM(require("fs-extra"), 1);
4250
+ var import_path6 = require("path");
4251
+ function createFsCache(wxtDir) {
4252
+ const getPath = (key) => (0, import_path6.resolve)(wxtDir, "cache", encodeURIComponent(key));
4204
4253
  return {
4205
- name: "Vite",
4206
- version: vite.version,
4207
- async build(group) {
4208
- let entryConfig;
4209
- if (Array.isArray(group))
4210
- entryConfig = getMultiPageConfig(group);
4211
- else if (group.inputPath.endsWith(".css"))
4212
- entryConfig = getCssConfig(group);
4213
- else
4214
- entryConfig = getLibModeConfig(group);
4215
- const buildConfig = vite.mergeConfig(await getBaseConfig(), entryConfig);
4216
- const result = await vite.build(buildConfig);
4217
- return {
4218
- entrypoints: group,
4219
- chunks: getBuildOutputChunks(result)
4220
- };
4254
+ async set(key, value) {
4255
+ const path13 = getPath(key);
4256
+ await (0, import_fs_extra8.ensureDir)((0, import_path6.dirname)(path13));
4257
+ await writeFileIfDifferent(path13, value);
4221
4258
  },
4222
- async createServer(info) {
4223
- const serverConfig = {
4224
- server: {
4225
- port: info.port,
4226
- strictPort: true,
4227
- host: info.hostname,
4228
- origin: info.origin
4229
- }
4230
- };
4231
- const baseConfig = await getBaseConfig();
4232
- const viteServer = await vite.createServer(
4233
- vite.mergeConfig(baseConfig, serverConfig)
4234
- );
4235
- const server = {
4236
- async listen() {
4237
- await viteServer.listen(info.port);
4238
- },
4239
- async close() {
4240
- await viteServer.close();
4241
- },
4242
- transformHtml(...args) {
4243
- return viteServer.transformIndexHtml(...args);
4244
- },
4245
- ws: {
4246
- send(message, payload) {
4247
- return viteServer.ws.send(message, payload);
4248
- },
4249
- on(message, cb) {
4250
- viteServer.ws.on(message, cb);
4251
- }
4252
- },
4253
- watcher: viteServer.watcher
4254
- };
4255
- return server;
4259
+ async get(key) {
4260
+ const path13 = getPath(key);
4261
+ try {
4262
+ return await import_fs_extra8.default.readFile(path13, "utf-8");
4263
+ } catch {
4264
+ return void 0;
4265
+ }
4256
4266
  }
4257
4267
  };
4258
4268
  }
4259
- function getBuildOutputChunks(result) {
4260
- if ("on" in result)
4261
- throw Error("wxt does not support vite watch mode.");
4262
- if (Array.isArray(result))
4263
- return result.flatMap(({ output }) => output);
4264
- return result.output;
4265
- }
4266
- function getRollupEntry(entrypoint) {
4267
- let virtualEntrypointType;
4268
- switch (entrypoint.type) {
4269
- case "background":
4270
- case "unlisted-script":
4271
- virtualEntrypointType = entrypoint.type;
4272
- break;
4273
- case "content-script":
4274
- virtualEntrypointType = entrypoint.options.world === "MAIN" ? "content-script-main-world" : "content-script-isolated-world";
4275
- break;
4276
- }
4277
- return virtualEntrypointType ? `virtual:wxt-${virtualEntrypointType}?${entrypoint.inputPath}` : entrypoint.inputPath;
4278
- }
4279
4269
 
4280
4270
  // src/core/utils/building/resolve-config.ts
4271
+ var import_consola = __toESM(require("consola"), 1);
4281
4272
  var import_defu = __toESM(require("defu"), 1);
4282
4273
 
4283
4274
  // src/core/utils/package.ts
@@ -4301,7 +4292,7 @@ function isModuleInstalled(name) {
4301
4292
  // src/core/utils/building/resolve-config.ts
4302
4293
  var import_fs_extra10 = __toESM(require("fs-extra"), 1);
4303
4294
  var import_meta = {};
4304
- async function resolveConfig(inlineConfig, command, server) {
4295
+ async function resolveConfig(inlineConfig, command) {
4305
4296
  let userConfig = {};
4306
4297
  let userConfigMetadata;
4307
4298
  if (inlineConfig.configFile !== false) {
@@ -4317,14 +4308,14 @@ async function resolveConfig(inlineConfig, command, server) {
4317
4308
  userConfig = loadedConfig ?? {};
4318
4309
  userConfigMetadata = metadata;
4319
4310
  }
4320
- const mergedConfig = mergeInlineConfig(inlineConfig, userConfig);
4311
+ const mergedConfig = await mergeInlineConfig(inlineConfig, userConfig);
4321
4312
  const debug = mergedConfig.debug ?? false;
4322
4313
  const logger = mergedConfig.logger ?? import_consola.default;
4323
4314
  if (debug)
4324
4315
  logger.level = import_consola.LogLevels.debug;
4325
4316
  const browser = mergedConfig.browser ?? "chrome";
4326
4317
  const manifestVersion = mergedConfig.manifestVersion ?? (browser === "firefox" || browser === "safari" ? 2 : 3);
4327
- const mode = mergedConfig.mode ?? (command === "build" ? "production" : "development");
4318
+ const mode = mergedConfig.mode ?? COMMAND_MODES[command];
4328
4319
  const env = { browser, command, manifestVersion, mode };
4329
4320
  const root = import_node_path12.default.resolve(
4330
4321
  inlineConfig.root ?? userConfig.root ?? process.cwd()
@@ -4365,13 +4356,19 @@ async function resolveConfig(inlineConfig, command, server) {
4365
4356
  "~~": root
4366
4357
  }).map(([key, value]) => [key, import_node_path12.default.resolve(root, value)])
4367
4358
  );
4368
- const analysisOutputFile = import_node_path12.default.resolve(
4369
- root,
4370
- mergedConfig.analysis?.outputFile ?? "stats.html"
4371
- );
4372
- const analysisOutputDir = import_node_path12.default.dirname(analysisOutputFile);
4373
- const analysisOutputName = import_node_path12.default.parse(analysisOutputFile).name;
4374
- const finalConfig = {
4359
+ let devServerConfig;
4360
+ if (command === "serve") {
4361
+ let port = mergedConfig.dev?.server?.port;
4362
+ if (port == null || !isFinite(port)) {
4363
+ const { default: getPort, portNumbers } = await import("get-port");
4364
+ port = await getPort({ port: portNumbers(3e3, 3010) });
4365
+ }
4366
+ devServerConfig = {
4367
+ port,
4368
+ hostname: "localhost"
4369
+ };
4370
+ }
4371
+ return {
4375
4372
  browser,
4376
4373
  command,
4377
4374
  debug,
@@ -4393,109 +4390,47 @@ async function resolveConfig(inlineConfig, command, server) {
4393
4390
  srcDir,
4394
4391
  typesDir,
4395
4392
  wxtDir,
4396
- zip: resolveInternalZipConfig(root, mergedConfig),
4397
- transformManifest(manifest) {
4398
- userConfig.transformManifest?.(manifest);
4399
- inlineConfig.transformManifest?.(manifest);
4400
- },
4401
- analysis: {
4402
- enabled: mergedConfig.analysis?.enabled ?? false,
4403
- open: mergedConfig.analysis?.open ?? false,
4404
- template: mergedConfig.analysis?.template ?? "treemap",
4405
- outputFile: analysisOutputFile,
4406
- outputDir: analysisOutputDir,
4407
- outputName: analysisOutputName,
4408
- keepArtifacts: mergedConfig.analysis?.keepArtifacts ?? false
4409
- },
4393
+ zip: resolveZipConfig(root, mergedConfig),
4394
+ transformManifest: mergedConfig.transformManifest,
4395
+ analysis: resolveAnalysisConfig(root, mergedConfig),
4410
4396
  userConfigMetadata: userConfigMetadata ?? {},
4411
4397
  alias,
4412
- experimental: {
4413
- includeBrowserPolyfill: mergedConfig.experimental?.includeBrowserPolyfill ?? true
4414
- },
4415
- server,
4398
+ experimental: (0, import_defu.default)(mergedConfig.experimental, {
4399
+ includeBrowserPolyfill: true
4400
+ }),
4416
4401
  dev: {
4402
+ server: devServerConfig,
4417
4403
  reloadCommand
4418
4404
  },
4419
- hooks: mergedConfig.hooks ?? {}
4420
- };
4421
- const builder = await createViteBuilder(
4422
- inlineConfig,
4423
- userConfig,
4424
- finalConfig
4425
- );
4426
- return {
4427
- ...finalConfig,
4428
- builder
4405
+ hooks: mergedConfig.hooks ?? {},
4406
+ vite: mergedConfig.vite ?? (() => ({}))
4429
4407
  };
4430
4408
  }
4431
4409
  async function resolveManifestConfig(env, manifest) {
4432
4410
  return await (typeof manifest === "function" ? manifest(env) : manifest ?? {});
4433
4411
  }
4434
- function mergeInlineConfig(inlineConfig, userConfig) {
4435
- let imports;
4436
- if (inlineConfig.imports === false || userConfig.imports === false) {
4437
- imports = false;
4438
- } else if (userConfig.imports == null && inlineConfig.imports == null) {
4439
- imports = void 0;
4440
- } else {
4441
- imports = (0, import_defu.default)(inlineConfig.imports ?? {}, userConfig.imports ?? {});
4442
- }
4412
+ async function mergeInlineConfig(inlineConfig, userConfig) {
4413
+ const imports = inlineConfig.imports === false || userConfig.imports === false ? false : userConfig.imports == null && inlineConfig.imports == null ? void 0 : (0, import_defu.default)(inlineConfig.imports ?? {}, userConfig.imports ?? {});
4443
4414
  const manifest = async (env) => {
4444
4415
  const user = await resolveManifestConfig(env, userConfig.manifest);
4445
4416
  const inline = await resolveManifestConfig(env, inlineConfig.manifest);
4446
4417
  return (0, import_defu.default)(inline, user);
4447
4418
  };
4448
- const runner = (0, import_defu.default)(
4449
- inlineConfig.runner ?? {},
4450
- userConfig.runner ?? {}
4451
- );
4452
- const zip2 = (0, import_defu.default)(
4453
- inlineConfig.zip ?? {},
4454
- userConfig.zip ?? {}
4455
- );
4456
- const hooks = (0, import_defu.default)(
4457
- inlineConfig.hooks ?? {},
4458
- userConfig.hooks ?? {}
4459
- );
4419
+ const transformManifest = (manifest2) => {
4420
+ userConfig.transformManifest?.(manifest2);
4421
+ inlineConfig.transformManifest?.(manifest2);
4422
+ };
4423
+ const builderConfig = await mergeBuilderConfig(inlineConfig, userConfig);
4460
4424
  return {
4461
- root: inlineConfig.root ?? userConfig.root,
4462
- browser: inlineConfig.browser ?? userConfig.browser,
4463
- manifestVersion: inlineConfig.manifestVersion ?? userConfig.manifestVersion,
4464
- configFile: inlineConfig.configFile,
4465
- debug: inlineConfig.debug ?? userConfig.debug,
4466
- entrypointsDir: inlineConfig.entrypointsDir ?? userConfig.entrypointsDir,
4467
- filterEntrypoints: inlineConfig.filterEntrypoints ?? userConfig.filterEntrypoints,
4425
+ ...(0, import_defu.default)(inlineConfig, userConfig),
4426
+ // Custom merge values
4427
+ transformManifest,
4468
4428
  imports,
4469
- logger: inlineConfig.logger ?? userConfig.logger,
4470
4429
  manifest,
4471
- mode: inlineConfig.mode ?? userConfig.mode,
4472
- publicDir: inlineConfig.publicDir ?? userConfig.publicDir,
4473
- runner,
4474
- srcDir: inlineConfig.srcDir ?? userConfig.srcDir,
4475
- outDir: inlineConfig.outDir ?? userConfig.outDir,
4476
- zip: zip2,
4477
- analysis: {
4478
- ...userConfig.analysis,
4479
- ...inlineConfig.analysis
4480
- },
4481
- alias: {
4482
- ...userConfig.alias,
4483
- ...inlineConfig.alias
4484
- },
4485
- experimental: {
4486
- ...userConfig.experimental,
4487
- ...inlineConfig.experimental
4488
- },
4489
- vite: void 0,
4490
- transformManifest: void 0,
4491
- dev: {
4492
- ...userConfig.dev,
4493
- ...inlineConfig.dev
4494
- },
4495
- hooks
4430
+ ...builderConfig
4496
4431
  };
4497
4432
  }
4498
- function resolveInternalZipConfig(root, mergedConfig) {
4433
+ function resolveZipConfig(root, mergedConfig) {
4499
4434
  const downloadedPackagesDir = import_node_path12.default.resolve(root, ".wxt/local_modules");
4500
4435
  return {
4501
4436
  name: void 0,
@@ -4520,6 +4455,23 @@ function resolveInternalZipConfig(root, mergedConfig) {
4520
4455
  downloadedPackagesDir
4521
4456
  };
4522
4457
  }
4458
+ function resolveAnalysisConfig(root, mergedConfig) {
4459
+ const analysisOutputFile = import_node_path12.default.resolve(
4460
+ root,
4461
+ mergedConfig.analysis?.outputFile ?? "stats.html"
4462
+ );
4463
+ const analysisOutputDir = import_node_path12.default.dirname(analysisOutputFile);
4464
+ const analysisOutputName = import_node_path12.default.parse(analysisOutputFile).name;
4465
+ return {
4466
+ enabled: mergedConfig.analysis?.enabled ?? false,
4467
+ open: mergedConfig.analysis?.open ?? false,
4468
+ template: mergedConfig.analysis?.template ?? "treemap",
4469
+ outputFile: analysisOutputFile,
4470
+ outputDir: analysisOutputDir,
4471
+ outputName: analysisOutputName,
4472
+ keepArtifacts: mergedConfig.analysis?.keepArtifacts ?? false
4473
+ };
4474
+ }
4523
4475
  async function getUnimportOptions(wxtDir, logger, config) {
4524
4476
  if (config.imports === false)
4525
4477
  return false;
@@ -4572,6 +4524,23 @@ function logMissingDir(logger, name, expected) {
4572
4524
  )}`
4573
4525
  );
4574
4526
  }
4527
+ var COMMAND_MODES = {
4528
+ build: "production",
4529
+ serve: "development"
4530
+ };
4531
+ async function mergeBuilderConfig(inlineConfig, userConfig) {
4532
+ const vite = await import("vite").catch(() => void 0);
4533
+ if (vite) {
4534
+ return {
4535
+ vite: async (env) => {
4536
+ const resolvedInlineConfig = await inlineConfig.vite?.(env) ?? {};
4537
+ const resolvedUserConfig = await userConfig.vite?.(env) ?? {};
4538
+ return vite.mergeConfig(resolvedUserConfig, resolvedInlineConfig);
4539
+ }
4540
+ };
4541
+ }
4542
+ throw Error("Builder not found. Make sure vite is installed.");
4543
+ }
4575
4544
 
4576
4545
  // src/core/utils/building/group-entrypoints.ts
4577
4546
  function groupEntrypoints(entrypoints) {
@@ -4836,7 +4805,7 @@ function getChunkSortWeight(filename) {
4836
4805
  var import_picocolors4 = __toESM(require("picocolors"), 1);
4837
4806
 
4838
4807
  // package.json
4839
- var version = "0.17.8";
4808
+ var version = "0.17.10";
4840
4809
 
4841
4810
  // src/core/utils/log/printHeader.ts
4842
4811
  var import_consola2 = require("consola");
@@ -5010,7 +4979,7 @@ async function generateManifest(entrypoints, buildOutput) {
5010
4979
  addDevModeCsp(manifest);
5011
4980
  if (wxt.config.command === "serve")
5012
4981
  addDevModePermissions(manifest);
5013
- wxt.config.transformManifest(manifest);
4982
+ wxt.config.transformManifest?.(manifest);
5014
4983
  await wxt.hooks.callHook("build:manifestGenerated", wxt, manifest);
5015
4984
  if (wxt.config.manifestVersion === 2) {
5016
4985
  convertWebAccessibleResourcesToMv2(manifest);
@@ -5291,8 +5260,8 @@ function discoverIcons(buildOutput) {
5291
5260
  return icons.length > 0 ? Object.fromEntries(icons) : void 0;
5292
5261
  }
5293
5262
  function addDevModeCsp(manifest) {
5294
- const permission = `http://${wxt.config.server?.hostname ?? ""}/*`;
5295
- const allowedCsp = wxt.config.server?.origin ?? "http://localhost:*";
5263
+ const permission = `http://${wxt.server?.hostname ?? ""}/*`;
5264
+ const allowedCsp = wxt.server?.origin ?? "http://localhost:*";
5296
5265
  if (manifest.manifest_version === 3) {
5297
5266
  addHostPermission(manifest, permission);
5298
5267
  } else {
@@ -5305,7 +5274,7 @@ function addDevModeCsp(manifest) {
5305
5274
  ) : manifest.content_security_policy ?? "script-src 'self'; object-src 'self';"
5306
5275
  // default CSP for MV2
5307
5276
  );
5308
- if (wxt.config.server)
5277
+ if (wxt.server)
5309
5278
  csp.add("script-src", allowedCsp);
5310
5279
  if (manifest.manifest_version === 3) {
5311
5280
  manifest.content_security_policy ??= {};
@@ -5565,7 +5534,7 @@ async function internalBuild() {
5565
5534
  const target = `${wxt.config.browser}-mv${wxt.config.manifestVersion}`;
5566
5535
  wxt.logger.info(
5567
5536
  `${verb} ${import_picocolors5.default.cyan(target)} for ${import_picocolors5.default.cyan(wxt.config.mode)} with ${import_picocolors5.default.green(
5568
- `${wxt.config.builder.name} ${wxt.config.builder.version}`
5537
+ `${wxt.builder.name} ${wxt.builder.version}`
5569
5538
  )}`
5570
5539
  );
5571
5540
  const startTime = Date.now();
@@ -5856,14 +5825,59 @@ var import_async_mutex = require("async-mutex");
5856
5825
  var import_picocolors7 = __toESM(require("picocolors"), 1);
5857
5826
  var import_node_path20 = require("path");
5858
5827
  async function createServer(inlineConfig) {
5859
- const port = await getPort();
5860
- const hostname = "localhost";
5861
- const origin = `http://${hostname}:${port}`;
5862
- const serverInfo = {
5863
- port,
5864
- hostname,
5865
- origin
5866
- };
5828
+ await registerWxt("serve", inlineConfig, async (config) => {
5829
+ const { port, hostname } = config.dev.server;
5830
+ const serverInfo = {
5831
+ port,
5832
+ hostname,
5833
+ origin: `http://${hostname}:${port}`
5834
+ };
5835
+ const server2 = {
5836
+ ...serverInfo,
5837
+ get watcher() {
5838
+ return builderServer.watcher;
5839
+ },
5840
+ get ws() {
5841
+ return builderServer.ws;
5842
+ },
5843
+ currentOutput: void 0,
5844
+ async start() {
5845
+ await builderServer.listen();
5846
+ wxt.logger.success(`Started dev server @ ${serverInfo.origin}`);
5847
+ await buildAndOpenBrowser();
5848
+ },
5849
+ async stop() {
5850
+ await runner.closeBrowser();
5851
+ await builderServer.close();
5852
+ },
5853
+ async restart() {
5854
+ await closeAndRecreateRunner();
5855
+ await buildAndOpenBrowser();
5856
+ },
5857
+ transformHtml(url2, html, originalUrl) {
5858
+ return builderServer.transformHtml(url2, html, originalUrl);
5859
+ },
5860
+ reloadContentScript(payload) {
5861
+ server2.ws.send("wxt:reload-content-script", payload);
5862
+ },
5863
+ reloadPage(path13) {
5864
+ server2.ws.send("wxt:reload-page", path13);
5865
+ },
5866
+ reloadExtension() {
5867
+ server2.ws.send("wxt:reload-extension");
5868
+ },
5869
+ async restartBrowser() {
5870
+ await closeAndRecreateRunner();
5871
+ await runner.openBrowser();
5872
+ }
5873
+ };
5874
+ return server2;
5875
+ });
5876
+ const server = wxt.server;
5877
+ let [runner, builderServer] = await Promise.all([
5878
+ createExtensionRunner(),
5879
+ wxt.builder.createServer(server)
5880
+ ]);
5867
5881
  const buildAndOpenBrowser = async () => {
5868
5882
  server.currentOutput = await internalBuild();
5869
5883
  try {
@@ -5878,50 +5892,6 @@ async function createServer(inlineConfig) {
5878
5892
  await wxt.reloadConfig();
5879
5893
  runner = await createExtensionRunner();
5880
5894
  };
5881
- const server = {
5882
- ...serverInfo,
5883
- get watcher() {
5884
- return builderServer.watcher;
5885
- },
5886
- get ws() {
5887
- return builderServer.ws;
5888
- },
5889
- currentOutput: void 0,
5890
- async start() {
5891
- await builderServer.listen();
5892
- wxt.logger.success(`Started dev server @ ${serverInfo.origin}`);
5893
- await buildAndOpenBrowser();
5894
- },
5895
- async stop() {
5896
- await runner.closeBrowser();
5897
- await builderServer.close();
5898
- },
5899
- async restart() {
5900
- await closeAndRecreateRunner();
5901
- await buildAndOpenBrowser();
5902
- },
5903
- transformHtml(url2, html, originalUrl) {
5904
- return builderServer.transformHtml(url2, html, originalUrl);
5905
- },
5906
- reloadContentScript(payload) {
5907
- server.ws.send("wxt:reload-content-script", payload);
5908
- },
5909
- reloadPage(path13) {
5910
- server.ws.send("wxt:reload-page", path13);
5911
- },
5912
- reloadExtension() {
5913
- server.ws.send("wxt:reload-extension");
5914
- },
5915
- async restartBrowser() {
5916
- await closeAndRecreateRunner();
5917
- await runner.openBrowser();
5918
- }
5919
- };
5920
- await registerWxt("serve", inlineConfig, server);
5921
- let [runner, builderServer] = await Promise.all([
5922
- createExtensionRunner(),
5923
- wxt.config.builder.createServer(server)
5924
- ]);
5925
5895
  server.ws.on("wxt:background-initialized", () => {
5926
5896
  if (server.currentOutput == null)
5927
5897
  return;
@@ -5931,10 +5901,6 @@ async function createServer(inlineConfig) {
5931
5901
  server.watcher.on("all", reloadOnChange);
5932
5902
  return server;
5933
5903
  }
5934
- async function getPort() {
5935
- const { default: getPort2, portNumbers } = await import("get-port");
5936
- return await getPort2({ port: portNumbers(3e3, 3010) });
5937
- }
5938
5904
  function createFileReloader(server) {
5939
5905
  const fileChangedMutex = new import_async_mutex.Mutex();
5940
5906
  const changeQueue = [];
@@ -6209,7 +6175,7 @@ async function zip(config) {
6209
6175
  const applyTemplate = (template) => template.replaceAll("{{name}}", projectName).replaceAll("{{browser}}", wxt.config.browser).replaceAll(
6210
6176
  "{{version}}",
6211
6177
  output.manifest.version_name ?? output.manifest.version
6212
- ).replaceAll("{{manifestVersion}}", `mv${wxt.config.manifestVersion}`);
6178
+ ).replaceAll("{{mode}}", wxt.config.mode).replaceAll("{{manifestVersion}}", `mv${wxt.config.manifestVersion}`);
6213
6179
  await import_fs_extra17.default.ensureDir(wxt.config.outBaseDir);
6214
6180
  const outZipFilename = applyTemplate(wxt.config.zip.artifactTemplate);
6215
6181
  const outZipPath = import_node_path22.default.resolve(wxt.config.outBaseDir, outZipFilename);