prismicio / prismic-ts-codegen

A Prismic model-to-TypeScript-type generator.
Apache License 2.0
18 stars 6 forks source link

Add option to config to export all types generated #38

Open AugusDogus opened 1 year ago

AugusDogus commented 1 year ago

Is your feature request related to a problem? Please describe.

I am in the middle of migrating my company's application from a CRA React 16 app to a T3 Stack Next.js app. Unfortunately, with the amount of technical debt we have I will be unable to make use of the slice-machine for the foreseeable future. While I would love for the code to own the custom types, this just isn't something possible for us at the moment.

Currently, when mapping over the slices on a custom type the slices are typed as PropertyDocumentDataBodySlice.

{property.data.body.map((slice) => {
  return <Slice key={slice.id} slice={slice} />;
})}

In my Slice component, I need to define the props for full type safety.

Ex:

import { type PropertyDocumentDataBodySlice } from "~/types/prismic.types.generated";
import PropertyIntroductionWithFloorplans from "./property/property-introduction-with-floorplans";

export default function Slice({ slice }: { slice: PropertyDocumentDataBodySlice }) {
  if (slice.slice_type === "property_introduction_with_floorplans") {
    return <PropertyIntroductionWithFloorplans slice={slice} />;
  }
}

In the above example, the type PropertyDocumentDataBodySlice is not exported when running prismic-ts-codegen so I have to create my own union by importing the 22 or so types that PropertyDocumentDataBodySlice is a union of. This same example can be applied to practically every custom type and as such leads to some really ugly code.

Describe the solution you'd like

Add a config option to prismicCodegen.config.ts to allow for exporting all types.

Additional context

Our company has platinum level support if it helps to prioritize this issue. Happy to provide additional information privately.

angeloashmore commented 1 year ago

Hey @AugusDogus, thanks for providing all of those details!

This makes sense to me. I'm not sure we should add an option to export all types as it could very quickly pollute the autocomplete list. Instead we could provide a way to retrieve all Slice types from "legacy" Custom Types.

We would just need to export the Slices union (e.g. PropertyDocumentDataBodySlice) from the file.

Before making this change, could you share your opinion on whether you think we need to export all types? This would include every subtype, like the -SlicePrimary and -DocumentData types. I personally think those types can remain un-exported, but what do you think?

In the meantime, you can use this utility type in your code to quickly get all Slice types from a legacy Slice Zone:

import * as prismicT from "@prismicio/types";

type ExtractSlices<TSliceZone extends prismicT.Slice[]> =
  TSliceZone extends (infer U)[] ? U : never;

With the ExtractSlices utility type, you can extract all Slice types from a document's Slice Zone type:

type PageDocumentDataBodySlice = ExtractSlices<PageDocument["data"]["body"]>;

Just to be clear for anyone else coming to this issue later, accessing an individual Slice's type in Slice Machine projects is already possible since all Slice types are exported as top level types (e.g. PropertyIntroductionWithFloorplansSlice).

AugusDogus commented 1 year ago

Thanks for the quick response and even more so for sharing that utility type @angeloashmore!

I think you raise a very valid concern about not wanting to pollute the autocomplete list. On a previous iteration of this project when I was testing its feasibility and learning a bit more about Next.js I found myself often reaching & needing to manually export the DocumentData types as well.

I have not yet ran into this issue, but I'm not sure if that's because I now have a more solid understanding of Typescript and am utilizing the types at my disposable more accurately or if I just haven't hit the same wall yet.

This may be in the same vein, but I also was occasionally displeased to see that many of the types I was consuming were wrapped in the Simplify type as then the data property on the object basically became worthless (as data was typed as undefined or any; hard to remember) unless I manually cast it with as PropertyDocumentData (which I had to manually export).

Fortunately, I think that this issue in particular has been resolved as I now see data's type being PrismicDocument<Simplify<PropertyDocumentData>, "property", string>.data: Simplify<PropertyDocumentData>.

While the above example is another instance where having all the typed being exported could have saved me some headache, I do think the solution that was implemented was a much better outcome than polluting the autocomplete list.

That being said, I think that your proposed solution is likely the best outcome. I'll work on this more today with everything in mind and comment again when I feel I have a more informed opinion after utilizing that helper. I imagine that just for the sake of scope that if there does happen to be a similar annoyance that pops up, it could be created under a new issue instead of this one.

Additionally, since this appears to be a niche problem, I would also recommend that the config option added go under a legacy grouping or similarly named property.

angeloashmore commented 1 year ago

Hey @AugusDogus, you're welcome!

I found myself often reaching & needing to manually export the DocumentData types as well.

We originally decided to not export the -DocumentData types because they can be accessed via -Document["data"] (e.g. PageDocument["data"]). The goal here was to reduce the number of exports from the generated types file so it was easier to find what you need. It might not totally match up with how people use the types, but that at least was the thinking.

This may be in the same vein, but I also was occasionally displeased to see that many of the types I was consuming were wrapped in the Simplify type as then the data property on the object basically became worthless (as data was typed as undefined or any; hard to remember) unless I manually cast it with as PropertyDocumentData (which I had to manually export).

Fortunately, I think that this issue in particular has been resolved as I now see data's type being PrismicDocument<Simplify<PropertyDocumentData>, "property", string>.data: Simplify<PropertyDocumentData>.

Simplify is unfortunately needed due to technical limitations in how TypeScript works. It's possible we could get around at least part of the issue by using types rather than interfaces, but it would require some exploration.

In practice, Simplify shouldn't cause any issues, but please let me know if you're running into something.

Additionally, since this appears to be a niche problem, I would also recommend that the config option added go under a legacy grouping or similarly named property.

That's a great idea! In the long-run, we want everyone to be using modern Slices where they are not nested in Custom Types. However, we don't have a migration path for those projects currently, and we'll still need to support them. Nesting those options in legacy should make it clear modern projects shouldn't use it, and should be easier to deprecate in the future.


Let me know how your exploration goes! I think we can just export the Slice union types by default without any extra options, but I'll wait to hear back from you in case anything changes. 🙂

AugusDogus commented 1 year ago

I am so sorry for the late reply. I had less time than I anticipated the day I intended to reply and have been out of town since.

Honestly, with this helper

import * as prismicT from "@prismicio/types";

type ExtractSlices<TSliceZone extends prismicT.Slice[]> =
  TSliceZone extends (infer U)[] ? U : never;

I've been to do everything I've needed without polluting the autocomplete list. With that being said, I would hesitate to even advocate for spending the resources on this feature and I'm not even sure if it's necessary. Perhaps instead of needing this feature I needed a few TypeScript lessons 😅