Investigating `createRequire is not a function`
On this page
The task was supposed to be simple: build a demo app. Next.js 15, webpack, ESM, the new prisma-client generator, Prisma Postgres with Driver Adapters. I’d done this kind of thing a hundred times. Wire up a schema, generate the client, run next dev, move on.
I ran next dev and immediately got:
⨯ TypeError: createRequire is not a function What followed was a full day of yak shaving through webpack internals, wasm-bindgen binary formats, and WebAssembly loading semantics.
Some context on the stack
The prisma-client generator is Prisma’s new code generator. Unlike the legacy prisma-client-js, it generates TypeScript directly into your codebase rather than into node_modules. It relies on a WebAssembly-based query compiler, a .wasm file that ships in @prisma/client/runtime/ and gets loaded at runtime to turn Prisma queries into SQL.
Historically, Prisma shipped a Rust-based query engine as a native binary, around 14 MB per target platform. It worked, but serializing data between Rust and TypeScript at runtime created overhead that negated Rust’s speed advantage. The new approach compiles the query compiler to WebAssembly: a ~1.6 MB .wasm module that runs in any JavaScript runtime. No native binaries, no cross-language serialization, and up to 3.4x faster queries on large datasets.
Driver Adapters are the other half of the picture. Instead of Prisma managing database connections through the Rust engine, Driver Adapters let Prisma Client talk to databases through JavaScript-native drivers like @prisma/adapter-pg for PostgreSQL. This is what makes Prisma Postgres and edge-compatible connections possible. Together with the Wasm Query Compiler, they form the serverless-friendly Prisma stack.
My schema looked like this:
generator client {
provider = "prisma-client"
output = "../lib/.generated/prisma"
previewFeatures = ["driverAdapters", "queryCompiler"]
} My package.json had "type": "module". My next.config.mjs was the default. No Edge runtime, no middleware. Plain Node.js.
The error pointed to the generated code in lib/.generated/prisma/internal/class.ts:
getQueryCompilerWasmModule: async () => {
const { readFile } = await import('node:fs/promises')
const { createRequire } = await import('node:module')
const require = createRequire(import.meta.url)
// ^^^^^^^^^^ ⨯ TypeError: createRequire is not a function
const wasmModulePath = require.resolve(
"@prisma/client/runtime/query_compiler_bg.postgresql.wasm"
)
const wasmModuleBytes = await readFile(wasmModulePath)
return new globalThis.WebAssembly.Module(wasmModuleBytes)
} A couple of users had already reported the same crash: prisma#27049 and prisma#27343. No workaround existed. Time to dig in.
Why webpack was the problem
The generated code is perfectly standard Node.js: dynamically import node:module, grab createRequire, build a require function, use require.resolve to find the .wasm file on disk. Nothing exotic.
Webpack doesn’t see it that way.
Webpack intercepts every import() and require() call. When it encounters await import('node:module'), it tries to resolve that module at compile time, fails, and replaces it with a broken shim. At runtime, createRequire is not a function; it’s whatever webpack left behind.
This isn’t a Prisma bug. It’s a known webpack limitation: webpack doesn’t handle the createRequire + require.resolve pattern correctly.
My first thought was import.meta.resolve, the standard way to resolve module paths in ESM. Webpack doesn’t support that either. That issue has been open since 2022.
Two obvious approaches, both dead on arrival. Time to get creative.
Dead end #1: direct WASM import
What if I bypassed createRequire entirely and imported the .wasm file directly?
getQueryCompilerWasmModule: async () => {
return await import(
"@prisma/client/runtime/query_compiler_bg.postgresql.wasm"
)
} Webpack rejected it immediately:
Module parse failed: Unexpected character '' (1:0)
The module seem to be a WebAssembly module, but module is not flagged
as WebAssembly module for webpack.
BREAKING CHANGE: Since webpack 5 WebAssembly is not enabled by default
and flagged as experimental feature.
You need to enable one of the WebAssembly experiments via
'experiments.asyncWebAssembly: true' (based on async modules) or
'experiments.syncWebAssembly: true' (like webpack 4, deprecated). Since webpack 5, WebAssembly support is opt-in.
Dead end #2: asyncWebAssembly
I added the experiment flag to next.config.mjs:
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (config, { isServer: _ }) => {
config.experiments = {
...config.experiments,
asyncWebAssembly: true,
}
return config
},
}
export default nextConfig Progress: webpack now tried to process the .wasm file. But it failed differently:
Module not found: Can't resolve './query_compiler_bg.js' Where does query_compiler_bg.js come from? It’s baked into the .wasm binary itself. I confirmed with strings:
strings node_modules/@prisma/client/runtime/query_compiler_bg.postgresql.wasm \
| grep "query_compiler_bg.js" \
| head -1
# --> ./query_compiler_bg.js Here’s what happens. Our build script in prisma-engines compiles each database provider’s WASM artifact with wasm-bindgen --out-name query_compiler. That encodes an import instruction, (import "./query_compiler_bg.js" ...), directly into the binary. We publish the artifacts that way in @prisma/query-compiler-wasm.
But then our client generation logic renames the files by inserting the database provider name:
ls node_modules/@prisma/client/runtime/query_compiler_bg* query_compiler_bg.mysql.js
query_compiler_bg.mysql.mjs
query_compiler_bg.mysql.wasm
query_compiler_bg.postgresql.js
query_compiler_bg.postgresql.mjs
query_compiler_bg.postgresql.wasm
query_compiler_bg.sqlite.js
query_compiler_bg.sqlite.mjs
query_compiler_bg.sqlite.wasm
query_compiler_bg.sqlserver.js
query_compiler_bg.sqlserver.mjs
query_compiler_bg.sqlserver.wasm The binary looks for query_compiler_bg.js. The file on disk is query_compiler_bg.postgresql.js. Stale reference.
Dead end #3: patching the binary
At this point I was too deep to give up on asyncWebAssembly. If the import reference inside the binary is wrong, why not patch it?
I grabbed wasm-tools and wrote a script to rewrite the import reference in the .wasm file:
#!/bin/bash
# Requirements: cargo binstall wasm-tools
FILE_IN="node_modules/@prisma/client/runtime/query_compiler_bg.postgresql.wasm"
FILE_OUT="node_modules/@prisma/client/runtime/query_compiler_bg.postgresql.patched.wasm"
ORIG="query_compiler_bg.js"
REPL="query_compiler_bg.postgresql.js"
wasm-tools print "${FILE_IN}" \
| sed "s/${ORIG}/${REPL}/g" \
| wasm-tools parse -o "${FILE_OUT}"
echo "✅ All occurrences replaced."
wasm-tools validate "${FILE_OUT}" The patched binary validated. I updated the generated code to point to it:
getQueryCompilerWasmModule: async () => {
const queryCompilerWasm = await import(
"@prisma/client/runtime/query_compiler_bg.postgresql.patched.wasm"
)
return queryCompilerWasm
} New error:
⨯ [TypeError: WebAssembly.Instance(): Argument 0 must be a WebAssembly.Module] I logged what queryCompilerWasm actually contained:
{
memory: Memory [WebAssembly.Memory] {},
__wbg_querycompiler_free: [Function: 1553],
querycompiler_new: [Function: 3660],
querycompiler_compile: [Function: 3578],
querycompiler_compileBatch: [Function: 3579],
__wbindgen_malloc: [Function: 3320],
__wbindgen_realloc: [Function: 3428],
__wbindgen_exn_store: [Function: 4342],
__externref_table_alloc: [Function: 899],
__wbindgen_export_4: Table [WebAssembly.Table] {},
__externref_table_dealloc: [Function: 2543],
__wbindgen_start: [Function: 60]
} Not a WebAssembly.Module. Webpack’s asyncWebAssembly experiment compiles and instantiates the WASM in one shot. What you get back is an object with the exported functions already bound, not the raw WebAssembly.Module that our WasmQueryCompilerLoader expects. We need the raw module so we can instantiate it ourselves with our own imports. Webpack won’t give us that.
I’d seen this exact WebAssembly.Instance error before in prisma#23536, where Cloudflare Pages + Remix ran into the same type mismatch. Different bundler, same fundamental problem.
The asyncWebAssembly path was a dead end entirely. No amount of binary patching would fix a type-level mismatch in how webpack hands back WASM modules.
The fix: __non_webpack_require__
Back to the drawing board. The original generated code was fine: createRequire, require.resolve, readFile, all standard Node.js. The only problem was webpack getting its hands on those calls and mangling them.
What I needed was a way to tell webpack: don’t touch this require.
Turns out there’s a thing for that. It’s called __non_webpack_require__.
Never heard of it? Neither had I.
It’s webpack’s official escape hatch. It’s been around since webpack 2, buried in the Module Variables section of the docs. It emits a plain require() call that webpack won’t parse, transform, or bundle. At runtime, it just calls Node.js’s native require.
The fix:
getQueryCompilerWasmModule: async () => {
const { readFile } = __non_webpack_require__('node:fs/promises')
const { createRequire } = __non_webpack_require__('node:module')
const _require = createRequire(import.meta.url)
const wasmModulePath = _require.resolve(
"@prisma/client/runtime/query_compiler_bg.postgresql.wasm"
)
const wasmModuleBytes = await readFile(wasmModulePath)
return new globalThis.WebAssembly.Module(wasmModuleBytes)
} Two lines changed, everything else identical. It works because Next.js SSR runs in Node.js: require exists, it can resolve node:module natively, and webpack never sees any of it.
Shortly after, a cleaner alternative landed: webpack merged support for the /* webpackIgnore: true */ magic comment on require.resolve:
(typeof __non_webpack_require__ === 'function'
? __non_webpack_require__
: require
).resolve(/* webpackIgnore: true */ '@prisma/client/runtime/query_compiler_bg.postgresql.wasm') This fix shipped in Prisma 6.11.0.
What I learned
Bundlers are not transparent. When I wrote await import('node:module'), I expected Node.js semantics. Webpack had other plans. It intercepts and transforms module loading in ways you don’t notice until something breaks, and when it breaks with WASM and ESM together, the errors point everywhere except the actual problem.
The fix wasn’t in our WASM code, our generator, or our runtime. It was a single webpack-specific keyword that I stumbled on after three failed attempts and a lot of strings-ing through binary files.
Sometimes the answer isn’t deeper understanding of the problem space. Sometimes it’s knowing about an obscure escape hatch that’s been sitting in the docs since webpack 2.
If you’re building a library that loads WASM at runtime and needs to work inside webpack, keep __non_webpack_require__ in mind. It’s the escape hatch you didn’t know existed.
References
- prisma#27049:
TypeError: createRequire is not a functionwith the query compiler - prisma#27343: generated
prisma-clientcode can’t be bundled by Next.js - prisma#23536: same
WebAssembly.Instancetype mismatch on Cloudflare Pages + Remix - webpack#19607: incorrect handling of
createRequireandrequire - webpack#16693:
import.meta.resolvesupport (still open) - webpack#19201:
/* webpackIgnore: true */forrequire.resolve