使用したバージョン
"webpack": "^3.11.0"
webpackとは
Webクライアントアプリを作るためのNode.js製ビルドツール。
概要
webpacはエントリーファイルを持ち、最終的にJavaScriptファイルを出力する。
この際、エントリーファイルに import されたモジュールを指定された loader を使って解決していく。
また用意されたPluginを組み合わせることで、関連するファイル(html,css等)を自動生成させることが出来る。
これらのエントリーファイルや出力先、使用するloader、Plugin等は webpack.config.js ファイルに定義する。
module.exports = {
entry: 'エントリーファイル.js',
output: {
path: '出力先ディレクトリ',
filename: '出力ファイル名.js',
},
module; {
rules: [
/*
使用するloaderを登録する。
wepbackは、指定されたloaderを使って、importされたファイルをJavaScriptに変換する。
*/
{
//例: jsxファイルがimportされたらbable-loaderを使って解決する
test: /\.jsx$/,
loader: 'babel-loader',
options: {
presets: ["react"]
}
}
]
},
pulugins: [
// index.htmlファイルを生成するプラグインや、リソースをコピーするプラグインなど様々なプラグインが用意されている。
]
}
インストール
$ npm install --save-dev webpack
使い方
- webpack.config.js を作成する
- webpack コマンドでビルドする
$ webpac
- オプション
-
-w
--watch
変更を監視 -
-d
開発用ビルド -
-p
リリース用ビルド
-
loader
import されたモジュールをJavaScriptに変換する役割を担う。
webpackは、configファイルで定義されたloaderに対し、 下から順に 問い合わせ、出力をチェーンしていく。
loaderの定義
説明 | |
---|---|
test | 変換対象モジュールを指定する。(正規表現) |
loader | 使用するloader。後ろの -loader は省略可能。 |
options | loaderが使用するoption。loaderに直接クエリ形式で指定することも出来る。 |
Ex1 jsxファイルがimportされた場合は、babel-loaderを使って解決する
{
test: /\.jsx/,
loader: 'balel' //-loaderは省略可
options: {
presets: ['react']
}
}
Ex2 pngなどのリソースがimportされた場合は、file-loaderを使って解決する
(file-loaderを使うと、importの出力値として、ファイルパスに変換してくれる。)
{
test: /\.(png|jpg|gif)$/,
loader: 'file?name=[name].[ext]&context=./app/static/' // optionsは、クエリ形式で指定できる
}
用意されているloader
webpackで用意されているloaderはこちらで確認できる。
https://webpack.js.org/loaders/
babel-loader
babel-loaderは、JavaScriptファイル(ES6 or JSX)を旧来のJavaScript(ES5)に変換する。
インストール
$ npm i --save-dev babel-core babel-loader
$ npm i --save-dev babel-preset-es2015 babel-preset-react
使い方
// babel-loader
{
loader: 'babel-loader'
test: /.js$/,
options: {
presets: ['es2015', 'react']
}
}
// jsファイルは拡張子が不要
import Foo from 'foo'
サンプル
以下のサンプルは、ES6対応のJavaScript(app.js)をwebpackでビルドして、旧来のJavaScript(ES5)を生成する。
* sample
* app.js
* foo.js
* package.json
* webpack.config.js
インストール
$ npm init -y
$ npm i --save-dev webpack
$ npm i --save-dev babel-core babel-loader
$ npm i --save-dev babel-preset-es2015
ファイルの作成
const path = require('path')
module.exports = {
entry: './app.js',
output: {
filename: './out/app.js'
},
module: {
rules:[
{
test: /\.js$/,
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}
]
}
}
export default class Foo {
constructor () {
this.message = "Hello World"
}
}
import Foo from 'foo'
const foo = new Foo()
console.log(foo.message)
ビルド
$ ./node_module/.bin/webpac
またpackage.jsonに"build"を定義すると npm run build
でビルド出来る。
scripts: {
"build": "webpack"
}
生成されたout/app.jsの内部を見ると、foo.jsが、ES5に変換されてapp.jsファイルにexportされている。
// 省略
var Foo = function Foo() {
_classCallCheck(this, Foo);
this.message = "Hello World";
};
exports.default = Foo;
file-loader
file-loaderは、webpackにfileとしてオブジェクトを出力するように命令する。
またimport結果としてファイルパスを渡す。
Ex: icon.pngをimportした際の、file-loaderによる出力結果
// file-loaderによって読み込む
import img from '../public/imgs/icon.png'
console.log(img.constructor.name) // String
console.log(img) // 出力パスは、webpack.config.jsで制御する
- imgにはicon.pngへのファイルパスが格納される
- icon.pngは出力先にコピーされる
インストール
$ npm --save-dev file-loader
使い方
{
test: /\.(png|jpg|gif)$/,
loader: 'file-loader'
}
オプション
キー | 説明 |
---|---|
name | 出力するファイル名 |
context | 作業ディレクトリを指定する。デフォルトはwebpack.config.contextと同一。nameの[path]に影響を与える。 |
publicPath | 独自にpublicPathを指定する場合に使用する |
outputPath: | 独自にoutputPath(コピー先)を指定する場合に使用する。nameの[path]に追加される。 |
useRelativePath | 出力に、contextを考慮するか。デフォルトでは true
|
emitFile | importされたファイルを出力先にコピーするかどうかを指定する |
placeholder
nameには以下のplaceholderが用意されている。
値 | 説明 |
---|---|
name | ファイル名(パスや拡張子は含まれない) |
ext | ファイル拡張子 |
path | ディレクトリパス(context, outputPathの影響を受ける) |
hash | コンテントのハッシュ値 |
N | regExpでマッチングした値を後方参照出来る |
Ex regExpによる後方参照によるnameの指定方法
import img from './customer01/file.png'
{
loader: 'file-loader',
options: {
regExp: /\/([a-z0-9]+)\/[a-z0-9]+\.png$/, //グルーピングでディレクトリ名を取得
name: '[1]-[name].[ext]'
}
}
出力結果
customer01-file.png
このように file-loader はimportの出力値を webpack.config.js 側で定義する。
サンプル
サンプルは、外部のcssを読み込み、linkタグを動的に追加するmain.jsを作成します。
ファイル構成
以下のようにdevフォルダ内でソースを管理し、ビルド結果をdist配下に出力するようにします。
app -+- package.json
|
+- webpac.config.s
|
+- dev -+- main.js
| |
| +- css - sample.css
| |
| +- img - sample.png
|
+- dist -+- index.html(静的に作成しておく)
webpack.config.js
const path = require('path')
module.exports = {
entry: './dev/main.js',
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
},
module: {
rules: [
{
test: /\.css$/,
loader: 'file-loader',
options: {
name: '[path][name].[ext]',
context: './dev',
outputPath: 'assets',
}
},
]
}
}
MEMO:
- context = 作業ディレクトリを
./dev
配下にすることで、[path]の出力から除外する。 - outputPath = assetsディレクトリ配下にリソースをコピーするようにする。また[path]にも追加される。
これによってfile-loaderによるimportの出力結果は以下となる。
assets/[images|css]/[name].[ext]
====================
= [path]
main.js
import styleFileName from './css/sample.css'
import './img/example.png'
document.addEventListener('DOMContentLoaded', (event) => {
const aLink = document.createElement('link')
aLink.type = 'text/css'
aLink.rel = 'stylesheet'
aLink.href = styleFileName
const head = document.getElementsByTagName('head')[0]
head.appendChild(aLink)
})
example.css
.content {
background-image: url(../images/sample.png);
}
h1 {
color: red
}
4. index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body class="content">
<h1>Hello World</h1>
<!- ビルドしたmain.jsを組み込む ->
<script type="text/javascript" src="./main.js"></script>
</body>
</html>
5. ビルド & 表示結果
css-loader
css-loaderは、cssファイルを解析し、JavaScriptオブジェクトに変換する。
その際に、 @import と url() を require 、 import と同じように扱い、それらの解決も行う。
.sample {
color: red;
// url()をハンドリングすると モジュールのimportと同一に扱う。
backgruond-image: url('./images/example.jpg');
}
// css-loaderによって読み込む
import sample from 'sample.css'
console.log(sample)
/* 出力結果
[ [ 27,
'.sample {\n color: red;\n background-image: url(images/example.jpg);\n}\n',
'' ],
toString: [Function: toString],
i: [Function] ]
*/
console.log(sample.toString())
/* 出力結果
sample {
color: red;
background-image: url(images/example.jpg);
}
*/
インストール
$ npm install --save-dev css-loader
使い方
webpack.config.js
{
test: /\.css$/,
loader: 'css-loader'
}
app.js
import style from 'sample.css' //JavaScriptオブジェクトに変換される
ビルド後のurl()の入力値がコピー先と異なる可能性がある
css-loaderは、url() を import と同じように扱うため、webpackに登録されている他のloaderに対して解決を問い合わる。
その際にfile-loaderを使用している場合、file-loaderは import の出力値を webpack.config.js 側で定義するため、ビルドして生成されたcss側で設定されているurl()の値がコピー先と異なる可能性がある。
以下例
dev-+-css-sample.css
|
+-images-sample.png
p { background-image: url('.../images/sample.png') }
上記において、例えばwebpack.config.js側が以下だとする。
// file-loader
{
test: /\.png$/,
loader: 'file?name=images/[name].png'
}
ビルド後の出力結果は以下となり、コピー先と異なってしまっている事がわかる。
p { background-image: url('images/sample.png') }
解決策としては、context、outputhPath、nameの[path]を上手く使う必要があります。
(サンプルを参照)
サンプル
サンプルは、cssファイルを読み込み、動的にHTMLにインジェクトするapp.jsを作成する。
ファイル構成
dev配下でソースを管理し、ビルド結果はdist配下に出力する。
app -+- package.json
|
+- webpac.config.s
|
+- dev -+- main.js
| |
| +- css - sample.css
| |
| +- images - sample.png
|
+- dist -+- index.html(静的に作成しておく)
|
+- main.js
|
assets -+- images - example.png
ファイルの作成
webpack.config.js
const path = require('path')
module.exports = {
entry: './dev/main.js',
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
module: {
rules: [
//css-loader
{
test: /\.css$/,
loader: 'css-loader',
},
//file-loader
{
test: /\.png$/,
loader: 'file-loader',
options: {
name: '[path][name].[ext]',
context: './dev',
outputPath: 'assets',
}
},
]
}
}
sample.css
h1 {
color: red;
}
.bg {
background-image: url(../images/example.png);
}
main.js
import sample from './css/sample.css'
document.addEventListener('DOMContentLoaded', (event) => {
const style = document.createElement('style')
style.type = 'text/css'
// 読み込んだcssを書き込む
style.innerHTML = sample.toString()
const head = document.getElementsByTagName('head')[0];
head.appendChild(style)
})
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body class="bg">
<h1> Hello Wrold </h1>
<!-- 生成したJavaScriptを読み込む -->
<script src="./main.js"></script>
</body>
</html>
ビルド
$ webpack
表示結果
style-loader
cssをDOMに追加する。
style-loader単体では、importを直接解決せず、file-loaderやcss-loaderと組み合わせて使用する。
使い方1 styleタグをDOMにインジェクトする
style-loaderは、css-loader と組み合わせることで、importされたcssをstyleタグに変換し、動的にHTML内に組み込ませるJavaScriptを生成する。
従って先にcss-loaderによってimportが解決される必要がある。
(webpackは下側から順にloaderに問い合わせる)
{
test: /\.css$/,
use: [
{ loader: 'style-loader'},
{ loader: 'css-loader' }
]
}
p {
color: red;
}
import './sample.css'
ビルドして生成したapp.jsファイルをindex.htmlに組み込めば完了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<p> Hello World</p>
<script src="./out/main.js"></script>
</body>
</html>
表示結果
app.jsによって、styleタグが動的に埋め込まれる。
表示結果 | inspector |
---|---|
使い方2 linkタグをDOMにインジェクトする
file-loader と組み合わせることによって、linkタグとしてcssファイルを動的に組み込むJavaScriptに変換される。
またwebpac.donfig.jsでは、先にfile-loaderを実行するようにする。
(webpackは下側から順に問い合わせる)
{
test: /\.css$/,
use: [
{ loader: 'style-loader/url'}, //url
{ loader: 'file-loader' },
]
}
p {
color: red;
}
import './sample.css'
上記をビルドして、index.htmlファイルに組み込む。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<p> Hello World</p>
<script src="./out/app.js"></script>
</body>
</html>
表示結果
app.jsによってlinkタグが動的に埋め込まれる
表示結果 | inspector |
---|---|
プラグイン
webpackにおいてプラグインは、関連するファイルを自動生成するために使用される。
HtmlWebpackPlugin
HTMLを自動生成する。
また生成したJavaScriptファイルをbodyにインジェクトする。
インストール
$ npm install --save-dev html-webpack-plugin
使い方1 htmlファイルの自動生成
1. webpack.config.jsの用意
module.exports = {
entry: './app.js',
output: {
path: path.join(__dirname, 'out'),
filename: '[name].js'
},
plugins: [
new HtmlWebpackPlugin(),
]
}
2. entryファイルの用意
document.addEventListener('DOMContentLoaded', (event) => {
const body = document.getElementsByTagName('body')[0]
body.innerHTML = 'Hello World'
})
3. ビルドする
$ webpack
4. 出力結果
index.htmlが自動生成され、 app.js ファイルが組み込まれる。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack App</title>
</head>
<body>
<script type="text/javascript" src="app.js"></script></body>
</html>
使い方2 テンプレートを使用する
webpack.config.js
plugins: [
new HtmlWebpackPlugin({
template: './template.html',
filename: 'index.html',
})
]
template.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<h1 id="root"></h1>
</body>
</html>
template.htmlを元に、index.htmlファイルが生成され、JavaScriptファイルも組み込まれる。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<h1 id="root"></h1>
<script type="text/javascript" src="main.js"></script></body>
</html>
ExtractTextWebpackPlugin
cssファイルを自動生成する。
(※ style-loaderは、importされたcssを動的にhtmlにインジェクトするJavaScriptに変換する。一方このプラグインを使用すれば、静的なcssファイルとして出力してくれる。)
css-loader, style-loaderと組み合わせて使用する。
インストール
$ npm install --save-dev extract-text-webpack-plugin
$ npm install --save-dev css-loader style-loader
使い方
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
// importされたcssをstyles.cssファイルに出力する
new ExtractTextPlugin("styles.css"),
]
}
サンプル
サンプルとして、DOMのbadyに書き込むapp.jsを作成し、htmlとcssをそれぞれプラグインを使って自動生成する。
ファイル構成
app -+- webpack.config.js
|
+- package.js
|
+- app.js
|
+- sample1.css
|
+- sample2.css
webpack.config.js
const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './app.js',
output: {
path: path.join(__dirname, 'out'),
filename: '[name].js'
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
new ExtractTextPlugin("styles.css"),
new HtmlWebpackPlugin(),
]
}
3. app.js
import './sample1.css'
import './sample2.css'
document.write('<h1>Hello World</h1>')
document.write('<p>Have a nice day!</p>')
'''
4. samples.css
```sample1.css
h1 {
color: red;
}
p {
color: blue;
}
5. ビルド
$ webpack
6. 出力結果
outディレクトリに以下が生成されます。
- index.html
- style.css
- main.js
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack App</title>
<link href="styles.css" rel="stylesheet"></head>
<body>
<script type="text/javascript" src="main.js"></script></body>
</html>
動的に生成したjsファイルとcssファイルが組み込まれている
index.html表示結果
※ style-loaderによって、app.jsが動的にstyleタグを組み込んでいないか確認するために、ためにし、生成したindex.htmlのlinkタグを削除して表示してみて下さい。
CopyWebpackPlugin
リソースファイルのコピー
file-loaderはimportされたファイルのみ出力先にコピーされる。
このプラグインを使えば、該当ディレクトリ配下にあるリソースをビルドはいかにコピーすることが出来る。
使い方
plugins: [
new CopyWebpackPlugin([{
from: './imgs', //コピー元
to: 'assets/imgs' //コピー先
}])
]
まとめ
使用するloaderによってimport結果が異なるため、定義する順番も含めて、どういったloaderが使われているのかは注意しておく必要があります。
公式リファレンス
参考書籍
いまどきのJSプログラマーのための Node.jsとReactアプリケーション開発テクニック
webpackだけでなく、npm、babel等の使い方が丁寧に解説されています。
参考サイト
webpackについて
[Qiita] step by stepで始めるwebpack
[Qiita] file-loaderで画像を扱うときのパス指定
[Medium] Webpack Loaders, CSS and Style Loaders
[stack overflow] Webpack style-loader vs css-loader
[Quora] How do you run react js code on your local?
Setting up CSS Modules with React and Webpack