Vite+Vitest+Zod项目extend读取未定义错误的解决咨询
我正在开发一个使用Vitest和Zod的Vite类库,项目目录结构如下:
. ├── src │ ├── leading │ │ ├── index.ts ( 2 ) │ │ └── leadingDataSourceConfigurationSchema.ts │ ├── index.ts ( 1 ) │ └── dataSourceConfigurationSchema.ts └── test └── my.spec.ts
基础Schema定义
src/dataSourceConfigurationSchema.ts:
import { z } from 'zod'; const dataSourceConfigurationSchema = z.object({ id: z.string().min(1) }).strict(); export { dataSourceConfigurationSchema };
根入口导出
src/index.ts:
export * from "./leading"; export { dataSourceConfigurationSchema } from "./dataSourceConfigurationSchema";
子Schema定义
src/leading/leadingDataSourceConfigurationSchema.ts:
import { z } from 'zod'; import { dataSourceConfigurationSchema } from '..'; const leadingDataSourceConfigurationSchema = dataSourceConfigurationSchema.extend({ entityTypeId: z.string().min(1), }).strict(); export { leadingDataSourceConfigurationSchema };
子目录入口导出
src/leading/index.ts:
export { leadingDataSourceConfigurationSchema } from "./leadingDataSourceConfigurationSchema";
测试文件
test/my.spec.ts:
import { describe, it, expect } from 'vitest'; import { ZodError } from 'zod'; import { leadingDataSourceConfigurationSchema } from '../src'; describe('test', () => { it('fails.', () => { expect(() => leadingDataSourceConfigurationSchema.parse({})).toThrow(ZodError); }); });
测试运行时报错:
TypeError: Cannot read properties of undefined (reading 'extend')
将子Schema中的导入语句从import { dataSourceConfigurationSchema } from '..';改为import { dataSourceConfigurationSchema } from '../dataSourceConfigurationSchema';后测试通过,推测是循环依赖问题。请问子目录必须直接导入文件吗?有没有其他解决方法?
你遇到的确实是循环依赖问题:根入口src/index.ts同时导出了leading模块和基础Schema,而leading模块里的子Schema又从根入口导入基础Schema,形成了循环引用链。模块加载时,基础Schema还未完成初始化就被子Schema尝试访问,因此出现undefined的错误。
以下是几种可行的解决方法:
1. 直接导入具体文件(最简方案)
如你已经验证的,跳过根入口中转,直接从基础Schema文件导入:
// src/leading/leadingDataSourceConfigurationSchema.ts import { dataSourceConfigurationSchema } from '../dataSourceConfigurationSchema';
这种方式直接切断了循环引用链,是最简单有效的解决方案。
2. 重构导出结构,拆分公共入口
如果希望保留统一入口的便利性,可以拆分出专门的公共依赖入口:
- 新建
src/common/index.ts,专门导出基础Schema:export { dataSourceConfigurationSchema } from "../dataSourceConfigurationSchema"; - 修改根入口
src/index.ts:export * from "./leading"; export * from "./common"; - 子Schema从公共入口导入:
// src/leading/leadingDataSourceConfigurationSchema.ts import { dataSourceConfigurationSchema } from '../common';
通过这种方式,所有子模块都依赖独立的公共入口,避免了和根入口的循环引用。
3. 延迟导入(特殊场景备选)
如果无法调整导入结构,可以使用动态导入延迟加载依赖,确保模块初始化完成后再访问:
// src/leading/leadingDataSourceConfigurationSchema.ts import { z } from 'zod'; // 先声明类型,后初始化 let dataSourceConfigurationSchema: typeof import('../dataSourceConfigurationSchema').dataSourceConfigurationSchema; // 初始化函数 async function initSchema() { dataSourceConfigurationSchema = (await import('../dataSourceConfigurationSchema')).dataSourceConfigurationSchema; } // 导出一个初始化后的Schema获取函数 export async function getLeadingDataSourceConfigurationSchema() { if (!dataSourceConfigurationSchema) { await initSchema(); } return dataSourceConfigurationSchema.extend({ entityTypeId: z.string().min(1), }).strict(); }
同时需要调整测试代码,适配异步获取:
// test/my.spec.ts import { describe, it, expect } from 'vitest'; import { ZodError } from 'zod'; import { getLeadingDataSourceConfigurationSchema } from '../src'; describe('test', () => { it('fails.', async () => { const schema = await getLeadingDataSourceConfigurationSchema(); expect(() => schema.parse({})).toThrow(ZodError); }); });
这种方法会增加代码复杂度,仅推荐在无法重构导入结构的特殊场景下使用。
内容的提问来源于stack exchange,提问作者baitendbidz

