step by stepで始めるwebpack

  • 888
    Like
  • 2
    Comment

※webpack1系の記事です。
1→2への移行ガイドを見る限り破壊的な変更点はそんなにありませんが、preLoaderspostLoadersがなくなっていたり、差が出ていますのでご注意ください。
https://webpack.js.org/guides/migrating/


webpackを手を動かしつつ学べる初学者向け資料を作成しました。
公式のチュートリアルもあるのですが、webpackが使用されているOSSのボイラープレートを見る限り、世の中の使われ方に沿ってないかも?と思い書きました。
これから始める人の手助けになれば幸いです。

また最終形のソースをupしてあるので詰まったら見てください。

webpackとは

webpack で始めるイマドキのフロントエンド開発の説明が良かったので引用させていただきます。m(_ _)m

webpack は WebApp に必要なリソースの依存関係を解決し、アセット(配布物)を生成するビルドツール(要するにコンパイラ)です。JavaScript だけでなく、CoffeeScript や TypeScript、CSS 系、画像ファイルなどを扱うことができます。
WebApp のビルドツールは Grunt や Gulp が有名です。これらは基本的に、ビルド手順をタスクという形で自ら定義する必要があり、フロントエンド開発に馴染みのない開発者にとっては敷居が高いものでした(少なくとも、自分はそうでした)。
webpack を使えば、Grunt も Gulp も必要ありません!覚えるべきことはほとんどありません。(必要なら)簡単な設定ファイルを書いて webpack コマンドを実行するだけです。

ただWebpackはビルドツールなのでビルド以外のタスクが出てきたらGruntやGulpに任せた方が無難だと思います。

また最近発表されたReact.jsアプリの雛形生成ツールでも使われていますね。
Facebook公式のcreate-react-appコマンドを使ってReact.jsアプリを爆速で作成する

image

前提

Nodejs,npmが導入済みであること

流れ

Step 目的
Step 1 準備
Step 2 簡単なjsでwebpackを実行してみる
Step 3 設定ファイル(webpack.config.js)を導入
Step 4 Loaderを導入
Step 5 Loaderの様々な指定方法
Step 6 拡張子の省略(resolve.extensions)
Step 7 Pluginを導入
Step 8 ソースマップの出力(devtool)

Step 1 準備

package.jsonの作成
npm init

webpackのインストールも行っておきます。
本記事ではグローバルではなくプロジェクト内にインストールする形にします。

webpackのインストール
npm install --save-dev webpack

Step 2 簡単なjsでwebpackを実行してみる

このStepでは簡単なjsファイルを作ってwebpackを実行します。
webpackがどのような処理をしているのかを理解しましょう。

cats.jsの作成

猫の一覧を配列で返すJavaScriptです。

cats.js
var cats = ['tama', 'kuro', 'tora']
module.exports = cats;

app.jsの作成

エントリーポイントとなるメインのJavaScriptです。
猫の一覧を読み込んでログ出力します。

app.js
var cats = require('./cats.js');
console.log(cats);

webpackの実行

早速実行してみます。
以下のようにして実行が可能です。

webpackの実行
node node_modules/.bin/webpack app.js bundle.js

Hash: 2781572ec422f461e66a
Version: webpack 1.13.1
Time: 64ms
    Asset     Size  Chunks             Chunk Names
bundle.js  1.58 kB       0  [emitted]  main
   [0] ./app.js 51 bytes {0} [built]
   [1] ./cats.js 58 bytes {0} [built]

app.jsがwebpackに読み込ませるファイルでbundle.jsが出力するファイル名です。
このapp.jsをエントリーポイントと呼びます。

実行方法の最適化

上で書いたような実行も可能ですが、毎回こんな長いパス打つのは大変です。
package.jsonに書くことでnode_modules/.bin/にでパスが自動で通るためコマンドをシンプルにできます。

package.jsonを修正
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
+   "build": "webpack app.js bundle.js"
  },
npmコマンド経由でwebpackを実行
npm run build

bundle.jsを実行

作るだけ作って実行はしていませんでした。
作成されたbundle.jsを実行してみましょう。

作成したbundle.jsを実行
node bundle.js

[ 'tama', 'kuro', 'tora' ]

cats.jsの中身が表示されていますね。

Step 3 設定ファイル(webpack.config.js)を導入

このStepではwebpack.config.jsという設定ファイルでwebpackの実行時の設定を行うように変更します。
上記の名前でファイルを作成しておくとwebpackと打つだけでwebpack.config.jsが自動で読み込まれます。

webpack.config.jsへの移行

