Next.js和Jest: SyntaxError:无法在模块外部使用导入语句

16

我正在使用TypeScript编写Next.js项目,并使用Jest和React Testing Lib进行测试。然而,当我导入 rehype-raw 组件时,遇到了SyntaxError: Cannot use import statement outside a module

据我所知,Jest不支持ES6,因此node_modules可能需要转换。可以使用 transformIgnorePatterns 进行配置。例如,如果使用 "transformIgnorePatterns": ["node_modules/(?!rehype-raw)/"],则会允许转换 rehype-raw,但不包括其他模块。由此解决此错误。

然而,这对我来说没有用。但我不知道为什么,也不知道如何解决。我附上了我的 error outputjest.config.jsbabel.rc 文件。

错误输出

 FAIL  test/pages/companies/[id].test.tsxTest suite failed to run

    Jest encountered an unexpected token

    [...]

    Details:

    /path/frontend-job/node_modules/rehype-raw/index.js:7
    import {raw} from 'hast-util-raw'
    ^^^^^^

    SyntaxError: Cannot use import statement outside a module

      3 | import Image from 'next/image';
      4 | import { Remark } from 'react-remark';
    > 5 | import rehypeRaw from 'rehype-raw';
        | ^
      6 | import rehypeSanitize from 'rehype-sanitize';
      7 | import { logError } from '@utils/logger';
      8 |

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1728:14)
      at Object.<anonymous> (components/markdown/JobMarkdown.tsx:5:1)

jest.config.js

const { resolve } = require('path');

module.exports = {
  roots: ['<rootDir>'],
  moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'node'],
  setupFiles: ['<rootDir>/test/setup.js'],
  testPathIgnorePatterns: ['<rootDir>[/\\\\](node_modules|.next)[/\\\\]'],
  transform: {
    '^.+\\.(ts|tsx)$': 'babel-jest',
  },
  transformIgnorePatterns: [
    'node_modules/(?!rehype-raw)/',
  ],
  watchPlugins: [
    'jest-watch-typeahead/filename',
    'jest-watch-typeahead/testname',
  ],
  moduleNameMapper: {
    // Force mocks: https://github.com/facebook/jest/issues/4262
    '@api/axios': '<rootDir>/test/__mocks__/axios.js',
    // Normal module aliases
    '\\.(css|less|sass|scss)$': 'identity-obj-proxy',
    '\\.(gif|ttf|eot|svg|png)$': '<rootDir>/test/__mocks__/fileMock.js',
    '^@test/(.*)$': resolve(__dirname, './test/$1'),
    '^@test/faker/(.*)$': resolve(__dirname, './test/faker/$1'),
    '^@components/(.*)$': resolve(__dirname, './components/$1'),
    '^@pages/(.*)$': resolve(__dirname, './pages/$1'),
    '^@utils/(.*)$': resolve(__dirname, './utils/$1'),
    '^@api/(.*)$': resolve(__dirname, './api/$1'),
    '^@store/(.*)$': resolve(__dirname, './store/$1'),
  },
  testEnvironment: 'jsdom',
};

babel.rc

{
  "presets": ["next/babel"]
}
5个回答

7

Jest不支持ECMAScript模块,这是hast-util-raw使用的内容。第二个问题是,transformIgnorePatterns也无法正常工作,因此以下是我在Next.JS设置中的解决方法。

1. 删除babel.rc

删除您的babel.rc。您只需使用以下更改即可。

2. 添加moduleNameMapper

这将修复另一个错误,其中无法找到parse5。由hast-util-raw所需。

/** @type {import('jest').Config} */
const customJestConfig = {
  //...
  moduleNameMapper: {
    'parse5/lib/parser/index.js':
      '<rootDir>/node_modules/hast-util-raw/node_modules/parse5/lib/parser/index.js',
  }
}

3. 添加 transformIgnorePatterns

transformIgnorePatterns 添加到配置文件的最后。正如我所说,直接添加到配置文件中并没有起作用,不知道为什么。我还必须添加每个由 hast-util-raw 使用的包。但我相信一定有更好的方法来解决这个问题 :)

module.exports = async () => ({
  ...(await createJestConfig(customJestConfig)()),
  transformIgnorePatterns: [
    'node_modules/(?!(rehype-raw|hast-util-raw|unist-util-position|unist-util-visit|unist-util-visit-parents|unist-util-is|hast-util-from-parse5|hastscript|property-information|hast-util-parse-selector|space-separated-tokens|comma-separated-tokens|vfile-location|web-namespaces|hast-util-to-parse5|zwitch|html-void-elements)/)'
  ]
})


