flightcontrolhq / superjson

Safely serialize JavaScript expressions to a superset of JSON, which includes Dates, BigInts, and more.
https://www.flightcontrol.dev?ref=superjson
MIT License
4.18k stars 90 forks source link

Error [ERR_REQUIRE_ESM]: require() of ES Module #268

Open viniciusteixeiradias opened 1 year ago

viniciusteixeiradias commented 1 year ago

Version 1.13.3: Works fine. Version 2.0.0: Din't work.

I'm using TRPC in my application (Electron + Vite + Vue + Typescript), according to the TRPC documentation I need to install superjson to transport data of types: Date/Map/Set. However, after installing the "superjson": "^2.0.0" lib, I am getting this error when running the application.

App threw an error during load
Error [ERR_REQUIRE_ESM]: require() of ES Module ~/node_modules/superjson/dist/index.js from ~/dist-electron/main/index.js not supported.
Instead change the require of ~/node_modules/superjson/dist/index.js in ~/dist-electron/main/index.js to a dynamic import() which is available in all CommonJS modules.
    at f._load (node:electron/js2c/asar_bundle:2:13330)
    at Object.<anonymous> (~/dist-electron/main/index.js:11:19)
    at f._load (node:electron/js2c/asar_bundle:2:13330)
    at loadApplicationPackage (~/node_modules/electron/dist/Electron.app/Contents/Resources/default_app.asar/main.js:121:16)
    at Object.<anonymous> (~/node_modules/electron/dist/Electron.app/Contents/Resources/default_app.asar/main.js:233:9)
    at f._load (node:electron/js2c/asar_bundle:2:13330)
    at node:electron/js2c/browser_init:2:115657
    at node:electron/js2c/browser_init:2:115860
    at node:electron/js2c/browser_init:2:115864
    at f._load (node:electron/js2c/asar_bundle:2:13330)

tsconfig.json:

{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "strict": true,
    "jsx": "preserve",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["ESNext", "DOM"],
    "skipLibCheck": true,
    "noEmit": true,
    "types": ["vitest/globals"],
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "paths": {
      "@/*": ["./src/*"],
      "@electron/*": ["./electron/*"]
    }
  },
  "include": [
    "src", 
    "src/**/*.vue"
  ],
  "references": [
    { "path": "./tsconfig.node.json" }
  ]
}

Uso:

// trpc.ts
import { initTRPC } from '@trpc/server';
import SuperJSON from 'superjson';

const t = initTRPC.create({
  transformer: SuperJSON
}); 

export { prisma } from '../prisma';
export const router = t.router;
export const publicProcedure = t.procedure;
// client.ts
import { ipcRenderer } from 'electron';
import { RequestInitEsque } from '@trpc/client/dist/internals/types';
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '@electron/api/routes/routes';
import SuperJSON from 'superjson';

type TRPCClient = ReturnType<typeof createTRPCProxyClient<AppRouter>>;

const fetch = async (url: RequestInfo | URL, init: RequestInit | RequestInitEsque | undefined) => {
  const response = await ipcRenderer.invoke('trpc', {
    url,
    method: init?.method,
    headers: init?.headers,
    body: init?.body
  });

  return new Response(response.body, {
    status: response.status,
    headers: response.headers,
  });
}

const useTRPC = () => {
  return createTRPCProxyClient<AppRouter>({
    transformer: SuperJSON,
    links: [
      httpBatchLink({
        url: '/trpc',
        fetch
      }),
    ],
  });
}

export { fetch, useTRPC }
export type { TRPCClient }
foloinfo commented 1 year ago

I faced same error using "next": "13.5.4". I use "node": "18.18.x" and "yarn": "3.6.4", not sure what's causing the problem. Falling back to 1.13.3 fixed the issue, thanks @viniciusteixeiradias . Maybe I'll wait for some more update before using 2.0.0

viniciusteixeiradias commented 1 year ago

I faced same error using "next": "13.5.4". I use "node": "18.18.x" and "yarn": "3.6.4", not sure what's causing the problem. Falling back to 1.13.3 fixed the issue, thanks @viniciusteixeiradias . Maybe I'll wait for some more update before using 2.0.0

