Open4
Next.jsプロジェクト作成テンプレ
実際に作られたもの
chakra-uiを使ったテンプレからスタート
terminal
yarn create next-app --example with-chakra-ui-typescript <project-name>
tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": ["src/*"]
}
}
}
pre-commit
周りのライブラリを入れる
terminal
yarn add -D prettier \
eslint \
@typescript-eslint/parser \
@typescript-eslint/eslint-plugin \
eslint-config-prettier \
eslint-plugin-react \
eslint-plugin-react-hooks \
eslint-plugin-jsx-a11y \
eslint-plugin-import \
eslint-import-resolver-typescript \
husky \
lint-staged \
npm-run-all
yarn husky-init
.eslintrc
{
"root": true,
"env": {
"es6": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": ["./tsconfig.json"]
},
"settings": {
"react": {
"version": "detect"
},
"import/resolver": {
"node": {
"extensions": [".js", ".jsx", ".ts", ".tsx", ".json"]
},
"typescript": {
"config": "tsconfig.json",
"alwaysTryTypes": true
}
}
},
"plugins": ["@typescript-eslint", "react", "import"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/all",
"plugin:react/all",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/strict",
"plugin:import/recommended",
"plugin:import/typescript",
"plugin:import/react",
"prettier"
],
"rules": {
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/prefer-readonly-parameter-types": "off",
"@typescript-eslint/no-magic-numbers": "off",
"@typescript-eslint/no-type-alias": "off",
"react/function-component-definition": "off",
"react/react-in-jsx-scope": "off",
"react/jsx-filename-extension": ["error", { "extensions": [".tsx"] }],
"react/jsx-no-literals": "off",
"react/jsx-max-depth": ["error", { "max": 5 }],
"react/jsx-pascal-case": ["warn"],
"react/jsx-props-no-spreading": "off",
"react/require-default-props": "off",
"react/no-multi-comp": "off"
}
}
.prettierrc
{
"semi": false,
"singleQuote": true,
"trailingComma": "all"
}
packages.json
{
"scripts": {
"pre-commit": "run-s lint-staged validate",
"lint-staged": "lint-staged",
"validate": "run-p validate:*",
"validate:type": "tsc --noEmit",
"validate:lint": "eslint \"src/**/*.{ts,tsx}\"",
"validate:format": "prettier -c \"src/**/*.{ts,tsx}\"",
"lint:fix": "eslint src/**/*.ts --fix",
"prepare": "husky install"
},
"lint-staged": {
"./src/**/*.{ts,tsx}": [
"prettier -w"
]
}
.husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# 以下を追加
yarn pre-commit
Storybook関連
terminal
yarn add -D @storybook/react \
@storybook/addons \
@storybook/addon-a11y \
@storybook/addon-actions \
@storybook/addon-docs \
@storybook/addon-knobs \
@storybook/addon-links \
@storybook/addon-storyshots \
babel-preset-react-app \
babel-plugin-react-require
package.json
{
"scripts": {
"dev": "run-p dev:*",
"dev:main": "next",
"dev:storybook": "start-storybook -p 6006",
"build": "run-s build:*",
"build:main": "next build && next export",
"build:storybook": "build-storybook -o out/_storybook",
}
}
.storybook/.babelrc
{
"presets": ["react-app"],
"plugins": ["react-require"]
}
.storybook/main.js
const path = require('path')
module.exports = {
stories: ['../src/components/**/*.stories.tsx'],
addons: [
'@storybook/addon-links',
'@storybook/addon-actions',
'@storybook/addon-knobs',
'@storybook/addon-docs',
'@storybook/addon-a11y',
'storybook-addon-jsx',
],
webpackFinal: (config) => {
config.resolve.alias = {
'~': path.resolve(__dirname, '../src'),
}
return config
},
}
.storybook/preview.tsx
import { ChakraProvider } from '@chakra-ui/react'
import { withKnobs } from '@storybook/addon-knobs'
import { StoryContext } from '@storybook/react'
import { jsxDecorator } from 'storybook-addon-jsx'
import theme from '../src/theme'
const withChakra = (StoryFn: Function, context: StoryContext) => {
return (
<ChakraProvider theme={theme}>
<div id="story-wrapper" style={{ minHeight: '100vh' }}>
<StoryFn />
</div>
</ChakraProvider>
)
}
export const decorators = [jsxDecorator, withKnobs, withChakra]
あとはsrc/components
配下のコンポーネントに対して*.stories.tsx
を作成する
コンポーネントを作成するためにテンプレートジェネレータを利用する
terminal
yarn add -D hygen
_templates/generator/add-component/prompt.js
module.exports = [
{
type: 'select',
name: 'componentSize',
message: 'コンポーネントの粒度を選択してください。',
choices: ['atom', 'molecule', 'organism', 'template', 'page'],
},
{
message: 'コンポーネント名を入力してください',
name: 'name',
type: 'input',
validate: (answer) => {
if (answer !== '') {
return true
}
},
},
]
_templates/generator/add-component/index.ts.t
---
to: "src/components/<%= componentSize %>s/<%= name %>/index.ts"
unless_exists: true
---
export { <%= name %> } from './<%= name %>'
_templates/generator/add-component/component.tsx.t
---
to: "src/components/<%= componentSize %>s/<%= name %>/<%= name %>.tsx"
unless_exists: true
---
interface <%= name %>Props {
text?: string
}
export const <%= name %> = ({ text }: <%= name %>Props): JSX.Element => {
return <div>{text}</div>
}
_templates/generator/add-component/component.stories.tsx.t
---
to: "src/components/<%= componentSize %>s/<%= name %>/<%= name %>.stories.tsx"
unless_exists: true
---
import { <%= name %> } from './<%= name %>'
export default {
title: '<%= h.changeCase.titleCase(componentSize) %>s/<%= name %>',
}
export const show<%= name %> = (): JSX.Element => <<%= name %> />
.eslintrc
{
"ignorePatterns": ["_templates"]
}
package.json
{
"scripts": {
"add-component": "hygen generator add-component"
}
}
これに合わせて、components
配下のコンポーネントにindex.ts
を追加しておきました。