世界一わかりみが深いかもしれないフロントエンドのビルド周り

みなさん、こんにちは。サイオステクノロジー武井です。今回は「世界一わかりみが深いかもしれないフロントエンドのビルド周り」と題しまして、多分C言語のポインタやAzureのサービスプリンシパルと同じくらい苦しむであろうフロントエンドのビルド周りをまとめたいと思います。いつも「あれ、なんだっけ?」的な感じになるので、備忘録的な意味も込めています。

複雑なフロントエンドのビルド周り

そう思っているのは、多分、私だけじゃないかと思います。とにかくReactなどのフロントエンドのビルド周りは複雑です。webpack、babel、esbuild、polyfillなんてたくさんの横文字が飛び交い、最近はViteなるものも登場してきました。初めてフロントエンドをやる人は、混乱してしまうかもしれません。

ただ、これらの複雑な処理を理解するのには、やっぱり基本に立ち返ることが大事です。

ビルドに必要なプロセス

フロントエンドのビルド周りは、たくさんの横文字ツールが乱立していますが、理解するには以下の2点が大事だと思います。

  • ビルドまでに必要なプロセスはどういうものがあって、どのような順序で行われるか?
  • 数多あるツールは、どのプロセスを処理することができるか?

この2点をきちんと理解することで、私はフロントエンド周りの複雑性をしっかりと把握し、納得することができました。

ちなみに、ここでいうビルドとは、開発者が書いたソースコード(TypeScript)を、ブラウザで実行できる最適化された形式に変換するプロセスのことを指します。TypeScriptはJavaScriptを拡張し、型の概念を取り入れたものです。TypeScriptはそのままではブラウザ上で動作しないため、JavaScriptに変換する必要があります。この変換プロセスを「ビルド」と呼びます。

ということで、本章では「ビルドに必要なプロセス」を説明します。そのプロセスは以下のとおりです。これはReactだろうがVueだろうが、概ね変わりません。

  • 型チェック
  • TypeScript→JavaScript変換
  • TSX/JSX→JavaScript変換
  • ECMAScript構文変換
  • Polyfill
  • バンドル

これを図解したのが以下になります。

以降は、これらのプロセスを詳しく説明していきます。

型チェック

TypeScriptには、JavaScriptにはない「型」というものが存在します。それがTypeScriptがTypeScriptである由縁なのですが、その型が正しいかどうかをチェックします。

型チェックについて改めておさらいしますが、型チェック、プログラムの中で変数や関数が正しいデータの種類(型)を持っているかどうかを確認する作業です。これにより、プログラムが意図しない動作をするのを防ぎます。

具体的な例で解説してみます。

let age: number = 25; // ここでageは数字(number)の型

age = "twenty-five";  // ここでエラー!文字列(string)を数字に代入しようとしている

このコードは、ageという変数が最初に数字の型として宣言されています。しかし、後で文字列を代入しようとすると、型チェックがエラーを検出します。これは、TypeScriptが型の一致を確認しているためです。

上記のコードをコンパイルしようとすると、エラーになります。

では、具体的に型チェックを行う方法をご紹介します。

まずは、TypeScriptをインストールします。

$ npm install typescript

型チェックを行いたいTypeScriptファイルを作成します。例えば、index.tsというファイルを作成し、以下のコードを追加します。

// index.ts
let age: number = 25;

age = "twenty-five"; // ここで型エラーが発生します

以下のコマンドを実行します。

$ tsc index.ts --noEmit --target es5 --module commonjs --strict --jsx react --skipLibCheck

以下のようなエラーメッセージが表示されます。このエラーメッセージは、「文字列型は数値型に代入できません」という意味です。

index.ts:3:1 - error TS2322: Type 'string' is not assignable to type 'number'.

3 age = "twenty-five"; // ここで型エラーが発生します
  ~~~~~~~~~~~~~~~~~~

このエラーメッセージは、「文字列型は数値型に代入できません」という意味です。

--noEmitオプションは、型チェックのみ実施し、ビルドは行いません。tscコマンドは、TypeScript純正のコマンドであり、型チェックに限らず、Polyfillを除くビルドプロセス(TypeScript→JavaScript変換、TSX/JSX→JavaScript変換、ECMAScript構文変換)ができるのですが、ここでは型変換のみ行っています。型変換以外は、他のツールに任せたほうが都合がいいからです。

TSX/JSX→JavaScript変換

TSX/JSX→JavaScript変換は、Reactで使われる特別な構文(TSXやJSX)を普通のJavaScriptに変換するプロセスです。これは、ブラウザが直接TSXやJSXを理解できないためです。この変換にはBabelなどのツールが使われます。

例えば、以下はよくあるReactのコードです。

// App.tsx
import React from 'react';

type AppProps = {
  message: string;
};

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

export default App;

<h1>{message}</h1>の部分が、JSXの部分に該当するのですが、もちろんこの構文はJavaScriptエンジンでは理解できません。なので、このプロセスでJavaScriptに変換して上げる必要があるのです。

