SANDFISH FACTORY

技術ブログです。python・vuejsを愛でる日々について綴ります

TypeScript+rollup.js+EmotionでReact開発できる環境を作ってみた

お久しぶりです。
前回のブログ投稿から、日が経ってしまいました。

ここ最近は個人的に作ってみたいサービスのイメージが湧いたので、AWS CDK(Python)で遊んでいたんですが、フロントエンドまでイメージが広がりました。

これまではVue.jsを使っていたんですが、折角なんで、きちんと触ったことのないReactでやってみようと思いつきました。
前回のブログ投稿からだいぶ日が経ってしまうとは、この時は思ってもいませんでした。

Motivation

Vue.jsにはVue CLIがあり、ReactにもCreate React Appというとても便利なCLIがあります。
今更ながらゼロからReact開発を学ぶので、CLIを利用しない環境構築をしてきちんと仕組みを理解してみようと思いました。
いっそのこと欲張りに全部盛りにしてやってみよう!!!(←ここが日が経った理由)
ということで

  • TypeScriptを使ってみよう
  • Babelとwebpackを使わないで実現してみよう
  • 公式のライブラリを前提に組み上げてみよう

という前提条件を課して進めることをMotivationとしました。

Babelとwebpack以外にしてみようと考えた理由は以下の通りです。

ツール 理由
Babel TypeScriptを利用するのでコンパイルはTypeScriptに任せたい
webpack 多機能なので軽量なバンドルの仕組みにしたい

今回は勉強も兼ねているので、少しだけセオリーから外れた構成で進めてみました。

開発環境の要素を考える

Reactの公式ドキュメントを読み進めながら構成を考えました。
この時点で決めていたのはTypeScriptを利用することだけだったので、検討する要素は以下の2点でした。

スタイルの定義の仕方

CSS とスタイルの使用をみる限り、クラス属性を利用した制御について書かれているのみで、React はスタイルがどのように定義されているかには関心を持ちません。と記載されていました。
コンポーネント単位でCSSは定義したいと考えていたので、サードパーティのライブラリの選定が必要になりました。

利用するバンドラ・コンパイラを決める

ゼロからツールチェインを作成するに記載されています。
パッケージマネージャはnpmとしたので、バンドラ、コンパイラの選定が必要になりました。

構成を検討する

  • バンドラはどれを利用するか
    • Parcel
    • browserify
    • rollup.js
  • CSSはどのライブラリを利用するか
    • CSS Modules
    • Styled Components
    • Emotion

なお、公式ドキュメントに記載のあったParcelはバンドルした結果のファイル名が制御できないので、今回の想定からは外しました。
CSSのライブラリはいくつか調査しましたが、Styled ComponentsはHTML要素(例えばbuttonなど)にまで影響を与える実装の仕方だったので、今回の想定からは外しました。
これらの要素で開発環境の構成を再検討しました。

実現方法 理由
browserify+CSS Modules NG
CSS Modulesのbrowserify公式プラグインがhighly experimentalのステータスのまま
rollup.js+CSS Modules NG
CSS Modulesのrollup公式プラグインが存在しない
rollup.js+Emotion OK
JavaScriptcssを定義するので、rollup公式プラグインのnode_modulesの解決プラグインで対応できる

実際に環境構築を行い、前提条件を満たすことができる構成はrollup.js、Emotionの組み合わせかな?と思ったので、一旦こちらの構成で進めることとしました。

環境構築手順

TypeScript、rollup.js、Emotionの構成で開発環境を構築してみたいと思います。

appディレクトリを作成

まずはappディレクトリを作成します。
ソースコードはsrcディレクトリ、ビルドした結果はpublicディレクトリに配置します。

mkdir react-app
cd react-app
npm init -y
mkdir src
mkdir -p public/scripts

ディレクトリを作成したら、npm initでpackage.jsonの最低限の設定を行います。

必要なパッケージのインストール

次に、React、TypeScript、rollup.js、Emotionに必要なパッケージのインストールを行います。

npm i -D react react-dom @types/react @types/react-dom
npm i -D typescript tslib
npm i -D rollup
npm i -D @rollup/plugin-typescript @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-replace
npm i -D @emotion/react

TypeScriptと一緒にtslibをインストールしています。これはrollup.jsのTypeScriptプラグインに必要なためです。

rollup.jsは4つプラグインをインストールしています。

