-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: create routing files via plop #244
base: main
Are you sure you want to change the base?
Conversation
Deploying head-start with
|
Latest commit: |
d74d7b0
|
Status: | ✅ Deploy successful! |
Preview URL: | https://77e094d2.head-start.pages.dev |
Branch Preview URL: | https://feat-routing-plopfile.head-start.pages.dev |
when: (data) => !!data.name, | ||
type: 'confirm', | ||
name: 'separateRouting', | ||
message: (data) => `Create separate file in routing for \`${plop.getHelper('pascalCase')(data.name)}\`?`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment file routing is in @lib/routing/index.ts
, choosingy
will create a file akin to @lib/routing/page.ts
.
@@ -14,9 +15,17 @@ export default function (plop) { | |||
.map((param) => param.slice(1, -1)) // remove brackets | |||
.filter((param) => param !== 'locale'); // remove locale param | |||
}); | |||
plop.setHelper('trailingParam', (value) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using this to find out if a developer creates a new file with a route that ends in a param, ie [slug]
. Obviously that param might be any word between square brackets, but I left mapping that correctly as an exercise for the developer. I felt that asking how to handle a route that doesn't end in [slug]
or remapping slug to the provided name would overcomplicate this functionality.
title | ||
{{#each (routeParams route)}} | ||
{{#if (eq (trailingParam ../route) this)}} | ||
slug |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Theoretically I could've reassigned slug to the trailingParam, but I felt that to be overcomplicating it, and I must say I think it makes more sense to promote the use of [slug]
as final param
type LocalizedSlugs = { | ||
_allSlugLocales?: | ||
| { | ||
locale?: SiteLocale | null; | ||
value?: string; | ||
}[] | ||
| null; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit of a weird type, but it is what's common between all route fragments with _allSlugLocales
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it correct that locale
can be null
but value
can not?
return slug; | ||
}; | ||
export const getPageSlugFromPath = (path: URL['pathname']) => | ||
getSlugFromPath(path) as string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only for pages will this always return a string.
0f9b0de
to
81692ab
Compare
plop.setHelper('trailingParam', (value) => | ||
// Return the trailing param in a route | ||
// If the last slug is not a param, this is undefined | ||
value.split('/').pop()?.match(/\[(.*?)\]/)?.[1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In theory routes can also contain multiple params i.e. [lang]-[version]
or rest params [...pages]
.
If only using to check wether or not a param is used, you might want to use REGEX.test(value.split('/').pop())
Or if you want to access all params maintaining similar logic
value.split('/').pop()?.match(/\[(.*?)\]/)?.[1] | |
[...'[lang]-[ext]'.matchAll(/\[(.*?)\]/g)].map(match => match?.[1]) |
Which would return an array of params in the route name.
This will break implementations, you could return a joined string with a -
or return a string if the array length is 1
to avoid regression in existing code from this PR.
This will still return ...
if used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, but then I'd say let's write it this way:
value.match(/\[([^]]*)\]\/?$/)?.[1]
type LocalizedSlugs = { | ||
_allSlugLocales?: | ||
| { | ||
locale?: SiteLocale | null; | ||
value?: string; | ||
}[] | ||
| null; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it correct that locale
can be null
but value
can not?
type LocalizedSlugs = { | ||
_allSlugLocales?: | ||
| { | ||
locale?: SiteLocale | null; | ||
value?: string; | ||
}[] | ||
| null; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you find typing these nested interfaces weird, I suggest splitting them with context specific names
type LocalizedSlugs = { | |
_allSlugLocales?: | |
| { | |
locale?: SiteLocale | null; | |
value?: string; | |
}[] | |
| null; | |
}; | |
interface SlugLocale { | |
locale?: SiteLocale | null; | |
value?: string; | |
} | |
interface LocalizedSlugs { | |
_allSlugLocales?: SlugLocale[] | null; | |
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant "weird" because it is somewhat convoluted and with many undefined
and null
possibilities, but based on our typegen from graphql. From that perspective I actually think it is clearer when it is not split up.
* - /page-1/?foo=bar | ||
* - /grand-parent/parent-slug/page-1 | ||
*/ | ||
export const getSlugFromPath = (path: URL['pathname']) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
URL['pathname']
simply resolves to string
so this seems somewhat redundant
export const getSlugFromPath = (path: URL['pathname']) => { | |
export const getSlugFromPath = (path: string) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It resolves to string
, but I think labeling it as URL['pathname']
has semantic meaning to a developer looking at it.
export type PageRouteForPath = | ||
& Pick<PageRouteFragment, '_allSlugLocales' | 'parentPage'> | ||
& { parentPage?: PageRouteForPath | null }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would avoid intersecting imported generated and newly defined types with the same keys.
Extending a type or an interface is usually safer and helps with finding inconsistencies rather than intersecting
i.e.
export interface PageRouteForPath extends Pick<PageRouteFragment, '_allSlugLocales' | 'parentPage'> {
parentPage?: PageRouteForPath | null
};
Or expanding on this when child keys are required, and we want to set them to optional
export interface PageRouteForPath extends Partial<Pick<PageRouteFragment, '_allSlugLocales' | 'parentPage'>> {
parentPage?: PageRouteForPath | null
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I try to encourage type
over interface
because it is clearer when overridden. I don't feel your proposed interface
is clearer tbh but I'm happy to hear in person the benefits of this.
export const getParentSlugs = ({ page, locale }: { page: PageRouteForPath, locale?: SiteLocale }): MaybeSlug[] => { | ||
export const getParentSlugs = ({ locale, page }: { locale?: SiteLocale, page: PageRouteForPath }): MaybeSlug[] => { | ||
if (page.parentPage) { | ||
const slug = page.parentPage._allSlugLocales?.find(slug => slug.locale === locale)?.value; | ||
const slug = getLocalizedSlug<PageRouteForPath>({ locale, record: page.parentPage }); | ||
return [ | ||
...getParentSlugs({ page: page.parentPage, locale }), | ||
...getParentSlugs({ locale, page: page.parentPage }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think your editor is reordering some arguments here which makes it harder to review some changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I purposely did that so the order is consistent among all those functions ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jurgenbelien when I run this:
? [PLOP] Please choose a generator. page - create a new Page route
? Page route (e.g. `[locale]/search/`)? [locale]/events/[slug]/
? Model name in DatoCMS? (leave empty if none) Event
? Create separate file in routing for `Event`? Yes
My EventRoute.fragment.grapqhl
has a weirdly nested slug
field with sub properties like __typename
. I'm not sure where it's coming from exactly. But I don't think this is right:
fragment EventRoute on EventRecord {
__typename
id
title
slug {
__typename
id
title
slug
_allSlugLocales {
locale
value
}
}
}
No that's not right, but I think I do know why that's happening |
Changes
How to test
Checklist
I have made updated relevant documentation files (in project README, docs/, etc)I have added a decision log entry if the change affects the architecture or changes a significant technology