はじめに
Next.js13の開発環境構築手順 step03 をやっていこうと思います。
emotionやlinariaもCSS in JSのパッケージとして候補でしたが、Next.jsに組み込まれているstyled-jsxを使うことにしました。
以下のようなことを前提としてCSS周りの技術選定をしていきました。
- 業務などで運用することも考慮して、Next.jsに組み込まれている機能をできるだけ使う(追加パッケージをできるだけ少なくする)
- 今後Next.jsのバージョンアップをするときなどによさそう
- 上記の「1」は考慮しつつ開発の利便性は損なわないようにしたい
- stylelintが使えるようにする
- media queryを効率よく書けるようにする
- CSSに変数を使えるようにする
- トランスパイラはSWCを使う(Babelは使わない)
- Next.jsのトランスパイラとしてSWCがデフォルトになったので、オプトアウトしてまでBabelを使わないようにしたい
- SWC対応のパッケージだけでも(Babelのプラグインが無くても)開発の利便性が維持できそうかを確認する
- CSS in JSはNext.jsにデフォルトで組み込まれているstyled-jsxを使う
- styled-componentsやemotionなどを追加で入れなくて済むならその方がよさそう
- Sassは使わない
Next.jsのデフォルトのindexページを一旦更地にする
まず src/pages/index.tsx を以下のように変更しました。
export default function Home() { return <div>home</div>; }
不要になったファイルを削除
src/pages/index.tsx で使用していた以下のファイルは不要になったので削除します。
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
インストールしたパッケージについて調べたことなどを以下に書いておきます。
- Stylelint · GitHub を参照すると recommended と standard がありますが、公式に stylelint-config-recommended よりも stylelint-config-standard を推奨している旨の記載があるため、stylelint-config-standard の方を使用します。
- stylelint-order でプロパティの記述順を制御します。記述順の設定については stylelintのorderモジュール選定 - Qiita を参考にさせていただいて、 stylelint-config-recess-order を使用することにしました。
- postcss-syntax と @stylelint/postcss-css-in-js についてはもっと styled-jsx に適したパッケージがあると良いのですが、2023年3月現在でSWCに対応している適当なパッケージが見つからなかったため、@stylelint/postcss-css-in-js が非推奨になっていることを承知のうえで使用しています。
- https://github.com/stylelint/postcss-css-in-js は global tagやresolve tag に対してもlintしてくれるが非推奨になっているため、しばらくしたら乗り換える必要がある。
- postcss-css-in-js が非推奨になった理由として https://github.com/stylelint/stylelint/blob/main/docs/migration-guide/to-15.md#removed-processors-configuration-property によると、全てのCSS in JSの構文をひとつのパッケージで対応しきれないということらしい
- 'postcss-styled-syntax' はglobal tagやresolve tagに対してlintしてくれない点で不充分なので、今回は使用しなかった。
- postcss-styled-jsx 1.0.1 は、
${example}
のようなプレースホルダを解釈できなかったため使用しなかった。 - mapbox/stylelint-processor-arbitrary-tags も便利そうだけど、 .stylelintrc の processors プロパティは非推奨になっているし、それほど無理して stylelint を使う必要もない。
- stylelintはあるに越したことはないが、最悪無くても良いぐらいの気持ち。
- https://github.com/stylelint/postcss-css-in-js は global tagやresolve tag に対してもlintしてくれるが非推奨になっているため、しばらくしたら乗り換える必要がある。
.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