簡體   English   中英

找不到模塊:無法解析 Next.js 應用程序中的“fs”

[英]Module not found: Can't resolve 'fs' in Next.js application

無法確定我的 next.js 應用程序中發生了什么。 由於fs是 nodejs 的默認文件系統模塊。 它給出了模塊未找到的錯誤。

在此處輸入圖像描述

在此處輸入圖像描述

如果您使用fs ,請確保它僅在getInitialPropsgetServerSideProps內。 (包括服務器端渲染)。

您可能還需要創建一個包含以下內容的next.config.js文件來構建客戶端包:

對於webpack4

module.exports = {
  webpack: (config, { isServer }) => {
    // Fixes npm packages that depend on `fs` module
    if (!isServer) {
      config.node = {
        fs: 'empty'
      }
    }

    return config
  }
}

對於webpack5

module.exports = {
  webpack5: true,
  webpack: (config) => {
    config.resolve.fallback = { fs: false };

    return config;
  },
};

注意:對於其他模塊,例如path ,您可以添加多個參數,例如

{
  fs: false,
  path: false
}

我在這上面花了幾個小時,解決方案也在 Stackoverflow 上,但在不同的問題上 -> https://stackoverflow.com/a/67478653/17562602

在此我請求 MOD 許可轉發此問題,因為此問題是第一個出現在 Google 上的問題,可能越來越多的人會偶然發現與我相同的問題,所以我會盡量為他們節省一些汗水

Soo,您需要在 next.config.js 中添加它

    module.exports = {
  future: {
    webpack5: true, // by default, if you customize webpack config, they switch back to version 4. 
      // Looks like backward compatibility approach.
  },
  webpack(config) {
    config.resolve.fallback = {
      ...config.resolve.fallback, // if you miss it, all the other options in fallback, specified
        // by next.js will be dropped. Doesn't make much sense, but how it is
      fs: false, // the solution
    };

    return config;
  },
};

它對我來說就像一個魅力

最小的可重現示例

一個干凈的最小示例將有利於 Webpack 初學者,因為基於使用的自動拆分是如此令人興奮的魔法。

工作你好世界基線:

頁面/index.js

// Client + server code.

export default function IndexPage(props) {
  return <div>{props.msg}</div>
}

// Server-only code.

export function getStaticProps() {
  return { props: { msg: 'hello world' } }
}

包.json

{
  "name": "test",
  "version": "1.0.0",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "next": "12.0.7",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  }
}

運行:

npm install
npm run dev

現在讓我們添加一個虛擬的require('fs')來把事情搞砸:

// Client + server code.

export default function IndexPage(props) {
  return <div>{props.msg}</div>
}

// Server-only code.

const fs = require('fs')

export function getStaticProps() {
  return { props: { msg: 'hello world' } }
}

失敗:

Module not found: Can't resolve 'fs' 

這並不奇怪,因為 Next.js 無法知道fs只是服務器,我們不希望它只是忽略隨機的 require 錯誤,對吧? Next.js 只知道getStaticProps因為這是一個硬編碼的 Next.js 函數名稱。

好的,讓我們通過在getStaticProps中使用fs來通知 Next.js,以下再次起作用:

// Client + server code.

export default function IndexPage(props) {
  return <div>{props.msg}</div>
}

// Server-only code.

const fs = require('fs')

export function getStaticProps() {
  fs
  return { props: { msg: 'hello world' } }
}

頭腦等於炸毀。 所以我們知道,在getStaticProps的主體中提到fs ,即使是像上面這樣無用的,也會讓 Next.js/Webpack 明白它將是僅限服務器的。

getServerSidePropsgetStaticPaths的工作方式相同。

高階組件 (HOC) 必須在它們自己的文件中

現在,我們在不同但相似的頁面中IndexPagegetStaticProps的方式是使用 HOC,它們只是返回其他函數的函數。