ECMAScript構文変換

これが一番説明が難しい部分ですので、順を追って説明します。まず、JavaScriptの歴史についてお話しします。JavaScriptは様々な事情により、多くのバージョンアップを経て進化してきました。このバージョンアップの変遷を理解することが非常に重要です。

次に、ES5からES6へのバージョンアップについて説明します。この劇的なバージョンアップが、このプロセスの存在理由と言っても過言ではありません。

最後に、具体的なソースコードを例に挙げて、ES6でしか使えないコードをES5に変換する事例を紹介します。

JavaScriptの歴史

JavaScriptは1995年にNetscape社のBrendan Eichによって開発されました。最初はウェブページに動的な機能を追加するためのスクリプト言語として誕生しました。JavaScriptの進化は以下のような段階を経ています:

  • 1997年: 最初の標準仕様であるECMAScript 1が登場。
  • 1998年: ECMAScript 2が登場。これはマイナーアップデート。
  • 1999年: ECMAScript 3が登場。正規表現や例外処理、文字列操作機能が追加されました。
  • 2009年: ECMAScript 5(ES5)が登場。厳密モード、アクセサプロパティ、Arrayメソッドの追加など、大幅な改良が行われました。
  • 2015年: ECMAScript 6(ES6)またはECMAScript 2015が登場。クラス構文、モジュール、アロー関数、テンプレートリテラルなど、多くの新機能が追加され、JavaScriptは大幅に進化しました。

最新のバージョンはECMAScript 2023で、毎年更新されています。

ES5からES6への劇的なバージョンアップ

ES6は、JavaScriptに多くの新機能と構文を導入したため、非常に大きなアップデートでした。しかし、古いブラウザはES6の機能をサポートしていないため、ES6で書かれたコードをES5に変換する必要があります。これをECMAScript構文変換と呼びます。主にBabelというツールを使って行ってきましたが、最近はViteなるものも台頭しつつあります。

具体的なソースコード例

ということで、ではES6でしか動かないコードをES5に変換する様子をお見せします。

まず、ES6のコードを示します。これは古いブラウザではサポートされていないコードです。このコードは、アロー関数とテンプレートリテラルを使用しています。古いブラウザではこれを理解できません。

// ES6コード
const greet = (name) => `Hello, ${name}!`;

次に、上記のES6コードをES5に変換したコードを示します。この変換によって、アロー関数は通常の関数表現に、テンプレートリテラルは文字列連結に置き換えられました。これにより、古いブラウザでも動作するようになります。

// ES5コード
var greet = function(name) {
  return 'Hello, ' + name + '!';
};

つまりまとめますと、以下になります。

  1. JavaScriptの歴史: 1995年に誕生し、ECMAScript 5(2009年)からECMAScript 6(2015年)へと劇的に進化。
  2. ES6の新機能: アロー関数、クラス構文、モジュール、テンプレートリテラルなどが追加されました。
  3. ECMAScript構文変換: 最新のJavaScript(ES6など)を古いブラウザ(ES5)で動作させるためにコードを変換するプロセス。具体例として、アロー関数とテンプレートリテラルの変換を示しました。

Polyfill

Polyfillは、最新のJavaScript機能をサポートしていない古いブラウザや環境でその機能を提供するためのコードやライブラリです。Polyfillの主な方法は以下の2つです:

  1. 自分で置き換えるためのコードを書く: 必要な機能を自分で実装する。
  2. ライブラリを使ってコードを読み込む: 既存のライブラリを使ってPolyfillを簡単に導入する。

先のECMAScript構文解析は、古いブラウザも動くようにコードを書き換えることですが、Polyfillは新しい機能を古い環境で提供するための追加コードです。これは実際のソースコードをお見せして説明したほうが早いかと思います。

まずはPolyfill前のコードです。このコードは、JavaScriptのArray.prototype.includesメソッドを使用しています。このメソッドは、配列内に特定の要素が存在するかどうかを調べて、その結果を真偽値(trueまたはfalse)で返します。

const array = [1, 2, 3];
console.log(array.includes(2)); // true

実は、このArray.prototype.includesメソッドは、ES2016(ES7)で導入された新しいJavaScriptの機能です。そのため、ES2016以前のバージョンのJavaScriptをサポートしている古いブラウザでは、このメソッドを直接使用することはできません。

よって、Polyfillによる変換が必要になります。先ほどご説明した2つの方法「自分で置き換えるためのコードを書く」「ライブラリを使ってコードを読み込む」で変換したいと思います。

では、まず最初に「自分で置き換えるためのコードを書く」です。このコードは、古いブラウザでArray.prototype.includesメソッドが存在しない場合に、そのメソッドを自分で実装しています。これにより、古いブラウザでもincludesメソッドを使えるようになります。