webpack.config.jsファイルを作成して、以下のようにエントリーポイントと出力ファイル名を記述します。
※エントリーポイントの頭に./がつくのに注意してください。

webpack.config.jsの作成

webpack.config.js
module.exports = {
  entry: './app.js',
  output: {
    filename: 'bundle.js'
  }
};

package.jsonを修正

package.json内のコマンドをwebpackだけにします。

package.jsonを修正
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
-   "build": "webpack app.js bundle.js"
+   "build": "webpack"
  },

確認

ビルド&実行して動作を確認してください。

npmコマンド経由でwebpackを実行
npm run build
実行
node bundle.js

ソースディレクトリ・出力ディレクトリを作成

プロジェクト直下のファイルが多くなってきました。
今後辛くなってくるのでソースディレクトリと出力ディレクトリを分けます。

ディレクトリの作成とファイルの移動

ディレクトリの作成とファイルの移動
mkdir src
mv app.js src
mv cats.js src
bundle.jsの削除
rm bundle.js

webpack.config.jsを修正

webpack.config.js
module.exports = {
- entry: './app.js',
+ entry: './src/app.js',
  output: {
+   path: 'dist',
    filename: 'bundle.js'
  }
};

確認

ビルド&実行して動作を確認してください。

npmコマンド経由でwebpackを実行
npm run build
実行
node dist/bundle.js

Step 4 Loaderを導入

このStepではwebpackの肝であるLoaderの概要と基本的な使い方を習得しましょう。

Loaderとは?

LoaderはTypeScriptやBabel、PNGやJPEG、SassやLESS、JSONやYAMLなど様々なファイルを読み込むことが可能になる拡張機能のことです。

JSONを読み込んでみよう

cats.jsは配列を返すJavaScriptでしたね。
これをJSONで読み込むようにしてみましょう。

cats.jsonの作成

※わかりやすくするためcats.jsonという猫がいることにします。。

cats.json
["cats.json", "tama", "kuro", "tora"]

json-loaderのインストール

JSONを読み込むにはjson-loaderというLoaderを使います。

json-loaderのインストール
npm install --save-dev json-loader

json-loaderをロードするように設定

cats.jsからcats.jsonに変更し、requireの頭にjson-loader!を追加します。

app.js
- var cats = require('./cats.js');
+ var cats = require('json-loader!./cats.json');
  console.log(cats);

また、以下のように -loaderは省略可能です
以後は省略記法で記載していきます。

app.js
- var cats = require('json-loader!./cats.json');
+ var cats = require('json!./cats.json');
  console.log(cats);

確認

出力にcats.jsonが含まれていることを確認してください。

npmコマンド経由でwebpackを実行
npm run build
実行
node dist/bundle.js

[ 'cats.json', 'tama', 'kuro', 'tora' ]

json-loaderは何をしている?

実際のjson-loaderのソースを見てみましょう。

json-loader
module.exports = function(source) {
    this.cacheable && this.cacheable();
    var value = typeof source === "string" ? JSON.parse(source) : source;
    this.value = [value];
    return "module.exports = " + JSON.stringify(value, undefined, "\t") + ";";
}

上の方は無視して最後のreturn行を見てください。
"module.exports = "JSONを文字列化したものつなげてreturnしていますね。
cats.jsと同じようなJavaScriptの文を生成しているのがわかるかと思います。
JSONで書かれたの文字列をJavaScriptで書かれた文字列に変換しているわけですね。

複数のLoaderを扱う

Loader同士は繋げることが可能です。
例としてYAMLファイルを扱えるyaml-loaderを扱います。

cats.ymlの作成

※わかりやすくするためcats.ymlという猫がいることにします。

cats.yml
[cats.yml, tama, kuro, tora]

yaml-loaderのインストール

yaml-loaderのインストール
npm install --save-dev yaml-loader

app.jsの修正

cats.jsonからcats.ymlに変更します。
json!の後にyaml!を追加します。

※Loaderは右から左の順に実行されます。
※yaml-loaderはJSONを出力します。
そのため以下の様にLoaderを繋ぐ必要があります。
YAML ⇢ yaml-loader ⇢ JSON ⇢ json-loader ⇢ JavaScript

app.js
- var cats = require('json!./cats.json');
+ var cats = require('json!yaml!./cats.yml');
  console.log(cats);

確認

出力にcats.yamlが含まれていることを確認してください。

npmコマンド経由でwebpackを実行
npm run build
実行
node dist/bundle.js

[ 'cats.yml', 'tama', 'kuro', 'tora' ]