プラグイン 理由
@rollup/plugin-typescript TypeScriptを利用するため
@rollup/plugin-commonjs CommonJSのモジュールをES6に変換するため
@rollup/plugin-node-resolve ローカルのnode_modulesにあるサードパーティのライブラリをバンドルするため
@rollup/plugin-replace バンドルする際に対象文字列を置換するため

@rollup/plugin-replaceは追加で入れたプラグインで、バンドル時のエラーで、process.env.NODE_ENVが解釈できないとエラーを解決するために追加しました。
プラグインREADMEをみる限り、最も多い利用するケースのようです。

tsconfigの設定

次に、tsconfigの設定を行います。

node_modules/.bin/tsc --init

を実行するとrootディレクトリ直下にtsconfig.jsonが生成されます。
下記の内容は動くための最低限の設定です。

{
  "compilerOptions": {
    /* Basic Options */
    "target": "es5",
    "module": "ES2015",
    "lib": [
      "es2015",
      "dom"
    ],
    "allowJs": true,
    "jsx": "react",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "./public/scripts",
    "rootDir": "./src",
    "removeComments": true,

    /* Strict Type-Checking Options */
    "strict": true,

    /* Module Resolution Options */
    "moduleResolution": "node",
    "esModuleInterop": true,
    
    /* Advanced Options */
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": [
    "./src/**/*"
  ]
}

rollup.config.jsの設定

次に、rollup.jsの設定を行います。
rootディレクトリ直下にrollup.config.jsを作成し、バンドルするための設定を定義します。

  • intput:entryポイントとなるファイルを指定
  • output:バンドルした結果の出力先を指定
  • plugins:利用するプラグインを指定
import typescript from '@rollup/plugin-typescript';
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import replace from '@rollup/plugin-replace';

export default {
    input: 'src/App.tsx',
    output: [
        {
            file: 'public/scripts/bundle.js',
            format: "cjs",
            sourcemap: true
        },
        {
            file: 'public/scripts/bundle.es.js',
            format: "esm",
            sourcemap: true
        }
    ],
    plugins: [
        replace({
            // alternatively, one could pass process.env.NODE_ENV or 'development` to stringify
            'process.env.NODE_ENV': JSON.stringify('development')
        }),
        commonjs(),
        resolve(),
        typescript()
    ],
};

動作確認

最後に動作確認を行うためのコードを定義します。
ディレクトリ構成と各種ファイルは以下のようにします。

f:id:sandfish03:20210126210841p:plain
ディレクトリ構成

index.html

index.htmlは以下の通りです。

<!doctype html>
<html>

<head>
    <meta charset="UTF-8">
    <title>test</title>
</head>

<body>
    <div id="app">
    </div>
    <script type="text/javascript" src="./scripts/bundle.js"></script>
</body>

</html>

Welcome.tsx

Welcome.tsxは以下の通りです。
Emotion.jsはcss属性を利用するのですが、公式ドキュメントに2つの方法が記載されています。

  • Babel Preset
  • JSX Pragma

今回はBabelを利用しないようにしているので、JSX Pragmaを利用しました。

/* @jsx jsx */
import React from "react";
import { css, jsx } from '@emotion/react';

interface WelcomeProps {
    name: string
}
const style = css`
  color: hotpink;
`;

export default class Welcome extends React.Component<WelcomeProps, {}> {
    render() {
        return <div css={style}>Hello, {this.props.name}</div>;
    }
}

App.tsx

entryファイルとなるApp.tsxは以下の通りです。

import React from 'react';
import ReactDOM from 'react-dom';
import Welcome from "./components/atoms/Welcome";

ReactDOM.render(
    <Welcome name="React" />,
    document.getElementById('app')
);

これらの設定が終わったら、ビルドを実行します。

package.jsonのscriptsにbuildコマンドとして「rollup -c」を追加します。

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "rollup -c"
  }

追加できたら「npm run build」を実行してください。rollup.config.jsに定義した出力先にバンドルされたファイルが生成されます。
index.htmlを開くと以下の画面が表示されます。

f:id:sandfish03:20210126212235p:plain
動作確認

無事、ビルドが成功しました!!!

まとめ

ひとまず、Reactで書いたコンポーネントをビルドしてブラウザで表示することができるようになりました。
設定周りの考慮など、まだ足りていない部分があるかもしれませんが、分かったらUPDATEしていきたいと思います。

当初の想定では去年の12月にはこの記事を投稿しているはずだったんですが、だいぶ手間取りました。。。
私のフロントエンド周りの知識が不足していることが分かってよかったです。

ようやく、本来のReactでのフロントエンド開発を行う下地ができたので、進めていきたいと思います。
ではでは。