【React】create-react-appを使わずプロジェクトを作成する

今回はcreate-react-appを使わず、webpackの設定を直接行いゼロからプロジェクトを作成していこうと思います。

当記事の目的
  • create-react-appを使わないでプロジェクトを作成する
  • webpackの設定を学ぶ
  • Bableを使わない

はじめに

Reactの開発環境を作成するFacebook製の「create-react-app」の開発が、2023年にて止まってしまいました。
これは、今後Create React Appではなく、別の手法でプロジェクトを作成すると解釈しています。

公式もプロジェクトの作成は、最新のReactフレームワークである「Next.js」「Remix」「Gatsby」「Expo」等の使用を薦めている状態です。

また、ビルドツールは「webpack」から「vite」に人気が移りつつあるようです。

上記の何れかを選択するとしても、まずはwebpackの役割等を知ることは重要な事だと思います。
本記事はcreate-react-appを使わず、webpackを設定しつつアプリを制作していこうかと考えています。

アプリ制作後は、同じ資材を用いて他のフレームワークを試してみたいと思います。

作業手順

先ずは、今回プロジェクトを作成する環境を記載します。

開発環境の確認
  • node > v21.0.0
  • npm > 10.3.0

npmの初期化

プロジェクトを作成したいフォルダに進み、下記コマンドを実行します。

npm init -y

コマンドの実行が完了すると「package.json」が作成され、内容がコマンドプロンプトに投影されます。

webpackのインストール

今回の本命、webpackのインストールです。下記コマンドを実行します。

npm install --save-dev webpack webpack-cli webpack-dev-server html-webpack-plugin
名称ver説明
webpack5.95.0webpackの本体
webpack-cli5.1.4webpackのコマンドラインインターフェイス。必須
webpack-dev-server5.1.0開発環境向けのwebサーバー。無いと開発が手間
html-webpack-plugin5.6.0HTMLファイルを「dist」直下に生成するプラグイン

コマンドが完了すると、「node_modules」フォルダと「package-lock.json」が作成されます。

Reactのインストール

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

npm install react react-dom
Reactの各バージョン
  • react > 18.3.1
  • react-dom > 18.3.1

TypeScriptのインストール

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

npm install --save-dev typescript ts-loader @types/react @types/react-dom
TypeScript関連の各バージョン
  • typescript > 5.6.2
  • ts-loader > 9.5.1
  • @types/react > 18.3.9
  • @types/react-dom > 18.3.0

tsconfig.json作成

TypeScriptの設定ファイルを作成します。下記コマンドを実行してください。

npx tsc --init

コマンド実行後、「tsconfig.json」が作成されます。
今回は、下記設定を反映しました。設定に正解はないので、開発環境等々とご相談下さい。

{
  "compilerOptions": {
    "target": "ES2020",                       /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    "module": "ESNext",                       /* Specify what module code is generated. */
    "lib": [
      "ES2020",
      "DOM"
    ],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
    "jsx": "react",                           /* Specify what JSX code is generated. */
    "moduleResolution": "node",               /* Specify how TypeScript looks up a file from a given module specifier. */
    "baseUrl": "src",                         /* Specify the base directory to resolve non-relative module names. */
    "allowJs": true,                          /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
    "sourceMap": true,                        /* Create source map files for emitted JavaScript files. */
    "outDir": "./dist",                       /* Specify an output folder for all emitted files. */
    "esModuleInterop": true,                  /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
    "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
    "strict": true,                           /* Enable all strict type-checking options. */
    "skipLibCheck": true,                     /* Skip type checking all .d.ts files. */
  },
  "include": ["src/**/*"],
}

CSS関連のインストール

CSS関連の便利なツールをインストールしていきます。

npm install --save-dev sass css-loader sass-loader mini-css-extract-plugin
CSS関連の各バージョン
  • sass > 1.79.3
  • sass-loader > 16.0.2
  • css-loader > 7.1.2
  • mini-css-extract-plugin > 2.9.1 ※cssを別ファイルとして出力する

各種プラグインをインストール

下記コマンドを実行します。
下記プラグインは必須ではないので、必要と判断した項目のみインストールして下さい。

npm install --save-dev clean-webpack-plugin copy-webpack-plugin dotenv-webpack file-loader
名称ver説明
clean-webpack-plugin4.0.0ビルド時に出力先のフォルダ内のファイルを削除する
webpack自体に機能があるため、不要かも…
copy-webpack-plugin 12.0.2ビルド時に特定ファイルをコピーする
dotenv-webpack8.1.0ビルド時に環境変数を読み込む
file-loader6.2.0画像を読み込む
各プラグインの説明

各フォルダ・ファイルを作成する

下記コマンドを実行し、フォルダとファイルを作成します。

mkdir dist
mkdir public
mkdir src
type nul > ./src/index.tsx
type nul > ./public/index.html
type nul > webpack.config.js
type nul > .env
cd src
mkdir images
mkdir css

