Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Repository files navigation

npm node tests coverage discussion size

diagnostics-webpack-plugin

This plugin only supports webpack >= 5.106.0 and Node.js >= 22.12.0.

This plugin runs linters, type checkers and other diagnostic tools over your sources during the webpack build and reports what they find as webpack errors and warnings.

It replaces eslint-webpack-plugin and stylelint-webpack-plugin: one plugin, one place to configure how problems are reported, and one pass over your project. Today it runs ESLint, Stylelint, oxlint, Biome and TypeScript; more linters and diagnostic tools are meant to be added the same way.

Getting Started

To begin, you'll need to install diagnostics-webpack-plugin:

npm install diagnostics-webpack-plugin --save-dev

or

yarn add -D diagnostics-webpack-plugin

or

pnpm add -D diagnostics-webpack-plugin

Note

Install the tools you want to run as well — the plugin only requires the ones you enable. It supports eslint >= 9, stylelint >= 17, oxlint >= 1, @biomejs/biome >= 2 and typescript >= 5:

npm install eslint stylelint oxlint @biomejs/biome typescript --save-dev

Then add the plugin to your webpack configuration and enable a check for each language you want inspected:

import DiagnosticsPlugin from "diagnostics-webpack-plugin";

export default {
  // ...
  plugins: [
    new DiagnosticsPlugin({
      checks: [
        { use: "eslint", extensions: ["js", "mjs"] },
        { use: "stylelint", extensions: ["css", "scss"] },
      ],
    }),
  ],
  // ...
};

The package ships an ECMAScript build next to a CommonJS one, so a CommonJS configuration works just as well:

const DiagnosticsPlugin = require("diagnostics-webpack-plugin");

Options

The plugin options have three layers:

Layer Where it goes What it covers
Plugin Top level only How the plugin schedules its work, for every check at once.
Shared Top level or in a checks entry Which files are linted and how problems are reported. An entry overrides what it sets.
Check In a checks entry Options only that tool understands, plus everything its own Node.js API accepts.

The options are checked against their schema from webpack's own validate hook, so a mistake is reported when webpack validates the rest of your configuration, and validate: false turns the check off along with webpack's.

Every check to run is an entry in checks, named by its use. The list may name the same tool more than once, so one instance can inspect two file sets under different configurations.

Every option

Option Layer What it decides
checks Plugin Which checks run, and the options only each of them understands.
context Plugin The folder every relative files and exclude pattern is resolved against.
lintOnStart Plugin Whether the first compilation checks everything it covers.
cache Shared Whether the tool keeps a cache of its own between runs.
cacheLocation Shared Where that cache is written.
exclude Shared What is left out.
extensions Shared Which extensions a named folder is walked for.
files Shared What to check: naming it checks every file it matches, built or not.
fix Shared Whether the tool writes back what it can fix.
formatter Shared How results are turned into the message that is reported.
outputReport Shared A file the same results are written to.
reportAs Shared What the build carries: an error, a warning, a log line, or nothing.
resourceQueryExclude Shared Which module queries are left out.
threads Shared How wide the work is spread.
configType, eslintPath Check ESLint's own.
stylelintPath Check Stylelint's own.
oxlintPath, configFile, args Check oxlint's own.
biomePath, command, configFile, args Check Biome's own.
typescriptPath, configFile, compilerOptions, build, diagnosticOptions, ignoreDiagnostics Check TypeScript's own.

Anything else written in a checks entry is handed to the tool itself, so its own Node.js API options go next to these.

new DiagnosticsPlugin({
  // Plugin options
  context: "src",
  // Shared options, every check uses them unless it says otherwise
  reportAs: "warning", // report every check's results as warnings
  exclude: ["node_modules", "vendor"],
  // The checks to run, each with the options only it understands
  checks: [
    { use: "eslint", extensions: ["js", "ts"], fix: true },
    { use: "stylelint", extensions: ["css", "scss"], threads: true },
  ],
});

Plugin options

checks

  • Type:
type checks = (string | ({ use: string | CheckAdapter } & Options))[];
  • Default: none — this is the one option the plugin requires

