Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 63f507ecf5 | |||
| 0a88955394 | |||
| 291d25b675 | |||
| 6f902cc598 | |||
| fde488ac82 | |||
| 0873c24ad8 | |||
| 7c02497148 |
@@ -74,3 +74,6 @@ jobs:
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: pnpm build
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
env:
|
||||
# Debug Vite 5's deprecated CJS support
|
||||
VITE_CJS_TRACE: true
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# Changelog
|
||||
|
||||
## v0.12.0
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.11.2...v0.12.0)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- ⚠️ Add support for "main world" content scripts ([#284](https://github.com/wxt-dev/wxt/pull/284))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Only use type imports for Vite ([#278](https://github.com/wxt-dev/wxt/pull/278))
|
||||
- Throw error when no entrypoints are found ([#283](https://github.com/wxt-dev/wxt/pull/283))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Improve content script UI guide ([#272](https://github.com/wxt-dev/wxt/pull/272))
|
||||
- Fix dead links ([291d25b](https://github.com/wxt-dev/wxt/commit/291d25b))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Convert WXT CLI to an ESM binary ([#279](https://github.com/wxt-dev/wxt/pull/279))
|
||||
|
||||
#### ⚠️ Breaking Changes
|
||||
|
||||
- ⚠️ Add support for "main world" content scripts ([#284](https://github.com/wxt-dev/wxt/pull/284))
|
||||
|
||||
## v0.11.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.11.1...v0.11.2)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
require('../dist/cli.cjs');
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
import '../dist/cli.js';
|
||||
@@ -0,0 +1,8 @@
|
||||
export default defineContentScript({
|
||||
matches: ['*://*/*'],
|
||||
world: 'MAIN',
|
||||
|
||||
main() {
|
||||
console.log(`Hello from ${location.hostname}!`);
|
||||
},
|
||||
});
|
||||
@@ -11,13 +11,13 @@ To setup your test environment for auto-imports, see [Testing](/guide/testing).
|
||||
Some WXT APIs can be used without importing them:
|
||||
|
||||
- [`browser`](/api/wxt/browser/variables/browser) from `wxt/browser`, a small wrapper around `webextension-polyfill`
|
||||
- [`defineContentScript`](/api/wxt/client/functions/defineContentScript) from `wxt/client`
|
||||
- [`defineBackground`](/api/wxt/client/functions/defineBackground) from `wxt/client`
|
||||
- [`createContentScriptUi`](/api/wxt/client/functions/createContentScriptUi) from `wxt/client`
|
||||
- [`defineContentScript`](/api/wxt/sandbox/functions/defineContentScript) from `wxt/sandbox`
|
||||
- [`defineBackground`](/api/wxt/sandbox/functions/defineBackground) from `wxt/sandbox`
|
||||
- [`defineUnlistedScript`](/api/wxt/sandbox/functions/defineUnlistedScript) from `wxt/sandbox`
|
||||
- [`createContentScriptUi`](/api/wxt/client/functions/createContentScriptUi) from `wxt/client`
|
||||
- [`fakeBrowser`](/api/wxt/testing/variables/fakeBrowser) from `wxt/testing`
|
||||
|
||||
And more. All `wxt/*` APIs can be used without imports.
|
||||
And more!
|
||||
|
||||
## Project Auto-imports
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ Each has their own set of advantages and disadvantages.
|
||||
|
||||
## Integrated
|
||||
|
||||
Integrated content script UIs use the page's CSS to inject a UI that looks like it's apart of the page.
|
||||
Integrated content script UIs are injected alongside the content of a page. This means that they are affected by CSS on that page.
|
||||
|
||||
WXT doesn't provide any utils for mounting integrated UIs yet. Here are some examples for setting up integrated UIs.
|
||||
You can control how CSS is injected for an integrated content script UI with the [`cssInjectionMode`](/api/wxt/interfaces/ContentScriptBaseDefinition#cssinjectionmode) property.
|
||||
|
||||
:::code-group
|
||||
|
||||
@@ -147,14 +147,14 @@ export default defineContentScript({
|
||||
|
||||
## ShadowRoot
|
||||
|
||||
Often in web extensions, you don't want your content script's CSS effecting the page, or vise-versa. The [`ShadowRoot` API](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot) is ideal for this. It isolates an element's style from the page's style.
|
||||
Often in web extensions, you don't want your content script's CSS affecting the page, or vise-versa. The [`ShadowRoot`](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot) API is ideal for this.
|
||||
|
||||
WXT provides a helper function, [`createContentScriptUi`](/api/wxt/client/functions/createContentScriptUi), that abstracts all the `ShadowRoot` setup away, making it easy to create UIs with isolated styles.
|
||||
WXT provides a helper function, [`createContentScriptUi`](/api/wxt/client/functions/createContentScriptUi), that abstracts all the `ShadowRoot` setup away, making it easy to create UIs with isolated CSS.
|
||||
|
||||
To use `createContentScriptUi`, follow these steps:
|
||||
|
||||
1. Import your CSS file at the top of your content script
|
||||
2. Set `cssInjectionMode: "ui"` inside `defineContentScript`
|
||||
2. Set [`cssInjectionMode: "ui"`](/api/wxt/interfaces/ContentScriptBaseDefinition#cssinjectionmode) inside `defineContentScript`
|
||||
3. Define your UI with `createContentScriptUi()`
|
||||
4. Mount the UI so it is visible to users
|
||||
|
||||
@@ -194,7 +194,7 @@ See the [API Reference](/api/wxt/client/functions/createContentScriptUi) for the
|
||||
`createContentScriptUi` supports TailwindCSS out of the box! When importing the styles, just import the main CSS file containing the `@tailwind` directives, and everything will just work :+1:.
|
||||
:::
|
||||
|
||||
When using a frontend framework for your UI, you'll need to unmount the app when the UI is removed. When defining the UI, return an app reference from the `mount` option and pass in a custom `onRemoved` option:
|
||||
When using a frontend framework for your UI, you'll need to unmount the app when the UI is removed. This is accomplished by returning an app reference from the `mount` option and by passing in a custom `onRemoved` option:
|
||||
|
||||
:::code-group
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ describe('Auto Imports', () => {
|
||||
const createContentScriptIframe: typeof import('wxt/client')['createContentScriptIframe']
|
||||
const createContentScriptUi: typeof import('wxt/client')['createContentScriptUi']
|
||||
const createStorage: typeof import('wxt/storage')['createStorage']
|
||||
const defineBackground: typeof import('wxt/client')['defineBackground']
|
||||
const defineBackground: typeof import('wxt/sandbox')['defineBackground']
|
||||
const defineConfig: typeof import('wxt')['defineConfig']
|
||||
const defineContentScript: typeof import('wxt/client')['defineContentScript']
|
||||
const defineContentScript: typeof import('wxt/sandbox')['defineContentScript']
|
||||
const defineDriver: typeof import('wxt/storage')['defineDriver']
|
||||
const defineUnlistedScript: typeof import('wxt/sandbox')['defineUnlistedScript']
|
||||
const fakeBrowser: typeof import('wxt/testing')['fakeBrowser']
|
||||
|
||||
@@ -174,6 +174,7 @@ describe('Manifest Content', () => {
|
||||
describe('icons', () => {
|
||||
it('should auto-discover icons with the correct name', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
project.addFile('public/icon-16.png');
|
||||
project.addFile('public/icon/32.png');
|
||||
project.addFile('public/icon@48w.png');
|
||||
@@ -196,6 +197,7 @@ describe('Manifest Content', () => {
|
||||
|
||||
it('should return undefined when no icons are found', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
project.addFile('public/logo.png');
|
||||
project.addFile('public/icon.jpeg');
|
||||
|
||||
@@ -207,6 +209,7 @@ describe('Manifest Content', () => {
|
||||
|
||||
it('should allow icons to be overwritten from the wxt.config.ts file', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
project.addFile('public/icon-16.png');
|
||||
project.addFile('public/icon-32.png');
|
||||
project.addFile('public/logo-16.png');
|
||||
@@ -524,6 +527,7 @@ describe('Manifest Content', () => {
|
||||
|
||||
it('should respect the transformManifest option', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
project.addFile(
|
||||
'wxt.config.ts',
|
||||
`import { defineConfig } from 'wxt';
|
||||
@@ -587,6 +591,7 @@ describe('Manifest Content', () => {
|
||||
const project = new TestProject({
|
||||
version: '1.0.0-alpha1',
|
||||
});
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
|
||||
await project.build({ browser, manifestVersion });
|
||||
const manifest = await project.getOutputManifest(
|
||||
@@ -604,6 +609,7 @@ describe('Manifest Content', () => {
|
||||
const project = new TestProject({
|
||||
version: '1.0.0-alpha1',
|
||||
});
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
|
||||
await project.build({ browser, manifestVersion });
|
||||
const manifest = await project.getOutputManifest(
|
||||
@@ -626,6 +632,7 @@ describe('Manifest Content', () => {
|
||||
const project = new TestProject({
|
||||
version: '1.0.0.1',
|
||||
});
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
|
||||
await project.build({ browser, manifestVersion });
|
||||
const manifest = await project.getOutputManifest(
|
||||
@@ -640,6 +647,7 @@ describe('Manifest Content', () => {
|
||||
|
||||
it('should respect the action field in the manifest without a popup', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
project.setConfigFileConfig({
|
||||
manifest: {
|
||||
action: {
|
||||
|
||||
@@ -7,13 +7,18 @@ describe('Output Directory Structure', () => {
|
||||
project.addFile('entrypoints/.DS_Store');
|
||||
project.addFile('entrypoints/.hidden1/index.html');
|
||||
project.addFile('entrypoints/.hidden2.html');
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
|
||||
await project.build();
|
||||
|
||||
expect(await project.serializeOutput()).toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{"manifest_version":3,"name":"E2E Extension","description":"Example description","version":"0.0.0"}"
|
||||
{"manifest_version":3,"name":"E2E Extension","description":"Example description","version":"0.0.0"}
|
||||
================================================================================
|
||||
.output/chrome-mv3/unlisted.html
|
||||
----------------------------------------
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -211,6 +216,7 @@ describe('Output Directory Structure', () => {
|
||||
|
||||
it("should output to a custom directory when overriding 'outDir'", async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
project.setConfigFileConfig({
|
||||
outDir: 'dist',
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { TestProject } from '../utils';
|
||||
describe('TypeScript Project', () => {
|
||||
it('should generate defined constants correctly', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
|
||||
await project.build();
|
||||
|
||||
@@ -58,6 +59,7 @@ describe('TypeScript Project', () => {
|
||||
|
||||
it('should augment the types for browser.i18n.getMessage', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
project.addFile(
|
||||
'public/_locales/en/messages.json',
|
||||
JSON.stringify({
|
||||
@@ -216,6 +218,7 @@ describe('TypeScript Project', () => {
|
||||
|
||||
it('should reference all the required types in a single declaration file', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
|
||||
await project.build();
|
||||
|
||||
@@ -235,6 +238,7 @@ describe('TypeScript Project', () => {
|
||||
|
||||
it('should generate a TSConfig file for the project', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
|
||||
await project.build();
|
||||
|
||||
@@ -275,6 +279,7 @@ describe('TypeScript Project', () => {
|
||||
|
||||
it('should generate correct path aliases for a custom srcDir', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('src/entrypoints/unlisted.html');
|
||||
project.setConfigFileConfig({
|
||||
srcDir: 'src',
|
||||
});
|
||||
@@ -318,6 +323,7 @@ describe('TypeScript Project', () => {
|
||||
|
||||
it('should add additional path aliases listed in the alias config, preventing defaults from being overridden', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('src/entrypoints/unlisted.html');
|
||||
project.setConfigFileConfig({
|
||||
srcDir: 'src',
|
||||
alias: {
|
||||
|
||||
@@ -61,6 +61,7 @@ describe('User Config', () => {
|
||||
|
||||
it('should merge inline and user config based manifests', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/unlisted.html');
|
||||
project.addFile(
|
||||
'wxt.config.ts',
|
||||
`import { defineConfig } from 'wxt';
|
||||
@@ -79,8 +80,8 @@ describe('User Config', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
const output = await project.serializeOutput();
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
expect(await project.serializeFile('.output/chrome-mv3/manifest.json'))
|
||||
.toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{"manifest_version":3,"name":"E2E Extension","description":"Example description","version":"0.0.0","example_customization":["3","build","production","chrome"]}"
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"type": "module",
|
||||
"version": "0.11.2",
|
||||
"version": "0.12.0",
|
||||
"description": "Next gen framework for developing web extensions",
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
@@ -30,7 +30,7 @@
|
||||
"bin",
|
||||
"dist"
|
||||
],
|
||||
"bin": "./bin/wxt.cjs",
|
||||
"bin": "./bin/wxt.mjs",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
||||
+7
-4
@@ -23,7 +23,8 @@ const preset: tsup.Options = {
|
||||
external: [
|
||||
'vite',
|
||||
'virtual:user-unlisted-script',
|
||||
'virtual:user-content-script',
|
||||
'virtual:user-content-script-isolated-world',
|
||||
'virtual:user-content-script-main-world',
|
||||
'virtual:user-background',
|
||||
],
|
||||
};
|
||||
@@ -76,8 +77,10 @@ const config: tsup.Options[] = [
|
||||
...preset,
|
||||
entry: {
|
||||
'virtual/background-entrypoint': 'src/virtual/background-entrypoint.ts',
|
||||
'virtual/content-script-entrypoint':
|
||||
'src/virtual/content-script-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':
|
||||
@@ -93,7 +96,7 @@ const config: tsup.Options[] = [
|
||||
entry: {
|
||||
cli: 'src/cli.ts',
|
||||
},
|
||||
format: ['cjs'],
|
||||
format: ['esm'],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './content-script-context';
|
||||
export * from './content-script-ui';
|
||||
export * from './content-script-iframe';
|
||||
export * from './define-content-script';
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Any runtime APIs that use the web extension APIs.
|
||||
*
|
||||
* @module wxt/client
|
||||
*/
|
||||
export * from './define-background';
|
||||
export * from './content-scripts';
|
||||
export {
|
||||
ContentScriptOverlayAlignment,
|
||||
|
||||
@@ -86,7 +86,7 @@ export interface ContentScriptModalPositioningOptions {
|
||||
/**
|
||||
* Choose between `"inline"`, `"overlay"`, or `"modal" `types.
|
||||
*
|
||||
* 
|
||||
* 
|
||||
*/
|
||||
export type ContentScriptPositioningOptions =
|
||||
| ContentScriptInlinePositioningOptions
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
InlineConfig,
|
||||
InternalConfig,
|
||||
UserConfig,
|
||||
VirtualEntrypointType,
|
||||
WxtBuilder,
|
||||
WxtBuilderServer,
|
||||
} from '~/types';
|
||||
@@ -46,7 +47,8 @@ export async function craeteViteBuilder(
|
||||
wxtPlugins.devHtmlPrerender(wxtConfig),
|
||||
wxtPlugins.unimport(wxtConfig),
|
||||
wxtPlugins.virtualEntrypoint('background', wxtConfig),
|
||||
wxtPlugins.virtualEntrypoint('content-script', wxtConfig),
|
||||
wxtPlugins.virtualEntrypoint('content-script-isolated-world', wxtConfig),
|
||||
wxtPlugins.virtualEntrypoint('content-script-main-world', wxtConfig),
|
||||
wxtPlugins.virtualEntrypoint('unlisted-script', wxtConfig),
|
||||
wxtPlugins.devServerGlobals(wxtConfig),
|
||||
wxtPlugins.tsconfigPaths(wxtConfig),
|
||||
@@ -65,13 +67,21 @@ export async function craeteViteBuilder(
|
||||
* Return the basic config for building an entrypoint in [lib mode](https://vitejs.dev/guide/build.html#library-mode).
|
||||
*/
|
||||
const getLibModeConfig = (entrypoint: Entrypoint): vite.InlineConfig => {
|
||||
const isVirtual = [
|
||||
'background',
|
||||
'content-script',
|
||||
'unlisted-script',
|
||||
].includes(entrypoint.type);
|
||||
const entry = isVirtual
|
||||
? `virtual:wxt-${entrypoint.type}?${entrypoint.inputPath}`
|
||||
let virtualEntrypointType: VirtualEntrypointType | undefined;
|
||||
switch (entrypoint.type) {
|
||||
case 'background':
|
||||
case 'unlisted-script':
|
||||
virtualEntrypointType = entrypoint.type;
|
||||
break;
|
||||
case 'content-script':
|
||||
virtualEntrypointType =
|
||||
entrypoint.options.world === 'MAIN'
|
||||
? 'content-script-main-world'
|
||||
: 'content-script-isolated-world';
|
||||
break;
|
||||
}
|
||||
const entry = virtualEntrypointType
|
||||
? `virtual:wxt-${virtualEntrypointType}?${entrypoint.inputPath}`
|
||||
: entrypoint.inputPath;
|
||||
|
||||
const plugins: NonNullable<vite.UserConfig['plugins']> = [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as vite from 'vite';
|
||||
import type * as vite from 'vite';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
|
||||
let increment = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as vite from 'vite';
|
||||
import type * as vite from 'vite';
|
||||
import { Entrypoint, InternalConfig } from '~/types';
|
||||
import { getEntrypointBundlePath } from '~/core/utils/entrypoints';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as vite from 'vite';
|
||||
import type * as vite from 'vite';
|
||||
import { InternalConfig } from '~/types';
|
||||
import { getEntrypointName } from '~/core/utils/entrypoints';
|
||||
import { parseHTML } from 'linkedom';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as vite from 'vite';
|
||||
import type * as vite from 'vite';
|
||||
import { EntrypointGroup } from '~/types';
|
||||
import { getEntrypointGlobals } from '~/core/utils/globals';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { InternalConfig } from '~/types';
|
||||
import * as vite from 'vite';
|
||||
import type * as vite from 'vite';
|
||||
|
||||
/**
|
||||
* Apply the experimental config for disabling the polyfill. It works by aliasing the
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as vite from 'vite';
|
||||
import type * as vite from 'vite';
|
||||
import { InternalConfig } from '~/types';
|
||||
import { getGlobals } from '~/core/utils/globals';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as vite from 'vite';
|
||||
import type * as vite from 'vite';
|
||||
import { Entrypoint, InternalConfig } from '~/types';
|
||||
import { dirname, extname, resolve, join } from 'node:path';
|
||||
import { getEntrypointBundlePath } from '~/core/utils/entrypoints';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { InternalConfig } from '~/types';
|
||||
import * as vite from 'vite';
|
||||
import type * as vite from 'vite';
|
||||
|
||||
export function tsconfigPaths(
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createUnimport } from 'unimport';
|
||||
import { InternalConfig } from '~/types';
|
||||
import { getUnimportOptions } from '~/core/utils/unimport';
|
||||
import * as vite from 'vite';
|
||||
import type * as vite from 'vite';
|
||||
import { extname } from 'path';
|
||||
|
||||
const ENABLED_EXTENSIONS = new Set([
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Plugin } from 'vite';
|
||||
import { Entrypoint, InternalConfig } from '~/types';
|
||||
import { InternalConfig, VirtualEntrypointType } from '~/types';
|
||||
import fs from 'fs-extra';
|
||||
import { resolve } from 'path';
|
||||
import { normalizePath } from '~/core/utils/paths';
|
||||
@@ -8,7 +8,7 @@ import { normalizePath } from '~/core/utils/paths';
|
||||
* Wraps a user's entrypoint with a vitual version with additional logic.
|
||||
*/
|
||||
export function virtualEntrypoint(
|
||||
type: Entrypoint['type'],
|
||||
type: VirtualEntrypointType,
|
||||
config: Omit<InternalConfig, 'builder'>,
|
||||
): Plugin {
|
||||
const virtualId = `virtual:wxt-${type}?`;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from 'node:path';
|
||||
import * as vite from 'vite';
|
||||
import type * as vite from 'vite';
|
||||
import { InternalConfig } from '~/types';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as vite from 'vite';
|
||||
import type * as vite from 'vite';
|
||||
|
||||
/**
|
||||
* Add all deps that import `webextension-polyfill` to `test.server.deps.inline`.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineBackground } from '~/client';
|
||||
import { defineBackground } from '~/sandbox';
|
||||
|
||||
export default defineBackground({
|
||||
main() {},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineContentScript } from '~/client';
|
||||
import { defineContentScript } from '~/sandbox';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineBackground } from '~/client';
|
||||
import { defineBackground } from '~/sandbox';
|
||||
|
||||
export const a = {};
|
||||
|
||||
|
||||
@@ -244,15 +244,15 @@ describe('findEntrypoints', () => {
|
||||
);
|
||||
|
||||
it("should include a virtual background script so dev reloading works when there isn't a background entrypoint defined by the user", async () => {
|
||||
globMock.mockResolvedValueOnce([]);
|
||||
globMock.mockResolvedValueOnce(['popup.html']);
|
||||
|
||||
const entrypoints = await findEntrypoints({
|
||||
...config,
|
||||
command: 'serve',
|
||||
});
|
||||
|
||||
expect(entrypoints).toHaveLength(1);
|
||||
expect(entrypoints[0]).toEqual({
|
||||
expect(entrypoints).toHaveLength(2);
|
||||
expect(entrypoints).toContainEqual({
|
||||
type: 'background',
|
||||
inputPath: 'virtual:user-background',
|
||||
name: 'background',
|
||||
@@ -586,6 +586,14 @@ describe('findEntrypoints', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('throw an error if there are no entrypoints', async () => {
|
||||
globMock.mockResolvedValueOnce([]);
|
||||
|
||||
await expect(() => findEntrypoints(config)).rejects.toThrowError(
|
||||
`No entrypoints found in ${unnormalizePath(config.entrypointsDir)}`,
|
||||
);
|
||||
});
|
||||
|
||||
describe('include option', () => {
|
||||
it("should filter out the background when include doesn't contain the target browser", async () => {
|
||||
globMock.mockResolvedValueOnce(['background.ts']);
|
||||
|
||||
@@ -53,7 +53,8 @@ export async function findEntrypoints(
|
||||
return results;
|
||||
}, []);
|
||||
|
||||
// Report duplicate entrypoint names
|
||||
// Validation
|
||||
preventNoEntrypoints(config, entrypointInfos);
|
||||
preventDuplicateEntrypointNames(config, entrypointInfos);
|
||||
|
||||
// Import entrypoints to get their config
|
||||
@@ -169,6 +170,12 @@ function preventDuplicateEntrypointNames(
|
||||
}
|
||||
}
|
||||
|
||||
function preventNoEntrypoints(config: InternalConfig, files: EntrypointInfo[]) {
|
||||
if (files.length === 0) {
|
||||
throw Error(`No entrypoints found in ${config.entrypointsDir}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getHtmlBaseOptions(document: Document): BaseEntrypointOptions {
|
||||
const options: BaseEntrypointOptions = {};
|
||||
|
||||
|
||||
@@ -37,6 +37,9 @@ export async function getInternalConfig(
|
||||
name: 'wxt',
|
||||
cwd: inlineConfig.root ?? process.cwd(),
|
||||
rcFile: false,
|
||||
jitiOptions: {
|
||||
esmResolve: true,
|
||||
},
|
||||
});
|
||||
userConfig = loadedConfig ?? {};
|
||||
userConfigMetadata = metadata;
|
||||
|
||||
@@ -2,11 +2,12 @@ import createJITI, { TransformOptions as JitiTransformOptions } from 'jiti';
|
||||
import { InternalConfig } from '~/types';
|
||||
import { createUnimport } from 'unimport';
|
||||
import fs from 'fs-extra';
|
||||
import { resolve } from 'path';
|
||||
import { resolve } from 'node:path';
|
||||
import { getUnimportOptions } from '~/core/utils/unimport';
|
||||
import { removeProjectImportStatements } from '~/core/utils/strings';
|
||||
import { normalizePath } from '~/core/utils/paths';
|
||||
import { TransformOptions, transformSync } from 'esbuild';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
/**
|
||||
* Get the value from the default export of a `path`.
|
||||
@@ -45,30 +46,44 @@ export async function importEntrypointFile<T>(
|
||||
['Text:', text, 'No imports:', textNoImports, 'Code:', code].join('\n'),
|
||||
);
|
||||
|
||||
const jiti = createJITI(__filename, {
|
||||
cache: false,
|
||||
debug: config.debug,
|
||||
esmResolve: true,
|
||||
alias: {
|
||||
'webextension-polyfill': resolve(
|
||||
config.root,
|
||||
'node_modules/wxt/dist/virtual/mock-browser.js',
|
||||
),
|
||||
const jiti = createJITI(
|
||||
typeof __filename !== 'undefined'
|
||||
? __filename
|
||||
: fileURLToPath(import.meta.url),
|
||||
{
|
||||
cache: false,
|
||||
debug: config.debug,
|
||||
esmResolve: true,
|
||||
alias: {
|
||||
'webextension-polyfill': resolve(
|
||||
config.root,
|
||||
'node_modules/wxt/dist/virtual/mock-browser.js',
|
||||
),
|
||||
},
|
||||
// Continue using node to load TS files even if `bun run --bun` is detected. Jiti does not
|
||||
// respect the custom transform function when using it's native bun option.
|
||||
experimentalBun: false,
|
||||
// List of extensions to transform with esbuild
|
||||
extensions: [
|
||||
'.ts',
|
||||
'.cts',
|
||||
'.mts',
|
||||
'.tsx',
|
||||
'.js',
|
||||
'.cjs',
|
||||
'.mjs',
|
||||
'.jsx',
|
||||
],
|
||||
transform(opts) {
|
||||
const isEntrypoint = opts.filename === normalPath;
|
||||
return transformSync(
|
||||
// Use modified source code for entrypoints
|
||||
isEntrypoint ? code : opts.source,
|
||||
getEsbuildOptions(opts),
|
||||
);
|
||||
},
|
||||
},
|
||||
// Continue using node to load TS files even if `bun run --bun` is detected. Jiti does not
|
||||
// respect the custom transform function when using it's native bun option.
|
||||
experimentalBun: false,
|
||||
// List of extensions to transform with esbuild
|
||||
extensions: ['.ts', '.cts', '.mts', '.tsx', '.js', '.cjs', '.mjs', '.jsx'],
|
||||
transform(opts) {
|
||||
const isEntrypoint = opts.filename === normalPath;
|
||||
return transformSync(
|
||||
// Use modified source code for entrypoints
|
||||
isEntrypoint ? code : opts.source,
|
||||
getEsbuildOptions(opts),
|
||||
);
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
try {
|
||||
const res = await jiti(path);
|
||||
|
||||
@@ -22,8 +22,7 @@ export function removeImportStatements(text: string): string {
|
||||
export function removeProjectImportStatements(text: string): string {
|
||||
const noImports = removeImportStatements(text);
|
||||
|
||||
return `import { defineContentScript, defineBackground } from 'wxt/client';
|
||||
import { defineUnlistedScript } from 'wxt/sandbox';
|
||||
return `import { defineUnlistedScript, defineContentScript, defineBackground } from 'wxt/sandbox';
|
||||
|
||||
${noImports}`;
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { defineBackground } from '~/client/define-background';
|
||||
import { defineBackground } from '~/sandbox/define-background';
|
||||
import { BackgroundDefinition } from '~/types';
|
||||
|
||||
describe('defineBackground', () => {
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { defineContentScript } from '~/client/content-scripts/define-content-script';
|
||||
import { defineContentScript } from '~/sandbox/define-content-script';
|
||||
import { ContentScriptDefinition } from '~/types';
|
||||
|
||||
describe('defineContentScript', () => {
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BackgroundDefinition } from '~/types';
|
||||
import type { BackgroundDefinition } from '~/types';
|
||||
|
||||
export function defineBackground(main: () => void): BackgroundDefinition;
|
||||
export function defineBackground(
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { ContentScriptDefinition } from '~/types';
|
||||
import type { ContentScriptDefinition } from '~/types';
|
||||
|
||||
export function defineContentScript(
|
||||
definition: ContentScriptDefinition,
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UnlistedScriptDefinition } from '~/types';
|
||||
import type { UnlistedScriptDefinition } from '~/types';
|
||||
|
||||
export function defineUnlistedScript(
|
||||
main: () => void,
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
/**
|
||||
* Any runtime APIs that don't use the web extension APIs.
|
||||
*
|
||||
* @module wxt/sandbox
|
||||
*/
|
||||
export * from './define-unlisted-script';
|
||||
export * from './define-background';
|
||||
export * from './define-content-script';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as vite from 'vite';
|
||||
import type * as vite from 'vite';
|
||||
import {
|
||||
unimport,
|
||||
download,
|
||||
|
||||
+30
-10
@@ -439,7 +439,36 @@ export type EntrypointGroup = Entrypoint | Entrypoint[];
|
||||
|
||||
export type OnContentScriptStopped = (cb: () => void) => void;
|
||||
|
||||
export interface ContentScriptDefinition extends ExcludableEntrypoint {
|
||||
export type ContentScriptDefinition =
|
||||
| ContentScriptIsolatedWorldDefinition
|
||||
| ContentScriptMainWorldDefinition;
|
||||
|
||||
export interface ContentScriptIsolatedWorldDefinition
|
||||
extends ContentScriptBaseDefinition {
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#isolated_world
|
||||
* @default "ISOLATED"
|
||||
*/
|
||||
world?: 'ISOLATED';
|
||||
/**
|
||||
* Main function executed when the content script is loaded.
|
||||
*/
|
||||
main(ctx: ContentScriptContext): void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface ContentScriptMainWorldDefinition
|
||||
extends ContentScriptBaseDefinition {
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#isolated_world
|
||||
*/
|
||||
world: 'MAIN';
|
||||
/**
|
||||
* Main function executed when the content script is loaded.
|
||||
*/
|
||||
main(): void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface ContentScriptBaseDefinition extends ExcludableEntrypoint {
|
||||
matches: PerBrowserOption<Manifest.ContentScript['matches']>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
@@ -478,11 +507,6 @@ export interface ContentScriptDefinition extends ExcludableEntrypoint {
|
||||
* @default false
|
||||
*/
|
||||
matchOriginAsFallback?: PerBrowserOption<boolean>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
* @default "ISOLATED"
|
||||
*/
|
||||
world?: PerBrowserOption<'ISOLATED' | 'MAIN'>;
|
||||
/**
|
||||
* Customize how imported/generated styles are injected with the content script. Regardless of the
|
||||
* mode selected, CSS will always be built and included in the output directory.
|
||||
@@ -497,10 +521,6 @@ export interface ContentScriptDefinition extends ExcludableEntrypoint {
|
||||
* @default "manifest"
|
||||
*/
|
||||
cssInjectionMode?: PerBrowserOption<'manifest' | 'manual' | 'ui'>;
|
||||
/**
|
||||
* Main function executed when the content script is loaded.
|
||||
*/
|
||||
main(ctx: ContentScriptContext): void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface BackgroundDefinition extends ExcludableEntrypoint {
|
||||
|
||||
@@ -67,3 +67,9 @@ export interface ExtensionRunner {
|
||||
openBrowser(config: InternalConfig): Promise<void>;
|
||||
closeBrowser(): Promise<void>;
|
||||
}
|
||||
|
||||
export type VirtualEntrypointType =
|
||||
| 'content-script-main-world'
|
||||
| 'content-script-isolated-world'
|
||||
| 'background'
|
||||
| 'unlisted-script';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import definition from 'virtual:user-content-script';
|
||||
import definition from 'virtual:user-content-script-isolated-world';
|
||||
import { logger } from '~/client/utils/logger';
|
||||
import { ContentScriptContext } from '~/client/content-scripts/content-script-context';
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import definition from 'virtual:user-content-script-main-world';
|
||||
import { logger } from '~/client/utils/logger';
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const { main } = definition;
|
||||
await main();
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`The content script "${__ENTRYPOINT__}" crashed on startup!`,
|
||||
err,
|
||||
);
|
||||
}
|
||||
})();
|
||||
+7
-2
@@ -10,8 +10,13 @@ declare module 'virtual:user-background' {
|
||||
export default definition;
|
||||
}
|
||||
|
||||
declare module 'virtual:user-content-script' {
|
||||
const definition: import('~/types').ContentScriptDefinition;
|
||||
declare module 'virtual:user-content-script-isolated-world' {
|
||||
const definition: import('~/types').ContentScriptIsolatedWorldDefinition;
|
||||
export default definition;
|
||||
}
|
||||
|
||||
declare module 'virtual:user-content-script-main-world' {
|
||||
const definition: import('~/types').ContentScriptMainWorldDefinition;
|
||||
export default definition;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user