#433

feat: add an optional Vite plugin for Portless-aware startup URLs

Openmippbippopened 1mo ago

Problem

When Portless starts a Vite dev/preview server, Portless prints the URL users should open, then Vite prints its own internal listener URL.

For example:

Portless output:
  -> https://myapp.localhost
  Tailscale -> https://devbox.example.ts.net:8443

Vite output:
  Local: http://127.0.0.1:5173/

The Vite URL points at the child process, not the Portless proxy. In a terminal multiplexer or a busy development window, the Portless URL can scroll out of view before the actual Vite server starts. Vite does not provide a display-only host option. server.host changes the listener, server.origin does not control the startup URL, and server.allowedHosts only controls request validation.

Proposal

Add an optional Vite plugin exposed through a portless/vite subpath, following the naming convention used by packages such as Nitro.

import { defineConfig } from "vite";
import { portless } from "portless/vite";

export default defineConfig({
  plugins: [portless()],
});

The plugin would:

  • Read the active Portless URL from the environment.
  • Prefer PORTLESS_TAILSCALE_URL when Tailscale Serve or Funnel is active.
  • Fall back to PORTLESS_URL for local and LAN URLs.
  • Display one primary URL, with Tailscale taking priority when both LAN and Tailscale are active.
  • Preserve Vite's base path, trailing slash behavior, and the rest of Vite's startup output.
  • Use the same Portless URL when Vite opens a browser.
  • Work in both development and preview mode.
  • Do nothing when the plugin is loaded outside a Portless-managed run.
  • Leave custom Vite startup messages and loggers unchanged.

The existing Portless and Vite processes would continue to use their current listeners. The plugin would not change Vite's bind address or make the child server public. The plugin should not depend on private Vite internals if the public server and preview hooks are sufficient.

Compatibility

The first implementation would support the Vite versions used in this repository:

  • Vite 6.4.1
  • Vite 7.3.0
  • Vite 7.3.1

Scope

This proposal does not attempt to:

  • Detect or patch arbitrary Vite commands.
  • Inject a plugin into package scripts automatically.
  • Rewrite stdout from opaque framework wrappers.
  • Support tools that bypass the Vite config, including framework-specific launchers that create Vite with configFile: false.
  • Change Vite's listener address.
  • Add a new Portless CLI flag.
  • Add a second URL line when Tailscale and LAN are both active. If the plugin is not present, Vite should keep its current behavior.

Tests

The implementation should cover:

  • Local Vite development output.
  • Tailscale Serve output.
  • Tailscale Funnel output.
  • LAN URL selection when Tailscale is inactive.
  • Tailscale priority when LAN and Tailscale are both active.
  • Vite preview mode.
  • Base paths and trailing slashes.
  • Browser opening with the Portless URL.
  • Direct Vite commands and supported package scripts.
  • Vite 6.4.1, 7.3.0, and 7.3.1.
  • Windows, macOS, and Linux.
  • Projects without Portless, where the plugin is a no-op.

Questions for maintainers

  1. Is a required portless/vite plugin an acceptable integration boundary, or should Portless also attempt automatic injection for direct Vite commands?
  2. Is portless/vite the preferred package and export shape?
  3. Should the first version support only the active sharing URL, or should it display local and sharing URLs together?

Activity

0

No activity yet.