🐬

【Sass】node-sassの終了アナウンスから、dart-sassを使う (2021/09)

15 min read

Next.js(v11) /TypescriptとStorybookでsass導入と@useを使ったサンプル

Sass(Scss)のnode-sassは、2022年10月に終了のアナウンスがされています。現在の推奨はdart-sassです。

node-sassの終了に関してはこちらの記事を参照


前回の記事で、Sassの導入について、以下のコメを記載していましたが、node-sassの終了に伴い、dart-sass(yarn add sass)によりインストールをしていることが理由です。

sass-loaderに依存するNext.js(v11系)と、Storybook(v6.3.7)へ、「dart-sassの導入」と、「@useを使った簡易なSassコードのサンプル」(@importも使わなくなるため)をおこしています。

@importが使われなくなるため、時期をみて、(前の記事は)@useに修正するかなとは考え中です。

Sassにふりまわされたことが執筆のモチベーション✨


開発情報

  • Macbook MacOS BigSur 11系
  • docker
  • node v16.3.0

実装リスト

  • docker
  • Next.js(v11)/TypeScript
  • Sass(dart-sass) 🌟今回のメインテーマ
  • Storybook(v6.3.7)
  • Atomic design ※一部だけを構成
  • jest ※参考です

導入は自己責任で(お約束文)


全体のステップ

本STEP_1〜5の環境構築は、別記事【2021-8】 Next.js + TypeScript + Storybook(Atomic Design) + Sass + jestにも記載しています。

このため、「STEP_6 sassを@useを使った簡易モック」からでもOKです。
※本記事の中で、参考として実装の流れをつけています。

  • STEP_1 Dockerの環境構築
  • STEP_2 Next.jsの導入 + テスト開発用(jest)の導入
  • STEP_3 TypeScriptの導入
  • STEP_4 Sass(dart-sass)の導入
  • STEP_5 Storybookの導入
  • STEP_6 sassを@useを使った簡易モック
  • おまけ next/imagesをStorybookで使う

STEP_1 Docker環境の構築

初期フォルダの構成はこちらです。(参考)

ディレクトリ、ファイルの作成

mkdir -p docker/dart_sass
touch docker/dart_sass/Dockerfile
touch docker-compose.yaml

1-0. Dockerfileとdocker-compose.yamlの編集

docker/dart_sass/Dockerfile
FROM node:16.3-alpine

RUN yarn install && yarn global add \
    create-react-app

WORKDIR /dart_sass
docker-compose.yaml
version: '3.8'
services:
  dart_sass:
    container_name: dart_sass_next_container
    build: ./docker/dart_app
    ports:
      - 13050:3000
      - 16056:6006
    volumes:
      - ./dart_sass:/dart_sass
    stdin_open: true
    tty: true
    environment:
      - TZ=Asia/Tokyo
    networks:
      - default

networks:
  default:

dockerステータスのチェックコマンド(参考)

docker images -a
docker ps -a
docker-compose ps -a

1-1.コンテナのビルド

docker-compose build --no-cache

※--no-cacheは念のため

1-2.コンテナの起動

docker-compose up -d

1-3.コンテナに接続

docker exec -it dart_sass_next_container sh

STEP_2 Next.js + テスト開発用(jest)の導入

2-1.create-next-app

npx create-next-app .

2-2. jestの設定

  1. PKGのインストール
yarn add -D jest @testing-library/react @types/jest @testing-library/jest-dom @testing-library/dom babel-jest @testing-library/user-event jest-css-modules

開発に必要なものもインストールします。

yarn add axios msw swr

package.jsonファイルに、scriptsとjestを追記します。

package.json
"scripts": {
        <省略>
        "test": "jest --env=jsdom --verbose"
    },

<省略>
"jest": {
        "testPathIgnorePatterns": [
            "<rootDir>/.next/",
            "<rootDir>/node_modules/"
        ],
        "moduleNameMapper": {
            "\\.(css|scss)$": "<rootDir>/node_modules/jest-css-modules"
        }
    }

テスト用のディレクトリとファイルを作成します。

mkdir __test__
touch __test__/Home.test.tsx

Home.test.tsxファイルを編集します。
Nextの文字をテストする(参考)

__test__/Home.test.tsx
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import Home from '../pages/index'

it('Should render text', () => {
  render(<Home />)
  expect(screen.getByText('Next')).toBeInTheDocument()
})

動作テストは割愛(STEP_3を完了後に実行してください)

yarn test

2-3. .babelrcファイルの編集

  1. .babelrcファイルを作成します
touch .babelrc
  1. .babelrcファイルにpresetsをコーディングします
.babelrc
    {
        "presets": ["next/babel"]
    }

STEP_3 TypeScriptの導入

3-1.tsconfig.jsonファイルの作成

touch tsconfig.json

3-2.PKGのインストール

yarn add -D typescript @types/react @types/node

3-3.Next.jsを起動

