通过NPM包在Next.js中动态加载JSON遇路径错误问题
问题场景
本地TypeScript项目中,通过PlatformConfigurationFactory.getInstance动态加载src/data下的JSON配置文件一切正常,但将项目发布为NPM包并在Next.js 14项目中安装调用时,出现文件路径错误:
Error loading configuration for facebook: [Error: ENOENT: no such file or directory, open '/Users/me/workspace/nextjs/.next/server/json/facebook-config.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/me/workspace/nextjs/.next/server/json/facebook-config.json'
}
⨯ Error: Configuration data not found
代码试图在Next.js的构建目录中查找配置文件,而非NPM包内部的dist/data路径。
问题原因
__dirname的指向在Next.js服务器端打包环境中发生了变化:Next.js会将依赖包的代码打包到自身的构建目录中,导致原本基于__dirname计算的相对路径不再指向NPM包内部的配置文件位置。
解决方案
方案一:静态导入配置文件(推荐)
将JSON配置文件作为模块静态导入,避免运行时动态读取文件,配置内容会直接嵌入到编译后的代码中,彻底规避路径问题。
修改JsonConfigurationRepository:
import { IConfigurationRepository } from "../../application/interfaces/IJsonConfigurationRepository"; import { Platform } from "../../core/domain/Platform"; // 静态导入所有平台配置文件 import facebookConfig from "../../data/facebook-config.json"; import twitterConfig from "../../data/twitter-config.json"; export class JsonConfigurationRepository implements IConfigurationRepository { async getConfig(platform: Platform): Promise<any> { // 建立平台与配置的映射 const configMap = { [Platform.Facebook]: facebookConfig, [Platform.Twitter]: twitterConfig, // 扩展其他平台配置 }; return configMap[platform] || null; } }
方案二:修正路径计算逻辑并确保配置文件被正确发布
- 确保配置文件被包含在NPM包中:在项目的
package.json中添加files字段,明确指定要发布的文件:
{ "files": [ "dist", "src/data/*.json" ] }
- 使用
require.resolve获取正确路径:替换基于__dirname的路径计算,改用require.resolve自动解析配置文件的绝对路径:
import { IConfigurationRepository } from "../../application/interfaces/IJsonConfigurationRepository"; import { Platform } from "../../core/domain/Platform"; import fs from "fs/promises"; export class JsonConfigurationRepository implements IConfigurationRepository { async getConfig(platform: Platform): Promise<any> { const fileName = `${platform}-config.json`; try { // 自动解析配置文件的绝对路径 const filePath = require.resolve(`../data/${fileName}`); const fileContent = await fs.readFile(filePath, "utf-8"); return JSON.parse(fileContent); } catch (error) { console.error(`Error loading configuration for ${platform}:`, error); return null; } } }
如果项目使用ES模块(tsconfig中module为ESNext),可以改用import.meta.url计算路径:
import { IConfigurationRepository } from "../../application/interfaces/IJsonConfigurationRepository"; import { Platform } from "../../core/domain/Platform"; import fs from "fs/promises"; import { fileURLToPath } from 'url'; import { dirname, resolve } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export class JsonConfigurationRepository implements IConfigurationRepository { async getConfig(platform: Platform): Promise<any> { const fileName = `${platform}-config.json`; const filePath = resolve(__dirname, "../../data", fileName); try { const fileContent = await fs.readFile(filePath, "utf-8"); return JSON.parse(fileContent); } catch (error) { console.error(`Error loading configuration for ${platform}:`, error); return null; } } }
方案三:构建时复制配置文件到dist目录
修改项目的构建脚本,在TypeScript编译完成后,将src/data目录复制到dist目录中:
{ "scripts": { "build": "tsc && cp -r src/data dist/" } }
确保编译后dist/data目录存在,此时基于__dirname的路径计算就能正确指向NPM包内部的配置文件。
内容的提问来源于stack exchange,提问作者Valentin Harrang