if (!Array.prototype.includes) {
  Array.prototype.includes = function(searchElement, fromIndex) {
    if (this == null) {
      throw new TypeError('"this" is null or not defined');
    }
    const o = Object(this);
    const len = o.length >>> 0;
    if (len === 0) {
      return false;
    }
    const n = fromIndex | 0;
    let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
    while (k < len) {
      if (o[k] === searchElement) {
        return true;
      }
      k++;
    }
    return false;
  };
}

const array = [1, 2, 3];
console.log(array.includes(2)); // true

次は、もう一つの方法「ライブラリを使ってコードを読み込む」です。この方法は、手動でPolyfillを実装する代わりに、既存のライブラリを使用して必要な機能を追加するものです。一般的に使用されるライブラリの1つがcore-jsです。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Polyfill Example</title>
  </head>
<body>
  <script src="https://cdn.jsdelivr.net/npm/core-js-bundle/minified.js"></script>
  <script>
    const array = [1, 2, 3];
    console.log(array.includes(2)); // core-jsライブラリがArray.prototype.includesをサポート
  </script>
</body>
</html>

core-jsは、最新のJavaScript機能を古いブラウザでも使用できるようにするためのPolyfillライブラリです。このライブラリは、ECMAScriptの標準機能だけでなく、その他のプロポーザルやグローバルオブジェクトのPolyfillも提供します。

このPolyfillライブラリは、core-js以外にもpolyfill.ioやes5-shimなどいろいろあります。

バンドル

バンドルとは、複数のJavaScriptファイルを一つのファイルにまとめることです。これにより、ウェブページの読み込み速度が向上し、依存関係の管理が容易になります。バンドルは、webpackなどのツールを使って行います。

バンドルが必要な理由については以下のとおりです。

  1. 依存関係の管理: 大規模なプロジェクトでは、多くのJavaScriptファイルが互いに依存しています。バンドルを使うと、これらの依存関係を自動的に解決し、正しい順序でファイルを読み込むことができます。
  2. 読み込み速度の向上: 複数のJavaScriptファイルを一つにまとめることで、HTTPリクエストの数を減らし、ウェブページの読み込み速度を向上させます。
  3. モジュール化: モジュールごとにコードを分割して開発できます。これにより、コードの再利用性が高まり、保守性が向上します。

では、ここでwebpackを使って簡単な具体例をご紹介します。その具体例を図解したのが以下です。index.jsとmath.jsという2つのJavaScriptファイルがあり、webpackはwebpack.config.jsという設定ファイルを参照してバンドルコマンドを実施し、index.jsとmath.jsをbundle.jsという一つのファイルにガッチャンコします。index.htmlはそのガッチャンコされたbundle.jsを参照します。

まずは、プロジェクトディレクトリを作成し、必要なパッケージをインストールします。

$ mkdir my-bundle-project
$ cd my-bundle-project
$ npm init -y
$ npm install webpack webpack-cli --save-dev

一つにまとめる(バンドル)する前の、複数のJavaScriptファイルを作成します。まずは、足し算を掛け算をするモジュールをexportするJavaScriptを作成します。

// src/math.js
export function add(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}

次に、足し算と掛け算をするモジュールを使って計算するJavaScriptを作成します。

// src/index.js
import { add, multiply } from './math';

console.log(add(2, 3)); // 5
console.log(multiply(2, 3)); // 6

これらのファイルをバンドルするためのwebpackの設定ファイル「webpack.config.js」を作成します。

const path = require('path');

module.exports = {
  // バンドルの開始時点である最初のファイルを指定する。このファイルから
  // 順々に依存関係をたどっていって、必要なファイルを一つにまとめる
  entry: './src/index.js',

  // バンドルしたファイルを出力するためのディレクトリとファイル名を指定する。
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  mode: 'development'
};

webpackのコマンドを実行して、バンドルファイルを作成します。

$ npx webpack

このコマンドを実行すると、distディレクトリにbundle.jsというファイルが生成されます。このファイルには、index.jsmath.jsの内容がすべて含まれています。

最後にdistディレクトリにindex.htmlファイルを作成し、バンドルされたJavaScriptファイルを読み込みます。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JavaScript Bundling Example</title>
  </head>
<body>
  <script src="bundle.js"></script>
</body>
</html>

ビルドを実現するいろいろなツール

ビルドを実現するためのツールには色々ありまして、いろんなものを組み合わせ使うのが普通です。その数多あるビルドツールの中で今回は以下を取り上げようと思います。

  • tsc (Type Script Compiler)
  • webpack
  • Babel
  • create react app
  • Vite

それぞれのツールの概要と担当可能なビルドプロセスをご説明致します。

tsc (Type Script Compiler)

tscは、TypeScriptの公式コンパイラです。TypeScriptコードをJavaScriptに変換します。

  • 型チェック: TypeScriptコードの型チェックを行います。
  • TypeScript→JavaScript変換: TypeScriptコードをJavaScriptに変換します。
  • TSX/JSX→JavaScript変換: TSX/JSXをJavaScriptに変換します。

