脱 create-react-app! webpackでReact ×TypeScrip開発環境構築からHello World!まで
まえおき
この記事では、なんとな〜くゆる〜く webpack を理解する記事となっています。
2022 年 3 月からエンジニアとしてキャリアをスタートして、業務でマニュアルでの環境構築が必要になったのでこの記事を書こうと思いました。
脱 create-react-app
みなさんはcreate-react-app
せずに React の開発環境を構築したことがありますか?
私は、ありませんでした。
実際の開発現場(業務)では、create-react-app
でプロジェクトを始めることが無いそうです。(わたしの周りの場合では。)
create-react-app
はコマンド一発で React の開発環境を構築できて便利ですが、webpack の設定が隠蔽されるため、の設定をいじくり回すにはeject
する必要があります。
eject
はひと手間かかるうえ、公式では推奨していないようです。
それだったら、マニュアルで環境構築をしてしまおう、という話です。
そもそも webpack とは
ひとことで言うと、.js
ファイルや.css
ファイル、.jpg
や.png
などの画像ファイルなどプロジェクトに必要なあらゆるファイルをひとつの.js
ファイルにまとめる(バンドルする)ツールです。
いわいるモジュールバンドラーと呼ばれるヤツです。
マニュアルで環境構築するメリット
create-react-app
は React の開発環境をワンコマンで用意できるので便利ですが、その反面デメリットも存在します。
一番のデメリットといえば、webpack(モジュールバンドラ) や Babel(トランスパイラ) といった、React アプリケーションの裏側で動いているライブラリの設定ができないことでしょうか。
誤解が無いように言うと、このデメリット自体がcreate-react-app
の最大のメリットでもあります。
なぜなら、面倒な細かな設定を気にせずに、アプリケーションの開発に集中できるからです。
ただし、create-react-app
では対応しきれない裏側の設定を変更したい場合はデメリットになりうります。
その他にもいくつかデメリットは存在しますが、今回の記事の内容から若干それてしまいますので、ここまでにさせてください。
他のデメリットについてはコメント欄にて、k-satoさんとハトすけさんがご紹介されているので、そちらを参考にしていただければと思います。
不要なパッケージをインストールしないで済むとか、package.json
がスッキリするとかでしょうか。
環境構築を始め!まずはパッケージのインストールをしよう
今回は以下のようなディレクトリ構成になっています。
root/
|
+ - dist/
| |
│ + index.html
| |
│ + main.js
│
+ - node_modules/
│
+ - src/
| |
| + - App.tsx
| |
│ + - index.tsx
│
+ - package.lock.json
|
+ - pacckage.json
|
+ - tsconfig.json
|
+ - webpack.config.js
npm init
するよ
まずは初期化処理から。
適当なディレクトリを用意してターミナルで以下のコマンドを実行します。
npm init
実行後、ターミナルに色々質問されるので、とりあえず Enter を連打 👇👇👇
npm init
時の設定は省略させていただきます。
(とりあえず連打で大丈夫なはず…)
npm init
の設定について知りたい方はググるなどして参考にしてみてください。
私は、下記の Udemy の講座で勉強しました。
React をインストール
まずは以下のコマンドで React をインストールします。
パッケージはプロジェクトフォルダのnode_modules
ディレクトリにインストールされ、package.json
のdependencies
にパッケージ名が記載されます。
npm install react react-dom @types/react @types/react-dom
dependencies にインストールするパッケージ一覧
react
react-dom
@types/react
@types/react-dom
TypeScript と webpack 周りのパッケージをインストール
TypeScript と webpack 周りのパッケージをインストールします。
npm install
の後にオプションコマンド--save-dev
をつけてパッケージ名を記述します。
--save-dev
をつけることで、開発環境だけでパッケージを使うことができます。
npm install --save-dev webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env @babel/preset-react typescript ts-loader sass css-loader style-loader sass-loader
devDependencies にインストールするパッケージ一覧
webpack
webpack-cli
webpack-dev-server
babel-loader
@babel/core
@babel/preset-env
@babel/preset-react
typescript
ts-loader
sass
css-loader
style-loader
sass-loader
--save-dev
の詳細は、過去に書いた以下の記事を参考にどうぞ!
webpack の設定をしよう
パッケージのインストールが完了したら、webpack の設定をしていきます。
ルートディレクトリに webpack の設定ファイルwebpack.config.js
を作成します。
touch webpack.config.js
まずは、ファイルに以下のコードを書いて設定の下準備をします。
const path = require('path');
module.exports = {};
プロパティの設定
module.exports
オブジェクトにプロパティを書いていきます。
今回は、以下のプロパティを設定します。
mode
entry
output
module
devServer
resolve
target
perfomance
module.exports
の中にプロパティを書いていくイメージです。
const path = require('path');
module.exports = {
mode,
entry,
output,
module,
devServer,
resolve,
target,
};
mode
mode
はバンドルするファイルに影響するプロパティです。
モードの値は 3 種類あります。
-
production
(デフォルト値) development
none
production
はバンドしたコード内の改行やインデント、余分な半角スペースなどを取り除いてバンドルします。
development
は改行やインデントはそのままに、バンドルします。
今回は、デプロイする予定もないので値にdevelopment
を設定します。
const path = require('path');
module.exports = {
mode: 'development',
// ...
};
entry
entry
はどのファイルを起点にバンドルするかを設定するプロパティです。
ひとつのファイルからインポートされたモジュールをたどってバンドルしてくれます。
今回はsrc
ディレクトリのindex.tsx
を起点とします。
const path = require('path');
module.exports = {
// ...
entry: './src/index.tsx',
// ...
};
output
output
はどのディレクトリにバンドルしたファイルを出力するかを設定するプロパティです。
今回は、dist
ディレクトリmain.js
というファイル名でバンドルしたファイルに出力するように設定します。
出力先のパスはoutput.path
に、出力するファイル名はoutput.filename
に値を渡します。
const path = require('path');
module.exports = {
// ...
output: {
path: path.join(__dirname, 'dist'),
filename: 'main.js',
}
// ...
};
module
modules
はbabel-loader
やcss-loader
といった Loader の設定を行うプロパティです。
Loader の設定を細かく書かないといけないため、コードがネストしまくります。
一見「ウェッ」っとなりがちですが、ひとつづず見ていけば意外と「こんなもんか」と思えるようになるかもしれません。
個人的には、取り扱う Loader の多さと、コードの読みづらさかが際立っているプロパティだなぁ、と思っています。
module.rules
基本的にmodule.rules
の使い方・コードが読めるようになれば webpack 初心者は脱出できるのではないかと思います。
module.rules
は配列です。配列の中に、TypeScript や CSS の Loader を設定するオブジェクトを渡すことで、設定できます。
イメージ的には下記になります。
const path = require('path');
module.exports = {
// ...
module: {
rules: [
{ /* TypeScriptのモジュール */ },
{ /* CSSのモジュール */ },
],
},
// ...
};
まずは、TypeScript の Loader から書いて行きましょう。
基本的に、オブジェクトの中にtest
とuse
を設定すれば Loader が動くようになります。
test
には、正規表現でターゲットとなる拡張子を書きます。どのファイルを処理の対象とするかといった設定です。
use
には、使用する Loader を書きます。配列になっており、その中に Loader を文字列もしくはオブジェクトで渡します。
Loader は実行順にルールがあります。配列の後ろから処理が行われるので、順番に気をつけながら設定しましょう。
TypeScript の例で言うと、TypeScript から JavaScript にコンパイルして、JavaScript から ES5 にトランスパイルしたいので、配列の最初にbabel-loader
を設定し、次にts-loader
を設定することで、ts-loader
→babel-loader
の順で実行できるようになります。
この順番が逆だとコンパイルエラーが発生するので注意しましょう。
{
test: /\.(ts|tsx)$/,
use: [
{
loader: 'babel-loader',
options: { presets: ['@babel/preset-env', '@babel/react'] },
},
{
loader: 'ts-loader',
options: {
configFile: path.resolve(__dirname, 'tsconfig.json'),
},
},
]
}
続いて、サクッと SCSS の Loader も設定しましょう。
こちらも、use
に設定するローダーの順番に気をつけましょう。
{
test: /\.scss$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: 'sass-loader',
},
]
}
ちなみに上記の様に Loader にオプションを渡さない場合は以下の様に省略して記述することもできます。
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
}
devServer
devServer
は、webpack-dev-server
の設定を行うプロパティです。
webpack-dev-server
は webpack で開発サーバーを立ち上げることができるライブラリです。コードを更新すると自動的にビルドしてブラウザのビューが更新されるので便利です。
devServer.static.directory
にはサーバーの起点となるディレクトリを書きます。
パスはoutput.path
と同様で構いません。
port
にはポート番号を指定できます。指定したポートでサーバーが立ち上がります。
const path = require('path');
module.exports = {
// ...
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
port: 3000,
},
// ...
};
resolve
resolve
は、インポート時にのパスの問題(絶対パスや相対パス)を解決するプロパティです。
resolve.extensions
に、拡張子を文字列として配列に渡すことで、インポートのパスに書く拡張子を省略できます。
const path = require('path');
module.exports = {
// ...
resolve: {
extensions: ['.ts', '.tsx', '.js', '.json'],
},
// ...
};
target
target
は、サーバー側(Node.js)とブラウザ側(フロント)どちらにコンパイルするかを設定するプロパティです。
サーバー側であればnode
、ブラウザ側であれば'web'をと書きます。
今回はフロントエンドの開発するのでweb
を設定します。
const path = require('path');
module.exports = {
// ...
target: 'web',
};
webpack の設定完了
これで、一通り webpack の設定は完了です。
コードをまとめると、以下の用になります。
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.tsx',
entry: './src/index.tsx',
output: {
path: path.join(__dirname, 'dist'),
filename: 'main.js',
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: [
{
loader: 'babel-loader',
options: { presets: ['@babel/preset-env', '@babel/react'] },
},
{
loader: 'ts-loader',
options: {
configFile: path.resolve(__dirname, 'tsconfig.json'),
},
},
],
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
},
],
},
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
port: 3000,
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.json'],
},
target: 'web',
};
Hello World!するよ
いよいよゴール目前です。
まずは、プロジェクトのビルドをするために必要なディレクトリとファイルを用意します。
dist
ディレクトリを作成してその中にindex.html
を作成します。
このディレクトリは、webpack.config.js
のoutput.path
とdevServer
に記述したディレクトリです。
mkdir dist
touch dist/index.html
index.html
には以下のコードを記述します。
コードの詳細は省きます。
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>webpack × React × TypeScript</title>
</head>
<body>
<div id="root"></div>
<script defer src="main.js"></script>
</body>
</html>
React を書いてく
プロジェクトのルートディレクトリにsrc
ディレクトリを作成して、その中にindex.tsx
とApp.tsx
を作成します。
mkdir src
touch src/index.tsx
touch src/App.tsx
まずはApp.tsx
のコードを書きます。
import React from 'react';
export const App: React.VFC = () => {
return <div>Hello World!</div>
}
次にindex.tsx
にApp.tsx
コンポーネントをインポートします。
import React from 'react';
import ReactDOM from 'react-dom';
import { App } from '../src/App';
ReactDOM.render(<App />, document.getElementById('root'));
TypeScript の設定をしよう
TypeScript の環境を構築します。
プロジェクトのルートディレクトリにて以下のコマンドを実行してを作成します。
touch tsconfig.json
tsconfig.json
に以下のコードを記述すれば OK です。
細かいことを省いて設定を要約すると、「TypeScript で書かれたコードは ES2020 にコンパイル、JSX で書かれたコードは React のコードにコンパイルしますよ」的な内容です。
{
"compilerOptions": {
"sourceMap": true,
"target": "ES2020",
"module": "ES2020",
"outDir": "dist",
"jsx": "react",
"moduleResolution": "Node",
"lib": ["ES2020", "DOM"],
"allowJs": true,
"allowSyntheticDefaultImports": true
},
"exclude": ["./node_modules"]
}
tsconfig.json
の各オプションは以下のリンクにまとめらているので、そちらを参照ください。
package.json の scripts の設定を設定して Hello World!してみる
package.json
のscripts
に以下のコマンドを登録します。
build
は、npm run build
をすることでdist
をディレクトリビルドしたファイルmain.js
が生成されます。
dev
は、npm run dev
をすることでwebpack-dev-server
が立ち上がります。http://localhost:3000
にアクセスすることでビルドの結果を見ることができます。
{
// ...
"scripts": {
// ...
"build": "webpack",
"dev": "webpack serve --open"
},
// ...
}
さて、ひと通り Hello World!までの設定が完了したので、ターミナルでプロジェクトのルートディレクトリにいる事を確認して、以下のコマンドを実行しましょう。
npm run dev
するとブラウザに Hello World!が表示されると思います。
お疲れさまでした。
もっと開発を楽しんでいきましょう!
さいなら~ 👋
Thanks
Discussion
create-react-appでプロダクションで回す様なアプリを開発する際のデメリットとして考えられるのは主に下記の2点でしょうか🤔🤔?(絞り出せば他にもありそうですかね)
ただ頭の良い方々が考えて作ってくれているので、Webpackの設定をよく理解せず、"プロダクションで使うアプリだから絶対独自のWebpack設定が必要だ!!"、よりもCRAを使わせてもらった方が楽で安心だったりする様な気もします☺️☺️☺️
参考
詳しい内容を教えて頂きありがとうございます🙇♂️
なるほど、CRAのデメリットは「重要な部分がブラックボック化」と「不必要なdependenciesが生まれる」の2点が主な理由なんですね❗️
ご共有頂いた記事も拝見しましたが、Don't use create-react-app: How you can set up your own reactjs boilerplate.の "Disadvantages of CRA" にもまとめられていますね‼️
確かに。場合によってはCRAにすべてを委ねて安心を得るのも良いかもしれませんね☺️
create-react-appのwebpackの設定のソースコードもザーッと見ましたが、webpackの設定がとても複雑で、カスタマイズが大変そうだと感じました🤔
正直、細かい部分までは理解できませんでした😂
こんにちは、webpackのとても詳しい記事ありがとうございます。
業務でcreate-react-app使ってるので、その立場からの意見です。参考程度に。
実際に使ってて不便に思うことは、jestがcreate-react-appの本体であるreact-scriptsに依存することですね。最近v5がでてきましたが、v4時代はjestはv26.6しか使えませんでした。
jestはv27系だとeach文で、以下のように$名前付きでコメント内でアクセスできる便利な機能があります。
v26系だとこの機能が提供されていないのでわざわざdescribeをもう一回ネストしてました。
微妙な例ですがreact-scriptsでできないなら、ejectしたくないかつ、
react-app-rewired
のような拡張もしたくないので、僕は諦めています。create-react-app使ってる人はwebpack扱いたくないかつ、
react-app-rewired
のようなハック的な機能も保守したくない人が多いイメージですね。長文すみません。別角度からの情報ありがとうございます🙇♂️
jestはreact-scriptsに依存しているんですね❗
新しい機能が使えないのは若干不便ですね🤔
やはり、面倒な設定を省けるのは大きそうですね😁
webpack周りのパッケージの保守も大変ですよね😅
どちらも一長一短といった感じですね🤔
ejectの他にも
react-app-rewired
でwebpackの設定を上書きできるんですね、知りませんでした❗とても参考になりました。ありがとうございます。
よくある、reactをejectして~だと設定項目が膨大でどこから読めば良いのかわからなくて途方に暮れていましたが
必要最低限の情報があって理解がすすみました。