The checks to run. An entry is the name of a built-in check, or an object naming it under use next to the options it takes, so these two are the same thing:

new DiagnosticsPlugin({ checks: ["eslint", { use: "eslint" }] });

Naming the same tool twice runs it twice, which is how one build checks two file sets under two configurations:

new DiagnosticsPlugin({
  checks: [
    { use: "eslint", files: "src", fix: true },
    { use: "eslint", files: "scripts", reportAs: "warning" },
  ],
});

use also takes an adapter of your own rather than a built-in name — see Adding a check.

context

  • Type:
type context = string;
  • Default: compiler.context

Base directory for linting. Every relative files and exclude pattern is resolved against it.

lintOnStart

  • Type:
type lintOnStart = boolean;
  • Default: true

Whether the first compilation lints every file it covers. Leave it alone and a build lints everything while a watch run lints everything once and then only what webpack rebuilds.

Set it to false to start a watch run quiet: nothing is linted until you touch a file, and only the modules webpack rebuilds are reported. A build is nothing but a first compilation, so it lints either way — the option cannot silence one.

Running the checks in one mode only

There is no option for this and none is needed: plugins ignores a falsy entry, so && decides whether the plugin is there at all. A configuration function is handed the --env values, and webpack-cli states the mode in them:

Value Set by
WEBPACK_SERVE webpack serve
WEBPACK_WATCH webpack watch and webpack --watch
WEBPACK_BUILD, WEBPACK_BUNDLE webpack, a build that runs once
import DiagnosticsPlugin from "diagnostics-webpack-plugin";
import { defineConfig } from "webpack";

export default defineConfig((env) => ({
  plugins: [
    // Both values are read because each command sets only its own: `serve` sets
    // `WEBPACK_SERVE` and `--watch` sets `WEBPACK_WATCH`, so asking for one of
    // them misses the other way of watching. Read `WEBPACK_BUILD` instead to
    // check a build that runs once and leave a watch run alone.
    (env.WEBPACK_SERVE || env.WEBPACK_WATCH) &&
      new DiagnosticsPlugin({ checks: ["eslint", "stylelint"] }),
  ],
}));

This is worth doing when something else already lints the same files — a CI job running eslint ., or an editor — and the release build need not pay for it twice.

Two configurations at once

A client and a server configuration lint the same sources twice. Where two compilers run a check configured the same way over the same files — the whole of it for a check reading from the file system, whatever they both built for one reading the module graph — the second joins the run the first is making rather than starting its own, and both compilations report what it finds and watch what it read.

Over three hundred files with the typescript check, two compilers build one program rather than two: 1.52 s rather than 1.79 s, and 248 MB rather than 298 MB. What a compiler checks alone it still checks alone, and a check configured differently in the second configuration runs on its own.

Shared options

These can be set at the top level, where they apply to every check, or inside one check, where they apply to that check alone.

cache

  • Type:
type cache = boolean;
  • Default: true

The cache is enabled by default to decrease execution time.

cacheLocation

  • Type:
type cacheLocation = string;
  • Default: node_modules/.cache/diagnostics-webpack-plugin/.<tool>cache

Specify the path to the cache location. Can be a file or a directory.

files

  • Type:
type files = string | string[];
  • Default: options.context

Specify directories, files, or globs. Must be relative to options.context. Directories are traversed recursively looking for files matching options.extensions. File and glob patterns ignore options.extensions.

Naming them says what to check, so every file they match is checked whether or not webpack built it — a module nothing imports yet, or one reached through a loader webpack resolves differently, is checked all the same. Leave it unset and a check reads whatever it reads by itself: ESLint the modules webpack built, Stylelint a walk of the context.

In a watch run the folder a check takes its files from is watched, so a file you add there is checked without anything else having to change — and for the typescript check, so is every folder the tsconfig.json include covers. A folder is only watched when the whole of it can be: webpack rebuilds on a change anywhere under one, so a folder holding what the build writes (output.path) or what the check itself leaves out (exclude, node_modules by default) is left alone rather than turning every write into a rebuild. Pointing files at your sources rather than leaving it at the project root is what makes it watchable.

extensions

  • Type:
type extensions = string | string[];
  • Default: 'js' for ESLint, ['css', 'scss', 'sass'] for Stylelint

Specify file extensions that should be checked.

exclude

  • Type:
type exclude = string | string[];
  • Default: 'node_modules', plus output.path for Stylelint

Specify the files/directories to exclude. Must be relative to options.context.

threads

  • Type:
type threads = boolean | number | "auto";
  • Default: "auto"

How many threads a check spreads its work over. "auto" takes one fewer than the machine has, leaving webpack the thread it builds on; a number asks for that many; false, 0 or 1 keeps the work on webpack's own thread.

A check that threads its own work is asked to, and one that cannot is run in a pool of workers — so the option means the same thing whatever is being checked, and a check added later gets it for nothing. ESLint threads its own work from 9.34.0 under flat config; Stylelint threads none of its own, so it is pooled. TypeScript reads a program whole rather than a file at a time, so it is run on one worker: what a thread buys it is webpack's own thread back.

Prefer "auto" over a count. A check sizes "auto" against the machine, where a fixed number can end up competing with webpack for the same cores: over three hundred modules on four of them, "auto" took about a fifth off the build while asking for three was no better than asking for none. A count above what the machine has is held to it, since asking for sixteen threads on four cores took twice as long as asking for none — the ceiling cannot save a number written against the tool itself, such as ESLint's concurrency, which is passed through as written.

Anything written against the tool itself wins — ESLint's own concurrency, say — and a check configured with a function, such as a formatter written in the configuration, is run on webpack's thread, because a function cannot be handed to a worker.

resourceQueryExclude

  • Type:
type resourceQueryExclude = RegExp | RegExp[];
  • Default: []

Specify the resource query to exclude. Only affects checks that read the module graph, such as ESLint.

fix

  • Type:
type fix = boolean;
  • Default: false

Will enable the autofix feature of the tool.

Be careful: this option will modify source files.

formatter

  • Type:
type formatter = string | ((results: LintResult[]) => string);
  • Default: the tool's own default formatter

Accepts the name of a formatter the tool ships, or a function that receives its results and returns the output as a string.

See the ESLint formatters and the Stylelint formatter option.

Errors and warnings

Every check reports its errors as webpack errors and its warnings as webpack warnings, which is what fails the build. reportAs overrides that.

reportAs

  • Type:
type reportAs = Severity | { errors?: Severity; warnings?: Severity };
type Severity = "error" | "warning" | "log" | false;
  • Default: unset — each result stays at the severity the check gave it

What a check reports its results as. One value covers its errors and its warnings alike; an object sets them apart, and a severity the object leaves out keeps its own:

Value Effect
unset Errors fail the build, warnings do not.
"error" Everything fails the build, warnings included.
"warning" Nothing fails the build; errors are reported as warnings.
"log" The terminal alone, through webpack's log.
false Nothing is reported. An outputReport is still written.
{ warnings: false } The errors alone, still failing the build.
{ warnings: "error" } Warnings fail the build too, and errors keep failing it.
{ errors: "warning" } Errors stop failing the build, and warnings stay warnings.
new DiagnosticsPlugin({
  reportAs: { warnings: false }, // the errors alone
  checks: [{ use: "eslint" }],
});

"log" is for a check nobody should be stopped by yet. Its results reach webpack's log — the terminal, and stats.logging — rather than compilation.errors or compilation.warnings, so the build carries neither, stats.hasWarnings() stays false and a dev server overlays nothing. An outputReport is written either way.

new DiagnosticsPlugin({
  // the whole project is linted, and only the new check is advisory
  checks: [{ use: "eslint" }, { use: "typescript", reportAs: "log" }],
});

A check nothing can be reported from is one a watch rebuild does not wait for: every reportAs that leaves both errors and warnings off the compilation — "log", false, or an object spelling both out — with no outputReport to write. The first build still waits, since it is what tells the watcher which files the check reads. From the rebuild on, the check is run once the build is over rather than beside it, and what it finds is printed through webpack's infrastructure log rather than through stats.logging.