Next.jsを開始します

yarn dev

ローカルポートより接続して参照できればOK

Next.jsのファイル編集

  1. pages/_app.jsと、pages/index.jsのファイル拡張子を.tsxに変更する

pages/_app.js

pages/_app.js
mv pages/_app.js pages/_app.tsx

pages/index.js

pages/index.js
mv pages/index.js pages/index.tsx
  1. pages/_app.tsxを編集する
pages/_app.tsx
import { AppProps } from 'next/app'
import '../styles/globals.css'

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />
} 
  1. pages/index.jsを編集する

不要なコードを削除するのと、後ほど、CSS/Sassの動作テスト用に、classNameを付与したbuttonタグをコーディングしています。

pages/index.js
const Home: React.FC = () => {
  return (
    <>
      <h1>Next</h1>
    </>
  )
}

export default Home

ローカルポートより接続して参照できればOK


STEP_4 Sassの導入

4-1.sassのインストール

dart-sassのインストールを行います。※sass(dart-sass)の導入としてはこれだけです。

yarn add sass

4-2.next.config.jsファイルの編集

こちら公式より、読み込むファイルのパス設定の掲載です。(公式

以降の、babel-plugin-module-resolverを用いても同じように動作します。

next.config.js
const path = require('path'); //追加

module.exports = {
  reactStrictMode: true,
  // 以下を追加
  sassOptions: {
    includePaths: [path.join(__dirname, 'styles')],
  },
}

設定する中で「module not found」など、警告(エラーメッセージ)が出ている時は.eslintrc.jsonファイルを、下記のように編集してみてください。

.eslintrc.json
{
  "extends": [
    "next",
    "next/core-web-vitals",
    "prettier",
    "next/babel"
  ]
}

4-3.各PKGのインストール

  1. sass-loadrのインストール
yarn add -D sass-loader@^10

※Storybookは10系でないと動かない・・・

  1. 各種PKGのインストール
yarn add -D webpack @storybook/builder-webpack5 @storybook/manager-webpack5 css-loader style-loader babel-plugin-module-resolver

4-4.パス設定

babel-plugin-module-resolverによりパス設定を行います。
  1. .babelrcファイルの編集
.babelrc
"plugins": [
            [
                "module-resolver",
                {
                    "root": [
                        "."
                    ],
                    "alias": {
                        "@": "./"
                    }
                }
            ]
        ]
  1. tsconfig.jsonファイルを編集
tsconfig.json
"compilerOptions": {
		<省略>(以下を追記する)
		"baseUrl": ".",
		  "paths": {
		    "@/": [
		      "./*"
		    ]
		  },

4-5.Sassをテスト

Sassインストール後のファイル動作を少しだけテストします

CatNoseさんを参考にコーディング✨zennをありがとうございます

  1. styles/globals.cssファイルの拡張子をscssへ変更する
mv styles/globals.css styles/globals.scss

2. styles/globals.scssを、pages/_app.tsxファイルへimportする

pages/_app.tsx

pages/_app.tsx
import '../styles/globals.css' // 削除
import '../styles/globals.scss'

styles/globals.scssファイルを、:root{}で囲み、h1タグにvarをコーディングしています。

styles/globals.scss
:root {
  --c-sass: pink;

  h1 {
    color: var(--c-sass);
  }
}

colorが変更されていればOKです!


STEP_5 Storybookの導入

5-1.Storybookのインストール

  1. sb initをします
npx sb init
  1. 作成されるstoriesは不要なので削除します。※必要な方は削除しなくてokです。
rm -r stories

5-2. 各ファイルの編集

  1. .storybook/main.jsファイルの編集

アドオンの@storybook/preset-scssを使うだけでscssは動作します。
ですが、エラーばかりが多く・・・試行錯誤した結果、アドオンは使わないことにしました。(今後・・・)

.storybook/main.js
const path = require('path');

module.exports = {
  "stories": [
    "../components/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
  ],
  core: {
    builder: 'webpack5',
  },
  webpackFinal: async (config) => {
    const rootPath = path.resolve(__dirname, '../');
    config.resolve.alias['@'] = rootPath;

    config.module.rules.push({
        test: /\.scss$/,
        sideEffects: true,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2,
            },
          },
          {
            loader: "sass-loader",
            options: {
              sourceMap: true,
            },
          },
        ],
      });
    
    return config;
  }
}
  1. .storybook/preview.jsファイルへ、styles/global.scssをimportします
.storybook/preview.js
import '@/styles/globals.scss'
  1. package-jsonファイルへ、storybookのパス設定を追記します
package-json
"scripts": {
	<省略>
	"storybook": "start-storybook -s public -p 6006"
	}

yarn storybookの前にモック用のコーディングを行います。


STEP_6 Sassの@useを使った簡易モック

モック用にボタン・コンポーネントをつくり、Sassのテストをします

6-1. 各コーディング

