まくろぐ

React + TypeScript の環境を整える (2) Parcel を使う方法

更新:
作成:

Parcel とは

/p/r7fov4b/img-001.png

Parcel は、ゼロ設定 をウリとした Web アプリバンドラーです。 React + TypeScript + SCSS + CSS Modules といった環境を自力でセットアップしようとすると なかなか大変な設定が必要 なのですが、Parcel を使うと、開発環境がサクサクッと完成します。

React プロジェクトを作成するときは、create-react-app を使って雛形を生成する方法もありますが、余計なファイルがたくさん作られたりして、あまり分かりやすいとは言えません。 そんなときは Parcel を使ってみると、そのシンプルさに驚くと思います。

Parcel を使うと、次のような機能がほとんど設定なしでいきなり使えます。 コマンドラインオプションの一覧 を見ると、どのような機能があるかをざっと把握することができます。

  • 開発用の Web サーバー機能(HMR: Hot Module Replacement 対応)
  • TypeScript の自動変換(HTML ファイルから直接ロードする記述が可能)
  • PostCSS による CSS 生成(SCSS、ベンダープレフィックス)
  • CSS ファイルのインポート (CSS Modules)
  • JSON ファイルのインポート
  • PNG ファイルのインポート
  • Pug (Jade) による HTML 生成
  • リリース用の minify

また、Parcel は Node モジュールの自動インストール機能を備えており、parcel コマンドで開発用 Web サーバーを起動すると、自動的に依存モジュールをインストールしてくれます。 例えば、HTML ファイルから TypeScript ファイルを読み込んでいると、typescript モジュールが自動的にインストールされます。

Parcel のインストール

Web サイト用のディレクトリを作成し、parcel モジュールを開発用にインストールします。

$ mkdir myapp && cd myapp
$ npm init -y
$ npm install --save-dev parcel

これで、Parcel の開発用サーバーを起動したり、Web サイトコンテンツのビルドを行えるようになります。

TypeScript のインストールは Parcel による自動インストールに任せることができるので、ここでは手動ではインストールしないことにします。 明示的にインストールしておきたければ次のように実行しておきます。

これは手動で行わなくてよい
$ npm install --save-dev typescript
☝️ ワンポイント 依存モジュールの自動インストール機能は、リリース用ビルド (parcel build) を行う場合はデフォルトで無効になっています。 自動インストール機能は便利ですが、基本的には Node モジュールは明示的にインストールした方がよいかもしれません。 Parcel によって依存モジュールが自動インストールされるときは、package.json の依存情報が更新されます。 更新された package.json をコミットするのを忘れないようにしてください。

Web サイトのコンテンツを用意する

Web サイトのコンテンツとして、適当な HTML ファイルと TypeScript ファイルを用意します。 ソースコードとなる HTML ファイルおよび TypeScript ファイルは、src ディレクトリ以下に配置することにします。

src/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>My App</title>
  <script src="script/index.ts" async></script>
</head>
<body>
  <div id="root"></div>
</body>
</html>

HTML ファイルの中から、直接 TypeScript ファイル (index.ts) を読み込んでいる ことに注目してください。 このように記述しておくだけで、Parcel が自動的に TypeScript トランスパイルを実行し、HTML ファイルから正しい JavaScript ファイルを読み込むように変換してくれます。

下記の TypeScript コードは、上記の HTML ファイルから読み込むファイルです。

src/script/index.ts
const root = document.getElementById('root') as HTMLElement;
root.innerText = 'Hello Parcel';

これくらいのコードであれば TypeScript にする必要はほとんどありませんが、ここでは TypeScript のトランスパイルが自動的に実行されることを示すために .js ではなく .ts ファイルを使用しています。

開発用 Web サーバーを起動する (parcel serve)

src ディレクトリ以下に必要な HTML ファイルと TypeScript ファイルを作成したら、次のように parcel (parcel server) コマンドを実行して、開発用の Web サーバーを起動します。 --open オプションを付けると、自動的に Web ブラウザを開いてくれます。 ポート番号をデフォルトの 1234 から変更したい場合は、--port オプションを使用してください。

$ npx parcel src/index.html --open
Server running at http://localhost:1234
✨  Built in 131ms.

この時点で、TypeScript モジュールがインストールされていない場合は、Parcel が自動的にインストールし、さらにトランスパイルまで実行してくれます。 変換後の HTML ファイルや JavaScript ファイルは、デフォルトで dist ディレクトリに出力されます。 出力先のディレクトリを変更したい場合は、-d オプションを使用します。

開発用の Web サーバーでは HMR (Hot Module Replacement) 機能が有効になっているので、TypeScript コード (index.ts) の内容を変更すると、再起動なしで自動的に反映されます。 root.innerText = 'Hello Parcel' の行を変更して、表示が変更されることを試してみてください。

開発用の Web サーバーでは、環境変数 NODE_ENV=development が設定されます。 これを利用して、開発時のみ有効なコードブロックを記述できます。

if (process.env.NODE_ENV === 'development') {
  // 開発中のみ有効なコード
}

デプロイ用にビルドする (parcel build)

parcel build コマンドを実行すると、公開用の Web サーバーにデプロイするためのファイル群を作成します。

デフォルトの出力先が、parcel (serve) で開発用サーバーを起動した場合と同じ dist ディレクトリになっているので、ファイルが混在しないように、-d オプションで出力先を変えておいた方がよいでしょう。 次の例では、出力先を build ディレクトリに設定しています。

$ npx parcel build src/index.html -d build
✨  Built in 1.50s.

