#
TypeScript + React + Storybook์ผ๋ก ๋์์ธ ์์คํ ๊ตฌ์ถํ๊ธฐ#
Design System vs Component Libraryโ ๋์์ธ ์์คํ ์ ๊ณ์ ์งํํ๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๊ตฌ์ฑ ์์ ๋ชจ์์ด๊ณ , ์ผ๊ด์ฑ๊ณผ ์๋๋ฅผ ๋ณด์ฅํ๋ ๊ท์น์ ๋ฐ๋ฅด๋ ๋ชจ๋ ์ ํ์ ๊ฐ๋ฐํ๊ธฐ ์ํ ๋จ์ผ ์ง์ค ๊ณต๊ธ์(SSOT)์ด๋ค.
- ๋์์ธ ์์คํ ์ ์ปดํฌ๋ํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋์ด์ ๋์์ธ ์์น, ์คํ์ผ ๊ฐ์ด๋, ํจํด, ํค, ๊ท์น๊ณผ ๋ช ์ธ์ ๋ฑ์ ํฌํจํ๋ค.
#
ํ๋ก์ ํธ ์์ฑ.gitignore
,package.json
์์ฑ
#
Storybook ์ค์นํ๊ธฐnpx -p @storybook/cli sb init --type react
- storybook ์์ํ๊ธฐ
yarn storybook
#
React peer dependencies@storybook/react
๊ฐ react
, react-dom
์ peer-dependency๋ก ๊ฐ์ง๋ฏ๋ก react, react-dom ์ ์ค์นํด์ผ ์คํํ ์ ์๋ค. ์ค์นํ์ง ์์ผ๋ฉด ๋ค์์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
Error: Cannot find module 'react-dom/package.json'
ํ์ง๋ง, dependency ์ ์ง์ ์ถ๊ฐํ๋ฉด, ๊ฐ๋ฐํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํ ์ ์ ๊ฐ react
, react-dom
์ญ์ ์ค์น ๋ฐ๊ฒ ๋๋ค. (๊ฒ๋ค๊ฐ ์ ํด์ง ๋ฒ์ ์ผ๋ก)
๋ฐ๋ผ์ ์ปดํฌ๋ํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์์ฑํ๋ ์์ฑ์ ์ ์ฅ์์๋ storybook์ ์คํํ๋ ๊ฐ๋ฐ ์์๋ง ์ด ์์กด์ฑ์ด ํ์ํ๋ฏ๋ก devDependency์ ๋ช ์ํด์ผํ๋ค.
๋ํ, ์ด ์ปดํฌ๋ํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ์ฌ์ฉ์ ์
์ฅ์์๋ react
, react-dom
๊ณผ ํจ๊ป ์ฌ์ฉํด์ผ ํ๊ธฐ ๋๋ฌธ์ ๋ฐ๋ผ์ peerDependency์๋ ๋ช
์ํด์ผํ๋ค.
peerDependency๋ ์ด ์์กด์ฑ๊ณผ ํจ๊ป ์ฌ์ฉํด์ผ ํ๋ค๋ ๋ป์ด๊ณ , ์ ์ ๊ฐ ์ง์ ์ค์น ํด์ผํ๊ธฐ ๋๋ฌธ์ด๋ค.
devDependency๋ ํ๋ก์ ํธ์ ๋ก์ปฌ์๋ง ์ค์น๋๊ณ ๋ฐฐํฌ์์๋ ์ ์ ๊ฐ ๋ค์ด๋ก๋ํ์ง ์๊ธฐ๋๋ฌธ์ peerDependency์ devDependency์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ ์ถ๊ฐํด๋ ๋ฌด๋ฐฉํ๋ค.
yarn add -D react react-dom
// package.json"peerDependencies": { "react": "17.0.1", "react-dom": "17.0.1", "styled-components": "5.2.1"}
Duplicate same dependency in package.json devDependencies and peerDependencies?
#
TypeScript๋ก ์ด์ ํ๊ธฐtypescript, react-docgen-typescript-loader ์ค์น
yarn add -D typescript react-docgen-typescript-loader
stories
typescript ๋ฒ์ ์ผ๋ก ๋ณ๊ฒฝcreate-react-app typescript ํ ํ๋ฆฟ์ sb init ์ผ๋ก ์์ฑํ ts ๋ฒ์ stories๋ก ํ ์คํธ
.storybook/main.js
๋ณ๊ฒฝmodule.exports = { stories: [ '../stories/**/*.stories.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)', ], addons: ['@storybook/addon-links', '@storybook/addon-essentials'], typescript: { check: false, checkOptions: {}, reactDocgen: 'react-docgen-typescript', reactDocgenTypescriptOptions: { shouldExtractLiteralValuesFromEnum: true, propFilter: (prop) => prop.parent ? !/node_modules/.test(prop.parent.fileName) : true, }, },};
tsconfig.json
์ถ๊ฐ{ "compilerOptions": { "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx" }, "include": ["stories"]}
#
Rollup์ผ๋ก ๋ฒ๋ค๋งํ๊ธฐโ ์นํฉ์ด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ๋ฒ๋ค๋ฌ๋ผ๋ฉด, ๋กค์ ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ํ ๋ฒ๋ค๋ฌ๋ค.
Webpack and Rollup: the same but different
#
Rollup ์ค์ - ์ฌ์ฉํ๋ ํ๋ฌ๊ทธ์ธ
"devDependencies": { "babel-preset-react-app": "10.0.0", // create-react-app์์ ์ฌ์ฉํ๋ babel ์ค์ "rollup": "2.35.1", "rollup-plugin-babel": "4.4.0", // babel ์ฌ์ฉ์ ์ํ ํ๋ก๊ทธ์ธ "rollup-plugin-cleaner": "1.0.0", // build ์ ์ dist ํด๋ ์ญ์ "rollup-plugin-commonjs": "10.1.0", // CommonJS์ ๋ชจ๋ ์ฝ๋๋ฅผ ES6๋ก ๋ณํํ์ฌ ๊ฒฐ๊ณผ๋ฌผ์ ํฌํจ "rollup-plugin-node-resolve": "5.2.0", // ์จ๋ํํฐ ๋ชจ๋์ ์ฌ์ฉํ๊ธฐ์ํ ์ฉ๋ "rollup-plugin-peer-deps-external": "2.2.4", // peerDependencies๋ฅผ ๋ฒ๋ค๋ง๋ ๊ฒฐ๊ณผ์ ํฌํจํ์ง ์์}
rollup.config.js
import commonjs from 'rollup-plugin-commonjs';import cleaner from 'rollup-plugin-cleaner';import resolve from 'rollup-plugin-node-resolve';import babel from 'rollup-plugin-babel';import external from 'rollup-plugin-peer-deps-external';import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import pkg from './package.json';
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
process.env.BABEL_ENV = 'production';
export default { input: './src/index.ts', plugins: [ cleaner({targets: ['./dist/']}), peerDepsExternal(), resolve({extensions}), commonjs({ include: 'node_modules/**', }), babel({ extensions, include: ['src/**/*'], presets: [['react-app', {flow: false, typescript: true}]], runtimeHelpers: true, }), ], output: [ { file: pkg.module, format: 'es', }, ],};
#
tsconfig.json & package.json ์ค์ declaration์ด๋, ์ปดํฌ๋ํธ๋ค์์ ์ฌ์ฉํ๊ณ ์๋ ํ์ ์ ๋ณด๋ค์ ์ง๋๊ณ ์๋ ํ์ผ.
์ด๋ ๋ค์ ๋ช ๋ น์ด๋ก ์์ฑ์ ํ ์ ์๋ค.
tsc --emitDeclarationOnly
์ด ๋ช ๋ น์ด๋ฅผ ์คํํ๊ธฐ ์ ์ tsconfig.json ์ ์์ ํด์ฃผ์ด์ผ ํ๋ค.
{ "compilerOptions": { "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "jsx": "react", "declaration": true, "declarationDir": "dist/types" }, "include": ["src"], "exclude": ["**/*.stories.tsx"]}
declaration
ย ๊ฐ์ยtrue
ย ,ยdeclarationDir
ย ๊ฒฝ๋ก๋ฅผย"dist/types"
ย ๋ก,allowJs: ์๋ฐ์คํฌ๋ฆฝํธ์ ํผ์ฉ์ ํ๊ณ ์๋ค๋ฉด declaration ํ์ผ์ ๋ง๋ค์ง ๋ชปํ๋ฏ๋ก์ ๊ฑฐ.
noEmit: ๊ฒฐ๊ณผ๋ฌผ์ ๋ง๋ค์ง ์๋๋ค๋ ์ต์ ์ผ๋ก ์ ๊ฑฐ.
isolatedModules: ์๋ฌด ๊ฐ๋ ๋ด๋ณด๋ด์ง ์๋ ํ์ผ์ ๋ฐฉ์งํ๋ ์ต์ . ์ ๊ฑฐ
stories.tsx
ย ํ์ฅ์๋ ๋ชจ๋ ๋ฌด์ํ๋๋กย exclude
ย ์ต์
์ ์ค์
package.json์๋ Build ์ปค๋งจ๋๋ฅผ ์ถ๊ฐํ๋ค.
"build": "rollup -c && tsc --emitDeclarationOnly",
package.json์์ module, types, files ๋ฅผ ์ถ๊ฐํ๋ค. name์ scope๋ฅผ ์ฌ์ฉํ๋ค.
{ "name": "@younho9/design-system", "module": "dist/index.js", "types": "dist/types/index.d.ts", "files": ["/dist"]}
๋ฐฐํฌ ๋ช ๋ น์ด
# npm publishnpm publish --access public # scope๋ฅผ ์ฌ์ฉํ ๋
#
์ฐธ๊ณ ์๋ฃDo you think your component library is your design system? Think again
TypeScript์ Storybook์ ์ฌ์ฉํ ๋ฆฌ์กํธ ๋์์ธ ์์คํ ๊ตฌ์ถํ๊ธฐ
How to create a react component library with TypeScript, rollup.js and Storybook
Building a Design System Package With Storybook, TypeScript, and React in 15 Minutes