nitro: `vercel-edge` preset should not target node runtime in `jose` npm package

Environment

Nuxt 3.5.2 Nitro 2.4.1 Node v16.14.2 & v18.16.0

Reproduction

https://stackblitz.com/edit/nuxt-starter-zmpnuh?file=server%2Futils%2Futils.js

It should run yarn install && NITRO_PRESET=vercel-edge yarn build automatically. Otherwise, use those commands.

Describe the bug

I’m using the library jose in the server functions of my Nuxt project because it has support for edge runtimes, described here. It uses Web Crypto API instead of Node ones.

The package has both runtimes bundled: a node runtime which uses node functions, and a browser runtime which should work with everything else without depending on node.

When I build the project with the default Nitro preset, which targets Node, everything works fine. With NITRO_PRESET=vercel-edge, the following error appears:

ℹ Building Nitro Server (preset: vercel-edge)                                                                                                                 nitro 3:15:01 PM

[nitro 3:15:06 PM]  ERROR  RollupError: "createHash" is not exported by "node_modules/unenv/runtime/node/crypto/index.mjs", imported by "node_modules/jose/dist/node/esm/runtime/digest.js".


1: import { createHash } from 'crypto';
            ^
2: const digest = (algorithm, data) => createHash(algorithm).update(data).digest();
3: export default digest;


 ERROR  "createHash" is not exported by "node_modules/unenv/runtime/node/crypto/index.mjs", imported by "node_modules/jose/dist/node/esm/runtime/digest.js".        3:15:06 PM

It seems that it still targets the node runtime of the package and tries to use unenv to polyfill the createHash function, which doesn’t have. In next.js, it seems to work out of the box when deploying to the Edge.

I don’t know if this is an issue from Nitro, unenv or jose, or if I should configure something to target the proper edge runtimes from jose in the build process.

The following is part of the package.json from jose:

"exports": {
  ".": {
    "types": "./dist/types/index.d.ts",
    "bun": "./dist/browser/index.js",
    "deno": "./dist/browser/index.js",
    "browser": "./dist/browser/index.js",
    "worker": "./dist/browser/index.js",
    "import": "./dist/node/esm/index.js",
    "require": "./dist/node/cjs/index.js"
  },
  "./package.json": "./package.json"
},
"main": "./dist/node/cjs/index.js",
"browser": "./dist/browser/index.js",
"types": "./dist/types/index.d.ts",
"files": [
  "dist/**/package.json",
  "dist/**/*.js",
  "dist/types/**/*.d.ts",
  "!dist/**/*.bundle.js",
  "!dist/**/*.umd.js",
  "!dist/**/*.min.js",
  "!dist/node/webcrypto/**/*",
  "!dist/types/runtime/*",
  "!dist/types/lib/*",
  "!dist/deno/**/*"
],
"deno": "./dist/browser/index.js"

Thank you so much!

Additional context

No response

Logs

No response

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 15 (5 by maintainers)

Commits related to this issue

Most upvoted comments

Hi, you can solve this by adding this to nuxt.config.ts:

import {createResolver} from '@nuxt/kit'

const { resolve } = createResolver(import.meta.url)

export default defineNuxtConfig({
  alias: {
    "jose": resolve(__dirname, "./node_modules/jose/dist/browser/index.js"),
    "@panva/hkdf": resolve(__dirname, "./node_modules/@panva/hkdf/dist/web/index.js")
  }
)}

Somewhere in the server side code of a Nuxt application. You might need to tweak this an use a real import instead of static import. Lucia has something similar in their docs https://lucia-auth.com/start-here/getting-started?nuxt#polyfill-crypto-global for example https://github.com/pilcrowOnPaper/lucia/blob/main/packages/lucia/src/polyfill/node.ts

I am not sure if this is the right place or if I should open a new issue for this but using the crypto global from newer node versions does not work in the generated build version where “crypto is not defined” is thrown. Instead, I have to manually import the used methods from node:crypto which makes prevents it from being runtime agnostic 😕

I’m using a workaround like this :

if (!globalThis.crypto) {
  // eslint-disable-next-line no-console
  console.log("Polyfilling crypto...")
  import("node:crypto").then((crypto) => {
    Object.defineProperty(globalThis, "crypto", {
      value: crypto.webcrypto || crypto,
      writable: false,
      configurable: true
    })
  })
}

In the meantime, we can track this here https://github.com/unjs/nitro/issues/1371

Thank you so much @v-moravec, that workaround works!

Okay yeah thought of the second way, didnt know the first existed