fix(docker): bundle LLMLingua optional dependencies (#9185)

Validated in local merge-train T4 (HouMinXi+Zartharas+Andrian+artickc)
This commit is contained in:
Aman
2026-08-05 20:52:10 -06:00
committed by GitHub
parent def958b97a
commit d2f3c1abf5
4 changed files with 363 additions and 19 deletions

View File

@@ -48,6 +48,10 @@
import fs from "node:fs/promises";
import fsSync from "node:fs";
import path from "node:path";
import {
colocateLlmlinguaOptionals,
SEED_PACKAGES,
} from "./colocateOptionals.mjs";
/**
* Check whether a path exists (async).
@@ -736,6 +740,19 @@ export function assembleStandalone({
// 6. Optionally copy native assets + extra modules (synchronous)
if (copyNatives) {
copyNativeAssetsAndExtraModules(projectRoot, resolvedOutDir);
// #9166: dynamically imported LLMLingua packages are not reliably traced
// into the standalone bundle. Copy their complete dependency closure from
// the installed root tree without overwriting packages already traced by
// Next.js. Include transformers here so its ONNX runtime closure is also
// guaranteed in Docker/standalone builds.
colocateLlmlinguaOptionals({
rootDir: projectRoot,
targetNodeModulesDir: path.join(resolvedOutDir, "node_modules"),
seeds: [...SEED_PACKAGES, "@huggingface/transformers"],
log: (message) =>
console.log(`[assembleStandalone] ${message.trim()}`),
});
}
// 7. Optionally dereference Turbopack hashed-module symlinks so the bundle is

View File

@@ -97,47 +97,81 @@ export function computeDependencyClosure(nodeModulesDir, seeds = SEED_PACKAGES)
}
/**
* Co-locate the SLM optional closure from `<rootDir>/node_modules` into
* `<rootDir>/dist/node_modules`. No-op when the standalone `dist` bundle or the optional seeds are
* absent, and idempotent once co-located. Never throws.
* Co-locate the SLM optional dependency closure from `<rootDir>/node_modules`
* into a standalone bundle's `node_modules`.
*
* @param {{ rootDir: string, log?: (message: string) => void }} opts
* The default destination remains `<rootDir>/dist/node_modules` for the npm
* postinstall path. Standalone builders, including Docker, may provide
* `targetNodeModulesDir`.
*
* Packages already present in the destination are never overwritten. This
* preserves the standalone bundle's pinned dependency instances while filling
* dynamically imported packages that Next.js did not trace.
*
* @param {{
* rootDir: string,
* targetNodeModulesDir?: string,
* seeds?: string[],
* log?: (message: string) => void
* }} opts
* @returns {{ skipped: true, reason: string }
* | { skipped: false, copied: number, closure: number }}
*/
export function colocateLlmlinguaOptionals({ rootDir, log = () => {} }) {
export function colocateLlmlinguaOptionals({
rootDir,
targetNodeModulesDir,
seeds = SEED_PACKAGES,
log = () => {},
}) {
const rootNm = join(rootDir, "node_modules");
const distNm = join(rootDir, "dist", "node_modules");
const targetNm = targetNodeModulesDir ?? join(rootDir, "dist", "node_modules");
if (!existsSync(distNm)) {
return { skipped: true, reason: "no standalone dist/node_modules" };
if (!existsSync(targetNm)) {
return {
skipped: true,
reason: targetNodeModulesDir
? "no target node_modules"
: "no standalone dist/node_modules",
};
}
// Gate: only run when the optional stack was actually installed (`npm install --include=optional`).
if (!SEED_PACKAGES.every((seed) => existsSync(join(rootNm, seed)))) {
// Only run when every requested closure root was installed.
if (!seeds.every((seed) => existsSync(join(rootNm, seed)))) {
return { skipped: true, reason: "SLM optionals not installed at root" };
}
// Idempotent: the entry package is already co-located → nothing to do.
if (existsSync(join(distNm, "@atjsh", "llmlingua-2"))) {
const closure = computeDependencyClosure(rootNm, seeds);
// Check the complete closure rather than only the entry package. A partially
// populated bundle must still receive any missing transitive dependencies.
if (
closure.length > 0 &&
closure.every((name) => existsSync(join(targetNm, name)))
) {
return { skipped: true, reason: "already co-located" };
}
const closure = computeDependencyClosure(rootNm);
let copied = 0;
for (const name of closure) {
const dest = join(distNm, name);
if (existsSync(dest)) continue; // no-clobber: keep dist's pinned copy (transformers 3.5.2, …)
const dest = join(targetNm, name);
if (existsSync(dest)) continue;
try {
mkdirSync(dirname(dest), { recursive: true });
cpSync(join(rootNm, name), dest, { recursive: true });
copied++;
} catch (err) {
log(` ⚠️ LLMLingua optional co-location failed for ${name}: ${err.message}`);
log(
` ⚠️ LLMLingua optional co-location failed for ${name}: ${err.message}`
);
}
}
if (copied > 0) {
log(` ✅ Co-located ${copied} LLMLingua SLM optional package(s) into dist/node_modules.\n`);
log(
` ✅ Co-located ${copied} LLMLingua SLM optional package(s) into standalone node_modules.\n`
);
}
return { skipped: false, copied, closure: closure.length };