3年ほど山ごもりをしていて、浦島太郎状態なので、その間に進んだフロント周りのキャッチアップを ちょっとづつ進めています。
今回はwebpackについて、フリーな個人制作が多くgulpで何とかなるしと秘伝のタレ使いまわしていたのですが、JavaScriptのモジュール化とかすると流石にwebpack使い方くらいは知ってないと…と感じる部分があり今更のwebpackをやってみました。(ゴーギャンだって35歳で仕事やめて画家をめざしたのですから、何かを始めるのに遅すぎることはないですよね!)
webpackとは何か
babelとセットだったり、CSSも1つのJSファイルにできるという解説があったり、参考にしようと使われてるGitHubのリポジトリ見に行ったら巨大な設定ファイルがあったりで分かりづらくなるのですが、webpackは要するにJSの依存関係を解決して一つにまとめる機能を提供しているもの。なのかなという印象です。
webpackを使うメリット
- 自作のJSファイルだけでなく、npmでインストールしたモジュールなど含め、読み込み順などの依存関係を解決して1つのファイルにまとめてくれる
- 各モジュールを閉じたスコープにしてまとめてくれるので、モジュール名や変数名が被るのを気にする必要がなくなる importする側では気にする必要はあるけど
単に1つのファイルにまとめるだけならgulp
とかでconcat
してしまえば1つのファイルにすることができるのですが、順番を担保するためにファイル名を01-xxx.js
, 02-yyy.js
とかにしたり、グローバルオブジェクトを作っておき、各ファイルを即時関数でスコープ化してグローバルオブジェクトを介してメインのスクリプトから使うようにした構成にして、1つのファイルにconcat
した後にファイルの全体を!function(){..<1つにしたJS>..}();
で囲むみたいなハックじみたことをしてましたw
そんなハックは必要ない。そうwebpackを使えばね。
そんな感じ。
webpackのinstall
yarn
$ yarn add --dev webpack webpack-cli
npm
$ npm install --save-dev webpack webpack-cli
設定ファイルwebpack.config.js
の作成
$ touch webpack.config.js
webpackではプロジェクトのルートにwebpack.config.js
というファイルがあればこれを設定ファイルとして自動的に読み込むようです。
webpack.config.js
const path = require('path'); module.exports = { // webpack 4 からは必須 // 'production' | 'development' | 'none' mode: 'development', // メインとなるファイル entry: './src/index.js', // 出力に関する設定 output: { // 出力先のディレクトリ path: path.resolve(__dirname, 'dist/js'), // 出力ファイル名 filename: 'bundle.js' } };
webpackの実行
$ yarn run webpack
./dist/js
配下にbundle.js
が出力されました。
entry / output
entry
は、メインとなるファイル。エントリーポイントとか書かれているのを目にすることが多いです。
output
はwebpackでまとめたファイルの出力先を・ファイル名を指定します。出力先のパスは絶対パスでないとダメらしい(ちゃんと調べてないです...)のでpath.resolve(__dirname)
を使って絶対パスに変換しています。
entry
・output
は指定が無ければ、それぞれ./src/index.js
, ./dist/main.js
になります。
複数ファイルをentryにして別々に出力する方法
webpack.config.js
module.exports = { entry: { app: './src/index.js', print: './src/print.js', }, output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist/js'), } }; // 出力 // -> dist/js/app.bundle.js // -> dist/js/print.bundle.js
entry
にオブジェクトで起点となるファイルを指定、entry
に渡したオブジェクトのキーが出力ファイル名の'[name]'部分になる。
cf. Output Management | webpack
mode
webpack4からはproduction
/development
/none
いずれかのmode
の指定が必要になったようです。
mode
によって使用されるオプションが変わるようです。
production
すべての種類の最適化を行ったバンドルファイルが生成されます。
また、ウォッチモードは持ちません。development
evtoolはevalで実行されます。 Devtool | webpack また、ウォッチモード時のために差分ビルドの高速化がされています。
none
すべての設定を無効にします。
cf. 次のリリースであるwebpack 4の主な変更点まとめ - 技術探し
mode
はwebpack.config.js
に書いておいても、コマンド(CLI
)から指定して上書きすることができます。
modeをコマンド(CLI)から指定する方法
$ yarn run webpack --mode=production
--mode=
オプションでモードを指定してwebpackを実行することができます。
cf. https://webpack.js.org/concepts/mode/#usage
npm scriptにモードを指定したコマンドを作成しておくと便利そうです。
configファイルを環境ごとに分ける
CLI
でモードを指定しなくても、--config
オプションを使うとconfigファイルそのものを切り替えることもできるようです。
$ yarn run webpack --config <config file name>
環境によって処理が大きく異なるのであれば、webpack.config.js
の中で分岐をさせるより、設定ファイルそのものを別にしてしまったほうが見通しが良くなりそう。
--mode=
でモード指定するにしろ、--config
で設定ファイルを切替えるにしろ
"script": { "build": "./node_modules/.bin/webpack --mode=production" };
のような感じでnpm scriptにしてしまえば、どんな指定をしているか気にすることなく使うことができそうです。
使用していないモジュールの除去 (Tree Shaking)
Tree Shaking とはデッドコードを除去する機能のコト。
webpack4は--mode=production
のときに自動的にTree Shakingされるようです。
/src/index.js
import { foo, bar } from './test.js'; console.log( foo() );
/src/test.js
export const foo = () => { console.log('module foo'); return 'Foo'; }; export const bar = () => { console.log('modele bar'); return 'Bar'; };
👇 webpack
--display-used-exports
オプションで使われているモジュールが何か表示できる
$ yarn run webpack --mode=production --display-used-exports ... Asset Size Chunks Chunk Names bundle.js 1.05 KiB 0 [emitted] main Entrypoint main = bundle.js [0] ./src/index.js + 1 modules 350 bytes {0} [built] | ./src/index.js 187 bytes [built] | ./src/test.js 163 bytes [built] | [only some exports used: foo] ✨ Done in 0.71s.
👇 出力
/dist/bundle.js
!function(e){var t={}; /*...<中略>...*/}([function(e,t,r){"use strict";r.r(t);console.log((console.log("module foo"),"Foo"))}]);
1行になってちょっと見辛いけど、console.log('module bar');
の文字が消えてる。
=> Tree Shakingされている!
minimize.true の時のみ Tree Shaking されるっぽい
min化しないようにするminimize.false
オプションを設定するとproduction
モードでもTree Shakingされないっぽい。
webpack.config.js
module.exports = { mode: 'development', entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist/js'), filename: 'bundle.js' }, optimization: { // min化しない minimize: false } };
👇 先ほどのindex.js
とtest.js
をproduction
モードでwebpack
$ yarn run build --mode=production --display-used-exports Asset Size Chunks Chunk Names bundle.js 4.01 KiB 0 [emitted] main Entrypoint main = bundle.js [0] ./src/index.js + 1 modules 350 bytes {0} [built] | ./src/index.js 187 bytes [built] | ./src/test.js 163 bytes [built] | [only some exports used: foo] ✨ Done in 0.62s.
👇 出力
/dist/bundle.js
// 略 (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); // CONCATENATED MODULE: ./src/test.js const foo = () => { console.log('module foo'); return 'Foo'; } const bar = () => { console.log('module bar'); return 'Bar' } // CONCATENATED MODULE: ./src/index.js console.log( foo() ); /***/ })
[only some exports used: foo]
が表示され使ってないモジュールは感知してる様ですが、
const bar
の定義が消えていません。
min化された時はconsole.log( foo() );
の中に直接foo
関数の内容が入っていたので、そもそも出力のフォーマットが全く違うみたいです。
I can also confirm that
/* unused harmony export */
comments are missing from the production output whenoptimizations.minimize=false
. cf. https://github.com/webpack/webpack/issues/6992#issuecomment-383756787
他にもsideEffects
オプションとかいろいろとある様ですが、それだけで記事1つになりそうな感じだったので割愛
webpackでbabelを使う
webpack設定ファイルのmodule
オプション内に、webpackでバンドルする前に下処理したい設定ができる。
下処理をするプラグインがいわゆるXXXX-loader
。
babelを使う為のローダーのインストール
yarn
$ yarn add --dev babel-loader @babel/core @babel/cli @babel/preset-env
npm
$ npm install --save-dev webpack babel-loader @babel/core @babel/cli @babel/preset-env
※ @babel/core
@babel/cli
@babel/preset-env
はbabel本体関連
rulesの設定
webpack設定ファイルのmodule
オプション内に何をどう処理をするのかをrules
として作成する。
webpack.config.js
module.exports = { mode: 'development', entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist/js'), filename: 'bundle.js' }, module: { rules: [ { // 対象ファイル test: /\.js$/, // 除外するディレクトリ exclude: /node_modules/, use: [ { // 利用するローダー loader: "babel-loader", } ] } ] } };
babelの設定はプロジェクトのルートにあるbabel.config.js
が使用される
webpack.config.js
内にoptions
としてbabelの設定を書くこともできる。 webpack.config.js
内に設定があればこちらが優先されるっぽい。
rules: [ { test: /\.js$/, exclude: /node_modules/, use: [ { loader: "babel-loader", options: { babelrc: false, presets: [["@babel/preset-env", { modules: false, useBuiltIns: "usage" }]] } } ] } ]
/src/index.js
import { foo, bar } from './test.js'; console.log( foo() );
/src/test.js
export const foo = () => { console.log('module foo'); return 'Foo'; }; export const bar = () => { console.log('modele bar'); return 'Bar'; };
👇 webpack with バベる 出力ファイルが見やすいようにnoneモードで
$ yarn run build --mode=none
👇 出力
// 略 __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "foo", function() { return foo; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bar", function() { return bar; }); var foo = function foo() { console.log('module foo'); return 'Foo'; }; var bar = function bar() { console.log('module bar'); return 'Bar'; };
babelで変換できてる。
Tree Shakingさせるにはbabelでimport
文がrequire
に変換されないように、modules: false
を指定しておくのが良いっぽい。
まとめ
webpack設定がややこしいイメージがあったけど、メインはJavaScriptモジュールの依存関係を解決してバンドルする機能を提供するもで、その部分はシンプルだと知れた。
実現したい事によってoptionを設定していったり、
CSS含めてwebpackでゴリっとするためにloaderを色々組み合わせてた設定を作っていくってのは大変そうだと思いました。個人的にはCSSとして出力するCSSのコンパイルとかはwebpackじゃなくても良いのでは?とも思うけど、node_moduleフォルダの肥大化を思うとgulpかあJSまわりだけwebpackってのもナンダカナーとも感じる訳で、なかなか闇っぽい...
babelすることを「バベる」とか言ったりするらしいけど、webpackの場合は、「ウェブパックる」とか言うのだろうか?割とどーでもいいけど、ちょっと気になった。
[参考]
- webpack
- 🚀webpack 4 beta — try it today!🚀 – webpack – Medium
- webpack 4 入門 - Qiita
- webpack 入門ハンズオン - 最小アプリを実際に作成してモジュールバンドラの利点を理解する - Qiita
- Webpack の考え方について - mizchi's blog
- configファイルを分ける
- mode
- options
- Tree shaking
- 作者: 木村泰司
- 出版社/メーカー: マガジンハウス
- 発売日: 2018/10/05
- メディア: 単行本
- この商品を含むブログを見る
- 作者: 山田祥寛
- 出版社/メーカー: WINGSプロジェクト
- 発売日: 2018/04/27
- メディア: Kindle版
- この商品を含むブログを見る