샘플
lightningcss 1.33.0: Run lightningcss on Alpine, where the native addon really does come in per-libc builds
검증된 샘플 — npm lightningcss 1.33.0: Run lightningcss on Alpine, where the native addon really does come in per-libc builds. node 22 · linux alpine/x64 …
sha256:de0de7aea8370a5ddbb611169fcb1bf333cb27e4a8318c0be8d18fb57c45682e
이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다.
통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다.
MIT-0
실행 증거
선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.
- 증거 기준
- 서명된 컨트랙트 통과
- 검증 영수증
- 2
- 빌드한 서명 키
- 2
선언된 환경
node 22 linux · musl x64 node 22 javascript npm
검증 실행 환경
| 환경 | 컨트랙트 | 단계 | 실행일 |
|---|---|---|---|
| node 22 · linux alpine/x64 · docker ed25519:a2ec939a4c60e243 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · npm@1 |
2026-08-14 |
| node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · npm@1 |
2026-08-18 |
케이스
HOW- 목표
- Run lightningcss on Alpine, where the native addon really does come in per-libc builds
- 패키지
- 심벌
-
- lightningcss.transform
- detectLibc.familySync
- lightningcss-linux-x64-musl
- lightningcss-linux-x64-gnu
- 환경
- node 22
- 생성일
- 2026-08-14T13:01:58Z
컨트랙트
- minify CSS with lightningcss on a musl image with lifecycle scripts disabled, asserting the merged margin shorthand
- assert transform returns code as a Buffer that equals the expected string under == and in a template string but is never === it
- assert targets lower CSS nesting for Chrome 90 and leave the nested rule alone for Chrome 130
- assert targets are packed as major << 16, so a bare 130 means Chrome 0.0.130 and lowers everything
- assert @custom-media needs both drafts.customMedia and targets, and lowers to queries joined with or
- assert an undefined custom media reference throws SyntaxError with data.type CustomMediaNotDefined and a source location
- assert exactly one .node was dlopened and it is the musl platform package, read from the CJS module cache
- assert the image is musl, that the specifier the loader builds is lightningcss-linux-x64-musl, and that detect-libc is lightningcss's only runtime dependency
- assert from the installed loader's own source that it calls detect-libc familySync at require time, requires the platform package by name, and only then falls back to a .node in the package root
- assert both linux-x64 platform packages declare libc, musl and glibc respectively
- assert npm ci installed BOTH linux-x64 libc variants because the lockfile records os and cpu on every optional entry but never libc
- assert the image ships the npm 10 whose lockfile writer drops libc, since npm 12 records it and then installs only the musl package
- assert the unusable glibc binary is a full second copy of the 10 MB addon on disk
- assert from the ELF dynamic sections that the musl build needs libc.musl-x86_64.so.1 and the glibc build needs libc.so.6 and ld-linux-x86-64.so.2
- assert requiring the installed glibc binary fails with ERR_DLOPEN_FAILED naming the missing glibc loader
- assert a platform package that was never installed fails earlier, at resolution, with MODULE_NOT_FOUND
- assert the classic Cannot find module lightningcss.linux-x64-musl.node is MODULE_NOT_FOUND from the loader's local-build fallback, meaning the platform package is absent rather than wrong
파일
- csx.json
- package-lock.json
- package.json
- src/compile.mjs
- test/contract.mjs
소스
{"case":{"caseId":"case:sha256:b538fcf585bfebf4203c6341d635aa3d87b08f7cb865de56bc24300d8cb17ebd","constraints":{"libc":"musl","runtime":"node"},"contract":["minify CSS with lightningcss on a musl image with lifecycle scripts disabled, asserting the merged margin shorthand","assert transform returns code as a Buffer that equals the expected string under == and in a template string but is never === it","assert targets lower CSS nesting for Chrome 90 and leave the nested rule alone for Chrome 130","assert targets are packed as major \u003c\u003c 16, so a bare 130 means Chrome 0.0.130 and lowers everything","assert @custom-media needs both drafts.customMedia and targets, and lowers to queries joined with or","assert an undefined custom media reference throws SyntaxError with data.type CustomMediaNotDefined and a source location","assert exactly one .node was dlopened and it is the musl platform package, read from the CJS module cache","assert the image is musl, that the specifier the loader builds is lightningcss-linux-x64-musl, and that detect-libc is lightningcss's only runtime dependency","assert from the installed loader's own source that it calls detect-libc familySync at require time, requires the platform package by name, and only then falls back to a .node in the package root","assert both linux-x64 platform packages declare libc, musl and glibc respectively","assert npm ci installed BOTH linux-x64 libc variants because the lockfile records os and cpu on every optional entry but never libc","assert the image ships the npm 10 whose lockfile writer drops libc, since npm 12 records it and then installs only the musl package","assert the unusable glibc binary is a full second copy of the 10 MB addon on disk","assert from the ELF dynamic sections that the musl build needs libc.musl-x86_64.so.1 and the glibc build needs libc.so.6 and ld-linux-x86-64.so.2","assert requiring the installed glibc binary fails with ERR_DLOPEN_FAILED naming the missing glibc loader","assert a platform package that was never installed fails earlier, at resolution, with MODULE_NOT_FOUND","assert the classic Cannot find module lightningcss.linux-x64-musl.node is MODULE_NOT_FOUND from the loader's local-build fallback, meaning the platform package is absent rather than wrong"],"goal":"Run lightningcss on Alpine, where the native addon really does come in per-libc builds","kind":"HOW","packages":["pkg:npm/lightningcss@1.33.0","pkg:npm/lightningcss-linux-x64-musl@1.33.0","pkg:npm/lightningcss-linux-x64-gnu@1.33.0","pkg:npm/detect-libc@2.1.2"],"schemaVersion":1,"symbols":["lightningcss.transform","detectLibc.familySync","lightningcss-linux-x64-musl","lightningcss-linux-x64-gnu"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"javascript","libc":"musl","moduleSystem":"esm","os":"linux","packageManager":"npm","runtime":"node","runtimeVersion":"22","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/lightningcss@1.33.0","pkg:npm/lightningcss-linux-x64-musl@1.33.0","pkg:npm/lightningcss-linux-x64-gnu@1.33.0","pkg:npm/detect-libc@2.1.2"],"schemaVersion":1,"symbols":["lightningcss.transform","detectLibc.familySync","lightningcss-linux-x64-musl","lightningcss-linux-x64-gnu"],"verifierAdapter":"npm@1"}
{
"name": "csx-lightningcss-musl-prebuild",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "csx-lightningcss-musl-prebuild",
"version": "1.0.0",
"dependencies": {
"lightningcss": "1.33.0"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/lightningcss": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
"integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
},
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-android-arm64": "1.33.0",
"lightningcss-darwin-arm64": "1.33.0",
"lightningcss-darwin-x64": "1.33.0",
"lightningcss-freebsd-x64": "1.33.0",
"lightningcss-linux-arm-gnueabihf": "1.33.0",
"lightningcss-linux-arm64-gnu": "1.33.0",
"lightningcss-linux-arm64-musl": "1.33.0",
"lightningcss-linux-x64-gnu": "1.33.0",
"lightningcss-linux-x64-musl": "1.33.0",
"lightningcss-win32-arm64-msvc": "1.33.0",
"lightningcss-win32-x64-msvc": "1.33.0"
}
},
"node_modules/lightningcss-android-arm64": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
"integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
"integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
"integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
"integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
"integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
"cpu": [
"arm"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
"integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
"integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
"integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
"integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
"integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
"integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
}
}
}
{
"name": "csx-lightningcss-musl-prebuild",
"version": "1.0.0",
"private": true,
"type": "module",
"dependencies": {
"lightningcss": "1.33.0"
}
}
import { createRequire } from "node:module";
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
const require = createRequire(import.meta.url);
const seedRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
/**
* "Run lightningcss on Alpine" is the opposite problem from running esbuild
* there, and the two get the same advice, which is wrong for one of them.
*
* esbuild ships one static Go binary per platform with no libc variants at
* all. lightningcss ships a real napi shared object per platform AND per
* libc: lightningcss-linux-x64-gnu links against ld-linux-x86-64.so.2 and
* libc.so.6, lightningcss-linux-x64-musl links against libc.musl-x86_64.so.1.
* Only one of the two can be dlopen'd on a given image, so something has to
* choose, and the interesting question is what.
*
* The folklore answer is "npm picks it, because the platform packages declare
* a libc field". That is only half right, and the half that is wrong is the
* half that matters in a Dockerfile. See lockedPlatformEntries below.
*
* What actually loads the right binary is lightningcss's own loader: it calls
* detect-libc's familySync() at require time and requires
* `lightningcss-${platform}-${arch}-${musl|gnu}` by name. npm's filtering
* only decides which packages are on disk; the loader decides which one runs.
*/
/**
* lightningcss targets are packed version numbers: major << 16 | minor << 8 |
* patch. Passing a bare `{ chrome: 130 }` is not Chrome 130, it is Chrome
* 0.0.130, so every lowering fires and you conclude that targets are ignored
* or that lightningcss is broken. The shift is not optional.
*/
export function browserVersion(major, minor = 0, patch = 0) {
return (major << 16) | (minor << 8) | patch;
}
/**
* transform() takes a Buffer and returns `code` as a Buffer, not a string.
* Comparing the result to a string silently fails for the same reason
* Buffer.from("a") !== "a".
*/
export function compile(css, options = {}) {
const lightningcss = require("lightningcss");
const { code } = lightningcss.transform({
filename: "input.css",
minify: true,
...options,
code: Buffer.from(css),
});
return code.toString();
}
export function transformThrows(css, options = {}) {
try {
compile(css, options);
return null;
} catch (err) {
return err;
}
}
/** The specifier lightningcss's loader builds for this machine. */
export function platformPackageName() {
const { MUSL, familySync } = require("detect-libc");
const parts = [process.platform, process.arch];
if (process.platform === "linux") {
if (familySync() === MUSL) parts.push("musl");
else if (process.arch === "arm") parts.push("gnueabihf");
else parts.push("gnu");
} else if (process.platform === "win32") {
parts.push("msvc");
}
return `lightningcss-${parts.join("-")}`;
}
export function libcFamily() {
const { familySync, MUSL } = require("detect-libc");
return { family: familySync(), isMusl: familySync() === MUSL };
}
function loaderPath() {
// lightningcss has an exports map with no "./package.json" entry, so
// require.resolve("lightningcss/package.json") throws
// ERR_PACKAGE_PATH_NOT_EXPORTED. Walk up from the loader instead.
return require.resolve("lightningcss");
}
/**
* The loader's own source. Every claim about how the platform package gets
* chosen is checkable there, so read it instead of inferring the mechanism
* from the fact that the right binary happened to load.
*/
export function loaderSource() {
return readFileSync(loaderPath(), "utf8");
}
export function installedTree() {
const loader = loaderPath();
const packageDir = path.resolve(path.dirname(loader), "..");
const nodeModules = path.resolve(packageDir, "..");
const manifest = JSON.parse(readFileSync(path.join(packageDir, "package.json"), "utf8"));
return {
loader,
packageDir,
nodeModules,
declaredPlatformPackages: Object.keys(manifest.optionalDependencies ?? {}),
runtimeDependencies: Object.keys(manifest.dependencies ?? {}),
installedPlatformPackages: readdirSync(nodeModules)
.filter((name) => name.startsWith("lightningcss-"))
.sort(),
// The loader's fallback target, in the package root one level above
// node/index.js. The published tarball's files list is node/*.js plus the
// type files, so this path exists only after a local build from source.
localBuildPresent: existsSync(
path.join(packageDir, `${platformPackageName().replace("lightningcss-", "lightningcss.")}.node`),
),
};
}
export function platformManifest(name) {
return require(`${name}/package.json`);
}
export function nativeBinaryPath(name) {
const { nodeModules } = installedTree();
return path.join(nodeModules, name, platformManifest(name).main);
}
export function binarySize(name) {
return statSync(nativeBinaryPath(name)).size;
}
/**
* Which .node files this process actually dlopen'd. Native addons land in the
* CJS module cache keyed by their resolved filename, so this is a receipt
* rather than an inference about what the loader chose.
*/
export function loadedNativeModules() {
return Object.keys(require.cache)
.filter((file) => file.endsWith(".node"))
.sort();
}
/**
* The lockfile's own view of the platform packages. The npm that ships in
* node:22-alpine records `os` and `cpu` per entry and drops `libc`, even
* though the registry manifests carry it, which is why `npm ci` on Alpine
* cannot narrow linux-x64 down to one libc the way `npm install` can. What
* decides this is the npm that wrote the lock, not the format: npm 11.11.0
* added `libc` to the writer's pkgMetaKeys (npm/cli#9025, closing
* npm/cli#8514) and records it in a still-lockfileVersion-3 lock.
*
* It is not specific to `npm ci`, either: a plain `npm install` re-run
* against a libc-less lock installs both variants too. Nor to lightningcss —
* rollup's platform packages behave identically.
*/
export function lockedPlatformEntries() {
const lock = JSON.parse(readFileSync(path.join(seedRoot, "package-lock.json"), "utf8"));
const entries = new Map();
for (const [key, value] of Object.entries(lock.packages)) {
const name = key.replace(/^.*node_modules\//, "");
if (!name.startsWith("lightningcss-")) continue;
entries.set(name, { os: value.os, cpu: value.cpu, libc: value.libc, optional: value.optional });
}
return { lockfileVersion: lock.lockfileVersion, entries };
}
/**
* The npm bundled with the image, read beside the running node. The lockfile
* above is whatever this npm wrote, so the version is part of the finding.
*/
export function bundledNpmVersion() {
const manifest = path.join(path.dirname(process.execPath), "..", "lib", "node_modules", "npm", "package.json");
return JSON.parse(readFileSync(manifest, "utf8")).version;
}
export function requireFailure(specifier, from = loaderPath()) {
const scoped = createRequire(from);
try {
scoped(specifier);
return null;
} catch (err) {
return err;
}
}
/**
* DT_NEEDED entries of an ELF64 shared object, read from the dynamic section
* rather than taken on faith. A .node is ET_DYN with no PT_INTERP, so the
* only evidence of which libc it was built against is the list of libraries
* it asks the loader for.
*/
export function elfNeededLibraries(binaryPath) {
const buf = readFileSync(binaryPath);
if (buf.toString("latin1", 0, 4) !== "\x7fELF") throw new Error("not an ELF file");
if (buf[4] !== 2) throw new Error("expected a 64-bit ELF");
const phoff = Number(buf.readBigUInt64LE(0x20));
const phentsize = buf.readUInt16LE(0x36);
const phnum = buf.readUInt16LE(0x38);
const PT_LOAD = 1;
const PT_DYNAMIC = 2;
const loads = [];
let dynamic = null;
for (let i = 0; i < phnum; i++) {
const at = phoff + i * phentsize;
const type = buf.readUInt32LE(at);
const segment = {
offset: Number(buf.readBigUInt64LE(at + 0x08)),
vaddr: Number(buf.readBigUInt64LE(at + 0x10)),
filesz: Number(buf.readBigUInt64LE(at + 0x20)),
};
if (type === PT_LOAD) loads.push(segment);
if (type === PT_DYNAMIC) dynamic = segment;
}
if (!dynamic) throw new Error("no PT_DYNAMIC segment");
const fileOffsetOf = (vaddr) => {
const load = loads.find((s) => vaddr >= s.vaddr && vaddr < s.vaddr + s.filesz);
if (!load) throw new Error(`vaddr 0x${vaddr.toString(16)} is in no PT_LOAD segment`);
return load.offset + (vaddr - load.vaddr);
};
const DT_NULL = 0;
const DT_NEEDED = 1;
const DT_STRTAB = 5;
const needed = [];
let strtab = null;
for (let at = dynamic.offset; at < dynamic.offset + dynamic.filesz; at += 16) {
const tag = Number(buf.readBigUInt64LE(at));
const value = Number(buf.readBigUInt64LE(at + 8));
if (tag === DT_NULL) break;
if (tag === DT_STRTAB) strtab = fileOffsetOf(value);
if (tag === DT_NEEDED) needed.push(value);
}
if (strtab === null) throw new Error("no DT_STRTAB");
return needed.map((offset) => {
const start = strtab + offset;
const end = buf.indexOf(0, start);
return buf.toString("latin1", start, end);
});
}
import assert from "node:assert/strict";
import { createRequire } from "node:module";
import {
binarySize,
browserVersion,
bundledNpmVersion,
compile,
elfNeededLibraries,
installedTree,
libcFamily,
loadedNativeModules,
loaderSource,
lockedPlatformEntries,
nativeBinaryPath,
platformManifest,
platformPackageName,
requireFailure,
transformThrows,
} from "../src/compile.mjs";
const require = createRequire(import.meta.url);
// The question itself: lightningcss compiles CSS on a musl image, installed
// with lifecycle scripts disabled. Shorthand merging is the cheapest proof
// that the native minifier ran and not some JS fallback.
assert.equal(
compile(".foo { margin-left: 10px; margin-right: 10px; margin-top: 20px; margin-bottom: 20px; }"),
".foo{margin:20px 10px}",
);
// `code` comes back as a Buffer, and a Buffer coerces to its own utf8 text
// under == and in template strings while never being === the same string. So
// logging the result looks right and asserting on it strictly fails, which is
// the first thing people hit after getting the install itself sorted out.
const raw = require("lightningcss").transform({
filename: "input.css",
code: Buffer.from(".a{color:red}"),
minify: true,
});
assert.equal(Buffer.isBuffer(raw.code), true);
assert.notStrictEqual(raw.code, ".a{color:red}");
assert.ok(raw.code == ".a{color:red}");
assert.equal(`${raw.code}`, ".a{color:red}");
assert.equal(raw.code.toString(), ".a{color:red}");
// Real lowering, driven by targets: Chrome 90 has no native nesting, so the
// nested rule is flattened. `blue` also minifies to #00f.
const nested = ".card { color: red; & .title { color: blue; } }";
assert.equal(
compile(nested, { targets: { chrome: browserVersion(90) } }),
".card{color:red}.card .title{color:#00f}",
);
assert.equal(
compile(nested, { targets: { chrome: browserVersion(130) } }),
".card{color:red;& .title{color:#00f}}",
);
// targets are packed as major << 16 | minor << 8 | patch. `{ chrome: 130 }`
// means Chrome 0.0.130, so it lowers everything and looks like targets were
// ignored. Same number, opposite result.
assert.equal(
compile(nested, { targets: { chrome: 130 } }),
".card{color:red}.card .title{color:#00f}",
);
// @custom-media needs BOTH the draft flag and a targets object: the flag only
// makes it parse, targets are what make lightningcss lower it. With the flag
// alone the at-rule survives into the output, which reads like a silent
// failure. The lowered form joins the queries with `or`, not with a comma.
const customMedia = "@custom-media --modern (color), (hover);\n@media (--modern) { .a { color: red } }";
assert.equal(
compile(customMedia, { drafts: { customMedia: true } }),
"@custom-media --modern (color),(hover);@media (--modern){.a{color:red}}",
);
assert.equal(
compile(customMedia, { drafts: { customMedia: true }, targets: { chrome: browserVersion(90) } }),
"@media (color) or (hover){.a{color:red}}",
);
// Referencing an undefined custom media is a hard error with a machine
// readable shape, not a warning.
const undefinedMedia = transformThrows("@media (--nope) { .a { color: red } }", {
drafts: { customMedia: true },
targets: { chrome: browserVersion(90) },
});
assert.equal(undefinedMedia?.constructor.name, "SyntaxError");
assert.deepEqual(undefinedMedia.data, { type: "CustomMediaNotDefined", name: "--nope" });
assert.deepEqual(undefinedMedia.loc, { line: 1, column: 1 });
// Which binary is running, measured from the CJS cache rather than guessed.
// Exactly one .node was dlopen'd and it is the musl one.
const loaded = loadedNativeModules();
assert.equal(loaded.length, 1);
assert.equal(
loaded[0].endsWith("/lightningcss-linux-x64-musl/lightningcss.linux-x64-musl.node"),
true,
loaded[0],
);
// It is lightningcss's own loader that made that choice, from detect-libc, at
// require time. npm never enters into it, and detect-libc is the only runtime
// dependency lightningcss has.
assert.deepEqual(libcFamily(), { family: "musl", isMusl: true });
assert.equal(platformPackageName(), "lightningcss-linux-x64-musl");
assert.deepEqual(installedTree().runtimeDependencies, ["detect-libc"]);
// That mechanism is quoted from the installed loader, not reconstructed from
// the fact that the right binary happened to load: familySync() decides the
// suffix, the platform package is required by name, and a .node in the package
// root is only the fallback. The fallback is what makes the classic error
// message misleading — see the end of this file.
const loaderSrc = loaderSource();
assert.ok(
loaderSrc.includes("const { MUSL, familySync } = require('detect-libc');"),
"loader no longer asks detect-libc for the family",
);
assert.ok(
loaderSrc.includes("native = require(`lightningcss-${parts.join('-')}`);"),
"loader no longer requires the platform package by name",
);
assert.ok(
loaderSrc.includes("native = require(`../lightningcss.${parts.join('-')}.node`);"),
"loader no longer falls back to a .node in the package root",
);
// Unlike esbuild, lightningcss really does publish per-libc platform packages,
// and each declares the libc field npm filters on.
const tree = installedTree();
assert.equal(tree.declaredPlatformPackages.length, 11);
assert.ok(tree.declaredPlatformPackages.includes("lightningcss-linux-x64-gnu"));
assert.ok(tree.declaredPlatformPackages.includes("lightningcss-linux-x64-musl"));
assert.deepEqual(platformManifest("lightningcss-linux-x64-musl").libc, ["musl"]);
assert.deepEqual(platformManifest("lightningcss-linux-x64-gnu").libc, ["glibc"]);
assert.deepEqual(platformManifest("lightningcss-linux-x64-musl").os, ["linux"]);
// And here is the part that contradicts the usual explanation. This tree was
// installed with `npm ci`, and BOTH linux-x64 libc variants are on disk. npm
// does honour libc when it resolves from the registry — a fresh `npm install`
// on this image installs the musl package alone — but the lock this image's
// npm writes records os and cpu per entry and no libc, so a later install from
// that lock has nothing to narrow linux-x64 with. os and cpu are recorded,
// which is why every darwin, win32, freebsd, android and arm64 package was
// still correctly skipped.
//
// What decides this is the npm that wrote the lock, not the lockfile format.
// This was npm/cli#8514, fixed by npm/cli#9025, which added `libc` to
// pkgMetaKeys in shrinkwrap.js and shipped in npm 11.11.0 on 2026-02-24:
// from 11.11.0 onward the writer records libc into a still-lockfileVersion-3
// lock and `npm ci` from it installs only the musl package.
//
// The fix is in the WRITER alone, which is what makes it easy to miss.
// Bisected on this image: npm 10.9.8 through 11.10.1 omit libc, 11.11.0
// onward record it. An npm 12 reading a lock written by npm 10 still
// installs both, and an npm 10 reading a lock written by npm 11.11.0
// installs only musl. So upgrading npm is not the fix — regenerating the
// lockfile is.
assert.deepEqual(tree.installedPlatformPackages, [
"lightningcss-linux-x64-gnu",
"lightningcss-linux-x64-musl",
]);
assert.match(
bundledNpmVersion(),
/^10\./,
`image npm is ${bundledNpmVersion()}: npm 11.11.0 and later record libc in the lockfile, and a lock written by one of those narrows to the musl package alone`,
);
const { lockfileVersion, entries } = lockedPlatformEntries();
assert.equal(lockfileVersion, 3);
assert.equal(entries.size, 11);
for (const [name, entry] of entries) {
assert.ok(Array.isArray(entry.os), `${name} has no os in the lockfile`);
assert.ok(Array.isArray(entry.cpu), `${name} has no cpu in the lockfile`);
assert.equal(entry.libc, undefined, `${name} unexpectedly records libc in the lockfile`);
assert.equal(entry.optional, true);
}
// The cost of that is not theoretical: the glibc binary is a full copy of the
// same 10 MB addon, shipped in the image, unloadable, never opened.
const muslBytes = binarySize("lightningcss-linux-x64-musl");
const gnuBytes = binarySize("lightningcss-linux-x64-gnu");
assert.ok(muslBytes > 9_000_000, `musl binary is ${muslBytes} bytes`);
assert.ok(Math.abs(gnuBytes - muslBytes) / muslBytes < 0.05, `${muslBytes} vs ${gnuBytes}`);
// Why only one of them can ever work: these are dynamically linked shared
// objects, read here from their ELF dynamic sections. The musl build asks for
// musl's libc, the glibc build asks for glibc's loader and libc.
const muslNeeded = elfNeededLibraries(nativeBinaryPath("lightningcss-linux-x64-musl"));
assert.ok(muslNeeded.includes("libc.musl-x86_64.so.1"), muslNeeded.join(","));
assert.equal(muslNeeded.some((lib) => lib.startsWith("libc.so.")), false, muslNeeded.join(","));
const gnuNeeded = elfNeededLibraries(nativeBinaryPath("lightningcss-linux-x64-gnu"));
assert.ok(gnuNeeded.includes("libc.so.6"), gnuNeeded.join(","));
assert.ok(gnuNeeded.includes("ld-linux-x86-64.so.2"), gnuNeeded.join(","));
// The two failures people conflate, separated. Loading a wrong-libc binary
// that IS installed fails at dlopen, naming the missing loader.
const dlopenErr = requireFailure(nativeBinaryPath("lightningcss-linux-x64-gnu"));
assert.equal(dlopenErr?.code, "ERR_DLOPEN_FAILED");
assert.match(dlopenErr.message, /ld-linux-x86-64\.so\.2/);
// A platform package that is absent fails at resolution instead.
const missingPkg = requireFailure("lightningcss-darwin-arm64");
assert.equal(missingPkg?.code, "MODULE_NOT_FOUND");
// So "Cannot find module '../lightningcss.linux-x64-musl.node'" is not a
// broken package and not a wrong-libc binary. It is the fallback quoted above:
// a .node in the package root, which only exists after building lightningcss
// from source. When the platform package is missing — pruned, --no-optional,
// --omit=optional, or a node_modules copied in from a glibc machine — the
// fallback is what throws, so the error names a file nobody ever installed and
// hides the real cause. Same specifier as the loader's, required from the same
// directory.
assert.equal(tree.localBuildPresent, false);
const fallbackErr = requireFailure("../lightningcss.linux-x64-musl.node");
assert.equal(fallbackErr?.code, "MODULE_NOT_FOUND");
assert.match(fallbackErr.message, /Cannot find module '\.\.\/lightningcss\.linux-x64-musl\.node'/);
console.log("contract ok");