vercel / next.js

The React Framework
https://nextjs.org
MIT License
125.17k stars 26.74k forks source link

nextjs/TypeScript with Jest: "SyntaxError: Cannot use import statement outside a module" for some packages #58707

Open erikmuttersbach opened 10 months ago

erikmuttersbach commented 10 months ago

Verify canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.1.0: Mon Oct  9 21:28:12 PDT 2023; root:xnu-10002.41.9~6/RELEASE_ARM64_T8103
Binaries:
  Node: 21.1.0
  npm: 10.2.0
  Yarn: 1.22.19
  pnpm: N/A
Relevant Packages:
  next: 14.0.4-canary.5
  eslint-config-next: N/A
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.2.2
Next.js Config:
  output: N/A

### Which example does this report relate to?

https://github.com/vercel/next.js/tree/canary/examples/with-jest

### What browser are you using? (if relevant)

_No response_

### How are you deploying your application? (if relevant)

_No response_

### Describe the Bug

I am using next.js with TypeScript and try to use jest for running tests. When importing some packages, such as `chalk` or `ora` I get the following error when running jest.

```bash
npx jest
(node:27468) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
 PASS  __tests__/index.test.tsx
 PASS  app/component.test.tsx
 PASS  app/rsc/page.test.tsx
 PASS  app/blog/[slug]/page.test.tsx
 PASS  __tests__/snapshot.tsx
 FAIL  app/client/page.test.tsx
  ● Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:

    /Users/erikmuttersbach/Projects/home-assistant-openai-nextjs/with-jest-app/node_modules/chalk/source/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import ansiStyles from '#ansi-styles';
                                                                                      ^^^^^^

    SyntaxError: Cannot use import statement outside a module

       8 | it('App Router: Works with Client Components', () => {
       9 |   console.log(chalk.blue('Client Component Test'))
    > 10 |   render(<ClientComponent />)
         |                                                       ^
      11 |   expect(screen.getByRole('heading')).toHaveTextContent('Client Component')
      12 | })
      13 |

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1505:14)
      at Object.<anonymous> (app/client/page.test.tsx:10:55)

Test Suites: 1 failed, 5 passed, 6 total
Tests:       5 passed, 5 total
Snapshots:   1 passed, 1 total
Time:        0.842 s, estimated 1 s
Ran all test suites.

Expected Behavior

Using the same packages for running the application works. I was expecting jest to behave the same

To Reproduce

Start with a clean setup:

nvm use 21.1 
npx create-next-app --example with-jest with-jest-app
cd with-jest-app/
npm install next@canary
npx jest # still works!

Introduce breaking changes:

npm install chalk 

Edit app/client/page.test.tsx (or any other test file) and use chalk:

/**
 * @jest-environment jsdom
 */
import { render, screen } from '@testing-library/react'
import ClientComponent from './page'
import chalk from 'chalk'

it('App Router: Works with Client Components', () => {
  console.log(chalk.blue('Client Component Test'))
  render(<ClientComponent />)
  expect(screen.getByRole('heading')).toHaveTextContent('Client Component')
})

Now npx jest fails:

npx jest
(node:27468) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
 PASS  __tests__/index.test.tsx
 PASS  app/component.test.tsx
 PASS  app/rsc/page.test.tsx
 PASS  app/blog/[slug]/page.test.tsx
 PASS  __tests__/snapshot.tsx
 FAIL  app/client/page.test.tsx
  ● Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:

    /Users/erikmuttersbach/Projects/home-assistant-openai-nextjs/with-jest-app/node_modules/chalk/source/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import ansiStyles from '#ansi-styles';
                                                                                      ^^^^^^

    SyntaxError: Cannot use import statement outside a module

       8 | it('App Router: Works with Client Components', () => {
       9 |   console.log(chalk.blue('Client Component Test'))
    > 10 |   render(<ClientComponent />)
         |                                                       ^
      11 |   expect(screen.getByRole('heading')).toHaveTextContent('Client Component')
      12 | })
      13 |

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1505:14)
      at Object.<anonymous> (app/client/page.test.tsx:10:55)

Test Suites: 1 failed, 5 passed, 6 total
Tests:       5 passed, 5 total
Snapshots:   1 passed, 1 total
Time:        0.842 s, estimated 1 s
Ran all test suites.
anthones commented 10 months ago

You are probably using babel-jest for transforming TS and TSX files. Take a look at your jest config file and in the "transform" option try switching to ts-jest for these two file types.

  preset: 'ts-jest',
  transform: {
    '^.+\\.(ts|tsx)?$': 'ts-jest',
    '^.+\\.(js|jsx)$': 'babel-jest',
  },
thesonpb commented 10 months ago

Same here for me:

node_modules/antd/es/message/index.js:3
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (node:internal/vm:77:18)
    at wrapSafe (node:internal/modules/cjs/loader:1288:20)
    at Module._compile (node:internal/modules/cjs/loader:1340:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at Module.require (node:internal/modules/cjs/loader:1235:19)
    at mod.require (/home/ubuntu/jenkins/workspace/ONE-DX/CICD_SERVER_68/ONEDX_FE_CORE_CUSTOMERS_PIPELINE/node_modules/next/dist/server/require-hook.js:64:28)
    at require (node:internal/modules/helpers:176:18)
    at 9573 (/home/ubuntu/jenkins/workspace/ONE-DX/CICD_SERVER_68/ONEDX_FE_CORE_CUSTOMERS_PIPELINE/build-workplace/server/pages/_app.js:1:1302) {
  type: 'SyntaxError'
}

Although the code has no changes, the build is completely difference

Update:

When I use nextjs 14.0.2, the error disappear. The cause could be the version 14.0.3

victorandree commented 10 months ago

I'm seeing the same issue as @thesonpb with next@14.0.3 and antd@4.24.15. It seems like next@14.0.3 includes this change which changes how antd is modularized: https://github.com/vercel/next.js/pull/57968. I'm pretty sure it's what breaks this for us.

Note: My issue is not when running jest, but when doing npm run build. My build fails during the "Collecting page data" step (output below).

Not sure how to fix this, or if it should be reported as a separate issue. For now, I'm pinning 14.0.2.

 ✓ Creating an optimized production build
 ✓ Compiled successfully
   Collecting page data  ../app/node_modules/antd/es/spin/index.js:1
import _extends from "@babel/runtime/helpers/esm/extends";
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (node:internal/vm:73:18)
    at wrapSafe (node:internal/modules/cjs/loader:1153:20)
    at Module._compile (node:internal/modules/cjs/loader:1197:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1287:10)
    at Module.load (node:internal/modules/cjs/loader:1091:32)
    at Module._load (node:internal/modules/cjs/loader:938:12)
    at Module.require (node:internal/modules/cjs/loader:1115:19)
    at mod.require (/app/node_modules/next/dist/server/require-hook.js:64:28)
    at require (node:internal/modules/helpers:130:18)
    at 2600 (/app/.next/server/pages/admin.js:1:2698)

> Build error occurred
Error: Failed to collect page data for /admin
    at /app/node_modules/next/dist/build/utils.js:1217:15
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  type: 'Error'
}
erikmuttersbach commented 10 months ago

@victorandree, @thesonpb I cannot confirm that this is connected to the issue I am posting. Changing to the nextjs version you mentioned does not help for me.

What ultimately ended up working for me is to add the problematic packages into transformIgnorePatterns:

jest.config.ts

const tsconfig = require("./tsconfig.json");
const moduleNameMapper = require("tsconfig-paths-jest")(tsconfig)

module.exports = {
  preset: 'ts-jest',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  moduleNameMapper,
  testEnvironment: 'jest-environment-jsdom',
  "transform": {
    "^.+\\.(ts|tsx)$": "ts-jest",
    '^.+\\.(js|jsx)$': 'babel-jest'
 },
 "transformIgnorePatterns": [
  "node_modules/(?!(ora|chalk|cli-cursor))",
],
}
victorandree commented 10 months ago

@erikmuttersbach Seems likely. I've reported my issue as a new one: https://github.com/vercel/next.js/issues/58817.

huypham1411 commented 10 months ago

I also got this issue, I am pretty new to setting up Jest so I hope someone can help me here is my jest.config.js

const nextJest = require('next/jest');

const createJestConfig = nextJest({
  // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
  dir: './',
});

// Add any custom config to be passed to Jest
const customJestConfig = {
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  testEnvironment: 'jest-environment-jsdom',
  preset: 'ts-jest',
  transformIgnorePatterns: [
    'node_modules/(?!@azure/msal-react)',
    'node_modules/(?!antd)/',
  ],
  transform: {
    '^.+\\.jsx?$': 'babel-jest',
    '^.+\\.(js|jsx|ts|tsx)$': 'ts-jest',
  },
  moduleNameMapper: {
    '@azure/msal-react': '<rootDir>/mocked/module.js',
  },
};

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig);

this code I follow the jest example in Nextjs source code, I got this issue when running jest to test component render

\node_modules\antd\es\button\index.js:3
    import Button from './button';
    ^^^^^^

    SyntaxError: Cannot use import statement outside a module