Help us understand the problem. What is going on with this article?

webpack

More than 1 year has passed since last update.

使用したバージョン

"webpack": "^3.11.0"

webpackとは

Webクライアントアプリを作るためのNode.js製ビルドツール。

概要

webpack.png

webpacはエントリーファイルを持ち、最終的にJavaScriptファイルを出力する。

この際、エントリーファイルに import されたモジュールを指定された loader を使って解決していく。
また用意されたPluginを組み合わせることで、関連するファイル(html,css等)を自動生成させることが出来る。

これらのエントリーファイルや出力先、使用するloader、Plugin等は webpack.config.js ファイルに定義する。

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ファイルを生成するプラグインや、リソースをコピーするプラグインなど様々なプラグインが用意されている。
    ]
}

インストール

terminal
$ npm install --save-dev webpack

使い方

  1. webpack.config.js を作成する
  2. webpack コマンドでビルドする
terminal
$ 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を使って解決する

webpack.config.js
{
    test: /\.jsx/,
    loader: 'balel' //-loaderは省略可
    options: {
        presets: ['react']
    }
}

Ex2 pngなどのリソースがimportされた場合は、file-loaderを使って解決する
(file-loaderを使うと、importの出力値として、ファイルパスに変換してくれる。)

webpack.config.js
{
    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)に変換する。

インストール

terminal
$ npm i --save-dev babel-core babel-loader
$ npm i --save-dev babel-preset-es2015 babel-preset-react

使い方

webpac.config.js
// babel-loader 
{
    loader: 'babel-loader' 
    test: /.js$/,
    options: {
        presets: ['es2015', 'react'] 
    }
}
app.js
// 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

ファイルの作成

webpac.config.js
const path = require('path')
module.exports = {
    entry: './app.js',
    output: {
        filename: './out/app.js'
    },
    module: {
        rules:[
            {
                test: /\.js$/,
                loader: 'babel-loader',
                options: {
                    presets: ['es2015']
                }
            }
        ]
    }
}
foo.js
export default class Foo {
    constructor () {
        this.message = "Hello World"
    }
}
app.js
import Foo from 'foo'

const foo = new Foo()
console.log(foo.message)

ビルド

$ ./node_module/.bin/webpac

またpackage.jsonに"build"を定義すると npm run build でビルド出来る。

package.json
scripts: {
    "build": "webpack"
}

生成されたout/app.jsの内部を見ると、foo.jsが、ES5に変換されてapp.jsファイルにexportされている。

out/app.js
// 省略
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による出力結果

App.js
// 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

使い方

webpack.config.js
{
    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の指定方法

app.js
import img from './customer01/file.png'
webpack.config.js
{
  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

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

dev/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

dev/css/example.css
.content {
        background-image: url(../images/sample.png);
}

h1 {
    color: red
}

4. index.html

dist/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. ビルド & 表示結果

Screen Shot 2018-02-22 at 14.02.43.png

css-loader

css-loaderは、cssファイルを解析し、JavaScriptオブジェクトに変換する。

その際に、 @importurl()requireimport と同じように扱い、それらの解決も行う。

sample.css
.sample {
    color: red;
    // url()をハンドリングすると モジュールのimportと同一に扱う。
    backgruond-image: url('./images/example.jpg');
}
main.js
// 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

webpack.config.js
{
    test: /\.css$/,
    loader: 'css-loader'
}

app.js

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
sample.css
p { background-image: url('.../images/sample.png') } 

上記において、例えばwebpack.config.js側が以下だとする。

webpack.config.js
// file-loader
{
    test: /\.png$/,
    loader: 'file?name=images/[name].png'
}

ビルド後の出力結果は以下となり、コピー先と異なってしまっている事がわかる。

build/assets/css/sample.css
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

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

dev/css/sample.css
h1 {
    color: red;
}

.bg {
    background-image: url(../images/example.png);
}

main.js

dev/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

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>

ビルド

terminal
$ webpack

表示結果

Screen Shot 2018-02-24 at 14.09.19.png

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に問い合わせる)

webpack.config.js
{
    test: /\.css$/,
    use: [
        { loader: 'style-loader'},
        { loader: 'css-loader' }
    ]
}
sample.css
p {
    color: red;
}
app.js
import './sample.css'

ビルドして生成したapp.jsファイルをindex.htmlに組み込めば完了

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
Screen Shot 2018-02-21 at 13.21.30.png Screen Shot 2018-02-21 at 13.22.08.png

使い方2 linkタグをDOMにインジェクトする

file-loader と組み合わせることによって、linkタグとしてcssファイルを動的に組み込むJavaScriptに変換される。

またwebpac.donfig.jsでは、先にfile-loaderを実行するようにする。
(webpackは下側から順に問い合わせる)

webpack.config.js
{
    test: /\.css$/,
    use: [
        { loader: 'style-loader/url'}, //url
        { loader: 'file-loader' },
    ]
}
sample.css
p {
    color: red;
}
app.js
import './sample.css'

上記をビルドして、index.htmlファイルに組み込む。

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
Screen Shot 2018-02-21 at 13.21.30.png Screen Shot 2018-02-21 at 14.05.29.png

プラグイン

webpackにおいてプラグインは、関連するファイルを自動生成するために使用される。

HtmlWebpackPlugin

HTMLを自動生成する。
また生成したJavaScriptファイルをbodyにインジェクトする。

インストール

$ npm install --save-dev html-webpack-plugin

使い方1 htmlファイルの自動生成

1. webpack.config.jsの用意

webpac.config.js
module.exports = {
    entry: './app.js',
    output: {
        path: path.join(__dirname, 'out'),
        filename: '[name].js'
    },
    plugins: [
        new HtmlWebpackPlugin(),
    ]
}

2. entryファイルの用意

app.js
document.addEventListener('DOMContentLoaded', (event) => {
    const body = document.getElementsByTagName('body')[0]
    body.innerHTML = 'Hello World'
})

3. ビルドする

$ webpack

4. 出力結果

index.htmlが自動生成され、 app.js ファイルが組み込まれる。

index.html
<!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

webpac.config.js
plugins: [
    new HtmlWebpackPlugin({
        template: './template.html',
        filename: 'index.html',
    })
]

template.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ファイルも組み込まれる。

out/index.html
<!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

使い方

webpack.config.js
  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

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

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;
}
sample2.css
p {
    color: blue;
}

5. ビルド

$ webpack

6. 出力結果

outディレクトリに以下が生成されます。

  • index.html
  • style.css
  • main.js
oud/index.html
<!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表示結果

Screen Shot 2018-02-24 at 14.29.51.png

※ style-loaderによって、app.jsが動的にstyleタグを組み込んでいないか確認するために、ためにし、生成したindex.htmlのlinkタグを削除して表示してみて下さい。

CopyWebpackPlugin

リソースファイルのコピー

file-loaderはimportされたファイルのみ出力先にコピーされる。
このプラグインを使えば、該当ディレクトリ配下にあるリソースをビルドはいかにコピーすることが出来る。

使い方

webpack.config.js
plugins: [
    new CopyWebpackPlugin([{
        from: './imgs',    //コピー元
        to: 'assets/imgs'  //コピー先
    }])
]

まとめ

使用するloaderによってimport結果が異なるため、定義する順番も含めて、どういったloaderが使われているのかは注意しておく必要があります。

公式リファレンス

https://webpack.js.org/

参考書籍

いまどきのJSプログラマーのための Node.jsとReactアプリケーション開発テクニック

webpackだけでなく、npm、babel等の使い方が丁寧に解説されています。

参考サイト

webpackについて

[Qiita] step by stepで始めるwebpack

[Qiita] 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

npmについて

[Qiita] npm

ysn
はてな https://yossan.hatenablog.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away