HOC 通常會放在pages/之外,然后從多個位置需要,但是當您要考慮將事情進行概括時,您可能會想暫時將它們直接放在pages/文件中,例如:

// Client + server code.

import Link from 'next/link'

export function makeIndexPage(isIndex) {
  return (props) => {
    return <>
      <Link href={isIndex ? '/index' : '/notindex'}>
        <a>{isIndex ? 'index' : 'notindex'}</a>
      </Link>
      <div>{props.fs}</div>
      <div>{props.isBlue}</div>
    </>
  }
}

export default makeIndexPage(true)

// Server-only code.

const fs = require('fs')

export function makeGetStaticProps(isBlue) {
  return () => {
    return { props: {
      fs: Object.keys(fs).join(' '),
      isBlue,
    } }
  }
}

export const getStaticProps = makeGetStaticProps(true)

但如果你這樣做,你會很難過地看到:

Module not found: Can't resolve 'fs' 

所以我們明白了另一件事: fs的使用必須直接在getStaticProps函數體內部,Webpack 無法在子函數中捕獲它。

解決此問題的唯一方法是為僅后端的內容創建一個單獨的文件,如下所示:

頁面/index.js

// Client + server code.

import { makeIndexPage } from "../front"

export default makeIndexPage(true)

// Server-only code.

import { makeGetStaticProps } from "../back"

export const getStaticProps = makeGetStaticProps(true)

頁面/notindex.js

// Client + server code.

import { makeIndexPage } from "../front"

export default makeIndexPage(false)

// Server-only code.

import { makeGetStaticProps } from "../back"

export const getStaticProps = makeGetStaticProps(false)

前端.js

// Client + server code.

import Link from 'next/link'

export function makeIndexPage(isIndex) {
  return (props) => {
    console.error('page');
    return <>
      <Link href={isIndex ? '/notindex' : '/'}>
        <a>{isIndex ? 'notindex' : 'index'}</a>
      </Link>
      <div>{props.fs}</div>
      <div>{props.isBlue}</div>
    </>
  }
}

返回.js

// Server-only code.

const fs = require('fs')

export function makeGetStaticProps(isBlue) {
  return () => {
    return { props: {
      fs: Object.keys(fs).join(' '),
      isBlue,
    } }
  }
}

Webpack 必須看到名稱makeGetStaticProps被分配給getStaticProps ,因此它決定整個back文件是僅限服務器的。

請注意,如果您嘗試將back.jsfront.js合並到一個文件中,則它不起作用,可能是因為當您export default makeIndexPage(true)時,webpack 必然會嘗試將整個front.js文件拉入前端,這包括 fs,所以它失敗了。

