vite-plugin-eslint2
Version:
ESLint plugin for Vite.
195 lines (194 loc) • 7.02 kB
JavaScript
import { a as WS_OVERLAY_EVENT, i as RUNTIME_CLIENT_RUNTIME_PATH, n as PLUGIN_NAME, o as WS_RUNTIME_LOADED_EVENT, t as createLinter } from "./linter-D_nGEUO0.mjs";
import { existsSync, readFileSync } from "node:fs";
import { dirname, extname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { Worker } from "node:worker_threads";
import debugWrap from "debug";
import * as Vite from "vite";
//#region src/client/preamble.ts
/**
* Compose the inline script that bootstraps the custom overlay.
*
* The script dynamically imports the runtime virtual module (resolved by the
* plugin's `resolveId`/`load` hooks) and calls `inject()` with the resolved
* overlay config. `overlayConfig` is JSON-serialized into the script so no
* additional request round-trip is needed.
*
* Mirrors vite-plugin-checker's preamble approach, minus its reconnect/buffer
* protocol (single linter needs no cross-reconnect resume).
*/
const composePreambleCode = ({ overlayConfig }) => `
import { inject } from "${RUNTIME_CLIENT_RUNTIME_PATH}";
inject({ overlayConfig: ${JSON.stringify(overlayConfig)} });
`;
//#endregion
//#region src/utils.ts
const getOptions = ({ test, dev, build, cache, include, exclude, eslintPath, formatter, lintInWorker, lintOnStart, lintDirtyOnly, emitError, emitErrorAsWarning, emitWarning, emitWarningAsError, customOverlay, ...eslintConstructorOptions }) => ({
test: test ?? false,
dev: dev ?? true,
build: build ?? false,
cache: cache ?? true,
include: include ?? ["src/**/*.{js,jsx,ts,tsx,vue,svelte}"],
exclude: exclude ?? ["node_modules", "virtual:"],
eslintPath: eslintPath ?? "eslint",
formatter: formatter ?? "stylish",
lintInWorker: lintInWorker ?? false,
lintOnStart: lintOnStart ?? false,
lintDirtyOnly: lintDirtyOnly ?? true,
emitError: emitError ?? true,
emitErrorAsWarning: emitErrorAsWarning ?? false,
emitWarning: emitWarning ?? true,
emitWarningAsError: emitWarningAsError ?? false,
customOverlay: customOverlay ?? false,
...eslintConstructorOptions
});
//#endregion
//#region src/index.ts
const debug = debugWrap(PLUGIN_NAME);
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const ext = extname(__filename);
const OVERLAY_DEGRADE_GRACE_MS = 2e3;
const RUNTIME_DIST_PATH = resolve(__dirname, "client-runtime.js");
let cachedRuntimeCode;
const getRuntimeCode = () => {
if (cachedRuntimeCode) return cachedRuntimeCode;
if (existsSync(RUNTIME_DIST_PATH)) {
cachedRuntimeCode = `${readFileSync(RUNTIME_DIST_PATH, "utf-8")}\n`;
return cachedRuntimeCode;
}
throw new Error(`[${PLUGIN_NAME}] custom overlay runtime bundle not found at ${RUNTIME_DIST_PATH}. Run the build before using customOverlay.`);
};
const wrapVirtualPrefix = (id) => `\0${id}`;
const getWorkerEnv = () => {
if (process.env.NO_COLOR || process.env.FORCE_COLOR !== void 0) return { ...process.env };
if (process.stdout.isTTY) return {
...process.env,
FORCE_COLOR: "1"
};
return { ...process.env };
};
function ESLintPlugin(userOptions = {}) {
const options = getOptions(userOptions);
const customOverlayEnabled = options.customOverlay !== false;
let worker;
let linter;
let logger;
let devServerRef;
let command;
let runtimeLoaded = false;
let degraded = false;
let warnedDegraded = false;
let pendingPayload;
let degradeTimer;
const pushOverlay = (server, payload) => {
if (!customOverlayEnabled) return;
pendingPayload = payload;
if (degraded) return;
if (runtimeLoaded) {
server.ws.send(WS_OVERLAY_EVENT, payload);
return;
}
if (!degradeTimer) degradeTimer = setTimeout(() => {
degradeTimer = void 0;
if (runtimeLoaded || degraded) return;
degraded = true;
if (!warnedDegraded) {
warnedDegraded = true;
logger?.warn(`[${PLUGIN_NAME}] custom overlay runtime did not load within the grace window. This environment (e.g. mini-program, SSR without index.html) is not supported. Falling back to terminal-only output. Set customOverlay: false to silence this.`);
}
}, OVERLAY_DEGRADE_GRACE_MS);
};
const makeEmit = () => ({ formattedText }) => {
console.log("");
console.log(formattedText);
};
const plugin = {
name: PLUGIN_NAME,
apply(config, { command }) {
debug("==== apply hook ====");
if (config.mode === "test" || process.env.VITEST) return options.test;
const shouldApply = command === "serve" && options.dev || command === "build" && options.build;
debug(`should apply this plugin: ${shouldApply}`);
return shouldApply;
},
configResolved(config) {
logger = config.logger;
command = config.command;
},
configureServer(server) {
devServerRef = server;
if (!customOverlayEnabled) return;
server.ws.on(WS_RUNTIME_LOADED_EVENT, () => {
runtimeLoaded = true;
if (degradeTimer) {
clearTimeout(degradeTimer);
degradeTimer = void 0;
}
if (pendingPayload !== void 0) server.ws.send(WS_OVERLAY_EVENT, pendingPayload);
});
},
async buildStart() {
debug("==== buildStart hook ====");
if (options.lintInWorker) {
if (worker) return;
debug("Initialize worker");
worker = new Worker(resolve(__dirname, `worker${ext}`), {
workerData: { options },
env: getWorkerEnv()
});
if (customOverlayEnabled) worker.on("message", (msg) => {
if (msg?.type === "overlay-payload") {
devServerRef?.ws.send(WS_OVERLAY_EVENT, msg.payload);
pendingPayload = msg.payload;
}
});
return;
}
debug("Initial ESLint");
const useCustomOverlayInServe = customOverlayEnabled && command === "serve";
linter = createLinter(options, {
emit: useCustomOverlayInServe ? makeEmit() : void 0,
onOverlayPayload: useCustomOverlayInServe ? (payload) => {
if (devServerRef) pushOverlay(devServerRef, payload);
} : void 0
});
if (options.lintOnStart) {
debug("Lint on start");
await linter.lintAll(this);
}
},
resolveId(id) {
if (id === "/@vite-plugin-eslint2-runtime" || id === "/@vite-plugin-eslint2-runtime-entry") return wrapVirtualPrefix(id);
},
load(id) {
if (id === wrapVirtualPrefix("/@vite-plugin-eslint2-runtime")) return getRuntimeCode();
if (id === wrapVirtualPrefix("/@vite-plugin-eslint2-runtime-entry")) return composePreambleCode({ overlayConfig: options.customOverlay });
},
transformIndexHtml() {
if (options.customOverlay === false) return;
if (command !== "serve") return;
return [{
tag: "script",
attrs: { type: "module" },
children: composePreambleCode({ overlayConfig: options.customOverlay })
}];
},
async transform(_, id) {
debug("==== transform hook ====");
if (worker) return worker.postMessage(id);
return await linter.lint(id, this);
},
async buildEnd() {
debug("==== buildEnd hook ====");
if (worker) await worker.terminate();
}
};
if (Vite.withFilter) return Vite.withFilter(plugin, { transform: { id: {
include: options.include,
exclude: options.exclude
} } });
return plugin;
}
//#endregion
export { ESLintPlugin as default };