つまりLoaderとは

通常、require()はJavaScriptを読み込むためのものですが、Loaderを通すことでJavaScript以外のリソースを読み込めるようにするためのものです。
最終的にJavaScriptの形に変換します。

Step 5 Loaderの様々な指定方法

このStepではLoaderの指定方法の違いを見ていきます。

requireで指定

これは今までに出てきたrequire('hoge-loader!〜)の形です。

module.Loadersで指定

ファイルを読み込む処理が発生するたびにrequireにLoaderを書いていくのは大変です。
そこでデフォルトのLoaderを定義しておくことで記述を省略できます。

設定プロパティは以下の2つが主です。

  • testプロパティに対象となるファイルを検索する正規表現を指定
  • loaderプロパティに通したいLoaderを指定

.ymlに対してはyaml-laoder->json-loaderで通すのでしたね。
それを行うための設定が以下のようになります。

webpack.config.js
module.exports = {
  entry: './app.js',
  output: {
    filename: 'bundle.js'
- }
+ },
+ module: {
+   loaders: [
+     {test: /\.yml$/, loader: 'json!yaml'}
+   ]
+ }
};

拡張子で設定することが多いようです。
testに複数マッチするように設定してしまうとと、マッチした全てのloaderを通すことになるので注意です。

上記設定をおこなったので以下の様にrequire部分でloaderを省略が可能です。

app.js
- var cats = require('json!yaml!./cats.yml');
+ var cats = require('./cats.yml');
  console.log(cats);

module.preLoadersで指定

これは最初に通すLoaderです。

module.postLoadersで指定

これは最後に通すLoaderです。

順番と使い分け

順番としては以下の様になります。

  1. module.preLoaders(設定ファイル)
  2. module.Loaders(設定ファイル)
  3. require内のLoader(ソースコード)
  4. module.postLoaders(設定ファイル)

基本はmodule.Loadersに設定するといいと思います。

loadersの指定方法について補足

複数loaderの指定

!で繋げて文字列で指定する書き方の他に配列で指定することも可能です。

loaders: [
  {
    test: /\.yml$/,
-    loader: 'json!yaml'
+    loaders: ['json', 'yaml']
  }
]

queryの指定

loaderの種類によってオプションとしてパラメータを受け取れるものがあります。
その場合、文字列ではなくオブジェクトで指定が可能です。
※複数loaderを繋げる場合は文字列で書くしかないかも?

loaders: [
  {
    test: /\.hoge$/,
-    loader: 'hoge?key1=value1'
+    loader: 'hoge'
+    query: { key1: 'value1' }
  }
]

Step 6 拡張子の省略(resolve.extensions)

通常requireに指定するファイル名には拡張子をつけますがその省略が可能です。
その設定を行うのがresolve.extensionsです。

拡張子の省略

resolve.extensionsのデフォルト値は
["", ".webpack.js", ".web.js", ".js"]
のようになっており.jsが最初から含まれています。
例えばjsを読み込む場合は、以下のように.jsが省略可能です。

.jsを省略した例
- var cats = require('./cats.js');
+ var cats = require('./cats');
  console.log(cats);

順番に注意

extensionsは配列になっており順番が重要です。
見つかるまで前から順に探します。

つまりrequire('./cats')と指定した場合は以下の流れです。

  1. catsのファイルを探しにいく -> 見つからない
  2. cats.webpack.jsのファイルを探しにいく -> 見つからない
  3. cats.web.jsのファイルを探しにいく -> 見つからない
  4. cats.jsのファイルを探しにいく -> 見つかった! -> require('./cats.js')として扱う。

上書きされるので注意

resolve.extensionsの設定は追加ではなく上書きです。
そのためextensionsの最初の空文字""を消して[".js"]だけ指定してしまうと拡張子付きのrequire('./cats.js')という書き方ができなくなってしまいます。

省略しつつYAMLを読み込む

デフォルトでは.ymlは入っていませんでしたね。
.ymlを拡張子なしで読み込んでみましょう。

以下の様にresolve.extenisonを上書きする設定を追加します。

webpack.config.js
module.exports = {
  entry: './src/app.js',
  output: {
    path: 'dist',
    filename: 'bundle.js'
  },
+ resolve: {
+   extensions: ["", ".webpack.js", ".web.js", ".js", ".yml"]
+ },
  module: {
    loaders: [
      {test: /\.yml$/, loader: 'json!yaml'}
    ]
  }
};
app.jsの修正(cats.ymlを拡張子を省略して読み込む)
- var cats = require('./cats.yml');
+ var cats = require('./cats');
  console.log(cats);

