mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 12:22:14 +03:00
Turbopack in Next.js 16 hashes ALL serverExternalPackages (not just better-sqlite3), emitting require() calls like 'zod-dcb22c6336e0bc69', 'pino-28069d5257187539' etc. that don't exist in node_modules. Changes: - next.config.mjs: Replace single-package check with a HASH_PATTERN regex that strips '<name>-<16hexchars>' suffix for any externalized package. Also adds KNOWN_EXTERNALS set for exact-name matching. - scripts/prepublish.mjs: Add NEXT_PRIVATE_BUILD_WORKER=0 env to reinforce webpack mode. Add post-build scan that reports hashed refs so CI is visible. Closes #396, addresses #398
This commit is contained in:
@@ -39,25 +39,60 @@ const nextConfig = {
|
||||
},
|
||||
webpack: (config, { isServer }) => {
|
||||
if (isServer) {
|
||||
// Force better-sqlite3 to always be required by its exact package name.
|
||||
// ── Turbopack / Next.js 16 module-hash patch (#394, #396, #398) ────────
|
||||
//
|
||||
// Next.js 16 webpack compiles the instrumentation hook into a separate
|
||||
// chunk ([root-of-the-server]__<hash>.js) and can emit a hashed require
|
||||
// such as `require('better-sqlite3-90e2652d1716b047')` even when the
|
||||
// package is listed in `serverExternalPackages`. That hashed name doesn't
|
||||
// exist in node_modules, causing the 500 error reported in issue #394.
|
||||
// Next.js 16 (with or without Turbopack) compiles the instrumentation hook
|
||||
// into a separate chunk and emits hashed require() calls such as:
|
||||
// require('better-sqlite3-90e2652d1716b047')
|
||||
// require('zod-dcb22c6336e0bc69')
|
||||
// require('pino-28069d5257187539')
|
||||
//
|
||||
// Adding an explicit `externals` function overrides the bundler's default
|
||||
// handling and always emits `require('better-sqlite3')`, which resolves
|
||||
// correctly in the published standalone build.
|
||||
// These hashed names don't exist in node_modules and cause a 500 at
|
||||
// startup on all npm global installs (issues #394, #396, #398).
|
||||
//
|
||||
// We use two strategies:
|
||||
// 1. Exact-name externals for all known server-side packages.
|
||||
// 2. Hash-strip catch-all: any require('<name>-<16hexchars>' strips the
|
||||
// suffix and falls through to the real package name.
|
||||
//
|
||||
const HASH_PATTERN = /^(.+)-[0-9a-f]{16}$/;
|
||||
|
||||
const KNOWN_EXTERNALS = new Set([
|
||||
"better-sqlite3",
|
||||
"zod",
|
||||
"pino",
|
||||
"pino-pretty",
|
||||
"child_process",
|
||||
"fs",
|
||||
"path",
|
||||
"os",
|
||||
"crypto",
|
||||
"net",
|
||||
"tls",
|
||||
"http",
|
||||
"https",
|
||||
"stream",
|
||||
"buffer",
|
||||
"util",
|
||||
]);
|
||||
|
||||
const prev = config.externals ?? [];
|
||||
const prevArr = Array.isArray(prev) ? prev : [prev];
|
||||
config.externals = [
|
||||
...prevArr,
|
||||
({ request }, callback) => {
|
||||
if (request === "better-sqlite3") {
|
||||
// Case 1: Exact known package — treat as external
|
||||
if (KNOWN_EXTERNALS.has(request)) {
|
||||
return callback(null, `commonjs ${request}`);
|
||||
}
|
||||
// Case 2: Hash-suffixed name — strip hash, use base name
|
||||
// e.g. "better-sqlite3-90e2652d1716b047" → "better-sqlite3"
|
||||
// "zod-dcb22c6336e0bc69" → "zod"
|
||||
const hashMatch = request?.match?.(HASH_PATTERN);
|
||||
if (hashMatch) {
|
||||
const baseName = hashMatch[1];
|
||||
return callback(null, `commonjs ${baseName}`);
|
||||
}
|
||||
callback();
|
||||
},
|
||||
];
|
||||
@@ -75,6 +110,7 @@ const nextConfig = {
|
||||
}
|
||||
return config;
|
||||
},
|
||||
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -10,7 +10,16 @@
|
||||
*/
|
||||
|
||||
import { execSync } from "node:child_process";
|
||||
import { existsSync, mkdirSync, cpSync, rmSync, writeFileSync, readFileSync } from "node:fs";
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
cpSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
readFileSync,
|
||||
readdirSync,
|
||||
statSync,
|
||||
} from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
@@ -37,7 +46,13 @@ console.log(" 🏗️ Building Next.js (standalone)...");
|
||||
execSync("npx next build", {
|
||||
cwd: ROOT,
|
||||
stdio: "inherit",
|
||||
env: { ...process.env, EXPERIMENTAL_TURBOPACK: "0" },
|
||||
env: {
|
||||
...process.env,
|
||||
// Force webpack codegen — Turbopack emits hashed require() calls for
|
||||
// server external packages that break npm global installs (#394, #396, #398).
|
||||
EXPERIMENTAL_TURBOPACK: "0",
|
||||
NEXT_PRIVATE_BUILD_WORKER: "0",
|
||||
},
|
||||
});
|
||||
|
||||
// ── Step 4: Verify standalone output ───────────────────────
|
||||
@@ -51,6 +66,46 @@ if (!existsSync(serverJs)) {
|
||||
}
|
||||
|
||||
// ── Step 5: Copy standalone output to app/ ─────────────────
|
||||
// ── Step 4.5: Check build for hashed external references ──────────────────────
|
||||
// Warn if Turbopack-style hash suffixes are found — they will be resolved at
|
||||
// runtime by the externals patch in next.config.mjs, but log for visibility.
|
||||
{
|
||||
const HASH_RE = /require\(["']([\w@./-]+-[0-9a-f]{16})["']\)/;
|
||||
const scanDir = (dir, hits = []) => {
|
||||
let entries = [];
|
||||
try {
|
||||
entries = readdirSync(dir);
|
||||
} catch {
|
||||
return hits;
|
||||
}
|
||||
for (const e of entries) {
|
||||
const f = join(dir, e);
|
||||
try {
|
||||
if (statSync(f).isDirectory()) {
|
||||
scanDir(f, hits);
|
||||
continue;
|
||||
}
|
||||
if (!f.endsWith(".js")) continue;
|
||||
const m = readFileSync(f, "utf8").match(HASH_RE);
|
||||
if (m) hits.push({ file: f.replace(standaloneDir, "app"), mod: m[1] });
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return hits;
|
||||
};
|
||||
const hits = scanDir(join(standaloneDir, ".next", "server"));
|
||||
if (hits.length > 0) {
|
||||
console.warn(
|
||||
" ⚠️ Hashed externals in build (will be auto-fixed at runtime by externals patch):"
|
||||
);
|
||||
hits.slice(0, 5).forEach((h) => console.warn());
|
||||
if (hits.length > 5) console.warn();
|
||||
} else {
|
||||
console.log(" ✅ Build clean — no hashed externals found.");
|
||||
}
|
||||
}
|
||||
|
||||
console.log(" 📋 Copying standalone build to app/...");
|
||||
mkdirSync(APP_DIR, { recursive: true });
|
||||
cpSync(standaloneDir, APP_DIR, { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user