feat(vite)!: don't bundle @vitejs/plugin-vue; compose it explicitly (0.2.0) (#234)
* feat(vite)!: don't bundle @vitejs/plugin-vue; compose it explicitly BREAKING: vueTui() no longer includes @vitejs/plugin-vue. Add your own SFC/JSX compiler alongside it — `[vueTui(), vue()]` for SFCs (JSX was already explicit: `[vueTui(), vueJsx()]`). This makes SFC and JSX consistent, matches Vite convention (your framework plugin is visible in the config), removes the "is plugin-vue already bundled?" ambiguity, and keeps @vue-tui/vite focused on the terminal dev server (HMR) + build wiring. The dev plugin already force-client-compiles whichever plugin-vue/plugin-vue-jsx is present (matched by name in configResolved), so user-provided plugins work identically to the previously-bundled one. - index.ts: drop the bundled vue() and the `vue` passthrough option. - package.json: @vitejs/plugin-vue moves from dependencies to devDependencies + an OPTIONAL peerDependency (^6) — optional because JSX (plugin-vue-jsx) and h() users don't need it; the version is still validated when present. Bump 0.1.2 -> 0.2.0. - Update all in-repo SFC configs (examples + fixtures) and the dev/build tests to `[vueTui(), vue()]`; update basic-template's README. vp run ready green (vite suite 129 tests, runtime examples 6). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(vite): use the public [vueTui(), vue()] form in dev tests The decouple migration left the tests on the spread `[...vueTui(), vue()]` while configs/examples/docs use `[vueTui(), vue()]` (Vite flattens the nested plugin array — verified it works in createServer too). Align the tests to the documented consumer form for consistency. Vite suite green (12 files, 28 tests). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(vite): compiler plugin first in order — [vue(), vueTui()] Convention: list the SFC/JSX compiler first and vueTui() last ([vue(), vueTui()] / [vueJsx(), vueTui()]). Order is functionally irrelevant (devPlugin force-compiles whichever plugin-vue/vue-jsx is present by name in configResolved), so this is purely stylistic consistency across configs, examples, fixtures, tests, and docs. Also refresh @vue-tui/vite's own README, which still showed the pre-decouple [vueTui()] form and documented the now-removed `vue` passthrough option. Vite suite green (28 tests, incl. the JSX path). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,5 +6,5 @@ import { vueTui } from "@vue-tui/vite";
|
||||
// is added alongside vueTui(); the `entry` option points both the dev launcher and
|
||||
// the production build at src/main.tsx instead of the default src/main.ts.
|
||||
export default defineConfig({
|
||||
plugins: [vueTui({ entry: "/src/main.tsx" }), vueJsx()],
|
||||
plugins: [vueJsx(), vueTui({ entry: "/src/main.tsx" })],
|
||||
});
|
||||
|
||||
@@ -8,10 +8,11 @@ canonical reference for wiring up the `@vue-tui/vite` plugin.
|
||||
```ts
|
||||
// vite.config.ts
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "@vue-tui/vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vueTui()],
|
||||
plugins: [vue(), vueTui()],
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "@vue-tui/vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vueTui()],
|
||||
plugins: [vue(), vueTui()],
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "@vue-tui/vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vueTui()],
|
||||
plugins: [vue(), vueTui()],
|
||||
});
|
||||
|
||||
+10
-9
@@ -6,19 +6,23 @@ with HMR, plus a production build, for Vue apps that render to the terminal via
|
||||
## Install
|
||||
|
||||
```sh
|
||||
npm install -D @vue-tui/vite
|
||||
npm install -D @vue-tui/vite @vitejs/plugin-vue
|
||||
# peer deps: vite ^8, @vue-tui/runtime
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
`vueTui()` adds the terminal dev server (HMR) and the production build. Bring your own SFC/JSX
|
||||
compiler alongside it — `@vitejs/plugin-vue` for SFCs (or `@vitejs/plugin-vue-jsx` for JSX):
|
||||
|
||||
```ts
|
||||
// vite.config.ts
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "@vue-tui/vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vueTui()],
|
||||
plugins: [vue(), vueTui()],
|
||||
});
|
||||
```
|
||||
|
||||
@@ -31,20 +35,16 @@ export default defineConfig({
|
||||
```ts
|
||||
vueTui({
|
||||
entry: "src/main.ts", // default; the app entry (a .ts/.tsx file, not an index.html)
|
||||
vue: {
|
||||
// options forwarded to @vitejs/plugin-vue
|
||||
/* … */
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
For a JSX/TSX entry, add `@vitejs/plugin-vue-jsx` alongside it and point `entry` at the `.tsx` file:
|
||||
For a JSX/TSX entry, use `@vitejs/plugin-vue-jsx` and point `entry` at the `.tsx` file:
|
||||
|
||||
```ts
|
||||
import vueJsx from "@vitejs/plugin-vue-jsx";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vueTui({ entry: "/src/main.tsx" }), vueJsx()],
|
||||
plugins: [vueJsx(), vueTui({ entry: "/src/main.tsx" })],
|
||||
});
|
||||
```
|
||||
|
||||
@@ -59,10 +59,11 @@ bundled but Node builtins — e.g. toward a standalone binary), set your own bui
|
||||
`vite.config.ts` and the plugin yields to them:
|
||||
|
||||
```ts
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { isBuiltin } from "node:module";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vueTui()],
|
||||
plugins: [vue(), vueTui()],
|
||||
build: {
|
||||
// Vite 8 is Rolldown-powered: the field is `rolldownOptions` (`rollupOptions` is the alias).
|
||||
rolldownOptions: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vue-tui/vite",
|
||||
"version": "0.1.2",
|
||||
"version": "0.2.0",
|
||||
"description": "Vite plugin for vue-tui: in-process terminal dev server with HMR + production build.",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
@@ -25,11 +25,9 @@
|
||||
"check:type": "tsc --noEmit",
|
||||
"prepublishOnly": "vp run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-vue": "^6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "catalog:",
|
||||
"@vitejs/plugin-vue": "^6",
|
||||
"@vitejs/plugin-vue-jsx": "catalog:",
|
||||
"@vue-tui/runtime": "workspace:*",
|
||||
"typescript": "^6.0.3",
|
||||
@@ -37,10 +35,14 @@
|
||||
"vue": "^3.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vitejs/plugin-vue": "^6",
|
||||
"@vue-tui/runtime": "workspace:^",
|
||||
"vite": "^8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vitejs/plugin-vue": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import type { Plugin } from "vite";
|
||||
import { devVmodPlugin } from "./dev-vmod.ts";
|
||||
import { devPlugin } from "./dev.ts";
|
||||
import { buildConfigPlugin } from "./build.ts";
|
||||
|
||||
export interface VueTuiOptions {
|
||||
vue?: Parameters<typeof vue>[0];
|
||||
entry?: string;
|
||||
}
|
||||
|
||||
export function vueTui(options: VueTuiOptions = {}): Plugin[] {
|
||||
// devPlugin (apply:"serve") and buildConfigPlugin (apply:"build") never run together — Vite
|
||||
// picks the right set per mode. normalizeEntry() (below) derives the entry string each needs.
|
||||
// plugin-vue here (and any plugin-vue-jsx the user adds) is force-client-compiled in
|
||||
// devPlugin's configResolved, so it runs in the SSR dev environment but emits CLIENT
|
||||
// render functions for the terminal renderer.
|
||||
//
|
||||
// Bring your own SFC/JSX compiler alongside vueTui() — `[vue(), vueTui()]` for SFCs, or
|
||||
// `[vueJsx(), vueTui()]` for JSX. devPlugin's configResolved finds whichever is present (by
|
||||
// plugin name) and force-client-compiles it, so it emits CLIENT render functions for the
|
||||
// terminal renderer even in Vite's SSR dev environment. vueTui deliberately does NOT bundle
|
||||
// @vitejs/plugin-vue: the app's authoring format is the consumer's choice, kept explicit.
|
||||
const { dev, build } = normalizeEntry(options.entry);
|
||||
return [
|
||||
devPlugin({ entry: dev }),
|
||||
buildConfigPlugin({ entry: build }),
|
||||
devVmodPlugin(),
|
||||
vue(options.vue) as Plugin,
|
||||
];
|
||||
return [devPlugin({ entry: dev }), buildConfigPlugin({ entry: build }), devVmodPlugin()];
|
||||
}
|
||||
|
||||
// Reconcile the entry for dev (matched against the absolute module id via endsWith) and build
|
||||
|
||||
@@ -17,6 +17,7 @@ import { fileURLToPath } from "node:url";
|
||||
import { existsSync, rmSync } from "node:fs";
|
||||
import { isBuiltin } from "node:module";
|
||||
import { build, type Rollup } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "../src/index.ts";
|
||||
|
||||
const root = fileURLToPath(new URL("./fixtures/build", import.meta.url));
|
||||
@@ -28,7 +29,12 @@ afterEach(() => {
|
||||
|
||||
test("vite build emits a single self-contained Node entry with deps externalized", async () => {
|
||||
rmSync(dist, { recursive: true, force: true });
|
||||
const output = await build({ root, configFile: false, plugins: vueTui(), logLevel: "silent" });
|
||||
const output = await build({
|
||||
root,
|
||||
configFile: false,
|
||||
plugins: [vue(), vueTui()],
|
||||
logLevel: "silent",
|
||||
});
|
||||
|
||||
// A single, named input resolves to one RollupOutput (not a watcher).
|
||||
const result = (Array.isArray(output) ? output[0] : output) as Rollup.RollupOutput;
|
||||
@@ -57,7 +63,7 @@ test("a consumer's own rolldownOptions.external overrides the plugin default (se
|
||||
const output = await build({
|
||||
root,
|
||||
configFile: false,
|
||||
plugins: vueTui(),
|
||||
plugins: [vue(), vueTui()],
|
||||
logLevel: "silent",
|
||||
build: {
|
||||
rolldownOptions: {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { test, expect, afterEach } from "vite-plus/test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createServer, type ViteDevServer } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "../src/index.ts";
|
||||
import { capture, waitFor } from "./helpers.ts";
|
||||
|
||||
@@ -24,7 +25,12 @@ afterEach(async () => {
|
||||
// server._shortcutsState (the readline). Assert vueTui leaves no _shortcutsState.
|
||||
test("vueTui disables Vite's CLI keyboard shortcuts so they can't hijack the TUI's stdin", async () => {
|
||||
const read = capture();
|
||||
server = await createServer({ root, logLevel: "silent", configFile: false, plugins: vueTui() });
|
||||
server = await createServer({
|
||||
root,
|
||||
logLevel: "silent",
|
||||
configFile: false,
|
||||
plugins: [vue(), vueTui()],
|
||||
});
|
||||
await server.listen(); // a real httpServer is required for bindCLIShortcuts to act
|
||||
await waitFor(read, "count="); // let the app mount with the normal (non-TTY) stdin first
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import { test, expect, afterEach } from "vite-plus/test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createServer, type ViteDevServer } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "../src/index.ts";
|
||||
import { capture, waitFor } from "./helpers.ts";
|
||||
|
||||
@@ -37,7 +38,12 @@ afterEach(async () => {
|
||||
|
||||
test("#214: dev-mode <Text color> emits real ANSI color", async () => {
|
||||
const read = capture();
|
||||
server = await createServer({ root, logLevel: "silent", configFile: false, plugins: vueTui() });
|
||||
server = await createServer({
|
||||
root,
|
||||
logLevel: "silent",
|
||||
configFile: false,
|
||||
plugins: [vue(), vueTui()],
|
||||
});
|
||||
await server.listen();
|
||||
await waitFor(read, "COLORTEST");
|
||||
// Asserting the SGR (not just the text) is what distinguishes the fix from #214 — under the
|
||||
|
||||
@@ -13,6 +13,7 @@ import { test, expect, afterEach } from "vite-plus/test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { writeFileSync, readFileSync } from "node:fs";
|
||||
import { createServer, type ViteDevServer } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "../src/index.ts";
|
||||
import { capture, waitFor } from "./helpers.ts";
|
||||
|
||||
@@ -34,7 +35,7 @@ test("boots the app in-process and renders a frame", async () => {
|
||||
root,
|
||||
logLevel: "silent",
|
||||
configFile: false,
|
||||
plugins: vueTui(),
|
||||
plugins: [vue(), vueTui()],
|
||||
});
|
||||
await server.listen();
|
||||
await waitFor(read, "LABEL-A");
|
||||
@@ -47,7 +48,7 @@ test("template-only edit hot-swaps with state preserved (no reload)", async () =
|
||||
root,
|
||||
logLevel: "silent",
|
||||
configFile: false,
|
||||
plugins: vueTui(),
|
||||
plugins: [vue(), vueTui()],
|
||||
});
|
||||
await server.listen();
|
||||
await waitFor(read, "count=3"); // let the counter tick
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "../../../src/index.ts";
|
||||
export default defineConfig({ plugins: [vueTui()] });
|
||||
export default defineConfig({ plugins: [vue(), vueTui()] });
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "../../../src/index.ts";
|
||||
export default defineConfig({ plugins: [vueTui()] });
|
||||
export default defineConfig({ plugins: [vue(), vueTui()] });
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "../../../src/index.ts";
|
||||
export default defineConfig({ plugins: [vueTui()] });
|
||||
export default defineConfig({ plugins: [vue(), vueTui()] });
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "../../../src/index.ts";
|
||||
export default defineConfig({ plugins: [vueTui()] });
|
||||
export default defineConfig({ plugins: [vue(), vueTui()] });
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vueJsx from "@vitejs/plugin-vue-jsx";
|
||||
import { vueTui } from "../../../src/index.ts";
|
||||
export default defineConfig({ plugins: [vueTui({ entry: "/src/main.tsx" }), vueJsx()] });
|
||||
export default defineConfig({ plugins: [vueJsx(), vueTui({ entry: "/src/main.tsx" })] });
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "../../../src/index.ts";
|
||||
export default defineConfig({ plugins: [vueTui()] });
|
||||
export default defineConfig({ plugins: [vue(), vueTui()] });
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "../../../src/index.ts";
|
||||
export default defineConfig({ plugins: [vueTui()] });
|
||||
export default defineConfig({ plugins: [vue(), vueTui()] });
|
||||
|
||||
@@ -12,6 +12,7 @@ import { test, expect, afterEach } from "vite-plus/test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { writeFileSync, readFileSync } from "node:fs";
|
||||
import { createServer, type ViteDevServer } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "../src/index.ts";
|
||||
import { capture, waitUntil } from "./helpers.ts";
|
||||
|
||||
@@ -38,7 +39,12 @@ function counts(chunk: string): number[] {
|
||||
|
||||
test("an entry-level (non-HMR) change restarts the app in-process with no zombie", async () => {
|
||||
const read = capture();
|
||||
server = await createServer({ root, logLevel: "silent", configFile: false, plugins: vueTui() });
|
||||
server = await createServer({
|
||||
root,
|
||||
logLevel: "silent",
|
||||
configFile: false,
|
||||
plugins: [vue(), vueTui()],
|
||||
});
|
||||
await server.listen();
|
||||
|
||||
// Let the original app boot and tick a few times.
|
||||
@@ -100,7 +106,7 @@ test("survives a SECOND full reload when @vue-tui/runtime is EXTERNALIZED (the p
|
||||
root,
|
||||
logLevel: "silent",
|
||||
configFile: false,
|
||||
plugins: vueTui(),
|
||||
plugins: [vue(), vueTui()],
|
||||
ssr: { external: ["@vue-tui/runtime", "@vue-tui/runtime/internal"] },
|
||||
server: { middlewareMode: true },
|
||||
});
|
||||
@@ -145,7 +151,7 @@ test("a genuine app exit closes the in-process dev server", async () => {
|
||||
root: exitRoot,
|
||||
logLevel: "silent",
|
||||
configFile: false,
|
||||
plugins: vueTui(),
|
||||
plugins: [vue(), vueTui()],
|
||||
});
|
||||
// Spy on close so we can detect the runtime-driven teardown. dev.ts captured
|
||||
// `server` and calls server.close() at exit time, so it sees this wrapper.
|
||||
|
||||
@@ -27,7 +27,7 @@ test("JSX (.tsx) components render in the in-process dev server", async () => {
|
||||
root,
|
||||
logLevel: "silent",
|
||||
configFile: false,
|
||||
plugins: [vueTui({ entry: "/src/main.tsx" }), vueJsx()],
|
||||
plugins: [vueJsx(), vueTui({ entry: "/src/main.tsx" })],
|
||||
});
|
||||
await server.listen();
|
||||
await waitFor(read, "JSX-LABEL");
|
||||
|
||||
@@ -16,6 +16,7 @@ import { test, expect, afterEach } from "vite-plus/test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { writeFileSync, readFileSync } from "node:fs";
|
||||
import { createServer, type ViteDevServer } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { vueTui } from "../src/index.ts";
|
||||
import { capture, waitFor } from "./helpers.ts";
|
||||
|
||||
@@ -37,7 +38,7 @@ test("a build error renders the in-process dev overlay", async () => {
|
||||
root,
|
||||
logLevel: "silent",
|
||||
configFile: false,
|
||||
plugins: vueTui(),
|
||||
plugins: [vue(), vueTui()],
|
||||
});
|
||||
await server.listen();
|
||||
await waitFor(read, "LABEL-A");
|
||||
|
||||
Generated
+3
-4
@@ -338,14 +338,13 @@ importers:
|
||||
version: 3.5.34(typescript@6.0.3)
|
||||
|
||||
packages/vite:
|
||||
dependencies:
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^6
|
||||
version: 6.0.7(vite@8.1.0(@types/node@24.12.4)(esbuild@0.28.0)(tsx@4.22.3))(vue@3.5.34(typescript@6.0.3))
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^24.12.4
|
||||
version: 24.12.4
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^6
|
||||
version: 6.0.7(vite@8.1.0(@types/node@24.12.4)(esbuild@0.28.0)(tsx@4.22.3))(vue@3.5.34(typescript@6.0.3))
|
||||
'@vitejs/plugin-vue-jsx':
|
||||
specifier: 'catalog:'
|
||||
version: 5.1.5(vite@8.1.0(@types/node@24.12.4)(esbuild@0.28.0)(tsx@4.22.3))(vue@3.5.34(typescript@6.0.3))
|
||||
|
||||
Reference in New Issue
Block a user