cats.jsをリネームしてcats.ymlが読み込まれることを確認

cats.jsを読み込ませないためにcats.jsを退避しておき、cats.ymlを読み込まれることを確認しましょう。

cats.jsをリネーム
mv src/cats.js src/cats.js_bk
npmコマンド経由でwebpackを実行&確認
npm run build
node dist/bundle.js

[ 'cats.yml', 'tama', 'kuro', 'tora' ]

省略するメリット

省略したらどのファイルを読み込むのかがわかりにくくなる反面、疎結合にできます。

先程の例でいうと呼び元は猫の一覧が欲しいのです。
それがJavaScriptであろうとJSONであろうとYAMLであろうと呼び元のソースコードは変えずにファイルの有無でファイルを切り替えることが可能です。

Step 7 Pluginを導入

webpackはPluginを用いることで機能を拡張する事が可能です。
このstepでは例として、トップページのhtmlを自動生成するPluginと圧縮するプラグインを導入します。

html-webpack-pluginのインストール

htmlを自動生成するためのプラグインです。

html-webpack-pluginのインストール
npm install --save-dev html-webpack-plugin

設定ファイルを修正

圧縮も一緒に設定してしまいます。
圧縮はwebpack.optimize.UglifyJsPluginを使用します。
(圧縮はwebpackの標準プラグインなのでnpmインストールは不要です)

webpack.config.js
+ var webpack = require('webpack');
+ var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/app.js',
  output: {
    path: 'dist',
    filename: 'bundle.js'
  },
  resolve: {
    extensions: ["", ".webpack.js", ".web.js", ".js", ".yml"]
  },
  module: {
    loaders: [
      {test: /\.yml$/, loader: 'json!yaml'}
    ]
- }
+ },
+ plugins: [
+   new webpack.optimize.UglifyJsPlugin(),
+   new HtmlWebpackPlugin({
+     title: 'Sample Page'
+   })
+ ]
}; 

※HtmlWebpackPluginにtitleだけ設定していますが、テンプレートHTMLから作ったり様々な機能があります。

htmlが作られたことを確認

npmコマンド経由でwebpackを実行
npm run build

Hash: 212358e547c6141ea787
Version: webpack 1.13.1
Time: 936ms
     Asset       Size  Chunks             Chunk Names
 bundle.js  304 bytes       0  [emitted]  main
index.html  182 bytes          [emitted]  
   [0] ./src/app.js 48 bytes {0} [built]
    + 1 hidden modules
Child html-webpack-plugin for "index.html":
        + 3 hidden modules

bundle.jsを読み込むhtmlが作られます。

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Sample Page</title>
  </head>
  <body>
  <script type="text/javascript" src="bundle.js"></script></body>
</html>

ブラウザでindex.htmlを開いて確認

ブラウザでindex.htmlを開いてみましょう。
consoleにちゃんと猫の一覧が表示されていますね。

image

またsourcesタブを開いてbundle.jsを見てみると圧縮されていることが確認できます。
image

Step 8 ソースマップの出力(devtool)

webpackではソースマップの出力の仕組みが元々組み込まれているのでプラグイン等不要で出力可能です。

ソースマップを設定

webpack.config.js
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/app.js',
  output: {
    path: 'dist',
    filename: 'bundle.js'
  },
  resolve: {
    extensions: ["", ".webpack.js", ".web.js", ".js", ".yml"]
  },
  module: {
    loaders: [
      {test: /\.yml$/, loader: 'json!yaml'}
    ]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    new HtmlWebpackPlugin({
      title: 'Sample Page'
    })
- ]
+ ],
+ devtool: 'source-map'
}; 
npmコマンド経由でwebpackを実行
npm run build

ブラウザでindex.htmlを開いて確認

index.htmlを開いてsourcesタブを表示します。
webpack://の配下に圧縮前のファイルが表示されます。
圧縮前のファイルにもブレークポイントも貼れるのでデバッグも問題ありませんね。

image

補足:devtoolの設定値

本記事では'source-map'を指定しましたが7種類もあるようです。
https://webpack.github.io/docs/configuration.html#devtool
'source-map'は情報量が多い反面、実行に時間がかかるみたいですね。
'source-map'で遅いと感じたら違うのを検討していく形でしょうか。

あとがき

本記事からさらに学びたい方は公式ドキュメントの使い方の例を見ていくと良いと思います。

webpackはLoaderやPluginの種類が豊富なのでそのへんの逆引きリファレンスみたいなやつの需要あるかもしれないなぁ。