Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

router client file is transpiled incorrectly #36794

Closed
1 task done
arackaf opened this issue May 10, 2022 · 17 comments · Fixed by #36877
Closed
1 task done

router client file is transpiled incorrectly #36794

arackaf opened this issue May 10, 2022 · 17 comments · Fixed by #36877
Assignees
Labels
bug Issue was opened via the bug report template. Testing Related to testing with Next.js.

Comments

@arackaf
Copy link

arackaf commented May 10, 2022

Verify canary release

  • I verified that the issue exists in Next.js canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: x64
  Version: Darwin Kernel Version 21.4.0: Mon Feb 21 20:34:37 PST 2022; root:xnu-8020.101.4~2/RELEASE_X86_64
Binaries:
  Node: 16.11.1
  npm: 8.0.0
  Yarn: 1.22.17
  pnpm: N/A
Relevant packages:
  next: 12.1.5
  react: 17.0.2
  react-dom: 17.0.2

What browser are you using? (if relevant)

n/a

How are you deploying your application? (if relevant)

n/a

Describe the Bug

The dist/client/router.js file had this code added in version 12.1.5

if (typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) {
  Object.assign(exports.default, exports);
  module.exports = exports.default;
}

This essentially erases all named exports from the CJS exports object, and instead hangs them all on the default export. This breaks Jest code that's mocking the useRouter hook.

import * as routerModule from 'next/router';

// ...

jest.spyOn(routerModule, 'useRouter').mockReturnValue(({
  pathname: '/foo/bar',
  push: pushMockFn,
} as unknown) as useRouter.NextRouter);

now breaks, since routerModule.useRouter is null, requiring instead

import routerModule from 'next/router';

