Compare commits

..

43 Commits

Author SHA1 Message Date
Aaron d5fe4248e2 bump version for early release 2024-04-19 20:34:36 -05:00
Aaron 58140cf3e8 update test snapshot 2024-04-19 20:33:21 -05:00
Aaron 1a39f856af get it working 2024-04-19 20:31:32 -05:00
Aaron 82b769e939 feat: Support returning values from scripts executed with the scripting API 2024-04-19 16:25:17 -05:00
Aaron e47519fb76 docs: Update content script registration JSDoc
See https://github.com/wxt-dev/wxt/discussions/623
2024-04-19 15:59:14 -05:00
Ayden 171e32c272 docs: Add ElemSnap to homepage (#621) 2024-04-19 08:00:41 -05:00
wuzequanyouzi 54e5ed0bb4 chore: Update vue template config (#607)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-04-15 12:43:43 -05:00
Aaron a94d673f31 docs: Fix typo in assets page 2024-04-15 12:17:02 -05:00
yacine-bens b7f7222c26 docs: Add Facebook Video Controls to homepage (#615) 2024-04-15 12:13:06 -05:00
Can Rau aa5a9d4fa9 docs: Tix typo in entrypoints.md (#614) 2024-04-14 17:21:12 -05:00
日高 凌 77da2d6ade docs: Fix typo on example for wxt.config.ts. (#609) 2024-04-12 09:00:15 -05:00
Bas van Zanten 3119c04e10 feat: Add zip compression settings (#605) 2024-04-12 08:59:54 -05:00
couriourc c09994263e doc: Fix bad extension API examples (#608) 2024-04-12 01:01:07 -05:00
ThinkStu 2a4fa9ae5c docs: Add Fluent Read to homepage (#600)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-04-07 09:01:02 -05:00
Aaron c833d3b15b Update test snapshot after vite upgrade 2024-04-07 08:53:15 -05:00
Aaron d6e095bfce Fix type error 2024-04-07 08:47:52 -05:00
Aaron 825160a3cc chore(deps): Upgrade vite 2024-04-03 14:16:51 -05:00
Aaron e29ca33f82 Add missing cleanup from PR 2024-04-03 14:15:55 -05:00
GitHub Actions ca5a52c6f1 chore(release): v0.17.12 2024-04-03 19:04:03 +00:00
Aaron 982c69f8ae feat: Add hooks for extending vite config (#599) 2024-04-03 14:00:48 -05:00
Aaron 6fc7d9254a fix(content-script-ui): Properly assign and unassign mounted value (#598) 2024-04-03 12:38:46 -05:00
dependabot[bot] 2e0e104d48 chore(deps): bump c12 from 1.5.1 to 1.10.0 (#592)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 23:23:11 -05:00
dependabot[bot] 02430fd7b8 chore(deps-dev): bump sass from 1.69.5 to 1.72.0 (#591)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 23:23:01 -05:00
dependabot[bot] 2e6d35d193 chore(deps-dev): bump vue from 3.3.10 to 3.4.21 (#589)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 23:08:37 -05:00
dependabot[bot] 7cf412ac1f chore(deps): bump vitest from 1.2.2 to 1.4.0 (#590)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 22:15:44 -05:00
Aaron 9398cbb596 docs: Add discord server link (#593) 2024-04-01 19:13:12 -05:00
dependabot[bot] ca9b96142e chore(deps-dev): bump @aklinker1/check from 1.1.1 to 1.2.0 (#588)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 18:10:28 -05:00
Aaron db57c8e079 chore: Remove unnecssary 'Omit' types 2024-03-31 08:40:21 -05:00
GitHub Actions 7e37e11caf chore(release): v0.17.11 2024-03-31 13:22:25 +00:00
Aaron bb90029c54 fix: Resolve absolute paths from the public directory properly (#583) 2024-03-31 08:19:06 -05:00
Aaron d3b14afbce chore: Extract helper function 2024-03-30 14:49:10 -05:00
Aaron ba2197a609 chore: Add some array utils (#582) 2024-03-30 13:30:45 -05:00
Aaron 4c558d3eb6 chore: Simplify virtual module setup (#581) 2024-03-30 12:44:27 -05:00
Qiwei Yang dcd175444a docs: Add "GitHub Custom Notifier" to homepage (#580) 2024-03-30 08:37:26 -05:00
Aaron 16fc58404a docs: Fix zip.includeSources example 2024-03-30 08:37:00 -05:00
GitHub Actions 28c7e7d9ed chore(release): v0.17.10 2024-03-29 05:52:30 +00:00
Zizheng Tai 5a3b42a4b0 feat: Add dev.server.port config (#577)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-03-29 00:49:36 -05:00
Aaron c9c5d721ec chore: Refactor order of config resolution (#578) 2024-03-29 00:21:09 -05:00
Aaron 1556b9ad73 chore: Use @aklinker1/check to simplify checks setup (#550) 2024-03-25 15:15:40 -05:00
GitHub Actions ca15b1f321 chore(release): v0.17.9 2024-03-25 06:13:23 +00:00
Aaron 448436cb08 chore: Merge user config using defu (#568) 2024-03-25 01:09:52 -05:00
Aaron c9ddc3d0f2 fix: Don't override wxt.config.ts options when CLI flags are not passed (#567) 2024-03-25 00:24:56 -05:00
Guillaume 6b72f883b6 feat: Add {{mode}} Template Variable (#566)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-03-25 00:01:59 -05:00
61 changed files with 1363 additions and 599 deletions
+2 -14
View File
@@ -7,24 +7,12 @@ on:
- main
jobs:
formatting:
checks:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm format:check
lint:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm lint
type-check:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm compile
- run: pnpm check
validate-demo:
runs-on: ubuntu-22.04
steps:
+83
View File
@@ -1,5 +1,88 @@
# Changelog
## v0.17.12
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.11...v0.17.12)
### 🚀 Enhancements
- Add hooks for extending vite config ([#599](https://github.com/wxt-dev/wxt/pull/599))
### 🩹 Fixes
- **content-script-ui:** Properly assign and unassign mounted value ([#598](https://github.com/wxt-dev/wxt/pull/598))
### 📖 Documentation
- Add discord server link ([#593](https://github.com/wxt-dev/wxt/pull/593))
### 🏡 Chore
- Remove unnecssary 'Omit' types ([db57c8e](https://github.com/wxt-dev/wxt/commit/db57c8e))
- **deps-dev:** Bump @aklinker1/check from 1.1.1 to 1.2.0 ([#588](https://github.com/wxt-dev/wxt/pull/588))
- **deps-dev:** Bump vue from 3.3.10 to 3.4.21 ([#589](https://github.com/wxt-dev/wxt/pull/589))
- **deps-dev:** Bump sass from 1.69.5 to 1.72.0 ([#591](https://github.com/wxt-dev/wxt/pull/591))
## v0.17.11
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.10...v0.17.11)
### 🩹 Fixes
- Resolve absolute paths from the public directory properly ([#583](https://github.com/wxt-dev/wxt/pull/583))
### 📖 Documentation
- Fix `zip.includeSources` example ([16fc584](https://github.com/wxt-dev/wxt/commit/16fc584))
- Add "GitHub Custom Notifier" to homepage ([#580](https://github.com/wxt-dev/wxt/pull/580))
### 🏡 Chore
- Simplify virtual module setup ([#581](https://github.com/wxt-dev/wxt/pull/581))
- Add some array utils ([#582](https://github.com/wxt-dev/wxt/pull/582))
- Extract helper function ([d3b14af](https://github.com/wxt-dev/wxt/commit/d3b14af))
### ❤️ Contributors
- Qiwei Yang ([@qiweiii](http://github.com/qiweiii))
## v0.17.10
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.9...v0.17.10)
### 🚀 Enhancements
- Add `dev.server.port` config ([#577](https://github.com/wxt-dev/wxt/pull/577))
### 🏡 Chore
- Use `@aklinker1/check` to simplify checks setup ([#550](https://github.com/wxt-dev/wxt/pull/550))
- Refactor order of config resolution ([#578](https://github.com/wxt-dev/wxt/pull/578))
### ❤️ Contributors
- Zizheng Tai ([@zizhengtai](http://github.com/zizhengtai))
## v0.17.9
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.8...v0.17.9)
### 🚀 Enhancements
- Add `{{mode}}` Template Variable ([#566](https://github.com/wxt-dev/wxt/pull/566))
### 🩹 Fixes
- Don't override `wxt.config.ts` options when CLI flags are not passed ([#567](https://github.com/wxt-dev/wxt/pull/567))
### 🏡 Chore
- Merge user config using `defu` ([#568](https://github.com/wxt-dev/wxt/pull/568))
### ❤️ Contributors
- Guillaume ([@GuiEpi](http://github.com/GuiEpi))
## v0.17.8
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.7...v0.17.8)
+2
View File
@@ -29,6 +29,8 @@
<a href="https://wxt.dev/api/config.html" target="_blank">Configuration</a>
&bull;
<a href="https://wxt.dev/examples.html" target="_blank">Examples</a>
&bull;
<a href="https://discord.gg/ZFsZqGery9" target="_blank">Discord</a>
</p>
![Example CLI Output](./docs/assets/cli-output.png)
+1 -1
View File
@@ -1,4 +1,4 @@
import messages from 'public/_locales/en/messages.json';
import messages from '~/public/_locales/en/messages.json';
export default defineBackground({
// type: 'module',
+3 -3
View File
@@ -12,10 +12,10 @@ export default defineConfig({
},
],
},
alias: {
public: 'src/public',
},
zip: {
downloadPackages: ['sass'],
},
analysis: {
open: true,
},
});
@@ -21,6 +21,10 @@ const chromeExtensionIds = [
'aiakblgmlabokilgljkglggnpflljdgp', // Markdown Sticky Notes
'nomnkbngkijpffepcgbbofhcnafpkiep', // DocVersionRedirector
'ceicccfeikoipigeghddpocceifjelph', // Plex Skipper
'aelkipgppclpfimeamgmlonimflbhlgf', // GitHub Custom Notifier
'djnlaiohfaaifbibleebjggkghlmcpcj', // Fluent Read
'nhclljcpfmmaiojbhhnkpjcfmacfcian', // Facebook Video Controls
'mblkhbaakhbhiimkbcnmeciblfhmafna', // ElemSnap - Quick capture of webpage elements and conversion to images
];
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
+4 -1
View File
@@ -146,6 +146,9 @@ export default defineConfig({
],
},
socialLinks: [{ icon: 'github', link: 'https://github.com/wxt-dev/wxt' }],
socialLinks: [
{ icon: 'discord', link: 'https://discord.gg/ZFsZqGery9' },
{ icon: 'github', link: 'https://github.com/wxt-dev/wxt' },
],
},
});
+1 -1
View File
@@ -63,7 +63,7 @@ Files in the assets directory will be processed by Vite. They are imported in yo
<!-- ... -->
</head>
<body>
<img src="~/illustration.svg" />
<img src="~/assets/illustration.svg" />
<!-- ... -->
</body>
</html>
+1 -1
View File
@@ -89,7 +89,7 @@ For a full list of entrypoints and each of their options, see the [`/entrypoints
### Side Effects
You cannot use imported variables outside the `main` function is JS entrypoints. This includes options, as shown below:
You cannot use imported variables outside the `main` function in JS entrypoints. This includes options, as shown below:
```ts
// entrypoints/content.ts
+2 -2
View File
@@ -39,9 +39,9 @@ Then we can use `browser.storage` to save the install date to local storage.
```ts
// background.ts
export default defineBackground(() => {
browser.runtime.onInstall.addEventListener(({ reason }) => {
browser.runtime.onInstalled.addListener(({ reason }) => {
if (reason === 'install') {
browser.storage.local.setItem({ installDate: Date.now() });
browser.storage.local.set({ installDate: Date.now() });
}
});
});
+1 -1
View File
@@ -11,7 +11,7 @@ The manifest.json is generated at build-time based on files in the `entrypoints/
While entrypoints are generated and added to the manifest at build-time, you can customize or add to your `manifest.json` in the config file.
```ts
// wxt.config.tsentrypoint of your extension
// wxt.config.ts entrypoint of your extension
import { defineConfig } from 'wxt';
export default defineConfig({
+16 -3
View File
@@ -9,16 +9,21 @@ const hooks: WxtHooks = {
'build:manifestGenerated': vi.fn(),
'entrypoints:resolved': vi.fn(),
'entrypoints:grouped': vi.fn(),
'vite:build:extendConfig': vi.fn(),
'vite:devServer:extendConfig': vi.fn(),
};
function expectHooksToBeCalled(called: Record<keyof WxtHooks, boolean>) {
function expectHooksToBeCalled(
called: Record<keyof WxtHooks, boolean | number>,
) {
Object.keys(hooks).forEach((key) => {
const hookName = key as keyof WxtHooks;
const times = called[hookName] ? 1 : 0;
const value = called[hookName];
const times = typeof value === 'number' ? value : value ? 1 : 0;
expect(
hooks[hookName],
`Expected "${hookName}" to be called ${times} time(s)`,
).toBeCalledTimes(called[hookName] ? 1 : 0);
).toBeCalledTimes(times);
});
}
@@ -40,6 +45,8 @@ describe('Hooks', () => {
'build:manifestGenerated': false,
'entrypoints:grouped': false,
'entrypoints:resolved': true,
'vite:build:extendConfig': false,
'vite:devServer:extendConfig': false,
});
});
@@ -56,6 +63,8 @@ describe('Hooks', () => {
'build:manifestGenerated': true,
'entrypoints:grouped': true,
'entrypoints:resolved': true,
'vite:build:extendConfig': 1,
'vite:devServer:extendConfig': false,
});
});
@@ -72,6 +81,8 @@ describe('Hooks', () => {
'build:manifestGenerated': true,
'entrypoints:grouped': true,
'entrypoints:resolved': true,
'vite:build:extendConfig': 1,
'vite:devServer:extendConfig': false,
});
});
@@ -94,6 +105,8 @@ describe('Hooks', () => {
'build:manifestGenerated': true,
'entrypoints:grouped': true,
'entrypoints:resolved': true,
'vite:build:extendConfig': 2,
'vite:devServer:extendConfig': 1,
});
});
});
+4 -1
View File
@@ -272,7 +272,7 @@ describe('Output Directory Structure', () => {
.toMatchInlineSnapshot(`
".output/chrome-mv3/background.js
----------------------------------------
import { l as logHello } from "./chunks/log-bezs0tt4.js";
import { l as logHello } from "./chunks/log-BsZv2eRn.js";
function defineBackground(arg) {
if (typeof arg === "function")
return { main: arg };
@@ -362,11 +362,13 @@ describe('Output Directory Structure', () => {
function logHello(name) {
console.log(\`Hello \${name}!\`);
}
_background;
const definition = defineBackground({
main() {
logHello("background");
}
});
_background;
chrome;
function print(method, ...args) {
return;
@@ -389,6 +391,7 @@ describe('Output Directory Structure', () => {
throw err;
}
})();
_background;
"
`);
});
+25
View File
@@ -71,4 +71,29 @@ describe('Zipping', () => {
}"
`);
});
it('should correctly apply template variables for zip file names based on provided config', async () => {
const project = new TestProject({
name: 'test',
version: '1.0.0',
});
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
);
const artifactZip = '.output/test-1.0.0-firefox-development.zip';
const sourcesZip = '.output/test-1.0.0-development-sources.zip';
await project.zip({
browser: 'firefox',
mode: 'development',
zip: {
artifactTemplate: '{{name}}-{{version}}-{{browser}}-{{mode}}.zip',
sourcesTemplate: '{{name}}-{{version}}-{{mode}}-sources.zip',
},
});
expect(await project.fileExists(artifactZip)).toBe(true);
expect(await project.fileExists(sourcesZip)).toBe(true);
});
});
+6 -10
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.17.8",
"version": "0.17.13-alpha1",
"description": "Next gen framework for developing web extensions",
"engines": {
"node": ">=18",
@@ -87,14 +87,9 @@
"scripts": {
"wxt": "tsx src/cli/index.ts",
"build": "tsx scripts/build.ts",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "run-p -c -s lint:*",
"lint:eslint": "echo 'ESLint: TODO'",
"lint:package": "publint",
"compile": "run-s -c compile:*",
"compile:wxt": "tsc --noEmit",
"compile:virtual": "tsc --noEmit -p src/virtual",
"check": "run-s -c check:*",
"check:default": "check",
"check:tsc-virtual": "tsc --noEmit -p src/virtual",
"test": "vitest",
"test:coverage": "vitest run --coverage.enabled \"--coverage.include=src/**\" \"--coverage.exclude=src/core/utils/testing/**\" \"--coverage.exclude=**/*.d.ts\"",
"prepare": "simple-git-hooks",
@@ -141,11 +136,12 @@
"prompts": "^2.4.2",
"publish-browser-extension": "^2.1.3",
"unimport": "^3.4.0",
"vite": "^5.1.3",
"vite": "^5.2.8",
"web-ext-run": "^0.2.0",
"webextension-polyfill": "^0.10.0"
},
"devDependencies": {
"@aklinker1/check": "^1.1.1",
"@faker-js/faker": "^8.3.1",
"@types/fs-extra": "^11.0.4",
"@types/lodash.merge": "^4.6.9",
+575 -218
View File
File diff suppressed because it is too large Load Diff
+12 -17
View File
@@ -8,6 +8,10 @@ import { consola } from 'consola';
import pMap from 'p-map';
import os from 'node:os';
import path from 'node:path';
import {
virtualModuleNames,
virtualEntrypointModuleNames,
} from '~/core/utils/virtual-modules';
const spinnerText = 'Building WXT';
const spinner = ora(spinnerText).start();
@@ -20,12 +24,7 @@ const preset = {
dts: true,
silent: true,
sourcemap: false,
external: [
'virtual:user-unlisted-script',
'virtual:user-content-script-isolated-world',
'virtual:user-content-script-main-world',
'virtual:user-background',
],
external: virtualEntrypointModuleNames.map((name) => `virtual:user-${name}`),
} satisfies tsup.Options;
function spinnerPMap(configs: tsup.Options[]) {
@@ -74,17 +73,13 @@ const config: tsup.Options[] = [
},
{
...preset,
entry: {
'virtual/background-entrypoint': 'src/virtual/background-entrypoint.ts',
'virtual/content-script-isolated-world-entrypoint':
'src/virtual/content-script-isolated-world-entrypoint.ts',
'virtual/content-script-main-world-entrypoint':
'src/virtual/content-script-main-world-entrypoint.ts',
'virtual/mock-browser': 'src/virtual/mock-browser.ts',
'virtual/reload-html': 'src/virtual/reload-html.ts',
'virtual/unlisted-script-entrypoint':
'src/virtual/unlisted-script-entrypoint.ts',
},
entry: virtualModuleNames.reduce<Record<string, string>>(
(acc, moduleName) => {
acc[`virtual/${moduleName}`] = `src/virtual/${moduleName}.ts`;
return acc;
},
{},
),
format: ['esm'],
splitting: false,
dts: false,
+14
View File
@@ -116,6 +116,20 @@ describe('CLI', () => {
});
});
it('should respect passing --port', async () => {
const expectedPort = 3100;
mockArgv('--port', String(expectedPort));
await importCli();
expect(createServerMock).toBeCalledWith({
dev: {
server: {
port: expectedPort,
},
},
});
});
it('should respect passing --debug', async () => {
mockArgv('--debug');
await importCli();
+3 -2
View File
@@ -1,5 +1,6 @@
import { CAC, Command } from 'cac';
import consola, { LogLevels } from 'consola';
import { filterTruthy, toArray } from '~/core/utils/arrays';
import { printHeader } from '~/core/utils/log';
import { formatDuration } from '~/core/utils/time';
import { ValidationError } from '~/core/utils/validation';
@@ -56,8 +57,8 @@ export function getArrayFromFlags<T>(
flags: any,
name: string,
): T[] | undefined {
const array = [flags[name]].flat() as Array<T | undefined>;
const result = array.filter((item) => item != null) as T[];
const array = toArray<T | undefined>(flags[name]);
const result = filterTruthy(array);
return result.length ? result : undefined;
}
+9
View File
@@ -16,6 +16,7 @@ cli
.option('-c, --config <file>', 'use specified config file')
.option('-m, --mode <mode>', 'set env mode')
.option('-b, --browser <browser>', 'specify a browser')
.option('-p, --port <port>', 'specify a port for the dev server')
.option(
'-e, --filter-entrypoint <entrypoint>',
'only build specific entrypoints',
@@ -35,6 +36,14 @@ cli
configFile: flags.config,
debug: flags.debug,
filterEntrypoints: getArrayFromFlags(flags, 'filterEntrypoint'),
dev:
flags.port == null
? undefined
: {
server: {
port: parseInt(flags.port),
},
},
});
await server.start();
return { isOngoing: true };
@@ -404,4 +404,62 @@ describe('Content Script UIs', () => {
});
});
});
describe('mounted value', () => {
describe('integrated', () => {
it('should set the mounted value based on the onMounted return value', () => {
const expected = Symbol();
const ui = createIntegratedUi(new ContentScriptContext('test'), {
position: 'inline',
onMount: () => expected,
});
expect(ui.mounted).toBeUndefined();
ui.mount();
expect(ui.mounted).toBe(expected);
ui.remove();
expect(ui.mounted).toBeUndefined();
});
});
describe('iframe', () => {
it('should set the mounted value based on the onMounted return value', async () => {
const expected = Symbol();
const ui = createIframeUi(new ContentScriptContext('test'), {
page: '',
position: 'inline',
onMount: () => expected,
});
expect(ui.mounted).toBeUndefined();
ui.mount();
expect(ui.mounted).toBe(expected);
ui.remove();
expect(ui.mounted).toBeUndefined();
});
});
describe('shadow-root', () => {
it('should set the mounted value based on the onMounted return value', async () => {
const expected = Symbol();
const ui = await createShadowRootUi(new ContentScriptContext('test'), {
name: 'test',
position: 'inline',
onMount: () => expected,
});
expect(ui.mounted).toBeUndefined();
ui.mount();
expect(ui.mounted).toBe(expected);
ui.remove();
expect(ui.mounted).toBeUndefined();
});
});
});
});
+14 -4
View File
@@ -35,12 +35,15 @@ export function createIntegratedUi<TMounted>(
const remove = () => {
options.onRemove?.(mounted);
wrapper.remove();
mounted = undefined;
};
ctx.onInvalidated(remove);
return {
mounted,
get mounted() {
return mounted;
},
wrapper,
mount,
remove,
@@ -71,12 +74,15 @@ export function createIframeUi<TMounted>(
const remove = () => {
options.onRemove?.(mounted);
wrapper.remove();
mounted = undefined;
};
ctx.onInvalidated(remove);
return {
mounted,
get mounted() {
return mounted;
},
iframe,
wrapper,
mount,
@@ -116,7 +122,7 @@ export async function createShadowRootUi<TMounted>(
});
shadowHost.setAttribute('data-wxt-shadow-root', '');
let mounted: TMounted;
let mounted: TMounted | undefined;
const mount = () => {
// Add shadow root element to DOM
@@ -134,6 +140,8 @@ export async function createShadowRootUi<TMounted>(
// Remove children from uiContainer
while (uiContainer.lastChild)
uiContainer.removeChild(uiContainer.lastChild);
// Clear mounted value
mounted = undefined;
};
ctx.onInvalidated(remove);
@@ -144,7 +152,9 @@ export async function createShadowRootUi<TMounted>(
uiContainer,
mount,
remove,
mounted: mounted!,
get mounted() {
return mounted;
},
};
}
+41 -27
View File
@@ -2,23 +2,29 @@ import type * as vite from 'vite';
import {
BuildStepOutput,
Entrypoint,
InlineConfig,
ResolvedConfig,
UserConfig,
VirtualEntrypointType,
WxtBuilder,
WxtBuilderServer,
WxtDevServer,
WxtHooks,
} from '~/types';
import * as wxtPlugins from './plugins';
import {
getEntrypointBundlePath,
isHtmlEntrypoint,
} from '~/core/utils/entrypoints';
import {
VirtualEntrypointType,
VirtualModuleId,
} from '~/core/utils/virtual-modules';
import { Hookable } from 'hookable';
import { toArray } from '~/core/utils/arrays';
import { safeVarName } from '~/core/utils/strings';
export async function createViteBuilder(
inlineConfig: InlineConfig,
userConfig: UserConfig,
wxtConfig: Omit<ResolvedConfig, 'builder'>,
wxtConfig: ResolvedConfig,
hooks: Hookable<WxtHooks>,
server?: WxtDevServer,
): Promise<WxtBuilder> {
const vite = await import('vite');
@@ -26,14 +32,7 @@ export async function createViteBuilder(
* Returns the base vite config shared by all builds based on the inline and user config.
*/
const getBaseConfig = async () => {
const resolvedInlineConfig =
(await inlineConfig.vite?.(wxtConfig.env)) ?? {};
const resolvedUserConfig = (await userConfig.vite?.(wxtConfig.env)) ?? {};
const config: vite.InlineConfig = vite.mergeConfig(
resolvedUserConfig,
resolvedInlineConfig,
);
const config: vite.InlineConfig = await wxtConfig.vite(wxtConfig.env);
config.root = wxtConfig.root;
config.configFile = false;
@@ -41,6 +40,8 @@ export async function createViteBuilder(
config.mode = wxtConfig.mode;
config.build ??= {};
config.publicDir = wxtConfig.publicDir;
config.build.copyPublicDir = false;
config.build.outDir = wxtConfig.outDir;
config.build.emptyOutDir = false;
// Disable minification for the dev command
@@ -55,13 +56,10 @@ export async function createViteBuilder(
config.plugins ??= [];
config.plugins.push(
wxtPlugins.download(wxtConfig),
wxtPlugins.devHtmlPrerender(wxtConfig),
wxtPlugins.devHtmlPrerender(wxtConfig, server),
wxtPlugins.unimport(wxtConfig),
wxtPlugins.virtualEntrypoint('background', wxtConfig),
wxtPlugins.virtualEntrypoint('content-script-isolated-world', wxtConfig),
wxtPlugins.virtualEntrypoint('content-script-main-world', wxtConfig),
wxtPlugins.virtualEntrypoint('unlisted-script', wxtConfig),
wxtPlugins.devServerGlobals(wxtConfig),
wxtPlugins.resolveVirtualModules(wxtConfig),
wxtPlugins.devServerGlobals(wxtConfig, server),
wxtPlugins.tsconfigPaths(wxtConfig),
wxtPlugins.noopBackground(),
wxtPlugins.globals(wxtConfig),
@@ -90,14 +88,22 @@ export async function createViteBuilder(
plugins.push(wxtPlugins.cssEntrypoints(entrypoint, wxtConfig));
}
const iifeReturnValueName = safeVarName(entrypoint.name);
const libMode: vite.UserConfig = {
mode: wxtConfig.mode,
plugins,
esbuild: {
// Add a footer with the returned value so it can return values to `scripting.executeScript`
// Footer is added apart of esbuild to make sure it's not minified. It
// get's removed if added to `build.rollupOptions.output.footer`
// See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript#return_value
footer: iifeReturnValueName + ';',
},
build: {
lib: {
entry,
formats: ['iife'],
name: '_',
name: iifeReturnValueName,
fileName: entrypoint.name,
},
rollupOptions: {
@@ -205,6 +211,11 @@ export async function createViteBuilder(
else entryConfig = getLibModeConfig(group);
const buildConfig = vite.mergeConfig(await getBaseConfig(), entryConfig);
await hooks.callHook(
'vite:build:extendConfig',
toArray(group),
buildConfig,
);
const result = await vite.build(buildConfig);
return {
entrypoints: group,
@@ -221,9 +232,9 @@ export async function createViteBuilder(
},
};
const baseConfig = await getBaseConfig();
const viteServer = await vite.createServer(
vite.mergeConfig(baseConfig, serverConfig),
);
const finalConfig = vite.mergeConfig(baseConfig, serverConfig);
await hooks.callHook('vite:devServer:extendConfig', finalConfig);
const viteServer = await vite.createServer(finalConfig);
const server: WxtBuilderServer = {
async listen() {
@@ -277,7 +288,10 @@ function getRollupEntry(entrypoint: Entrypoint): string {
: 'content-script-isolated-world';
break;
}
return virtualEntrypointType
? `virtual:wxt-${virtualEntrypointType}?${entrypoint.inputPath}`
: entrypoint.inputPath;
if (virtualEntrypointType) {
const moduleId: VirtualModuleId = `virtual:wxt-${virtualEntrypointType}-entrypoint`;
return `${moduleId}?${entrypoint.inputPath}`;
}
return entrypoint.inputPath;
}
@@ -5,9 +5,8 @@ import path from 'node:path';
let increment = 0;
export function bundleAnalysis(
config: Omit<ResolvedConfig, 'builder'>,
): vite.Plugin {
export function bundleAnalysis(config: ResolvedConfig): vite.Plugin {
// @ts-expect-error: Vite/Rollup type mismatch, but it's fine.
return visualizer({
template: 'raw-data',
filename: path.resolve(
@@ -14,7 +14,7 @@ import { getEntrypointBundlePath } from '~/core/utils/entrypoints';
*/
export function cssEntrypoints(
entrypoint: Entrypoint,
config: Omit<ResolvedConfig, 'builder'>,
config: ResolvedConfig,
): vite.Plugin {
return {
name: 'wxt:css-entrypoint',
@@ -12,7 +12,8 @@ let reactRefreshPreamble = '';
* Pre-renders the HTML entrypoints when building the extension to connect to the dev server.
*/
export function devHtmlPrerender(
config: Omit<ResolvedConfig, 'builder'>,
config: ResolvedConfig,
server: WxtDevServer | undefined,
): vite.PluginOption {
const htmlReloadId = '@wxt/reload-html';
const resolvedHtmlReloadId = resolve(
@@ -38,7 +39,6 @@ export function devHtmlPrerender(
// Convert scripts like src="./main.tsx" -> src="http://localhost:3000/entrypoints/popup/main.tsx"
// before the paths are replaced with their bundled path
transform(code, id) {
const server = config.server;
if (
config.command !== 'serve' ||
server == null ||
@@ -68,7 +68,6 @@ export function devHtmlPrerender(
// Pass the HTML through the dev server to add dev-mode specific code
async transformIndexHtml(html, ctx) {
const server = config.server;
if (config.command !== 'serve' || server == null) return;
const originalUrl = `${server.origin}${ctx.path}`;
@@ -134,7 +133,7 @@ export function devHtmlPrerender(
}
export function pointToDevServer(
config: Omit<ResolvedConfig, 'builder'>,
config: ResolvedConfig,
server: WxtDevServer,
id: string,
document: Document,
@@ -1,22 +1,23 @@
import { Plugin } from 'vite';
import { ResolvedConfig } from '~/types';
import { ResolvedConfig, WxtDevServer } from '~/types';
/**
* Defines global constants about the dev server. Helps scripts connect to the server's web socket.
*/
export function devServerGlobals(
config: Omit<ResolvedConfig, 'builder'>,
config: ResolvedConfig,
server: WxtDevServer | undefined,
): Plugin {
return {
name: 'wxt:dev-server-globals',
config() {
if (config.server == null || config.command == 'build') return;
if (server == null || config.command == 'build') return;
return {
define: {
__DEV_SERVER_PROTOCOL__: JSON.stringify('ws:'),
__DEV_SERVER_HOSTNAME__: JSON.stringify(config.server.hostname),
__DEV_SERVER_PORT__: JSON.stringify(config.server.port),
__DEV_SERVER_HOSTNAME__: JSON.stringify(server.hostname),
__DEV_SERVER_PORT__: JSON.stringify(server.port),
},
};
},
+1 -1
View File
@@ -9,7 +9,7 @@ import { fetchCached } from '~/core/utils/network';
* @example
* import "url:https://google-tagmanager.com/gtag?id=XYZ";
*/
export function download(config: Omit<ResolvedConfig, 'builder'>): Plugin {
export function download(config: ResolvedConfig): Plugin {
return {
name: 'wxt:download',
resolveId(id) {
@@ -6,9 +6,7 @@ import type * as vite from 'vite';
* `webextension-polyfill` module to a virtual module and exporting the `chrome` global from the
* virtual module.
*/
export function excludeBrowserPolyfill(
config: Omit<ResolvedConfig, 'builder'>,
): vite.Plugin {
export function excludeBrowserPolyfill(config: ResolvedConfig): vite.Plugin {
const virtualId = 'virtual:wxt-webextension-polyfill-disabled';
return {
+1 -3
View File
@@ -2,9 +2,7 @@ import type * as vite from 'vite';
import { ResolvedConfig } from '~/types';
import { getGlobals } from '~/core/utils/globals';
export function globals(
config: Omit<ResolvedConfig, 'builder'>,
): vite.PluginOption {
export function globals(config: ResolvedConfig): vite.PluginOption {
return {
name: 'wxt:globals',
config() {
+1 -1
View File
@@ -3,7 +3,7 @@ export * from './devServerGlobals';
export * from './download';
export * from './multipageMove';
export * from './unimport';
export * from './virtualEntrypoint';
export * from './resolveVirtualModules';
export * from './tsconfigPaths';
export * from './noopBackground';
export * from './cssEntrypoints';
@@ -20,7 +20,7 @@ import { normalizePath } from '~/core/utils/paths';
*/
export function multipageMove(
entrypoints: Entrypoint[],
config: Omit<ResolvedConfig, 'builder'>,
config: ResolvedConfig,
): vite.Plugin {
return {
name: 'wxt:multipage-move',
@@ -0,0 +1,41 @@
import { Plugin } from 'vite';
import { ResolvedConfig } from '~/types';
import { normalizePath } from '~/core/utils/paths';
import {
VirtualModuleId,
virtualModuleNames,
} from '~/core/utils/virtual-modules';
import fs from 'fs-extra';
import { resolve } from 'path';
/**
* Resolve all the virtual modules to the `node_modules/wxt/dist/virtual` directory.
*/
export function resolveVirtualModules(config: ResolvedConfig): Plugin[] {
return virtualModuleNames.map((name) => {
const virtualId: `${VirtualModuleId}?` = `virtual:wxt-${name}?`;
const resolvedVirtualId = '\0' + virtualId;
return {
name: `wxt:resolve-virtual-${name}`,
resolveId(id) {
// Id doesn't start with prefix, it looks like this:
// /path/to/project/virtual:wxt-background?/path/to/project/entrypoints/background.ts
const index = id.indexOf(virtualId);
if (index === -1) return;
const inputPath = normalizePath(id.substring(index + virtualId.length));
return resolvedVirtualId + inputPath;
},
async load(id) {
if (!id.startsWith(resolvedVirtualId)) return;
const inputPath = id.replace(resolvedVirtualId, '');
const template = await fs.readFile(
resolve(config.wxtModuleDir, `dist/virtual/${name}.js`),
'utf-8',
);
return template.replace(`virtual:user-${name}`, inputPath);
},
};
});
}
@@ -1,9 +1,7 @@
import { ResolvedConfig } from '~/types';
import type * as vite from 'vite';
export function tsconfigPaths(
config: Omit<ResolvedConfig, 'builder'>,
): vite.Plugin {
export function tsconfigPaths(config: ResolvedConfig): vite.Plugin {
return {
name: 'wxt:aliases',
async config() {
+1 -3
View File
@@ -15,9 +15,7 @@ const ENABLED_EXTENSIONS = new Set([
/**
* Inject any global imports defined by unimport
*/
export function unimport(
config: Omit<ResolvedConfig, 'builder'>,
): vite.PluginOption {
export function unimport(config: ResolvedConfig): vite.PluginOption {
const options = config.imports;
if (options === false) return [];
@@ -1,39 +0,0 @@
import { Plugin } from 'vite';
import { ResolvedConfig, VirtualEntrypointType } from '~/types';
import fs from 'fs-extra';
import { resolve } from 'path';
import { normalizePath } from '~/core/utils/paths';
/**
* Wraps a user's entrypoint with a vitual version with additional logic.
*/
export function virtualEntrypoint(
type: VirtualEntrypointType,
config: Omit<ResolvedConfig, 'builder'>,
): Plugin {
const virtualId = `virtual:wxt-${type}?`;
const resolvedVirtualId = `\0${virtualId}`;
return {
name: `wxt:virtual-entrypoint`,
resolveId(id) {
// Id doesn't start with prefix, it looks like this:
// /path/to/project/virtual:background?/path/to/project/entrypoints/background.ts
const index = id.indexOf(virtualId);
if (index === -1) return;
const inputPath = normalizePath(id.substring(index + virtualId.length));
return resolvedVirtualId + inputPath;
},
async load(id) {
if (!id.startsWith(resolvedVirtualId)) return;
const inputPath = id.replace(resolvedVirtualId, '');
const template = await fs.readFile(
resolve(config.wxtModuleDir, `dist/virtual/${type}-entrypoint.js`),
'utf-8',
);
return template.replace(`virtual:user-${type}`, inputPath);
},
};
}
@@ -16,7 +16,7 @@ import { ResolvedConfig } from '~/types';
* `npm list` and inline them automatically.
*/
export function webextensionPolyfillMock(
config: Omit<ResolvedConfig, 'builder'>,
config: ResolvedConfig,
): vite.PluginOption {
return {
name: 'wxt:testing-inline-deps',
+62 -64
View File
@@ -26,7 +26,10 @@ import pc from 'picocolors';
import { relative } from 'node:path';
import { registerWxt, wxt } from './wxt';
import { unnormalizePath } from './utils/paths';
import { mapWxtOptionsToRegisteredContentScript } from './utils/content-scripts';
import {
getContentScriptJs,
mapWxtOptionsToRegisteredContentScript,
} from './utils/content-scripts';
/**
* Creates a dev server and pre-builds all the files that need to exist before loading the extension.
@@ -40,14 +43,63 @@ import { mapWxtOptionsToRegisteredContentScript } from './utils/content-scripts'
export async function createServer(
inlineConfig?: InlineConfig,
): Promise<WxtDevServer> {
const port = await getPort();
const hostname = 'localhost';
const origin = `http://${hostname}:${port}`;
const serverInfo: ServerInfo = {
port,
hostname,
origin,
};
await registerWxt('serve', inlineConfig, async (config) => {
const { port, hostname } = config.dev.server!;
const serverInfo: ServerInfo = {
port,
hostname,
origin: `http://${hostname}:${port}`,
};
// Server instance must be created first so its reference can be added to the internal config used
// to pre-render entrypoints
const server: WxtDevServer = {
...serverInfo,
get watcher() {
return builderServer.watcher;
},
get ws() {
return builderServer.ws;
},
currentOutput: undefined,
async start() {
await builderServer.listen();
wxt.logger.success(`Started dev server @ ${serverInfo.origin}`);
await buildAndOpenBrowser();
},
async stop() {
await runner.closeBrowser();
await builderServer.close();
},
async restart() {
await closeAndRecreateRunner();
await buildAndOpenBrowser();
},
transformHtml(url, html, originalUrl) {
return builderServer.transformHtml(url, html, originalUrl);
},
reloadContentScript(payload) {
server.ws.send('wxt:reload-content-script', payload);
},
reloadPage(path) {
server.ws.send('wxt:reload-page', path);
},
reloadExtension() {
server.ws.send('wxt:reload-extension');
},
async restartBrowser() {
await closeAndRecreateRunner();
await runner.openBrowser();
},
};
return server;
});
const server = wxt.server!;
let [runner, builderServer] = await Promise.all([
createExtensionRunner(),
wxt.builder.createServer(server),
]);
const buildAndOpenBrowser = async () => {
// Build after starting the dev server so it can be used to transform HTML files
@@ -74,55 +126,6 @@ export async function createServer(
runner = await createExtensionRunner();
};
// Server instance must be created first so its reference can be added to the internal config used
// to pre-render entrypoints
const server: WxtDevServer = {
...serverInfo,
get watcher() {
return builderServer.watcher;
},
get ws() {
return builderServer.ws;
},
currentOutput: undefined,
async start() {
await builderServer.listen();
wxt.logger.success(`Started dev server @ ${serverInfo.origin}`);
await buildAndOpenBrowser();
},
async stop() {
await runner.closeBrowser();
await builderServer.close();
},
async restart() {
await closeAndRecreateRunner();
await buildAndOpenBrowser();
},
transformHtml(url, html, originalUrl) {
return builderServer.transformHtml(url, html, originalUrl);
},
reloadContentScript(payload) {
server.ws.send('wxt:reload-content-script', payload);
},
reloadPage(path) {
server.ws.send('wxt:reload-page', path);
},
reloadExtension() {
server.ws.send('wxt:reload-extension');
},
async restartBrowser() {
await closeAndRecreateRunner();
await runner.openBrowser();
},
};
await registerWxt('serve', inlineConfig, server);
let [runner, builderServer] = await Promise.all([
createExtensionRunner(),
wxt.config.builder.createServer(server),
]);
// Register content scripts for the first time after the background starts up since they're not
// listed in the manifest
server.ws.on('wxt:background-initialized', () => {
@@ -137,11 +140,6 @@ export async function createServer(
return server;
}
async function getPort(): Promise<number> {
const { default: getPort, portNumbers } = await import('get-port');
return await getPort({ port: portNumbers(3000, 3010) });
}
/**
* Returns a function responsible for reloading different parts of the extension when a file
* changes.
@@ -237,7 +235,7 @@ function reloadContentScripts(steps: BuildStepOutput[], server: WxtDevServer) {
const entry = step.entrypoints;
if (Array.isArray(entry) || entry.type !== 'content-script') return;
const js = [getEntrypointBundlePath(entry, wxt.config.outDir, '.js')];
const js = getContentScriptJs(wxt.config, entry);
const cssMap = getContentScriptsCssMap(server.currentOutput, [entry]);
const css = getContentScriptCssFiles([entry], cssMap);
@@ -0,0 +1,20 @@
import { describe, it } from 'vitest';
import {
VirtualModuleId,
VirtualModuleName,
VirtualEntrypointType,
VirtualEntrypointModuleName,
} from '../virtual-modules';
describe('Virtual Modules', () => {
it('should resolve types to litteral values, not string', () => {
// @ts-expect-error
const _c: VirtualEntrypointType = '';
// @ts-expect-error
const _d: VirtualEntrypointModuleName = '';
// @ts-expect-error
const _b: VirtualModuleName = '';
// @ts-expect-error
const _a: VirtualModuleId = '';
});
});
+11
View File
@@ -21,3 +21,14 @@ export function some<T>(
if (predicate(array[i], i)) return true;
return false;
}
/**
* Convert an item or array to an array.
*/
export function toArray<T>(a: T | T[]): T[] {
return Array.isArray(a) ? a : [a];
}
export function filterTruthy<T>(array: Array<T | undefined>): T[] {
return array.filter((item) => !!item) as T[];
}
+3 -2
View File
@@ -5,6 +5,7 @@ import { dirname, resolve } from 'path';
import type { Ora } from 'ora';
import pc from 'picocolors';
import { wxt } from '../../wxt';
import { toArray } from '../arrays';
export async function buildEntrypoints(
groups: EntrypointGroup[],
@@ -13,12 +14,12 @@ export async function buildEntrypoints(
const steps: BuildStepOutput[] = [];
for (let i = 0; i < groups.length; i++) {
const group = groups[i];
const groupNames = [group].flat().map((e) => e.name);
const groupNames = toArray(group).map((e) => e.name);
const groupNameColored = groupNames.join(pc.dim(', '));
spinner.text =
pc.dim(`[${i + 1}/${groups.length}]`) + ` ${groupNameColored}`;
try {
steps.push(await wxt.config.builder.build(group));
steps.push(await wxt.builder.build(group));
} catch (err) {
spinner.stop().clear();
wxt.logger.error(err);
+1 -1
View File
@@ -37,7 +37,7 @@ export async function internalBuild(): Promise<BuildOutput> {
const target = `${wxt.config.browser}-mv${wxt.config.manifestVersion}`;
wxt.logger.info(
`${verb} ${pc.cyan(target)} for ${pc.cyan(wxt.config.mode)} with ${pc.green(
`${wxt.config.builder.name} ${wxt.config.builder.version}`,
`${wxt.builder.name} ${wxt.builder.version}`,
)}`,
);
const startTime = Date.now();
+99 -101
View File
@@ -7,14 +7,13 @@ import {
UserManifestFn,
UserManifest,
ExtensionRunnerConfig,
WxtDevServer,
WxtResolvedUnimportOptions,
Logger,
WxtCommand,
} from '~/types';
import path from 'node:path';
import { createFsCache } from '~/core/utils/cache';
import consola, { LogLevels } from 'consola';
import { createViteBuilder } from '~/core/builders/vite';
import defu from 'defu';
import { NullablyRequired } from '../types';
import { isModuleInstalled } from '../package';
@@ -30,8 +29,7 @@ import { normalizePath } from '../paths';
*/
export async function resolveConfig(
inlineConfig: InlineConfig,
command: 'build' | 'serve',
server?: WxtDevServer,
command: WxtCommand,
): Promise<ResolvedConfig> {
// Load user config
@@ -53,7 +51,7 @@ export async function resolveConfig(
// Merge it into the inline config
const mergedConfig = mergeInlineConfig(inlineConfig, userConfig);
const mergedConfig = await mergeInlineConfig(inlineConfig, userConfig);
// Apply defaults to make internal config.
@@ -65,8 +63,7 @@ export async function resolveConfig(
const manifestVersion =
mergedConfig.manifestVersion ??
(browser === 'firefox' || browser === 'safari' ? 2 : 3);
const mode =
mergedConfig.mode ?? (command === 'build' ? 'production' : 'development');
const mode = mergedConfig.mode ?? COMMAND_MODES[command];
const env: ConfigEnv = { browser, command, manifestVersion, mode };
const root = path.resolve(
@@ -113,14 +110,20 @@ export async function resolveConfig(
}).map(([key, value]) => [key, path.resolve(root, value)]),
);
const analysisOutputFile = path.resolve(
root,
mergedConfig.analysis?.outputFile ?? 'stats.html',
);
const analysisOutputDir = path.dirname(analysisOutputFile);
const analysisOutputName = path.parse(analysisOutputFile).name;
let devServerConfig: ResolvedConfig['dev']['server'];
if (command === 'serve') {
let port = mergedConfig.dev?.server?.port;
if (port == null || !isFinite(port)) {
const { default: getPort, portNumbers } = await import('get-port');
port = await getPort({ port: portNumbers(3000, 3010) });
}
devServerConfig = {
port,
hostname: 'localhost',
};
}
const finalConfig: Omit<ResolvedConfig, 'builder'> = {
return {
browser,
command,
debug,
@@ -142,42 +145,20 @@ export async function resolveConfig(
srcDir,
typesDir,
wxtDir,
zip: resolveInternalZipConfig(root, mergedConfig),
transformManifest(manifest) {
userConfig.transformManifest?.(manifest);
inlineConfig.transformManifest?.(manifest);
},
analysis: {
enabled: mergedConfig.analysis?.enabled ?? false,
open: mergedConfig.analysis?.open ?? false,
template: mergedConfig.analysis?.template ?? 'treemap',
outputFile: analysisOutputFile,
outputDir: analysisOutputDir,
outputName: analysisOutputName,
keepArtifacts: mergedConfig.analysis?.keepArtifacts ?? false,
},
zip: resolveZipConfig(root, mergedConfig),
transformManifest: mergedConfig.transformManifest,
analysis: resolveAnalysisConfig(root, mergedConfig),
userConfigMetadata: userConfigMetadata ?? {},
alias,
experimental: {
includeBrowserPolyfill:
mergedConfig.experimental?.includeBrowserPolyfill ?? true,
},
server,
experimental: defu(mergedConfig.experimental, {
includeBrowserPolyfill: true,
}),
dev: {
server: devServerConfig,
reloadCommand,
},
hooks: mergedConfig.hooks ?? {},
};
const builder = await createViteBuilder(
inlineConfig,
userConfig,
finalConfig,
);
return {
...finalConfig,
builder,
vite: mergedConfig.vite ?? (() => ({})),
};
}
@@ -193,77 +174,45 @@ async function resolveManifestConfig(
/**
* Merge the inline config and user config. Inline config is given priority. Defaults are not applied here.
*/
function mergeInlineConfig(
async function mergeInlineConfig(
inlineConfig: InlineConfig,
userConfig: UserConfig,
): NullablyRequired<InlineConfig> {
let imports: InlineConfig['imports'];
if (inlineConfig.imports === false || userConfig.imports === false) {
imports = false;
} else if (userConfig.imports == null && inlineConfig.imports == null) {
imports = undefined;
} else {
imports = defu(inlineConfig.imports ?? {}, userConfig.imports ?? {});
}
): Promise<InlineConfig> {
// Merge imports option
const imports: InlineConfig['imports'] =
inlineConfig.imports === false || userConfig.imports === false
? false
: userConfig.imports == null && inlineConfig.imports == null
? undefined
: defu(inlineConfig.imports ?? {}, userConfig.imports ?? {});
// Merge manifest option
const manifest: UserManifestFn = async (env) => {
const user = await resolveManifestConfig(env, userConfig.manifest);
const inline = await resolveManifestConfig(env, inlineConfig.manifest);
return defu(inline, user);
};
const runner: InlineConfig['runner'] = defu(
inlineConfig.runner ?? {},
userConfig.runner ?? {},
);
const zip: InlineConfig['zip'] = defu(
inlineConfig.zip ?? {},
userConfig.zip ?? {},
);
const hooks: InlineConfig['hooks'] = defu(
inlineConfig.hooks ?? {},
userConfig.hooks ?? {},
);
// Merge transformManifest option
const transformManifest: InlineConfig['transformManifest'] = (manifest) => {
userConfig.transformManifest?.(manifest);
inlineConfig.transformManifest?.(manifest);
};
// Builders
const builderConfig = await mergeBuilderConfig(inlineConfig, userConfig);
return {
root: inlineConfig.root ?? userConfig.root,
browser: inlineConfig.browser ?? userConfig.browser,
manifestVersion: inlineConfig.manifestVersion ?? userConfig.manifestVersion,
configFile: inlineConfig.configFile,
debug: inlineConfig.debug ?? userConfig.debug,
entrypointsDir: inlineConfig.entrypointsDir ?? userConfig.entrypointsDir,
filterEntrypoints:
inlineConfig.filterEntrypoints ?? userConfig.filterEntrypoints,
...defu(inlineConfig, userConfig),
// Custom merge values
transformManifest,
imports,
logger: inlineConfig.logger ?? userConfig.logger,
manifest,
mode: inlineConfig.mode ?? userConfig.mode,
publicDir: inlineConfig.publicDir ?? userConfig.publicDir,
runner,
srcDir: inlineConfig.srcDir ?? userConfig.srcDir,
outDir: inlineConfig.outDir ?? userConfig.outDir,
zip,
analysis: {
...userConfig.analysis,
...inlineConfig.analysis,
},
alias: {
...userConfig.alias,
...inlineConfig.alias,
},
experimental: {
...userConfig.experimental,
...inlineConfig.experimental,
},
vite: undefined,
transformManifest: undefined,
dev: {
...userConfig.dev,
...inlineConfig.dev,
},
hooks,
...builderConfig,
};
}
function resolveInternalZipConfig(
function resolveZipConfig(
root: string,
mergedConfig: InlineConfig,
): NullablyRequired<ResolvedConfig['zip']> {
@@ -274,6 +223,7 @@ function resolveInternalZipConfig(
artifactTemplate: '{{name}}-{{version}}-{{browser}}.zip',
sourcesRoot: root,
includeSources: [],
compressionLevel: 9,
...mergedConfig.zip,
excludeSources: [
'**/node_modules',
@@ -292,6 +242,28 @@ function resolveInternalZipConfig(
};
}
function resolveAnalysisConfig(
root: string,
mergedConfig: InlineConfig,
): NullablyRequired<ResolvedConfig['analysis']> {
const analysisOutputFile = path.resolve(
root,
mergedConfig.analysis?.outputFile ?? 'stats.html',
);
const analysisOutputDir = path.dirname(analysisOutputFile);
const analysisOutputName = path.parse(analysisOutputFile).name;
return {
enabled: mergedConfig.analysis?.enabled ?? false,
open: mergedConfig.analysis?.open ?? false,
template: mergedConfig.analysis?.template ?? 'treemap',
outputFile: analysisOutputFile,
outputDir: analysisOutputDir,
outputName: analysisOutputName,
keepArtifacts: mergedConfig.analysis?.keepArtifacts ?? false,
};
}
async function getUnimportOptions(
wxtDir: string,
logger: Logger,
@@ -360,3 +332,29 @@ function logMissingDir(logger: Logger, name: string, expected: string) {
)}`,
);
}
/**
* Map of `ConfigEnv` commands to their default modes.
*/
const COMMAND_MODES: Record<WxtCommand, string> = {
build: 'production',
serve: 'development',
};
export async function mergeBuilderConfig(
inlineConfig: InlineConfig,
userConfig: UserConfig,
): Promise<Pick<InlineConfig, 'vite'>> {
const vite = await import('vite').catch(() => void 0);
if (vite) {
return {
vite: async (env) => {
const resolvedInlineConfig = (await inlineConfig.vite?.(env)) ?? {};
const resolvedUserConfig = (await userConfig.vite?.(env)) ?? {};
return vite.mergeConfig(resolvedUserConfig, resolvedInlineConfig);
},
};
}
throw Error('Builder not found. Make sure vite is installed.');
}
+9 -1
View File
@@ -1,5 +1,6 @@
import type { Manifest, Scripting } from '~/browser';
import { ContentScriptEntrypoint } from '~/types';
import { ContentScriptEntrypoint, ResolvedConfig } from '~/types';
import { getEntrypointBundlePath } from './entrypoints';
/**
* Returns a unique and consistent string hash based on a content scripts options.
@@ -83,3 +84,10 @@ export function mapWxtOptionsToRegisteredContentScript(
world: options.world,
};
}
export function getContentScriptJs(
config: ResolvedConfig,
entrypoint: ContentScriptEntrypoint,
): string[] {
return [getEntrypointBundlePath(entrypoint, config.outDir, '.js')];
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { ResolvedConfig } from '~/types';
export function getGlobals(
config: Omit<ResolvedConfig, 'builder'>,
config: ResolvedConfig,
): Array<{ name: string; value: any; type: string }> {
return [
{
+4 -4
View File
@@ -111,7 +111,7 @@ export async function generateManifest(
if (wxt.config.command === 'serve') addDevModePermissions(manifest);
// TODO: Remove in v1
wxt.config.transformManifest(manifest);
wxt.config.transformManifest?.(manifest);
await wxt.hooks.callHook('build:manifestGenerated', wxt, manifest);
if (wxt.config.manifestVersion === 2) {
@@ -455,8 +455,8 @@ function discoverIcons(
}
function addDevModeCsp(manifest: Manifest.WebExtensionManifest): void {
const permission = `http://${wxt.config.server?.hostname ?? ''}/*`;
const allowedCsp = wxt.config.server?.origin ?? 'http://localhost:*';
const permission = `http://${wxt.server?.hostname ?? ''}/*`;
const allowedCsp = wxt.server?.origin ?? 'http://localhost:*';
if (manifest.manifest_version === 3) {
addHostPermission(manifest, permission);
@@ -473,7 +473,7 @@ function addDevModeCsp(manifest: Manifest.WebExtensionManifest): void {
"script-src 'self'; object-src 'self';", // default CSP for MV2
);
if (wxt.config.server) csp.add('script-src', allowedCsp);
if (wxt.server) csp.add('script-src', allowedCsp);
if (manifest.manifest_version === 3) {
manifest.content_security_policy ??= {};
+1 -1
View File
@@ -26,7 +26,7 @@ export async function isOnline(): Promise<boolean> {
*/
export async function fetchCached(
url: string,
config: Omit<ResolvedConfig, 'builder'>,
config: ResolvedConfig,
): Promise<string> {
let content: string = '';
+8
View File
@@ -5,6 +5,14 @@ export function kebabCaseAlphanumeric(str: string): string {
.replace(/\s+/g, '-'); // Replace spaces with hyphens
}
/**
* Return a safe variable name for a given string.
*/
export function safeVarName(str: string): string {
// _ prefix to ensure it doesn't start with a number
return '_' + kebabCaseAlphanumeric(str).replace('-', '_');
}
/**
* Removes import statements from the top of a file. Keeps import.meta and inline, async `import()`
* calls.
+4 -2
View File
@@ -270,7 +270,6 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
srcDir: fakeDir(),
typesDir: fakeDir(),
wxtDir: fakeDir(),
server: mock<WxtDevServer>(),
analysis: {
enabled: false,
open: false,
@@ -289,6 +288,7 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
name: faker.person.firstName().toLowerCase(),
downloadedPackagesDir: fakeDir(),
downloadPackages: [],
compressionLevel: 9,
},
transformManifest: () => {},
userConfigMetadata: {},
@@ -296,11 +296,11 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
experimental: {
includeBrowserPolyfill: true,
},
builder: mock(),
dev: {
reloadCommand: 'Alt+R',
},
hooks: {},
vite: () => ({}),
};
});
@@ -310,6 +310,8 @@ export const fakeWxt = fakeObjectCreator<Wxt>(() => ({
logger: mock(),
reloadConfig: vi.fn(),
pm: mock(),
server: faker.helpers.arrayElement([undefined, mock<WxtDevServer>()]),
builder: mock(),
}));
export function setFakeWxt(overrides?: DeepPartial<Wxt>) {
+37
View File
@@ -0,0 +1,37 @@
export const virtualEntrypointTypes = [
'content-script-main-world' as const,
'content-script-isolated-world' as const,
'background' as const,
'unlisted-script' as const,
];
export type VirtualEntrypointType = (typeof virtualEntrypointTypes)[0];
/**
* All the names of entrypoint files in the `src/virtual/` and `dist/virtual/` directories, minus the extension.
*/
export const virtualEntrypointModuleNames = virtualEntrypointTypes.map(
(name) => `${name}-entrypoint` as const,
);
/**
* Name of entrypoint files in the `src/virtual/` and `dist/virtual/` directories, minus the extension.
*/
export type VirtualEntrypointModuleName =
(typeof virtualEntrypointModuleNames)[0];
/**
* All the names of files in the `src/virtual/` and `dist/virtual/` directories, minus the extension.
*/
export const virtualModuleNames = [
...virtualEntrypointModuleNames,
'mock-browser' as const,
'reload-html' as const,
];
/**
* Name of files in the `src/virtual/` and `dist/virtual/` directories, minus the extension.
*/
export type VirtualModuleName = (typeof virtualModuleNames)[0];
/**
* Import alias used for importing a virtual module
*/
export type VirtualModuleId = `virtual:wxt-${VirtualModuleName}`;
+17 -5
View File
@@ -1,7 +1,15 @@
import { InlineConfig, Wxt, WxtDevServer, WxtHooks } from '~/types';
import {
InlineConfig,
ResolvedConfig,
Wxt,
WxtCommand,
WxtDevServer,
WxtHooks,
} from '~/types';
import { resolveConfig } from './utils/building';
import { createHooks } from 'hookable';
import { createWxtPackageManager } from './package-managers';
import { createViteBuilder } from './builders/vite';
/**
* Global variable set once `createWxt` is called once. Since this variable is used everywhere, this
@@ -13,12 +21,14 @@ export let wxt: Wxt;
* Create and register a global instance of the Wxt interface for use throughout the project.
*/
export async function registerWxt(
command: 'build' | 'serve',
command: WxtCommand,
inlineConfig: InlineConfig = {},
server?: WxtDevServer,
getServer?: (config: ResolvedConfig) => Promise<WxtDevServer>,
): Promise<void> {
const config = await resolveConfig(inlineConfig, command, server);
const hooks = createHooks<WxtHooks>();
const config = await resolveConfig(inlineConfig, command);
const server = await getServer?.(config);
const builder = await createViteBuilder(config, hooks, server);
const pm = await createWxtPackageManager(config.root);
wxt = {
@@ -28,9 +38,11 @@ export async function registerWxt(
return config.logger;
},
async reloadConfig() {
wxt.config = await resolveConfig(inlineConfig, command, server);
wxt.config = await resolveConfig(inlineConfig, command);
},
pm,
builder,
server,
};
// Initialize hooks
+10 -1
View File
@@ -38,6 +38,7 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
'{{version}}',
output.manifest.version_name ?? output.manifest.version,
)
.replaceAll('{{mode}}', wxt.config.mode)
.replaceAll('{{manifestVersion}}', `mv${wxt.config.manifestVersion}`);
await fs.ensureDir(wxt.config.outBaseDir);
@@ -134,7 +135,15 @@ async function zipDir(
}
}
await options?.additionalWork?.(archive);
const buffer = await archive.generateAsync({ type: 'base64' });
const buffer = await archive.generateAsync({
type: 'base64',
...(wxt.config.zip.compressionLevel === 0
? { compression: 'STORE' }
: {
compression: 'DEFLATE',
compressionOptions: { level: wxt.config.zip.compressionLevel },
}),
});
await fs.writeFile(outputPath, buffer, 'base64');
}
+2 -1
View File
@@ -8,6 +8,7 @@
import { Storage, browser } from '~/browser';
import { dequal } from 'dequal/lite';
import { logger } from './sandbox/utils/logger';
import { toArray } from './core/utils/arrays';
export const storage = createStorage();
@@ -107,7 +108,7 @@ function createStorage(): WxtStorage {
await driver.removeItem(metaKey);
} else {
const newFields = getMetaValue(await driver.getItem(metaKey));
[properties].flat().forEach((field) => delete newFields[field]);
toArray(properties).forEach((field) => delete newFields[field]);
await driver.setItem(metaKey, newFields);
}
};
+1 -1
View File
@@ -4,7 +4,7 @@ declare const __DEV_SERVER_PORT__: string;
// Globals defined by the vite-plugins/devServerGlobals.ts and utils/globals.ts
interface ImportMetaEnv {
readonly COMMAND: 'build' | 'serve';
readonly COMMAND: WxtCommand;
readonly MANIFEST_VERSION: 2 | 3;
readonly ENTRYPOINT: string;
}
+92 -26
View File
@@ -117,10 +117,11 @@ export interface InlineConfig {
*
* Available template variables:
*
* - `{{name}}` - The project's name converted to kebab-case
* - `{{version}}` - The version_name or version from the manifest
* - `{{browser}}` - The target browser from the `--browser` CLI flag
* - `{{manifestVersion}}` - Either "2" or "3"
* - <span v-pre>`{{name}}`</span> - The project's name converted to kebab-case
* - <span v-pre>`{{version}}`</span> - The version_name or version from the manifest
* - <span v-pre>`{{browser}}`</span> - The target browser from the `--browser` CLI flag
* - <span v-pre>`{{mode}}`</span> - The current mode
* - <span v-pre>`{{manifestVersion}}`</span> - Either "2" or "3"
*
* @default "{{name}}-{{version}}-{{browser}}.zip"
*/
@@ -130,10 +131,11 @@ export interface InlineConfig {
*
* Available template variables:
*
* - `{{name}}` - The project's name converted to kebab-case
* - `{{version}}` - The version_name or version from the manifest
* - `{{browser}}` - The target browser from the `--browser` CLI flag
* - `{{manifestVersion}}` - Either "2" or "3"
* - <span v-pre>`{{name}}`</span> - The project's name converted to kebab-case
* - <span v-pre>`{{version}}`</span> - The version_name or version from the manifest
* - <span v-pre>`{{browser}}`</span> - The target browser from the `--browser` CLI flag
* - <span v-pre>`{{mode}}`</span> - The current mode
* - <span v-pre>`{{manifestVersion}}`</span> - Either "2" or "3"
*
* @default "{{name}}-{{version}}-sources.zip"
*/
@@ -171,7 +173,7 @@ export interface InlineConfig {
*
* @example
* [
* "coverage", // Ignore the coverage directory in the `sourcesRoot`
* "coverage", // Include the coverage directory in the `sourcesRoot`
* ]
*/
excludeSources?: string[];
@@ -200,6 +202,14 @@ export interface InlineConfig {
* ["@scope/package-name@1.1.3", "package-name@^2"]
*/
downloadPackages?: string[];
/**
* Compression level to use when zipping files.
*
* Levels: 0 (no compression) to 9 (maximum compression).
*
* @default 9
*/
compressionLevel?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
};
/**
@@ -307,6 +317,12 @@ export interface InlineConfig {
* Config effecting dev mode only.
*/
dev?: {
server?: {
/**
* Port to run the dev server on. Defaults to the first open port from 3000 to 3010.
*/
port?: number;
};
/**
* Controls whether a custom keyboard shortcut command, `Alt+R`, is added during dev mode to
* quickly reload the extension.
@@ -342,12 +358,39 @@ export interface InlineConfig {
vite?: (env: ConfigEnv) => WxtViteConfig | Promise<WxtViteConfig>;
}
// TODO: Move into @wxt/vite-builder
export interface ResolvedConfig {
vite: (env: ConfigEnv) => WxtViteConfig | Promise<WxtViteConfig>;
}
// TODO: Move into @wxt/vite-builder
export type WxtViteConfig = Omit<
vite.UserConfig,
'root' | 'configFile' | 'mode'
>;
// TODO: Move into @wxt/vite-builder
export interface WxtHooks {
/**
* Called when WXT has created Vite's config for a build step. Useful if you
* want to add plugins or update the vite config per entrypoint group.
*
* @param entrypoints The list of entrypoints being built with the provided config.
* @param viteConfig The config that will be used for the dev server.
*/
'vite:build:extendConfig': (
entrypoints: readonly Entrypoint[],
viteConfig: vite.InlineConfig,
) => HookResult;
/**
* Called when WXT has created Vite's config for the dev server. Useful if
* you want to add plugins or update the vite config per entrypoint group.
*
* @param viteConfig The config that will be used to build the entrypoints. Can be updated by reference.
*/
'vite:devServer:extendConfig': (config: vite.InlineConfig) => HookResult;
}
export interface BuildOutput {
manifest: Manifest.WebExtensionManifest;
publicAssets: OutputAsset[];
@@ -551,8 +594,8 @@ export interface BaseContentScriptEntrypointOptions
* - `"manifest"`: The content script will be added to the `content_scripts` entry in the
* manifest. This is the normal and most well known way of registering a content script.
* - `"runtime"`: The content script's `matches` is added to `host_permissions` and you are
* responsible for using the scripting API to register the content script dynamically at
* runtime.
* responsible for using the scripting API to register/execute the content script
* dynamically at runtime.
*
* @default "manifest"
*/
@@ -694,16 +737,24 @@ export interface IsolatedWorldContentScriptDefinition
extends IsolatedWorldContentScriptEntrypointOptions {
/**
* Main function executed when the content script is loaded.
*
* When running a content script with `browser.scripting.executeScript`,
* values returned from this function will be returned in the `executeScript`
* result as well. Otherwise returning a value does nothing.
*/
main(ctx: ContentScriptContext): void | Promise<void>;
main(ctx: ContentScriptContext): any | Promise<any>;
}
export interface MainWorldContentScriptDefinition
extends MainWorldContentScriptEntrypointOptions {
/**
* Main function executed when the content script is loaded.
*
* When running a content script with `browser.scripting.executeScript`,
* values returned from this function will be returned in the `executeScript`
* result as well. Otherwise returning a value does nothing.
*/
main(): void | Promise<void>;
main(): any | Promise<any>;
}
export type ContentScriptDefinition =
@@ -720,8 +771,12 @@ export interface BackgroundDefinition extends BackgroundEntrypointOptions {
export interface UnlistedScriptDefinition extends BaseEntrypointOptions {
/**
* Main function executed when the unlisted script is ran.
*
* When running a content script with `browser.scripting.executeScript`,
* values returned from this function will be returned in the `executeScript`
* result as well. Otherwise returning a value does nothing.
*/
main(): void | Promise<void>;
main(): any | Promise<any>;
}
/**
@@ -772,7 +827,7 @@ export interface ConfigEnv {
/**
* The command used to run WXT. `"serve"` during development and `"build"` for any other command.
*/
command: 'build' | 'serve';
command: WxtCommand;
/**
* Browser passed in from the CLI via the `-b` or `--browser` flag. Defaults to `"chrome"` when not passed.
*/
@@ -785,6 +840,8 @@ export interface ConfigEnv {
manifestVersion: 2 | 3;
}
export type WxtCommand = 'build' | 'serve';
/**
* Configure how the browser starts up.
*/
@@ -998,6 +1055,14 @@ export interface Wxt {
* Package manager utilities.
*/
pm: WxtPackageManager;
/**
* If the dev server was started, it will be availble.
*/
server?: WxtDevServer;
/**
* The module in charge of executing all the build steps.
*/
builder: WxtBuilder;
}
export interface ResolvedConfig {
@@ -1016,7 +1081,7 @@ export interface ResolvedConfig {
*/
wxtModuleDir: string;
mode: string;
command: 'build' | 'serve';
command: WxtCommand;
browser: TargetBrowser;
manifestVersion: TargetManifestVersion;
env: ConfigEnv;
@@ -1024,7 +1089,6 @@ export interface ResolvedConfig {
imports: false | WxtResolvedUnimportOptions;
manifest: UserManifest;
fsCache: FsCache;
server?: WxtDevServer;
runnerConfig: C12ResolvedConfig<ExtensionRunnerConfig>;
zip: {
name?: string;
@@ -1035,8 +1099,12 @@ export interface ResolvedConfig {
sourcesRoot: string;
downloadedPackagesDir: string;
downloadPackages: string[];
compressionLevel: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
};
transformManifest: (manifest: Manifest.WebExtensionManifest) => void;
/**
* @deprecated Use `build:manifestGenerated` hook instead.
*/
transformManifest?: (manifest: Manifest.WebExtensionManifest) => void;
analysis: {
enabled: boolean;
open: boolean;
@@ -1057,11 +1125,15 @@ export interface ResolvedConfig {
experimental: {
includeBrowserPolyfill: boolean;
};
builder: WxtBuilder;
dev: {
/** Only defined during dev command */
server?: {
port: number;
hostname: string;
};
reloadCommand: string | false;
};
hooks: Partial<WxtHooks>;
hooks: NestedHooks<WxtHooks>;
}
export interface FsCache {
@@ -1074,12 +1146,6 @@ export interface ExtensionRunner {
closeBrowser(): Promise<void>;
}
export type VirtualEntrypointType =
| 'content-script-main-world'
| 'content-script-isolated-world'
| 'background'
| 'unlisted-script';
export type EslintGlobalsPropValue =
| boolean
| 'readonly'
+1 -1
View File
@@ -1,4 +1,4 @@
import definition from 'virtual:user-background';
import definition from 'virtual:user-background-entrypoint';
import { setupWebSocket } from './utils/setup-web-socket';
import { logger } from '../sandbox/utils/logger';
import { browser } from 'wxt/browser';
@@ -1,17 +1,24 @@
import definition from 'virtual:user-content-script-isolated-world';
import definition from 'virtual:user-content-script-isolated-world-entrypoint';
import { logger } from '../sandbox/utils/logger';
import { ContentScriptContext } from 'wxt/client';
(async () => {
const result = (async () => {
try {
const { main, ...options } = definition;
const ctx = new ContentScriptContext(import.meta.env.ENTRYPOINT, options);
await main(ctx);
return await main(ctx);
} catch (err) {
logger.error(
`The content script "${import.meta.env.ENTRYPOINT}" crashed on startup!`,
err,
);
throw err;
}
})();
// Return the main function's result to the background when executed via the
// scripting API. Default export causes the IIFE to return a value.
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript#return_value
// Tested on both Chrome and Firefox
export default result;
@@ -1,14 +1,21 @@
import definition from 'virtual:user-content-script-main-world';
import definition from 'virtual:user-content-script-main-world-entrypoint';
import { logger } from '../sandbox/utils/logger';
(async () => {
const result = (async () => {
try {
const { main } = definition;
await main();
return await main();
} catch (err) {
logger.error(
`The content script "${import.meta.env.ENTRYPOINT}" crashed on startup!`,
err,
);
throw err;
}
})();
// Return the main function's result to the background when executed via the
// scripting API. Default export causes the IIFE to return a value.
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript#return_value
// Tested on both Chrome and Firefox
export default result;
+10 -3
View File
@@ -1,13 +1,20 @@
import definition from 'virtual:user-unlisted-script';
import definition from 'virtual:user-unlisted-script-entrypoint';
import { logger } from '../sandbox/utils/logger';
(async () => {
const result = (async () => {
try {
await definition.main();
return await definition.main();
} catch (err) {
logger.error(
`The unlisted script "${import.meta.env.ENTRYPOINT}" crashed on startup!`,
err,
);
throw err;
}
})();
// Return the main function's result to the background when executed via the
// scripting API. Default export causes the IIFE to return a value.
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript#return_value
// Tested on both Chrome and Firefox
export default result;
+4 -4
View File
@@ -1,11 +1,11 @@
// Types required to make the virtual modules happy.
declare module 'virtual:user-background' {
declare module 'virtual:user-background-entrypoint' {
const definition: { main: () => void };
export default definition;
}
declare module 'virtual:user-content-script-isolated-world' {
declare module 'virtual:user-content-script-isolated-world-entrypoint' {
const definition: {
main: (
ctx: import('wxt/client').ContentScriptContext,
@@ -14,12 +14,12 @@ declare module 'virtual:user-content-script-isolated-world' {
export default definition;
}
declare module 'virtual:user-content-script-main-world' {
declare module 'virtual:user-content-script-main-world-entrypoint' {
const definition: { main: () => void | Promise<void> };
export default definition;
}
declare module 'virtual:user-unlisted-script' {
declare module 'virtual:user-unlisted-script-entrypoint' {
const definition: { main: () => void | Promise<void> };
export default definition;
}
+4
View File
@@ -10,5 +10,9 @@ export default defineConfig({
},
vite: () => ({
plugins: [vue()],
build: {
// Enabling sourcemaps with Vue during development is known to cause problems with Vue
sourcemap: false,
},
}),
});