chore(ui): migrate to Vite 8, oxlint, and oxfmt (#1362)

* chore(ui): migrate to Vite 8, oxlint, and oxfmt

Replace esbuild+Rollup with Oxc+Rolldown (Vite 8), ESLint with oxlint
(with type-aware linting), and Prettier with oxfmt. No source code
changes — formatting and lint fixes will roll out incrementally via
lint-staged as files are touched.

* fix(ui): resolve oxlint errors in existing code

Fix errors caught by oxlint: useless rename, redundant undefined on
optional params, duplicate union type constituent, and unsafe toString
on union type.
This commit is contained in:
Adam Shiervani
2026-03-28 12:47:40 +01:00
committed by GitHub
parent edaa86c0d3
commit 48eeb147eb
9 changed files with 2019 additions and 5062 deletions
+4 -4
View File
@@ -5,9 +5,9 @@
"useTabs": false,
"arrowParens": "avoid",
"singleQuote": false,
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindFunctions": ["clsx", "cx"],
"printWidth": 100,
"max_line_length": 100,
"tailwindStylesheet": "./src/index.css"
"sortTailwindcss": {
"functions": ["clsx", "cx"],
"stylesheet": "./src/index.css",
},
}
+29
View File
@@ -0,0 +1,29 @@
{
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "import"],
"options": {
"typeAware": true
},
"categories": {
"correctness": "error"
},
"rules": {
"typescript/no-unused-vars": ["warn", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
"typescript/no-floating-promises": "warn",
"typescript/restrict-template-expressions": "warn",
"typescript/no-base-to-string": "warn",
"import/order": [
"error",
{
"groups": ["builtin", "external", "internal", "parent", "sibling"],
"newlines-between": "ignore"
}
]
},
"settings": {
"react": {
"version": "19"
}
},
"ignorePatterns": ["dist", "tailwind.config.js", "postcss.config.js"]
}
-90
View File
@@ -1,90 +0,0 @@
import globals from "globals";
import { defineConfig, globalIgnores } from "eslint/config";
import { fixupConfigRules } from "@eslint/compat";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
import path from "path";
import { fileURLToPath } from "url";
import tsParser from "@typescript-eslint/parser";
import reactRefresh from "eslint-plugin-react-refresh";
// mimic CommonJS variables
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
export default defineConfig([
{
languageOptions: {
globals: globals.browser,
parser: tsParser,
ecmaVersion: "latest",
sourceType: "module",
parserOptions: {
project: ["./tsconfig.app.json", "./tsconfig.node.json"],
tsconfigRootDir: __dirname,
ecmaFeatures: {
jsx: true
}
},
},
extends: fixupConfigRules(compat.extends(
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/stylistic",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:react/jsx-runtime",
"plugin:import/recommended",
"plugin:prettier/recommended",
"prettier",
)),
plugins: {
"react-refresh": reactRefresh,
},
rules: {
"react-refresh/only-export-components": ["warn", {
allowConstantExport: true,
}],
"import/order": ["error", {
groups: ["builtin", "external", "internal", "parent", "sibling"],
"newlines-between": "ignore",
}],
"@typescript-eslint/no-unused-vars": ["warn", {
"argsIgnorePattern": "^_", "varsIgnorePattern": "^_"
}],
},
settings: {
"react": {
"version": "detect"
},
"import/resolver": {
alias: {
map: [
["@components", "./src/components"],
["@routes", "./src/routes"],
["@hooks", "./src/hooks"],
["@providers", "./src/providers"],
["@assets", "./src/assets"],
["@localizations", "./localization/paraglide"],
["@", "./src"],
],
extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
},
},
},
},
globalIgnores([
"**/dist",
"**/tailwind.config.js",
"**/postcss.config.js",
])]);
+1942 -4899
View File
File diff suppressed because it is too large Load Diff
+16 -30
View File
@@ -1,11 +1,8 @@
{
"name": "kvm-ui",
"private": true,
"version": "2025.12.11.1200",
"private": true,
"type": "module",
"engines": {
"node": "^22.21.1"
},
"scripts": {
"dev": "./dev_device.sh",
"dev:ssl": "USE_SSL=true ./dev_device.sh",
@@ -15,9 +12,9 @@
"build:staging": "npm run i18n:compile && tsc && vite build --mode=cloud-staging",
"build:prod": "npm run i18n:compile && tsc && vite build --mode=cloud-production",
"lint": "npm run i18n:compile && npm run lint:only",
"lint:only": "eslint './src/**/*.{ts,tsx}'",
"lint:only": "oxlint ./src",
"lint:fix": "npm run i18n:compile && npm run lint:fixonly",
"lint:fixonly": "eslint './src/**/*.{ts,tsx}' --fix",
"lint:fixonly": "oxlint ./src --fix",
"i18n": "npm run i18n:resort && npm run i18n:validate && npm run i18n:compile",
"i18n:resort": "python3 tools/resort_messages.py",
"i18n:validate": "inlang validate --project ./localization/jetKVM.UI.inlang",
@@ -34,7 +31,7 @@
"@headlessui/react": "^2.2.9",
"@headlessui/tailwindcss": "^0.2.2",
"@heroicons/react": "^2.2.0",
"@vitejs/plugin-basic-ssl": "^2.1.0",
"@vitejs/plugin-basic-ssl": "^2.3.0",
"@xterm/addon-clipboard": "^0.1.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-unicode11": "^0.8.0",
@@ -66,9 +63,6 @@
"zustand": "^4.5.2"
},
"devDependencies": {
"@eslint/compat": "^2.0.0",
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
"@inlang/cli": "^3.0.12",
"@inlang/paraglide-js": "^2.6.0",
"@inlang/plugin-m-function-matcher": "^2.1.0",
@@ -78,38 +72,30 @@
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/postcss": "^4.1.18",
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.1.18",
"@tailwindcss/vite": "^4.2.2",
"@types/node": "^24.10.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@types/semver": "^7.7.1",
"@types/validator": "^13.15.10",
"@typescript-eslint/eslint-plugin": "^8.49.0",
"@typescript-eslint/parser": "^8.49.0",
"@vitejs/plugin-react-swc": "^4.2.2",
"@vitejs/plugin-react-swc": "^4.3.0",
"autoprefixer": "^10.4.23",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.25",
"globals": "^16.5.0",
"husky": "^9.1.7",
"lint-staged": "^16.4.0",
"oxfmt": "^0.42.0",
"oxlint": "^1.57.0",
"oxlint-tsgolint": "^0.18.1",
"postcss": "^8.5.6",
"prettier": "^3.7.4",
"prettier-plugin-tailwindcss": "^0.7.2",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
"typescript-eslint": "^8.49.0",
"vite": "^7.2.7",
"vite-tsconfig-paths": "^5.1.4"
"vite": "^8.0.3",
"vite-tsconfig-paths": "^6.1.1"
},
"lint-staged": {
"*.{ts,tsx}": "eslint --fix",
"*.{ts,tsx,js,jsx,json,css,md}": "prettier --write"
"*.{ts,tsx}": "oxlint --fix --deny-warnings",
"*.{ts,tsx,js,jsx,json,css,md}": "oxfmt"
},
"engines": {
"node": "^22.21.1"
}
}
+2 -3
View File
@@ -161,7 +161,6 @@ type ButtonPropsType = Pick<
| "onMouseLeave"
| "onMouseDown"
| "onMouseUp"
| "onMouseLeave"
| "data-testid"
> &
React.ComponentProps<typeof ButtonContent> & {
@@ -220,9 +219,9 @@ export const LinkButton = ({ to, ...props }: LinkPropsType) => {
props.className,
);
if (to.toString().startsWith("http")) {
if (typeof to === "string" && to.startsWith("http")) {
return (
<ExtLink href={to.toString()} className={classes} target={props.target}>
<ExtLink href={to} className={classes} target={props.target}>
<ButtonContent {...props} />
</ExtLink>
);
+2 -10
View File
@@ -899,18 +899,10 @@ export interface MacrosState {
loadMacros: () => Promise<void>;
saveMacros: (macros: KeySequence[]) => Promise<void>;
sendFn:
| ((
method: string,
params: unknown,
callback?: ((resp: JsonRpcResponse) => void) | undefined,
) => void)
| ((method: string, params: unknown, callback?: (resp: JsonRpcResponse) => void) => void)
| null;
setSendFn: (
sendFn: (
method: string,
params: unknown,
callback?: ((resp: JsonRpcResponse) => void) | undefined,
) => void,
sendFn: (method: string, params: unknown, callback?: (resp: JsonRpcResponse) => void) => void,
) => void;
}
+1 -1
View File
@@ -34,7 +34,7 @@ export default function SettingsRoute() {
const { width = 0 } = useResizeObserver({
ref: scrollContainerRef as React.RefObject<HTMLDivElement>,
});
const { isFailsafeMode: isFailsafeMode, reason: failsafeReason } = useFailsafeModeStore();
const { isFailsafeMode, reason: failsafeReason } = useFailsafeModeStore();
const isVideoDisabled = isFailsafeMode && failsafeReason === "video";
// Handle scroll position to show/hide gradients
+23 -25
View File
@@ -18,37 +18,35 @@ export default defineConfig(({ mode, command }) => {
const { JETKVM_PROXY_URL, USE_SSL } = process.env;
const useSSL = USE_SSL === "true";
const plugins = [
tailwindcss(),
tsconfigPaths(),
react()
];
const plugins = [tailwindcss(), tsconfigPaths(), react()];
if (useSSL) {
plugins.push(basicSsl());
}
plugins.push(paraglideVitePlugin({
project: "./localization/jetKVM.UI.inlang",
outdir: "./localization/paraglide",
outputStructure: 'message-modules',
cookieName: 'JETKVM_LOCALE',
strategy: ['cookie', 'baseLocale'],
}))
plugins.push(
paraglideVitePlugin({
project: "./localization/jetKVM.UI.inlang",
outdir: "./localization/paraglide",
outputStructure: "message-modules",
cookieName: "JETKVM_LOCALE",
strategy: ["cookie", "baseLocale"],
}),
);
return {
plugins,
esbuild: {
pure: command === "build" ? ["console.debug"]: [],
oxc: {
pure: command === "build" ? ["console.debug"] : [],
},
assetsInclude: ["**/*.woff2"],
build: {
outDir: isCloud ? "dist" : "../static",
rollupOptions: {
rolldownOptions: {
output: {
manualChunks: (id) => {
manualChunks: id => {
if (id.includes("node_modules")) {
// Let Rollup handle tesseract.js naturally via dynamic import
// Let Rolldown handle tesseract.js naturally via dynamic import
// to avoid CommonJS/ESM interop issues across chunks
if (id.includes("tesseract")) return;
return "vendor";
@@ -66,14 +64,14 @@ export default defineConfig(({ mode, command }) => {
https: useSSL,
proxy: JETKVM_PROXY_URL
? {
"/me": JETKVM_PROXY_URL,
"/device": JETKVM_PROXY_URL,
"/webrtc": JETKVM_PROXY_URL,
"/auth": JETKVM_PROXY_URL,
"/storage": JETKVM_PROXY_URL,
"/cloud": JETKVM_PROXY_URL,
"/developer": JETKVM_PROXY_URL,
}
"/me": JETKVM_PROXY_URL,
"/device": JETKVM_PROXY_URL,
"/webrtc": JETKVM_PROXY_URL,
"/auth": JETKVM_PROXY_URL,
"/storage": JETKVM_PROXY_URL,
"/cloud": JETKVM_PROXY_URL,
"/developer": JETKVM_PROXY_URL,
}
: undefined,
},
base: onDevice && command === "build" ? "/static" : "/",