From 11a455601f8e66dc431465f6da585edbf67f9f2f Mon Sep 17 00:00:00 2001 From: Iulian Meghea Date: Fri, 31 Jul 2026 05:16:56 +0000 Subject: [PATCH 1/3] build(deps): bump 5 deps to latest major + fix ESM module loading Major bumps (Batch A): - mime-types 2->3 (Node >=18, mime-score resolution; lookup() API unchanged) - cosmiconfig 8->9 (add searchStrategy: 'global' to preserve upward dir traversal) - p-queue 8->9 (Node >=20; API surface unchanged) - sugarss 4->5 (Node >=18; parser interface unchanged) - postcss-custom-properties 14->15 (Node >=20.19; ESM-only, options unchanged) - @types/mime-types 2->3 (matching types; lookup() signature identical) - engines.node: >=20.19.0 (was already required by sass 1.102 from Phase 1) Bug fixes required by postcss-custom-properties v15 (ESM-only with exports): - fix(resolve): handle array returns from resolveExports in packageFilterBuilder (resolveExports returns string[] for packages with nested exports maps; the existing code only checked typeof === 'string', missing array results) - fix(load-module): make async with ESM fallback via dynamic import() (require() of .mjs fails in jest VM context; fall back to await import()) Uses packageFilterBuilder() instead of no-op packageFilter to resolve packages that only have an exports field (no main/module) - fix(options): make ensurePCSSOption/ensurePCSSPlugins async - fix(postcss/index): resolve parser/syntax/stringifier/plugins lazily in async process() instead of eagerly in sync plugin factory - fix(config): await async ensurePCSSOption/ensurePCSSPlugins calls - fix(types): postcss sub-options accept string (resolved lazily) - fix(tests): update load-module/ensurePCSSOption tests to async --- __tests__/utils.test.ts | 16 +- package-lock.json | 324 +++++++++------------------------- package.json | 14 +- src/index.ts | 11 +- src/loaders/postcss/config.ts | 12 +- src/loaders/postcss/index.ts | 14 +- src/types.ts | 8 +- src/utils/load-module.ts | 16 +- src/utils/options.ts | 15 +- src/utils/resolve.ts | 4 + 10 files changed, 155 insertions(+), 279 deletions(-) diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts index efc64dfc..eb1458ba 100644 --- a/__tests__/utils.test.ts +++ b/__tests__/utils.test.ts @@ -21,16 +21,16 @@ test("noop", async () => { }); describe("load-module", () => { - test("wrong path", () => { - expect(loadModule("totallyWRONGPATH/here")).toBeUndefined(); + test("wrong path", async () => { + expect(await loadModule("totallyWRONGPATH/here")).toBeUndefined(); }); - test("correct cwd path", () => { - expect(loadModule(humanlizePath(fixture("utils/fixture")))).toBe("this is fixture"); + test("correct cwd path", async () => { + expect(await loadModule(humanlizePath(fixture("utils/fixture")))).toBe("this is fixture"); }); - test("correct absolute path", () => { - expect(loadModule(fixture("utils/fixture"))).toBe("this is fixture"); + test("correct absolute path", async () => { + expect(await loadModule(fixture("utils/fixture"))).toBe("this is fixture"); }); }); @@ -68,8 +68,8 @@ describe("load-sass", () => { }); describe("option-utils", () => { - test("wrong postcss option", () => { - expect(() => ensurePCSSOption("pumpinizer", "plugin")).toThrowErrorMatchingSnapshot(); + test("wrong postcss option", async () => { + await expect(ensurePCSSOption("pumpinizer", "plugin")).rejects.toThrowErrorMatchingSnapshot(); }); }); diff --git a/package-lock.json b/package-lock.json index 8901d183..cc411e17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,12 +10,12 @@ "license": "MIT", "dependencies": { "@rollup/pluginutils": "^5.0.0", - "cosmiconfig": "^8.0.0", + "cosmiconfig": "^9.0.0", "cssnano": "^7.0.0", "fs-extra": "^11.0.0", "icss-utils": "^5.1.0", - "mime-types": "^2.1.35", - "p-queue": "^8.0.0", + "mime-types": "^3.0.0", + "p-queue": "^9.0.0", "postcss": "^8.4.31", "postcss-modules-extract-imports": "^3.0.0", "postcss-modules-local-by-default": "^4.0.3", @@ -46,7 +46,7 @@ "@types/fs-extra": "^11.0.0", "@types/jest": "^29.0.0", "@types/less": "^3.0.6", - "@types/mime-types": "^2.1.4", + "@types/mime-types": "^3.0.0", "@types/node": "^22.0.0", "@types/resolve": "^1.20.5", "@types/stylus": "^0.48.42", @@ -68,7 +68,7 @@ "less": "^4.2.0", "lint-staged": "^15.0.0", "minireset.css": "^0.0.7", - "postcss-custom-properties": "^14.0.0", + "postcss-custom-properties": "^15.0.0", "prettier": "^3.0.0", "rollup": "^4.4.1", "rollup-plugin-dts": "^6.1.0", @@ -78,14 +78,14 @@ "semantic-release": "^25.0.1", "shx": "^0.3.4", "stylus": "^0.61.0", - "sugarss": "^4.0.1", + "sugarss": "^5.0.0", "ts-jest": "^29.0.0", "typedoc": "^0.27.0", "typedoc-plugin-missing-exports": "^3.0.0", "typescript": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=20.19.0" }, "peerDependencies": { "rollup": "^2.63.0 || ^3.0.0 || ^4.0.0" @@ -2103,40 +2103,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/load/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/@commitlint/load/node_modules/cosmiconfig": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", - "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/@commitlint/load/node_modules/cosmiconfig-typescript-loader": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.3.0.tgz", @@ -2155,29 +2121,6 @@ "typescript": ">=5" } }, - "node_modules/@commitlint/load/node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@commitlint/message": { "version": "20.4.3", "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-20.4.3.tgz", @@ -2316,40 +2259,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/prompt/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/@commitlint/prompt/node_modules/cosmiconfig": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", - "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/@commitlint/prompt/node_modules/cosmiconfig-typescript-loader": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.3.0.tgz", @@ -2394,29 +2303,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@commitlint/prompt/node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@commitlint/read": { "version": "20.5.0", "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-20.5.0.tgz", @@ -2560,9 +2446,9 @@ } }, "node_modules/@csstools/cascade-layer-name-parser": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz", - "integrity": "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-3.0.0.tgz", + "integrity": "sha512-/3iksyevwRfSJx5yH0RkcrcYXwuhMQx3Juqf40t97PeEy2/Mz2TItZ/z/216qpe4GgOyFBP8MKIwVvytzHmfIQ==", "dev": true, "funding": [ { @@ -2576,17 +2462,17 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "dev": true, "funding": [ { @@ -2600,16 +2486,16 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "dev": true, "funding": [ { @@ -2623,13 +2509,13 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@csstools/utilities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", - "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-3.0.0.tgz", + "integrity": "sha512-etDqA/4jYvOGBM6yfKCOsEXfH96BKztZdgGmGqKi2xHnDe0ILIBraRspwgYatJH9JsCZ5HCGoCst8w18EKOAdg==", "dev": true, "funding": [ { @@ -2643,7 +2529,7 @@ ], "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { "postcss": "^8.4" @@ -5577,9 +5463,9 @@ "license": "MIT" }, "node_modules/@types/mime-types": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", - "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==", "dev": true, "license": "MIT" }, @@ -7356,15 +7242,15 @@ "license": "MIT" }, "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", "license": "MIT", "dependencies": { + "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" }, "engines": { "node": ">=14" @@ -8258,7 +8144,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -13392,24 +13277,28 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/mimic-fn": { @@ -15989,16 +15878,28 @@ } }, "node_modules/p-queue": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", - "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.3.tgz", + "integrity": "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==", "license": "MIT", "dependencies": { - "eventemitter3": "^5.0.1", - "p-timeout": "^6.1.2" + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue/node_modules/p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "license": "MIT", + "engines": { + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -16018,6 +15919,7 @@ "version": "6.1.4", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=14.16" @@ -16153,6 +16055,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -16408,9 +16311,9 @@ } }, "node_modules/postcss-custom-properties": { - "version": "14.0.6", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz", - "integrity": "sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ==", + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-15.0.1.tgz", + "integrity": "sha512-cuyq8sd8dLY0GLbelz1KB8IMIoDECo6RVXMeHeXY2Uw3Q05k/d1GVITdaKLsheqrHbnxlwxzSRZQQ5u+rNtbMg==", "dev": true, "funding": [ { @@ -16424,14 +16327,14 @@ ], "license": "MIT", "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.5", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/utilities": "^2.0.0", + "@csstools/cascade-layer-name-parser": "^3.0.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "@csstools/utilities": "^3.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { "postcss": "^8.4" @@ -18071,13 +17974,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/semantic-release/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/semantic-release/node_modules/clean-stack": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", @@ -18127,33 +18023,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/cosmiconfig": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", - "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/semantic-release/node_modules/emoji-regex": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", @@ -18267,29 +18136,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/semantic-release/node_modules/lru-cache": { "version": "11.5.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", @@ -19366,17 +19212,23 @@ } }, "node_modules/sugarss": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-4.0.1.tgz", - "integrity": "sha512-WCjS5NfuVJjkQzK10s8WOBY+hhDxxNt/N6ZaGwxFZ+wN3/lKKFSaaKUNecULcTTvE4urLcKaZFQD8vO0mOZujw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-5.0.1.tgz", + "integrity": "sha512-ctS5RYCBVvPoZAnzIaX5QSShK8ZiZxD5HUqSxlusvEMC+QZQIPCPOIJg6aceFX+K2rf4+SH89eu++h1Zmsr2nw==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "node": ">=18.0" }, "peerDependencies": { "postcss": "^8.3.3" diff --git a/package.json b/package.json index 39be80b3..f4e5c09d 100644 --- a/package.json +++ b/package.json @@ -54,12 +54,12 @@ }, "dependencies": { "@rollup/pluginutils": "^5.0.0", - "cosmiconfig": "^8.0.0", + "cosmiconfig": "^9.0.0", "cssnano": "^7.0.0", "fs-extra": "^11.0.0", "icss-utils": "^5.1.0", - "mime-types": "^2.1.35", - "p-queue": "^8.0.0", + "mime-types": "^3.0.0", + "p-queue": "^9.0.0", "postcss": "^8.4.31", "postcss-modules-extract-imports": "^3.0.0", "postcss-modules-local-by-default": "^4.0.3", @@ -90,7 +90,7 @@ "@types/fs-extra": "^11.0.0", "@types/jest": "^29.0.0", "@types/less": "^3.0.6", - "@types/mime-types": "^2.1.4", + "@types/mime-types": "^3.0.0", "@types/node": "^22.0.0", "@types/resolve": "^1.20.5", "@types/stylus": "^0.48.42", @@ -112,7 +112,7 @@ "less": "^4.2.0", "lint-staged": "^15.0.0", "minireset.css": "^0.0.7", - "postcss-custom-properties": "^14.0.0", + "postcss-custom-properties": "^15.0.0", "prettier": "^3.0.0", "rollup": "^4.4.1", "rollup-plugin-dts": "^6.1.0", @@ -122,7 +122,7 @@ "semantic-release": "^25.0.1", "shx": "^0.3.4", "stylus": "^0.61.0", - "sugarss": "^4.0.1", + "sugarss": "^5.0.0", "ts-jest": "^29.0.0", "typedoc": "^0.27.0", "typedoc-plugin-missing-exports": "^3.0.0", @@ -132,6 +132,6 @@ "rollup": "^2.63.0 || ^3.0.0 || ^4.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=20.19.0" } } diff --git a/src/index.ts b/src/index.ts index f7b15871..c1106c73 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,8 +15,6 @@ import { inferSourceMapOption, inferHandlerOption, ensureUseOption, - ensurePCSSOption, - ensurePCSSPlugins, } from "./utils/options"; export default (options: Options = {}): Plugin => { @@ -47,14 +45,13 @@ export default (options: Options = {}): Plugin => { ) throw new Error("`inject.treeshakeable` option is incompatible with `namedExports` option"); - if (options.parser) loaderOpts.postcss.parser = ensurePCSSOption(options.parser, "parser"); + if (options.parser) loaderOpts.postcss.parser = options.parser; - if (options.syntax) loaderOpts.postcss.syntax = ensurePCSSOption(options.syntax, "syntax"); + if (options.syntax) loaderOpts.postcss.syntax = options.syntax; - if (options.stringifier) - loaderOpts.postcss.stringifier = ensurePCSSOption(options.stringifier, "stringifier"); + if (options.stringifier) loaderOpts.postcss.stringifier = options.stringifier; - if (options.plugins) loaderOpts.postcss.plugins = ensurePCSSPlugins(options.plugins); + if (options.plugins) loaderOpts.postcss.plugins = options.plugins; const loaders = new Loaders({ use: [["postcss", loaderOpts], ...ensureUseOption(options), ["sourcemap", {}]], diff --git a/src/loaders/postcss/config.ts b/src/loaders/postcss/config.ts index a1aa48fa..e62904cf 100644 --- a/src/loaders/postcss/config.ts +++ b/src/loaders/postcss/config.ts @@ -29,7 +29,9 @@ export default async function ( type Found = { config: Config | ((ctx: Record) => Config); isEmpty?: boolean }; const searchPath = config.path ? path.resolve(config.path) : dir; - const found: Found | null = await cosmiconfig("postcss").search(searchPath); + const found: Found | null = await cosmiconfig("postcss", { searchStrategy: "global" }).search( + searchPath, + ); if (!found || found.isEmpty) return { plugins: [], options: {} }; @@ -43,10 +45,10 @@ export default async function ( }) : found.config; - const result: Result = { plugins: ensurePCSSPlugins(plugins), options: {} }; - if (parser) result.options.parser = ensurePCSSOption(parser, "parser"); - if (syntax) result.options.syntax = ensurePCSSOption(syntax, "syntax"); - if (stringifier) result.options.stringifier = ensurePCSSOption(stringifier, "stringifier"); + const result: Result = { plugins: await ensurePCSSPlugins(plugins), options: {} }; + if (parser) result.options.parser = await ensurePCSSOption(parser, "parser"); + if (syntax) result.options.syntax = await ensurePCSSOption(syntax, "syntax"); + if (stringifier) result.options.stringifier = await ensurePCSSOption(stringifier, "stringifier"); return result; } diff --git a/src/loaders/postcss/index.ts b/src/loaders/postcss/index.ts index 0ec6122a..3dce86a3 100644 --- a/src/loaders/postcss/index.ts +++ b/src/loaders/postcss/index.ts @@ -8,6 +8,7 @@ import { PostCSSLoaderOptions, InjectOptions } from "../../types"; import { humanlizePath, normalizePath } from "../../utils/path"; import { mm } from "../../utils/sourcemap"; import { resolveAsync } from "../../utils/resolve"; +import { ensurePCSSOption, ensurePCSSPlugins } from "../../utils/options"; import safeId from "../../utils/safe-id"; import { Loader } from "../types"; import loadConfig from "./config"; @@ -67,12 +68,21 @@ const loader: Loader = { delete postcssOpts.plugins; + // Resolve string-based parser/syntax/stringifier/plugins lazily (async) + if (typeof postcssOpts.parser === "string") + postcssOpts.parser = await ensurePCSSOption(postcssOpts.parser, "parser"); + if (typeof postcssOpts.syntax === "string") + postcssOpts.syntax = await ensurePCSSOption(postcssOpts.syntax, "syntax"); + if (typeof postcssOpts.stringifier === "string") + postcssOpts.stringifier = await ensurePCSSOption(postcssOpts.stringifier, "stringifier"); + if (options.import) plugins.push(postcssImport({ extensions: options.extensions, ...options.import })); if (options.url) plugins.push(postcssUrl({ inline: Boolean(options.inject), ...options.url })); - if (options.postcss.plugins) plugins.push(...options.postcss.plugins); + if (options.postcss.plugins) + plugins.push(...(await ensurePCSSPlugins(options.postcss.plugins))); if (config.plugins) plugins.push(...config.plugins); @@ -96,7 +106,7 @@ const loader: Loader = { // Avoid PostCSS warning if (plugins.length === 0) plugins.push(postcssNoop); - const res = await postcss(plugins).process(code, postcssOpts); + const res = await postcss(plugins).process(code, postcssOpts as ProcessOptions); for (const msg of res.messages) switch (msg.type) { diff --git a/src/types.ts b/src/types.ts index e623731b..68992f0b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -53,13 +53,13 @@ export interface PostCSSLoaderOptions extends Record { /** Options for PostCSS processor */ postcss: { /** @see {@link Options.parser} */ - parser?: postcss.Parser; + parser?: string | postcss.Parser; /** @see {@link Options.syntax} */ - syntax?: postcss.Syntax; + syntax?: string | postcss.Syntax; /** @see {@link Options.stringifier} */ - stringifier?: postcss.Stringifier; + stringifier?: string | postcss.Stringifier; /** @see {@link Options.plugins} */ - plugins?: postcss.AcceptedPlugin[]; + plugins?: Options["plugins"]; }; } diff --git a/src/utils/load-module.ts b/src/utils/load-module.ts index f5760156..6bd6a534 100644 --- a/src/utils/load-module.ts +++ b/src/utils/load-module.ts @@ -1,4 +1,4 @@ -import { resolveSync, ResolveOpts } from "./resolve"; +import { resolveSync, ResolveOpts, packageFilterBuilder } from "./resolve"; import { createRequire } from "node:module"; const require = createRequire(import.meta.url); @@ -9,15 +9,23 @@ const options: ResolveOpts = { basedirs: [process.cwd()], extensions: [".js", ".mjs", ".cjs", ".json"], preserveSymlinks: false, - packageFilter: pkg => pkg, + packageFilter: packageFilterBuilder(), }; -export default function (moduleId: string): unknown { +export default async function (moduleId: string): Promise { if (loaded[moduleId]) return loaded[moduleId]; if (loaded[moduleId] === null) return; try { - loaded[moduleId] = require(resolveSync([moduleId, `./${moduleId}`], options)); + const resolved = resolveSync([moduleId, `./${moduleId}`], options); + try { + loaded[moduleId] = require(resolved); + } catch { + // ESM module that cannot be require()d (e.g. .mjs in jest VM context) + + const mod = (await import(resolved)) as { default: unknown }; + loaded[moduleId] = mod.default; + } } catch { loaded[moduleId] = null; return; diff --git a/src/utils/options.ts b/src/utils/options.ts index ba30eda2..e12b14fa 100644 --- a/src/utils/options.ts +++ b/src/utils/options.ts @@ -68,14 +68,14 @@ export function ensureUseOption(opts: Options): [string, Record } type PCSSOption = "parser" | "syntax" | "stringifier" | "plugin"; -export function ensurePCSSOption(option: T | string, type: PCSSOption): T { +export async function ensurePCSSOption(option: T | string, type: PCSSOption): Promise { if (typeof option !== "string") return option; - const module = loadModule(option); + const module = await loadModule(option); if (!module) throw new Error(`Unable to load PostCSS ${type} \`${option}\``); return module as T; } -export function ensurePCSSPlugins(plugins: Options["plugins"]): AcceptedPlugin[] { +export async function ensurePCSSPlugins(plugins: Options["plugins"]): Promise { if (plugins === undefined) return []; else if (typeof plugins !== "object") throw new TypeError("`plugins` option must be an array or an object!"); @@ -85,15 +85,18 @@ export function ensurePCSSPlugins(plugins: Options["plugins"]): AcceptedPlugin[] if (!p) continue; if (!Array.isArray(p)) { - ps.push(ensurePCSSOption(p, "plugin")); + // eslint-disable-next-line no-await-in-loop + ps.push(await ensurePCSSOption(p, "plugin")); continue; } const [plug, opts] = p; if (opts) { - ps.push(ensurePCSSOption(plug, "plugin")(opts)); + // eslint-disable-next-line no-await-in-loop + ps.push((await ensurePCSSOption(plug, "plugin"))(opts)); } else { - ps.push(ensurePCSSOption(plug, "plugin")); + // eslint-disable-next-line no-await-in-loop + ps.push(await ensurePCSSOption(plug, "plugin")); } } diff --git a/src/utils/resolve.ts b/src/utils/resolve.ts index c5cc7a6e..acf9ee1d 100644 --- a/src/utils/resolve.ts +++ b/src/utils/resolve.ts @@ -48,6 +48,10 @@ export const packageFilterBuilder: PackageFilterBuilderFn = (opts = {}) => { pkg.main = resolvedExport; return pkg; } + if (Array.isArray(resolvedExport) && resolvedExport.length > 0) { + pkg.main = resolvedExport[0]; + return pkg; + } } catch { /* noop */ } From 9ec89681cabd1bd8008e76450cf941ba280e6464 Mon Sep 17 00:00:00 2001 From: Iulian Meghea Date: Fri, 31 Jul 2026 06:03:40 +0000 Subject: [PATCH 2/3] test(options): cover ensurePCSSPlugins array-without-options branch Add unit test for ensurePCSSPlugins([[autoprefixer]]) which exercises the else branch (line 99) where a plugin is specified as an array with only the name and no options object. Resolves codecov patch coverage. --- __tests__/utils.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts index eb1458ba..37ea9f4f 100644 --- a/__tests__/utils.test.ts +++ b/__tests__/utils.test.ts @@ -3,7 +3,7 @@ import postcss from "postcss"; import Loaders from "../src/loaders"; import postcssNoop from "../src/loaders/postcss/noop"; import loadSass from "../src/loaders/sass/load"; -import { ensurePCSSOption } from "../src/utils/options"; +import { ensurePCSSOption, ensurePCSSPlugins } from "../src/utils/options"; import { mm, getMap, stripMap } from "../src/utils/sourcemap"; import { humanlizePath } from "../src/utils/path"; @@ -71,6 +71,12 @@ describe("option-utils", () => { test("wrong postcss option", async () => { await expect(ensurePCSSOption("pumpinizer", "plugin")).rejects.toThrowErrorMatchingSnapshot(); }); + + test("plugin as array without options", async () => { + const plugins = await ensurePCSSPlugins([["autoprefixer"]]); + expect(plugins).toHaveLength(1); + expect(typeof plugins[0]).toBe("function"); + }); }); describe("sourcemap-utils", () => { From e2b2a2736ca85fcd8467b10dd463aacf2d529907 Mon Sep 17 00:00:00 2001 From: Iulian Meghea Date: Fri, 31 Jul 2026 06:51:59 +0000 Subject: [PATCH 3/3] feat(load-module): support ESM-only packages with exports fields loadModule now resolves and loads ESM-only packages that only have an exports field (no main/module). Falls back to dynamic import() when require() fails for .mjs files (e.g. in jest VM context). packageFilterBuilder handles array returns from resolveExports, which is the return type for all modern package exports shapes (flat and nested). parser/syntax/stringifier/plugins options are now resolved lazily in the async postcss process() hook instead of eagerly in the sync plugin factory, enabling async module loading.