From 481f27be5db031f5bb0f084d3a8f2fd58d584843 Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Tue, 8 Sep 2026 09:33:55 +0000 Subject: [PATCH 1/2] perf: match files with a compiled picomatch and walk with tinyglobby MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `globby` walked the file system and `micromatch` matched a path against the patterns — two packages for two jobs, but between them they carried `fast-glob`, `braces` and a second `picomatch`, and our `globby` was four majors behind the one `stylelint` already ships. `tinyglobby` walks with `fdir`, and `picomatch` is the matcher `micromatch` wraps, so neither is a new name in the tree. The production dependency tree drops from 66 packages to 44, and walking the file system is measurably faster (0.29 ms against 0.55 ms over the stylelint fixtures, best of five). The larger win is not the package count. `micromatch.isMatch(file, patterns)` compiles the patterns on every call, and the plugin calls it per module per check from `succeedModule`. Compiling once in `resolveCheck`, where the patterns are built, costs 0.0001 ms against 0.0043 ms a call. `dot` is kept on both, so a hidden directory reached by a wildcard is still linted; Node's own `fs.globSync` and `path.matchesGlob` were measured first and are both slower than what they would replace, and neither takes the option. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GzZci4NQeiqwdrVfd7dGXy --- .changeset/replace-glob-dependencies.md | 5 ++ package-lock.json | 102 ++++++++---------------- package.json | 7 +- src/index.js | 50 +++++++----- types/index.d.ts | 8 ++ 5 files changed, 79 insertions(+), 93 deletions(-) create mode 100644 .changeset/replace-glob-dependencies.md diff --git a/.changeset/replace-glob-dependencies.md b/.changeset/replace-glob-dependencies.md new file mode 100644 index 0000000..c7b4671 --- /dev/null +++ b/.changeset/replace-glob-dependencies.md @@ -0,0 +1,5 @@ +--- +"diagnostics-webpack-plugin": patch +--- + +Replace `globby` and `micromatch` with `tinyglobby` and `picomatch`, and compile the file matchers once per check rather than on every module. diff --git a/package-lock.json b/package-lock.json index c6cc788..c542784 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,20 @@ { - "name": "lint-webpack-plugin", - "version": "6.0.0", + "name": "diagnostics-webpack-plugin", + "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "lint-webpack-plugin", - "version": "6.0.0", + "name": "diagnostics-webpack-plugin", + "version": "0.0.0", "license": "MIT", "dependencies": { "@types/eslint": "^9.6.1", - "globby": "^11.1.0", "jest-worker": "^30.5.1", - "micromatch": "^4.0.8", "normalize-path": "^3.0.0", - "schema-utils": "^4.3.3" + "picomatch": "^4.0.4", + "schema-utils": "^4.3.3", + "tinyglobby": "^0.2.17" }, "devDependencies": { "@babel/cli": "^7.29.7", @@ -27,6 +27,7 @@ "@types/micromatch": "^4.0.10", "@types/node": "^26.4.1", "@types/normalize-path": "^3.0.2", + "@types/picomatch": "^4.0.3", "babel-plugin-transform-import-meta": "^2.3.3", "chokidar": "^5.0.0", "cross-env": "^10.1.0", @@ -5053,6 +5054,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -5066,6 +5068,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -5075,6 +5078,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -5738,6 +5742,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-iG0T6+nYJ9FAPmx9SsUlnwcq1ZVRuCXcVEvWnntoPlrOpwtSTKNDC9uVAxTsC3PUvJ+99n4RpAcNgBbHX3JSnQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -6566,15 +6577,6 @@ "dev": true, "license": "MIT" }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/array.prototype.findlast": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", @@ -7028,6 +7030,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -8355,18 +8358,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -10040,6 +10031,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -10056,6 +10048,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -10135,6 +10128,7 @@ "version": "1.20.3", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -10170,7 +10164,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -10272,6 +10265,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -10757,35 +10751,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/globjoin": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", @@ -11037,6 +11002,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -11374,6 +11340,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11431,6 +11398,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -11469,6 +11437,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -14285,6 +14254,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -14922,6 +14892,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -14935,6 +14906,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -15803,15 +15775,6 @@ "node": "20 || >=22" } }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -16219,6 +16182,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -16501,6 +16465,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -16511,6 +16476,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -17924,7 +17890,6 @@ "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -17941,6 +17906,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" diff --git a/package.json b/package.json index 9d980e9..0698bef 100644 --- a/package.json +++ b/package.json @@ -65,11 +65,11 @@ }, "dependencies": { "@types/eslint": "^9.6.1", - "globby": "^11.1.0", "jest-worker": "^30.5.1", - "micromatch": "^4.0.8", "normalize-path": "^3.0.0", - "schema-utils": "^4.3.3" + "picomatch": "^4.0.4", + "schema-utils": "^4.3.3", + "tinyglobby": "^0.2.17" }, "devDependencies": { "@babel/cli": "^7.29.7", @@ -82,6 +82,7 @@ "@types/micromatch": "^4.0.10", "@types/node": "^26.4.1", "@types/normalize-path": "^3.0.2", + "@types/picomatch": "^4.0.3", "babel-plugin-transform-import-meta": "^2.3.3", "chokidar": "^5.0.0", "cross-env": "^10.1.0", diff --git a/src/index.js b/src/index.js index c9d6029..722b6f2 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ import { isAbsolute, join } from "node:path"; -import globby from "globby"; -import micromatch from "micromatch"; +import picomatch from "picomatch"; +import { globSync } from "tinyglobby"; import createCheckRunner from "./check.js"; import { getOptions, validateOptions } from "./options.js"; @@ -12,9 +12,6 @@ import { writeOutputFile, } from "./utils.js"; -// `micromatch` is CommonJS, whose named exports ESM cannot always see -const { isMatch } = micromatch; - /** @typedef {import("webpack").Compilation} Compilation */ /** @typedef {import("webpack").Compiler} Compiler */ /** @typedef {import("webpack").Module} Module */ @@ -32,6 +29,8 @@ const { isMatch } = micromatch; * @property {CheckOptions} options options resolved for this check * @property {string[]} wanted the globs of the files to lint * @property {string[]} exclude the globs of the files not to lint + * @property {(file: string) => boolean} isWanted whether a path is one to lint + * @property {(file: string) => boolean} isExcluded whether a path is left out */ const LINT_PLUGIN = "DiagnosticsWebpackPlugin"; @@ -40,19 +39,16 @@ let compilerId = 0; /** * @param {Compiler} compiler compiler - * @param {string[]} wanted the globs of the files to lint - * @param {string[]} exclude the globs of the files not to lint + * @param {ResolvedCheck} check the check to collect the files of * @returns {string[]} the files on disk to lint */ -function collectFromFileSystem(compiler, wanted, exclude) { +function collectFromFileSystem(compiler, { wanted, exclude, ...check }) { if (!compiler.modifiedFiles) { - return globby.sync(wanted, { dot: true, ignore: exclude }); + return globSync(wanted, { absolute: true, dot: true, ignore: exclude }); } return [...compiler.modifiedFiles].filter( - (file) => - isMatch(file, wanted, { dot: true }) && - !isMatch(file, exclude, { dot: true }), + (file) => check.isWanted(file) && !check.isExcluded(file), ); } @@ -149,15 +145,24 @@ class DiagnosticsWebpackPlugin { ), }; + const wanted = parseFoldersToGlobs( + /** @type {string[]} */ (resolved.files), + resolved.extensions, + ); + const exclude = parseFoldersToGlobs( + /** @type {string[]} */ (resolved.exclude), + ); + return { name, adapter, options: resolved, - wanted: parseFoldersToGlobs( - /** @type {string[]} */ (resolved.files), - resolved.extensions, - ), - exclude: parseFoldersToGlobs(/** @type {string[]} */ (resolved.exclude)), + wanted, + exclude, + // Compiled here rather than per call: the two run on every module of + // every build, and matching by pattern recompiles them each time. + isWanted: picomatch(wanted, { dot: true }), + isExcluded: picomatch(exclude, { dot: true }), }; } @@ -206,11 +211,11 @@ class DiagnosticsWebpackPlugin { if (!file) return; - for (const { files, wanted, exclude, options } of fromModules) { + for (const check of fromModules) { + const { files, options } = check; const isFileNotListed = !files.includes(file); const isFileWanted = - isMatch(file, wanted, { dot: true }) && - !isMatch(file, exclude, { dot: true }); + check.isWanted(file) && !check.isExcluded(file); const isQueryNotExclude = /** @type {RegExp[]} */ ( options.resourceQueryExclude ).every((reg) => !reg.test(query)); @@ -231,11 +236,12 @@ class DiagnosticsWebpackPlugin { // Lint all files added compilation.hooks.finishModules.tap(this.key, () => { - for (const { adapter, files, wanted, exclude, runner } of runners) { + for (const check of runners) { + const { adapter, files, runner } = check; const filesToLint = adapter.filesSource === "modules" ? files - : collectFromFileSystem(compiler, wanted, exclude); + : collectFromFileSystem(compiler, check); if (filesToLint.length > 0) runner.lint(filesToLint); } diff --git a/types/index.d.ts b/types/index.d.ts index f211917..d9bb2c6 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -29,6 +29,14 @@ export type ResolvedCheck = { * the globs of the files not to lint */ exclude: string[]; + /** + * whether a path is one to lint + */ + isWanted: (file: string) => boolean; + /** + * whether a path is left out + */ + isExcluded: (file: string) => boolean; }; declare class DiagnosticsWebpackPlugin { /** From 96af375b12d1ff1326597e0fe6543c0dcde03766 Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Tue, 8 Sep 2026 09:55:17 +0000 Subject: [PATCH 2/2] refactor: turn separators into forward slashes without normalize-path `normalize-path` was called on one value, the result of `path.resolve`, which has already collapsed the duplicate separators and the trailing slash it exists to remove. What is left of it there is the separator swap, which is a `replaceAll`. It is also wrong twice on the shapes `resolve` returns on Windows, and both were measured before the swap: `\\server\share\a.css` came back as `/server/share/a.css`, no longer naming the share, and the drive root `C:\` came back as `C:`, which names whatever the drive's working directory is. Both keep their shape now, and a unit test pins all four shapes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GzZci4NQeiqwdrVfd7dGXy --- .changeset/replace-glob-dependencies.md | 2 +- package-lock.json | 10 +--------- package.json | 2 -- src/utils.js | 14 +++++++++++--- test/stylelint/threads.test.js | 8 +++----- test/utils.test.js | 17 ++++++++++++++++- types/utils.d.ts | 6 ++++++ 7 files changed, 38 insertions(+), 21 deletions(-) diff --git a/.changeset/replace-glob-dependencies.md b/.changeset/replace-glob-dependencies.md index c7b4671..98b8ea3 100644 --- a/.changeset/replace-glob-dependencies.md +++ b/.changeset/replace-glob-dependencies.md @@ -2,4 +2,4 @@ "diagnostics-webpack-plugin": patch --- -Replace `globby` and `micromatch` with `tinyglobby` and `picomatch`, and compile the file matchers once per check rather than on every module. +Replace `globby`, `micromatch` and `normalize-path` with `tinyglobby` and `picomatch`, and compile the file matchers once per check rather than on every module. diff --git a/package-lock.json b/package-lock.json index c542784..24609a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "@types/eslint": "^9.6.1", "jest-worker": "^30.5.1", - "normalize-path": "^3.0.0", "picomatch": "^4.0.4", "schema-utils": "^4.3.3", "tinyglobby": "^0.2.17" @@ -26,7 +25,6 @@ "@commitlint/config-conventional": "^21.2.2", "@types/micromatch": "^4.0.10", "@types/node": "^26.4.1", - "@types/normalize-path": "^3.0.2", "@types/picomatch": "^4.0.3", "babel-plugin-transform-import-meta": "^2.3.3", "chokidar": "^5.0.0", @@ -5735,13 +5733,6 @@ "undici-types": "~8.3.0" } }, - "node_modules/@types/normalize-path": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/normalize-path/-/normalize-path-3.0.2.tgz", - "integrity": "sha512-DO++toKYPaFn0Z8hQ7Tx+3iT9t77IJo/nDiqTXilgEP+kPNIYdpS9kh3fXuc53ugqwp9pxC1PVjCpV1tQDyqMA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-4.0.3.tgz", @@ -15236,6 +15227,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" diff --git a/package.json b/package.json index 0698bef..e268464 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "dependencies": { "@types/eslint": "^9.6.1", "jest-worker": "^30.5.1", - "normalize-path": "^3.0.0", "picomatch": "^4.0.4", "schema-utils": "^4.3.3", "tinyglobby": "^0.2.17" @@ -81,7 +80,6 @@ "@commitlint/config-conventional": "^21.2.2", "@types/micromatch": "^4.0.10", "@types/node": "^26.4.1", - "@types/normalize-path": "^3.0.2", "@types/picomatch": "^4.0.3", "babel-plugin-transform-import-meta": "^2.3.3", "chokidar": "^5.0.0", diff --git a/src/utils.js b/src/utils.js index 3526c0f..509d46a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -7,8 +7,6 @@ import { dirname, isAbsolute, resolve } from "node:path"; import { pathToFileURL } from "node:url"; -import normalizePath from "normalize-path"; - const nodeRequire = createRequire(import.meta.url); /** @typedef {import("webpack").Compiler} Compiler */ @@ -73,6 +71,15 @@ async function importFrom(specifier) { } } +/** + * Globs only know the forward slash, so a path is compared and matched as one. + * @param {string} file a path + * @returns {string} the path with its separators turned into forward slashes + */ +function toPosixPath(file) { + return file.replaceAll("\\", "/"); +} + /** * @param {string | string[]} files files * @param {string} context context @@ -80,7 +87,7 @@ async function importFrom(specifier) { */ function parseFiles(files, context) { return arrify(files).map((/** @type {string} */ file) => - normalizePath(resolve(context, file)), + toPosixPath(resolve(context, file)), ); } @@ -193,5 +200,6 @@ export { omitPluginOptions, parseFiles, parseFoldersToGlobs, + toPosixPath, writeOutputFile, }; diff --git a/test/stylelint/threads.test.js b/test/stylelint/threads.test.js index 89eba16..a369b18 100644 --- a/test/stylelint/threads.test.js +++ b/test/stylelint/threads.test.js @@ -3,11 +3,9 @@ import { createRequire } from "node:module"; import { join } from "node:path"; import { describe, it } from "node:test"; -// @ts-expect-error no types -import normalizePath from "normalize-path"; - import { lintFiles, setup } from "../../src/checks/stylelint-worker.js"; import { getLoadedStylelint } from "../../src/checks/stylelint.js"; +import { toPosixPath } from "../../src/utils.js"; import pack from "./utils/pack.js"; @@ -40,10 +38,10 @@ describe("Threading", () => { try { const [good, bad] = await Promise.all([ threaded.lintFiles( - normalizePath(join(import.meta.dirname, "fixtures/good/test.scss")), + toPosixPath(join(import.meta.dirname, "fixtures/good/test.scss")), ), threaded.lintFiles( - normalizePath(join(import.meta.dirname, "fixtures/error/test.scss")), + toPosixPath(join(import.meta.dirname, "fixtures/error/test.scss")), ), ]); assert.strictEqual(good[0].errored, false); diff --git a/test/utils.test.js b/test/utils.test.js index 00c7a1b..25f2af9 100644 --- a/test/utils.test.js +++ b/test/utils.test.js @@ -2,13 +2,28 @@ import assert from "node:assert/strict"; import { join } from "node:path"; import { describe, it } from "node:test"; -import { parseFiles, parseFoldersToGlobs } from "../src/utils.js"; +import { parseFiles, parseFoldersToGlobs, toPosixPath } from "../src/utils.js"; // `parseFoldersToGlobs` stats what it is given, so the fixtures have to exist. const directory = join(import.meta.dirname, "fixtures"); const file = join(import.meta.dirname, "fixtures", "good.js"); describe("utils", () => { + it("toPosixPath should turn every separator into a forward slash", () => { + assert.strictEqual(toPosixPath("/home/user/a.css"), "/home/user/a.css"); + assert.strictEqual( + toPosixPath(String.raw`C:\Users\me\a.css`), + "C:/Users/me/a.css", + ); + // A UNC share and a drive root keep their shape, which is what a path + // walked back to `\\server` or `C:` would no longer name. + assert.strictEqual( + toPosixPath(String.raw`\\server\share\a.css`), + "//server/share/a.css", + ); + assert.strictEqual(toPosixPath("C:\\"), "C:/"); + }); + it("parseFiles should return relative files from context", () => { const [all, packageA, packageB] = parseFiles( ["**/*", "../package-a/src/**/", "../package-b/src/**/"], diff --git a/types/utils.d.ts b/types/utils.d.ts index 3cbddf1..ea6e284 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -79,6 +79,12 @@ export function parseFoldersToGlobs( patterns: string | string[], extensions?: string | string[], ): string[]; +/** + * Globs only know the forward slash, so a path is compared and matched as one. + * @param {string} file a path + * @returns {string} the path with its separators turned into forward slashes + */ +export function toPosixPath(file: string): string; /** * @param {Compiler} compiler compiler * @param {string} name absolute file name