Static typing for Custom Fields #534
Replies: 3 comments 1 reply
-
Wonderful, This will be so helpful!!! |
Beta Was this translation helpful? Give feedback.
-
Hi @michaelbromley, would be a nice improvement! Do you thinks this would suffice or would it cause problems on the runtime generation? It would probably require to change the VendureConfig from interface to class, but maybe that's worth the change if no declaration at all is needed at the plugin develop's end. |
Beta Was this translation helpful? Give feedback.
-
Sorry for the late reaction. I could perhaps build a Vendure fork so you could reproduce the suggestion more easily, but please find a concrete example below which at my end seems to result in strict typings. The main catch is that it requires to pass // vendure-config.ts
import { CustomFieldBoolean, CustomFieldString, VendureConfig } from './custom-field-types';
// It is required to extract the custom fields to its own const for reference later on.
export const customFields = {
Product: {
infoUrl: CustomFieldString,
downloadable: CustomFieldBoolean,
},
};
export const config: VendureConfig = {
customFields: customFields
// Other config parts should be added here
}; // example.ts
import { Product } from './custom-field-types';
import { customFields } from './vendure-config';
// Passing the custom fields here fixes typing
const product: Product<typeof customFields> = new Product();
product.customFields = { downloadable: true, infoUrl: '' } // TS OK
product.customFields = { downloadable: 'true', infoUrl: '' } // TS ERROR // custom-field-types.ts
export class CustomFieldString { constructor(v: string) {} }
export class CustomFieldBoolean { constructor(v: boolean) {} }
export type CustomFieldConfig = Record<string, typeof CustomFieldString | typeof CustomFieldBoolean>;
export interface CustomFields {
Product: CustomFieldConfig;
// Other custom fields should be added here
}
export type VendureConfig = { customFields: CustomFields };
export type EntityCustomFields<E extends keyof CustomFields, T extends CustomFields[E]> = { [K in keyof T]: ConstructorParameters<T[K]>[0]};
export class Product<T extends CustomFields> {
public customFields?: EntityCustomFields<'Product', T['Product']>;
} |
Beta Was this translation helpful? Give feedback.
-
Problem
Defining custom fields is done in the Vendure config object:
The problem is that these fields are generated dynamically at run-time, so the static TS
Product.customFields
property has no knowledge of them (the actual type is{}
which is pretty useless). This leads to awkward code in your plugins like:Solution: declaration merging
TypeScript has a feature known as "declaration merging" which allows interface declarations to "stack", adding properties to the overall interface:
We can use this feature, and combine it with ambient module declarations to merge custom field properties into the built-in Vendure custom field types:
The nice thing is that this declaration need only be made in one place, and then all usages of the
product.customFields
field will be correctly typed implicitly.Improving dev experience
In a future release I will export the
CustomProductFields
etc types so that the deep import@vendure/core/dist/entity/custom-entity-fields
will be just@vendure/core
.A possible good pattern would be to bundle a
custom-field-typings.ts
file in the plugin template which sets up the declaration boilerplate for you. Then importing this file into the main plugin.ts file would give access to the merged declarations in all the TS files of the plugin:then the
plugin.ts
file imports it:We could also do something similar in the project root of a new
@vendure/create
install.Feedback?
I'd like to hear feedback on this proposal - you can try it out in your own projects and let me know how it goes, and any suggestions for better ways to do it.
Beta Was this translation helpful? Give feedback.
All reactions