[旧記事]npmとwebpack3でビルド - jQueryからの次のステップ

[旧記事]むずかしくないJavaScriptのやさしい話　デモチュートリアル
=========================================================

**この記事はwebpack3までの古い内容を残したものです。webpack4以降の最新記事はこちらです。**
「[npmとwebpack4でビルド - jQueryからの次のステップ](https://qiita.com/civic/items/82c0184bcadc50965f91)」


このチュートリアルは、#ndsmeetup8 で発表した内容のデモの手順をまとめたものです。

jQueryをscriptタグ挿入で使っているような開発者向けの内容です。
npmとwebpackでビルドをするという使い方の提案です。

発表内容はこちら。
<http://www.slideshare.net/fbcivic/javascript-ndsmeetup8>

※発表当時はwebpack1についての記事でしたが、webpack1→webpack2→webpack3と記事を更新しています。
（基本的なことなのでほとんど変わりません。設定ファイルの記述が多少変更になりました。）

事前準備
----------

nodejsをインストールしてください

ターミナル(Mac)や、コマンドプロンプト(Windows)を使いますので
基本的な操作は事前に学習しておいてください。

旧来のJavaScript開発フロー
--------------------------

index.htmlを作る

```html:index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
</head>
<body>
    <h1 id="msg">Hello JavaScrit</h1>
</body>
</html>
```
jQueryのサイトからjqueryのjsをダウンロードしてくる。
<https://jquery.com/> 

Download jQuery → 適当なバージョンのCompressed jQueryX.Y.Zをダウンロード。

index.htmlと同じフォルダにコピー。（もしくはCDN経由で取得するようにしてもよいですね）

htmlにjqueryのscriptタグを挿入。

```html:index.html
...
<head>
    <meta charset="UTF-8">
    <script src="./jquery-3.3.1.min.js"></script>
</head>
...
```

main.jsを作成。

```js:main.js
$(function(){
    var $msg = $("#msg");
    $msg.fadeOut("slow", function(){
        $msg.text("jQuery")
            .css("color", "red")
            .fadeIn("slow");
    });
});
```

htmlにmain.jsのscriptタグを挿入。

```html:index.html
...
<head>
    <meta charset="UTF-8">
    <script src="./jquery-3.3.1.min.js"></script>
    <script src="./main.js"></script>
</head>
...
```
ブラウザでindex.htmlを開いて、fadeOut, fadeInすることを確認。

npmを使ってjqueryをインストール
--------------------------------

旧来の開発フローとは別の新しいフォルダで作業開始。

コマンドで、npm initと入力して、とりあえず全部デフォルトでENTERを連続して入力して完了。

```
npm init
```

package.jsonファイルができていることを確認。

jqueryをnpmでインストールする。

```
npm install jquery --save
```

package.jsonにjqueryとバージョンが記録されていることを確認。

node\_modulesフォルダの配下にjqueryが入っていることを確認。


WebPackをつかってビルド
--------------------------------

index.html, main.jsを旧来の開発で作ったファイルをコピー。

main.jsをrequireを使ってjqueryを使用するように修正。

```js:main.js
var $ = require("jquery");
$(function(){
...

```

~~WebPackをグローバル(-g)インストール~~
WebPackを開発用にインストール
*※以前の記事ではwebpackを-gでグローバルインストールしていましたが、カレントディレクトリのnode_modulesにインストールしてnpm run から使う方が環境を汚さず良いと思ったため全体的に書き直しました。*

*※ WebPack4になるとwebpackをインストールしただけでは、コマンドラインツールはインストールされなくなりました。(webpack-cliが必要）この記事はwebpack3について記述してありますので、修正するまではwebpack3をバージョン指定してインストールしてください。*

```
npm install webpack@3.11.0 --save-dev
```

さっきのjqueryは --saveでインストールした。今回のwebpackは --save-devでインストールした。

- `--save` 実行に必要な依存モジュールとしてpackage.jsonに記録
- `--save-dev` 開発時に必要なモジュールとしてpackage.jsonに記録

webpackは、`node_modules/.bin/webpack`にインストールされる。これを簡単に起動するために`package.json`の`scripts`の設定をする。こうすることで、`npm install`で`node_modules`にインストールしたツール群を`npm run`経由で実行できる。（グローバル環境を汚さない）

```json:package.json
...中略...
  "scripts": {
    "webpack": "webpack"
  },
...中略...
```

`npm run scriptsの定義名`で実行し、引数は` -- `の後ろに記述する。

```:例
npm run webpack -- -v
> webpack-study@1.0.0 webpack /home/foobar/
> webpack "-v"
3.11.0
```
よく使うコマンドは別名をつけてnpm runのコマンド化してよい。

webpackでのビルドの話に戻す。

WebPackでmain.jsをビルドして、bundle.jsを作成。

```
npm run webpack -- main.js bundle.js
```

bundle.jsが作成されたことを確認。

index.htmlでbundle.jsを使うようにscriptタグを修正。

```html:index.html
...
<head>
    <meta charset="UTF-8">
    <script src="./bundle.js"></script>
</head>
...
```

ブラウザでindex.htmlを開いて、fadeIn, fadeOutが動作することを確認。

webpackを使ってビルドするという作業は、「**複数のJavaScriptソースファイルを依存関係を考慮してくっつけた一つのファイルを作ること**」（今回で言うbundle.jsがビルドして生成されたファイル）

今までは、jQueryのソースファイル、Xライブラリのソース、自分の作ったソースなどをscriptタグで列挙していたと思いますが、たくさんの外部ライブラリを使用するようになると列挙する順番も複雑になって難しい。今後はwebpackが生成したbundle.jsを一つだけscriptタグに指定すればよい。

では、別の外部ライブラリである日付書式のライブラリmoment.jsを使ってみる。

npmでmomentをインストール

```
npm install moment --save
```

package.jsonにmomentとバージョンが記載されたことを確認。

main.jsを編集して、momentを使ってみる。

```js:main.js
var $ = require("jquery");
var moment = require("moment");

$(function(){
    var $msg = $("#msg");
    $msg.fadeOut("slow", function(){
        $msg.text("WebPack " + 
                moment().format("YYYY-MM-DD HH:mm:ss"))
            .css("color", "red")
            .fadeIn("slow");
    });
});
```

再びWebPackでビルドを行う。

```
npm run webpack -- main.js bundle.js
```

再びindex.htmlをブラウザで確認する（リロード）

moment.jsで書式化した日付が表示される。

外部モジュールを追加して使用するという流れがnpm installしてrequireで使うという汎用的な使い方になった。

ファイルの連結時に圧縮したい
-----------------------------

オプション引数 `--optimize-minimize` をつけてビルドする。

```
npm run webpack -- main.js bundle.js --optimize-minimize
```

bundle.jsを開いて、コードが圧縮（minify）されていることを確認。

再びindex.htmlをブラウザで正しく動作することを確認する（リロード）


デバッグしにくいからsource mapを作成
-----------------------------

オプション引数 `--devtool source-map` をつけてビルドする。

```
npm run webpack -- main.js bundle.js --optimize-minimize --devtool source-map
```

bundle.js.mapが作成されていることを確認。


再びindex.htmlをブラウザで正しく動作することを確認する（リロード）

Chromeでデベロッパーツールを開いて、Sourcesタブのツリーからwebpack://のツリーが存在することを確認。

webpack:// のは以下を見るとmain.jsがあり、圧縮前のjsを見られることを確認。（ブレイクポイントなどもこちらで設定できる）


オプション引数覚えられない
----------------------------

webpack.config.jsを作成。 (minifyはなし）

```js:webpack.config.js
module.exports = {
    entry: './main.js',
    output: {filename: 'bundle.js'},
    devtool: 'source-map'
};
```

オプション引数なしでビルド。

```
npm run webpack
```

再びindex.htmlをブラウザで正しく動作することを確認する（リロード）


コマンドを毎回実行するのが面倒
----------------------------

ファイルの変更を監視してwebpackの実行を行う。

```
npm run webpack -- --watch
```

webpackの実行が終了せずに、ファイルの変更待ちになることを確認。

main.jsを適当に修正して保存すると、自動的にWebPackのビルドが行われることを確認。
ブラウザでの確認はリロードが必要。

webpack-dev-serverをインストールする。

```
npm install webpack-dev-server --save-dev
```

webpackの時と同じように、`package.json`の`scripts`にserveコマンドとして登録する。

```json:package.json
...中略...
  "scripts": {
    "webpack": "webpack",
    "serve": "webpack-dev-server"
  },
...中略...
```


webpack-dev-serverを起動し、bundle.jsにinlineでオートリロードのコードを埋め込む。

```
npm run serve -- --inline
```

http://localhost:8080/ をブラウザで開いて、正しく表示されることを確認。

main.jsを適当に修正して保存すると、自動的にWebPackのビルドが行われたあと、
ブラウザのリロードまで行われることを確認。
（このときwebpack-dev-serverはビルドをしているがビルド後のbundle.jsを出力しているわけではないのでファイルは更新されない。）


CTRL+cでwebpack-dev-serverを停止。

ES2015(ES6)を使ってみる
-----------------

babel-core, babel-loader, babel-preset-es2015 をインストールする。

```
npm install babel-core babel-loader babel-preset-es2015 --save-dev
```

webpack.config.jsを修正して、babel-loaderの記述を追加。

```js:webpack.config.js
module.exports = {
  entry: './main.js',
  output: {filename: 'bundle.js'},
  devtool: 'source-map',
  module: {
    rules: [
      {
        test: /\.js$/, 
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['babel-preset-es2015']
          }
        }
      }
    ]
  }
};

```

webpack-dev-serverを起動

```
npm run serve -- --inline
```

http://localhost:8080/ をブラウザで開いて、ES5のままでも正しく表示されることを確認。

main.jsを編集し、ES2015(ES6)を使ったコードに修正して保存する。
(requireの仕方が標準のimportのやり方に変わったり、関数をアロー演算子で記述できている）

```js:main.js
import $ from "jquery";
import moment from "moment";

$(() => {
    let $msg = $("#msg");
    $msg.fadeOut("slow", () => {
        $msg.text("WebPack " + 
                moment().format("YYYY-MM-DD HH:mm:ss"))
            .css("color", "blue")
            .fadeIn("slow");
    });
});
```

自動的にWebPackのビルドが行われたあとブラウザのリロードが行われること。

ES2015(ES6)でも正しく動作することを確認。

自分が書いたコードはES2015(ES6)の新しいコードで記述しているが、ビルド後はbabelの機能によってES5のコードに変換されている。Chromeのデベロッパーツールでビルド後のソースを閲覧してみると、ES6→ES5のコードに置き換えられている。

※この記事を執筆したときはES2015の機能に対応していないブラウザについて考慮する必要があったが、最近のブラウザであれば上記程度の機能はブラウザ側ですでに対応されているため、babelを使わなくてもよいことも考えられる。（例えばモバイルブラウザだけの対応であればES2015の機能をそのまま使っても大丈夫）

古いブラウザのことを考慮したり、ES2015よりも新しいJavaScriptの機能を使う場合にはこれと同じようにbabelでブラウザが対応できる機能に変換してやる必要がある。


#### 参考：動作確認時のpackage.jsonについて

記事最終変更時の動作確認したnode, npm, モジュールのバージョンなどについて。

```bash:versions
$ node -v
v8.9.4
$ npm -v
5.6.0
```
```json:package.json
{
  "name": "webpack-study",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "webpack": "webpack",
    "serve": "webpack-dev-server"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "jquery": "^3.3.1",
    "moment": "^2.20.1"
  },
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-es2015": "^6.24.1",
    "webpack": "^3.11.0",
    "webpack-dev-server": "^2.11.1"
  }
}

```