Motomichi Works Blog

モトミチワークスブログです。その日学習したことについて書いている日記みたいなものです。

Next.js13の開発環境構築手順 step03 css-in-jsとstylelintを設定する

はじめに

Next.js13の開発環境構築手順 step03 をやっていこうと思います。

emotionやlinariaもCSS in JSのパッケージとして候補でしたが、Next.jsに組み込まれているstyled-jsxを使うことにしました。

以下のようなことを前提としてCSS周りの技術選定をしていきました。

  1. 業務などで運用することも考慮して、Next.jsに組み込まれている機能をできるだけ使う(追加パッケージをできるだけ少なくする)
    1. 今後Next.jsのバージョンアップをするときなどによさそう
  2. 上記の「1」は考慮しつつ開発の利便性は損なわないようにしたい
    1. stylelintが使えるようにする
    2. media queryを効率よく書けるようにする
    3. CSSに変数を使えるようにする
  3. トランスパイラはSWCを使う(Babelは使わない)
    1. Next.jsのトランスパイラとしてSWCがデフォルトになったので、オプトアウトしてまでBabelを使わないようにしたい
    2. SWC対応のパッケージだけでも(Babelのプラグインが無くても)開発の利便性が維持できそうかを確認する
  4. CSS in JSはNext.jsにデフォルトで組み込まれているstyled-jsxを使う
    1. styled-componentsやemotionなどを追加で入れなくて済むならその方がよさそう
  5. Sassは使わない
    1. CSS in JSとSassが混ざってしまうと読みにくくなったり、後々混乱の元になりそう
    2. CSS in JSを使うので、変数や関数などの機能はTSで定義すればよさそう
    3. 適切にコンポーネントを分ければSass(SCSS)のセレクタのネスト構文も無くてよさそう
    4. 2023年3月現在では、Sassを使うにはBabelプラグインが必要になることと、上記の「5-1」「5-2」「5-3」の理由でSassは不要そう

Next.jsのデフォルトのindexページを一旦更地にする

まず src/pages/index.tsx を以下のように変更しました。

export default function Home() {
  return <div>home</div>;
}

不要になったファイルを削除

src/pages/index.tsx で使用していた以下のファイルは不要になったので削除します。

  • public/next.svg
  • public/thirteen.svg
  • public/vercel.svg
  • src/styles/Home.module.css
rm public/next.svg \
  public/thirteen.svg \
  public/vercel.svg \
  src/styles/Home.module.css

DefaultLayoutコンポーネントを作成

DefaultLayoutコンポーネントを作成します。

ディレクトリを作成します。

mkdir -p src/components/common/layouts/defaultLayout/

defaultLayout.tsxとindex.tsを作成します。

touch src/components/common/layouts/defaultLayout/defaultLayout.tsx
touch src/components/common/layouts/defaultLayout/index.ts

defaultLayout.tsxは以下の通り記述します。

import { ReactNode } from 'react';

type Props = {
  children: ReactNode;
};

export function DefaultLayout({ children }: Props) {
  return (
    <div className="default-layout">
      <main className="main-contents">{children}</main>
    </div>
  );
}

index.tsは以下の通り記述します。

export { DefaultLayout } from './defaultLayout';

src/pages/_app.tsxを編集

さきほど作成した DefaultLayoutコンポーネントを src/pages/_app.tsx に適用します。

_app.tsxを以下の通り編集しました。

import { DefaultLayout } from '@/components/common/layouts/defaultLayout';

import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <DefaultLayout>
      <Component {...pageProps} />
    </DefaultLayout>
  );
}

不要になったファイルを削除

src/pages/_app.tsx で使用していた以下のファイルは不要になったので削除します。

  • src/styles/globals.css
rm src/styles/globals.css

reset.cssをダウンロードする

https://github.com/richclark/HTML5resetCSS から、おなじみのhtml5doctorのCSSをダウンロードしました。

src/styles/global/_reset.tsを作成

ディレクトリを作成します。

mkdir src/styles/global

src/styles/global/_reset.ts を作成します。

touch src/styles/global/_reset.ts

_reset.ts には、さきほどダウンロードしたreset.cssの記述内容をコピペして、以下の通り記述します。

import css from 'styled-jsx/css';