webpack

webpackは、モジュールをバンドルするためのツールです。複数のJavaScriptファイルやその他のリソースを一つのバンドルファイルにまとめます。これにより、依存関係が解決され、効率的なコード配信が可能になります。

バンドルに加えて、ローダーという仕組みを使うことで機能を拡張できます。ローダーを使うと、JavaScript以外のファイル(TypeScript、CSS、画像ファイルなど)もバンドルに含めることができるようになります。

babelとtscのローダーを使うことで、webpackから以下のようにバンドル以外のこともできます。

  • バンドル: Webpackの主な役割は、複数のJavaScriptファイルを一つにまとめることです。
  • ECMAScript構文変換: Babelと組み合わせることで、最新のJavaScript構文を古いブラウザでも動作するように変換できます。
  • TypeScript→JavaScript変換: ts-loaderを使って、TypeScriptをJavaScriptに変換します。
  • TSX/JSX→JavaScript変換: Babelと組み合わせて、TSX/JSXをJavaScriptに変換します。

Babel

Babelは、JavaScriptのトランスパイラです。最新のJavaScript機能を古いバージョンのJavaScriptに変換します。

  • ECMAScript構文変換: 最新のJavaScript構文を古いJavaScriptに変換します。
  • TSX/JSX→JavaScript変換: TSX/JSXをJavaScriptに変換します。
  • Polyfill: 必要に応じて、core-jsなどのライブラリと連携してPolyfillを提供します。

ちなみに発音は「ばべる」ではなく「ばぶる」と読むらしいです。

create react app

Create React App (以降、CRAと省略)は、Reactアプリケーションを素早くセットアップするための公式CLIツールです。CRAは、開発者がすぐにReactプロジェクトを開始できるように、必要な設定や依存関係を自動的に管理します。CRAは内部的にwebpack、Babel、tscなどのツールを使用していますが、それらの詳細を開発者が直接扱う必要はありません。

  • 型チェック: TypeScriptプロジェクトの場合、tscを使用。
  • TypeScript→JavaScript変換: TypeScriptコードをJavaScriptに変換します。
  • TSX/JSX→JavaScript変換: Babelを使用して、TSX/JSXをJavaScriptに変換します。
  • ECMAScript構文変換: Babelを使用して、最新のJavaScript構文を古いバージョンに変換します。
  • Polyfill: 必要に応じてPolyfillを提供します。
  • バンドル: Webpackを使用して、複数のファイルを一つにまとめます。

Vite

Viteは、次世代のフロントエンドツールで、高速なビルドと開発サーバーを提供します。ESモジュールをネイティブにサポートし、開発中のホットモジュールリプレースメント(HMR)を実現します。

  • 型チェック: vite-plugin-checkerを使って型チェックを行うことができます。
  • TypeScript→JavaScript変換: TypeScriptをサポートし、JavaScriptに変換します。
  • TSX/JSX→JavaScript変換: TSX/JSXをJavaScriptに変換します。
  • ECMAScript構文変換: ESモジュールをサポートし、必要に応じて変換します。
  • Polyfill: 必要に応じてPolyfillを提供します。
  • バンドル: 高速なビルドを行い、ファイルをバンドルします。

 

ツールとプロセスの対応表

今まで説明してきたツールと各プロセスの対応表を以下にまとめました。

ツール 型チェック TypeScript→JavaScript変換 TSX/JSX→JavaScript変換 ECMAScript構文変換 Polyfill バンドル
Webpack ◯ (`ts-loader`使用) ◯ (`babel-loader`使用) ◯ (Babelと組み合わせ) ◯ (Babelと組み合わせ)
Babel
tsc
Vite
Create React App

ビルドツールの変遷

フロントエンドのビルドツールは、webpackからCreate React App(CRA)、そしてViteへと進化してきました。それぞれのツールが登場した背景と、それに伴うメリット・デメリットを理解することで、なぜこのような変遷を遂げたのかが見えてきます。

最初に登場したwebpackは、2012年に開発されたモジュールバンドラーです。webpackの主な目的は、複数のJavaScriptファイルやその他のリソースを一つのバンドルファイルにまとめることでした。これにより、依存関係の管理が簡単になり、効率的なコード配信が可能となりました。webpackは、柔軟性が非常に高い一方で、その設定が非常に複雑であり、学習コストが高いという課題がありました。初心者にとって、プロジェクトの初期設定が難しく、手間がかかることが多かったのです。

次に登場したCreate React App(CRA)は、2016年にFacebookによって開発されました。CRAはwebpackやBabelを内部で使用しながらも、その設定を隠蔽し、開発者が簡単にReactプロジェクトを開始できるように設計されています。CRAの主な特徴は、ゼロ設定でプロジェクトを開始できること、一貫した開発環境を提供すること、そしてテスト、ビルド、デプロイなどのツールが内蔵されていることです。これにより、開発者はすぐに開発に集中できる環境を手に入れることができました。しかし、CRAには柔軟性の制限があり、内部の設定が隠蔽されているため、独自の設定やカスタマイズが難しいというデメリットがありました。また、大規模なプロジェクトでは、ビルド速度が遅くなることがあるという問題もありました。

