Open16
Nuxt3とStorybookの相性が悪いのでNext.jsに手をだす
問題
Nuxt3のauto importの影響でstorybookが非常に使いづらい
目的
いっそNextに手を出してみたらどうだろうか
参考
〜React & Next完全初心者による環境構築〜
Next
npx create-next-app@latest
Need to install the following packages:
create-next-app@latest
Ok to proceed? (y) y
✔ What is your project named? … next-template-v1
✔ Would you like to use TypeScript with this project? … No / Yes
✔ Would you like to use ESLint with this project? … No / Yes
✔ Would you like to use `src/` directory with this project? … No / Yes
✔ Would you like to use experimental `app/` directory with this project? … No / Yes
✔ What import alias would you like configured? … @/*
app/
directoryは最近できた機能らしい。特に使わないと思うのでスルー
.
├── .eslintrc.json
├── .gitignore
├── README.md
├── next-env.d.ts
├── next.config.js
├── package.json
├── public
│ ├── favicon.ico
│ ├── next.svg
│ ├── thirteen.svg
│ └── vercel.svg
├── src
│ ├── pages
│ │ ├── _app.tsx
│ │ ├── _document.tsx
│ │ ├── api
│ │ │ └── hello.ts
│ │ └── index.tsx
│ └── styles
│ ├── Home.module.css
│ └── globals.css
└── tsconfig.json
静的HTMLとして出力できるようにする
package.json
{
"name": "next-template-v1",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
+ "export": "next build && next export"
},
"dependencies": {
"@next/font": "13.1.4",
"@types/node": "18.11.18",
"@types/react": "18.0.27",
"@types/react-dom": "18.0.10",
"eslint": "8.32.0",
"eslint-config-next": "13.1.4",
"next": "13.1.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"typescript": "4.9.4"
}
}
Nuxtでいうgenerator
かな?
ESLint + Prettier
インストール
yarn add -D prettier eslint-config-prettier eslint-plugin-import @typescript-eslint/eslint-plugin @typescript-eslint/parser
設定ファイルの作成
.eslintrc.cjs
module.exports = {
root: true,
extends: [
'next/core-web-vitals',
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
plugins: ['@typescript-eslint'],
rules: {
/* typescript */
'no-restricted-imports': [
'error',
{
patterns: [
'../*',
'~/*',
'~~/*',
'./assets/*',
'./components/*',
'./pages/*',
'./plugins/*',
'./router/*',
'./hooks/*',
'./server/*',
'./store/*',
'./types/*',
'./utils/*',
'./libs/*',
'./*.vue',
],
},
],
'import/order': [
'error',
{
'groups': [
'builtin',
'external',
'parent',
'sibling',
'index',
'object',
'type',
],
'pathGroups': [
{
pattern: '{react,react-dom}',
group: 'builtin',
position: 'before',
},
{
pattern: '@src/**',
group: 'parent',
position: 'before',
},
],
'pathGroupsExcludedImportTypes': ['builtin'],
'alphabetize': {
order: 'asc',
},
'newlines-between': 'always',
},
],
'@typescript-eslint/consistent-type-imports': [
'error',
{ prefer: 'type-imports' },
],
},
};
.prettierrc
{
"singleQuote": true,
"semi": true,
"tabWidth": 2,
"quoteProps": "consistent",
"trailingComma": "es5",
"jsxSingleQuote": false,
"bracketSpacing": true,
"arrowParens": "always"
}
TypeScript
インストール
yarn add -D @tsconfig/strictest
tsconfig
の編集
tsconfig.json
{
+ "extends": "@tsconfig/strictest/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", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
TailwindCSS
参考
インストール
yarn add -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
tailwind.config.cjs
の編集
tailwind.config.cjs
/** @type {import('tailwindcss').Config} */
module.exports = {
- content: [],
+ content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {},
},
plugins: [],
}
globals.css
の編集
src/styles/globals.css
の中身を消去して、以下の通り編集する。なお、Home.module.css
は存在ごと消してOK
src/styles/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
.
├── .eslintrc.cjs
├── .gitignore
├── .prettierrc
├── README.md
├── next-env.d.ts
├── next.config.js
├── package.json
├── postcss.config.js
├── public
│ ├── favicon.ico
│ ├── next.svg
│ ├── thirteen.svg
│ └── vercel.svg
├── src
│ ├── pages
│ │ ├── _app.tsx
│ │ ├── _document.tsx
│ │ ├── api
│ │ │ └── hello.ts
│ │ └── index.tsx
│ └── styles
│ └── globals.css
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock
報告は上がっているっぽい
TailwindCSS用のESLintとPrettierの設定
インストール
yarn add -D eslint-plugin-tailwindcss prettier-plugin-tailwindcss
.eslintrc.cjs
の編集
.eslintrc.cjs
module.exports = {
root: true,
extends: [
'next/core-web-vitals',
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
+ 'plugin:tailwindcss/recommended',
'prettier',
],
- plugins: ['@typescript-eslint'],
+ plugins: ['@typescript-eslint', 'tailwindcss'],
rules: {
/* typescript */
'no-restricted-imports': [
'error',
{
patterns: [
'../*',
'~/*',
'~~/*',
'./assets/*',
'./components/*',
'./pages/*',
'./plugins/*',
'./router/*',
'./hooks/*',
'./server/*',
'./store/*',
'./types/*',
'./utils/*',
'./libs/*',
'./*.vue',
],
},
],
'import/order': [
'error',
{
'groups': [
'builtin',
'external',
'parent',
'sibling',
'index',
'object',
'type',
],
'pathGroups': [
{
pattern: '{react,react-dom}',
group: 'builtin',
position: 'before',
},
{
pattern: '@src/**',
group: 'parent',
position: 'before',
},
],
'pathGroupsExcludedImportTypes': ['builtin'],
'alphabetize': {
order: 'asc',
},
'newlines-between': 'always',
},
],
'@typescript-eslint/consistent-type-imports': [
'error',
{ prefer: 'type-imports' },
],
+
+ /* tailwindcss */
+ 'tailwindcss/no-custom-classname': [
+ 'warn',
+ {
+ config: 'tailwind.config.cjs',
+ },
+ ],
+ 'tailwindcss/classnames-order': 'off',
},
};
daisyUI
インストール
yarn add daisyui
型定義の追加
src/types/global.d.ts
declare module 'daisyui';
tailwind.config.cjs
の編集
tailwind.config.cjs
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {},
},
- plugins: [],
+ plugins: [require('daisyui')],
};
.
├── .eslintrc.cjs
├── .gitignore
├── .prettierrc
├── README.md
├── next-env.d.ts
├── next.config.js
├── package.json
├── postcss.config.cjs
├── public
│ ├── favicon.ico
│ ├── next.svg
│ ├── thirteen.svg
│ └── vercel.svg
├── src
│ ├── pages
│ │ ├── _app.tsx
│ │ ├── _document.tsx
│ │ ├── api
│ │ │ └── hello.ts
│ │ └── index.tsx
│ ├── styles
│ │ └── globals.css
│ └── types
│ └── global.d.ts
├── tailwind.config.cjs
├── tsconfig.json
└── yarn.lock
Storybook
インストール
npx sb init --builder webpack5
✔ Do you want to run the 'eslintPlugin' migration on your project? … yes
ESLintの設定
.eslintrc.cjs
module.exports = {
root: true,
extends: [
'next/core-web-vitals',
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:tailwindcss/recommended',
+ 'plugin:storybook/recommended',
'prettier',
],
plugins: ['@typescript-eslint', 'tailwindcss'],
rules: {
/* typescript */
'no-restricted-imports': [
'error',
{
patterns: [
'../*',
'~/*',
'~~/*',
'./assets/*',
'./components/*',
'./pages/*',
'./plugins/*',
'./router/*',
'./hooks/*',
'./server/*',
'./store/*',
'./types/*',
'./utils/*',
'./libs/*',
'./*.vue',
],
},
],
'import/order': [
'error',
{
'groups': [
'builtin',
'external',
'parent',
'sibling',
'index',
'object',
'type',
],
'pathGroups': [
{
pattern: '{react,react-dom}',
group: 'builtin',
position: 'before',
},
{
pattern: '@src/**',
group: 'parent',
position: 'before',
},
],
'pathGroupsExcludedImportTypes': ['builtin'],
'alphabetize': {
order: 'asc',
},
'newlines-between': 'always',
},
],
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
},
],
/* tailwindcss */
'tailwindcss/no-custom-classname': [
'warn',
{
config: 'tailwind.config.cjs',
},
],
'tailwindcss/classnames-order': 'off',
},
};
staticDirs
の追加
.storybook/main.js
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
framework: '@storybook/react',
core: {
builder: '@storybook/builder-webpack5',
},
+ staticDirs: ['../public'],
};
next/router
に対応させる
yarn add -D storybook-addon-next-router
.storybook/main.js
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
+ 'storybook-addon-next-router',
],
framework: '@storybook/react',
core: {
builder: '@storybook/builder-webpack5',
},
staticDirs: ['../public'],
};
.storybook/preview.js
+import { RouterContext } from 'next/dist/shared/lib/router-context';
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
+ nextRouter: {
+ Provider: RouterContext.Provider,
+ },
};
aliasの設定
.storybook/main.js
+const path = require('path');
+const rootPath = path.resolve(__dirname, '../src/');
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'storybook-addon-next-router',
],
framework: '@storybook/react',
core: {
builder: '@storybook/builder-webpack5',
},
staticDirs: ['../public'],
+ webpackFinal: async (config, { configType }) => {
+ config.resolve.alias['@'] = rootPath;
+ return config;
+ },
};
TailwindCSSに対応させる
yarn add -D @storybook/addon-postcss
.storybook/main.js
const path = require('path');
const rootPath = path.resolve(__dirname, '../src/');
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'storybook-addon-next-router',
+ {
+ name: '@storybook/addon-postcss',
+ options: {
+ postcssLoaderOptions: {
+ implementation: require('postcss'),
+ },
+ },
+ },
],
framework: '@storybook/react',
core: {
builder: '@storybook/builder-webpack5',
},
staticDirs: ['../public'],
webpackFinal: async (config, { configType }) => {
config.resolve.alias['@'] = rootPath;
return config;
},
};
.storybook/preview.js
+import '@/styles/globals.css';
import { RouterContext } from 'next/dist/shared/lib/router-context';
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
nextRouter: {
Provider: RouterContext.Provider,
},
};
Vitest
参考
インストール
yarn add -D vitest jsdom @vitejs/plugin-react @testing-library/react @testing-library/jest-dom @testing-library/user-event @types/testing-library__user-event
package.json
の編集
package.json
{
"name": "next-template-v1",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"export": "next build && next export",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook",
+ "test": "vitest",
+ "coverage": "vitest run --coverage"
},
...
}
setup.ts
の作成
src/__tests__/setup.ts
import '@testing-library/jest-dom';
vitest.config.ts
の作成
vitest.config.ts
/// <reference types="vitest" />
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/__tests__/setup.ts',
},
});
aliasに対応させる
vitest.config.ts
/// <reference types="vitest" />
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/__tests__/setup.ts',
},
+ resolve: {
+ alias: {
+ '@': '/src',
+ },
+ },
});
.
├── .eslintrc.cjs
├── .gitignore
├── .prettierrc
├── .storybook
│ ├── main.js
│ └── preview.js
├── README.md
├── next-env.d.ts
├── next.config.js
├── package.json
├── postcss.config.cjs
├── public
│ ├── favicon.ico
│ ├── next.svg
│ ├── thirteen.svg
│ └── vercel.svg
├── src
│ ├── __tests__
│ │ └── setup.ts
│ ├── pages
│ │ ├── _app.tsx
│ │ ├── _document.tsx
│ │ ├── api
│ │ │ └── hello.ts
│ │ └── index.tsx
│ ├── styles
│ │ └── globals.css
│ └── types
│ └── global.d.ts
├── tailwind.config.cjs
├── tsconfig.json
├── vitest.config.ts
└── yarn.lock
Template Repository
Vitest用のESLintの設定
インストール
yarn add -D eslint-plugin-vitest
.eslintrc.cjs
の編集
.eslintrc.cjs
module.exports = {
root: true,
extends: [
'next/core-web-vitals',
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:tailwindcss/recommended',
'plugin:storybook/recommended',
'prettier',
],
- plugins: ['@typescript-eslint', 'tailwindcss'],
+ plugins: ['@typescript-eslint', 'tailwindcss', 'vitest'],
rules: {
/* typescript */
'no-restricted-imports': [
'error',
{
patterns: [
'../*',
'~/*',
'~~/*',
'./assets/*',
'./components/*',
'./pages/*',
'./plugins/*',
'./router/*',
'./hooks/*',
'./server/*',
'./store/*',
'./types/*',
'./utils/*',
'./libs/*',
'./*.vue',
],
},
],
'import/order': [
'error',
{
'groups': [
'builtin',
'external',
'parent',
'sibling',
'index',
'object',
'type',
],
'pathGroups': [
{
pattern: '{react,react-dom}',
group: 'builtin',
position: 'before',
},
{
pattern: '@src/**',
group: 'parent',
position: 'before',
},
],
'pathGroupsExcludedImportTypes': ['builtin'],
'alphabetize': {
order: 'asc',
},
'newlines-between': 'always',
},
],
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
},
],
/* tailwindcss */
'tailwindcss/no-custom-classname': [
'warn',
{
config: 'tailwind.config.cjs',
},
],
'tailwindcss/classnames-order': 'off',
+ /* vitest */
+ 'vitest/consistent-test-it': [
+ 'error',
+ {
+ fn: 'test',
+ },
+ ],
+ 'vitest/expect-expect': 'warn',
+ 'vitest/lower-case-title': 'off',
+ 'vitest/max-nested-describe': [
+ 'error',
+ {
+ max: 2,
+ },
+ ],
+ 'vitest/no-conditional-tests': 'error',
+ 'vitest/no-focused-tests': 'warn',
+ 'vitest/no-identical-title': 'error',
+ 'vitest/no-skipped-tests': 'warn',
},
};