2022.12.19[Mon]

React Cosmos チュートリアル

  • React
  • Javascript
  • TypeScript

目次

はじめに

こんにちは、Webエンジニアの柴田です。
前回 StorybookからReact Cosmosに移行した話を執筆しました。

StorybookからReact Cosmosに移行した際に、実はReact Cosmosのキャッチアップに苦労しました。また日本語の記事もあまりありませんでしたので、他の方のお役に立てばということで、今回チュートリアルの記事を執筆することにしました。
本記事ではReact Cosmosのチュートリアルと題して、React CosmosのインストールからReact Cosmosの設定、React Cosmosでのコンポーネントのストーリーの作成方法、React Cosmosの便利な機能をご紹介します。

環境

React 17.0.2
React Cosmos 5.7.2

React Cosmos インストール

# Using npm
npm i -D react-cosmos
# or Yarn
yarn add --dev react-cosmos

package.jsonに起動スクリプトを記入

"scripts": {
   "cosmos": "cosmos",
   "cosmos:export": "cosmos-export"
}

React Cosmos 起動コマンド

# Using npm
npm run cosmos
# or Yarn
yarn cosmos

起動に成功すると下記のスクショの画面が表示されます。

設定

・必要な設定としては、cosmos.config.jsonの設定 + webpackの設定 + babelの設定 が必要になります。
・Reactを使用している場合とNext.jsを導入している場合、その他で設定方法が異なってきます。
・設定等でトラブルがあれば、下記をご確認頂けたらと思います。
https://github.com/react-cosmos/react-cosmos/blob/main/docs/README.md#troubleshooting

cosmos.config.json

cosmos.config.jsonの定義方法は2パターンに分かれます。

  • ルートディレクトリ直下にcosmos.config.jsonを作成した場合
    CLIで実行するときにファイルパスを定義しなくても、起動時にReact Cosmosが読み取ってくれます。デフォルトではこちらになります。
  • カスタムディレクトリにcosmos.config.jsonを作成した場合
    CLIで実行するときに--config={cosmos.config.jsonのファイルパス}を引数に渡してあげる必要があります。

cosmos.config.jsonの中身

// (例)
// cosmos.config.json
{
  "webpack":{
    "configPath": "./webpack.config.js"
  },
  "globalImports": [
    // (例)
    "./common/reset.css", 
  ],
  "staticPath": "public",
  "publicUrl": "./",
}

webpack  optional

  • cosmosの仕様上、デフォルトではReact Cosmosが定義しているwebpackが読み込まれます。
  • 自前もしくは指定のwebpackがある場合は、cosmos.config.jsonのconfigPathにて、webpackのconfigのファイルパスを定義する必要があります。定義しないとReact Cosmosが定義しているwebpackが読み込まれます。
  • TypeScript(JavaScript)やCSS、image、Fontなどをカスタムにバンドル設定したい場合は自前でwebpackを設定する必要があります。この場合は必須の設定になります。

globalImports optional

  • cosmosに読み込ませたいcss定義のためのものです。
    例: reset.css, base.css, common.css etc...

staticPath optional

  • React Cosmos内部で静的アセットを読み込むためのものです。

publicUrl optional

  • ネストされたファイルのパスを相対パスで参照できるようにするためのものです。

Create React Appの設定

webpack.configPathreact-scripts/config/webpack.configに設定します。
staticPathsrcに設定します。
watchDirssrcに制限して、該当ディレクトリ以外のファイル変更を無視します。
globalImports に カスタムcssや自前で用意したcssを追加します。

// cosmos.config.json
{
  "staticPath": "src",
  "watchDirs": ["src"],
  "globalImports": ["src/index.css"],
  "webpack": {
    "configPath": "react-scripts/config/webpack.config"
  }
}

Next.jsを導入している場合の設定

cosmos.config.jsonの設定

html-webpack-pluginをdevDependenciesにインストールします。(必須)
staticPathpublicに設定します。
globalImportsstyles/globals.cssを追加します。
     その他カスタムcssや自前で用意したcssがあれば、追加します。

// cosmos.config.json
{
  "globalImports": ["styles/globals.css"],
  "staticPath": "public"
}

babelの設定

ルートディレクトリ直下に .babelrc を作成し、"next/babel"presets に追加します。

// .babelrc
{
  "presets": ["next/babel"],
  "plugins": []
}

Webpackの設定

(S)CSS、Image、Font、TypeScript 用のローダー設定を記述します。
・その他プラグイン、オプション周りの設定を記述します。

// webpack.config.js
const webpack = require('webpack');
const path = require('path');

/**
 * (S)CSS 用のローダー設定。
 */
const cssRules = {
  test: /\.s?css/,
  use: [
    { loader: require.resolve('style-loader') },
    { loader: require.resolve('css-loader'),
      options: {
      url: false,
      importLoaders: 1,
      sourceMap: true
    }},
  ],
  exclude: /node_modules/,
};

/**
 * Image 画像用のローダー設定
*/
const imageRules = {
  test: /\.(jpg|png|gif|svg|eot)$/,
  loader: require.resolve('file-loader'),
  options: {
    name: '[name].[ext]'
  }
}

/**
 * Font用のローダー設定
*/
const fontRules = {
  test: /\.(woff|woff2|ttf)$/,
  loader: require.resolve('file-loader'),
  options: {
    name: '[name].[ext]',
  }
}

/**
 * TypeScript 用のローダー設定。
 */
