Skip to content

Commit 4b4e5c8

Browse files
committed
test: add CJS/ESM dual-package compatibility tests
Mirrors the cjs.test.js suite from webpack/sass-loader: builds a CommonJS bundle from src/ at test time via @babel/core and asserts the shape promised by the build:cjs pipeline (commonjs package marker, strict-mode prologue, exports.default unwrap) so require() returns the loader function with .default pointing back at itself, matching the ESM default export.
1 parent e055dde commit 4b4e5c8

1 file changed

Lines changed: 116 additions & 0 deletions

File tree

test/cjs.test.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import assert from "node:assert";
2+
import {
3+
copyFileSync,
4+
mkdtempSync,
5+
readFileSync,
6+
readdirSync,
7+
rmSync,
8+
writeFileSync,
9+
} from "node:fs";
10+
import { createRequire } from "node:module";
11+
import { tmpdir } from "node:os";
12+
import path from "node:path";
13+
import { after, before, describe, it } from "node:test";
14+
import { fileURLToPath } from "node:url";
15+
16+
import { transformFileAsync } from "@babel/core";
17+
18+
import src from "../src/index.js";
19+
20+
const require = createRequire(import.meta.url);
21+
22+
const srcDir = fileURLToPath(new URL("../src", import.meta.url));
23+
24+
/**
25+
* Transpile every `.js` file under `src/` to CommonJS in `outDir`, copy
26+
* non-JS assets, then drop the `dist/cjs/package.json` type marker and
27+
* the same `module.exports = exports.default` post-build append the
28+
* `build:cjs` npm script writes. The test then `require()`s the result.
29+
* @param {string} outDir target directory
30+
*/
31+
async function buildCjsBundle(outDir) {
32+
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
33+
const source = path.join(srcDir, entry.name);
34+
const target = path.join(outDir, entry.name);
35+
36+
if (entry.name.endsWith(".js")) {
37+
const result = await transformFileAsync(source, { envName: "cjs" });
38+
39+
writeFileSync(target, result.code);
40+
} else {
41+
copyFileSync(source, target);
42+
}
43+
}
44+
45+
writeFileSync(
46+
path.join(outDir, "package.json"),
47+
`${JSON.stringify({ type: "commonjs" })}\n`,
48+
);
49+
50+
const indexPath = path.join(outDir, "index.js");
51+
52+
writeFileSync(
53+
indexPath,
54+
`${readFileSync(indexPath, "utf8")}module.exports = exports.default;\nmodule.exports.default = exports.default;\n`,
55+
);
56+
}
57+
58+
describe("cjs", () => {
59+
let cjsDir;
60+
let cjsIndexPath;
61+
let cjsIndexSource;
62+
let cjsPackage;
63+
let cjsLoader;
64+
65+
before(async () => {
66+
cjsDir = mkdtempSync(path.join(tmpdir(), "less-loader-cjs-"));
67+
68+
await buildCjsBundle(cjsDir);
69+
70+
cjsIndexPath = path.join(cjsDir, "index.js");
71+
cjsIndexSource = readFileSync(cjsIndexPath, "utf8");
72+
cjsPackage = JSON.parse(
73+
readFileSync(path.join(cjsDir, "package.json"), "utf8"),
74+
);
75+
cjsLoader = require(cjsIndexPath);
76+
});
77+
78+
after(() => {
79+
if (cjsDir) rmSync(cjsDir, { recursive: true, force: true });
80+
});
81+
82+
it("should expose the loader as the default export of the ESM entry", () => {
83+
assert.strictEqual(typeof src, "function");
84+
});
85+
86+
// Run Babel against `src/` directly (no dependency on the published
87+
// `dist/`) and assert the resulting CommonJS bundle has the shape the
88+
// `build:cjs` pipeline promises: it's marked CommonJS, opens with
89+
// Babel's strict-mode prologue and `exports.default = lessLoader`, and
90+
// ends with the `module.exports = exports.default;` unwrap so
91+
// `require()` returns the loader function directly.
92+
it("should produce a require()-able CommonJS bundle via @babel/core", () => {
93+
assert.strictEqual(cjsPackage.type, "commonjs");
94+
95+
assert.match(cjsIndexSource, /^"use strict";/);
96+
assert.match(cjsIndexSource, /exports\.default = lessLoader/);
97+
assert.match(cjsIndexSource, /module\.exports = exports\.default;/);
98+
99+
assert.strictEqual(typeof cjsLoader, "function");
100+
assert.strictEqual(cjsLoader.default, cjsLoader);
101+
});
102+
103+
// Pre-refactor consumers loaded the loader two ways:
104+
// const loader = require("less-loader"); // function
105+
// import loader from "less-loader"; // function
106+
// Both surfaces must continue to return a callable function, with a
107+
// `.default` property pointing back at the same function so transitional
108+
// `require("less-loader").default` calls keep working.
109+
it("should expose the loader through `require` as a callable function (pre-refactor shape)", () => {
110+
assert.strictEqual(typeof cjsLoader, "function");
111+
assert.strictEqual(typeof src, "function");
112+
assert.strictEqual(cjsLoader.default, cjsLoader);
113+
assert.strictEqual(cjsLoader.name, src.name);
114+
assert.strictEqual(cjsLoader.length, src.length);
115+
});
116+
});

0 commit comments

Comments
 (0)