webpackではv4から、mode
というオプションが追加された。
それに伴いminimize(コードの圧縮)の設定の仕組みも変わったので、それについても書いていく。
以下のライブラリのバージョンで動作確認している。
- webpack@4.1.0
- webpack-cli@2.0.10
- uglifyjs-webpack-plugin@1.2.2
- license-info-webpack-plugin@1.0.0
- react@16.2.0
2種類のmode
mode
は、production
とdevelopment
の2種類。
以下のように、webpackを実行する際に引数として渡すことで、設定できる。
"scripts": { "build": "webpack --mode production", "develop": "webpack --mode development" },
mode
を指定しなくてもwebpackを実行することは出来るようだが、警告が出る。
mode
を指定すると、特に設定を行わなくてもそれぞれに最適化してくれる。
例えばコードの圧縮については、development
では行われないが、production
では行われる。
もちろんこの設定を自分でカスタマイズすることも出来る。その方法は後述。
公式ブログによると、何か手を加えなくても最適化された設定を利用できるようになっており、必要に応じて各自でカスタマイズしましょう、という考え方らしい。
With the new
mode
option we tried to reduce the required configuration for a useful build. We tried to cover the common use cases with these defaults.But from our experience we also know that defaults are not for everyone.
Many people do want to change defaults to adapt to own use cases. We got you covered. Adding
mode
doesn’t mean that we remove configuration. Everything is still configurable. We actually made most of the internal optimization steps configurable (you can now disable them).
mode
is implemented by setting default values to configuration options. No special behavior is done bymode
which isn’t possible via other configuration option.
webpack 4: mode and optimization – webpack – Medium
modeと環境変数
mode
で設定した値は、そのままコードのなかで環境変数として使うことが出来る。
下記のコードをproduction
でビルドした場合、production
という文字列が表示される。development
の場合も同様。
console.log(process.env.NODE_ENV);
.babelrc
ではenv
によって使用する設定を切り替えることが出来るが、webpack経由でトランスパイルする場合、このenv
もmode
の値をそのまま参照する。
{ "env": { "production": { "presets": [ ["env", { "targets": { "browsers": ["ie 11"] } }], ], }, "development": { "presets": [ ["env", { "targets": { "browsers": ["last 2 Chrome versions"] } }], ], } } }
webpack.config.jsでmodeを参照する
webpack.config.js
でmode
を参照するには、一工夫必要になる。
まず、module.exports
に代入する値を、オブジェクトから、オブジェクトを返す関数、に変える。
// before module.exports = { // your settings... }; // after module.exports = () => { return { // your settings... }; };
こうすると、関数の第二引数から、mode
を取得出来るようになる。
module.exports = (env, argv) => { console.log(argv.mode); // production もしくは development return { // your settings... }; };
この値を使うことで、mode
毎にビルドの設定を変えることが可能になる。
minimizeのカスタマイズ
前述の通り、production
にすれば、何も指定しなくてもコードが圧縮される。
3.x
のときはES2015+の構文を使っているとエラーになるため別途プラグインが必要だったが、それも必要ない。
これは、UglifyJS2
にアップグレードされたためらしい。
😍Upgrade to UglifyJS2
This means that you can use ES6 Syntax, minify it, without a transpiler first.
🚀webpack 4 beta — try it today!🚀 – webpack – Medium
だが、自分で設定をカスタマイズする場合は、uglifyjs-webpack-plugin
というプラグインが必要になる。
使わずにカスタマイズする方法は見つけられなかった。
https://github.com/webpack-contrib/uglifyjs-webpack-plugin
$ npm i -D uglifyjs-webpack-plugin
例:consoleを消す
webpack.config.js
を以下の内容にしてビルドすると、src/index.js
の内容がdest/bundle.js
としてビルドされる。
const path = require('path'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dest'), }, };
production
でビルドすると圧縮されるが、行われるのは圧縮だけなので、プログラムの処理の内容は変わらない。
// src/index.js console.log('hoge'); // dest/bundle.js !function(e){var n={};function r(t){if(n[t])return n[t].exports;var o=n[t]={i:t,l:!1,exports:{}};return e[t].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=n,r.d=function(e,n,t){r.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:t})},r.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,"a",n),n},r.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},r.p="",r(r.s=0)}([function(e,n){console.log("hoge")}]);
上記の例では、元のコードが短いため圧縮によって却って長くなっているが、それはともかく、console.log
も当然残っている。
$ node dest/bundle.js hoge $
optimization.minimizer
で設定することで、console
を消せるようになる。
const path = require('path'); const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dest'), }, optimization: { minimizer: [ new UglifyJSPlugin({ uglifyOptions: {compress: {drop_console: true}}, }), ], }, };
これでビルドすると、dest/bundle.js
からconsole
が消える。
!function(e){var n={};function r(t){if(n[t])return n[t].exports;var o=n[t]={i:t,l:!1,exports:{}};return e[t].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=n,r.d=function(e,n,t){r.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:t})},r.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,"a",n),n},r.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},r.p="",r(r.s=0)}([function(e,n){}]);
$ node dest/bundle.js $
production
のときだけ消してdevelopment
のときは残したい、という場合は次のように書いてargv.mode
で処理を振り分ければいい。
const path = require('path'); const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); module.exports = (env, argv) => ({ entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dest'), }, optimization: { minimizer: argv.mode === 'production' ? [ new UglifyJSPlugin({ uglifyOptions: {compress: {drop_console: true}}, }), ] : [], }, });
ライセンスコメント
個人的に気になっていたライセンスコメントの抽出についても、調べた。
ライセンスについては詳しくないのだが、多くのOSSの恩恵に乗っかっている以上、出来るだけ正しく使いたい。
そのためには、全てを圧縮してしまうのではなく、ライセンスに関するコメントはそのまま残しておく必要がある。
例として、react@16.2.0
をimport
してみる。
import 'react';
これをビルドすると、1行のファイルに圧縮されてしまう。
そこで、optimization.minimizer
を編集して、以下のようにする。
const path = require('path'); const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); module.exports = (env, argv) => ({ entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dest'), }, optimization: { minimizer: argv.mode === 'production' ? [ new UglifyJSPlugin({ uglifyOptions: { output: {comments: /^\**!|@preserve|@license|@cc_on/}, }, }), ] : [], }, });
このようにすると、ライセンスに関する部分だけそのままdest/bundle.js
に出力することが出来る。
/* object-assign (c) Sindre Sorhus @license MIT /** @license React v16.2.0 * react.production.min.js * * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree.
license-info-webpack-plugin
license-info-webpack-plugin
というプラグインを使うと、さらに詳細にライセンスコメントを出力できる。
https://github.com/yami-beta/license-info-webpack-plugin
$ npm i -D license-info-webpack-plugin
const path = require('path'); const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); const LicenseInfoWebpackPlugin = require('license-info-webpack-plugin').default; module.exports = (env, argv) => ({ entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dest'), }, plugins: [ new LicenseInfoWebpackPlugin({ glob: '{LICENSE,license,License}*', }), ], optimization: { minimizer: argv.mode === 'production' ? [ new UglifyJSPlugin({ uglifyOptions: { output: {comments: /^\**!|@preserve|@license|@cc_on/}, }, }), ] : [], }, });
ライセンスの文言も表示され、react
だけでなく、react
と依存関係にあるパッケージのライセンスも表示されている。
/*! * fbjs@0.8.16 (MIT) * url: git+https://github.com/facebook/fbjs.git * * MIT License * * Copyright (c) 2013-present, Facebook, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * // 以下略
だが、依存関係にある全てのパッケージについて表示されるわけではないらしい。
react
の依存関係は以下のようになっているが、loose-envify
については出力されていなかった。
これについては調べていないが、恐らく、そのパッケージが自身のライセンスについて正しく記述していないと、上手く読み取れないのだと思う。
─┬ react@16.2.0 ├── fbjs@0.8.16 ├── loose-envify@1.3.1 ├── object-assign@4.1.1 └── prop-types@15.6.1