ค้นหาเว็บไซต์

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"]

ฉันทดสอบอีกครั้งและมันใช้ได้! ขอบคุณ!!

บทความที่เกี่ยวข้อง