Closed23

React+TypeScript+Storybook+Rollupのプロジェクト作成

Kazunori KimuraKazunori Kimura

コンポーネントをガッツリ作る必要が出てきたので、この際 Storybook にも入門してみる。
WebPack v5 でトランスパイルしようとしたところ、Storybook の内部で使用している WebPack のバージョンと競合してしまったため、Rollup に変更する

https://zenn.dev/kazunori_kimura/scraps/522b0e4a28b1de

やること: React コンポーネントのプロジェクトを作成し、Storybook で動作確認する

環境など:

Mac
Node v15
npm v7
TypeScript v4
React v17
Storybook v6
Rollup v2.39

Kazunori KimuraKazunori Kimura

プロジェクトフォルダを作成して npm, git の初期化

mkdir project-dir
cd project-dir
npm init -y
git init
touch .gitignore
Kazunori KimuraKazunori Kimura

.gitignore の内容は create-react-app で生成されるものをコピペ。

.gitignore
# 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*

Kazunori KimuraKazunori Kimura

Storybook の導入。
npm v7 の場合、依存関係のチェックでエラーが出るので、--legacy-peer-deps のオプションを指定

npx --legacy-peer-deps sb init --type react
Kazunori KimuraKazunori Kimura

React と TypeScript をインストール

npm install --save-dev react react-dom typescript @types/react @types/react-dom
Kazunori KimuraKazunori Kimura

ここまでで 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/    │
│                                                   │
╰───────────────────────────────────────────────────╯

起動した!

Kazunori KimuraKazunori Kimura

Rollup およびそのプラグインをインストール

npm install rollup @rollup/plugin-commonjs @rollup/plugin-node-resolve rollup-plugin-peer-deps-external rollup-plugin-typescript2 -D
touch rollup.config.js
Kazunori KimuraKazunori Kimura

Rollupの設定ファイル。
ひとまず参考のサイトに記載されている内容をそのままコピペ。

rollup.config.js
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()]
};

Kazunori KimuraKazunori Kimura

rollup.config.js は出力先を package.json から取得しているので、package.json に設定を追加する。
ついでに peerDependencies を追記。

package.json
{
  "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"
  }
}

Kazunori KimuraKazunori Kimura

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
Kazunori KimuraKazunori Kimura

ESLint, Prettier, TypeScript の設定ファイル、VSCode の設定ファイルを作成

touch .eslintignore .prettierignore .prettierrc.js tsconfig.json
mkdir .vscode
touch .vscode/settings.json
Kazunori KimuraKazunori Kimura
.eslintignore
node_modules/
build/
.prettierignore
node_modules/
build/
.prettierrc.js
module.exports = {
    trailingComma: 'es5',
    tabWidth: 4,
    printWidth: 100,
    singleQuote: true,
};
tsconfig.json
{
    "compilerOptions": {
        "outDir": "./build/",
        "sourceMap": true,
        "noImplicitAny": true,
        "esModuleInterop": true,
        "module": "esnext",
        "target": "es6",
        "jsx": "react",
        "declaration": true,
        "moduleResolution": "Node"
    },
    "include": ["src"]
}

.vscode/settings.json
{
    "typescript.tsdk": "node_modules/typescript/lib",
    "editor.codeActionsOnSave": {
        "source.organizeImports": true,
        "source.fixAll.eslint": true
    },
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true
}
Kazunori KimuraKazunori Kimura

package.json に ESLint の設定を追加。
ついでに ESLint, Prettier を npm scripts から呼べるようにコマンド追加。

package.json
{
    "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"
+       ]
    }
}

Kazunori KimuraKazunori Kimura

reactjs - 'React' was used before it was defined - Stack Overflow
https://stackoverflow.com/questions/63818415/react-was-used-before-it-was-defined

ESLint のルール追加。

package.json
{
    "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"
+           ]
+       }
    }
}

Kazunori KimuraKazunori Kimura
❯ 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!
Kazunori KimuraKazunori Kimura

buildテスト用のコンポーネントを作成

mkdir -p src/components
touch src/index.ts
touch src/components/Text.tsx
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;

src/index.ts
export * from './components/Text';
export { default as Text } from './components/Text';

Kazunori KimuraKazunori Kimura

storybook の main.js を修正する。
冒頭でリンクを貼った参考サイトのコードが変なので、そこにリンクされている GitHub のソースコードから拝借。

react-component-library-starter/main.js at main · thepeaklab/react-component-library-starter
https://github.com/thepeaklab/react-component-library-starter/blob/main/.storybook/main.js

.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),
        },
    },
};

``
Kazunori KimuraKazunori Kimura

先程作成した Text コンポーネントの story を作成する

mkdir stories/components
touch stories/components/Text.stories.tsx
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.',
};

Kazunori KimuraKazunori Kimura

Storybook を実行する

npm run storybook

追加したコンポーネントが表示された!

storybook

Kazunori KimuraKazunori Kimura

build してみる。
package.json に build コマンドを追加

package.json
{
    "name": "react-ts-starter",
    "version": "1.0.0",
    "description": "",
    "main": "build/index.js",
    "module": "build/index.es.js",
    "scripts": {
+       "build": "rollup -c",

~~ 以下略 ~~
このスクラップは2021/02/22にクローズされました