npmとwebpack4でビルド - jQueryからの次のステップ

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

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

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

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

※発表当時はwebpack1についての記事でしたが、webpack1→webpack2→webpack3→webpack4と記事を更新しています。
[webpack3までの古い解説はこちら](https://qiita.com/civic/items/9f6d973839d2a1c99ac9)
（基本的なことなのでほとんど変わりませんが、webpack4でwebpack-cliが必要になったり設定ファイルの記述が変わりました）


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

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 JavaScript</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(){
...
```
今までは、jQueryのスクリプトタグを追加すると、暗黙的に`$`がjQueryを使うための記号になっていたが、これはグローバルに`$`という名前が定義されていたことになる。今後は`$`がどのように定義されているかが明示的に宣言されるようになる。


### webpackとwebpack-cliを開発用にインストール

```
npm install webpack webpack-cli --save-dev
```
<aside>※webpack4になるとwebpackをインストールしただけでは、コマンドラインツールはインストールされなくなりました。webpackとともにwebpack-cliをインストールします。</aside>

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

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

webpackは、`node_modules/.bin/webpack`にインストールされる。
PATH指定してwebpackを実行してもよいが、これを簡単に起動するために`npx`というコマンドを使用する。

```:例
node_modules/.bin/webpack -v
4.5.0

npx webpack -v
4.5.0

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

### bundle.jsの作成
webpackでmain.jsをビルドして、bundle.jsを作成。

```
npx webpack --mode development main.js --output 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を使ってみる
では、別の外部ライブラリである日付書式のライブラリ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でビルドを行う。

```
npx webpack --mode development main.js --output bundle.js
```

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

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

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

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

bundle.jsはソースコードが連結され、require部分が書き換えられたりすることで自分が編集したオリジナルのソースコードとは変わっていて、Chromeのデベロッパーツールでソースをデバッグするのがやりづらくなる。これを解決するのがsource map。

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

```
npx webpack --mode development main.js --output bundle.js --devtool source-map
```

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

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

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

webpack:// の配下を見るとmain.jsがあり、変換前のjsを見られることを確認。（ブレイクポイントなどもこちらで設定できる）
このことで、Chromeのデベロッパーツールでソースを開いた時に、ビルド前のソースを閲覧することができます。


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

オプション引数が増えてきたので、それを設定ファイルファイルに記述することでコマンド実行を単純にする。

webpack.config.jsを作成。

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


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

```
npx webpack
```

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


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

ファイルの変更を監視してwebpackの実行を行う。(他のオプションについてはwebpack.config.jsを引き続き使用）

```
npx webpack --watch
```

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

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

ブラウザの自動リロード
-------------------
webpack-dev-serverをインストールする。

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

webpack-dev-serverを起動する。(他のオプションについてはwebpack.config.jsを引き続き使用）

```
npx webpack-dev-server
```

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

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

<aside>※このときwebpack-dev-serverはビルドをしているがビルド後のbundle.jsを出力しているわけではない。ファイルbundle.jsは更新されないので注意。</aside>


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

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

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

```
npm install babel-core babel-loader@7 babel-preset-env --save-dev
```

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

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

```

webpack-dev-serverを起動

```
npx webpack-dev-server
```

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のコードに置き換えられている。

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


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

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

```bash:versions
$ node -v
v10.15.1
$ npm -v
6.4.1
```
```json:package.json
{
  "name": "webpack-study",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "jquery": "^3.3.1",
    "moment": "^2.24.0"
  },
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.5",
    "babel-preset-env": "^1.7.0",
    "webpack": "^4.29.0",
    "webpack-cli": "^3.2.1",
    "webpack-dev-server": "^3.1.14"
  }
}


```