はじめに
本編では今時のフロントエンド開発2017 (2. 構築編)に続きwebpackを使っていきます。
おしながき
- 今時のフロントエンド開発2017 (1. 愚痴編)
- 今時のフロントエンド開発2017 (2. 構築編)
- 今時のフロントエンド開発2017 (3. webpack編)
- 今時のフロントエンド開発2017 (4. TypeScript編)
- 今時のフロントエンド開発2017 (5. もっと効率よく編)
webpack
webpack.config.js
webpackでバンドルするファイルの設定をします。
プロジェクトディレクトリにwebpack.config.js
を作ります。
modern-front-end
│
+-- node_modules
| `-- *
|
+-- src
|
+-- package.json
`-- webpack.config.js <
webpack.config.js
に次のように記述します。
var path = require('path');
module.exports = [{
entry: ['./src/app.js'],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}];
各項目の意味
- entry
-
バンドルのエントリポイントとなるファイルのパスを指定します。
この場合は`modern-frontend/src/app.js`がバンドルのエントリポイントになります。 - output
- ビルド時の出力に関するオプションを指定します。
- output.filename
- 出力するファイル名を指定します。
- output.path
-
出力するディレクトリを絶対パスで指定します。
この場合は`modern-frontend/dist/`にビルドしたファイルが出力されます。
webpackのエントリポイントを作成する
modern-frontend/src/app.js
となるようにファイルを作成します。
modern-front-end
│
+-- node_modules
| `-- *
|
+-- src
| `-- app.js <
|
+-- package.json
`-- webpack.config.js
次にapp.js
に以下のように処理を記述します。
今回は標準出力をしてみます。
console.log('hello, world');
HTMLを書く
modern-frontend/dist/index.html
となるようにHTMLを作成します。
modern-front-end
|
+-- dist <
| `-- index.html <
|
+-- node_modules
| `-- *
|
+-- src
| `-- app.js
|
+-- package.json
`-- webpack.config.js
作成したらサクッとHTMLを書いてbundle.js
を読み込みましょう。
Emmetを使うと今後も楽にマークアップできるのでオススメです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>modern-front-end</title>
<script src="./bundle.js"></script>
</head>
<body>
<p>hello, world</p>
</body>
</html>
npm-scriptsを設定する
npm-scriptsを通してwebpackを実行する準備をします。
package.jsonのscripts
に"webpack": "webpack"
を追記します。
{
"scripts": {
"webpack": "webpack"
}
}
npm-scriptsはnpm run
で実行できます。
webpackでビルドしてみる
それでは実際にwebpackでビルドしてみます。
プロジェクトディレクトリで次のコマンドを実行してみましょう。
npm run webpack
以下のようなログが出力されれば成功です。
Version: webpack 2.3.2
Child
Hash: fedbbe24aa69ef7f1937
Time: 43ms
Asset Size Chunks Chunk Names
bundle.js 2.78 kB 0 [emitted] main
[0] ./src/app.js 29 bytes {0} [built]
[1] multi ./src/app.js 28 bytes {0} [built]
すると,modern-frontend/dist/
にbundle.js
というファイルができあがっているのが確認できます。
modern-front-end
|
+-- dist
| +-- bundle.js <
| `-- index.html
|
+-- node_modules
| `-- *
|
+-- src
| `-- app.js
|
+-- package.json
`-- webpack.config.js
ブラウザでmodern-frontend/dist/index.html
を開き,デベロッパーツールのコンソールで標準出力できているか確認します。
バンドルファイルを圧縮する
modern-frontend/dist/bundle.js
を開いてみると長々と何か書いてあります。
しかし,改行やらインデントやらでこれでは冒頭に問題としてあげた”無駄の多いデータ”そのものなので圧縮します。
bundle.js
の圧縮は--optimize-minimize
を指定することできます。
次のコマンドを実行しましょう。
npm run
でオプションを付けるには--
を間に挟みます。
npm run webpack -- --optimize-minimize
ビルド後もう一度bundle.js
を確認すると無駄な改行やインデントが削除されファイルサイズが小さくなったことがわかります。
デバッグできるようにする
ブラウザのコンソールに表示されているログからconsole.log('hello, world');
した箇所をデバッグ画面に表示してみます。
JavaScriptを圧縮してしまうと次の画像のようにすべて1行で出力されるのでデバッグができなくなってしまいます。
そこで,--devtool source-map
オプションを付けてソースマップを作成できるようにします。
次のコマンドを実行しましょう。
npm run webpack -- --optimize-minimize --devtool source-map
ビルドが終わるとmodern-front-end/dist/bundle.js.map
というファイルができます。
modern-front-end
|
+-- dist <
| +-- bundle.js
| +-- bundle.js.map <
| `-- index.html
|
+-- node_modules
| `-- *
|
+-- src
| `-- app.js
|
+-- package.json
`-- webpack.config.js
同じ手順でデバッグ画面を見てみます。
とても見やすくなりました。
毎回ビルドするのが面倒くさい
毎回ビルドするのが面倒なのでファイルの変更を監視してビルドできるようにします。
--watch
オプションを付けることで監視状態になります。
npm run webpack -- --optimize-minimize --devtool source-map --watch
これでapp.js
を更新するたびに自動的にビルドされるようになりました。
抜け出すにはCLIでCtrl-C
を押します。
--watch
はキャッシュを利用した差分ビルドによって,更新のあったファイルのみ変更が加えられるので高速にビルドすることができます。
開発中はこの差分ビルドを使っていきましょう。
コマンドが長くて面倒くさい
コマンドが長いのでnpm-scriptsを使って工夫します。
package.jsonのscripts
を次のように書き直します。
{
"scripts": {
"start": "webpack --optimize-minimize --devtool source-map --watch",
"build": "webpack --optimize-minimize --devtool source-map"
}
}
先程までのコマンドとは少し違うので気をつけてください。
start
は特別でnpm start
と記述するだけで実行できます。
通常どおりビルドする場合はnpm run build
を実行します。
監視状態にするにはnpm start
を実行します。
CSSをバンドルする準備
CSSをバンドルするためにwebpack.config.js
にローダーを登録します。
var path = require('path');
module.exports = [{
entry: ['./src/app.js'],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [{
test: /\.(css|sass|scss)$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
}]
}
}];
SCSSを書く
CSSをバンドルするためにSassを書いていきます。
SassにはSASS記法とSCSS記法がありますが今回はSCSS記法で書いていきます。
ここらへんはお好みでどうぞ。
modern-frontend/src/scss/style.scss
を作成します。
modern-front-end
|
+-- dist
| +-- bundle.js
| +-- bundle.js.map
| `-- index.html
|
+-- node_modules
| `-- *
|
+-- src
| +-- scss <
| | `-- style.scss <
| |
| `-- app.js
|
+-- package.json
`-- webpack.config.js
style.scss
に以下のように記述します。
body {
color: darkcyan;
}
modern-frontend/src/app.js
でstyle.scss
を読み込みます。
var css = {
style: require('./scss/style.scss')
};
console.log('hello, world');
npm start
を実行していれば自動的にビルドされているのでブラウザで確認してみます。
style.scss
に書いた内容が<style>
によって<head>
内で展開されているのがわかります。
画面内のhello, world
の文字色が変わっていることも確認してください。
ベンダープレフィックスを付ける
postcss-loader
とautoprefixer
を使ってビルド時にベンダープレフィックスを付けるようにします。
var path = require('path');
module.exports = [{
entry: ['./src/app.js'],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [{
test: /\.(css|sass|scss)$/,
use: [
'style-loader',
'css-loader',
'sass-loader',
{
loader: 'postcss-loader',
options: {
plugins: function () {
return [
require('autoprefixer')
];
}
}
}
]
}]
}
}];
ベンダープレフィックスを付けてくれるか確認するためにstyle.scss
も変更します。
body {
color: darkcyan;
transition: all .1s ease;
}
ビルドされたらブラウザで確認してみます。
ベンダープレフィックスが自動で付与されていることがわかります。
url-loaderを使ってみる
url-loaderはCSS中で使用するアセットをdata URIに変換してバンドルできるようにします。
指定したファイルサイズより大きい場合は外部ファイルとして読み込みます。
Font Awesomeで試す
Font AwesomeはAwesomeなアイコンフォントです。
url-loaderを使ってFont Awesomeを使ってみましょう。
まずは次のコマンドでFont Awesomeをインストールします。
$ npm install -save font-awesome
ソースコードから利用するパッケージなので--save
オプションにしました。
package.json
を見るとfont-awesomeの依存関係がdependencies
になっているのがわかります。
{
"name": "modern-front-end",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack --optimize-minimize --devtool source-map --watch",
"build": "webpack --optimize-minimize --devtool source-map"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"font-awesome": "^4.7.0"
},
"devDependencies": {
"autoprefixer": "^6.7.7",
"css-loader": "^0.27.3",
"file-loader": "^0.10.1",
"node-sass": "^4.5.1",
"postcss-loader": "^1.3.3",
"sass-loader": "^6.0.3",
"style-loader": "^0.16.0",
"ts-loader": "^2.0.3",
"url-loader": "^0.5.8",
"webpack": "^2.3.2"
}
}
Font Awesomeを読み込む
webpack.config.js
を以下のように修正します。
var path = require('path');
module.exports = [{
entry: ['./src/app.js'],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.(css|sass|scss)$/,
use: [
'style-loader',
'css-loader',
'sass-loader',
{
loader: 'postcss-loader',
options: {
plugins: function () {
return [
require('autoprefixer')
];
}
}
}
]
},
{
test: /\.(eot|otf|ttf|woff|woff2|svg)(\?.+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 8192,
name: './fonts/[name].[ext]'
}
}
}
]
}
}];
.eot
.otf
.ttf
.woff
.woff2
.svg
ファイルに対してurl-loader
を使うようにしています。
後ほど書きますがurl-loader
じゃなくてfile-loader
でも良いです。
test
の部分はカッコよく書こうとしたら/\.(eot|[ot]tf|woff2?|svg)(\?.+)?$/
でしょうか。
見づらいので丁寧に/\.(eot|otf|ttf|woff|woff2|svg)(\?.+)?$/
で良さそうです。
(\?.+)?
は拡張子のあとにクエリパラメータを付ける場合を考慮して付けています。
- use.options.limit
-
指定したバイト数以上のファイルはバンドルしないようにできます。
この場合8KB以上のファイルは除外するということになります。 - use.options.name
-
バンドルしない場合コピー先のファイルパスを指定します。
[name]と[ext]を使うことで元のファイル名と拡張子を維持できます。
次にapp.js
を次のように修正します。
var css = {
fontAwesome: require('font-awesome/scss/font-awesome.scss'),
style: require('./scss/style.scss')
};
console.log('hello, world');
ここで書いているfont-awesome
は相対パスではありません。
npmによってfont-awesome
に対してパスが通っているのでパッケージ名として記述しています。
区別するために相対パスは常に./
を付けておくと良いでしょう。
続けてindex.html
でFont Awesomeを使ってみます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>modern-front-end</title>
<script src="./bundle.js"></script>
</head>
<body>
<p>
<i class="fa fa-font-awesome" aria-hidden="true"></i>hello, world
</p>
</body>
</html>
ビルドするとmodern-front-end/dist/fonts/
にフォントファイルがコピーされます。
modern-front-end
|
+-- dist
| +-- fonts <
| | +-- fontawesome-webfont.eot <
| | +-- fontawesome-webfont.svg <
| | +-- fontawesome-webfont.ttf <
| | +-- fontawesome-webfont.woff <
| | `-- fontawesome-webfont.woff2 <
| |
| +-- bundle.js
| +-- bundle.js.map
| `-- index.html
|
+-- node_modules
| `-- *
|
+-- src
| +-- scss
| | `-- style.scss
| |
| `-- app.js
|
+-- package.json
`-- webpack.config.js
画面を確認してと表示されていれば読み込めています。
続けてデベロッパーツールの方も確認してみます。
fontawesome-webfont.woff2
が読み込まれているのが確認できます。
ちょっと考察
バンドルするファイル
url-loaderでバンドルするファイルですが,Font Awesomeなどのフォントファイルはbundle.js
に含めないほうが良いです。
フォントファイルはブラウザによって読み込める種類が異なるので.eot
や.woff
などが用意されています。
つまり,フォントファイルは1回のセッションにつきすべてが読み込まれるわけではないので,すべて含めてしまうと読み込まれることのないファイルの分だけbundle.js
のファイルサイズが大きくなってしまいます。
こういったバンドルしないものに限っては次のようにfile-loader
を指定してしまった方が良いです。
url-loaderはuse.options.limit
を超えた場合file-loaderの機能を使っているのでuse.options.name
は同じ書き方ができます。
{
test: /\.(eot|otf|ttf|woff|woff2|svg)(\?.+)?$/,
use: {
loader: 'file-loader',
options: {
name: './fonts/[name].[ext]'
}
}
}
画像などのメディアをBase64エンコードすると元のデータに比べファイルサイズが30~40%ほど増加すると言われていますが,サーバー側でレスポンスをgzipで圧縮して転送するようになっていればこの増加分は2~3%まで減らすことができます。
容量の大きなファイルをバンドルしてしまうとバンドルファイル自身のファイルサイズが増えてしまうので,なんでもかんでもバンドルせずによく考えることを心がけましょう。
MIMEタイプ
url-loaderにはMIMEタイプを指定できます。
サーバーレスポンスをgzipなどで圧縮する場合MIMEタイプを見て区別していることがあります。
このようにサーバーがMIMEタイプによってあらゆる動作を決めていることがあるのでMIMEタイプは付けたほうが望ましいです。
しかし,ファイルをバンドルしない場合は通常通りCSSから外部のファイルを読み込んでいるのと変わりませんので不要であることがわかります。
ファイルサイズがurl-loaderのlimit
オプションに収まる場合(もしくはその可能性がある場合)はMIMEタイプを付けておきましょう。
data URIに変換されるとブラウザが元のデータの種類を区別できなくなるからです。
ベースURL問題
執筆中...