Open nickfrosty opened 1 year ago
I ran into this also. In my case, I want to define tags like this my mdx blog posts:
tags:
- React
- Next.js
- Something Else
... and transform to an array of objects with the slug work already done, ready for consumption by component(s):
tags: [
{ displayName: 'React', slug: 'react' },
{ displayName: 'Next.js', slug: 'nextjs' },
{ displayName: 'Something Else', slug: 'something-else' },
]
I ended up working around the typing issues by omitting tags
from the original type and re-typing it myself, then using that custom type everywhere.
Note - I acknowledge my solution might be a little cumbersome for others who are importing from contentlayer/generated
directly, but in my case I was already creating a custom BlogPost
type anyway (I never import from contentlayer/generated
directly in components/pages so everything is not coupled to contentlayer as a concept). So doing some omit work was fine for me.
// inside contentlayer.config.ts
export const BlogPost = defineDocumentType(() => ({
name: "BlogPost",
filePathPattern: `blog/**/*.mdx`,
fields: {
tags: { type: 'list', required: true, of: { type: 'string' } },
},
computedFields: {
// This takes all the tags defined in the frontmatter (list of strings) and automatically
// derives slugs for them (saving us having to do it each time we consume them).
// This ultimately changes the type of the field from `string[]` (in the frontmatter)
// to `Tag[]` which will be consumed everywhere.
tags: {
type: 'list',
resolve: doc =>
// For some reason, the actual value of `tags` here is PlainArr, so we have to map over the `_array` property instead.
// I don't really care to figure out the typing for that :/
// ref: https://github.com/contentlayerdev/contentlayer/issues/150
/* eslint-disable*/
// @ts-ignore
doc.tags._array.map((tag: string) => ({
displayName: tag,
slug: kebabCase(tag),
})),
/* eslint-enable */
},
}));
Inside my custom src/content/blog/types.ts
file (which I then import from whenever I need the BlogPost
type):
import type { BlogPost as CLBlogPost } from 'contentlayer/generated';
export type Tag = {
displayName: string;
slug: string;
};
export type BlogPost = Omit<CLBlogPost, 'tags'> & {
// `tags` has a conflicting type due to defining it in both `fields` and `computedFields`
// in the schema, so we've omitted it here and forced it to the type we need.
// ref: https://github.com/contentlayerdev/contentlayer/issues/398
tags: Tag[];
};
Then I can create a base selector to use everywhere:
import { allBlogPosts } from 'contentlayer/generated';
import type { BlogPost } from '~/content/blog/types'; // the custom type I've defined above
export const getAllBlogPosts = () => allBlogPosts as unknown as BlogPost[];
Anytime I need to retrieve all the blog posts, I would use the custom getAllBlogPosts
selector, so tags
are properly typed as Tag[]
throughout.
Hope it's useful for someone! Though it would be nice if the type wasn't appended so we didn't have to do this.
Thanks a lot for opening this issue. I agree with the underlying issue but want to be careful with introducing (potentially) breaking changes too quickly. I'll account for this problem in upcoming API iterations.
For now as a (unfortunately cumbersome) workaround you can give your computed tags
field another name e.g. computedTags
which shouldn't have the problem above.
When defining a document with a
field
andcomputedFields
child item with the same name, thecomputedFields
field seems to be appended onto the generated type. Resulting in undesired types for the field.I would have expected the
computedFields
data to overwrite thefield
data, both thedescription
and thetype
.For reference, I am converting a comma separated string of
tags
into a parsed array via thecomputedFields
. Here is an example of my document definition:Generates these types (simplified for the example):
Extra note: It also seems like the
computedFields
children do not supportlist
types, or it may be a bug?