Open4

Next.jsプロジェクト作成テンプレ

YutaUraYutaUra

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

YutaUraYutaUra

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を作成する

YutaUraYutaUra

コンポーネントを作成するためにテンプレートジェネレータを利用する

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を追加しておきました。