build/script.6d3c0223.js        1.18 KB    757ms
build/script.6d3c0223.js.map      280 B      2ms
build/index.html                  193 B    693ms

あとは、build ディレクトリ以下に生成されたファイル群を Web サーバーにデプロイするだけです。

parcel build によるバンドル時には、デフォルトで HTML、CSS、JS ファイルの圧縮(改行削除や、変数名の簡略化)が有効になっています。 その他の特徴は、下記の公式ドキュメントを参照してください。

package.json にビルド用スクリプトを追加する

ここまでの例では、直接 parcel コマンドを実行していましたが、npm のスクリプトとして登録しておくと便利です。

package.json(抜粋)
"scripts": {
  "start": "parcel src/index.html --open",
  "build": "rm -r build && parcel build src/index.html -d build"
},

このように記述しておくと、npm start で開発用サーバーの起動、npm run build で本番用ビルドを行えるようになります。

☝️ build ディレクトリの削除 parcel build コマンドは、前回のビルド時に出力したファイルを削除してくれません。 余計なファイルがデプロイされてしまうのを防ぐため、上記のように build スクリプトで古いファイルを削除しておくとよいでしょう。

React (Preact) のセットアップ

次に React コンポーネント (.tsx) を読み込めるようにしてみます。

React 関連の NPM パッケージは先に手動でインストールしておきます。 React 本体は Parcel による自動インストールに任せることもできるのですが、TypeScript の型定義ファイルは自動インストールしてくれないみたいです。

$ npm install --save react react-dom
$ npm install --save-dev @types/react @types/react-dom

ここでは、次のような簡単な App コンポーネントを作って表示してみます。 JSX コードが含まれているので、拡張子を .tsx にすることに注意してください。

src/script/components/App.tsx
import * as React from 'react';

export const App: React.FC = () => {
  return <h1>Hello React</h1>;
};

index.ts ファイルの拡張子も .tsx に変更し、上記の App コンポーネントを読み込んで使用します。

src/script/index.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { App } from './components/App';

ReactDOM.render(<App />, document.getElementById('root'));

最後に index.html ですが、index.ts ではなく index.tsx を読み込むようにするところだけ変更します。

src/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>My App</title>
  <script src="script/index.tsx" async></script>
</head>
<body>
  <div id="root"></div>
</body>
</html>

あとは何も設定しなくても、次のようにおもむろに Parcel の開発用サーバを起動するだけで、React を使った Web サイトが表示されます。

$ npm start

本当に「ゼロ設定」ですね! こんなに簡単でよいのでしょうか。

おまけ:SCSS の有効化

生の CSS をゴリゴリ記述するのはつらいので、SCSS は使えるようにしておいた方がよいでしょう。 Parcel で SCSS を使用するには、ちょっとだけ設定が必要です。

Parcel で SCSS を使うための設定

公式サイト の記述通りですが、次のように sass モジュールをインストールし、

sass モジュールのインストール
$ npm install --save-dev sass

下記のような設定ファイル (.sassrc) をプロジェクトルートに配置すれば OK です。

.sassrc
{
  "includePaths": ["node_modules"]
}

SCSS を使ってみる

次のような簡単な SCSS ファイルを使ってみます。

src/css/main.scss
$BG_COLOR: yellow;

.app-title {
  background: $BG_COLOR;
}

あとは、HTML ファイルから次のように参照しておけば、

src/index.html(抜粋)
<link rel="stylesheet" href="css/main.scss">

React コンポーネント (.tsx) から次のように CSS クラスを参照できるようになります。

src/script/components/App.tsx
import * as React from 'react';

export const App: React.FC = () => {
  return <h1 className="app-title">Hello React</h1>
};

index.html の中でスタイルシートを読み込むのではなく、.tsx の中でインポートして参照することもできます。 この場合は、.tsx ファイルのあるディレクトリからの相対パスになることに注意してください。 CSS クラスの使用方法は上記と同様です。

import '../../css/main.scss';

ただ、このように .tsx ファイルの中から CSS ファイルをインポートするのであれば、下記の CSS Modules の仕組みを使って、そのコンポーネント専用の CSS ファイルをインポートするようにした方がよいでしょう。

おまけ:CSS Modules の有効化

CSS Modules の仕組みを使用するときも、ちょっとだけ設定が必要です。

Parcel で CSS Modules を使うための設定

ほぼ 公式サイトの説明 通りですが、postcss-modules のインストールが必要です。

$ npm install --save-dev postcss-modules

そして、プロジェクトルートに次のような設定ファイル (.postcssrc) を配置します。

.postcssrc
{
  "modules": true
}

CSS Modules を使ってみる

CSS Modules の仕組みでスタイル定義する場合は、React コンポーネントと同じディレクトリに .css ファイル(あるいは .scss ファイル)を置いておくと、コンポーネントとしてのまとまりがよくなります。 前述の SCSS の設定を行っておけば、.scss ファイルも CSS Modules の仕組みで読み込むことができます。

下記の App.scss ファイルでは、App コンポーネント用のスタイル定義を行っています。 CSS Modules の仕組みを使うと、CSS クラス名の名前空間がファイルごとに分離されるため、シンプルなクラス名を付けることができます。

src/script/components/App.scss
.title {
  background: red;
}

ここでは、app-title というクラス名から app プレフィックスを省略して、title というシンプルなクラス名に変更してみました。 モジュール化されたスタイシートは、.tsx ファイルから次のように使用します。

src/script/components/App.tsx
import * as React from 'react';
import styles from './App.scss';

export const App: React.FC = () => {
  return <h1 className={styles.title}>Hello React</h1>
};

関連記事

まくろぐ
サイトマップまくへのメッセージ