-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
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
[docs-infra] Simplify API sections typing #43128
Conversation
Netlify deploy previewhttps://deploy-preview-43128--material-ui.netlify.app/ Bundle size report |
|
||
const [displayOption, setDisplayOption] = useApiPageOption(layoutStorageKey, defaultLayout); | ||
const formatedProperties = Object.entries(properties) | ||
.filter(([, propData]) => propData.description !== '@ignore') |
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 take a close look, you will notice this line is removed in the tsx
files. That's because the description
property does not exist in the typing.
I will need to double-check with all components using this. But for example, base uses the @ignore
on some props, and they never reach that place.
packages/mui-docs/src/i18n/i18n.tsx
Outdated
@@ -96,7 +107,9 @@ export interface TranslateOptions { | |||
ignoreWarning?: boolean; | |||
} | |||
|
|||
export function useTranslate() { | |||
export type Translator = (key: string, options?: TranslateOptions) => 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.
Added to simplify the typing when using translator in helper functions
top-notch. I think the strategy makes a lot of sense. add types => refactor => extract. Similar approach can be done between the pages and the output of the markdown generator. Or anywhere else we bridge between build time and runtime.
I'd love for those {{"component": "./SomeComponent", "prop1": "foo", "prop2": 123}}
Is spiritually equivalent to
import SomeComponent from "./SomeComponent"
// ...
<SomeComponent prop1='foo' prop2={123} /> |
@Janpot Not sure to understand your point on this one. The API pages are not markdown rendered The page file fetch the JSON and render the ApiPage component with the JSON data. |
Ah yes ofcourse. Long term we could perhaps think about making the constituents composable in markdown pages? e.g. due to complexity in the types we're at the moment writing some tables by hand in Toolpad that look a lot like these property tables. |
t, | ||
componentName: pageContent.name, | ||
componentProps, | ||
inheritance, | ||
themeDefaultProps: pageContent.themeDefaultProps, | ||
}), | ||
createTocEntry('props'), | ||
componentSlots?.length > 0 && createTocEntry('slots'), | ||
...getClassesToC({ | ||
t, | ||
componentName: pageContent.name, | ||
componentClasses, | ||
}), | ||
createTocEntry('classes'), |
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.
Need to bring that feature back. I initially removed it because I remembered that with the table layout it was useless, but did not manage to find back this conclusion on github
properties={componentProps} | ||
propertiesDescriptions={propDescriptions} | ||
componentName={pageContent.name} | ||
properties={propsApiProcessor({ |
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 the new way of defining API pages, such that if someone need to pass other kind of data, they can do it
The previous one should still work the time of the migration
{classes.map((classDefinition) => { | ||
const { className, key, description, isGlobal, isDeprecated, deprecationInfo } = | ||
const { hash, className, key, description, isGlobal, isDeprecated, deprecationInfo } = |
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.
Generating the hash
is now the responsibility of the processors.
// interface InterfaceApiProcessorParams {} | ||
|
||
// export function InterfaceApiProcessor(params: InterfaceApiProcessorParams): PropertyDefinition[] { | ||
// return []; | ||
// } |
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.
TODO on X side
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.
Two minor comments about naming and type-safety. The latter wasn't introduced in this PR, just something I noticed. They're just suggestions, no blockers.
componentName: string; | ||
} | ||
|
||
export function classesApiProcessor(params: ClassesApiProcessorParams): ClassDefinition[] { |
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.
Not a blocker at all, just some reflections from reviewing this but I find the naming confusing. a "processor" is a noun, for a function I usually expect a verb. The meaning of the word "processor" is quite broad, it can mean anything.
For functions that transform one data type to another, I'd prefer to choose a name that includes both types. e.g. mapXtoY(x)
, xToY(x)
, yFromX(x)
. I like the latter as it mimics javascript API design (Object.fromEntries()
, Array.from()
, String.fromCharCode()
, String.fromCodePoint()
). I realize those aren't verbs neither, but the from/to clearly indicates a transformation.
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.
Effectively I was not super happy with those namings
About mapXtoY
naming, my struggle is that the X
is not easy to name since it's a mix of descriptions and translations. So I went with a getter naming get[Yyy]ApiDefinitions
...classDefinition, | ||
description: | ||
classDescriptions[classDefinition.key]?.description | ||
?.replace(/{{conditions}}/, classDescriptions[classDefinition.key].conditions!) |
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 there any way to get rid of these non-null assertion operators? It removes type safety and an upstream bug can be introduced that essentially turns this replacement into 'undefined'
without a warning.
If it's hard to guarantee type-wise and have this as a compile time error, we could add an invariant()
call to turn this into a build time error instead of quietly starting to replace strings with 'undefined'
at run time.
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 went with the following solution
if (!conditions && description.search(/{{conditions}}/) !== -1) {
throw Error(
`In ${componentName} the class "${classDefinition.className}" description with "{{conditions}}" but without \`conditions\` to replace it.`,
);
}
It adds the runtime error, but does not fix the typing, because the fact that conditions
is defined depends on the presence of {{conditions}}
in the description, which might be hard to define with types
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.
The way I would write this is to replace only within the check of whether the description contains {{conditions}}
// (use .includes())
if (!conditions && description.includes(/{{conditions}}/)) {
throw new Error('...')
}
// This is suboptimal, a type checker can't relate the previous condition with the following replacement
return {
// we're replacing, even when we know there is nothing to replace
description: description.replace(/{{conditions}}/, conditions!),
}
The solution requires a refactor
// make description a mutable variable
if (description.includes(/{{conditions}}/)) {
// Use an invariant to assert the condition, this would be a programming error
// (don't use invariant when an error comes from user input)
invariant(conditions, 'conditions should be defined if description includes {{conditions}}')
// only replace when it makes sense
descripton = description.replace(/{{conditions}}/, conditions)
}
return {
description
}
It's cleaner and more defensive imo. non-null assertion is always a code smell. I have never seen a use-case that isn't better solved with a refactor or an invariant
. But again, this is all polish. Doesn't have to be in scope for this PR.
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.
Thanks for the detailed explanation 👍
); | ||
} | ||
|
||
if (!conditions && description.search(/{{nodeName}}/) !== -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.
if (!conditions && description.search(/{{nodeName}}/) !== -1) { | |
if (!nodeName && description.search(/{{nodeName}}/) !== -1) { |
Signed-off-by: Alexandre Fauquette <[email protected]>
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.
clean
Follow up of #43149
Previously
In the previous PR, The section props (
PropertiesSection
,ClassesSection
, andSlotsSection
) have been typed base on the@mui-internal/api-docs-builder
types to enforce consistency.In This PR
Section types
The sections now define the items they will render independently from the
api-docs-builder
with interfacesClassDefinition
,SlotDefinition
, andPropertyDefinition
. They come with processor that map slot, classes, props, and hooks params and return values to the appropriate definitionThis migration is optional. See for example the typing of
ClassesSectionProps
. If the props are not the new ones, the processor will be applied by theClassesSection
. One other repository (I think only X is impacted) we will be able to remove those old options.Table of contents
The generation of the ToC can be done based in those
ClassDefinition
,SlotDefinition
, andPropertyDefinition
.But because the
MarkdownV2
which defines its ToC before rendering the Sections, I kept the old one and marked them as deprecatedTable of content entries are now typed