reactjs - ข้อผิดพลาด 'ไม่สามารถใช้คำสั่ง import นอกโมดูล' ขณะทดสอบใน Vite
ฉันกำลังทำงานในโปรเจกต์ Vite เป้าหมายของฉันคือสามารถเขียนการทดสอบได้ ดังนั้นฉันจึงตั้งค่า Jest และ Babel และเขียนการทดสอบ แต่ฉันได้รับข้อผิดพลาดแบบนี้:
C:\Users\joaov\OneDrive\Dev Projetos\Projetos pessoais\csgo-e-commerce\node_modules\firebase\app\dist\index.esm.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { registerVersion } from '@firebase/app';
^^^^^^
SyntaxError: Cannot use import statement outside a module
> 1 | import { initializeApp } from 'firebase/app';
| ^
2 | import {
3 | getAuth,
4 | signInWithRedirect,
นี่คือการทดสอบที่ฉันกำลังพยายามเขียน:
// ProductCard.test.tsx
import { render, screen } from '@testing-library/react';
import { Theme } from '../../Theme';
import ProductCard from './ProductCard';
import { Provider } from 'react-redux';
import { store } from '../../store/store';
import '@testing-library/jest-dom';
const product = {
id: 1,
imageUrl: 'www.google.com',
name: 'Dragon Lore',
price: 90,
};
describe('ProductCard', () => {
it('should render the Product Card', () => {
render(
<Provider store={store}>
<Theme>
<ProductCard product={product} />
</Theme>
</Provider>
);
const nameElement = screen.getByText(/dragon lore/i);
const imageElement = screen.getByAltText(/dragon lore/i);
const button = screen.getByRole('button', { name: /add to cart/i });
expect(nameElement).toBeInTheDocument();
expect(imageElement).toBeInTheDocument();
expect(button).toBeInTheDocument();
});
});
ด้านล่างนี้คือการกำหนดค่า Jest ของฉัน:
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jest-environment-jsdom',
setupFilesAfterEnv: ['<rootDir>/.jest/setup-tests.js'],
moduleNameMapper: {
'\\.(gif|ttf|eot|svg|png)$': '<rootDir>/.jest/__mocks__/fileMock.js',
'\\.(css|less|sass|scss)$': 'identity-obj-proxy',
},
transform: {
'^.+\\.jsx?$': 'babel-jest',
'^.+\\.tsx?$': 'ts-jest',
},
};
การตั้งค่า Babel คือ:
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', { targets: { esmodules: true, node: 'current' } }],
'@babel/preset-typescript',
['@babel/preset-react', { runtime: 'automatic' }],
],
};
และ การกำหนดค่า TypeScript คือ:
{
"compilerOptions": {
"target": "es5",
"types": ["vite/client", "node"],
"lib": ["dom", "dom.iterable", "esnext"],
"downlevelIteration": true,
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "CommonJS",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
}
สุดท้าย นี่คือไฟล์ package.json ของฉัน:
{
"name": "csgo-e-commerce",
"private": true,
"version": "0.0.0",
"type": "commonjs",
"scripts": {
"dev": "vite",
"test": "jest",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@stripe/react-stripe-js": "^1.16.4",
"@stripe/stripe-js": "^1.46.0",
"@types/react-router-dom": "^5.3.3",
"dotenv": "^16.0.3",
"firebase": "^9.14.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.40.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.4.3",
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
"redux-saga": "^1.2.2",
"redux-thunk": "^2.4.2",
"reselect": "^4.1.7",
"stripe": "^11.10.0",
"styled-components": "^5.3.6",
"typed-redux-saga": "^1.5.0",
"validator": "^13.7.0",
"vite-plugin-svgr": "^2.4.0"
},
"devDependencies": {
"@babel/core": "^7.21.0",
"@babel/plugin-transform-modules-commonjs": "^7.21.2",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/jest": "^29.5.0",
"@types/node": "^18.14.2",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/redux-logger": "^3.0.9",
"@types/styled-components": "^5.1.26",
"@types/validator": "^13.7.13",
"@vitejs/plugin-react": "^2.2.0",
"babel-jest": "^29.4.3",
"babel-loader": "^8.3.0",
"babel-plugin-macros": "^3.1.0",
"eslint": "^8.28.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-react": "^7.31.11",
"eslint-plugin-react-hooks": "^4.6.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.4.3",
"prettier": "^2.7.1",
"redux-logger": "^3.0.6",
"ts-jest": "^29.0.5",
"typescript": "^4.9.5",
"vite": "^3.2.3"
}
}
คำตอบ #1
มีหลายสาเหตุที่ข้อผิดพลาดนี้อาจเกิดขึ้น
สาเหตุที่พบบ่อยที่สุดคือแพ็กเกจที่คุณใช้งานไม่ได้ประกาศคุณสมบัติ "type": "module" ในไฟล์ package.json ดังนั้นในกรณีนั้น ไฟล์ใด ๆ ที่มีนามสกุล .js จะถูกตีความเป็นไฟล์ CommonJS ซึ่งไม่อนุญาตให้ใช้ไวยากรณ์ ESM - คำสั่ง import และ export และไฟล์ที่มีนามสกุล .js กำลังใช้ไวยากรณ์ ESM
เหตุผลข้อที่สองคือ แพ็กเกจของบุคคลที่สามเป็นแพ็กเกจที่ใช้ ESM เท่านั้น และตัวคอมไพเลอร์/ตัวรันทดสอบ/ตัวบันเดิลไม่ได้คอมไพล์ไฟล์นั้นเนื่องจากถือว่าเป็นแพ็กเกจของบุคคลที่สาม โดยทั่วไป ตัวบันเดิลหรือคอมไพเลอร์จะไม่พยายามคอมไพล์ไฟล์ใดๆ ภายในโฟลเดอร์ node_modules
เหตุผลที่เป็นไปได้ประการที่สามคือแพ็กเกจของคุณถูกเผยแพร่แบบสองรูปแบบ รองรับทั้ง ESM และ CommonJS โดยใช้ฟิลด์ exports แบบมีเงื่อนไขของ package.json แต่ตัวบันเดลหรือตัวทดสอบของคุณอาจไม่ได้เลือกไฟล์ที่ถูกต้องที่เกี่ยวข้อง
ตอนนี้ให้เรากลับมาที่ปัญหาของคุณ คุณกำลังใช้เวอร์ชันล่าสุด (v29) ของ Jest ซึ่งหมายความว่าฟิลด์ exports ได้รับการสนับสนุนและมันยังสามารถจัดการโมดูล ESM ได้ดีด้วย
นั่นทำให้เรามีปัญหาแรกที่ไฟล์ package.json อาจไม่ได้ระบุว่าเป็นโมดูล ESM หรือไม่ แพ็กเกจที่มีปัญหาคือแพ็กเกจ firebase ถ้าเราดูภายในแพ็กเกจ:
ไฟล์ <ROOT>/node_modules/firebase/app/package.json ประกาศฟิลด์ exports อย่างถูกต้องพร้อมกับการส่งออกแบบมีเงื่อนไข
ในโค้ดของคุณ ที่ไหนสักแห่งคุณกำลังนำเข้า firebase/app และในการกำหนดค่า Jest ของคุณ สภาพแวดล้อมที่คุณใช้คือ jest-environment-jsdom ซึ่งหมายความว่ามันตรงกับเงื่อนไข browser และมันเลือกสิ่งต่อไปนี้:
"browser": {
"require": "./app/dist/index.cjs.js",
"import": "./app/dist/esm/index.esm.js"
},
จากสิ่งนี้ มันกำลังเลือก ./app/dist/esm/index.esm.js ตามที่เห็นได้จากข้อความแสดงข้อผิดพลาด และ เหตุผลที่ Jest ใช้ import แทน require เป็นเพราะหลักการที่มันกำลังใช้ (โค้ดของคุณเองอยู่ใน ESM และ Babel กำลังทำงานในเป้าหมาย ESM) ซึ่งจนถึงตอนนี้ถือว่าดี
แต่ทันทีที่มันเริ่มอ่านไฟล์ ./app/dist/esm/index.esm.js ปัญหาก็เริ่มเกิดขึ้น เนื่องจากนี่คือไฟล์ .js Jest ไม่สามารถรู้ได้จริง ๆ ว่านี่คือไฟล์ ESM หรือ CommonJS สิ่งที่ชัดเจนที่สุดที่ Jest จะทำคือมองหาไฟล์ package.json ที่ใกล้ที่สุด และก็มีอยู่ที่นี่ <ROOT>/node_modules/firebase/app/package.json ซึ่งไม่เหมือนกับ <ROOT>node_modules/firebase/package.json ไฟล์ package.json ที่ใกล้ที่สุดนี้มีเนื้อหาดังต่อไปนี้:
{
"name": "firebase/app",
"main": "dist/index.cjs.js",
"browser": "dist/esm/index.esm.js",
"module": "dist/esm/index.esm.js",
"typings": "dist/app/index.d.ts"
}
อย่างที่คุณเห็น ไฟล์นี้ไม่ได้ระบุฟิลด์ "type": "module" ดังนั้น Jest จึงสมมติว่า <ROOT>/firebase/app/dist/esm/index.esm.js เป็นไฟล์ CommonJS แบบเก่า แทนที่จะเป็นไฟล์ ESM ใหม่
มันไม่ใช่ความผิดของคุณต่อคำพูดโดยตรง แต่เป็นความผิดของผู้แต่งแพ็กเกจที่พวกเขาส่งไฟล์ package.json เพิ่มเติมนี้ไปด้วย อุดมการณ์แล้ว แพ็กเกจหนึ่งควรมีไฟล์ package.json เพียงไฟล์เดียว แต่ในช่วงวันแรก ๆ เมื่อทุกอย่างยังอยู่ในช่วงเริ่มต้น นี่เป็นวิธีปฏิบัติที่พบได้บ่อยในการฝังซับโมดูลและใช้ประโยชน์จากอัลกอริทึมการแก้ไขของ Node แต่หลังจากมีฟิลด์ exports สิ่งนี้ไม่จำเป็นอีกต่อไป
นั่นคือสาเหตุที่คุณได้รับข้อผิดพลาดนี้ ตอนนี้เพื่อแก้ไขข้อผิดพลาดนี้ คุณต้องบอก Jest ให้แปลงไฟล์นี้หากจำเป็นโดยใช้การตั้งค่า transformIgnorePatterns ผมค่อนข้างไม่แน่ใจเกี่ยวกับนิพจน์ปกติ แต่จะประมาณนี้:
{
"transformIgnorePatterns": [
"node_modules/(?!firebase)"
],
}
นี่เป็นคำตอบที่ยาวแต่ฉันหวังว่าจะช่วยให้คุณเข้าใจสาเหตุที่แท้จริงของปัญหา
คำตอบ #2
ดังนั้นปัญหาคือ Firebase ฉันแก้ปัญหานี้ด้วยการตั้งค่าขั้นสุดท้ายนี้:
การกำหนดค่า Jest:
//jest.config.json
{
"preset": "ts-jest",
"testEnvironment": "jest-environment-jsdom",
"setupFilesAfterEnv": ["<rootDir>/.jest/setup-tests.js"],
"moduleNameMapper": {
"\\.(gif|ttf|eot|svg|png)$": "<rootDir>/.jest/__mocks__/fileMock.js",
"\\.(css|less|sass|scss)$": "identity-obj-proxy"
},
"transform": {
"^.+\\.jsx?$": "babel-jest",
"^.+\\.tsx?$": "ts-jest"
},
"transformIgnorePatterns": ["/node_modules/(?!@firebase)/"]
}
การกำหนดค่า Babel:
//babel.config.json
{
"presets": [
[
"@babel/preset-env",
{ "targets": { "esmodules": true, "node": "current" } }
],
"@babel/preset-typescript",
["@babel/preset-react", { "runtime": "automatic" }]
],
"plugins": ["@babel/plugin-transform-runtime"]
}
แพ็กเกจ :
//package.json
{
"name": "csgo-e-commerce",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"test": "jest",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@stripe/react-stripe-js": "^1.16.4",
"@stripe/stripe-js": "^1.46.0",
"dotenv": "^16.0.3",
"firebase": "^9.14.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.40.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.4.3",
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
"redux-saga": "^1.2.2",
"redux-thunk": "^2.4.2",
"reselect": "^4.1.7",
"stripe": "^11.10.0",
"styled-components": "^5.3.6",
"typed-redux-saga": "^1.5.0",
"validator": "^13.7.0",
"vite-plugin-svgr": "^2.4.0"
},
"devDependencies": {
"@babel/core": "^7.21.0",
"@babel/plugin-transform-modules-commonjs": "^7.21.2",
"@babel/plugin-transform-runtime": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/jest": "^29.5.0",
"@types/node": "^18.14.2",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react-router-dom": "^5.3.3",
"@types/redux-logger": "^3.0.9",
"@types/styled-components": "^5.1.26",
"@types/validator": "^13.7.13",
"@vitejs/plugin-react": "^2.2.0",
"babel-jest": "^29.4.3",
"babel-loader": "^8.3.0",
"babel-plugin-macros": "^3.1.0",
"eslint": "^8.28.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-react": "^7.31.11",
"eslint-plugin-react-hooks": "^4.6.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.4.3",
"prettier": "^2.7.1",
"redux-logger": "^3.0.6",
"ts-jest": "^29.0.5",
"typescript": "^4.9.5",
"vite": "^3.2.3"
}
}
ฉันปฏิบัติตาม 'transformIgnorePatterns' ที่แนะนำ ติดตั้ง 'babel/plugin-transform-runtime' และเพิ่มบรรทัดนี้ในการตั้งค่า babel:
"plugins": ["@babel/plugin-transform-runtime"]
ฉันทดสอบอีกครั้งและมันใช้ได้! ขอบคุณ!!