最後に登場したViteは、2020年にVue.jsの開発者であるEvan Youによって開発されました。Viteは、次世代のフロントエンドビルドツールであり、ESモジュール(ESM)をネイティブにサポートし、従来のツールよりも高速なビルドと開発体験を提供します。Viteの主な利点は、高速なビルド、特に開発サーバーの起動が非常に速いことです。これにより、開発時のビルドが大幅に短縮されます。さらに、ホットモジュールリプレースメント(HMR)により、コードの変更が即座に反映され、開発者の生産性が向上します。また、Viteはデフォルト設定がシンプルで、必要に応じて簡単にカスタマイズできるため、設定の複雑さが大幅に軽減されます。Viteは、ビルド時にモジュールを自動的に最適化するため、大規模なプロジェクトでも効率的に動作します。

このように、ビルドツールは、設定の簡略化、高速ビルド、柔軟なカスタマイズなどの要件に応じて進化してきました。webpackは柔軟性が高い一方で設定が複雑であり、CRAは設定の簡略化と一貫性を提供しましたが柔軟性に制限がありました。Viteはこれらの問題を解決し、より高速で柔軟な開発環境を提供することで、現代のフロントエンド開発に適したツールとなりました。

ビルドしてみよう

では実際にビルドをしてみましょう。最近はViteが流行っているのは先に述べたとおりですが、ここではあえてwebpackを使います。CRAやViteだといろんな処理が隠蔽されすぎて中でやっていることが見えなくなるためです。遠回りかもしれませんが、何事も基礎をしっかり学ぶことが、どこでも通用する普遍的な能力を身につけるための近道です。

このビルドのプロセスで実施することを以下に図解しました。

今回試してみるビルドプロセスの詳細は以下のとおりです。

  1. 型チェック

    • webpackは、fork-ts-checker-webpack-pluginというプラグインを使って行います。これは型チェックを他のビルドプロセスと並列で行うことができまして、ビルドの実行速度を向上できます。fork-ts-checker-webpack-pluginは、裏でtscコマンドを呼び出し、TypeScriptの型チェックを行います。これは、TypeScriptのコンパイルとは別に、型チェック専用のプロセスとして実行されます。
    • つまり、fork-ts-checker-webpack-pluginは、他のビルドプロセスと並行して型チェックを行い、ビルドプロセスとは独立して動作します。
  2. TypeScript→JavaScript変換とTSX/JSX→JavaScript変換

    • webpackは、ts-loaderを使用してTypeScriptファイル(.tsおよび.tsx)をJavaScriptに変換します。webpackはローダーという仕組みで機能を拡張することができます。TypeScript→JavaScriptへの変換はts-loader内部で行われます。ts-loadertscコマンドを直接呼び出すわけではありませんが、TypeScriptコンパイラAPIを利用しています。
    • ここで、ts-loadertranspileOnly: true設定により、トランスパイルのみを行い、型チェックは省略します。型チェックは前述のfork-ts-checker-webpack-pluginで行われます。
  3. ECMAScript構文変換とPolyfill

    • TypeScriptの変換が完了した後、babel-loaderを使用して、Babelを通じて最新のJavaScript構文に変換し、必要なPolyfillを適用します。この段階で、TypeScriptから生成されたJavaScriptは、さらに最新のJavaScript機能をサポートするために変換されます。
  4. バンドル

    • 最終的に、webpackがすべての変換済みJavaScriptファイルをバンドルし、一つのファイル(または複数のチャンク)にまとめます。このプロセスでは、依存関係を解析し、効率的にコードを結合します。

ステップ 1: プロジェクトのセットアップ

まずはプロジェクトをセットアップします。

$ mkdir my-webpack-project
$ cd my-webpack-projec
$ npm init -y

ステップ 2: 必要なパッケージのインストール

TypeScript、Webpack、および関連するパッケージをインストールします。

$ npm install --save-dev typescript ts-loader webpack webpack-cli webpack-dev-server @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript babel-loader html-webpack-plugin fork-ts-checker-webpack-plugin @types/react @types/react-dom
$ npm install react react-dom

上記のコマンドでインストールされるパッケージの詳細は以下のとおりです。