The work is moved out of the rebuild rather than off the machine: an edit arriving while such a check is still running waits for the rest of it, since a check holds the thread the build runs on. A report a newer rebuild has already overtaken is dropped, and a check never has two of its runs going at once — the next starts when the last has reported.

ignoreDiagnostics

  • Type:
type ignoreDiagnostics =
  IgnoreOne | IgnoreOne[] | ((message: Message) => boolean);

type IgnoreOne = number | string | IgnoreMatch;

interface IgnoreMatch {
  file?: string | undefined;
  code?: string | undefined;
  severity?: "error" | "warning" | undefined;
}

interface Message {
  file?: string | undefined;
  code?: string | undefined;
  severity: "error" | "warning";
  text: string;
}
  • Default: unset

What not to report. A linter's rule can be turned off in its own configuration and a TypeScript diagnostic cannot, so this is where one thing a check found is left out whichever check found it — for adopting a check on a project that is not clean yet, without turning the check off altogether.

Written as Leaves out
2307 The TypeScript code, as tsc prints it after TS.
"no-unused-vars" The rule or code a check names.
{ file, code, severity } What all three match; one left out matches everything.
[…] Anything the list matches.
(message) => boolean Whatever the function answers true for.
new DiagnosticsPlugin({
  checks: [
    // TS2307 is `Cannot find module`, TS7016 an untyped dependency
    { use: "typescript", ignoreDiagnostics: [2307, 7016] },
    // a rule that has not been dealt with in one folder yet
    {
      use: "eslint",
      ignoreDiagnostics: { code: "no-unused-vars", file: "src/legacy/**" },
    },
  ],
});

A file is a glob matched against the path relative to context, as exclude is. Something a check reported of no file in particular — a configuration it could not read — is not a file's to leave out, so a match naming one keeps it.

What is left out is left out of everything the check reports, an outputReport included, and of the counts a formatter prints. A check shipped outside this package answers for its own results, so it reads this option once it says how (see Adding a check).

outputReport

  • Type:
type outputReport =
  | boolean
  | {
      filePath?: string | undefined;
      formatter?: (string | ((results: LintResult[]) => string)) | undefined;
    };
  • Default: false

Write the results to a file, for example a checkstyle xml file for use for reporting on Jenkins CI.

  • filePath: path to the output report file, relative to output.path unless absolute.
  • formatter: a different formatter for the output file; the default/configured formatter is used when none is passed in.

Set at the top level, every check appends its report to the same file. Set it inside a checks entry to give that check a file of its own.

new DiagnosticsPlugin({
  checks: [
    {
      use: "eslint",
      outputReport: { filePath: "eslint.json", formatter: "json" },
    },
    {
      use: "stylelint",
      outputReport: { filePath: "stylelint.json", formatter: "json" },
    },
  ],
});

ESLint

Run with { use: "eslint" }. It lints the files webpack builds, so only the modules that end up in the bundle are checked.

Alongside the shared options you can pass any ESLint Node.js API option — they are handed to the ESLint class as they are.

threads reaches ESLint as its own concurrency from 9.34.0 under flat config, and an older ESLint or an eslintrc one is pooled instead. Write concurrency yourself and that is what ESLint is given, whatever threads says. Note that webpack spells this idea parallelism, and uses the word concurrency for something else again — bounded work on one thread.

A rebuild lints only the files webpack rebuilt and reports the rest from the previous run, so lintOnStart is only worth setting to start a watch run quiet.

configType

  • Type:
type configType = "flat" | "eslintrc";
  • Default: flat

Specify the type of configuration to use with ESLint.

  • flat is the current standard configuration format.
  • eslintrc is the legacy configuration format and has been officially deprecated.

The new configuration format is explained in its own documentation.

eslintrc needs an ESLint that still reads one: ESLint 9 does, ESLint 10 removed it, and the option stays for as long as this plugin supports 9. The check loads ESLint the same way wherever it runs it, in the build's own thread and in a worker of a pool alike, and the ESLint 9 job in CI checks both.

eslintPath

  • Type:
type eslintPath = string;
  • Default: eslint

Path to the eslint instance that will be used for linting.

If the eslintPath is a folder like the official ESLint, or you specify a formatter option, you don't have to install eslint.

