【読書記録】WebPackに入門してみた
先週記事をサボってしまいました。 今週は書きます。(とはいえ読書記録なので内容は薄いですが)
今回読んだ本
「Webpack実践入門」という本になります。 KDP(Kindle ダイレクト・パブリッシング)に出版されているようで、 ワンコインで購入できる技術書になります。
なぜ読んだか
今扱っているシステムでは、RailsのMVCで構築されており一部jQueryで非同期処理をするような構成です。
若干時代遅れな点もあり、Vue.js
も導入していきたいなと思っています。
Rails6ではwebpacker
が標準ですが、巷では
- カスタマイズ性が低い
- webpacker
側でサポートしていないパッケージと相性が悪い
ということもあり、導入推進のためwebpack
を勉強し始めました。
読んで何が得れたか
- webpackに関する基本的な知識、利用用途、知識を身に着ける
- 用語と仕組みの説明→ハンズオンという形式なので、理論と実装を紐づけることができました
- 有名どころなローダー(babel, sass, url)やプラグインを使ってみることができる
- webpack単体では恩恵はあまりなくて、babal-loaderやsass-loaderといったものと組み合わせることで、フロントエンド開発を効率化することができます
- 本番環境と開発環境での
webpack.config.js
の使い分けの方法を理解できる- 本番環境と開発環境では、利用するローダ・プラグインや設定を分けたいといったことがあります。
- ただ共通している部分もあるといった場合に、ハンズオンを通して本番環境と開発環境でwebpack.config.jsの使い分けを身に着けることができます。
そもそもwebpackって何?
- フロントエンド開発用のモジュールバンドラ
- モジュールバンドラを介して、複数のモジュール(JSファイルやcssファイル)をまとめてくれる
雑に表すと下図のようなイメージ
フレームワークなども含めて、複数(or 単体)にまとめて出力してくれます。
webpackを使うと何が嬉しいの?
メリット1 機能後にファイルをモジュール化できる
フロントエンド開発では、機能ごとにファイルを分割して開発することが主流です。 なんで機能ごとにファイルを分割するかと言うと、
- 可読性の向上
- どこで使われているかわからないコードをみるとすごい疲れますよね。しかも安易に消せないと言う。。
- 機能ごとに別れていれば、システムのどの機能を実装していると言う全体把握にも役立ちます。
- 開発作業の分担とテストがしやすくなる(=開発効率の向上)
- 機能ごとにファイルが別れていれば、コンフリやデグレを引き起こす可能性を大幅に削減できます
- 名前空間を生成できる
- モジュールの保守性を高められる
- リリースして、ある程度の期間が立って、「ここもう少しこうしたいな」と要望を受けたとしましょう
- ここで、1つのファイルから該当のコードを探すの比べたら、機能ごとに別れていた方が機能拡張も修正もしやすいですよね
メリット2 外部モジュールも利用できる
フロントエンド周りでは、vue
やreact
といったモダンなライブラリやフレームワークを利用する機会が大幅に多くなると思います。
webpackは外部モジュールを含めてバンドルすることができます。
メリット3 リクエスト数を減らせる
HTTP2.0が普及してきているので、メリットか分かりませんが、
複数のファイルがまとめられるため、リクエスト数が減ります。
メリット4 依存関係を解決したファイルを出力できる
webpackが依存関係を自動的に解決してファイルを主力するため、依存関係による不具合を減らすことができます。 小規模なシステムでは、依存関係を追っていくことができそうですが、 中+大規模なシステムになってくるとまず依存関係を目グレップで追っていくのは無理ですし効率がかなり悪いです。
webpackを試してみる
実行環境はこんな感じです。
srcディレクトリ配下のapp.js
がモジュールadd.js
とtax.js
を利用します(依存関係)
これらをバンドルした結果をpublic/js配下のbundle.js
に出力します。
root@e9bc5cdc81ea:/my_webpack# yarn -v 1.21.1 tree ?[master] . ├── Dockerfile ├── docker-compose.yaml └── getting-started-webpack ├── node_modules ├── package-lock.json ├── package.json ├── public │ ├── index.html │ └── js │ └── bundle.js └── src ├── js │ └── app.js └── modules ├── add.js └── tax.js
package.json作成 & 必要なモジュール群をインストール
まずはpackage.json
を作成して、webpack
をインストールします。
root@e729ef118310:/my_webpack# yarn init -y yarn init v1.21.1 warning The yes flag has been set. This will automatically answer yes to all questions, which may have security implications. warning package.json: No license field success Saved package.json Done in 0.11s. root@e729ef118310:/my_webpack# yarn add --dev webpack webpack-cli root@e729ef118310:/my_webpack# yarn add jquery
package.jsonにモジュールが追加されていることを確認します。
- dependencies
- 出力されるファイルにバンドルされるモジュール
- 本番環境で必要なモジュール群
yarn add {モジュール名}
で入れます
- devDependencies
- 開発時に必要なモジュール
yarn add -dev {モジュール名}
で入れます
{ "name": "my_webpack", "version": "1.0.0", "main": "index.js", "license": "MIT", "devDependencies": { "webpack": "^4.41.6", "webpack-cli": "^3.3.11" }, "dependencies": { "jquery": "^3.4.1" } }
package.jsonにスクリプトを書く
package.jsonのscripts
フィールドにエイリアスと対応するコマンドを記述することで
yarn run {エイリアス}
で実行することができます。
"scripts": { // yarn run devで実行ができるようになる "dev": "webpack --mode development --watch --hide-modules --config webpack.config.js" }
webpack.config.jsを書く
webpack.config.js
とはwebpackがエントリーポイント(どこを起点に依存関係を解決していくか)や出力先(バンドルしたファイルの保存先)を設定することができます
const path = require('path'); module.exports = { mode: 'development', entry: './src/js/app.js', output: { filname: 'bundle.js', path: path.resolve(__dirname, 'public/js') } }
mode
- webpackの動作モードで、
development
,production
,none
から選択します development
の場合は、エラー表示やデバッグしやすいファイルを出力するため開発時にはこちらを選択しますproduction
の場合は、ファイルの圧縮やモジュールの最適化などがされるので、本番環境に適用する場合にはこちらを選択します
- webpackの動作モードで、
entry
- 上でwebpackは依存関係を解析して、自動的に解決すると書きましたが、その依存関係の解析を開始する始点になります
output
- バンドルの設定です。
- filename
- バンドルした結果のファイル名
- path
- バンドルしたファイルの保存先
モジュール作成
モジュールadd.js
とtax.js
を作成します。
add.js
はそのままの通り、2つの引数を受け取って合計を返すモジュールです。
tax.js
は値段と税率を引数で受け取り、税込み価格を返すモジュールです。
export
を利用して、外部のファイルが利用できるようにします。
//add.js export default function add(n1, n2) { return n1 + n2; } //tax.js export default function(price, salesTax) { return Math.round(price * salesTax); }
エントリーポイントの作成
webpackが解析を始めるエントリーポイントを作成していきます。 先ほど作成したモジュールとjqueryをimportして、bodyにベタ書きするプログラムです。
import $ from 'jquery'; import add from './modules/add'; import tax from './modules/tax' const price1 = 100; const price2 = 500; const totalPrice = add(price1, price2); const salesTax = 1.08; const priceIncludeTax = tax(totalPrice, salesTax); $('boxy').text(priceIncludeTax);
ビルドしてみる
先ほど記述した scripts
でビルドするとpublic/js/bundle.js
が作成されていると思います。
root@e729ef118310:/my_webpack# yarn run dev yarn run v1.21.1 warning package.json: No license field $ webpack --mode development --watch --hide-modules --config webpack.config.js webpack is watching the files… Hash: 9e92343bbee09bc21d50 Version: webpack 4.41.6 Time: 559ms Built at: 02/15/2020 6:11:34 AM Asset Size Chunks Chunk Names bundle.js 316 KiB main [emitted] main Entrypoint main = bundle.js
ビルドしたファイルを読み込む
public/index.html
からはbundle.js
を読み込めば完了です。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Getting Started </title> </head> <body> <script src="js/bundle.js"></script> </body> </html>
ローダについて
フロントエンドはjavascriptだけではなく、css(scss)や画像ファイルも必要です。
js以外のファイルを同じようにバンドルできる形に変換するプログラムがローダになります。
TypeScript
やVueコンポーネント
もローダを介してバンドルすることが可能です。
冒頭のWebpackのイメージをもう少し細かく分離すると下のような形になります。
代表的なローダーの紹介とwebpack.config.jsへの記載方法をメモしていきます。
babel-loader
babel-loeaderはES6以降のコードをES5のコードに変換するローダーです。 IEなど古いブラウザでも動くようなjs構文に変換してくれます。
babel-loaderを追加
babale-loaderを使う場合には@babel/core
と@babel/preset-env
が必須なので一緒に追加します。
- @babel/core : Babel本体
- @babel/preset-env: ECMAを変換するために使う
root@9e670eed7c44:/my_webpack# yarn add --dev babel @babel/core @babel/preset-env
webpack.config.jsを追記
ローダの設定はmodule
ブロックに記述していくことになります。
- test
- どの形式のファイルを対象とするか
- include
- 適用対象のフォルダ
- use
- 使うローダを設定
module: { rules: [ { test: /\.js&/, //ローダの対象ファイルの形式? include: path.resolve(__dirname, 'src/js'), // ローダ対象のフォルダ(excludeで対象外のフォルダを設定できる) use: [ { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env' ] ] } } ] } ] }
sass-loader, css-loader, style-loader
scss
ファイルをバンドルするために3つのローダを使います。
モジュールを追加
sass-loaderを使うためにはnode-sass
も同時にいれる必要があります。
root@9e670eed7c44:/my_webpack# yarn add --dev sass-loader node-sass css-loader style-loader
webpack.config.jsに追記
rules
ブロックに追記します。
今回は複数のローダを利用するのですが、ローダは配列に入れた逆順で実行されます。
(今回の場合は、sass → css → styleの順番で実行する必要があります)
{ test: /\.scss$/, include: path.resolve(__dirname, 'src/scss'), use:[ //下から順番に実行される(scss-loader -> css-loader -> style-loader) 'style-loader', 'css-loader', 'sass-loader' ] }
プラグイン
webpack自体を拡張させるためのプログラムです。
バンドルする際にプラグインで追加した処理を行うことができます。
ProvidePlugin
指定したモジュールを全てのファイルから変数として利用できるようにするプラグインです。
jquery
などほぼ全てのファイルで利用されるモジュールを毎回import
する必要がなくなります。
このプラグインについては、webpackをインストールした時点ですでに利用することができます。
webpack.config.js
プラグインを利用するために、webpack
自体を読み込んおきます。
const webpack = require('webpack')
プラグインの設定はplugins
ブロックに記述していきます。
ProvidePlugnを利用してjqueryを$
で利用するようにしています。
plugins:[ new webpack.ProvidePlugin({ $: 'jquery' // juqueryを$として使うことができる }) ]
Watchモード
watchモードは、ファイルの変更差分を監視して、
変更があった際に自動的にビルドを再実行してくれる機能です。
自分で都度、ビルドする必要がなくなります。
webpack.config.jsに追記することもできるのですが、
--watch
をつけてコマンドを実行すればwatchモードになります。
なので、package.jsonのscriptで使い分けた方が利便性は高いです。
"scripts": { "dev": "webpack --watch", "build": "webpack" }
webpack-dev-server
node.jsで作られた開発用サーバです。
jsファイルやリソースに変更があると即座に反映してくれます。
webpack-dev-serverの注意点としては、ファイルを出力しないことです。
ビルドした結果はオンメモリに保持するため、実際にビルドする際にはwebpackコマンドを実行する必要があります。
インストール
yarnで追加できます。
開発で使うものなのでローカルインストールしておきます。
root@9e670eed7c44:/my_webpack# yarn add --dev webpack-dev-server yarn add v1.21.1
webpackに設定を追加
devServer
プロパティにwebpack-dev-serverの設定を追加していきます。
devServer: { open: false, //サーバ起動時に自動的にブラウザを起動するか?(コンテナ上で試していたのでfalse) host: '0.0.0.0', //コンテナで動作させているため, disableHostCheck: true, // port: 3000, // サーバが待ち構えるポート番号 openPage: 'index.html', // contentBase: path.join(__dirname, 'public'), // コンテンツのルートディレクトリ watchContentBase: true, }
開発環境と本番環境の切り分け
開発環境ではwatch
モードを有効にしたり、
webpack-dev-server
を動かしたいとい要望が出てくると思います。
もちろんconfigをそれぞれ作ればいいのですが、重複部分なども同じように 書いていくのは無駄が多くなります。
そこでベース(共通部分)
+本番のみの部分
+開発のみの部分
といった形で切り分けをしていきます。
ディレクトリ構造
tree -L 1 . ├── node_modules ├── package.json ├── public ├── src ├── webpack.base.config.js ├── webpack.dev.config.js ├── webpack.prod.config.js ├── yarn-error.log └── yarn.lock
必要なモジュールをインストール
webpackのconfigをマージするwebpack-merge
と
本番環境で利用するterser-webpack-plugin
をインストールします。
root@9e670eed7c44:/my_webpack# yarn add --dev webpack-dev-server root@05d680e73065:/my_webpack# yarn add --dev terser-webpack-plugin
ベース部分の作成
webpack.base.config.jsにベース部分を記載していきます。
エントリーポイントの設定や出力先、ローダーは同じように設定します。
const path = require('path') module.exports = { entry: '.src/js/app.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'public/js') }, module: { rules: [ { test: /\.js&/, //ローダの対象ファイルの形式? include: path.resolve(__dirname, 'src/js'), // ローダ対象のフォルダ(excludeで対象外のフォルダを設定できる) use: [ { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env' ] ] } } ] }, { test: /\.scss$/, include: path.resolve(__dirname, 'src/scss'), use:[ //下から順番に実行される(scss-loader -> css-loader -> style-loader) 'style-loader', 'css-loader', 'sass-loader' ] } ] } }
開発用の設定
開発環境では、develop
モードを有効にして、webpack-dev-server
も利用します。
webpack-merge
を利用して、共通設定に追加しているイメージです。
const merge = require('webpack-merge'); const baseConfig = require("./webpack.base.config.js"); module.exports = merge(baseConfig, { mode: 'development', watch: true, devServer: { open: true, host: '0.0.0.0', port: 3000, openPage: 'index.html', contentBase: path.resolve(__dirname, 'public'), watchContentBase: true, }, //ソースマップの表示 devtool: 'cheap-module-eval-source-map' });
本番環境の設定
本番環境でも同じようにwebpack-merge
を利用して、
共通設定に追加していきます。
const merge = require('webpack-merge'); const baseConfig = require('./webpack.base.config'); const TerserPlugin = require("terser-webpack-plugin") module.exports = merge(baseConfig,{ mode: 'production', // production時に有効になるoptimizationを上書き optimization: { minimizer: [ new TerserPlugin({ terserOptions: { //console.logを削除する compress: { drop_console: true} } }) ] } })
package.jsonにコマンドの使い分けを追記
最後に本番環境と開発環境で使い分けるコマンドを
package.jsonのscripts
に記述していきます。
yarn run dev
、yarn run prod
で環境に合わせたビルドが可能になります。
"scripts": { "start": "webpack-dev-server", "dev": "webpack --config webpack.dev.config.js", "prod": "webpack --config webpack.prod.config.js", "build": "webpack" }
まとめ
今回もつらづらと本を読んだメモを書いただけになりますが、 これまでコピペ使っていたwebpackが少しだけ読めるようになりました。
ただwebpackは設定できる項目がかなり多いようなので、 プロジェクトに合わせた設定が必要になってきます。
まだまだ習得には時間がかかりそうです。。