Compiler Foundation
DynamicForm 3.0 adds a field module and compiler layer before the existing FormConfig pipeline.
Field Modules
-> compileFormConfig
-> FormConfig
-> processFormConfig
-> DynamicForm
This is additive. Existing FormConfig usage does not need to change.
Field Modules
A field module packages reusable business-field behavior:
import type { FieldModule } from '@whynotsnow/dynamic-form';
interface UserSelectorOptions {
label?: string;
options?: Array<{ label: string; value: string }>;
}
export const UserSelectorModule: FieldModule<UserSelectorOptions> = {
type: 'UserSelector',
defaultProps: {
allowClear: true,
showSearch: true
},
dependencies: ['departmentId'],
createConfig: (options) => ({
id: 'userId',
label: options?.label ?? 'User',
component: 'Select',
componentProps: {
options: options?.options ?? []
}
})
};
Modules describe domain fields. Render components are still resolved by the existing component registry.
FieldModule<TOptions> and ModuleConfig<TOptions> provide module-level option typing. Their default remains Record<string, unknown>, so existing callers do not need to migrate. The registry still resolves modules by runtime type; this version does not attempt a global type-to-options mapping across heterogeneous config arrays.
Registry
Use ModuleRegistryManager for isolated registries or defaultModuleRegistry for shared registration.
import { ModuleRegistryManager } from '@whynotsnow/dynamic-form';
const registry = new ModuleRegistryManager();
registry.register(UserSelectorModule);
registry.has('UserSelector');
registry.get('UserSelector');
registry.list();
registry.unregister('UserSelector');
Duplicate module types are rejected by default. Pass { override: true } when replacement is intentional.
Compiler
Compile module config into standard FormConfig:
import { Form } from 'antd';
import { DynamicForm, compileFormConfig } from '@whynotsnow/dynamic-form';
const compiled = compileFormConfig(
{
fields: [
{
type: 'UserSelector',
id: 'ownerId',
options: { label: 'Owner' },
overrides: {
required: true
}
}
]
},
{ registry }
);
export function Example() {
const [form] = Form.useForm();
return (
<DynamicForm
form={form}
formConfig={compiled.formConfig}
componentRegistry={{ customComponents: compiled.componentRegistry }}
/>
);
}
The higher-level CompiledDynamicForm is recommended when rendering compiler output because it automatically wires the returned component registry:
import { CompiledDynamicForm } from '@whynotsnow/dynamic-form';
<CompiledDynamicForm form={form} compiled={compiled} />;
An additional componentRegistry may still be provided; an explicitly supplied custom component wins on name conflicts. The lower-level manual DynamicForm integration remains available when config and registry need separate control.
The compiler returns:
{
formConfig: FormConfig;
componentRegistry: ComponentRegistry;
}
formConfig can be passed to processFormConfig() or DynamicForm.
Mixed Fields And Groups
Compiler input is unified as ModuleFormConfig. Fields remain flat declarations and join a group through groupId; fields without groupId remain at the top level. Group rules only support show and hide. Manual effect runs first, and rules run afterward with matching result keys taking precedence.
compileFormConfig({
fields: [
{ type: 'AccountType', id: 'accountType' },
{ type: 'CompanyName', id: 'companyName', groupId: 'companyInfo' }
],
groups: [
{
id: 'companyInfo',
title: 'Company Information',
initialVisible: false,
rules: [{ when: { field: 'accountType', equals: 'company' }, then: { action: 'show' } }]
}
]
});
The compiler validates globally unique IDs, valid group references, and non-empty groups.
Hooks
Compiler hooks allow compile-time extension:
compileFormConfig(moduleFormConfig, {
registry,
hooks: {
beforeCompile(context) {},
beforeModuleExpand(context) {},
afterModuleExpand(context) {},
afterCompile(context) {}
}
});
Hooks operate on compiler context only. They should not mutate Ant Design Form instances or React runtime state.
Boundaries
compileFormConfig()createsFormConfig.processFormConfig()keeps preparing runtime data.DynamicFormprops are unchanged.compileFormConfig()supports flat, grouped, and mixed output; groups do not change field value paths.- Ant Design Form remains the source of truth for values and validation state.