const tsRules = {
  exclude: /node_modules/,
  test:  /\.tsx?$/,
  use: [
    {
      loader: require.resolve('ts-loader'),
      options: {
        transpileOnly: true,
        compilerOptions: { jsx: 'react', module: 'commonjs' }
      }
    },
    {
      loader: require.resolve('babel-loader'),
      options: {
        configFile: path.resolve(__dirname, `.babelrc`)
      }
    }
  ]
};

/**
 * Webpackの設定をカスタマイズ
 */
const config = {
  mode: 'development',
  target: 'web',
  optimization: {
    minimize: false
  },
  module: {
    rules: [
      tsRules, imageRules, cssRules
    ]
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js'],
  },
  plugins: [
    new webpack.ProvidePlugin({
      React:'react',
    })
  ]
}

module.exports = config;

ReactやNext.jsなどのフレームワークを使用していない場合

html-webpack-pluginをdevDependenciesにインストールします。(必須)
staticPathpublic に設定します。
publicUrl"./" に設定します。

cosmos.config.json

{
  "globalImports": [],
  "staticPath": "public",
  "publicUrl": "./",
  "webpack": {
    "configPath": "./webpack.config.js"
  }
}

 ルートディレクトリ直下に .babelrc を作成し、"presets" に "@babel/env" , "@babel/react" を追加します。

// .babelrc
{
  "presets": ["@babel/env", "@babel/react"],
  "plugins": []
}

・Webpackの設定

必須になります。
記述方法はNext.jsでの webpack.config.js のファイルを参考にして頂けたらと思います。

Fixtureファイルの作成方法

React Cosmosではコンポーネントのストーリーを作成するとき、Fixtureファイルを作成する必要があります。

Fixtureファイル形式は下記の2パターンになります。

xxx.fixture.{js,jsx,ts,tsx}
/__fixtures__/xxx.{js,jsx,ts,tsx}


例)
Text.fixture.tsx
/__fixtures__/Text.tsx

コンポーネントのFixtureファイルの書き方

主要なパターンを3つ紹介します。

1. Propsを必要としないコンポーネントの場合

// Text.fixture.tsx
export default <Text>Hoge!!!</Text>

2. 可変Propsを渡す場合
Fixtureファイル内でもReact Hooksを使用できます。
useStateを使い、可変Propsを渡すことでシュミレーションできます。

// CounterButton.fixture.tsx
export default () => {
  const [count, setCount] = useState(0);
  const handleCounter = useCallback(() => {
    setCount((prev) => prev + 1);
  }, []);
  return <CounterButton count={count} onClick={handleCounter}/>; 
};

3. コンポーネントの複数のパターンを一つのFixtureファイルにまとめたい場合
コンポーネントのパターンをオブジェクト形式でまとめて、出力するだけです。

// AlertBox.fixture.tsx
import AlertBox from './AlertBox';

export default {
  Info: <AlertBox type="info" message="ログインしました" />,
  Success: <AlertBox type="success" message="ログイン成功しました" />,
  Warning: <AlertBox type="warning" message="パスワード変更してください" />,
  Error: <AlertBox type="error" message="ただいま大変混み合っています。" />,
};

Control Panelについて

React Cosmos UI上でコンポーネントのPropsの値を動的に操作・変更して、コンポーネントの挙動を確認できる機能になります。StorybookのAddonであるControls機能に似ています。

useValue

useValueは動作的にはReactのuseStateと変わらず、useStateとの違いはControl Panel名をカスタムできるAPIです。

書き方としては第一引数にControl Panel名、第二引数にはデフォルト値を定義します。

import React from 'react';
import { useValue } from 'react-cosmos/fixture';
import Modal, { BoxWithTitle } from './Modal';

export default () => {
  const [isActive, setIsActive] = useValue('モーダルを表示するかどうか', { defaultValue: false });

  return (
    <div id="parent">
      <Modal
        isActive={isActive}
      >
        <BoxWithTitle title="タイトル">
          <p>説明</p>
          <p>コンテンツ</p>
          <p>文章</p>
        </BoxWithTitle>
      </Modal>
    </div>
  );
};

上記のコードで実装すると、第一引数で定義したControl Panel名がそのまま反映されます。(下記の赤枠を参照)

赤枠の拡大画像

useSelect

useSelectはreact cosmosのUI上でコンポーネントのPropsの値をプルダウン形式で変更できるAPIです。

書き方としては、useSelectの第一引数にControl Panel名、第二引数のoptionsにプルダウンの選択肢を定義します。

import { useSelect } from 'react-cosmos/fixture';
import DisplayNumberSelector from './DisplayNumberSelector';

export default () => {
  const [displayedNumber] = useSelect('表示数', {
    options: ['48', '120'],
  });

  return (
    <DisplayNumberSelector
      displayedNumber={displayedNumber}
      onClickCallBack={() => {}} // mock
    />
  );
};

useSelectの見た目は下記のスクリーンショットの赤枠部分を参照。

赤枠の拡大画像

おわりに

React Cosmos チュートリアルでした。
React Cosmosの導入検討している方のご参考になれば幸いです。

参考

https://github.com/react-cosmos/react-cosmos
・Cosmosの関連記事で執筆したStorybookからReact Cosmosに移行した話 
    https://techlab.q-co.jp/articles/48/

Share

Shopify Post-Purchase Extensionでレジ前販売を実装する チュートリアル編 ①Shopify Post-Purchase Extensionでレジ前販売を実装する 概要編