Open39

今Reactで新規PJを作るなら 2022

むらさきむらさき

スタイリング候補

  • styled-component
  • linaria
  • emotion
むらさきむらさき
  • styled-component

一番使い慣れている
その分デメリットをたくさん知っている

  • linaria

zero runtime
viteと併用する場合プラグインが必要

むらさきむらさき

一旦linariaで試す

時間があったらCSS in JSは一通り試したい
そしてStyledXに期待

むらさきむらさき
tree -I "node_modules|dist"

.
├── index.html
├── package.json
├── public
│   └── favicon.svg
├── src
│   ├── App.tsx
│   ├── main.tsx
│   └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── yarn.lock

2 directories, 10 files
むらさきむらさき

とりあえずlint

module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier",
  ],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: "latest",
    sourceType: "module",
  },
  plugins: ["react", "@typescript-eslint"],
  rules: {},
  settings: {
    react: {
      version: "detect",
    },
  },
}

むらさきむらさき

React18でこれを指定しないとwarningがでた
Warning: React version not specified in eslint-plugin-react settings. See https://github.com/yannickcr/eslint-plugin-react#configuration .

settings: {
    react: {
      version: "detect",
    },
  },
むらさきむらさき

現時点のpackage.json

{
  "name": "react-template",
  "private": true,
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "format": "prettier --write ./src/**/*.{ts,tsx}",
    "lint": "eslint --ext ts,tsx .",
    "lint:fix": "yarn lint --fix"
  },
  "dependencies": {
    "react": "18.0.0",
    "react-dom": "18.0.0"
  },
  "devDependencies": {
    "@types/react": "18.0.0",
    "@types/react-dom": "18.0.0",
    "@typescript-eslint/eslint-plugin": "5.20.0",
    "@typescript-eslint/parser": "5.20.0",
    "@vitejs/plugin-react": "1.3.0",
    "eslint": "8.14.0",
    "eslint-config-prettier": "8.5.0",
    "eslint-plugin-react": "7.29.4",
    "eslint-plugin-react-hooks": "4.4.0",
    "prettier": "2.6.2",
    "typescript": "4.6.3",
    "vite": "2.9.5"
  }
}

むらさきむらさき

現時点のdir

.
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── index.html
├── package.json
├── public
│   └── favicon.svg
├── src
│   ├── App.tsx
│   ├── assets
│   │   └── .gitkeep
│   ├── components
│   │   └── Button
│   │       └── .gitkeep
│   ├── context
│   │   └── .gitkeep
│   ├── features
│   │   ├── objective
│   │   │   └── .gitkeep
│   │   └── user
│   │       └── .gitkeep
│   ├── hooks
│   │   └── .gitkeep
│   ├── lib
│   │   └── .gitkeep
│   ├── main.tsx
│   ├── pages
│   │   └── .gitkeep
│   ├── routes
│   │   ├── index.ts
│   │   └── routes.tsx
│   ├── utils
│   │   └── .gitkeep
│   └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── yarn.lock

14 directories, 25 files
むらさきむらさき

stylingはstyled-componentsを採用する

理由

  • 候補だったlinariaはviteの正式サポートがなく不安が残る
  • パフォーマンスは困ったときに対応したらいい
  • styled-componentsはなんだかんだデファクトでありメンタルモデルが働く
むらさきむらさき

Vitestを触ってみる
特に問題なければ採用するかも

むらさきむらさき

vanilla-extractと相性が悪い
rollupのプラグイン使ってbabelのプラグイン噛ませてもダメ

 FAIL  src/components/Spinner/spinner.test.tsx [ src/components/Spinner/spinner.test.tsx ]
Error: Styles were unable to be assigned to a file. This is generally caused by one of the following:

- You may have created styles outside of a '.css.ts' context
- You may have incorrect configuration. See https://vanilla-extract.style/documentation/setup
むらさきむらさき

babel書いてやっとテストできるようになった
ここまできたらvanilla-extractやめてもいい気持ちになってきた

むらさきむらさき

せっかくだしvitestを使いたい気持ちが拭えない
styled-componentならいけるのか試すことにする

むらさきむらさき

vanilaの供養

import { keyframes } from "@vanilla-extract/css"
import * as V from "@vanilla-extract/recipes"

const rotate = keyframes({
  "0%": { transform: "rotate(0deg)" },
  "100%": { transform: "rotate(360deg)" },
})

export const recipe = V.recipe({
  base: {
    borderStyle: "solid",
    borderRadius: "99999px",
    borderWidth: "2px",
    borderBottomColor: "transparent",
    borderLeftColor: "transparent",
    animation: `${rotate} 0.45s linear infinite`,
  },
  variants: {
    size: {
      sm: {
        width: "16px",
        height: "16px",
      },
      md: {
        width: "24px",
        height: "24px",
      },
    },
  },
  defaultVariants: {
    size: "md",
  },
})

export type Variants = V.RecipeVariants<typeof recipe>

むらさきむらさき

styled-componentsのv6を試しに入れてみたらbuildできず
v5にする

✘ [ERROR] No matching export in "node_modules/tslib/tslib.es6.js" for import "__spreadArray"

    node_modules/styled-components/dist/styled-components.browser.esm.js:1:7:
      1 │ import{__spreadArray as e,__read as t,__values as n,__assign as o,__rest as r}from"tslib";import i ...
        ╵        ~~~~~~~~~~~~~

23:27:50 [vite] error while updating dependencies:
Error: Build failed with 1 error:
node_modules/styled-components/dist/styled-components.browser.esm.js:1:7: ERROR: No matching export in "node_modules/tslib/tslib.es6.js" for import "__spreadArray"
むらさきむらさき

これはstyled-componentsが依存してるtslibのバージョンが低いことが原因

むらさきむらさき

errorboudaryつくる

むらさきむらさき

18でも特に変わったことなし
ただsuspenseをたくさん活用したいので、ErrorBoundaryも複数箇所で使えるようにかいた

import * as React from "react"

type Props = {
  onError?: (err: unknown) => void
  fallback: (err: unknown) => React.ReactNode
  children: React.ReactNode
}

type State = {
  cause: unknown | null
}

export class ErrorBoundary extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props)
    this.state = {
      cause: null,
    }
  }

  static getDerivedStateFromError(cause: unknown) {
    return { cause }
  }

  componentDidCatch(error: unknown, _info: React.ErrorInfo) {
    if (!this.props.onError) return
    this.props.onError(error)
  }

  render() {
    const { cause } = this.state
    if (cause) {
      return this.props.fallback(cause)
    }
    return this.props.children
  }
}