Next.js + TypeScript + TailwindCSS + storybook + jest のテンプレートを作った話 後編
自己紹介
こんにちは、2022 年の秋から駆け出し web エンジニアの柿です。
現在は都内の SES 企業で働いております。
自分の担当領域は基本的にフロントエンドですが、バックエンドの開発を行うこともあります。
最初にプログラミングに興味を持ち始めたのは緊急事態宣言がきっかけでした。
遊び感覚で始めた Progate が楽しくて、いつの間にか仕事にしたいと思い始めて今に至ります。
拙い文章ではありますが頑張って書いていこうと思うので、お読みいただけると幸いです!
はじめに
こちらの記事は後編です!
前編の記事もよろしければ御覧ください!
簡単に前編の内容をまとめると、
「Next.js の開発環境を毎回構築するのめんどい」
「そうだ、テンプレ作ったろ」
です。
こちらが私が作成したテンプレートです。
使用技術はざっと、
ツール名 | 説明 |
---|---|
Next.js v13 | React フレームワークで、app ディレクトリは不採用 |
TypeScript v5 | JavaScript の上位互換言語 |
TailwindCSS v3 | CSS フレームワーク。daisyUI というフレームワークを使用 |
axios | API を叩くためのライブラリ |
Recoil | React 状態管理ライブラリ |
Prettier | コードフォーマッター |
ESLint | コード書き方をチェックするツール |
storybook v7 | コンポーネント単位での UI デザインを確認できるツール |
Jest | JavaScript のテストフレームワーク |
Husky | コミットやプッシュ時に任意のコマンドを自動実行できるツール |
hygen | コンポーネントジェネレーター |
Volta | Node のバージョン管理ツール |
こんな感じですね。
今回は Volta が導入済みであるという前提で作成していきます。
まだの方はこちらの記事を参考にしていただければなと思います。
別に Volta 使わなくていい人はそのへん読み飛ばして頂いたりして構いません。
ご自身の好きなように環境構築していただければと思います!
では早速作成していきましょう!
なお今回は yarn
で作成していきます。
npm
で作成したい方は適宜読み替えてください。
create next-app でプロジェクトを作成
まずはプロジェクトを作成します。
今回の NextJS のバージョンは 13 ですが、ベータ版の app ディレクトリは採用していません。
yarn create next-app
すると、プロジェクト名を聞かれるので、適当に入力してください。
今回は nextjs-template
とします。
yarn create next-app
yarn create v1.22.19
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
[3/4] 🔗 Linking dependencies...
[4/4] 🔨 Building fresh packages...
success Installed "create-next-app@13.3.1" with binaries:
- create-next-app
✔ What is your project named? … nextjs-template
✔ Would you like to use TypeScript with this project? … No / Yes # Yes
✔ Would you like to use ESLint with this project? … No / Yes # Yes
✔ Would you like to use Tailwind CSS with this project? … No / Yes # Yes
✔ Would you like to use `src/` directory with this project? … No / Yes # Yes
✔ Would you like to use experimental `app/` directory with this project? … No / Yes # No
✔ What import alias would you like configured? … @/* # そのままEnter
これ今知ったんですが、TailwindCSS 使うかどうか聞かれるようになったんですね、、笑
さて、プロジェクトが作成されたら、作成したプロジェクトに移動します。
cd nextjs-template
試しに yarn dev
で起動してみましょう。
yarn dev
にアクセスして見てください。おそらくページが表示できているのではないかと思います!
ここから先は必要なことを順不同で行っていきます。
不要なファイルを削除
まずは不要なファイルを削除していきます。
src/pages/api/hello.ts
は不要なので削除します。
rm src/pages/api/hello.ts
としてファイルを削除でもいいですし、ディレクトリごと消しちゃってもいいです。
Node と yarn のバージョンを固定する
Volta を使って固定していきます。
volta pin node@18
volta pin yarn@1
とすると、
{
"volta": {
"node": "18.15.0",
"yarn": "1.22.19"
}
}
という用になっていると思います。
axios 導入
次に axios を導入します。
yarn add axios
Recoil 導入
次に Recoil を導入します。
yarn add recoil
src/pages/_app.tsx
に追記します
import type { AppProps } from 'next/app';
import { RecoilRoot } from 'recoil';
import '@/styles/globals.css';
const App = ({ Component, pageProps }: AppProps) => {
return (
<RecoilRoot>
<Component {...pageProps} />
</RecoilRoot>
);
};
export default App;
このようにすることでアプリ全体で使えるようになります。
テスト環境の構築
次にテスト環境を構築していきます。
yarn add -D jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event @types/jest @types/testing-library__jest-dom eslint-plugin-jest ts-jest
インストールが終わったら、jest.config.js
と jest.setup.ts
を作成します。
touch jest.config.js && touch jest.setup.ts
そして、以下の内容を記述します。
import '@testing-library/jest-dom';
const nextJest = require('next/jest');
const createJestConfig = nextJest({
dir: './',
});
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
testEnvironment: 'jest-environment-jsdom',
};
module.exports = createJestConfig(customJestConfig);
そして、package.json
の scripts に test を追加します。
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "jest",
"test:watch": "jest --watch",
"test:watchAll": "jest --watchAll"
},
}
tsconfig.ts
にも以下の内容を追記しておきましょう
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.js",
"**/*.jsx",
"**/*.ts",
"**/*.tsx",
"jest.config.js"
],
"exclude": ["node_modules"]
}
storybook 導入
次に storybook を導入します。
yarn add storybook init
yarn add -D @storybook/addon-essentials @storybook/addon-interactions @storybook/addon-links @storybook/blocks @storybook/addon-styling @storybook/nextjs @storybook/react @storybook/testing-library eslint-plugin-storybook
インストールが完了したら、package.json
の scripts に storybook を追加します。
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "jest",
"test:watch": "jest --watch",
"test:watchAll": "jest --watchAll",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
}
storybook の設定ファイルを作成しましょう。
npx sb init
実行が終了すれば、.storybook
ディレクトリが作成されているはずです。
そしてその中にmain.ts
とpreview.ts
が作成されていれば OK です!
下記のようにしてください。
import type { StorybookConfig } from '@storybook/nextjs';
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-styling',
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/nextjs',
options: {},
},
docs: {
autodocs: 'tag',
},
};
export default config;
storybook v7.0.5 のときの話ですが、
storybook に tailwindCSS を適用させるには addons に'@storybook/addon-styling'
が必要になります。
以前は
addons: [
{
name: '@storybook/addon-postcss',
options: {
postcssLoaderOptions: {
implementation: require('postcss'),
},
},
},
];
という感じだったらしいんですけど、前者に変更となりました。
続けて、
import type { Preview } from '@storybook/react';
const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
};
export default preview;
あとはsrc
ディレクトリに stories
というディレクトリが作成されていると思います!
ではここで storybook を試しに起動してみましょう!
yarn storybook
とすると、
にアクセスできるはずです。こんな感じで使えます。
今はstories
ディレクトリのものを見ているような感じになります。
正常な起動が確認できたらそのディレクトリは基本的には使わないので、削除しちゃって大丈夫です!
husky と lint-staged 導入
まずはインストールしていきましょう
yarn add -D husky lint-staged
インストールが終わったら、commit 時に実行したいコマンドを設定していきましょう
ファイルを作成してください
touch .lintstagedrc.json
そして作成したファイルの中に以下を記述していきます
{
"**/*.{js,jsx,ts,tsx}": "npx eslint --fix",
"**/*.{js,jsx,ts,tsx,json}": "npx prettier --write",
"**/*.{spec,test}.{js,jsx,ts,tsx}": "npx jest"
}
整形後、テストするっていう流れにしています。
では次に git hooks を有効化するために以下を実行します。
yarn husky install
こうすると、.husky
ディレクトリが作成されたのではないでしょうか?
そうしたら、scripts を登録しましょう
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "jest",
"test:watch": "jest --watch",
"test:watchAll": "jest --watchAll",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"prepare": "husky install"
},
}
husky install
は GitHub リポジトリから clone して各パッケージをインストールするタイミングで、git hooks を有効化するために設定しています。
では最後に commit 前の hooks を作っていきましょう
yarn husky add .husky/pre-commit "yarn lint-staged"
こうすると、.husky
ディレクトリ配下に pre-commit というものが爆誕して、
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn lint-staged
という感じになっているのではと思います。
これで設定は OK です!
TailwindCSS 導入
インストールした時に勝手にやってくれていますが、いくつか手を加えましょう!
まずインストールします
yarn add -D prettier-plugin-tailwindcss
後ほど導入します。
では、next.config.js
に手を加えます
/** @type {import('next').NextConfig} */
const nextConfig = {
swcMinify: true,
reactStrictMode: true,
}
module.exports = nextConfig
postcss.config.js
に手を加えます
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'postcss-nested': {},
},
};
tailwind.config.js
に手を加えます
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
}
tsconfig.json
に手を加えます
{
"include": [
"next-env.d.ts",
"**/*.js",
"**/*.jsx",
"**/*.ts",
"**/*.tsx",
"postcss.config.js",
"jest.config.js"
],
}
そうしましたら、src/styles/global.css
を書き換えます。
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
この 3 行で終了です。
daisyUI
では daisyUI を導入します。
yarn add daisyui
tailwind.config.js
に手を加えます
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [require('daisyui')],
}
これで終了です。
hygen 導入
まずはインストールをしていきましょう
yarn add -D hygen
scripts
にも登録します。
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "jest",
"test:watch": "jest --watch",
"test:watchAll": "jest --watchAll",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"prepare": "husky install",
"gen": "hygen generator components"
},
}
では早速テンプレートを作成していきます。
yarn hygen init self
そうすると、
_template
ディレクトリが出来上がっているのではないでしょうか?
そうしたらディレクトリ構成はこんな感じにしてください。
_templates
└─ generator
└─ components
├─ component.ejs.t
├─ component.stories.ejs.t
├─ component.test.ejs.t
└─ prompt.js
では最初は、CLI での対話をどんなものにするのかを決めるため、prompt.js
を編集しましょう。
module.exports = [
{
type: 'select',
name: 'atomic',
message: 'select directory',
choices: ['atoms', 'molecules', 'organisms', 'templates'],
},
{
type: 'input',
name: 'component_name',
message: 'input component name',
validate: input => input !== '',
},
];
- type: 自分が入力する input や選択型の select など
- name: input や select で入力した値が入る変数名
- message: 対話に出てくるメッセージ
という感じになっているので、そのように設定しています。
- 1 つ目の質問: どのディレクトリにする?
- 2 つ目の質問: コンポーネントの名前は?
って感じです。
input
に関しては必須入力としたいので、バリデーションかけてます。
それでは、
- コンポーネントファイル
- テスト用ファイル
- storybook 用ファイル
の雛形を作成していきます。
---
to: src/components/<%= atomic %>/<%= h.changeCase.pascal(component_name) %>/index.tsx
---
import { FC } from 'react';
export const <%= h.changeCase.pascal(component_name) %>: FC = () => {
return (
<>
<h1><%= h.changeCase.pascal(component_name) %></h1>
</>
);
};
---
to: src/components/<%= atomic %>/<%= h.changeCase.pascal(component_name) %>/index.stories.tsx
---
import type { Meta, StoryObj } from '@storybook/react';
import { <%= h.changeCase.pascal(component_name) %> } from '.';
const meta = {
title: 'components/<%= atomic %>/<%= h.changeCase.pascal(component_name) %>',
component: <%= h.changeCase.pascal(component_name) %>,
parameters: {},
args: {},
} satisfies Meta<typeof <%= h.changeCase.pascal(component_name) %>>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
primary: true,
},
};
---
to: src/components/<%= atomic %>/<%= h.changeCase.pascal(component_name) %>/index.test.tsx
---
import { render } from '@testing-library/react';
import { <%= h.changeCase.pascal(component_name) %> } from '.';
describe('<%= atomic %>/<%= h.changeCase.pascal(component_name) %>', () => {
it('renders correctly', () => {
const { container } = render(<<%= h.changeCase.pascal(component_name) %> />);
expect(container).toMatchSnapshot();
});
});
これで雛形ができました。
これは一例なので、皆さんが好きなように作っていただければと思います。
では実際に作成してみましょう!
yarn gen
どうでしょう?ご自身が設定された対話が出てきて、終わったらコンポーネント作成されたかと思います!
これでファイル作成の手間がかなり削減できたのではないかと思います。
prettier と eslint の設定
まずはイントールをしていきましょう
yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-prettier eslint-plugin-import eslint-plugin-unused-imports prettier stylelint-config-prettier
一応 script も登録してきましょう
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:js": "eslint --fix '**/*.@(js|ts|tsx)'",
"format": "prettier --ignore-path .gitignore --write .",
"test": "jest",
"test:watch": "jest --watch",
"test:watchAll": "jest --watchAll",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"prepare": "husky install",
"gen": "hygen generator components"
},
}
次に設定ファイルを作成していきます。
touch .prettierrc.json
mkdir .vscode && touch .vscode/setting.json
設定ファイルに追記していきます。
{
"env": {
"browser": true,
"es6": true,
"node": true,
"jest/globals": true
},
"plugins": ["unused-imports", "jest"],
"extends": [
"plugin:jest/recommended",
"plugin:jest/style",
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"next/core-web-vitals",
"plugin:storybook/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"rules": {
"jest/consistent-test-it": ["error", { "fn": "it" }],
"jest/require-top-level-describe": ["error"],
"no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"react/jsx-filename-extension": ["error", { "extensions": [".jsx", ".tsx"] }],
"react/jsx-props-no-spreading": "off",
"import/prefer-default-export": "off",
"react/function-component-definition": [
2,
{
"namedComponents": "arrow-function",
"unnamedComponents": "arrow-function"
}
],
"arrow-body-style": "off",
"import/extensions": [
"error",
"ignorePackages",
{
"js": "never",
"jsx": "never",
"ts": "never",
"tsx": "never"
}
],
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-empty-function": 0,
"react/prop-types": 0,
"react/react-in-jsx-scope": 0,
"no-empty-function": 0,
"@typescript-eslint/ban-ts-comment": 0
},
"overrides": [
{
"files": ["src/pages/**/*", "*.stories.{ts,tsx}"],
"rules": { "import/no-default-export": "off" }
}
]
}
rules 等はお好きなのを入れてください。
最初は空でもいいと思いますけどね。
ホントこの辺は十人十色だと思います。
次に prettier の設定ファイルを
{
"arrowParens": "avoid",
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"jsxSingleQuote": true,
"printWidth": 100,
"importOrder": [
"^(react(.*)/(.*)$)|^react$",
"<THIRD_PARTY_TS_TYPES>",
"<THIRD_PARTY_MODULES>",
"^types$",
"^@/(.*)$",
"^[./]"
]
}
Tailwind 導入の時にインストールしたプラグインもここで追加しましょう
touch prettier.config.js
module.exports = {
plugins: [require('prettier-plugin-tailwindcss')],
};
なんとなくこんな感じで。
vscode の設定も
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.addMissingImports": true,
"source.fixAll.eslint": true
}
}
こんな感じにしておきましょう。
まとめ
さて、これで設定が一通り終わりました。
漏れがあったらすみません、、。
ここで完成形を載せておきます。
うまくいかなかったらコピペして頂くか、リポジトリから clone してください。
package.json
{
"name": "nextjs-template",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:js": "eslint --fix '**/*.@(js|ts|tsx)'",
"format": "prettier --ignore-path .gitignore --write .",
"test": "jest",
"test:watch": "jest --watch",
"test:watchAll": "jest --watchAll",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"prepare": "husky install",
"gen": "hygen generator components"
},
"dependencies": {
"@types/node": "18.16.1",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.1",
"autoprefixer": "^10.4.14",
"axios": "^1.3.4",
"daisyui": "^2.51.5",
"eslint": "8.37.0",
"eslint-config-next": "13.2.4",
"init": "^0.1.2",
"next": "13.3.1",
"next-themes": "^0.2.1",
"postcss": "^8.4.21",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.8.0",
"recoil": "^0.7.7",
"storybook": "^7.0.5",
"tailwindcss": "^3.3.0",
"typescript": "5.0.3"
},
"volta": {
"node": "18.16.0",
"yarn": "1.22.19"
},
"devDependencies": {
"@storybook/addon-essentials": "^7.0.5",
"@storybook/addon-interactions": "^7.0.5",
"@storybook/addon-links": "^7.0.5",
"@storybook/addon-styling": "^1.0.1",
"@storybook/blocks": "^7.0.5",
"@storybook/nextjs": "^7.0.5",
"@storybook/react": "^7.0.5",
"@storybook/testing-library": "^0.1.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
"@types/jest": "^29.5.0",
"@types/testing-library__jest-dom": "^5.14.5",
"@typescript-eslint/eslint-plugin": "^5.57.0",
"@typescript-eslint/parser": "^5.57.0",
"eslint-config-prettier": "^8.7.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-storybook": "^0.6.11",
"eslint-plugin-unused-imports": "^2.0.0",
"husky": "^8.0.3",
"hygen": "^6.2.11",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"lint-staged": "^13.2.0",
"prettier": "^2.8.4",
"prettier-plugin-tailwindcss": "^0.2.5",
"stylelint-config-prettier": "^9.0.5",
"ts-jest": "^29.1.0"
}
}
.eslintrc.json
{
"env": {
"browser": true,
"es6": true,
"node": true,
"jest/globals": true
},
"plugins": ["unused-imports", "jest"],
"extends": [
"plugin:jest/recommended",
"plugin:jest/style",
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"next/core-web-vitals",
"plugin:storybook/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"rules": {
"jest/consistent-test-it": ["error", { "fn": "it" }],
"jest/require-top-level-describe": ["error"],
"no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"react/jsx-filename-extension": ["error", { "extensions": [".jsx", ".tsx"] }],
"react/jsx-props-no-spreading": "off",
"import/prefer-default-export": "off",
"react/function-component-definition": [
2,
{
"namedComponents": "arrow-function",
"unnamedComponents": "arrow-function"
}
],
"arrow-body-style": "off",
"import/extensions": [
"error",
"ignorePackages",
{
"js": "never",
"jsx": "never",
"ts": "never",
"tsx": "never"
}
],
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-empty-function": 0,
"react/prop-types": 0,
"react/react-in-jsx-scope": 0,
"no-empty-function": 0,
"@typescript-eslint/ban-ts-comment": 0
},
"overrides": [
{
"files": ["src/pages/**/*", "*.stories.{ts,tsx}"],
"rules": { "import/no-default-export": "off" }
}
]
}
.lintstagedrc.json
{
"**/*.{js,jsx,ts,tsx}": "npx eslint --fix",
"**/*.{js,jsx,ts,tsx,json}": "npx prettier --write",
"**/*.{spec,test}.{js,jsx,ts,tsx}": "npx jest"
}
.prettierrc.json
{
"arrowParens": "avoid",
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"jsxSingleQuote": true,
"printWidth": 100,
"importOrder": [
"^(react(.*)/(.*)$)|^react$",
"<THIRD_PARTY_TS_TYPES>",
"<THIRD_PARTY_MODULES>",
"^types$",
"^@/(.*)$",
"^[./]"
]
}
jest.config.js
// eslint-disable-next-line @typescript-eslint/no-var-requires
const nextJest = require('next/jest');
const createJestConfig = nextJest({
dir: './',
});
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
testEnvironment: 'jest-environment-jsdom',
};
module.exports = createJestConfig(customJestConfig);
jest.setup.ts
import '@testing-library/jest-dom';
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
swcMinify: true,
reactStrictMode: true,
};
module.exports = nextConfig;
postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'postcss-nested': {},
},
};
prettier.config.js
module.exports = {
plugins: [require('prettier-plugin-tailwindcss')],
};
tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
mode: 'jit',
content: ['./src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [require('daisyui')],
};
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.js",
"**/*.jsx",
"**/*.ts",
"**/*.tsx",
"postcss.config.js",
"jest.config.js"
],
"exclude": ["node_modules"]
}
template/generator/components/component.ejs.t
---
to: src/components/<%= atomic %>/<%= h.changeCase.pascal(component_name) %>/index.tsx
---
import { FC } from 'react';
export const <%= h.changeCase.pascal(component_name) %>: FC = () => {
return (
<>
<h1><%= h.changeCase.pascal(component_name) %></h1>
</>
);
};
template/generator/components/component.stories.ejs.t
---
to: src/components/<%= atomic %>/<%= h.changeCase.pascal(component_name) %>/index.stories.tsx
---
import type { Meta, StoryObj } from '@storybook/react';
import { <%= h.changeCase.pascal(component_name) %> } from '.';
const meta = {
title: 'components/<%= atomic %>/<%= h.changeCase.pascal(component_name) %>',
component: <%= h.changeCase.pascal(component_name) %>,
parameters: {},
args: {},
} satisfies Meta<typeof <%= h.changeCase.pascal(component_name) %>>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
primary: true,
},
};
template/generator/components/component.test.ejs.t
---
to: src/components/<%= atomic %>/<%= h.changeCase.pascal(component_name) %>/index.test.tsx
---
import { render } from '@testing-library/react';
import { <%= h.changeCase.pascal(component_name) %> } from '.';
describe('<%= atomic %>/<%= h.changeCase.pascal(component_name) %>', () => {
it('renders correctly', () => {
const { container } = render(<<%= h.changeCase.pascal(component_name) %> />);
expect(container).toMatchSnapshot();
});
});
template/generator/components/prompt.js
module.exports = [
{
type: 'select',
name: 'atomic',
message: 'select directory',
choices: ['atoms', 'molecules', 'organisms', 'templates'],
},
{
type: 'input',
name: 'component_name',
message: 'input component name',
validate: input => input !== '',
},
];
.husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn lint-staged
.storybook/main.js
import type { StorybookConfig } from '@storybook/nextjs';
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-styling',
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/nextjs',
options: {},
},
docs: {
autodocs: 'tag',
},
};
export default config;
.storybook/preview.js
import type { Preview } from '@storybook/react';
import '@/styles/globals.css';
const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
};
export default preview;
.vscode/setting.json
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.addMissingImports": true,
"source.fixAll.eslint": true
}
}
こんな感じでしょうか?
これも漏れていれば追記いたします!
皆さんの参考になった部分があれば幸いです。
では、良きカタカタライフを: )
Discussion