A story of a shared components library
Andrey Semin
Senior Software Engineer
at MicroMech
https://twitter.com/ifedyukin
https://www.instagram.com/semin_andrey
Hello Andrey!
Has anyone tried to build your own components library?
Why we decided to build this library?
- Build more than 1 app
- Attract more customers
- Provide same look & feel
Starting point
- App built with MaterialUI 🤮
- Sometimes components from MUI were wrapped with custom components
- Sometime they were not
- No documentation
- MUI-styled components API
- Weird or missing tests
Requirements
- Simple and handy component API
- Framework-independent API
- Written in TypeScript
- Well documented
- Well tested
- Include components showcase
- Built on fresh MUI version
How exciting it is!
- Extract basic components to separate package
- Drop lots of dependencies from the app
- Remove weird tests
- Make everything "right"
Cotton
Component folder
- index.ts
- ComponentName.tsx
- ComponentName.spec.tsx
- ComponentName.stories.tsx
- ComponentName.README.md
- components/
Component migration
- Search for all usages of a component
- List all used props and their values
- Use prepared list to design component API
- Migrate component
How to bundle?
Webpack!
Time to test!
- Write tests from scratch
- Jest + @testing-library/react ⚡
- External component API
- Separate snapshot for each render path
Time for Storybook!
How MaterialUI uses styles
const typographyStyles = (): StyleRules => createStyles({...})
const TypographyComponent: React.FC<TypographyProps> = () => {...}
export const Typography = withStyles(typographyStyles)(TypographyComponent)
css-in-js 👋
*.md for the rescue!
A component to add an empty space with controllable height based on default theme gutter value
| Name | Required | type | Default Value | Description |
| ---------------- | -------- | ------ | ------------- | ----------------------------------- |
| gutterMultiplier | - | number | 1 | Multiplier to default theme gutter |
| data-test-id | - | string | - | Custom data-test-id attribute value |
### Example:
```js
import { Spacer } from '@motokaptia/cotton';
<>
<SomeBlock />
<Spacer />
<SomeOtherBlock />
</>;
```
*.md for the rescue!
We have to ship parts of MUI itself
- ThemeProvider component
- createGenerateClassName function
- withStyles, withWidth HOCs
- StyleRules, Theme types
- isWidthDown function
css-in-js 👋
OK, let's try again
New error 😞
- Quick googling - no results
- Not-so-quick googling - no results
- 2 days of investigation
Root cause of the issue is...
React hook call wrapped with if statement
And the solution is
- Downgrade MaterialUI to latest v3 release
- Replace webpack with regular TS transpilation
Now it should work!
But it doesn't
Why?
css-in-js 👋
+
Server Side Rendering
function createStyleContext() {
return {
sheetsManager: new Map(),
sheetsRegistry: new SheetsRegistry(),
generateClassName: createGenerateClassName()
};
}
export function getStyleContext() {
if (!process.browser) {
return createStyleContext();
}
if (!global.__INIT_MATERIAL_UI__) {
global.__INIT_MATERIAL_UI__ = createStyleContext();
}
return global.__INIT_MATERIAL_UI__;
}
Same error appeared in tests
jest.mock('@material-ui/core/styles/createGenerateClassName');
createGenerateClassName.mockImplementation(() => (rule, styleSheet) =>
`${styleSheet.options.classNamePrefix}-${rule.key}`
);
One more testing issue
Select elements by data-test-id attribute
Naïve approach
- Specify data-test-id value in components library
- Use it in library consumers
Such approach won't work in long-term perspective
- Update value in library
- Have to update e2e tests in all apps
Better way to handle data-test-id
- Add data-test-id prop to components' API
- For complex components, generate data-test-id value
Better way to handle data-test-id
data-test-id + postfix
data-test-id="select"
data-test-id="select-root"
data-test-id="select-option"
Better way to handle data-test-id
data-test-id is omitted from production build
babel-plugin-jsx-remove-data-test-id
Finally!
- App is running
- SSR works
- Styles are not broken
- Component is on it's place
Conclusion
- css-in-js - 💩
- MaterialUI - 💩
Now seriously
- Easy to work on brand redesign
- Focus on components and their state
- Reduced time to interact with design guy
- Great DX
Library is ready & in use
Benefits we got:
What went wrong
- Outdated MaterialUI version
- Quality of tests dropped over time
- Failed to incapsulate MUI within the library
- No automatic releases
What's next?
- Finish redesign
- Implement more components
- Update MaterialUI
- Configure releases
https://www.instagram.com/semin_andrey
Shared Components Library
By Andrey Semin
Shared Components Library
- 395