I'm not sure, but I think the bug is related to this PR: https://github.com/blitz-js/superjson/pull/263

Skn0tt commented 1 year ago

Hi! This looks like an issue with how your TSC is configured.

It likely outputs CJS code that contains require instead of import directives. If you look at the ~/dist-electron/main/index.js file that's referenced in your error message, you can double-check if that's what's happening.

I don't directly spot anything in your tsconfig.json that leads to that, but this is where i'd start to debug this!

The whole CJS -> ESM story is a mess, and the fact that TypeScript uses a syntax that looks like ESM doesn't make things easier. Please make sure to post your solution in this issue, i'm pretty sure a lot of others will run into similar issue!

peterbud commented 1 year ago

I think it is related to the fact that there is no target specified int the tsconfig.json in the superjson library. The default is ES3 and it means the emitted JS is very old style.

For Node16 the recommendation is at least target es2021: https://github.com/tsconfig/bases/blob/main/bases/node16.json

Have you considered specifying target in the compilerOptions?

Skn0tt commented 1 year ago

@peterbud I don't think it's related to SuperJSON's target config - I've doublechecked that it outputs correct ES Modules code.

peterbud commented 1 year ago

Hi @Skn0tt , thanks for looking into this.

There is no syntactical problem with the code which tsc outputs with the current settings (aka without target)/ The problem is that this 'dialect' is very outdated.

Consider the following: https://github.com/blitz-js/superjson/blob/ba7346c7fd98170306780128d32e7fe38df7bb34/src/transformer.ts#L125-L132

With the current ES3 it is transpiled to:

    simpleTransformation(isSet, 'set', 
    // (sets only exist in es6+)
    // eslint-disable-next-line es5/no-es6-methods
    function (v) { return __spreadArray([], __read(v.values())); }, function (v) { return new Set(v); }),

Where __read is a generated function which uses this at top level

var __read = (this && this.__read) || function (o, n) {
    var m = typeof Symbol === "function" && o[Symbol.iterator];
    ...
}

leading to a warnings like The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten if you use the library

If you compile with target 'es2020' (with typescript 4.2 that's the best you can use), you get a much better transpiled javascript dialect for the same function:

    simpleTransformation(isSet, 'set', 
    // (sets only exist in es6+)
    // eslint-disable-next-line es5/no-es6-methods
    v => [...v.values()], v => new Set(v)),

If superjson is targeting node16 I think es2020 would be a reasonable minimal target as I have linked above.

peterbud commented 1 year ago

One more thing: You have mentioned that you have followed Axel Rauschmayer guide: https://2ality.com/2021/06/typescript-esm-nodejs.html

That also specifies target es2020 in tsconfig.json: https://2ality.com/2021/06/typescript-esm-nodejs.html#tsconfig.json

Skn0tt commented 1 year ago

Good points!

peterbud commented 1 year ago

Thanks @Skn0tt - this has fixed the situation: warnings are gone! Thanks!

titanism commented 11 months ago

Can you please release a CJS export?

Skn0tt commented 11 months ago

ESM is the standard format, has been supported by current Node.js versions for years. We need good reasons to add a CJS export again. If you feel your usecase has specific requirements and is important enough for SuperJSON to revisit its bundling, please outline that and open a separate issue.

titanism commented 11 months ago

You're already using TypeScript which isn't a standard, so your bundler could export CJS format pretty easily. Then just update the package.json and then you can support the majority of users who still use CJS (or dislike ESM).

queicherius commented 10 months ago

We need good reasons to add a CJS export again.

Exporting multiple variants would be the most constructive for everyone and can happen seamlessly. Here would be an example of how to do that with TypeScript.

Skn0tt commented 10 months ago

I appreciate the effort in making your point @queicherius! My thoughts on the issue are very much the same that Jimmy Warting mentions here: https://github.com/node-fetch/node-fetch/issues/1263#issuecomment-1025241402

In short, I want the transition period between CJS and ESM to be over, and making packages ESM-only is a good way of doing that. For specific issues that arise from this, i'm happy to help finding workarounds in separate issues.

perusopersonale commented 8 months ago

I have the same problem, I'm unable to use this library. I have a typescript project with Express and trpc and I wanted to use this lib (as suggested from trpc). But after wasting hours, I wasn't able to fix this, the whole argument of commonJS and ESM for me is always cryptic and it's not something that I would like to dig in. This is my current config:

{
  "extends": "@tsconfig/node16/tsconfig.json",
  "version": "4.4.2",
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "./lib",
    "strictPropertyInitialization": false,
    "target": "ESNext",
    "module": "nodenext",
    "moduleResolution": "nodenext"
  },
  "include": ["**/*.ts"],
  "exclude": ["lib", "src/**/*.spec.ts", "node_modules"],
  "ts-node": {
    "files": true
  }
}