Button.tsxに、.button_sass_sample、.button_containerのふたつのclassを設けて、sassの動作をチェックします。

  1. ボタンコンポーネントの作成
mkdir -p components/atoms
mkdir -p components/atoms/types
touch components/atoms/Button.tsx
touch components/atoms/Button.stories.tsx // storybook用
touch components/atoms/types/Button.types.ts

Button.types.ts

components/atoms/types/Button.types.ts
export interface ButtonProps {
    label: string,
    onClick?: () => void,
}

Button.tsx

components/atoms/Button.tsx
import { ButtonProps } from './types/Button.types'
import styles from '../../styles/components/atoms/Button.module.scss'

export const Button = ({
    label,
    ...props
}: ButtonProps) => {
    return (
        <>
            <div className={styles.button_sass_sample}>
                <h1>sass</h1>
            </div>
            <div className={styles.button_container}>
                <button>
                    {label}
                </button>
            </div>
        </>
    )
}

Button.stories.tsx

components/atoms/Button.stories.tsx
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Button } from './Button';

export default {
    title: 'Example/Button',
    component: Button,
    argTypes: {
        backgroundColor: { control: 'color' },
    },
} as ComponentMeta<typeof Button>;

const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;

export const PrimaryButton = Template.bind({});
PrimaryButton.args = {
    label: 'sample',
};
  1. Sass(dart-sass)のモックファイルを作成
mkdir -p styles/components/atoms/Button.module.scss
touch styles/_variables.scss

sassの@useサンプルコーディング

variablesに用意した変数を、moduleで使う(@use)。すごく簡素なコーディング内容です。

①styles/_variables.scssに変数を用意
②styles/components/atoms/Button.module.scssで「@use」で変数を使用

_variables.scss

styles/_variables.scss
$sass-test: red;
$sass-test_two: blue;

Button.module.scss

styles/components/atoms/Button.module.scss
@use '../../variables' as v;

.button_sass_sample {
  background-color: v.$sass-test;
}

.button_container {
  background-color: v.$sass-test_two;
  width: 100px;
  height: 50px;
}

これをButton.tsxをimportした、pages/index.tsxで動作を確認します。

pages/index.tsx
import { Button } from '../components/atoms/Button'

const Home: React.FC = () => {
  return (
    <>
      <div>
        <h1>Next</h1>
        <Button label={"sample-scss"} />
      </div>
    </>
  );
}

export default Home

6-2. Storybookの起動

Storybookを起動します。

yarn storybook

起動した時に、@babel/plugin-proposalの["loose": true]エラーを検知した場合は、.babelrcの"plugins"へ以下を追記してください。

.babelrc
"plugins": [
            [
                "@babel/plugin-proposal-class-properties",
                {
                    "loose": true
                }
            ],
            [
                "@babel/plugin-proposal-private-methods",
                {
                    "loose": true
                }
            ],
            [
                "@babel/plugin-proposal-private-property-in-object",
                {
                    "loose": true
                }
            ],
	    <省略>
	    ]

実行結果

Next.js

Storybook上

ボタンストーリー上での表示になります。

どちらでもSassの@useを使うことでスタイルが変化していればOKです!

Sassのふかい使い方はこちらの記事が参考になります。

学んで別記事に起こせるように、いつか(きっと)

長文でしたが、最後まで読んでいただき、ありがとうございました。どなたかの助けになりますように。


おまけ

next/imagesを使用できるようにする

Next.js(v11)でnext/imagesを、Storybookで使うための設定です。
エラーばかりにあい、かなりハマりました。こちら参考リンクに感謝です。

  1. モック用のディレクトリとファイルを作成
mkdir -p .storybook/__mocks
touch .storybook/__mocks/NextImage.tsx
  1. NextImage.tsxファイルを編集
.storybook/__mocks/NextImage.tsx
import * as nextImage from "next/image"

Object.defineProperty(nextImage, "default", {
    configurable: true,
    value: props => {
        const { width, height } = props
        const ratio = (height / width) * 100
        return (
            <div
                style={{
                    paddingBottom: `${ratio}%`,
                    position: "relative",
                }}
            >
                <img
                    style={{
                        objectFit: "cover",
                        position: "absolute",
                        minWidth: "100%",
                        minHeight: "100%",
                        maxWidth: "100%",
                        maxHeight: "100%",
                    }}
                    {...props}
                />
            </div>
        )
    },
})
  1. .storybook/preview.jsファイルへimport
.storybook/preview.js
import '@/styles/global.scss'
import './__mocks/NextImage'
  1. コーディング例
pages/index.tsx
import NextImage from 'next/images'
import icon from '../public/vercel-icon.svg'

<NextImage src={icon.src} alt="next/images" height="50px" width="50px" />

next/imagesがStorybookでも利用できるようになります。
が・・・Storybook上で、.png、.svgを表示することができません。(どなたか・・・教えてください)

Discussion

ログインするとコメントできます