開発用依存パッケージ(--save-dev

これらのパッケージは、開発環境を設定し、ビルドプロセスを管理するために使用されます。

  1. TypeScript

    • typescript: TypeScriptコンパイラ。TypeScriptコードをJavaScriptに変換し、型チェックを行います。ここでは型チェックの機能のみしか使用しません。
  2. TypeScriptローダー

    • ts-loader: webpack用のTypeScriptローダー。TypeScriptファイルをJavaScriptに変換するために使用されます。
  3. Webpackと関連ツール

    • webpack: モジュールバンドラー。複数のJavaScriptファイルやその他のリソースを一つのバンドルファイルにまとめます。
    • webpack-cli: webpackのコマンドラインインターフェース。webpackの設定をコマンドラインから実行するために必要です。
    • webpack-dev-server: 開発用サーバー。ローカル開発環境でホットリロードを提供し、リアルタイムで変更を反映します。
  4. Babelと関連ツール

    • @babel/core: Babelのコアライブラリ。JavaScriptのトランスパイラであり、最新のJavaScript構文を古いバージョンに変換します。
    • @babel/preset-env: Babelのプリセット。ターゲット環境に合わせて必要なトランスパイルを自動的に行います。
    • @babel/preset-react: Babelのプリセット。JSXをJavaScriptに変換するために必要です。
    • @babel/preset-typescript: Babelのプリセット。TypeScriptをJavaScriptに変換するために使用されます。
    • babel-loader: webpack用のBabelローダー。Babelを使用してJavaScriptやTypeScriptコードをトランスパイルします。
  5. HTMLプラグイン

    • html-webpack-plugin: webpackプラグイン。バンドルされたJavaScriptを自動的に含むHTMLファイルを生成します。
  6. 型チェックプラグイン

    • fork-ts-checker-webpack-plugin: webpackプラグイン。型チェックを他のプロセスと並列で行うことができます。
  7. 型定義ファイル

    • @types/react: Reactの型定義ファイル。TypeScriptでReactを使用するために必要です。
    • @types/react-dom: ReactDOMの型定義ファイル。TypeScriptでReactDOMを使用するために必要です。

実行時依存パッケージ

これらのパッケージは、実際にアプリケーションが動作するために必要な依存関係です。

  1. ReactとReactDOM
    • react: Reactのコアライブラリ。UIコンポーネントを作成するためのライブラリです。
    • react-dom: ReactDOMライブラリ。ReactコンポーネントをDOMにレンダリングするために使用されます。

ステップ3: tsconfig.jsonの作成

プロジェクトのルートディレクトリにtsconfig.jsonファイルを作成し、以下の内容を追加します。tsconfig.jsonはtscコマンド実行時の設定ファイルになります。

{
    "compilerOptions": {
    "target": "esnext", // 最新のJavaScript機能をターゲット
    "module": "esnext", // ESモジュールをターゲット
    "strict": true, // 厳密な型チェックを有効にする
    "esModuleInterop": true, // ESモジュールとの互換性を有効にする
    "skipLibCheck": true, // ライブラリの型チェックをスキップ
    "noEmit": true, // 型チェックのみを実施
    "moduleResolution": "node"
  },
"include": ["src/**/*"]
}

上記の設定ファイルの詳細な説明は以下のとおりです。

  • target: esnextを指定することで、コンパイラは最新のECMAScript仕様に準拠したコードを生成します。例えば、async/awaitや最新の構文をサポートします。トランスパイル(ECMAScript構文解析)はbabelに任せるので、ここはとりあえず最新の構文で出力します。
  • module: esnextを指定することで、ESモジュールを使用する設定です。これは、JavaScriptの標準モジュールシステムであり、他のモジュールシステム(CommonJSなど)よりも新しいものです。
  • strict: TypeScriptの厳格モードを有効にします。これにより、型の厳密なチェックが行われ、潜在的なバグを未然に防ぐことができます。具体的には、以下のオプションが含まれます:
    • noImplicitAny: 型が明示されていない変数に対して暗黙的にany型を許可しない。
    • strictNullChecks: nullおよびundefinedのチェックを厳格に行う。
    • strictFunctionTypes: 関数型の互換性を厳格にチェックする。
  • esModuleInterop: このオプションを有効にすることで、ESモジュールとCommonJSモジュールの互換性が向上します。特に、import文でCommonJSモジュールをインポートする際に、デフォルトエクスポートが正しく扱われます。
  • skipLibCheck: node_modulesフォルダ内のライブラリファイルの型チェックをスキップします。これにより、コンパイル時間が短縮されますが、外部ライブラリの型定義の誤りが見逃されるリスクがあります。
  • noEmit: 型チェックのみを実施します。それ以外のプロセスはBabelなどに任せます。
  • moduleResolution: nodeを指定することで、Node.jsのモジュール解決アルゴリズムを使用します。これにより、node_modulesフォルダ内のモジュールが適切に解決されます。
  • include: 型チェック対象のファイルを指定します。ここでは、srcフォルダ内のすべてのファイルとサブフォルダが対象となります。

ステップ4: Babel設定ファイルの作成

プロジェクトのルートディレクトリにbabel.config.jsonファイルを作成し、以下の内容を追加します。

{
  "presets": [
    ["@babel/preset-env", {
      "targets": "> 0.25%, not dead", // ターゲットブラウザを指定
      "useBuiltIns": "entry",
      "corejs": 3
    }],
    "@babel/preset-react",
    "@babel/preset-typescript"
  ]
}

上記の設定ファイルの詳細な説明は以下のとおりです。

1. @babel/preset-env

@babel/preset-envは、最新のJavaScript機能をターゲットとする環境に合わせて必要なPolyfillやトランスパイルを自動的に行うためのBabelプリセットです。以下は、その設定オプションの詳細です。

  • targets: このオプションは、トランスパイルのターゲットとなるブラウザや環境を指定します。

    "targets": "> 0.25%, not dead"
    • "> 0.25%, not dead": 使用頻度が0.25%以上で、サポートが終了していない(”not dead”)すべてのブラウザを対象としています。これにより、一般的に利用されている最新のブラウザをカバーしつつ、古すぎるブラウザ(例えばIE 10など)を除外します。
    • 具体的には、ユーザーシェアが0.25%以上のブラウザで、まだサポートが続いているものが対象となります。
  • useBuiltIns: Polyfillのインポート方法を指定します。

    "useBuiltIns": "entry"
    
    • "entry": アプリケーションのエントリーポイントで一度だけ必要なPolyfillをインポートします。これにより、必要なPolyfillだけがインポートされ、不要なものはインポートされません。
    • エントリーポイントで以下のように指定します:
      import "core-js/stable";
      import "regenerator-runtime/runtime";
  • corejs: 使用するcore-jsのバージョンを指定します。

    "corejs": 3
    
    • "corejs": 3: core-jsのバージョン3を使用します。core-jsは標準ライブラリのPolyfillを提供するライブラリです。これにより、最新のJavaScript機能が古いブラウザでも利用できるようになります。

2. @babel/preset-react

@babel/preset-reactは、Reactコードをトランスパイルするためのプリセットです。これにより、JSX構文を標準のJavaScriptに変換し、Reactアプリケーションを構築できるようにします。

  • JSXをJavaScriptに変換します。具体的には、以下のようなJSXコード:
    const element = <h1>Hello, world!</h1>;
    

    が、ReactのcreateElementを使用したJavaScriptコードに変換されます:

    const element = React.createElement('h1', null, 'Hello, world!');
    

3. @babel/preset-typescript

@babel/preset-typescriptは、TypeScriptコードをJavaScriptにトランスパイルするためのプリセットです。Babelは型チェックを行わず、単純に構文を変換するだけなので、型チェックはTypeScriptのコンパイラ(tsc)を使用して行う必要があります。

  • TypeScriptコードをJavaScriptに変換します。以下のようなTypeScriptコード:
    const greeting: string = "Hello, world!";

    が、型注釈を削除したJavaScriptコードに変換されます:

    const greeting = "Hello, world!";

ステップ5: webpack設定ファイルの作成

プロジェクトのルートディレクトリにwebpack.config.jsファイルを作成し、以下の内容を追加します。

const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ForkTsChecker = require('fork-ts-checker-webpack-plugin')
module.exports = {
  entry: './src/index.tsx',       // エントリポイントを指定
  output: {
    filename: 'bundle.js',        // 出力ファイル名
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "src/index.html"),
    }),
    new ForkTsChecker()
  ],
  resolve: {
    extensions: ['.ts', '.tsx', '.js']  // 拡張子を解決
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,          // .ts, .tsxファイルを処理
        use: [
          {
            loader: 'ts-loader',
            options: {
              transpileOnly: true // TypeScript→JavaScript構文解析のみ行う
            }
          }
        ],        
        exclude: /node_modules/
      },
      {
        test: /\.jsx?$/,          // .js, .jsxファイルを処理
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader', // Babelでトランスパイル
        }
      }
    ]
  },
  mode: "development",
  devServer: {
    static: {
      directory: path.resolve(__dirname, 'dist'),
    },
    compress: true,
    port: 9000
  }
};