「create-react-app」では、フォルダ「build」にファイルが出力されていましたが、今回は「dist」フォルダに出力します。※任意設定です

他は基本的に変わらないと思います。
フォルダ「src」直下に、画像ファイル配置用のフォルダ「images」、cssファイル配置用のフォルダ「css」を作成しています。

※「images」ファイルには、何か適当な画像ファイルを放り込んで下さい。

index.html

フォルダ「public」に配置しています。適宜修正を行います。

<!DOCTYPE html>
<html lang='ja'>
    <head>
        <meta charset='UTF-8'>
    <meta
        name="description"
        content="create-react-appを使わずサンプルアプリを組んでいます"
    />
    <title>Sampleアプリを作ろう</title>
    </head>
    <body>
        <noscript>You need to enable JavaScript to run this app.</noscript>
        <div id='root'></div>
    </body>
</html>

index.tsx

フォルダ「src」に配置しています。こちらも適宜修正します。

import React from 'react';
import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(
    document.getElementById('root') as HTMLElement
);
root.render(
    <React.StrictMode>
        <div>test</div>
    </React.StrictMode>
);

webpackの設定を行う

ここから本記事のメインである、「webpack.config.js」の設定を行います。

今回の設定は、あくまで一例です。
各設定は、環境毎・プロジェクト毎に変わってきます。適宜対応してください。

プラグインのインポートとベース作成

先ずは、色々とインストールしたプラグインの読み込みと、全体のベースを記述します。

変数「config」を設定し、色々と設定を盛り込んでいきます。

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const Dotenv = require('dotenv-webpack');

const isProduction = process.env.NODE_ENV == 'production';

const config = {
};

module.exports = () => {
    if (isProduction) {
        config.mode = 'production';
    } else {
        config.mode = 'development';
    }
    return config;
}

entry

バンドルする際、起点となるファイルを指定します。
今回は「src/index.tsx」にする予定なので、下記設定を行いました。

entry: './src/index.tsx',

output

バンドルした際のファイル配置先と、ファイルの命名等を設定します。

今回はフォルダ「dist」に出力する予定なので、その設定を入れています。
jsファイルは、「dist/static/js」配下に出力されます。

output:{
    // バンドルしたファイルの出力先を設定する
    clean: true,
    path: path.join(__dirname, './dist'),
    chunkFilename: 'static/js/[contenthash].chunk.js',
    filename: 'static/js/[contenthash].bundle.js',
    publicPath: '/',
},

module

各モジュールの振る舞いを指定します。
今回は、TypeScriptを使用するので「ts-loader」を指定し、CSSファイル、画像ファイルも各々の「loader」を指定します。
 ※CSSの「loader」は、設定の順番が決まっているので注意して下さい。

module: {
    rules: [
        {
           test: /\.(ts|tsx)$/i,
           exclude: ['/node_modules/'],
           use: {
               loader: 'ts-loader',
               options: {
                   configFile: path.resolve(__dirname, 'tsconfig.json'),
               }
           }
        },
        {
            test: /\.(scss|sass|css)$/i,
            use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
        },
        {
            test: /\.(png|jpg|gif|svg)/,
            use: [
                {
                    loader: 'file-loader',
                    options: {
                        name: path.resolve(__dirname, 'src/images/[name].[ext]') //[name]は画像名、[ext]は拡張子
                    }
                }
            ]
        },
        {
            test: /\.(eot|ttf|woff|woff2)$/i,
            type: 'asset',
        },
        // Add your rules for custom modules here
        // Learn more about loaders from https://webpack.js.org/loaders/
    ],
},

devServer

開発サーバ「webpack-dev-server」の設定を行います。

「historyApiFallback:true」は、後々のreact-router-domで重要になってくるので、必ず設定しておいて下さい。

下記設定では規定ブラウザが起動し、「http://localhost:3000/sample-app」のURLで起動します。

devServer:{
    static: {
        directory: path.join(__dirname, 'public'),
    },
    server: 'http',
    host: 'localhost',
    port: '3000',
    open: ['/sample-app'], // true:サーバを起動したとき自動でブラウザを起動する
    liveReload: true, // true:ファイルの変更を検知して自動でブラウザをリロードする
    watchFiles: ['src/**/*'], // ファイルの変更を検知して自動でブラウザをリロードする
    historyApiFallback: true, // true:ブラウザのリロードを行わない
    compress: true, // true:gzip圧縮を有効にする
    client:{
        logging: 'info',
        progress: true,
        overlay: true
    }
},

resolve

ファイルのインポート時に、拡張子を省略する事ができます。

resolve:{
    extensions: ['.ts', '.tsx', '.js', '.jsx'],
},

target

コンパイルターゲットを指定します。今回は「web」のみで。

target: 'web',

plugins

導入するプラグインを組み込みます。
CSSファイルの命名、出力先、画像ファイルのコピー先等を指定します。

plugins: [
    new HtmlWebpackPlugin({
        template: path.join(__dirname, 'public', 'index.html'),
    }),
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
        chunkFilename: 'static/css/[contenthash].css',
        filename: 'static/css/[contenthash].css',
    }),
    new CopyWebpackPlugin({
        patterns: [
            { from: 'src/images', to: 'static/img' },
        ]
    }),
    new Dotenv(),
     // Add your plugins here
    // Learn more about plugins from https://webpack.js.org/configuration/plugins/
],

全体

設定項目はまだまだ存在していますが、今回のアプリ制作で必要な箇所のみ行っています。

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const Dotenv = require('dotenv-webpack');

const isProduction = process.env.NODE_ENV == 'production';

const config = {
    entry: './src/index.tsx',
    output:{
        clean: true,
        path: path.join(__dirname, './dist'),
        chunkFilename: 'static/js/[contenthash].chunk.js',
        filename: 'static/js/[contenthash].bundle.js',
        publicPath: '/',
    },
    module: {
        rules: [
            {
                test: /\.(ts|tsx)$/i,
                exclude: ['/node_modules/'],
                use: {
                    loader: 'ts-loader',
                    options: {
                        configFile: path.resolve(__dirname, 'tsconfig.json'),
                    }
                }
            },
            {
                test: /\.(scss|sass|css)$/i,
                use: [MiniCssExtractPlugin.loader,'css-loader', 'sass-loader'],
            },
            {
                test: /\.(png|jpg|gif|svg)/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: path.resolve(__dirname, 'src/images/[name].[ext]') //[name]は画像名、[ext]は拡張子
                        }
                    }
                ]
            },
            {
                test: /\.(eot|ttf|woff|woff2)$/i,
                type: 'asset',
            },
             // Add your rules for custom modules here
            // Learn more about loaders from https://webpack.js.org/loaders/
        ],
    },
    devServer:{
        static: {
            directory: path.join(__dirname, 'public'),
        },
        server: 'http',
        host: 'localhost',
        port: '3000',
        open: ['/sample-app'], // true:サーバを起動したとき自動でブラウザを起動する
        liveReload: true, // true:ファイルの変更を検知して自動でブラウザをリロードする
        watchFiles: ['src/**/*'], // ファイルの変更を検知して自動でブラウザをリロードする
        historyApiFallback: true, // true:ブラウザのリロードを行わない
        compress: true, // true:gzip圧縮を有効にする
        client:{
            logging: 'info',
            progress: true,
            overlay: true
        }
    },
    resolve:{
        extensions: ['.ts', '.tsx', '.js', '.jsx'],
    },
    target: 'web', // ブラウザ側にコンパイルする
    plugins: [
        new HtmlWebpackPlugin({
            template: path.join(__dirname, 'public', 'index.html'),
        }),
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin({
            chunkFilename: 'static/css/[contenthash].css',
            filename: 'static/css/[contenthash].css',
        }),
        new CopyWebpackPlugin({
            patterns: [
                { from: 'src/images', to: 'static/img' },
            ]
        }),
        new Dotenv(),
         // Add your plugins here
        // Learn more about plugins from https://webpack.js.org/configuration/plugins/
    ],
};

module.exports = () => {
    if (isProduction) {
        config.mode = 'production';
    } else {
        config.mode = 'development';
    }
    return config;
};

package.jsonの修正・追記

「package.json」の修正を行います。

「name」「description」「main」「scripts」に修正を行います。

{
  "name": "sample-app",
  "version": "1.0.0",
  "description": "create-react-appを使わずにReactを使ってみる",
  "main": "./src/index.tsx",
  "scripts": {
    "start": "webpack-dev-server .",
    "build": "webpack --mode production",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/react": "^18.3.9",
    "@types/react-dom": "^18.3.0",
    "clean-webpack-plugin": "^4.0.0",
    "copy-webpack-plugin": "^12.0.2",
    "css-loader": "^7.1.2",
    "dotenv-webpack": "^8.1.0",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^5.6.0",
    "mini-css-extract-plugin": "^2.9.1",
    "sass": "^1.79.3",
    "sass-loader": "^16.0.2",
    "ts-loader": "^9.5.1",
    "typescript": "^5.6.2",
    "webpack": "^5.95.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^5.1.0"
  },
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  }
}

テスト起動

プロジェクトの下地が完成したので、一旦ブラウザから画面を確認してみたいと思います。

コマンドプロンプトで下記コマンドを実行してください。

npm run start

さて画面を確認してみましょう。

おわりに

以上になります。

いかがだったでしょうか。
「create-react-app」を使わずにプロジェクトが作成できたと思います。

これからはこのプロジェクトを使用して、目標のサンプルアプリを作成していこうかと思います。

これからも引き続きよろしくお願いいたします!