這導致庫文件在以下之間自然(並且基本上幾乎是強制性的)拆分:

  • front.jsfront/* :前端 + 后端文件。 這些對前端來說是安全的。 后端可以做前端可以做的任何事情(我們正在做 SSR 對嗎?)所以這些也可以從后端使用。

    也許這就是許多官方示例中傳統的“組件”文件夾背后的想法。 但這是一個壞名字,因為該文件夾不僅應該包含組件,還應該包含將從前端使用的庫組件。

  • back.jsback/* (或者任何在front/*之外的東西):僅后端文件。 這些只能由后端使用,在前端導入它們會導致錯誤

fspath或其他節點原生模塊只能在服務器端代碼中使用,例如“getServerSide”函數。 如果您嘗試在客戶端使用它,即使您只是 console.log 也會出現錯誤。console.log 也應該在服務器端函數中運行。

當您導入“fs”並在服務器端使用它時,next.js 足夠聰明,可以看到您在服務器端使用它,因此它不會將該導入添加到客戶端包中

我使用的一個包給了我這個錯誤,我用

module.exports = {
 
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.resolve.fallback.fs = false
    }

    return config
  },
  
}

但這在終端上發出警告:

"Critical dependency: require function is used in a way in which

 dependencies cannot be statically extracted"

然后我嘗試在瀏覽器上加載節點模塊。 我從 node_modules 復制了節點模塊的“min.js”並放在“public/js/myPackage.js”中並用腳本加載它

export default function BaseLayout({children}) {
  return (
    <>
      <Script
        // this in public folder
        src="/js/myPackage.js"
        // this means this script will be loaded first
        strategy="beforeInteractive"
      />
    </>
  )
}

這個包附加到window對象和 node_modules 源代碼的 index.js 中:

if (typeof window !== "undefined") {
  window.TruffleContract = contract;
}

所以我可以作為window.TruffleContract訪問這個腳本。 但這不是一種有效的方法。

雖然此錯誤比您將遇到的大多數錯誤需要更多的推理,但它發生的原因很簡單。

為什么會這樣

Next.js,與許多框架不同,它允許您將僅限服務器(在瀏覽器中不起作用的 Node.js API)代碼導入頁面文件 當 Next.js 構建您的項目時,它通過檢查以下任一內置方法(代碼拆分)中存在哪些代碼,從您的客戶端包中刪除僅服務器代碼:

  • getServerSideProps
  • getStaticProps
  • getStaticPaths

旁注:有一個演示應用程序可以可視化其工作原理。

The Module not found: can't resolve 'xyz'錯誤發生在您嘗試使用這些方法之外的僅服務器代碼時

錯誤示例 1 - 基本

要重現此錯誤,讓我們從一個有效的簡單 Next.js 頁面文件開始。

工作文件

/** THIS FILE WORKS FINE! */

import type { GetServerSideProps } from "next";

import fs from "fs"; // our server-only import

type Props = {
  doesFileExist: boolean;
};

export const getServerSideProps: GetServerSideProps = async () => {
  const fileExists = fs.existsSync("/some-file"); 

  return {
    props: {
      doesFileExist: fileExists,
    },
  };
};

const ExamplePage = ({ doesFileExist }: Props) => {
  return <div>File exists?: {doesFileExist ? "Yes" : "No"}</div>;
};

export default ExamplePage;

現在,讓我們通過將fs.existsSync方法移到getServerSideProps之外來重現錯誤。 差別很小,但下面的代碼會拋出我們可怕的Module not found錯誤。

錯誤文件

import type { GetServerSideProps } from "next";
import fs from "fs";

type Props = {
  doesFileExist: boolean;
};

/** ERROR!! - Module not found: can't resolve 'fs' */
const fileExists = fs.existsSync("/some-file");

export const getServerSideProps: GetServerSideProps = async () => {
  return {
    props: {
      doesFileExist: fileExists,
    },
  };
};

const ExamplePage = ({ doesFileExist }: Props) => {
  return <div>File exists?: {doesFileExist ? "Yes" : "No"}</div>;
};

export default ExamplePage;

錯誤示例 2 - 現實

當您使用包含多種類型代碼(客戶端 + 服務器端)的模塊時,最常見(和令人困惑)的錯誤發生。

假設我有以下名為file-utils.ts的模塊:

import fs from 'fs'

// This code only works server-side
export function getFileExistence(filepath: string) {
  return fs.existsSync(filepath)
}

// This code works fine on both the server AND the client
export function formatResult(fileExistsResult: boolean) {
  return fileExistsResult ? 'Yes, file exists' : 'No, file does not exist'
}

在這個模塊中,我們有一個僅用於服務器的方法和一個理論上應該在客戶端工作的“共享”方法(但正如我們將看到的,理論並不完美)。

現在,讓我們嘗試將其合並到我們的 Next.js 頁面文件中。

/** ERROR!! */

import type { GetServerSideProps } from "next";

import { getFileExistence, formatResult } from './file-utils.ts'

type Props = {
  doesFileExist: boolean;
};

export const getServerSideProps: GetServerSideProps = async () => {
  return {
    props: {
      doesFileExist: getFileExistence('/some-file')
    },
  };
};