如果有人感兴趣,这是我的完整配置...

/* eslint-disable @typescript-eslint/no-var-requires */
// 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
/** @type {import('jest').Config} */
const customJestConfig = {
  // Add more setup options before each test is run
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
  // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
  moduleDirectories: ['<rootDir>/node_modules', '<rootDir>/src', '<rootDir>/src/pages'],
  testEnvironment: 'jest-environment-jsdom',
  testPathIgnorePatterns: [
    '<rootDir>/.next/',
    '<rootDir>/node_modules/',
    '<rootDir>/coverage',
    '<rootDir>/dist'
  ],
  moduleNameMapper: {
    'react-markdown': '<rootDir>/node_modules/react-markdown/react-markdown.min.js',
    'parse5/lib/parser/index.js':
      '<rootDir>/node_modules/hast-util-raw/node_modules/parse5/lib/parser/index.js'
  },
  resolver: '<rootDir>/.jest/resolver.js',
  clearMocks: true,
  testTimeout: 20000
}

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
// module.exports = createJestConfig(customJestConfig)
module.exports = async () => ({
  ...(await createJestConfig(customJestConfig)()),
  transformIgnorePatterns: [
    'node_modules/(?!(rehype-raw|hast-util-raw|unist-util-position|unist-util-visit|unist-util-visit-parents|unist-util-is|hast-util-from-parse5|hastscript|property-information|hast-util-parse-selector|space-separated-tokens|comma-separated-tokens|vfile-location|web-namespaces|hast-util-to-parse5|zwitch|html-void-elements)/)'
  ]
})


3
你需要了解的是,Next.js 会告诉 Jest 不要转换 /node_modules 下未在其配置文件(transpilePackages 属性)中定义的包。
对于正确添加转换排除项有点混乱,但 https://github.com/vercel/next.js/discussions/31152#discussioncomment-1697047 让我找到了正确的方向。

如果您不想手动管理数组,因为它取决于 Next.js 端的 transpilePackages,我重用了他们在 https://github.com/vercel/next.js/blob/435eca3fc5b5dd23cad6cff43632bdcc777727d9/packages/next/src/build/jest/jest.ts#L156-L173 中的逻辑,以转换这两个部分,既包括来自 transpilePackages 的部分,也包括 Jest 正常运行所需的部分。

这是我的解决方法的差异: https://github.com/inclusion-numerique/mediature/commit/cfe3ad85ab13d3f38d863afb8ee24b6995ae4e00

很抱歉没有在此帖子中直接放置代码,这会使事情变得不清楚。


太好了 - 谢谢! - undefined

0
对我有效的答案是@rottitime。我注意到将transformIgnorePatterns移动到底部的config对象之外的异步函数中是不起作用的。例如,这样是不起作用的:
import nextJest from 'next/jest.js'
 
const createJestConfig = nextJest({
  dir: './',
})
 
// Add any custom config to be passed to Jest
/** @type {import('jest').Config} */
const customJestConfig = {
  // Add more setup options before each test is run
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  testEnvironment: 'jest-environment-jsdom',
  preset: 'ts-jest',
  testPathIgnorePatterns: [
    '<rootDir>/.next/',
    '<rootDir>/node_modules/',
    '<rootDir>/coverage',
    '<rootDir>/dist'
  ],
  transformIgnorePatterns: [
    'node_modules/(?!(mui-tel-input)/)'
  ]
}

const exportConfig = async () => ({
  ...(await createJestConfig(customJestConfig)())
});


export default exportConfig;

-2
你是否已经在package.json中使用了type:"module"?

这对我不起作用。 - Greeneco
对我也没用。我认为需要将更改应用于jest配置。 - rottitime

-2

7
我按照文档中的说明尝试了next/js,并扩展了自定义jest配置,但仍然出现“SyntaxError: Cannot use import statement outside a module”的错误。 - Greeneco
3
我认为使用 https://github.com/vercel/next.js/discussions/31152#discussioncomment-1697047 可以解决这个语法错误,但是一些模拟数据不再起作用了。 - Greeneco
1
这个线程包含了一些讨论和代码,当我遇到 OP 的问题时,我发现它们非常有价值:https://github.com/remarkjs/react-markdown/issues/635#issuecomment-956158474。长话短说,我只是模拟那些不友好的 ES6 模块,在适当的情况下。 - Jason R Stevens CFA
现在的next/jest中有一个bug,但已经被修复了,目前一切都可以正常工作。 - Greeneco
2
能否请您在此发布解决方案?文档没有提供修复方法。 - rottitime

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接