export const reset = css.global`
  /*!
    html5doctor.com Reset Stylesheet
    v1.6.1
    Last Updated: 2010-09-17
    Author: Richard Clark - http://richclarkdesign.com
    Twitter: @rich_clark
  */

  html,
  body,
  div,
  span,
  object,
  iframe,
  h1,
  h2,
  h3,
  h4,
  h5,
  h6,
  p,
  blockquote,
  pre,
  abbr,
  address,
  cite,
  code,
  del,
  dfn,
  em,
  img,
  ins,
  kbd,
  q,
  samp,
  small,
  strong,
  sub,
  sup,
  var,
  b,
  i,
  dl,
  dt,
  dd,
  ol,
  ul,
  li,
  fieldset,
  form,
  label,
  legend,
  table,
  caption,
  tbody,
  tfoot,
  thead,
  tr,
  th,
  td,
  article,
  aside,
  canvas,
  details,
  figcaption,
  figure,
  footer,
  header,
  hgroup,
  menu,
  nav,
  section,
  summary,
  time,
  mark,
  audio,
  video {
    margin: 0;
    padding: 0;
    border: 0;
    outline: 0;
    font-size: 100%;
    vertical-align: baseline;
    background: transparent;
  }

  body {
    line-height: 1;
  }

  article,
  aside,
  details,
  figcaption,
  figure,
  footer,
  header,
  hgroup,
  menu,
  nav,
  section {
    display: block;
  }

  nav ul {
    list-style: none;
  }

  blockquote,
  q {
    quotes: none;
  }

  blockquote:before,
  blockquote:after,
  q:before,
  q:after {
    content: '';
    content: none;
  }

  a {
    margin: 0;
    padding: 0;
    font-size: 100%;
    vertical-align: baseline;
    background: transparent;
  }

  /* change colours to suit your needs */
  ins {
    background-color: #ff9;
    color: #000;
    text-decoration: none;
  }

  /* change colours to suit your needs */
  mark {
    background-color: #ff9;
    color: #000;
    font-style: italic;
    font-weight: bold;
  }

  del {
    text-decoration: line-through;
  }

  abbr[title],
  dfn[title] {
    border-bottom: 1px dotted;
    cursor: help;
  }

  table {
    border-collapse: collapse;
    border-spacing: 0;
  }

  /* change border colour to suit your needs */
  hr {
    display: block;
    height: 1px;
    border: 0;
    border-top: 1px solid #cccccc;
    margin: 1em 0;
    padding: 0;
  }

  input,
  select {
    vertical-align: middle;
  }
`;

src/styles/global/index.tsを作成

ファイルを作成します。

touch src/styles/global/index.ts

以下の通り記述します。

import css from 'styled-jsx/css';

import { reset } from './_reset';

export const global = css.global`
  ${reset}
`;

GlobalStylesコンポーネントを作成

ディレクトリを作成します。

mkdir -p src/components/common/elements/globalStyles

globalStyles.tsxとindex.tsを作成します。

touch src/components/common/elements/globalStyles/globalStyles.tsx
touch src/components/common/elements/globalStyles/index.ts

globalStyles.tsxは以下の通り記述します。

import { global } from '@/styles/global';

export function GlobalStyles() {
  return (
    <style jsx global>
      {global}
    </style>
  );
}

index.tsは以下の通り記述します。

export { GlobalStyles } from './globalStyles';

グローバルスタイルをDefaultLayoutコンポーネントに適用する

src/pages/_app.tsx でグローバルスタイルをimportするか、DefaultLayoutコンポーネントでグローバルスタイルをimportするか迷ったのですが、とりあえず以下のnextjs.orgのstyled-jsxのブログに倣って、DefaultLayoutコンポーネントでグローバルスタイルを読み込むことにしました。

まぁサイトの規模によって色々あると思いますが、今の所私が想定している規模だと大差ないかなと思っています。

src/components/common/layouts/default-layout/defaultLayout.tsx を以下の通り編集します。

import { ReactNode } from 'react';

import { GlobalStyles } from '../../elements/globalStyles';

type Props = {
  children: ReactNode;
};

export function DefaultLayout({ children }: Props) {
  return (
    <div className="default-layout">
      <main className="main-contents">{children}</main>
      <GlobalStyles />
    </div>
  );
}

MediaQueriesの定数ファイルを作成

ディレクトリを作成します。

mkdir src/styles/common/

ファイルを作成します。

touch src/styles/common/mq.ts

とりあえず以下の通り記述しました。

export const mq = {
  lg: 'screen and (max-width: 1200px)',
  md: 'screen and (max-width: 992px)',
  sm: 'screen and (max-width: 768px)',
  xs: 'screen and (max-width: 576px)',
} as const;

