- UI Kits, Design System ๊ตฌ์ถ์ ์ํ NPM์ผ๋ก ์คํ์์ค ๋ฐฐํฌ์ ์ต์ ํ ๋ณด์ผ๋ฌ ํ๋ ์ดํธ
2023๋ 6์ ๊ธฐ์ค
์ต์ ๋ฒ์ ์ผ๋ก ๊ตฌ์ถ๋์ด ์์ต๋๋ค.- ์นํฉ ๊ฐ๋ฐ ์๋ฒ, ๋ถ ํ์ํ ํ๋ฌ๊ทธ์ธ, ๋ก๋ ๋ฑ ๋ชจ๋ ์ ๊ฑฐ ํ ๊ฒฝ๋ํ
- ๋ชจ๋ ์ปดํฌ๋ํธ UI ํ
์คํธ๋ ์นํฉ ๊ฐ๋ฐ ์๋ฒ๋ฅผ ๋์ฐ์ง ์๊ณ ,
Storybook
์ ์ด์ฉ (ํ๋จ ๋ด์ฉ ์ฐธ๊ณ ) - cjs, esm ๋ชจ๋ ์ง์
- React v18
- Babel์ ์ด์ฉํ ํธ๋์คํ์ผ๋ง
- Rollup์ ์ด์ฉํ ๋ฒ๋ค๋ง
- TypeScript v5
- @emotion
- Storybook v7
- ํด๋น ๋ ํฌ๋
yarn ํจํค์ง ๋งค๋์
๋ก ๊ตฌ์ฑํ๊ธฐ ๋๋ฌธ์ ํน์ ์ด์๊ฐ ์๋ค๋ฉด yarn์ ์ด์ฉํด ์์กด์ฑ ์ค์น๋ฅผ ๊ถ์ฅ
yarn
or
yarn install
- ํด๋น ๋ณด์ผ๋ฌ ํ๋ ์ดํธ๋ฅผ cloneํด์ ์ฌ์ฉํ๋ค๋ฉด package.json ์์ ์ด ํ์ํจ
name
,version
,description
,repository/url
,author
,homepage
... ๋ฑ ์์ ํ์
{
"name": "react-npm-deploy-boilerplate", // (*)
"version": "1.0.0", // (*)
"description": "react-npm-deploy-boilerplate", // (*)
"scripts": {
// ...
},
"repository": {
"type": "git",
"url": "git+https://github.com/ssi02014/react-npm-deploy-boilerplate.git" // (*)
},
"author": "Gromit", // (*)
"license": "ISC",
"bugs": {
"url": "https://github.com/ssi02014/react-npm-deploy-boilerplate.git/issues" // (*)
},
"homepage": "https://github.com/ssi02014/react-dev-env-boilarplate", // (*)
// ...
}
src/components
์์ ์ปดํฌ๋ํธ ์์
// src/components/Button/Button.tsx
import React from 'react';
import styled from '@emotion/styled';
interface Props {
children: React.ReactNode;
size?: 'medium' | 'large';
}
const Button = ({ children, size = 'medium' }: Props) => {
return <StyledButton size={size}>{children}</StyledButton>;
};
src/stories/components
์์{Component}.stories.tsx
ํํ๋ก ํ์ผ ์์ฑ ํ ์คํ ๋ฆฌ๋ถ์ผ๋ก UI ํ ์คํธ ์งํ- ์คํ ๋ฆฌ๋ถ์์ UI ํ
์คํธ ์งํ ํ
src/index.tsx
์์ export
export { default as theme } from '@shared/theme';
export { default as Button } from '@components/Button';
import image from '../../assets/sheep.jpg';
import testUrl, { ReactComponent as TestSVG } from '../../assets/test.svg';
const Component = ({ children, size = 'medium' }: Props) => {
return (
<>
{/* image test */}
<img width={300} src={image}></img>
{/* svg component test */}
<TestSVG />
{/* svg url test */}
<img src={testUrl} alt="" />
</>
);
};
- ์ปดํฌ๋ํธ ์์ ํ ์๋ ๋ช ๋ น์ด๋ฅผ ํตํด ๋ฐฐํฌ๋ฅผ ์ํ build ์งํ
yarn build
- build๋ฅผ ์งํํ๋ฉด ์ ์์ ์ผ๋ก
dist
ํด๋๊ฐ ์์ฑ๋์ด์ผ ํ๋ค. ์ค์ง์ ์ผ๋ก ํด๋น ํด๋๋ฅผ npm์ ๋ฐฐํฌํ๊ฒ ๋๋ค.
- ์ฌ์ค ํ์ฌ ์ ์ฅ์๋
github actions
์ ํตํด ์๋ ๋น๋ ๋ฐ npm ๋ฐฐํฌ๋ฅผ ์งํํ๋ค. ๋ํ, ์คํ ๋ฆฌ๋ถ ํ์ด์ง๋ ์๋ ๋น๋ ํ์ github page๋ก ๋ฐฐํฌ๋ฅผ ์งํํ๋ค. - ์ ๊ณผ์ ์
master
๋ธ๋์น๋ก๋ง ์ปค๋ฐ์ด ์ฌ๋ผ๊ฐ๋ฉด ์ด๋ฅผ ํธ๋ฆฌ๊ฑฐ ์ผ์ ์๋์ผ๋ก ์งํ๋๋ค. - ๋ฐฐํฌ ์ ์ package.json
version
์ ๋ฐ์ดํธ ํด์ฃผ๋ ๊ฒ์ ๊ผญ! ์์ง๋ง์.
yarn add (๋ณธ์ธ ๋ฐฐํฌ ์ ์ฅ์)
import { Button } from 'react-npm-deploy-boilerplate';
function App() {
return (
<div>
<Button>ํ์ด</Button>
<Button size="large">๋ฐ์ด</Button>
</div>
)
}
export default App;
- storybook์ ํตํด์ ui ํ ์คํธ๊ฐ ๊ฐ๋ฅํ๋ค.
- ์๋ ๋ช ๋ น์ด๋ฅผ ํฐ๋ฏธ๋๋ก ์ ๋ ฅ์ ํตํด ์คํ ๋ฆฌ๋ถ ์๋ฒ๋ฅผ ์คํํ ์ ์๋ค.
yarn storybook
- ์๋์ ๊ฐ์ ์์ ์ฝ๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์คํ ๋ฆฌ๋ถ ์ฝ๋ ์์ฑ
// src/stories/components/Button.stories.tsx
import React from 'react';
import { StoryFn } from '@storybook/react';
import Button from '@components/Button';
export default {
title: 'components/Button',
argTypes: {
variant: {
options: ['primary', 'secondary'],
control: { type: 'check' },
},
size: {
options: ['medium', 'large', 'small'],
control: { type: 'select' },
},
},
};
interface Props {
size: 'medium' | 'large' | 'small';
select: any[];
}
const Template: StoryFn<Props> = ({ size }: Props) => {
return (
<div>
<Button size={size}>์๋
</Button>
</div>
);
};
export const Default = {
render: Template,
args: {
size: 'medium',
},
};
// ...
build:storybook
์ผ๋ก ๋น๋ ํ์deploy:storybook
์ผ๋ก github page๋ก ๋ฐฐํฌ
yarn build:storybook
yarn deploy:storybook
// tsconfig.paths.json
{
"compilerOptions": {
"paths": {
"@components/*": ["src/components/*"],
"@shared/*": ["src/shared/*"],
// ์ฌ๊ธฐ๋ค ์ถ๊ฐ
},
}
}