Suppressions

Bulk suppressions are supported: enable ESLint's own applySuppressions, and point suppressionsLocation at the file if it is not the default eslint-suppressions.json.

new DiagnosticsPlugin({
  checks: [{ use: "eslint", applySuppressions: true }],
});

Important

ESLint resolves the suppressions file, and every path recorded inside it, against its own cwd — not against the plugin's context. Where the two differ, pass cwd to the check as well:

new DiagnosticsPlugin({
  context: "src",
  checks: [
    { use: "eslint", applySuppressions: true, cwd: import.meta.dirname },
  ],
});

Suppressions need ESLint 9.24 or later. ESLint 10 takes both options itself; below that they reach its CLI alone, so the plugin applies the suppressions after linting instead — the same file, the same paths, the same result.

Stylelint

Run with { use: "stylelint" }, and requires stylelint >= 17. It lints every file matching files and extensions on disk, whether or not webpack imported it, so a stylesheet nothing imports yet is still checked.

Alongside the shared options you can pass any Stylelint option — they are handed to stylelint.lint() as they are.

stylelintPath

  • Type:
type stylelintPath = string;
  • Default: stylelint

Path to the stylelint instance that will be used for linting.

oxlint

Run with { use: "oxlint" }, and requires oxlint >= 1. It lints the files webpack builds, as the ESLint check does, and reads whatever .oxlintrc.json oxlint finds for itself.

new DiagnosticsPlugin({ checks: ["eslint", "oxlint"] });

oxlint is a binary behind a Node entry rather than a library, so this check runs it and reads the JSON it answers with. Two things follow. Its severities are oxlint's own — a rule set to "error" in .oxlintrc.json is reported as a webpack error and one set to "warn" as a warning, and reportAs moves them from there as it does for every check. And the options this check does not name are not guessed at: write them as args, the flags oxlint itself documents.

oxlintPath

  • Type:
type oxlintPath = string;
  • Default: oxlint

Path to the oxlint instance that will be used for linting.

configFile

  • Type:
type configFile = string;
  • Default: unset, leaving oxlint to find its own

Path to the .oxlintrc.json to lint with.

args

  • Type:
type args = string[];
  • Default: []

Arguments passed to oxlint as they are, for the flags this check does not name — ["--deny", "correctness"], say. Not --format: the check asks for JSON and formats the results itself, and oxlint declines being asked twice.

Biome

Run with { use: "biome" }, and requires @biomejs/biome >= 2. It checks the files webpack builds and reads whatever biome.json Biome finds for itself, reporting at Biome's own severities.

new DiagnosticsPlugin({ checks: ["biome"] });

Like the oxlint check this runs a binary and reads the JSON it answers with, which Biome calls an experimental reporter — a release of its own may move the shape, and it is the only reporter carrying the severities this check needs.

biomePath

  • Type:
type biomePath = string;
  • Default: @biomejs/biome

Path to the @biomejs/biome instance that will be used for checking.

command

  • Type:
type command = "lint" | "check";
  • Default: "lint"

Which of Biome's commands to run. "lint" runs the linter; "check" adds its formatting and assist diagnostics, so a badly formatted file is reported as well as one breaking a rule.

configFile

  • Type:
type configFile = string;
  • Default: unset, leaving Biome to find its own

Path to the biome.json to check with.

args

  • Type:
type args = string[];
  • Default: []

Arguments passed to Biome as they are, for the flags this check does not name. Not --reporter: the check asks for JSON and formats the results itself.

TypeScript

Run with { use: "typescript" }, and requires typescript >= 5. It type checks the program a tsconfig.json describes, so every file that config includes is checked whether or not webpack built it, and a type error in a module nothing imports yet is still reported. Emit is forced off: webpack writes the output.

new DiagnosticsPlugin({
  checks: [{ use: "typescript", configFile: "tsconfig.build.json" }],
});

Project references are read rather than built unless build says otherwise, which is what a tsconfig.json listing only references needs.

Two things differ from the linters. A diagnostic belongs to the program rather than to one file, so there is nothing to report a single file from. And extensions only decides which files make the check run at all; what is checked is whatever the config file includes.

