I have two modules 'web,ts' and 'node.ts' with identical interfaces. The first is meant to run on both the client side and the edge environment while the other relies on node:crypto.
I would like to have a single module (dyna.ts) that acts as a front for both that I can import from anywhere in the project without worrying about the current environment.
I have attempted this but the compiler always crashes, as an example my last attempt:
import webBox from "./web";
type CryptoBox = {
hash: (data: string, salt: string, ...moreSalt: string[]) => Promise<string>;
random: (range: bigint) => bigint;
};
let _hash: CryptoBox["hash"] | undefined;
let _random: CryptoBox["random"] | undefined;
if (typeof crypto === "undefined" && process.release.name === "node") {
try {
let box = require("./node");
_hash = box.hash;
_random = box.random;
} catch (error) {
if (typeof webBox.hash !== "undefined") _hash = webBox.hash;
if (typeof webBox.random !== "undefined") _random = webBox.random;
}
} else {
if (typeof webBox.hash !== "undefined") _hash = webBox.hash;
if (typeof webBox.random !== "undefined") _random = webBox.random;
}
if (typeof _hash === "undefined") throw new Error("no hash function available");
if (typeof _random === "undefined")
throw new Error("no random function available");
export const hash = _hash;
export const random = _random;
note1: The type CryptoBox
is the type of the default export of both './node.ts' and './web.ts'
note2: Checking typeof window === undefined
is useless here as the edge runtime lacks a window but cannot use node:crypto, and even if it did work require("./node")
crashes the compiler when wrapped in a if(false)
.
note3: (await import()).default
crashes the same way require()
Hopefully this is possible so I don't have to maintain 3 identical modules (except for what they import) to do basic verification on data passed around by the client middleware and node server.
ps: the error message:
node:crypto
Module build failed: UnhandledSchemeError: Reading from "node:crypto" is not handled by plugins (Unhandled scheme).
Webpack supports "data:" and "file:" URIs by default.
You may need an additional plugin to handle "node:" URIs.
Import trace for requested module:
node:crypto
./src/lib/crypto/node.ts
./src/lib/crypto/dyna.ts
./src/lib/server_only/auth/login.ts
pps: (for Mike) the older variant I tried first:
type CryptoBox={
hash: (data: string, salt: string, ...moreSalt: string[]) => Promise<string>;
random: (range: bigint) => bigint;
}
let box:CryptoBox;
try{
box=require("./node");
}catch(error){
box=require("./web");
}
export const hash=box.hash;
export const random=box.random;
The function webpack.NormalModuleReplacementPlugin along with the webpack function arguments found next.js's Custom Webpack Config documentation solve this problem using the following next.config.js
file:
const webpack = require('webpack');
/**
* @typedef {Object} NextJsConfigContext
* @property {undefined|"nodejs"|"web"} nextRuntime - Specifies the runtime environment, e.g., "nodejs" or "web". (undefined for client side)
*/
module.exports = {
/**
* @param {import('webpack').Configuration} config - The webpack configuration object.
* @param {NextJsConfigContext} context - The context object for the Next.js configuration.
* @returns {import('webpack').Configuration} - The modified webpack configuration object.
*/
webpack: (config, {nextRuntime}) => {
const interfaceEnv=(nextRuntime==="nodejs")?"-node":"-web";
config.plugins.push(
new webpack.NormalModuleReplacementPlugin(
/(.*)-WebOrNode(\.*)/,
function (resource){
resource.request = resource.request.replace(
"-WebOrNode",
interfaceEnv
)
}
)
);
return config;
}
};
Now by re-naming node.ts
and web.ts
to core-node.ts
and core-web.ts
respectively, dyna.ts
is reduced to:
import { CryptoBox } from "../types";
//@ts-expect-error
import _box from './core-WebOrNode';//! points to different files depending on the environment (core-WebOrNode.jd is non-extant)
const box:CryptoBox=_box;
export default box;
(or just import from the non-existent './core-WebOrNode'
file directly in the same way dyna.ts
would instead of using it as a wrapper)