プロジェクトによって、スマホのアクセスが多かったり、PCのアクセスが多かったり、要件は異なると思うので、以下のようなことはその都度適切に設定する必要があります。

  • Window幅の小さい方を基準にするか、大きい方を基準にするか決める(max-widthかmin-widthか決める)
  • break pointの数値を決める

この定数はコンポーネント側で以下のように使用する想定です。

import { mq } from '@/styles/common/mq';

const styles = css`
  .example {
    background: #fdd;
  }

  @media ${mq.md} {
    .example{
      background: #dff;
    }
  }
`;

stylelintをインストールする

インストールします。

yarn add --dev stylelint stylelint-config-standard stylelint-order stylelint-config-recess-order postcss-syntax @stylelint/postcss-css-in-js

インストールしたパッケージについて調べたことなどを以下に書いておきます。

.stylelintrc.jsを作成

ファイルを作成します。

touch .stylelintrc.js

以下の通り記述します。

module.exports = {
  extends: ['stylelint-config-standard', 'stylelint-config-recess-order'],
  plugins: ['stylelint-order'],
  ignoreFiles: ['**/node_modules/**'],
  // NOTE:
  // 2023年04月27日現在で、 @stylelint/postcss-css-in-js は非推奨になっていますが、
  // 開発の利便性を優先して customSyntax として @stylelint/postcss-css-in-js を使用しています。
  // しばらく経ってから、より良い customSyntax があったら乗り換え検討が必要です。
  // その際は postcss-syntax と @stylelint/postcss-css-in-js を remove してください。
  // - ’postcss-styled-jsx 1.0.1’ は ${example} のようなプレースホルダを解釈できなかったため使用しませんでした。
  // 'postcss-styled-syntax' は global tag と resolvet tag に対して lint が実行されないため使用していません。
  // ’@stylelint/postcss-css-in-js’が非推奨になった経緯なども踏まえると、
  // styled-jsx専用の customSyntax が見つかれば一番良いと思います。
  customSyntax: '@stylelint/postcss-css-in-js',
};

VSCodeのextentionsをインストールする

styled-jsxのシンタックスハイライトと入力補完のextentionをインストールします。

以下のページに書きました。

動作確認

styled-jsxやstylelintの設定ができているか、開発していくにあたって不便なことは無さそうかなど、以下の動作確認をしました。

  • CSSを書くときにエディタの入力補完が使える
  • コンポーネントで styled-jsx の基本的な記法が使える
  • styled-jsx で TS の変数が使える
  • reset.css をグローバルに適用できる
  • global を指定しない場合は scoped になる
  • media queries を効率よく書けそうかやってみる
  • TS の変数を使って、Dynamic styles による条件分岐ができること
  • css.global を使用した場合 head タグの中に出力されること
  • css.resolve の挙動を確認する
  • production ビルドしてみて確認する
  • yarn lint を実行して、error が検知されること
  • yarn lint:fix を実行して、auto-fix できること
  • yarn stylelint を実行して、error が検知されること
  • yarn stylelint:fix を実行して、Dynamic styles が混ざっていても正常に auto-fix できること

ここまでで抱えている問題点

1. VSCodeでファイル保存したときにstylelintによるautofixが実行されない

色々試してみたものの、settings.jsonの設定内容が悪いのか、VSCodeで保存したときには実行されません。

いずれにしてもhuskyでpre-commit時に実行するからまぁいいかなと思っています。

もちろんVSCodeで保存したときに実行できるならそれに越したことはないですが。

2. @stylelint/postcss-css-in-js は非推奨になっている

より適切な customSyntax を探して乗り換える必要があります。

package.jsonのscriptsにstylelintとstylelint:fixを定義する

package.json の scripts に以下の行を追記します。

    "stylelint": "stylelint --ignore-path .gitignore './src/**/*.{css,scss,ts,tsx}'",
    "stylelint:fix": "stylelint --fix --ignore-path .gitignore './src/**/*.{css,scss,ts,tsx}'",

stylelintでsrc/styles/global/_reset.tsを修正する

以下のコマンドを実行すると stylelint のルールに応じて修正されます。

yarn stylelint:fix

今回はここまでにしておきます。

続きはこちら

Next.js13の開発環境構築手順 step04 lint-stagedとhuskyを設定する - Motomichi Works Blog