const ExamplePage = ({ doesFileExist }: Props) => {

  // ERROR!!!
  return <div>File exists?: {formatResult(doesFileExist)}</div>;
};

export default ExamplePage;

如您所見,我們在這里得到一個錯誤,因為當我們嘗試在客戶端使用formatResult時,我們的模塊仍然必須導入服務器端代碼

為了解決這個問題,我們需要將我們的模塊分成兩類:

  1. 僅限服務器
  2. 共享代碼(客戶端或服務器)
// file-utils.ts

import fs from 'fs'

// This code (and entire file) only works server-side
export function getFileExistence(filepath: string) {
  return fs.existsSync(filepath)
}
// file-format-utils.ts

// This code works fine on both the server AND the client
export function formatResult(fileExistsResult: boolean) {
  return fileExistsResult ? 'Yes, file exists' : 'No, file does not exist'
}

現在,我們可以創建一個工作頁面文件:

/** WORKING! */

import type { GetServerSideProps } from "next";

import { getFileExistence } from './file-utils.ts' // server only
import { formatResult } from './file-format-utils.ts' // shared

type Props = {
  doesFileExist: boolean;
};

export const getServerSideProps: GetServerSideProps = async () => {
  return {
    props: {
      doesFileExist: getFileExistence('/some-file')
    },
  };
};

const ExamplePage = ({ doesFileExist }: Props) => {
  return <div>File exists?: {formatResult(doesFileExist)}</div>;
};

export default ExamplePage;

解決方案

有兩種方法可以解決這個問題:

  1. “正確”的方式
  2. “讓它工作”的方式

“正確”的方式

解決此錯誤的最佳方法是確保您了解它發生的原因(如上),並確保您僅在getStaticPathsgetStaticPropsgetServerSideProps和 NOWHERE else 中使用服務器端代碼