A program is read whole, so threads buys it webpack's own thread back rather than a share of the work: the check runs on one worker while webpack builds, and is read where it is reported. Over a three-hundred-file solution beside three hundred modules that took the build from 2149 ms to 1920 ms and the module graph from 2004 ms to 385 ms, and an edit-to-report in watch from 259 ms to 203 ms. A small project pays the thread's start without much to hide behind it; threads: false keeps the work where webpack is.

While webpack watches, the program is kept and handed to the build after it, so a rebuild type checks what the change reaches rather than the project over again — about 30 ms rather than 300 over three hundred files, for the memory the program holds on to (some 50 MB there). A change to the config file, or to the compiler options given here, starts a new one.

Alongside the shared options you can pass any compiler option — they override what the config file sets, as --strict would on the command line.

typescriptPath

  • Type:
type typescriptPath = string;
  • Default: typescript

Path to the typescript instance that will be used for checking.

configFile

  • Type:
type configFile = string;
  • Default: the nearest tsconfig.json at or above context

Path to the tsconfig.json that describes the program.

compilerOptions

  • Type:
type compilerOptions = object;
  • Default: unset

Compiler options overriding the ones the config file sets. The same as writing them at the top level of the check, and useful when a name collides with one of the plugin's own.

build

  • Type:
type build = boolean;
  • Default: false

Build the projects the config file references before reporting, the way tsc -b does. A tsconfig.json that only lists references describes an empty program of its own, so without this a solution reports nothing at all; with it, every project in the graph is checked in the order its references say.

new DiagnosticsPlugin({
  checks: [{ use: "typescript", build: true }],
});

A reference is read through the declarations the referenced project publishes, so building one is how the next is checked at all — the build therefore writes each project's .d.ts and its .tsbuildinfo, and nothing else. The JavaScript stays webpack's to write: a later tsc -b of your own still emits it.

The cost is a tsc -b pass rather than the kept program the check uses otherwise. Over five projects and 205 files: 573 ms with nothing built yet, 4–8 ms when the solution is up to date, 258 ms after one file changed, and around 200 ms on every rebuild for as long as an error stands, since a project that fails is never up to date. Pair it with reportAs: "log" if you would rather a watch rebuild did not wait for that.

diagnosticOptions

  • Type:
interface diagnosticOptions {
  syntactic?: boolean | undefined;
  semantic?: boolean | undefined;
  declaration?: boolean | undefined;
  global?: boolean | undefined;
}
  • Default: every kind reported

Which kinds of diagnostic to report. Turning one off asks TypeScript for less rather than dropping what it answered, so it is also the one filter that saves the work — semantic: false is what makes a check that only wants syntax errors cheap.

new DiagnosticsPlugin({
  // webpack's own parser reports a syntax error in a file it builds
  checks: [{ use: "typescript", diagnosticOptions: { syntactic: false } }],
});

What the tsconfig.json itself is wrong about is reported whatever is turned off here: with the config file misread, nothing below it would be answering the right question. build reports the whole of what it finds either way, since a solution is built rather than asked kind by kind.

fork-ts-checker-webpack-plugin spells this option the same way, with semantic alone on by default.

Adding a check

A use may also be an adapter of its own rather than a built-in name, so a check can ship as its own package without an entry in this one:

new DiagnosticsPlugin({
  checks: [
    { use: require("diagnostics-webpack-plugin-typescript"), strict: true },
  ],
});

Such an adapter is an object with a name, and a create returning the five functions the plugin drives it through — what to lint, what came back, which results are errors and which warnings, how to format them, and what to release afterwards. It splits its results by their own severity and nothing else; reportAs is applied to what it returns:

module.exports = {
  name: "made-up",
  // "modules" lints the files webpack built, "glob" every file matching `files`
  filesSource: "glob",
  // Merged under the options the user passes, and under the shared options
  defaults: { extensions: ["ts"] },
  async create({ key, options, compilation }) {
    return {
      lintFiles: async (files) => runTheTool(files),
      getResults: async (results) => results,
      splitResults: (results) => ({ errors: results, warnings: [] }),
      getFormatter: async (formatter) => async (results) => format(results),
      cleanup: async () => {},
    };
  },
};

label, filesSource, defaults, defaultExclude and schema are optional; the plugin fills in the defaults of a module-scanning check that excludes node_modules.

Migrating

Both plugins become one, and every option they had is still here. What changed is where an option is written and how the four that decided severity are spelled.

Where an option goes. context, lintOnStart and checks are the plugin's own and stay at the top level. Everything else is shared: write it at the top level to cover every check, or inside a checks entry to cover that one. configType, eslintPath and stylelintPath belong to a single check and go in its entry.

Severity is one option. emitError, emitWarning, failOnError, failOnWarning and quiet are reportAs, because reporting a result as a webpack error is what fails the build:

Was Is
quiet: true, emitWarning: false reportAs: { warnings: false }
emitError: false reportAs: { errors: false }
emitError: false and emitWarning: false reportAs: false
failOnError: true the default
failOnError: false reportAs: "warning"
failOnWarning: true reportAs: { warnings: "error" }

The build is no longer aborted from inside the plugin. A result reported as a webpack error fails the build the way every other webpack error does — stats.hasErrors() is true and the CLI exits non-zero — and the assets are still written. Nothing about severity depends on mode any more.

Requirements. Node >= 22.12, webpack >= 5.106, and ESLint 9 or 10 / Stylelint 17 for whichever checks you run.

From eslint-webpack-plugin

-const ESLintPlugin = require("eslint-webpack-plugin");
+const DiagnosticsPlugin = require("diagnostics-webpack-plugin");

 module.exports = {
   plugins: [
-    new ESLintPlugin({ extensions: ["js"], fix: true }),
+    new DiagnosticsPlugin({
+      checks: [{ use: "eslint", extensions: ["js"], fix: true }],
+    }),
   ],
 };

Every option eslint-webpack-plugin accepted, and where it is now:

Option Now
cache Unchanged, shared.
cacheLocation Unchanged, shared. The default moved to node_modules/.cache/diagnostics-webpack-plugin/.eslintcache.
configType Unchanged, in the eslint entry.
context Unchanged, top level.
emitError reportAs, see the table above.
emitWarning reportAs, see the table above.
eslintPath Unchanged, in the eslint entry.
exclude Unchanged, shared.
extensions Unchanged, shared. Still defaults to js.
failOnError reportAs. It defaulted to on outside development mode; the default no longer depends on mode.
failOnWarning reportAs, see the table above.
files Unchanged, shared.
fix Unchanged, shared.
formatter Unchanged, shared.
lintDirtyModulesOnly lintOnStart, inverted: lintDirtyModulesOnly: true is lintOnStart: false. Top level.
outputReport Unchanged, shared. It is still written even when reportAs is false.
quiet reportAs: { warnings: false }.
resourceQueryExclude Unchanged, shared.

Any other option is passed to ESLint itself, as before.

From stylelint-webpack-plugin

-const StylelintPlugin = require("stylelint-webpack-plugin");
+const DiagnosticsPlugin = require("diagnostics-webpack-plugin");

 module.exports = {
   plugins: [
-    new StylelintPlugin({ extensions: ["css"], threads: true }),
+    new DiagnosticsPlugin({
+      checks: [{ use: "stylelint", extensions: ["css"], threads: true }],
+    }),
   ],
 };

Every option stylelint-webpack-plugin accepted, and where it is now:

Option Now
cache Unchanged, shared.
cacheLocation Unchanged, shared. The default moved to node_modules/.cache/diagnostics-webpack-plugin/.stylelintcache.
context Unchanged, top level.
emitError reportAs, see the table above.
emitWarning reportAs, see the table above.
exclude Unchanged, shared.
extensions Unchanged, shared. Still defaults to css, scss and sass.
failOnError reportAs. It defaulted to on in every mode, and the default is still to fail on an error.
failOnWarning reportAs, see the table above.
files Unchanged, shared.
formatter Unchanged, shared.
lintDirtyModulesOnly lintOnStart, inverted: lintDirtyModulesOnly: true is lintOnStart: false. Top level.
outputReport Unchanged, shared. It is still written even when reportAs is false.
quiet reportAs: { warnings: false }.
stylelintPath Unchanged, in the stylelint entry.
threads Shared now, and "auto" by default rather than off. Every check honours it.

