You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vite+Vitest+Zod项目extend读取未定义错误的解决咨询

问题:Vite类库中Zod Schema循环依赖导致测试报错

我正在开发一个使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 02:57:43