請記住,如果您導入一個同時包含服務器端和客戶端代碼的模塊,則您不能使用該模塊客戶端的任何導入(重新訪問上面的示例 #2)。

“讓它工作”的方式

正如其他人所建議的那樣,您可以更改next.config.js以在構建時忽略某些模塊。 這意味着當 Next.js 嘗試在服務器代碼和共享代碼之間拆分頁面文件時,它不會嘗試填充無法構建客戶端的 Node.js API。

在這種情況下,您只需要:

/** next.config.js - with Webpack v5.x */
module.exports = {

  ... other settings ... 

  webpack: (config, { isServer }) => {
    
    // If client-side, don't polyfill `fs`
    if (!isServer) {
      config.resolve.fallback = {
        fs: false,
      };
    }

    return config;
  },

};

這種方法的缺點

如 Webpack 文檔的resolve.fallback部分所示,此配置選項的主要原因是因為從 Webpack v5.x開始,默認情況下不再對核心 Node.js 模塊進行 polyfill。 因此,此選項的主要目的是為您提供一種方式來定義要使用polyfill。

當您將false作為選項傳遞時,這意味着“不包含 polyfill”。

雖然這可行,但它可能很脆弱,需要持續維護以包含您引入項目的任何新模塊。 除非您要轉換現有項目/支持遺留代碼,否則最好將 go 用於上面的選項 #1,因為它根據 Next.js 實際上如何拆分代碼來促進更好的模塊組織。

您嘗試實現的模塊可能不應該在瀏覽器中運行。 即它只是服務器端。

如果嘗試在 Next.js 中使用 fs-extra,這對我有用

module.exports = {
  webpack: (config) => {
    config.resolve.fallback = { fs: false, path: false, stream: false, constants: false };
    return config;

  }
}

我的 NextJS 應用程序中出現此錯誤,因為我缺少export

export function getStaticProps()

對我來說清除緩存 npm cache clean -f

然后將節點版本更新到最新的穩定版本(14.17.0)工作

對我來說,問題是安裝了舊版本的 node.js。 它需要 node.js 版本 14 及更高版本。 解決方案是轉到 node.js 網頁,下載最新版本並安裝它。 然后重新運行項目。 一切正常!

當我嘗試使用 babel 時,我遇到了同樣的問題。

對我來說,這很有效:

#添加一個.babelrc文件到項目的根目錄並定義預設和插件(在我的例子中,我對babel的宏有一些問題,所以我定義了它們)

{
    "presets": ["next/babel"],
    "plugins": ["macros"]
}

之后關閉您的服務器並再次運行它

我有這個確切的問題。 我的問題是我正在導入我在types.d.ts文件中聲明的類型。

多虧了 VSCode 提供的自動填充功能,我才像這樣導入它。

import {CUSTOM_TYPE} from './types'

它應該是這樣的:

import {CUSTOM_TYPE} from './types.d'

就我而言,我認為.d是不必要的,所以我最終完全刪除了它並將我的文件重命名為types.ts

很奇怪,它被直接導入index.tsx沒有問題,但是src目錄中的任何幫助文件/函數都會給我錯誤。

我在 NextJS 應用程序中遇到了這個問題,因為我在 getServerSideProps() 正下方定義了一個新的輔助函數,但還沒有在 getServerSideProps() 中調用該函數。

我不確定為什么這會造成問題,但確實如此。 我只能通過調用該函數、刪除它或注釋掉它來讓它工作。

/** @type {import('next').NextConfig} */
module.exports = {
  reactStrictMode: false,
  webpack5: true,
  webpack: (config) => {
    config.resolve.fallback = {
      fs: false,
      net: false,
      dns: false,
      child_process: false,
      tls: false,
    };

    return config;
  },
};

此代碼解決了我的問題,我想分享。將此代碼添加到您的 next.config 文件中。我正在使用

webpack5

不要在pages目錄中使用fs ,因為 next.js 假設pages目錄中的文件在瀏覽器環境中運行。

您可以將使用fs的 util 文件放到其他目錄,例如/core

然后require在 node.js 環境中運行的getStaticProps中的 util。

// /pages/myPage/index.tsx
import View from './view';
export default View;

export async function getStaticProps() {
  const util = require('core/some-util-uses-fs').default; // getStaticProps runs in nodes
  const data = await util.getDataFromDisk();
  return {
    props: {
      data,
    },
  };
}

在我的例子中,這個錯誤是在重構 Next.js 頁面的授權流程時出現的。 原因是一些我尚未刪除的未使用的導入。

以前我將頁面設為受保護的路由,如下所示:

export async function getServerSideProps ({ query, req, res }) {
  const session = await unstable_getServerSession(req, res, authOptions)
  if (!session) {
    return {
      redirect: {
        destination: '/signin',
        permanent: false,
      },
    }
  }
//... rest of server-side logic
}

在重構的同時,我閱讀了 NextAuth useSession 根據我在那里閱讀的內容,我能夠更改實現,這樣我只需要添加MyComponent.auth = true即可使頁面受到保護。 然后我刪除了getServerSideProps中的上述代碼塊。 但是,我還沒有刪除上述代碼塊使用的兩個導入:

import { unstable_getServerSession } from 'next-auth/next'
import { authOptions } from 'pages/api/auth/[...nextauth]'

我相信這兩個進口中的第二個是造成問題的原因。 所以總結是,除了上面所有的好答案之外,它也可能是一個未使用的導入。

有時這個錯誤可能是因為你導入了一些東西但沒有在任何地方掌握它。 這對我有用。 我檢查了我的代碼並刪除了未使用的依賴項。

默認情況下,fs 模塊帶有 node。通常不需要顯式下載 fs 模塊。 這可能是一些錯誤或需要刷新。 首先通過編寫這個命令制作一個 package.json 文件

npm init -y // to make a package.json file

然后嘗試以這種方式再次安裝fs模塊

npm install fs --save  // to save the dependency 

我希望它會起作用...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM