■はじめに
こんにちは。
株式会社iimonでフロントエンドを担当している白水です。
今回は「webpackを使ってバンドルしてみる」というテーマで書いていきたいと思います。
■環境構築
まずは、環境構築から始めたいと思います。
HTTPSか、SSHで以下リポジトリをcloneしてください。
// HTTPS ❯ git clone https://github.com/shiramizu-junya/webpack-sample.git // SSH ❯ git clone git@github.com:shiramizu-junya/webpack-sample.git
以下コマンドを実行して、環境を作ってください。
// package.jsonに記載しているパッケージをインストール ❯ npm install // TypeScriptのコマンドが使えるか確認 ❯ npx tsc -v
package.jsonで
lite-server
(簡易的なWebサーバー。import や exportはfile://
プロトコルでは使えないので、webサーバーを使います)typescript
(TypeScript本体です)
をインストールしています。
tsconfig.json
で
"sourceMap": true
(TypeScriptファイルをデバッグしやすいようにするための設定)"outDir": "./dist”
(distributionの略で、コンパイルしたファイルを吐き出す為の場所)"removeComments": true
(コメント消す設定)"exclude": ["node_modules"]
(ライブラリ内にもTSファイルがあってそれをコンパイルすると不具合のもとになるので省く設定)"include": ["src/**/*.ts"]
(ここで指定したファイルだけをコンパイルの対象にする設定)
以下コマンドを実行して、サーバーの起動とTypeScriptコンパイラーの起動をしてください。
// lite-serverを起動 ❯ npm run start // TypeScriptコンパイラーをwatchモードで起動 ❯ npx tsc -w
■前提
1つのファイルにプロジェクトのコードが全部書かれていると見通しも悪いですし、保守しづらくなります。
そのため、開発者目線では
- 複数のファイルに分割して開発したい
その反面、実行時は
- 1つのファイルをHTTPリクエストで取得する
だけにしたいという願望があります。
なので 開発するときはコードを複数のモジュールに分割する方法が必要になります。
それを実現できるのが「ESModule」です。
逆に実行時は1つのファイルにまとめたいです。
それを実現するのが「Webpack」や「Vite」などモジュールバンドラーというものです。
■TypeScriptコンパイラーとESModuleを使ってコンパイルと実行してみる
まず、以下コマンドを実行して、WebサーバーとTypeScriptコンパイラーを起動します。
// lite-serverを起動 ❯ npm run start // TypeScriptコンパイラーをwatchモードで起動 ❯ npx tsc -w
ビルドすると、以下画像のようにdist
配下にファイルが吐き出されます。
この際、バンドル(1つのファイルにまとめる)はしていないので、src配下のディレクトリ構成やファイル構成はそのままdist
配下に吐き出されます。
ちなみに、モジュールバンドラーを使っていないので、importするときは、拡張子が必要になります。
index.html
を確認すると、<script src="./dist/app.js" type="module"></script>
というコードがあり、
ここで、./dist/app.js
を読み込んでいます。
ESModuleを使うので、type="module"
を指定しています。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./dist/app.js" type="module"></script> </head> <body> <p>サンプルホームページ</p> </body> </html>
http://localhost:3000/
にアクセスすると、以下のようにコンソールに表示されます。
正しく実行されていることがわかります。
ここで開発ツールの「NETWORK」を確認してみます。
そうすると、以下画像のように
<script src="./dist/app.js" 〜 >
import 〜
と書かれている部分が、毎回HTTPリクエストされていることがわかります。
TypeScriptコンパイラーとESModuleを使う場合は「前提」であげた
- 開発者目線では「複数のファイルに分割したい」
- 実行時は「1つのファイルをHTTPリクエストで取得する」
のうち、上の「複数のファイルに分割したい」だけを叶えられている事がわかります。
これだと、大きなプロジェクトでは実行時にHTTPリクエストが何度も発生するのは、ユーザーは待ち時間が発生してしまいます。
また、ミニファイ(ファイルの圧縮)もされていないので、その分ファイルの容量も大きくなってしまいます。
余談ですが、以下画像のようにapp.ts
とvalidation.ts
でbase-component.js
をimport
した場合、base-component.js
は何回実行されるかというと、1回しか実行されないようです。
JavaScript モジュール - JavaScript | MDN
■Webpackを使って、バンドルしてみる
◆Webpackで必要なライブラリのインストール
ESモジュールの利点はモジュールに分割できるところでした。 逆に欠点は、分割した分だけたくさんのHTTPリクエストが発生していまい、待ち時間が発生することでした。
ローカルマシンの開発サーバーで実行しているのでファイルの読み込みは高速に見えますが、これらをWeb上にデプロイして不特定多数のたくさんのユーザーのリクエストを受け付けるようにした場合は、大きなレイテンシ(転送要求を出してから実際にデータが送られてくるまでに生じる、通信の遅延時間のこと)が発生します。
なので HTTPリクエストの量を減らすことができれば、フォーマンスの向上に繋がります。
これが Webpack(モジュールバンドラー)が役に立つところになります。
まずは、Webpackの設定をしていきます。
lite-server
とTypeScriptコンパイラーを停止して、以下コマンドを実行します。
dist配下のフォルダやファイルを全部削除しておきます。
❯ npm install --save-dev webpack webpack-cli ts-loader
- webpack
- distフォルダにあるJavaScriptのファイルを1つのバンドルにまとめる役割を担います。
- webpack-cli
- Webpackのコマンドを実行するためのライブラリです。
- ts-loader
- WebpackがTypeScriptをJavaScriptに変換する方法を知るためのパッケージです。 これがあることで、WebpackがTypeScriptをJSにコンパイルできます。
◆Webpackの設定
まずは、Webpackの設定ファイルを作ります。
名前はwebpack.config.js
です。
Webpackがこの名前のファイルを自動的に見つけます。
このファイルの中にWebpackがどのように動作するかを設定していきます。
本来はtouchコマンドで作成しなくても、npx webpack init
コマンドでテンプレートを作ってくれますが、今回は手動で作ります。
❯ touch webpack.config.js
以下が、webpack.config.js
の中身になります。
WebpackはNode.js上で実行されるので、CommonJSのexportの構文を使います。
// webpack.config.js const path = require('path'); // デバッグのため console.log(__dirname); console.log(path.resolve(__dirname, 'dist')); module.exports = { mode: 'development', // エントリーポイント entry: './src/app.ts', // 出力するフォルダやファイルの設定 output: { // 最終的に出力されるファイルの名前 filename: 'bundle.js', // 出力先のディレクトリを絶対パスで指定。tsconfig.jsonの"outDir"の代わり。 path: path.resolve(__dirname, 'dist'), }, // TypeScriptのファイルをどのように処理するかの設定 module: { // 全てのファイルに適用されるルールを複数設定(SASS用、画像用,,, など) rules: [ { // ルールを適用するファイル名。今回は拡張子の末尾が.tsで終わるファイルは全てこのルールを適用するため以下のように書く。 test: /\.ts$/, // 何を使って上のルールに適用するファイルをコンパイルするかの設定。ts-loaderを使ってTSをJSにコンパイルするという意味。 use: 'ts-loader', // node_modules以下のファイルをWebpackに処理させないための除外設定 exclude: /node_modules/, }, ], }, resolve: { // importされたmoduleをどのように解決するか(拡張子を何にするか)を指定するためのオプション extensions: ['.ts', 'js'], }, // バンドルファイルでもソースマップを使えるようにする devtool: 'inline-source-map', };
importするファイルの拡張子をextensions: ['.ts', 'js']
で指定したので、import文の拡張子を削除します。
import baseComponent from "./components/base-component.js";
→ import baseComponent from "./components/base-component";
に変更しておきます。
ブラウザに組み込まれた ビルトインのES6モジュールの機能でJavaScriptをimportする場合には拡張子が必要ですが、Webpackは.js
という拡張子や他の拡張子のファイルを自動的に探して取得するので不要になります。
package.jsonのscriptsに「webpack」と書いておきます。
// package.json "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "lite-server", "dev-start": "npx webpack" },
これでnpm run dev-start
コマンドを実行すると、dist/bundle.js
が出力されます。
webpack.config.js
のmode: 'development'
をmode: 'production',
に変えると、ミニファイされます。
今回は、Webpack-dev-serverの設定をしていないので、npm run start
でlite-serverを起動しておきます。
また、HTMLファイルのscriptもbundle.js
を読み込むようにしておきます。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./dist/bundle.js" type="module"></script> </head> <body> <p>サンプルHP</p> </body> </html>
これでブラウザをリロードすると以下のように問題なく実行できます。
また、Webpackを使ってバンドルすることで、前提で記載した「1つのファイルをHTTPリクエストで取得する」も叶えることができています。
■まとめ
Webpackなど、バンドルツールは設定が多くて難しいですが、この記事がエンジニアの皆さんの理解の助けになれれば幸いです。
また、弊社ではエンジニアを募集しております。
ぜひカジュアル面談でお話ししましょう!
ご興味ありましたら、ご応募ください!