Skip to content

Commit

Permalink
Components: add TypeScript refactor guidelines (#40153)
Browse files Browse the repository at this point in the history
* Initial draft

* Tiny fixups

* Update packages/components/CONTRIBUTING.md

Co-authored-by: Lena Morita <lena@jaguchi.com>

* Update packages/components/CONTRIBUTING.md

Co-authored-by: Lena Morita <lena@jaguchi.com>

* Add a point regarding `@example` JSDoc on the main export

* Small tweaks

* Add more details to the point about renaming un-connected un-forwarded components

* American english

* Add a point regarding Storybook props table

* Use tabs

* Remove mention of  unstable or experimental props as props to be hidden

* Add more details on how to configure the meta Storybook settings

* Rewrite intro

* Remove "TBD" from unit tests section

Co-authored-by: Lena Morita <lena@jaguchi.com>
  • Loading branch information
ciampo and mirka committed Apr 25, 2022
1 parent 186b1e7 commit dc3d29f
Showing 1 changed file with 71 additions and 7 deletions.
78 changes: 71 additions & 7 deletions packages/components/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,17 +212,17 @@ Styles should be structured so the deprecated styles are cleanly encapsulated, a
```js
// styles.ts
const deprecatedMargins = ({ __nextHasNoOuterMargins }) => {
if ( ! __nextHasNoOuterMargins ) {
return css`
margin: 8px;
`;
}
if ( ! __nextHasNoOuterMargins ) {
return css`
margin: 8px;
`;
}
};

export const Wrapper = styled.div`
margin: 0;
margin: 0;
${deprecatedMargins}
${deprecatedMargins}
`;
```

Expand Down Expand Up @@ -472,3 +472,67 @@ component-family-name/
├── types.ts
└── utils.ts
```

## Refactoring a component to TypeScript

*Note: This section assumes that the local developer environment is set up correctly, including TypeScript linting. We also strongly recommend using an IDE that supports TypeScript.*

Given a component folder (e.g. `packages/components/src/unit-control`):

1. Add the folder to `tsconfig.json`, if it isn’t already.
2. Remove any `// @ts-nocheck` comments in the folder, if any.
3. Rename `*.js{x}` files to `*.ts{x}` (except stories and unit tests).
4. Run `npm run dev` and take note of all the errors (your IDE should also flag them).
5. Since we want to focus on one component’s folder at the time, if any errors are coming from files outside of the folder that is being refactored, there are two potential approaches:
1. Following those same guidelines, refactor those dependencies first.
1. Ideally, start from the “leaf” of the dependency tree and slowly work your way up the chain.
2. Resume work on this component once all dependencies have been refactored.
2. Alternatively:
1. For each of those files, add `// @ts-nocheck` at the start of the file.
2. Add the folders to the `tsconfig.json` file.
3. If you’re still getting errors about a component’s props, the easiest way is to slightly refactor this component and perform the props destructuring inside the component’s body (as opposed as in the function signature) — this is to prevent TypeScript from inferring the types of these props.
4. Continue with the refactor of the current component (and take care of the refactor of the dependent components at a later stage).
6. Create a new `types.ts` file.
7. Slowly work your way through fixing the TypeScript errors in the folder:
1. Try to avoid introducing any runtime changes, if possible. The aim of this refactor is to simply rewrite the component to TypeScript.
2. Add a named export for the unconnected, un-forwarded component. This ensures that the docgen can properly extract the types data. The naming should be so that the connected/forwarded component has the plain component name (`MyComponent`), and the raw component is prefixed (`UnconnectedMyComponent` or `UnforwardedMyComponent`). This makes the component's `displayName` look nicer in React devtools and in the autogenerated Storybook code snippets.
3. Extract props to `types.ts`, and use them to type components. The README can be of help when determining a prop’s type.
4. Use existing HTML types when possible? (e.g. `required` for an input field?)
5. Use the `CSSProperties` type where it makes sense.
6. Extend existing components’ props if possible, especially when a component internally forwards its props to another component in the package.
7. Use `WordPressComponent` type if possible.
8. Use JSDocs syntax for each TypeScript property that is part of the public API of a component. The docs used here should be aligned with the component’s README. Add `@default` values where appropriate.
9. Prefer `unknown` to `any`, and in general avoid it when possible.
8. On the component's main export, add a JSDoc comment that includes the main description and `@example` code snippet from the README ([example](https://github.com/WordPress/gutenberg/blob/943cec92f21fedcd256502ea72d9903941f3b05a/packages/components/src/unit-control/index.tsx#L290-L306))
9. Make sure that:
1. tests still pass;
2. storybook examples work as expected.
3. the component still works as expected in its usage in Gutenberg;
4. the JSDocs comments on `types.ts` and README docs are aligned.
10. Convert Storybook examples to TypeScript (and from knobs to controls, if necessary) ([example](https://github.com/WordPress/gutenberg/pull/39320)).
1. Update all consumers of the component to potentially extend the newly added types (e.g. make `UnitControl` props extend `NumberControl` props after `NumberControl` types are made available).
2. Rename Story extension from `.js` to `.tsx`.
3. Rewrite the `meta` story object, and export it as default. In particular, make sure you add the following settings under the `parameters` key:

```tsx
const meta: ComponentMeta< typeof MyComponent > = {
parameters: {
controls: { expanded: true },
docs: { source: { state: 'open' } },
},
};
```

These options will display prop descriptions in the `Canvas ▸ Controls` tab, and expand code snippets in the `Docs` tab.

4. Go to the component in Storybook and check the props table in the Docs tab. If there are props that shouldn't be there, check that your types are correct, or consider `Omit`-ing props that shouldn't be exposed.
1. Use the `parameters.controls.exclude` property on the `meta` object to hide props from the docs.
2. Use the `argTypes` prop on the `meta` object to customize how each prop in the docs can be interactively controlled by the user (tip: use `control: { type: null }` to remove the interactive controls from a prop, without hiding the prop from the docs).
3. See the [official docs](https://storybook.js.org/docs/react/essentials/controls) for more details.
5. Comment out all existing stories.
6. Create a default template, where the component is being used in the most “vanilla” way possible.
7. Use the template for the `Default` story, which will serve as an interactive doc playground.
8. Add more focused stories as you see fit. These non-default stories should illustrate specific scenarios and usages of the component. A developer looking at the Docs tab should be able to understand what each story is demonstrating. Add JSDoc comments to stories when necessary.
11. Convert unit tests.
1. Rename test file extensions from `.js` to `.tsx`.
2. Fix all TypeScript errors.

0 comments on commit dc3d29f

Please sign in to comment.