I've tried everything that make sense to me (esModuleInterop true) changing target/module/moduleResolution, but with no results. The solution for me at this point is simple I won't use this library

aseemk commented 8 months ago

I +1 others' comments on the pain and difficulty here.

But if helpful to others, here's how I got a superjson import working in my CommonJS codebase for use with tRPC, using @joepie91's fix-esm module (in a TypeScript + ESLint codebase):

// HACK: The `superjson` library is ESM-only (does not support CJS), while our codebase is CJS.
// This is a workaround to still get to use the latest version of the library from our codebase.
// https://github.com/blitz-js/superjson/issues/268
// https://www.npmjs.com/package/fix-esm
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment
const fixESM = require("fix-esm");
// @ts-expect-error This is a type-only import, so won't get transformed to `require()`.
import type SuperJSON from "superjson";
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment
const SuperJSON: SuperJSON = fixESM.require("superjson");
JClackett commented 8 months ago

Would really appreciate a cjs export, its currently not possible for me to use a esm node server

oskarhertzman commented 8 months ago

Same, we are forced to use 1.13.3 for now.

AndreiCravtov commented 8 months ago

i was toying around with a super basic express/trpc/superjson setup, and for HOURS i could not figure out why none of it was working. I tried changing my config in 1000 different ways, and none of it worked, except for

@aseemk 's answer!!!! just, than you, so much !!!!!!!

ephraimduncan commented 7 months ago

In response to @Skn0tt statement

Hi! This looks like an issue with how your TSC is configured.

After reviewing my tsconfig.json file, I made a small change to the moduleResolution option as shown below:

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Default",
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "esModuleInterop": true,
    "incremental": false,
    "isolatedModules": true,
    "lib": ["es2022", "DOM", "DOM.Iterable"],
    "module": "NodeNext",
    "moduleDetection": "force",
-   "moduleResolution": "node",
+   "moduleResolution": "NodeNext",
    "noUncheckedIndexedAccess": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true,
    "target": "ES2022"
  }
}
incompletude commented 4 months ago

I think it is too soon to make this change. A lot of packages and codebases are still locked in cjs and it is not possible to use NodeNext due to backwards compatibility, which is exactly my case. I'm locked in v1 because of this. Please provide cjs. <3

index

https://github.com/wooorm/npm-esm-vs-cjs

nekochan0122 commented 4 months ago
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
export const SuperJSON = require('fix-esm').require('superjson') as typeof import('superjson')
pablojsx commented 4 months ago

Omg! I've trying to fix this for an entire year lol it was so hard to find this thread!!!

Many thanks @aseemk your solution worked

So the whole trpc.ts file looks like this:

import { initTRPC } from '@trpc/server';
import { Context } from '../context';
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment
const fixESM = require('fix-esm');
// @ts-expect-error This is a type-only import, so won't get transformed to `require()`.
import type SuperJSON from 'superjson';
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment
const superjson: SuperJSON = fixESM.require('superjson');

export const t = initTRPC.context<Context>().create({
  transformer: superjson,
});
export const router = t.router;
export const publicProcedure = t.procedure;
thereis commented 3 months ago

Still facing this issue on a shared lib inside of a monorepo using nx...

pablojsx commented 2 months ago

I haven't tried this but a guy on discord replied this to me:

image

No time to test this tsconfig