上記の設定ファイルの詳細な説明は以下のとおりです。

1. entry

entry: './src/index.tsx'
  • 説明: Webpackがバンドルの作成を開始するエントリーポイントです。この設定では、./src/index.tsxがエントリーポイントとして指定されています。
  • 詳細: エントリーポイントは、アプリケーションの依存関係のルートファイルであり、ここから全ての依存ファイルが解決されます。TypeScriptとReactのプロジェクトでindex.tsxファイルをエントリーポイントとして指定します。

2. output

output: {
  filename: 'bundle.js',
  path: path.resolve(__dirname, 'dist')
}
  • 説明: バンドルされたファイルの出力設定です。
  • filename: バンドルファイルの名前を指定します。この設定では、bundle.jsという名前で出力されます。
  • path: 出力先ディレクトリを絶対パスで指定します。path.resolve(__dirname, 'dist')は、現在のディレクトリにあるdistフォルダを指します。

3. plugins

plugins: [
  new HtmlWebpackPlugin({
    template: path.resolve(__dirname, "src/index.html"),
  }),
  new ForkTsChecker()
]
  • 説明: Webpackのプラグインを設定します。ここでは、HtmlWebpackPluginを使用しています。
  • HtmlWebpackPlugin: このプラグインは、バンドルされたJavaScriptファイルを自動的にHTMLテンプレートに挿入します。
  • ForkTsChecker: このプラグインは、型チェックを他のプロセスと並列で行うことができます。
  • template: テンプレートとして使用するHTMLファイルを指定します。この設定では、src/index.htmlがテンプレートとして使用されます。そして、distディレクトリにindex.htmlがコピーされます。

