fix(vite): resolve @vue-tui/vite from CommonJS Vite configs (#243)

* fix(vite): resolve @vue-tui/vite from CommonJS Vite configs

When a consumer project is CommonJS — package.json "type": "commonjs", or
no "type" field at all (Node defaults to CommonJS) — Vite loads vite.config.ts
as CommonJS and resolves its imports under the `require` condition.
@vue-tui/vite's exports used an `import`-only conditions object with no
fallback, so nothing matched under `require` and Vite threw:

    [plugin externalize-deps] Failed to resolve "@vue-tui/vite".
    This package is ESM only but it was tried to load by `require`.

Switch the export to a bare string (plus "./package.json"), matching the
other @vue-tui/* packages and @vitejs/plugin-vue. A string export is
condition-agnostic, so it resolves under both `import` and `require`; Node
then loads the ESM file via require(ESM), which every supported Node
(>=22.18) provides. No CommonJS build is added — this mirrors how the Vite
plugin ecosystem ships today (all ESM-only).

Add a regression test (test/cjs-config.test.ts): a `type: commonjs` fixture
whose vite.config.ts imports @vue-tui/vite by name, loaded through the real
config loader via resolveConfig(). It fails with the exact #238 error on the
old exports and passes with this change.

* chore(lint): exclude test fixtures from linting

Test fixtures are test INPUTS, not shipped source, and some are deliberately
broken: the overlay / full-reload dev-server tests transiently overwrite a
fixture's app.vue with a syntax error (to exercise the error overlay / failed
HMR), then restore it. Because `vp run ci` runs lint concurrently with those
tests, a linter that read a fixture inside that broken window failed with a
spurious "Unexpected token" — a flaky race whose surfacing depended on test
timing. Add lint.ignorePatterns ["**/test/fixtures/**"] to remove the race
(and the wasted lint work on test data).
This commit is contained in:
Yunfei He
2026-07-05 18:30:31 +08:00
committed by GitHub
parent 8bc7976561
commit e5015b0a80
5 changed files with 68 additions and 4 deletions
+2 -4
View File
@@ -10,10 +10,8 @@
], ],
"type": "module", "type": "module",
"exports": { "exports": {
".": { ".": "./dist/index.mjs",
"types": "./dist/index.d.mts", "./package.json": "./package.json"
"import": "./dist/index.mjs"
}
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
+44
View File
@@ -0,0 +1,44 @@
// Regression test for #238: @vue-tui/vite must be loadable from a CommonJS consumer project.
//
// When a consumer's nearest package.json is `type: commonjs` (or has no `type` field — Node
// defaults to CommonJS), Vite bundles+loads vite.config.ts as CommonJS and resolves its imports
// under the `require` condition. @vue-tui/vite once exported only an `import` condition with no
// fallback, so nothing matched under `require` and config loading threw:
//
// [plugin externalize-deps] Failed to resolve "@vue-tui/vite".
// This package is ESM only but it was tried to load by `require`.
//
// The fixture (test/fixtures/cjs-config) has its own `type: commonjs` package.json to force the
// CJS config path, and imports @vue-tui/vite BY NAME. We symlink the real package into the
// fixture's node_modules first, so the bare import resolves against the built package and this
// exercises its published exports map under `require` — exactly a real consumer's layout.
// resolveConfig() runs the real config loader (the code path that threw) without a server/build.
import { test, expect, beforeAll, afterAll } from "vite-plus/test";
import { fileURLToPath } from "node:url";
import { mkdirSync, rmSync, symlinkSync } from "node:fs";
import { resolveConfig } from "vite";
const root = fileURLToPath(new URL("./fixtures/cjs-config", import.meta.url));
const pkgRoot = fileURLToPath(new URL("../", import.meta.url)); // packages/vite
const fixtureModules = `${root}/node_modules`;
beforeAll(() => {
rmSync(fixtureModules, { recursive: true, force: true });
mkdirSync(`${fixtureModules}/@vue-tui`, { recursive: true });
// "junction" keeps this cross-platform (absolute dir link on Windows, plain symlink on POSIX).
symlinkSync(pkgRoot, `${fixtureModules}/@vue-tui/vite`, "junction");
});
afterAll(() => {
rmSync(fixtureModules, { recursive: true, force: true });
});
test("loads a config that imports @vue-tui/vite from a CommonJS project (#238)", async () => {
const config = await resolveConfig(
{ root, configFile: `${root}/vite.config.ts`, logLevel: "silent" },
"serve",
);
// The config resolved AND executed vueTui() under the CJS `require` path: its plugins are here.
expect(config.plugins.some((p) => p.name?.startsWith("vue-tui"))).toBe(true);
});
+4
View File
@@ -0,0 +1,4 @@
{
"private": true,
"type": "commonjs"
}
+11
View File
@@ -0,0 +1,11 @@
// Fixture for the #238 regression test (test/cjs-config.test.ts).
//
// The sibling package.json declares `type: commonjs`, so Vite bundles+loads THIS config as
// CommonJS and resolves its imports under the `require` condition. Importing @vue-tui/vite BY
// NAME exercises the published package's exports map: if it isn't resolvable under `require`,
// config loading throws "This package is ESM only but it was tried to load by `require`".
import { vueTui } from "@vue-tui/vite";
export default {
plugins: [vueTui()],
};
+7
View File
@@ -6,6 +6,13 @@ export default defineConfig({
}, },
fmt: {}, fmt: {},
lint: { lint: {
// Don't lint test fixtures. They are test INPUTS, not shipped source, and some are
// deliberately broken: the overlay / full-reload dev-server tests transiently overwrite a
// fixture's app.vue with a syntax error (to exercise the error overlay / failed HMR), then
// restore it. Because `vp run ci` runs lint CONCURRENTLY with those tests, a linter that read
// a fixture inside that broken window failed with a spurious "Unexpected token" (a flaky race,
// surfaced by test timing). Excluding fixtures removes the race and the wasted lint work.
ignorePatterns: ["**/test/fixtures/**"],
options: { typeAware: true, typeCheck: false }, options: { typeAware: true, typeCheck: false },
rules: { rules: {
// This is a terminal UI library: parsing keyboard escape sequences and // This is a terminal UI library: parsing keyboard escape sequences and