From 65be0b9fd4eda8b01626a6c3a1824f18c145a776 Mon Sep 17 00:00:00 2001 From: lemachadoh Date: Fri, 15 May 2026 14:08:33 -0300 Subject: [PATCH] fix: revert ESM dynamic import, require less >= 4.6.3 less@4.6.3 ships a pre-built CJS bundle (dist/less-node.cjs) and maps the "require" exports condition to it, resolving the ESM/CJS interop issue that prompted the dynamic import workaround in #578. Revert getLessImplementation back to synchronous require() and restore the standard test:only script. Remove peer dependency constraint added in previous commit as it would break users on less@4.0.0-4.6.2. Source map improvements introduced in #578 (sourceMapBasepath, disableSourcemapAnnotation, normalizeSourceMap) are preserved. --- babel.config.js | 1 - package-lock.json | 12 ++-- package.json | 4 +- src/index.js | 2 +- src/utils.js | 7 +- .../sourceMap-options.test.js.snap | 6 +- .../fixtures/folder/customFileLoaderPlugin.js | 57 ++++++++-------- test/helpers/getCodeFromLess.js | 67 +++++++++---------- test/implementation.test.js | 6 +- test/loader.test.js | 11 +-- test/sourceMap-options.test.js | 19 ++---- test/validate-options.test.js | 2 +- 12 files changed, 86 insertions(+), 108 deletions(-) diff --git a/babel.config.js b/babel.config.js index 106a962..b65dedb 100644 --- a/babel.config.js +++ b/babel.config.js @@ -12,7 +12,6 @@ module.exports = (api) => { targets: { node: "18.12.0", }, - exclude: ["transform-dynamic-import"], }, ], ], diff --git a/package-lock.json b/package-lock.json index 6490e96..43da122 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "eslint-config-webpack": "^4.5.1", "husky": "^9.1.3", "jest": "^30.0.0", - "less": "^4.6.2", + "less": "^4.6.3", "less-plugin-glob": "^3.0.0", "lint-staged": "^15.2.7", "memfs": "^4.9.3", @@ -7874,7 +7874,7 @@ }, "node_modules/copy-anything": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "resolved": "https://artifactory.concurtech.net/artifactory/api/npm/npm-aggregate/copy-anything/-/copy-anything-3.0.5.tgz", "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", "dev": true, "license": "MIT", @@ -12853,7 +12853,7 @@ }, "node_modules/is-what": { "version": "4.1.16", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "resolved": "https://artifactory.concurtech.net/artifactory/api/npm/npm-aggregate/is-what/-/is-what-4.1.16.tgz", "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", "dev": true, "license": "MIT", @@ -14058,9 +14058,9 @@ } }, "node_modules/less": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/less/-/less-4.6.2.tgz", - "integrity": "sha512-/f26k/Z8VOtdkmubElnfZGtNoOlnZN6E1g9WkPkRmsz/FNVaaArbwCIMflPdH//RJeowfuTPmdZSp9zyEYQ1gQ==", + "version": "4.6.3", + "resolved": "https://artifactory.concurtech.net/artifactory/api/npm/npm-aggregate/less/-/less-4.6.3.tgz", + "integrity": "sha512-xxjVIiz0rvWAhx49t8YLA/Dj6Uig4XdJgTRI5hbYHGmiDSh+S8hi2Wxsed2trjl/uur4kbW2+e2a8Mrk9Rcgbw==", "dev": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index cd8b404..e5aa40c 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "fix:js": "npm run lint:js -- --fix", "fix:prettier": "npm run lint:prettier -- --write", "fix": "npm-run-all -l fix:js fix:prettier", - "test:only": "node --experimental-vm-modules node_modules/jest-cli/bin/jest.js", + "test:only": "cross-env NODE_ENV=test jest", "test:watch": "npm run test:only -- --watch", "test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage", "pretest": "npm run lint", @@ -61,7 +61,7 @@ "eslint-config-webpack": "^4.5.1", "husky": "^9.1.3", "jest": "^30.0.0", - "less": "^4.6.2", + "less": "^4.6.3", "less-plugin-glob": "^3.0.0", "lint-staged": "^15.2.7", "memfs": "^4.9.3", diff --git a/src/index.js b/src/index.js index 18d234b..7bfdb9d 100644 --- a/src/index.js +++ b/src/index.js @@ -15,7 +15,7 @@ async function lessLoader(source) { let implementation; try { - implementation = await getLessImplementation(this, options.implementation); + implementation = getLessImplementation(this, options.implementation); } catch (error) { callback(error); diff --git a/src/utils.js b/src/utils.js index 59e1d76..a20ae4a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -223,19 +223,20 @@ function normalizeSourceMap(map) { newMap.sourceRoot = ""; - // `less` (old versions) returns POSIX paths, that's why we need to transform them back to native paths. + // `less` returns POSIX paths, that's why we need to transform them back to native paths. + newMap.sources = newMap.sources.map((source) => path.normalize(source)); return newMap; } -async function getLessImplementation(loaderContext, implementation) { +function getLessImplementation(loaderContext, implementation) { let resolvedImplementation = implementation; if (!implementation || typeof implementation === "string") { const lessImplPkg = implementation || "less"; - resolvedImplementation = (await import(lessImplPkg)).default; + resolvedImplementation = require(lessImplPkg); } return resolvedImplementation; diff --git a/test/__snapshots__/sourceMap-options.test.js.snap b/test/__snapshots__/sourceMap-options.test.js.snap index 6fe574c..e7e039c 100644 --- a/test/__snapshots__/sourceMap-options.test.js.snap +++ b/test/__snapshots__/sourceMap-options.test.js.snap @@ -48,7 +48,7 @@ exports[`"sourceMap" options should generate source maps when value has "false" #it-works { margin: 10px; } -" +/*# sourceMappingURL=source-map.css.map */" `; exports[`"sourceMap" options should generate source maps when value has "false" value, but the "lessOptions.sourceMap.outputSourceFiles" is "true": errors 1`] = `[]`; @@ -60,8 +60,8 @@ exports[`"sourceMap" options should generate source maps when value has "false" "names": [], "sourceRoot": "", "sources": [ - "test/fixtures/node_modules/some/module.less", - "test/fixtures/source-map.less", + "node_modules/some/module.less", + "source-map.less", ], "sourcesContent": [ ".modules-dir-some-module { diff --git a/test/fixtures/folder/customFileLoaderPlugin.js b/test/fixtures/folder/customFileLoaderPlugin.js index e7c6041..a5889cc 100644 --- a/test/fixtures/folder/customFileLoaderPlugin.js +++ b/test/fixtures/folder/customFileLoaderPlugin.js @@ -1,39 +1,36 @@ import path from "path"; +import less from "less"; -export default async () => { - const less = (await import("less")).default; - - class Plugin extends less.FileManager { - supports(filename) { - if (filename === 'forFileLoaderPluginResolve.less') { - return true; - } - - if (filename === "https://fonts.googleapis.com/css?family=Roboto:500") { - return true; - } - - return false; +class Plugin extends less.FileManager { + supports(filename) { + if (filename === 'forFileLoaderPluginResolve.less') { + return true; + } + + if (filename === "https://fonts.googleapis.com/css?family=Roboto:500") { + return true; } - loadFile(filename, ...args) { - let result; - - if (filename === 'forFileLoaderPluginResolve.less') { - result = path.resolve(__dirname, '../', 'file-load-replacement.less'); - } else if (filename === "https://fonts.googleapis.com/css?family=Roboto:500") { - result = path.resolve(__dirname, '../', 'mock-fonts.less'); - } - - return super.loadFile(result, ...args); - }; + return false; } - class CustomFileLoaderPlugin { - install(less, pluginManager) { - pluginManager.addFileManager(new Plugin()); + loadFile(filename, ...args) { + let result; + + if (filename === 'forFileLoaderPluginResolve.less') { + result = path.resolve(__dirname, '../', 'file-load-replacement.less'); + } else if (filename === "https://fonts.googleapis.com/css?family=Roboto:500") { + result = path.resolve(__dirname, '../', 'mock-fonts.less'); } + + return super.loadFile(result, ...args); + }; +} + +class CustomFileLoaderPlugin { + install(less, pluginManager) { + pluginManager.addFileManager(new Plugin()); } +} - return CustomFileLoaderPlugin; -}; +module.exports = CustomFileLoaderPlugin; diff --git a/test/helpers/getCodeFromLess.js b/test/helpers/getCodeFromLess.js index 761b8be..c5e7c78 100644 --- a/test/helpers/getCodeFromLess.js +++ b/test/helpers/getCodeFromLess.js @@ -1,6 +1,8 @@ import fs from "node:fs"; import path from "node:path"; +import less from "less"; + const pathMap = { "some/css.css": path.resolve( __dirname, @@ -141,6 +143,37 @@ const pathMap = { "3rd/b.less": path.resolve(__dirname, "..", "fixtures", "3rd", "b.less"), }; +class ResolvePlugin extends less.FileManager { + supports(filename) { + if (filename[0] === "/" || path.win32.isAbsolute(filename)) { + return true; + } + + if (this.isPathAbsolute(filename)) { + return false; + } + + return true; + } + + supportsSync() { + return false; + } + + async loadFile(filename, ...args) { + const result = + pathMap[filename] || path.resolve(__dirname, "..", "fixtures", filename); + + return super.loadFile(result, ...args); + } +} + +class CustomImportPlugin { + install(lessInstance, pluginManager) { + pluginManager.addFileManager(new ResolvePlugin()); + } +} + async function getCodeFromLess(testId, options = {}, context = {}) { let pathToFile; @@ -186,40 +219,6 @@ async function getCodeFromLess(testId, options = {}, context = {}) { ...lessOptions, }; - const less = (await import("less")).default; - - class ResolvePlugin extends less.FileManager { - supports(filename) { - if (filename[0] === "/" || path.win32.isAbsolute(filename)) { - return true; - } - - if (this.isPathAbsolute(filename)) { - return false; - } - - return true; - } - - supportsSync() { - return false; - } - - async loadFile(filename, ...args) { - const result = - pathMap[filename] || - path.resolve(__dirname, "..", "fixtures", filename); - - return super.loadFile(result, ...args); - } - } - - class CustomImportPlugin { - install(lessInstance, pluginManager) { - pluginManager.addFileManager(new ResolvePlugin()); - } - } - mergedOptions.plugins.unshift(new CustomImportPlugin()); return less.render(data.toString(), mergedOptions); diff --git a/test/implementation.test.js b/test/implementation.test.js index b48fafd..02a43b0 100644 --- a/test/implementation.test.js +++ b/test/implementation.test.js @@ -9,10 +9,9 @@ import { describe('"implementation" option', () => { it("should work", async () => { - const less = (await import("less")).default; const testId = "./basic.less"; const compiler = getCompiler(testId, { - implementation: less, + implementation: require("less"), }); const stats = await compile(compiler); const codeFromBundle = getCodeFromBundle(stats, compiler); @@ -26,9 +25,8 @@ describe('"implementation" option', () => { it("should work when implementation option is string", async () => { const testId = "./basic.less"; - const compiler = getCompiler(testId, { - implementation: "less", + implementation: require.resolve("less"), }); const stats = await compile(compiler); const codeFromBundle = getCodeFromBundle(stats, compiler); diff --git a/test/loader.test.js b/test/loader.test.js index 39175ce..2039761 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -14,6 +14,8 @@ import { validateDependencies, } from "./helpers"; +const CustomFileLoaderPlugin = require("./fixtures/folder/customFileLoaderPlugin"); + const nodeModulesPath = path.resolve(__dirname, "fixtures", "node_modules"); jest.setTimeout(30000); @@ -105,11 +107,6 @@ describe("loader", () => { }); it("should work third-party plugins as fileLoader", async () => { - const getCustomFileLoaderPlugin = ( - await import("./fixtures/folder/customFileLoaderPlugin") - ).default.default; - const CustomFileLoaderPlugin = await getCustomFileLoaderPlugin(); - const testId = "./file-load.less"; const compiler = getCompiler(testId, { lessOptions: { @@ -805,9 +802,7 @@ describe("loader", () => { let contextInClass = false; let contextInObject = false; - const less = (await import("less")).default; - - class Plugin extends less.FileManager { + class Plugin extends require("less").FileManager { constructor(less, pluginManager) { super(); diff --git a/test/sourceMap-options.test.js b/test/sourceMap-options.test.js index dd923d4..097b96a 100644 --- a/test/sourceMap-options.test.js +++ b/test/sourceMap-options.test.js @@ -110,29 +110,18 @@ describe('"sourceMap" options', () => { const compiler = getCompiler(testId, { sourceMap: false, lessOptions: { - sourceMap: { - sourceMapBasepath: "", - outputSourceFiles: true, - disableSourcemapAnnotation: true, - }, + sourceMap: { outputSourceFiles: true }, }, }); const stats = await compile(compiler); const codeFromBundle = getCodeFromBundle(stats, compiler); - const codeFromLess = await getCodeFromLess(testId); const { css, map } = codeFromBundle; map.sourceRoot = ""; - map.sources = map.sources.map((source) => { - expect(path.isAbsolute(source)).toBe(true); - expect(fs.existsSync(path.resolve(map.sourceRoot, source))).toBe(true); - - return path - .relative(path.resolve(__dirname, ".."), source) - .replaceAll("\\", "/"); - }); + map.sources = map.sources.map((source) => + path.normalize(source).replaceAll("\\", "/"), + ); - expect(css).toBe(codeFromLess.css); expect(css).toMatchSnapshot("css"); expect(map).toMatchSnapshot("source map"); expect(getWarnings(stats)).toMatchSnapshot("warnings"); diff --git a/test/validate-options.test.js b/test/validate-options.test.js index c382fa3..80311bd 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -24,7 +24,7 @@ describe("validate options", () => { failure: ["string"], }, implementation: { - success: ["less"], + success: [require("less"), "less"], failure: [true, false, () => {}, []], }, lessLogAsWarnOrErr: {