This fixes the test, but is wrong. useRouter is not a property on the default export (or shouldn't be) but is rather a named export in that module.

Expected Behavior

CJS exports should match ESM.

To Reproduce

See above

@arackaf arackaf added the bug Issue was opened via the bug report template. label May 10, 2022
@Brooooooklyn Brooooooklyn self-assigned this May 11, 2022
@Brooooooklyn
Copy link
Contributor

This essentially erases all named exports from the CJS exports object, and instead hangs them all on the default export.

Actually it will keep everything on exports.

Since we are discussing CJS import exports here, and your code here is ESM style, could you please provide your jest config as well, it may issue in jest transformers.

@balazsorban44 balazsorban44 added type: needs investigation Testing Related to testing with Next.js. labels May 11, 2022
@arackaf
Copy link
Author

arackaf commented May 11, 2022

Actually it will keep everything on exports.

It doesn't. Look closely at the new code

if (typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) {
  Object.assign(exports.default, exports);
  module.exports = exports.default;
}

The first line in the if block smears all named exports onto the default export; the second line makes the default export be the only thing existing on exports

That's why

import * as routerModule from 'next/router';

routerModule.useRouter // is now null

but

import routerModule from 'next/router';

routerModule.useRouter // works but shouldn't

This does not match the esm structure of this module, causing code to break.

As for configuration, we have this in our Jest config

transform: {
  '^.+\\.(js|jsx|mjs|cjs|ts|tsx)$': require.resolve(
    '@mrkt/jest/transforms/babel',
  ),
  '^.+\\.css$': require.resolve('@mrkt/jest/transforms/css'),
  '^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)': require.resolve(
    '@mrkt/jest/transforms/file',
  ),
},

and we have babel-jest version 24.9.0 installed.

@Brooooooklyn
Copy link
Contributor

The reason why

import * as routerModule from 'next/router';

routerModule.useRouter // is now null

is because of the transformer, not the code itself. If you are using native require, it works fine:

const routerModule = require('next/router');
console.log(routerModule.createRouter) // [Function: createRouter]
const { createRouter } = require('next/router');
console.log(createRouter) // [Function: createRouter]

@arackaf
Copy link
Author

arackaf commented May 11, 2022

I understand it's the transformer. That's what this entire issue is about. The transformer is breaking Jest code that babel-jest is transpiling (and probably other transpilation mechanisms).

The problem is, I'm trying to access useRouter in a way that will allow me to mock it with Jest. A very common way is like this:

import * as router from 'next/router';

jest.spyOn(router, 'useRouter').mockReturnValue(({
  pathname: '/artist/[artistId]/audience',
  push: pushMockFn,
};

see comment here: #7479 (comment)

This transformer is fundamentally breaking the ability for that to work, and for no ostensible reason.

useRouter is a named export of the module. It should not be hung off of exports.default; it should be directly on the exports object.

@Bonitis
Copy link

Bonitis commented May 11, 2022

I am running into a similar problem. As of 12.1.6 all of the tests I have which mock useRouter have broken. I am also not able to compile in dev on that version. I have tested up to 12.1.6-canary.4, which will compile, but still has broken tests. I am trying to include the router fix for memory leaks, which I am experiencing in production. Do I need to change anything about my configuration to get 12.1.6 working after upgrading from 12.1.5? I tested the latest canary build and it has the same problems.

@arackaf
Copy link
Author

arackaf commented May 11, 2022

@Bonitis in a pinch, you can change your test code to

import routerModule from 'next/router';

and then mock routerModule.useRouter but that's really bad. You're basically lying to your tests, which will only happen to work because of this transform.

The only real solution here is for Next to just remove it.

@Bonitis
Copy link

Bonitis commented May 11, 2022

@arackaf unfortunately for now I have had to roll back to 12.0.7. The memory leak was what led me here, and it all seems related to the router. I couldn't get any of the latest versions to build properly, so I'm skipping the hack to get my tests to pass until the larger issues get resolved.

@arackaf
Copy link
Author

arackaf commented May 12, 2022

I've created a stand-alone repro: clone this repo, npm i then npm test and you'll see the lone test fail with this error message.

The test file has a commented out line that causes the test to pass, even though it should't.

Edit: link is here:

https://github.com/arackaf/next-repro

@leerob
Copy link
Member

leerob commented May 12, 2022

@arackaf looks like your link didn't get copy/pasta'd - this one I assume? https://github.com/arackaf/next-repro

@arackaf
Copy link
Author

arackaf commented May 12, 2022

@leerob yes indeed - thank you!

@leerob
Copy link
Member

leerob commented May 13, 2022

@Brooooooklyn
Copy link
Contributor

@arackaf thanks for your repro, I've found the root cause of this issue, it's because the mocked methods on the original module.exports.xxx aren't reflected on the re-exported module.exports.default.xxx. I'm trying to fix it on #36877

@leerob The error thrown in cypress-io/cypress#19382 is ReferenceError require is not defined, it might not be caused by the esm interpolation because it didn't introduce any require statement

@arackaf
Copy link
Author

arackaf commented May 13, 2022

@Brooooooklyn is there no way to just remove this code?

if (typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) {
  Object.assign(exports.default, exports);
  module.exports = exports.default;
}

that was the original cause of the mismatch between what’s exported from the module, and what’s on the actual module.exports object that jest is processing

@Brooooooklyn
Copy link
Contributor

is there no way to just remove this code?

No, these codes are intended to fix #34412 . It's indeed an ugly workaround that should be removed in the future, but I think it's a straightforward way to make type: "module" applications work with next.js at present.

@arackaf
Copy link
Author

arackaf commented May 13, 2022

@Brooooooklyn ohhhh I see. The disastrous Node / esm interop debacle continues. Thanks for the context.

@ijjk
Copy link
Member

ijjk commented May 17, 2022

Hi, this has been updated in v12.1.7-canary.7 of Next.js, please update and give it a try!

@github-actions
Copy link
Contributor

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 17, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Issue was opened via the bug report template. Testing Related to testing with Next.js.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants