React+TypeScript+Storybook+Rollupのプロジェクト作成
コンポーネントをガッツリ作る必要が出てきたので、この際 Storybook にも入門してみる。
WebPack v5 でトランスパイルしようとしたところ、Storybook の内部で使用している WebPack のバージョンと競合してしまったため、Rollup に変更する
やること: React コンポーネントのプロジェクトを作成し、Storybook で動作確認する
環境など:
Mac
Node v15
npm v7
TypeScript v4
React v17
Storybook v6
Rollup v2.39
以下を参考にやってみる
How to create a React component library with TypeScript, rollup.js and Storybook | by Dennis Schneider | Medium
プロジェクトフォルダを作成して npm, git の初期化
mkdir project-dir
cd project-dir
npm init -y
git init
touch .gitignore
.gitignore
の内容は create-react-app
で生成されるものをコピペ。
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
Storybook の導入。
npm v7 の場合、依存関係のチェックでエラーが出るので、--legacy-peer-deps
のオプションを指定
npx --legacy-peer-deps sb init --type react
React と TypeScript をインストール
npm install --save-dev react react-dom typescript @types/react @types/react-dom
ここまでで Storybook が正常起動するか確認
❯ npm run storybook
> react-ts-starter@1.0.0 storybook
> start-storybook -p 6006
info @storybook/react v6.1.18
info
info => Using prebuilt manager
info => Loading presets
info => Loading 1 config file in "./.storybook"
info => Loading 7 other files in "./.storybook"
info => Adding stories defined in ".storybook/main.js"
info => Using default Webpack setup
webpack built b69fdada25beecc27be0 in 4517ms
╭───────────────────────────────────────────────────╮
│ │
│ Storybook 6.1.18 started │
│ 5.35 s for preview │
│ │
│ Local: http://localhost:6006/ │
│ On your network: http://192.168.1.10:6006/ │
│ │
╰───────────────────────────────────────────────────╯
起動した!
Rollup およびそのプラグインをインストール
npm install rollup @rollup/plugin-commonjs @rollup/plugin-node-resolve rollup-plugin-peer-deps-external rollup-plugin-typescript2 -D
touch rollup.config.js
Rollupの設定ファイル。
ひとまず参考のサイトに記載されている内容をそのままコピペ。
import commonjs from "@rollup/plugin-commonjs";
import resolve from "@rollup/plugin-node-resolve";
import peerDepsExternal from "rollup-plugin-peer-deps-external";
import typescript from "rollup-plugin-typescript2";
import packageJson from "./package.json";
export default {
input: "./src/index.ts",
output: [
{
file: packageJson.main,
format: "cjs",
sourcemap: true
},
{
file: packageJson.module,
format: "esm",
sourcemap: true
}
],
plugins: [peerDepsExternal(), resolve(), commonjs(), typescript()]
};
rollup.config.js
は出力先を package.json
から取得しているので、package.json
に設定を追加する。
ついでに peerDependencies
を追記。
{
"name": "react-ts-starter",
"version": "1.0.0",
"description": "",
- "main": "index.js",
+ "main": "build/index.js",
+ "module": "build/index.es.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.12.17",
"@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-node-resolve": "^11.2.0",
"@storybook/addon-actions": "^6.1.18",
"@storybook/addon-essentials": "^6.1.18",
"@storybook/addon-links": "^6.1.18",
"@storybook/react": "^6.1.18",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.1",
"babel-loader": "^8.2.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-typescript2": "^0.30.0",
"typescript": "^4.1.5"
+ },
+ "peerDependencies": {
+ "react": "^17.0.1",
+ "react-dom": "^17.0.1"
}
}
ESLint, Prettier, npm-run-all をインストール
npm install --save-dev eslint eslint-config-react-app eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/eslint-plugin @typescript-eslint/parser prettier eslint-config-prettier npm-run-all
ESLint, Prettier, TypeScript の設定ファイル、VSCode の設定ファイルを作成
touch .eslintignore .prettierignore .prettierrc.js tsconfig.json
mkdir .vscode
touch .vscode/settings.json
node_modules/
build/
node_modules/
build/
module.exports = {
trailingComma: 'es5',
tabWidth: 4,
printWidth: 100,
singleQuote: true,
};
{
"compilerOptions": {
"outDir": "./build/",
"sourceMap": true,
"noImplicitAny": true,
"esModuleInterop": true,
"module": "esnext",
"target": "es6",
"jsx": "react",
"declaration": true,
"moduleResolution": "Node"
},
"include": ["src"]
}
{
"typescript.tsdk": "node_modules/typescript/lib",
"editor.codeActionsOnSave": {
"source.organizeImports": true,
"source.fixAll.eslint": true
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}
package.json に ESLint の設定を追加。
ついでに ESLint, Prettier を npm scripts から呼べるようにコマンド追加。
{
"name": "react-ts-starter",
"version": "1.0.0",
"description": "",
"main": "build/index.js",
"module": "build/index.es.js",
"scripts": {
+ "fix": "run-s fix:prettier fix:eslint",
+ "fix:eslint": "npm run lint:eslint -- --fix",
+ "fix:prettier": "npm run lint:prettier -- --write",
+ "lint": "run-p -l -c --aggregate-output lint:*",
+ "lint:eslint": "eslint .",
+ "lint:prettier": "prettier --check .",
"test": "echo \"Error: no test specified\" && exit 1",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.12.17",
"@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-node-resolve": "^11.2.0",
"@storybook/addon-actions": "^6.1.18",
"@storybook/addon-essentials": "^6.1.18",
"@storybook/addon-links": "^6.1.18",
"@storybook/react": "^6.1.18",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.1",
"@typescript-eslint/eslint-plugin": "^4.15.1",
"@typescript-eslint/parser": "^4.15.1",
"babel-loader": "^8.2.2",
"eslint": "^7.20.0",
"eslint-config-prettier": "^8.0.0",
"eslint-config-react-app": "^6.0.0",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.5",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-typescript2": "^0.30.0",
"typescript": "^4.1.5"
},
"peerDependencies": {
"react": "^17.0.1",
"react-dom": "^17.0.1"
+ },
+ "eslintConfig": {
+ "parser": "@typescript-eslint/parser",
+ "extends": [
+ "react-app",
+ "plugin:@typescript-eslint/eslint-recommended",
+ "plugin:@typescript-eslint/recommended",
+ "prettier"
+ ]
}
}
reactjs - 'React' was used before it was defined - Stack Overflow
ESLint のルール追加。
{
"name": "react-ts-starter",
"version": "1.0.0",
"description": "",
"main": "build/index.js",
"module": "build/index.es.js",
"scripts": {
"fix": "run-s fix:prettier fix:eslint",
"fix:eslint": "npm run lint:eslint -- --fix",
"fix:prettier": "npm run lint:prettier -- --write",
"lint": "run-p -l -c --aggregate-output lint:*",
"lint:eslint": "eslint .",
"lint:prettier": "prettier --check .",
"test": "echo \"Error: no test specified\" && exit 1",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.12.17",
"@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-node-resolve": "^11.2.0",
"@storybook/addon-actions": "^6.1.18",
"@storybook/addon-essentials": "^6.1.18",
"@storybook/addon-links": "^6.1.18",
"@storybook/react": "^6.1.18",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.1",
"@typescript-eslint/eslint-plugin": "^4.15.1",
"@typescript-eslint/parser": "^4.15.1",
"babel-loader": "^8.2.2",
"eslint": "^7.20.0",
"eslint-config-prettier": "^8.0.0",
"eslint-config-react-app": "^6.0.0",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.5",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-typescript2": "^0.30.0",
"typescript": "^4.1.5"
},
"peerDependencies": {
"react": "^17.0.1",
"react-dom": "^17.0.1"
},
"eslintConfig": {
"parser": "@typescript-eslint/parser",
"extends": [
"react-app",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
- ]
+ ],
+ "rules": {
+ "no-use-before-define": "off",
+ "@typescript-eslint/no-use-before-define": [
+ "error"
+ ]
+ }
}
}
❯ npm run lint
> react-ts-starter@1.0.0 lint
> run-p -l -c --aggregate-output lint:*
~~~ 中略 ~~~
[lint:prettier]
[lint:prettier] Checking formatting...
[lint:prettier] .storybook/main.js
~~~ 中略 ~~~
[lint:prettier] stories/Page.stories.js
[lint:prettier] Code style issues found in the above file(s). Forgot to run Prettier?
~~~ 中略 ~~~
[lint:eslint ] /Users/kazunorikimura/repository/react-ts-starter/stories/Page.stories.js
[lint:eslint ] 6:1 warning Assign object to a variable before exporting as module default import/no-anonymous-default-export
[lint:eslint ]
[lint:eslint ] ✖ 10 problems (0 errors, 10 warnings)
[lint:eslint ]
ERROR: "lint:prettier" exited with 1.
npm ERR! code 1
npm ERR! path /Users/kazunorikimura/repository/react-ts-starter
npm ERR! command failed
npm ERR! command sh -c run-p -l -c --aggregate-output lint:*
npm ERR! A complete log of this run can be found in:
npm ERR! /Users/kazunorikimura/.npm/_logs/2021-02-22T02_12_46_586Z-debug.log
ESLint の警告は Storybook が初回生成した js ファイルばかりなので対応は保留。
Prettier のフォーマットエラーはまとめて修正する。
❯ npm run fix:prettier
> react-ts-starter@1.0.0 fix:prettier
> npm run lint:prettier -- --write
> react-ts-starter@1.0.0 lint:prettier
> prettier --check . "--write"
Checking formatting...
.storybook/main.js
.storybook/preview.js
.vscode/settings.json
stories/button.css
stories/Button.js
stories/Button.stories.js
stories/header.css
stories/Header.js
stories/Header.stories.js
stories/Introduction.stories.mdx
stories/page.css
stories/Page.js
stories/Page.stories.js
Code style issues fixed in the above file(s).
もう一度 Prettier の lint をして修正されているか確認。
❯ npm run lint:prettier
> react-ts-starter@1.0.0 lint:prettier
> prettier --check .
Checking formatting...
All matched files use Prettier code style!
buildテスト用のコンポーネントを作成
mkdir -p src/components
touch src/index.ts
touch src/components/Text.tsx
import React from 'react';
export interface TextProps {
value: string;
}
const Text: React.FC<TextProps> = ({ value }) => {
return <p>{value}</p>;
};
export default Text;
export * from './components/Text';
export { default as Text } from './components/Text';
storybook の main.js
を修正する。
冒頭でリンクを貼った参考サイトのコードが変なので、そこにリンクされている GitHub のソースコードから拝借。
react-component-library-starter/main.js at main · thepeaklab/react-component-library-starter
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),
},
},
};
``
先程作成した Text
コンポーネントの story
を作成する
mkdir stories/components
touch stories/components/Text.stories.tsx
import { Meta, Story } from '@storybook/react/types-6-0';
import React from 'react';
import Text, { TextProps } from '../../src/components/Text';
export default {
title: 'components/Text',
component: Text,
} as Meta;
const Template: Story<TextProps> = (args) => <Text {...args} />;
export const Sample = Template.bind({});
Sample.args = {
value: 'My first storybook.',
};
Storybook を実行する
npm run storybook
追加したコンポーネントが表示された!
build してみる。
package.json に build コマンドを追加
{
"name": "react-ts-starter",
"version": "1.0.0",
"description": "",
"main": "build/index.js",
"module": "build/index.es.js",
"scripts": {
+ "build": "rollup -c",
~~ 以下略 ~~
npm run build
build
フォルダにファイルが生成されることを確認する。
Kazunori-Kimura/react-typescript-component-starter: react component project starter kit.