Any other option is passed to Stylelint itself, as before. Two more things changed for Stylelint alone:

  • Stylelint 17 or later is required. stylelint-webpack-plugin accepted 13 through 17; the merged plugin drops the older majors rather than carrying their compatibility branches forward. Stylelint 17 itself needs Node >= 20.19.
  • Errors and warnings are no longer swapped. failOnError: false used to report errors as webpack warnings, and failOnWarning: true to report warnings as webpack errors. Each result now keeps its own severity unless reportAs says otherwise — which is what those two spellings in the table above do, explicitly.

fix is a documented option now rather than one passed through to Stylelint unnamed. resourceQueryExclude is shared but has no effect here: it reads the query of a module webpack built, and Stylelint is given the files matching files instead.

Running both

The two plugins become one instance, and options they had in common are written once:

 module.exports = {
   plugins: [
-    new ESLintPlugin({ context: "src", failOnError: true, extensions: ["js"] }),
-    new StylelintPlugin({ context: "src", failOnError: true, extensions: ["css"] }),
+    new DiagnosticsPlugin({
+      context: "src",
+      checks: [
+        { use: "eslint", extensions: ["js"] },
+        { use: "stylelint", extensions: ["css"] },
+      ],
+    }),
   ],
 };

From fork-ts-checker-webpack-plugin

The typescript check does what that plugin does — type check the program a tsconfig.json describes, rather than the files webpack happens to build — and is written as one entry in checks:

-const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
+const DiagnosticsPlugin = require("diagnostics-webpack-plugin");

 module.exports = {
   plugins: [
-    new ForkTsCheckerWebpackPlugin({
-      typescript: { configFile: "tsconfig.build.json", build: true },
-    }),
+    new DiagnosticsPlugin({
+      checks: [
+        { use: "typescript", configFile: "tsconfig.build.json", build: true },
+      ],
+    }),
   ],
 };

The check runs in the build's own process. That plugin forks one, which is why it has a memoryLimit and a profile of its own; here the program is kept between rebuilds instead, so a rebuild type checks what the change reaches rather than the project again. What that costs and saves is under the TypeScript check.

fork-ts-checker-webpack-plugin Here
async Not an option: the check runs on a worker thread while webpack builds (threads), and a check the compilation carries nothing of — reportAs "log" or false, with no outputReport — is run after a watch rebuild rather than during it.
typescript.configFile configFile, in the entry — see the TypeScript check.
typescript.context context, the plugin's own.
typescript.build build.
typescript.configOverwrite compilerOptions, or any compiler option written at the top of the entry.
typescript.typescriptPath typescriptPath.
typescript.mode Not an option: nothing is written without build, and build writes the declarations a referenced project publishes and nothing else.
typescript.memoryLimit, profile Nothing to set — the check is not a forked process.
typescript.diagnosticOptions diagnosticOptions, spelled the same way. Every kind is reported here unless you turn one off; there, only semantic is on to begin with.
issue.include, issue.exclude ignoreDiagnostics, which takes the same match of a file, a code and a severity, or a function of your own. It says what to leave out, so an include is written as the exclude of everything else.
formatter formatter. Unset, TypeScript's own formatter is used, with color and the source line.
logger Webpack's logger is what the plugin writes to; reportAs: "log" is what sends results there rather than onto the compilation.
devServer Nothing to set: results reach the compilation, so a dev server overlays them, and reportAs: "log" keeps them off it.

Running it next to a linter is one plugin rather than two, with one place to say how what they find is reported:

new DiagnosticsPlugin({
  checks: [{ use: "eslint" }, { use: "typescript" }],
});

Changelog

Changelog

Contributing

We welcome all contributions!

If you're new here, please take a moment to review our contributing guidelines.

CONTRIBUTING

License

MIT

About

A ESLint plugin for webpack

Resources

Code of conduct

Contributing

Security policy

Stars

258 stars

Watchers

10 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages