Next.js + TypeScript + Tailwind + Storybook プロジェクトセットアップ
はじめに
以下の設定を適応したプロジェクトセットアップを行います。
- Next.js
- TypeScript
- Tailwind
- Storybook
- ESLint
- Prettier
- husky
セットアップ済みのプロジェクトはこちらのリポジトリから「Use this template」をクリックすると以下の手順をベースとしたリポジトリを新規作成することが出来ます。
動作環境
node, yarn はインストール済みとします。
$node -v
v12.16.3
$yarn -v
1.22.10
動作検証したMacOSのバージョンはこちらです。
$sw_vers
ProductName: Mac OS X
ProductVersion: 10.15.7
BuildVersion: 19H2
Next.js プロジェクト作成
まずはNext.jsのプロジェクトを作成します。
npx create-next-app nextjs-with-typescript-tailwind-storybook
このようなメッセージが出たら作成完了です。
作成が出来たらメッセージの通り立ち上げてみます。
cd nextjs-with-typescript-tailwind-storybook
yarn dev
http://localhost:3000 にアクセスすると下記ページが表示されます。
TypeScriptのセットアップ
tsconfig.jsonを作成します。
touch tsconfig.json
作成後、yarn devを実行すると下記のようなエラーメッセージが出るはずです。
It looks like you're trying to use TypeScript but do not have the required package(s) installed.
Please install typescript, @types/react, and @types/node by running:
yarn add --dev typescript @types/react @types/node
If you are not trying to use TypeScript, please remove the tsconfig.json file from your package root (and any TypeScript files in your pages directory).
指示に従い必要なパッケージを追加します。
yarn add --dev typescript @types/react @types/node
パッケージの追加が完了したら再度 yarn dev で立ち上げます。
するとtsconfig.jsonが下記のように設定されます。
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
またnext-env.d.tsが自動で生成されます。
/// <reference types="next" />
/// <reference types="next/types/global" />
既存のファイル拡張子を変更していきます
pages/_app.js -> pages/_app.tsx
pages/index.js -> pages/index.tsx
pages/api/hellow.js -> pages/api/hello.tsx
再度 yarn dev を実行し立ち上がれば TypeScript の導入は完了です。
Tailwind のセットアップ
Tailwind に必要なモジュールを追加します。
yarn add tailwindcss postcss autoprefixer
次に以下コマンドを実行します。
npx tailwindcss init -p
tailwind.config.js postcss.config.jsが作成されます
tailwindcss 2.0.2
✅ Created Tailwind config file: tailwind.config.js
✅ Created PostCSS config file: postcss.config.js
tailwind.config.js を変更します
module.exports = {
- purge: [],
+ purge: ['./pages/**/*.tsx', './components/**/*.tsx'],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}
globals.css を下記のように上書きします。
@tailwind base;
@tailwind components;
@tailwind utilities;
Tailwindが適応されているかindex.tsxの Welcome to Next.js の下に下記を追加し確認してみます。
<p className="text-3xl text-red-500 font-bold">Hello Tailwind</p>
このようにclassNameに指定したものが反映されていればTailwindセットアップは完了です
Storybook のセットアップ
次に Storybook を追加していきます。
npx sb init
モジュールが追加されたら立ち上げてみます。
yarn storybook
この時 PostCSS エラーが出るはずです。
ERROR in ./stories/button.css (./node_modules/css-loader/dist/cjs.js??ref--11-1!./node_modules/postcss-loader/src??ref--11-2!./stories/button.css)
Module build failed (from ./node_modules/postcss-loader/src/index.js):
Error: PostCSS plugin tailwindcss requires PostCSS 8.
Tailwind と Storybook の PostCSS依存関係を解消する必要があるので、
公式のドキュメント通りにTailwindでPostCSS 7を使用するように変更します。
yarn remove tailwindcss postcss autoprefixer
yarn add tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
再度 yarn storybook を行い http://localhost:6006/ にアクセスし、
storybookが立ち上がることを確認します。
componentsへの変更とTailwindの反映
ディレクトリをstoriesからcomponentsに変更します。
mv stories components
storybookで指定するディレクトリも変更します。
module.exports = {
"stories": [
- "../stories/**/*.stories.mdx",
- "../stories/**/*.stories.@(js|jsx|ts|tsx)"
+ "../components/**/*.stories.mdx",
+ "../components/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials"
]
}
tailwindを反映するようにstyles/globals.css をimportします。
+ import "../styles/globals.css"
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
}
まずはbuttonのみ作成したいため、Button.stories.tsx、Button.tsx以外のファイルを削除し、このような状態にします。
components/
├── Button.stories.tsx
└── button.tsx
storybookのサンプルとして提供されているbutton のstyleをtailwindに置き換えてみます。
独自のカラー指定とフォント指定はtailwind.jsonのそれぞれcolors, fontFamilyに記述ていきます。
module.exports = {
purge: ['./pages/**/*.tsx', './components/**/*.tsx'],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {
colors: {
blue: {
450: '#1ea7fd',
},
},
},
fontFamily: {
sans: ['Nunito Sans', 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'],
},
boxShadow: {
inner: 'rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset',
},
},
variants: {
extend: {},
},
plugins: [],
}
ButtonのstyleをTailwindに置き換えていきます
import React from 'react'
export interface ButtonProps {
primary?: boolean
backgroundColor?: string
size?: 'small' | 'medium' | 'large'
label: string
onClick?: () => void
}
export const Button: React.FC<ButtonProps> = ({
primary = false,
size = 'medium',
backgroundColor,
label,
...props
}) => {
const baseButton = 'rounded-full font-bold'
const sizeMode =
size === 'small'
? 'py-1.5 px-4 text-xs'
: size === 'medium'
? 'py-2 px-5 text-sm'
: size === 'large'
? 'py-3 px-6 text-base'
: ''
return primary ? (
<div>
<button
type="button"
className={`text-white bg-blue-450 ${baseButton} ${sizeMode}`}
{...props}
>
{label}
</button>
</div>
) : (
<button
type="button"
className={`text-gray-600 bg-transparent shadow-inner ${baseButton} ${sizeMode}`}
style={{ backgroundColor }}
{...props}
>
{label}
</button>
)
}
yarn storybook で立ち上げし、buttonのstyleがtailwindで適応されていることが確認出来ます。
ESLintのセットアップ
ESLint, Prettierに必要なパッケージを追加していきます。
yarn add --dev eslint prettier eslint-plugin-react eslint-config-prettier eslint-plugin-prettier @typescript-eslint/parser @typescript-eslint/eslint-plugin
dependenciesに追加され下記のようになります。
{
...
"dependencies": {
"autoprefixer": "^9",
"@storybook/react": "^6.1.11",
"@types/node": "^14.14.10",
"@types/react": "^17.0.0",
+ "@typescript-eslint/eslint-plugin": "^4.10.0",
+ "@typescript-eslint/parser": "^4.10.0",
"babel-loader": "^8.2.2",
+ "eslint": "^7.15.0",
+ "eslint-config-prettier": "^7.0.0",
+ "eslint-plugin-prettier": "^3.3.0",
+ "eslint-plugin-react": "^7.21.5",
+ "prettier": "^2.2.1",
"typescript": "^4.1.2"
}
}
eslintの設定ファイルとeslintignore を作成します。
touch .eslintrc.json .eslintignore
eslintrc内を下記のように設定していきます。
{
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:prettier/recommended",
"prettier/@typescript-eslint"
],
"settings": {
"react": {
"version": "latest"
}
},
"plugins": ["@typescript-eslint"],
"parser": "@typescript-eslint/parser",
"env": {
"browser": true,
"node": true,
"es6": true
},
"rules": {
"prettier/prettier": [2, { "singleQuote": true, "semi": false }],
"react/react-in-jsx-scope": 0,
"react/display-name": 0,
"react/prop-types": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-member-accessibility": 0,
"@typescript-eslint/indent": 0,
"@typescript-eslint/member-delimiter-style": 0,
"@typescript-eslint/no-explicit-any": 1,
"@typescript-eslint/no-var-requires": 2,
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-unused-vars": [
2,
{
"argsIgnorePattern": "^_"
}
],
"no-console": [
2,
{
"allow": ["warn", "error"]
}
]
}
}
eslintとprettierが競合しないようprettierに関連する extends は必ずextends配列内の最後に記述します。
rulesのprettier/prettierに関しては後述するvscodeの自動整形時にeslintとprettierが競合してしまうため記載しています。
rulesは開発規約や好みに合わせて設定します。
eslintで対象外にするファイルを設定します。
**/node_modules/*
**/out/*
**/.next/*
storybook-static
package.json にlintのscriptを追加します。
{
...
"scripts": {
"build": "next build",
"start": "next start",
"storybook": "start-storybook -p 6006",
- "build-storybook": "build-storybook"
+ "build-storybook": "build-storybook",
+ "lint": "eslint . --ext .ts,.js,.tsx,.jsx",
+ "lint:fix": "eslint --fix . --ext .ts,.js,.tsx,.jsx"
},
...
}
lintを実行してみます
yarn lint
たくさんエラーが出ると思いますので、fixしてみます
yarn lint:fix
Doneが出ればokです。
...
✨ Done in 2.25s.
Prettierのセットアップ
touch .prettierrc.json .prettierignore
{
"semi": false,
"singleQuote": true,
"bracketSpacing": true,
"tabWidth": 2,
"printWidth": 100
}
node_modules
.next
yarn.lock
package-lock.json
public
storybook-static
scriptsに下記を追加します。
...
"scripts":{
...
"format": "prettier --write ."
}
prettierを実行します。
yarn format
コードが整形されます。
yarn format
yarn run v1.22.10
$ prettier --write .
.eslintrc.json 33ms
.prettierrc.json 10ms
.vscode/settings.json 2ms
components/Button.stories.tsx 203ms
components/Button.tsx 40ms
package.json 5ms
pages/_app.tsx 7ms
pages/api/hello.tsx 9ms
pages/index.tsx 42ms
README.md 49ms
tsconfig.json 6ms
✨ Done in 0.83s.
VSCodeで保存時に自動整形
vscodeでsave時に自動整形が出来るように設定します。
VSCode拡張ツールのESLintが事前に必要なのでインストールします。
mkdir .vscode
touch ./.vscode/settings.json
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
エラーが検知されているファイルで保存すると、自動で整形されます。
husky のセットアップ
huskyとはgitのcommit時やpush時に特定のコマンドを実行してくれるツールです。
今回はcommit時に先ほど設定したeslintやprettierが走るように設定します。
yarn add --dev husky lint-staged
下記を追加
{
"scripts": {
...
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"yarn lint",
"yarn format"
]
},
...
}
コミット時にlint-stagedで設定したscriptsが走ります。
next-typescript-storybook-tailwind-sandbox (main=) $git commit -m "husky test"
husky > pre-commit (node v12.16.3)
✔ Preparing...
✔ Running tasks...
✔ Applying modifications...
✔ Cleaning up...
[main 015cc25] husky test
1 file changed, 1 insertion(+)
これでプロジェクトセットアップ完了です。
リファレンス
- https://nextjs.org/learn/basics/create-nextjs-app
- https://tailwindcss.com/docs/guides/nextjs
- https://storybook.js.org/docs/react/get-started/install
- https://github.com/vercel/next.js/tree/master/examples
- https://eslint.org/docs/user-guide/configuring#configuring-rules
- https://tech.ga-tech.co.jp/entry/2020/01/refactoring-type-safety-with-eslint
- https://medium.com/better-programming/start-a-component-library-with-storybook-tailwind-css-and-typescript-ebaffc33d098
- https://github.com/prettier/prettier/issues/2280
Discussion