Closed31

【失敗】React + TypeScript + Storybook のプロジェクト作成

ピン留めされたアイテム
Kazunori KimuraKazunori Kimura

【結論】Storybook v6.1 は WebPack 5 に対応していないため、この手順では環境構築できません!

TypeError: Cannot read property 'createSnapshot' of undefined · Issue #13332 · storybookjs/storybook
https://github.com/storybookjs/storybook/issues/13332

Webpack 5 upgrade · Issue #9216 · storybookjs/storybook
https://github.com/storybookjs/storybook/issues/9216

#9216 が close したらもう一度やってみる。

Kazunori KimuraKazunori Kimura

コンポーネントをガッツリ作る必要が出てきたので、この際 Storybook にも入門してみる。

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

環境など:

  • Mac
  • Node v15
  • npm v7
  • TypeScript v4
  • React v17
  • Storybook v6
Kazunori KimuraKazunori Kimura

プロジェクトを作成して npm, git の初期化。
ついでに .gitignorereadme.md の空ファイルを作成しておく。

mkdir react-ts-component-base
cd react-ts-component-base
npm init -y
git init
touch .gitignore
touch readme.md
Kazunori KimuraKazunori Kimura

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

Kazunori KimuraKazunori Kimura

React, TypeScript, WebPack のインストール

npm install --save-dev react react-dom webpack webpack-cli typescript ts-loader @types/react @types/react-dom
Kazunori KimuraKazunori Kimura

TypeScript をトランスパイルして dist フォルダに書き出す

webpack.config.js
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/index.ts',
  output: {
    filename: 'index.js',
    path: path.join(__dirname, 'dist'),
    libraryTarget: 'commonjs2',
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        exclude: /node_modeules/,
        use: [
          'ts-loader',
        ],
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx'],
  },
}
Kazunori KimuraKazunori Kimura

TypeScript の設定。
src フォルダを対象とする。"declaration": true を忘れずつける。

tsconfig.json
{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "noImplicitAny": true,
    "esModuleInterop": true,
    "module": "esnext",
    "target": "es6",
    "jsx": "react",
    "declaration": true
  },
  "include": [
      "src"
  ]
}

Kazunori KimuraKazunori Kimura
package.json
{
  "name": "react-ts-component-base",
  "version": "1.0.0",
  "description": "",
-  "main": "index.js",
+  "main": "dist/index.js",
  "scripts": {
+    "build": "webpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/react": "^17.0.2",
    "@types/react-dom": "^17.0.1",
    "react": "^17.0.1",
    "react-dom": "^17.0.1"
    "ts-loader": "^8.0.17",
    "typescript": "^4.1.5",
    "webpack": "^5.23.0",
    "webpack-cli": "^4.5.0"
-  }
+  },
+  "peerDependencies": {
+    "react": "^17.0.1",
+    "react-dom": "^17.0.1"
+  }
}

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

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

Kazunori KimuraKazunori Kimura

トランスパイルしてみる

❯ npm run build

> react-ts-component-base@1.0.0 build
> webpack

asset index.js 80.9 KiB [emitted] (name: main)
asset components/Text.d.ts 139 bytes [emitted]
asset index.d.ts 88 bytes [emitted]
runtime modules 670 bytes 3 modules
cacheable modules 72.9 KiB
  modules by path ./node_modules/ 72.7 KiB
    ./node_modules/react/index.js 190 bytes [built] [code generated]
    ./node_modules/react/cjs/react.development.js 70.5 KiB [built] [code generated]
    ./node_modules/object-assign/index.js 2.06 KiB [built] [code generated]
  modules by path ./src/ 219 bytes
    ./src/index.ts 88 bytes [built] [code generated]
    ./src/components/Text.tsx 131 bytes [built] [code generated]
webpack 5.23.0 compiled successfully in 1749 ms

/dist にファイルが生成されていることを確認する。
.d.ts ファイルも存在することを忘れず確認。

Kazunori KimuraKazunori Kimura

.gitignore/dist を追加しておく。

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

+/dist

Kazunori KimuraKazunori Kimura

ESLint, Prettier のインストール

npm install --save-dev eslint eslint-config-react-app eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/eslint-plugin @typescript-eslint/parser prettier eslint-config-prettier
Kazunori KimuraKazunori Kimura

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

touch .eslintignore .prettierignore .prettierrc.js
Kazunori KimuraKazunori Kimura

.eslintignore, .prettierignore では dist, node_modules を除外する
(ESLint はデフォルトで node_modules が除外されている?)

.eslintignore
dist/
.prettierignore
node_modules/
dist/
Kazunori KimuraKazunori Kimura

.prettierrc.js の設定は僕の好みに合わせて。
Prettier デフォルトで問題無い人は不要。

.prettierrc.js
module.exports = {
    trailingComma: 'es5',
    tabWidth: 4,
    printWidth: 100,
    singleQuote: true,
};

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

src/index.ts を開いて Command + S してみる。
import の順番が変わることが確認できる。

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

src/components/Text.tsx を開いて Command + S する。
セミコロンの抜けが補完される。

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;

Kazunori KimuraKazunori Kimura

CLI で ESLint、Prettier できるように npm script を設定しておく。
まとめてコマンド実行できるように npm-run-all を使用する。

npm install --save-dev npm-run-all
Kazunori KimuraKazunori Kimura

あ、package.json の説明が抜けた。

先に package.json に ESLint の設定を追加する。

これは ESLint, Prettier インストール直後に行う。

package.json
{
    "name": "react-ts-component-base",
    "version": "1.0.0",
    "description": "",
    "main": "dist/index.js",
    "scripts": {
        "build": "webpack",
+        "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"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
        "@types/react": "^17.0.2",
        "@types/react-dom": "^17.0.1",
        "@typescript-eslint/eslint-plugin": "^4.15.1",
        "@typescript-eslint/parser": "^4.15.1",
        "eslint": "^7.20.0",
        "eslint-config-prettier": "^7.2.0",
        "eslint-config-react-app": "^6.0.0",
        "eslint-plugin-import": "^2.22.1",
        "eslint-plugin-jsx-a11y": "^6.4.1",
        "eslint-plugin-react": "^7.22.0",
        "eslint-plugin-react-hooks": "^4.2.0",
        "npm-run-all": "^4.1.5",
        "prettier": "^2.2.1",
        "ts-loader": "^8.0.17",
        "typescript": "^4.1.5",
        "webpack": "^5.23.0",
        "webpack-cli": "^4.5.0"
    },
    "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",
+           "plugin:import/errors",
+           "plugin:import/warnings",
+           "plugin:import/typescript",
+           "prettier"
+       ]
+   }
}

Kazunori KimuraKazunori Kimura

lint を実行してみる

❯ npm run lint

> react-ts-component-base@1.0.0 lint
> run-p -l -c --aggregate-output lint:*

~~~中略~~~

[lint:eslint  ] /Users/kazunorikimura/repository/react-ts-component-base/webpack.config.js
[lint:eslint  ]   1:14  error  Require statement not part of import statement  @typescript-eslint/no-var-requires
[lint:eslint  ] 
[lint:eslint  ] ✖ 1 problem (1 error, 0 warnings)
[lint:eslint  ] 
ERROR: "lint:eslint" exited with 1.
npm ERR! code 1
npm ERR! path /Users/kazunorikimura/repository/react-ts-component-base
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-19T15_57_44_639Z-debug.log

webpack.config.js が引っかかった。
const path = require('path'); が気に入らない模様。
webpack.config.js はビルド設定なのであまりこだわる必要はない。ここはサクっと ESLint の指摘を無視する。

webpack.config.js
+/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');

module.exports = {
    mode: 'development',
    entry: './src/index.ts',
    output: {
        filename: 'index.js',
        path: path.join(__dirname, 'dist'),
        libraryTarget: 'commonjs2',
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                exclude: /node_modeules/,
                use: ['ts-loader'],
            },
        ],
    },
    resolve: {
        extensions: ['.ts', '.tsx', '.js', '.jsx'],
    },
};

Kazunori KimuraKazunori Kimura

再度 lint を実行。

❯ npm run lint

> react-ts-component-base@1.0.0 lint
> run-p -l -c --aggregate-output lint:*

[lint:prettier] 
[lint:prettier] > react-ts-component-base@1.0.0 lint:prettier
[lint:prettier] > prettier --check .
[lint:prettier] 
[lint:prettier] Checking formatting...
[lint:prettier] All matched files use Prettier code style!
[lint:eslint  ] 
[lint:eslint  ] > react-ts-component-base@1.0.0 lint:eslint
[lint:eslint  ] > eslint .
[lint:eslint  ] 

エラーがなくなった。

Kazunori KimuraKazunori Kimura

2021-02-20 現在、npm v7 で npx sb init すると依存関係のチェックでエラーとなり、インストールが中断される。

npx --legacy-peer-deps sb init とするとインストールが正常終了した。

❯ npx --legacy-peer-deps sb init

 sb init - the simplest way to add a Storybook to your project. 

 • Detecting project type. ✓
 • Adding Storybook support to your "React" library⸨░░░░░░░░░░░░░░░░░░⸩ ⠹ reify:npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
npm WARN deprecated fsevents@1.2.13: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.
npm WARN deprecated chokidar@2.1.8: Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.

added 1320 packages, removed 7 packages, and audited 1718 packages in 35s

180 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
. ✓
 • Preparing to install dependencies. ✓


 • Installing dependencies⸨░░░░░░░░░░░░░░░░░░⸩ ⠹ reify: timing arborist:ctor Com
up to date, audited 1718 packages in 2s

180 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
. ✓

To run your Storybook, type:

   npm run storybook 

For more information visit: https://storybook.js.org
Kazunori KimuraKazunori Kimura

試しに src/stories/Button.stories.tsx を開いてみると、from '@storybook/react/types-6-0'; が解決できない、とエラーになっていた。

VSCode に表示されているエラーメッセージに従って、tsconfig.json の内容を修正する。

tsconfig.json
{
    "compilerOptions": {
        "outDir": "./dist/",
        "sourceMap": true,
        "noImplicitAny": true,
        "esModuleInterop": true,
        "module": "esnext",
        "target": "es6",
        "jsx": "react",
-        "declaration": true
+        "declaration": true,
+        "moduleResolution": "Node"
    },
    "include": ["src"]
}

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