4. resolve

resolve: {
  extensions: ['.ts', '.tsx', '.js']
}
  • 説明: モジュール解決時に考慮するファイル拡張子のリストを指定します。
  • 詳細: import文でファイル拡張子を省略した場合に、ここで指定された拡張子を順に試します。TypeScript(.ts.tsx)とJavaScript(.js)のファイルを対象としています。

5. module.rules

module: {
  rules: [
    {
      test: /\.tsx?$/,
      use: [
        {
          loader: 'ts-loader',
          options: {
            transpileOnly: true // TypeScript→JavaScript構文解析のみ行う
          }
        }
      ],        
      exclude: /node_modules/
    },
    {
      test: /\.jsx?$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
      }
    }
  ]
}
  • 説明: モジュールの異なるタイプに対して、どのローダーを適用するかを指定します。
  • rules: 各ルールは、特定のファイルタイプに対する処理を定義します。

TypeScriptローダー

{
  test: /\.tsx?$/,
  use: [
    {
      loader: 'ts-loader',
      options: {
        transpileOnly: true // TypeScript→JavaScript構文解析のみ行う
      }
    }
  ], 
  exclude: /node_modules/
}
  • test: 対象となるファイルの拡張子を正規表現で指定します。この設定では、.ts.tsxファイルが対象です。
  • use: 使用するローダーを指定します。ここでは、TypeScriptファイルをJavaScriptに変換するためにts-loaderを使用します。optionsには、transpileOnlyをtrueに指定しています。これは、型チェックを他のプロセスと並列でfork-ts-checker-webpack-pluginに実施させ、ts-loderでは「TypeScript→JavaScript構文解析」「TSX/JSX→JavaScript変換」のみを行い型チェックの作業をts-loaderから除外するためです。
  • exclude: 除外するディレクトリを指定します。通常、node_modulesディレクトリは除外します。

Babelローダー

{
  test: /\.jsx?$/,
  exclude: /node_modules/,
  use: {
    loader: 'babel-loader',
  }
}
  • test: 対象となるファイルの拡張子を正規表現で指定します。この設定では、.js.jsxファイルが対象です。
  • use: 使用するローダーを指定します。ここでは、Babelを使用してJavaScriptファイルをトランスパイルするためにbabel-loaderを使用します。

6. mode

mode: "development"
  • 説明: Webpackのビルドモードを指定します。開発モードでは、最適化やデバッグに適した設定が有効になります。
  • 詳細: developmentモードでは、ソースマップの生成、最適化の最小化、ホットモジュールリプレースメント(HMR)など、開発に便利な機能が有効になります。もう一つのモードはproductionで、本番環境向けに最適化されます。

7. devServer

devServer: {
  static: {
    directory: path.resolve(__dirname, 'dist'),
  },
  compress: true,
  port: 9000
}
  • 説明: Webpack Dev Serverの設定です。これにより、開発用のローカルサーバーが起動し、ファイルの変更がリアルタイムに反映されます。
  • static: サーバーが提供する静的ファイルのディレクトリを指定します。ここでは、distディレクトリが指定されています。
  • compress: サーバーからのすべてのファイルに対してgzip圧縮を有効にします。これにより、ファイルの転送速度が向上します。
  • port: 開発サーバーがリッスンするポートを指定します。ここでは、ポート9000を使用します。

ステップ6: TypeScriptコードの作成

srcディレクトリを作成し、その中にTypeScriptファイルを作成します。例えば、src/index.tsxというファイルを作成し、以下のコードを追加します。

// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';

type AppProps = {
  message: string;
};

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

ReactDOM.render(, document.getElementById('root'));

src/index.htmlファイルも作成し、以下の内容を追加します。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Webpack TypeScript React</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="bundle.js"></script>
  </body>
</html>

ステップ7: ビルド

以下のコマンドでビルドを実行します。distディレクトリ以下にindex.htmlとbundle.jsが作成されます。

$ npx webpack

ステップ8: 実行

以下のコマンドを実行して、Webpack Dev Serverを起動し、プロジェクトを実行します。

$ npx webpack-dev-server --open

ブラウザが起動して、アプリケーションにアクセスできます。Hello, World!とブラウザに表示されるはずです。

まとめ

いかがでしょうか?この記事で複雑なフロントエンドのビルド周りが理解できたのなら、非常にうれしみ深いです。今後ともわかりみ深い情報をお届けしていますので、今後ともよろしくお願いいたしますm(_ _)m

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

2人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です