Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable wxt usage inside of devcontainers #1406

Merged
merged 6 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/guide/resources/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,16 @@ Additionally, if you want to train your own model or provide context to your edi
https://wxt.dev/knowledge/index.json

You don't need to crawl the entire website, these files already contain all the relevant docs for training a LLM on WXT. But feel free to crawl it and generate your own files if you want!

## How do I run my WXT project with docker / [devcontainers](https://containers.dev)?

To run the WXT dev server in a devcontainer, but load the dev build of your extension in your browser:

1. **Bind-mount your project directory to your host**
If you're using VS Code, you can open your project folder with the `Dev Containers: Open Folder in Container...` command. This keeps the folder synchronized between your host and the devcontainer, ensuring that the extension `dist` directory remains accessible from the host.

2. **Disable auto-opening the browser**
WXT automatically opens your browser during development, but since you're running inside a container, it won't be able to access it. Follow the instructions [here](https://wxt.dev/guide/essentials/config/browser-startup.html#disable-opening-browser) to disable browser auto-opening in your `wxt.config.ts`.

3. **Tell WXT to listen on all network interfaces**
To enable hot-reloading, your extension has to connect to the WXT dev server running inside your container. WXT will only listen on `localhost` by default, which prevents connections from outside the devcontainer. To fix this you can instruct WXT to listen on all interfaces with `wxt --host 0.0.0.0`.
4 changes: 1 addition & 3 deletions packages/wxt/src/@types/globals.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
declare const __DEV_SERVER_PROTOCOL__: string;
declare const __DEV_SERVER_HOSTNAME__: string;
declare const __DEV_SERVER_PORT__: string;
declare const __DEV_SERVER_ORIGIN__: string;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of constructing a best-guess URL to use to contact the dev server, we can use ORIGIN here


// Globals defined by the vite-plugins/devServerGlobals.ts and utils/globals.ts
interface ImportMetaEnv {
Expand Down
17 changes: 10 additions & 7 deletions packages/wxt/src/cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ 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('--host <host>', 'specify a host for the dev server to bind to')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't use -h because that's already taken by --help

.option('-p, --port <port>', 'specify a port for the dev server to bind to')
.option(
'-e, --filter-entrypoint <entrypoint>',
'only build specific entrypoints',
Expand All @@ -28,6 +29,12 @@ cli
.option('--mv2', 'target manifest v2')
.action(
wrapAction(async (root, flags) => {
const serverOptions: NonNullable<
NonNullable<Parameters<typeof createServer>[0]>['dev']
>['server'] = {};
if (flags.host) serverOptions.host = flags.host;
if (flags.port) serverOptions.port = parseInt(flags.port);

const server = await createServer({
root,
mode: flags.mode,
Expand All @@ -37,13 +44,9 @@ cli
debug: flags.debug,
filterEntrypoints: getArrayFromFlags(flags, 'filterEntrypoint'),
dev:
flags.port == null
Object.keys(serverOptions).length === 0
? undefined
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a test which requires this to be undefined when neither host nor port are set

: {
server: {
port: parseInt(flags.port),
},
},
: { server: serverOptions },
});
await server.start();
return { isOngoing: true };
Expand Down
2 changes: 1 addition & 1 deletion packages/wxt/src/core/builders/vite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,9 +327,9 @@ export async function createViteBuilder(
async createServer(info) {
const serverConfig: vite.InlineConfig = {
server: {
host: info.host,
port: info.port,
strictPort: true,
host: info.hostname,
origin: info.origin,
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('Dev HTML Prerender Plugin', () => {
},
});
const server = fakeDevServer({
hostname: 'localhost',
host: 'localhost',
port: 5173,
origin: 'http://localhost:5173',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export function devServerGlobals(

return {
define: {
__DEV_SERVER_PROTOCOL__: JSON.stringify('ws:'),
__DEV_SERVER_HOSTNAME__: JSON.stringify(server.hostname),
__DEV_SERVER_PORT__: JSON.stringify(server.port),
__DEV_SERVER_ORIGIN__: JSON.stringify(
server.origin.replace(/^http(s):/, 'ws$1:'),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex replace converts http: to ws: and https: to wss:

),
},
};
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ try {
handler(html, _ctx) {
const src =
config.command === 'serve'
? `http://${config.dev.server?.hostname}:${config.dev.server?.port}/@id/${virtualHtmlModuleId}`
? `${config.dev.server?.origin}/@id/${virtualHtmlModuleId}`
: virtualHtmlModuleId;

const { document } = parseHTML(html);
Expand Down
16 changes: 7 additions & 9 deletions packages/wxt/src/core/create-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,8 @@ export async function createServer(

async function createServerInternal(): Promise<WxtDevServer> {
const getServerInfo = (): ServerInfo => {
const { port, hostname } = wxt.config.dev.server!;
return {
port,
hostname,
origin: `http://${hostname}:${port}`,
};
const { host, port, origin } = wxt.config.dev.server!;
return { host, port, origin };
};

let [runner, builderServer] = await Promise.all([
Expand All @@ -71,8 +67,8 @@ async function createServerInternal(): Promise<WxtDevServer> {
// Server instance must be created first so its reference can be added to the internal config used
// to pre-render entrypoints
const server: WxtDevServer = {
get hostname() {
return getServerInfo().hostname;
get host() {
return getServerInfo().host;
},
get port() {
return getServerInfo().port;
Expand All @@ -96,7 +92,9 @@ async function createServerInternal(): Promise<WxtDevServer> {
}

await builderServer.listen();
wxt.logger.success(`Started dev server @ ${server.origin}`);
const hostInfo =
server.host === 'localhost' ? '' : ` (listening on ${server.host})`;
wxt.logger.success(`Started dev server @ ${server.origin}${hostInfo}`);
await wxt.hooks.callHook('server:started', wxt, server);

// Register content scripts for the first time after the background starts
Expand Down
26 changes: 22 additions & 4 deletions packages/wxt/src/core/resolve-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,19 +143,37 @@ export async function resolveConfig(

let devServerConfig: ResolvedConfig['dev']['server'];
if (command === 'serve') {
const hostname = mergedConfig.dev?.server?.hostname ?? 'localhost';
if (mergedConfig.dev?.server?.hostname)
logger.warn(
`The 'hostname' option is deprecated, please use 'host' or 'origin' depending on your circumstances.`,
);

const host =
mergedConfig.dev?.server?.host ??
mergedConfig.dev?.server?.hostname ??
'localhost';
let port = mergedConfig.dev?.server?.port;
const origin =
mergedConfig.dev?.server?.origin ??
mergedConfig.dev?.server?.hostname ??
'localhost';
if (port == null || !isFinite(port)) {
port = await getPort({
// Passing host required for Mac, unsure of Windows/Linux
host,
port: 3000,
portRange: [3001, 3010],
// Passing host required for Mac, unsure of Windows/Linux
host: hostname,
});
}
const originWithProtocolAndPort = [
origin.match(/^https?:\/\//) ? '' : 'http://',
origin,
origin.match(/:[0-9]+$/) ? '' : `:${port}`,
].join('');
Comment on lines +168 to +172
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a convenience, the protocol and port are pre/appended when not given, but are left alone when specified by the user.

devServerConfig = {
host,
port,
hostname,
origin: originWithProtocolAndPort,
watchDebounce: safeStringToNumber(process.env.WXT_WATCH_DEBOUNCE) ?? 800,
};
}
Expand Down
12 changes: 6 additions & 6 deletions packages/wxt/src/core/utils/__tests__/manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1463,7 +1463,7 @@ describe('Manifest Utils', () => {
command: 'build',
},
server: {
hostname: 'localhost',
host: 'localhost',
port: 3000,
origin: 'http://localhost:3000',
},
Expand All @@ -1487,8 +1487,8 @@ describe('Manifest Utils', () => {
manifestVersion: 2,
},
server: fakeWxtDevServer({
host: 'localhost',
port: 3000,
hostname: 'localhost',
origin: 'http://localhost:3000',
}),
});
Expand All @@ -1503,7 +1503,7 @@ describe('Manifest Utils', () => {
expect(actual).toMatchObject({
content_security_policy:
"script-src 'self' http://localhost:3000; object-src 'self';",
permissions: ['http://localhost/*', 'tabs'],
permissions: ['http://localhost:3000/*', 'tabs'],
});
});

Expand All @@ -1515,7 +1515,7 @@ describe('Manifest Utils', () => {
browser: 'chrome',
},
server: fakeWxtDevServer({
hostname: 'localhost',
host: 'localhost',
port: 3000,
origin: 'http://localhost:3000',
}),
Expand All @@ -1535,7 +1535,7 @@ describe('Manifest Utils', () => {
sandbox:
"script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:3000; sandbox allow-scripts allow-forms allow-popups allow-modals; child-src 'self';",
},
host_permissions: ['http://localhost/*'],
host_permissions: ['http://localhost:3000/*'],
permissions: ['tabs', 'scripting'],
});
});
Expand All @@ -1561,8 +1561,8 @@ describe('Manifest Utils', () => {
},
},
server: fakeWxtDevServer({
host: 'localhost',
port: 3000,
hostname: 'localhost',
origin: 'http://localhost:3000',
}),
});
Expand Down
2 changes: 1 addition & 1 deletion packages/wxt/src/core/utils/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ function discoverIcons(
}

function addDevModeCsp(manifest: Browser.runtime.Manifest): void {
const permission = `http://${wxt.server?.hostname ?? ''}/*`;
const permission = `${wxt.server?.origin ?? ''}/*`;
const allowedCsp = wxt.server?.origin ?? 'http://localhost:*';

if (manifest.manifest_version === 3) {
Expand Down
8 changes: 4 additions & 4 deletions packages/wxt/src/core/utils/testing/fake-objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,9 @@ export const fakeWxt = fakeObjectCreator<Wxt>(() => ({

export const fakeWxtDevServer = fakeObjectCreator<WxtDevServer>(() => ({
currentOutput: fakeBuildOutput(),
hostname: 'localhost',
origin: 'http://localhost:3000',
host: 'localhost',
port: 3000,
origin: 'http://localhost:3000',
reloadContentScript: vi.fn(),
reloadExtension: vi.fn(),
reloadPage: vi.fn(),
Expand Down Expand Up @@ -367,9 +367,9 @@ export const fakeManifestCommand = fakeObjectCreator<Browser.commands.Command>(
);

export const fakeDevServer = fakeObjectCreator<WxtDevServer>(() => ({
hostname: 'localhost',
origin: 'http://localhost',
host: 'localhost',
port: 5173,
origin: 'http://localhost:3000',
reloadContentScript: vi.fn(),
reloadExtension: vi.fn(),
reloadPage: vi.fn(),
Expand Down
25 changes: 19 additions & 6 deletions packages/wxt/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,14 +319,26 @@ export interface InlineConfig {
*/
dev?: {
server?: {
/**
* Host to bind the dev server to.
*
* @default "localhost"
*/
host?: string;
/**
* Port to run the dev server on. Defaults to the first open port from 3000 to 3010.
*/
port?: number;
/**
* Origin to use to connect from the extension ui runtime to the dev server.
*
* @default "http://localhost:3000"
*/
origin?: string;
/**
* Hostname to run the dev server on.
*
* @default "localhost"
* @deprecated use `host` to specify the interface to bind to, or use `origin` to specify the dev server hostname.
*/
hostname?: string;
};
Expand Down Expand Up @@ -1068,13 +1080,13 @@ export interface WxtBuilderServer {

export interface ServerInfo {
/**
* Ex: `3000`
* Ex: `"localhost"`
*/
port: number;
host: string;
/**
* Ex: `"localhost"`
* Ex: `3000`
*/
hostname: string;
port: number;
/**
* Ex: `"http://localhost:3000"`
*/
Expand Down Expand Up @@ -1343,8 +1355,9 @@ export interface ResolvedConfig {
dev: {
/** Only defined during dev command */
server?: {
host: string;
port: number;
hostname: string;
origin: string;
/**
* The milliseconds to debounce when a file is saved before reloading.
* The only way to set this option is to set the `WXT_WATCH_DEBOUNCE`
Expand Down
2 changes: 1 addition & 1 deletion packages/wxt/src/utils/internal/dev-server-websocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function getDevServerWebSocket(): WxtWebSocket {
);

if (ws == null) {
const serverUrl = `${__DEV_SERVER_PROTOCOL__}//${__DEV_SERVER_HOSTNAME__}:${__DEV_SERVER_PORT__}`;
const serverUrl = __DEV_SERVER_ORIGIN__;
logger.debug('Connecting to dev server @', serverUrl);

ws = new WebSocket(serverUrl, 'vite-hmr') as WxtWebSocket;
Expand Down