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

webpackとBabelの基本を理解する(1) ―webpack編―

独学の内容をまとめたものです。誤りがございましたら、ご連絡いただけると幸いです。

リンク
1. webpackとBabelの基本を理解する(1) ―webpack編―(本記事)
2. webpackとBabelの基本を理解する(2) ―Babel編―
3. webpackとBabelの基本を理解する(3) ―webpackとBabel編―
4. webpackとBabelの基本を理解する(4) ―React編―
5. webpackとBabelの基本を理解する(5) ―Sass編―

概要

この記事の概要

  • 目的
    • フロントエンドの環境構築に利用されるツールへの理解を深める
  • 本記事のゴール
    • webpackでJSファイルを結合する方法を知る
  • 対象者
    • WEBフロント担当者
    • HTML,CSS,JavaScript(es2015含む)の基本的な構文を理解している人
    • npmの利用方法を理解している人
  • 環境・バージョン
    • Windows10
    • Node.js(推奨版) 10.15.01
    • npm 6.4.1
    • webpack 4.29.6
    • webpack-cli 3.2.3

ESモジュールとwebpack

es2015より、JavaScriptにモジュールの概念が導入されました。ここでいうモジュールは、.jsファイルのことです。
これまでは、ファイル内でグローバルな変数や関数は、他のファイルから見ても同様にグローバルでした。つまりファイルの間にスコープの垣根がありませんでした。その為、他のファイルに記述されたコードと名前が重複したときに、意図せず値を書き換えられてしまうなどのリスクがありました。
モジュールの概念が存在すればスコープがファイル内に限定されるため、名前が衝突する心配が大幅に少なくなります。

2015年に仕様として提示され、ブラウザへの実装も進みつつあるようですが、まだ十分ではありませんし、古いブラウザをサポート対象に含めていれば端から無理です。また、参照ファイル数が多いと当然ブラウザからのリクエスト数が増えてしまいます。

しかし、モジュールで便利に開発したい!という人々の熱意で、それを実現する様々なツールが生み出され、その一つがwebpackです。

公式サイト(英語)
スコープとは | IT用語辞典

Node.jsとnpm

Node.jsとnpmの利用が前提となっています。まだ使ったことなーい!という方は、こちらの記事をご覧ください。

Node.jsとExpressでローカルサーバーを構築する(1) ―Node.jsとnpmの導入―

webpackの基本

importとexport

異なるモジュール間で連携したい場合は、明示的に手続きをとる必要があります。
他のモジュールに対して公開したい変数や関数、クラスなどをexport文を用いてエクスポートします。
公開されている変数や関数、クラスなどを利用する側ではimport文でインポートして利用します。

/**
 * /utils/tax.js
 */
const TAX = 1.08;

// 1円未満切捨て
const CalcTax = (num)=>{
  return Math.floor(num * TAX);
}

export { CalcTax };


/**
 * /index.js
 */
import { CalcTax } from './utils/tax';

let amounts = CalcTax(10000);

console.log(amounts);

/utils/tax.jsTAXはエクスポートされていないので、/index.jsからは参照できません。/index.js内で宣言されている変数amountsも同様です。

import { ClacTax, TAX } from '/utils/tax';

// ↑↑TAXは公開されていないので、SyntaxErrorとなる

MDN: export
MDN: import

Node.jsでのimport/export
上記コードはes2015の記法ですが、Node.jsで直接実行する場合、es2015のimport/exportはそのままでは利用できません。(Ver.10.15.01現在)
module.exportsrequire文を利用するか、以下の手順で利用します。

  1. エクスポート・インポート、双方のファイルの拡張子を.mjsにする
  2. ファイル実行時に、--experimental-modulesオプションを追加する

$ node --experimental-modules <ファイル名>

experimentalとあるとおり、2019年2月現在では実験段階のオプションです。webpackを利用する場合は、特に.mjsにする必要はありません。
リファレンス: ECMAScript Modules

webpackの役割

webpackは指定されたファイルを起点として、そこからimport文を頼りに芋づる式にファイルを繋げてゆき、一つにまとめたJavaScriptファイルを出力します。
このまとめる処理はバンドル(bundle)と呼ばれています。(束ねるの意)そしてバンドルを行うツールはバンドラーと呼ばれています。
ポイントは、“基本機能はあくまで一つのJavaScriptファイルにまとめる”というところです。

webpackでまとめる

webpackのインストール

まずは、npmを使ってプロジェクトを開始します。
任意の場所に作業フォルダを作成し、コマンドプロンプトで下記コマンドを実行してpackage.jsonを作成します。

$ npm init -y

引き続き、npmでwebpackをインストールします。

$ npm install webpack webpack-cli --save-dev

ここでインストールしているのは、webpack本体と、webpackのコマンドライン操作用のパッケージの二つです。

ガイド: installation

対象ファイルの準備

作業フォルダ配下に、下記のような名前のフォルダとファイルを用意します。

sample/
  ├ src/
  │   ├ component/
  │   │    └ test.js
  │   └ index.js 
  └ dist/
/**
 * /src/component/test.js
 */ 
function test(){
  console.log('Hello World!!');
}

export { test };


/**
 * /src/index.js
 */
import { test } from './component/test'; //拡張子は省略可能

test();

webpackでバンドる

下記コマンドを実行してバンドルします。
ここで登場しているnpxは、npmのバージョン5.2.0以降から利用できるコマンドです。
webpackのコマンドをそのまま利用するには、/node_modules/.bin/までのパスを事前に登録しておく必要がありますが(Pathを通す)、npxコマンドを利用するとすぐに利用できます。(npm自体はPathが通っている前提)

$ npx webpack

すると、なんということでしょう。distフォルダの中にmain.jsというファイルが新たに出力されているではありませんか。
出力されたファイルはこちら。

!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){
/* 中略 */
return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";r.r(t),console.log("Hello World!!")}]);

圧縮されていますね。ためしに実行してみましょう。

$ node ./dist/main.js

webpack01.gif

/src/component/test.jsで定義され/src/index.jsで実行される関数の結果がコンソールに出力されました。
規定では、/src/index.jsが起点ファイル、/dist/main.jsが出力ファイルとなっています。こちらでパスやファイル名を指定したい場合は、下記のとおりです。

$ npx webpack <起点ファイル> -o <出力ファイル>

Command Line Interface | webpack

webpack.config.js による設定

基本の設定

webpack.config.jsは、webpackへの指示内容をまとめた設定ファイルです。都度コマンドを打ち込むのは面倒なのでこの設定ファイルを利用します。

作業フォルダのルートに置きます。

sample/
  ├ src/
  │   ├ component/
  │   │    └ test.js
  │   └ index.js 
  ├ dist/
  └ webpack.config.js

最低限の設定内容は以下のとおりです。無名の連想配列に設定情報を書き連ね、エクスポートしています。

module.exports = {
  entry: './src/index.js', 
  output: {
    path: __dirname + '/dist',
    filename: 'sample.js'
  }
};
  • entryは、バンドルの起点となるファイル名です。(パスを含む)
  • outputは、出力情報です。
    • output.pathは、出力フォルダの名前です。(パスを含む)
    • output.filenameは、出力ファイル名です。

__dirname
__dirnameはNode.jsで予め用意されている変数で、記述されたファイルのパスが格納されています。webpack.config.jsC:/hoge/sample/webpack.config.jsにある場合、C:/hoge/sampleが返ってきます。
entryは、設定ファイルからの相対パスですが、outputは絶対パスを指定する必要があるため、__dirnameを利用しています。

このファイルでwebpackに指示している内容は、
IN:./src/index.js → OUT:./dist/sample.jsです。

では、バンドルしてみましょう。先ほどと同じコマンド$ npx webpackです。今度は、distフォルダに「sample.js」の名前で出力されます。内容は同じものです。
規定で設定ファイルの名前はwebpack.config.jsです。好みの名前にしたい場合は、コマンドで指定します。

$ npx webpack --config <ファイル名>

モードの設定

バンドルした際、コンソールにWARNING in configurationと、注意文言が表示されたことにお気づきでしょうか。
webpackでは、バンドる際のモードを下記のいずれかに指定する必要があります。

  • development: 開発モード,デバッグしやすい状態にバンドルする
  • production: 本番モード,なるべくファイルサイズを小さくする(初期値)

先ほどは初期値のproductionが適用されたので、ファイルは圧縮されたものが出力されています。
本番モードに設定した場合はこちら。mode:'production',が追加されています。実行結果は先ほどと同じですが、コンソールにエラーが出てこなくなります。

module.exports = {
  mode: 'production',
  entry: './src/index.js', 
  output: {
    path: __dirname + '/dist',
    filename: 'sample.js'
  }
};

開発モードに設定した場合はこちら。

module.exports = {
  mode: 'development',
  /* 略 */
};

開発モードで出力したファイルはこちら。なんかいろいろコメント入っています。ファイルサイズも本番モードと比較して約5倍になりました。

webpack02.gif

ソースマップ

単に開発モードに設定しただけでは、デバッグ時にブラウザから確認できるファイルはバンドル後のファイルです。その為、元ファイルと出力ファイルのコードの対応関係を示すソースマップの指定も併せて行います。ソースマップがあることで、デバッグ時にブラウザが元ファイルを参照してくれます。

module.exports = {
  mode: 'development',
  devtool: 'inline-source-map',
  /* 略 */
};

ChromeのDevToolにて、/src/component/test.jsが確認できます。

ここで指定している内容は、出力ファイル内にソースマップも含めるというものです。そのほかにもいろいろモードがあります。
Devtool | webpack

watchモード

ファイルを編集するたびにコマンドを打ち込んでバンドルするのは面倒です。Watchモードを利用すると、ファイルを編集するたびに自動で実行されるようにします。webpackコマンドを実行する際に、--watchオプションをつけます。

$ webpack --watch

jsファイルに何か適当な変更を加えてみると、速やかにバンドルが実行されます。2回目以降は差分更新なので、毎回コマンド実行した場合よりも高速です。なお、-wに省略することも出来ます。あらかじめwebpack.config.jsで設定する場合はこちら。

module.exports = {
  watch: true,
  /* 略 */
};

Watchモードにするだけならこれで十分なのですが、例えばnode_modulesフォルダのファイル群など、編集する予定が無いファイルもウォッチしてしまっては効率が悪いです。その為、webpack.config.jsに対象外のフォルダやファイルを追記します。

module.exports = {
  watch: true,
  watchOptions: {
    ignored: /node_modules/
  },
  /* 略 */
};

watchOptions.ignoredに、文字列(ワイルドカード可)・正規表現で指定します。複数ある場合は、['files/**/*.js', 'node_modules']というように配列で指定できます。
そのほかにも、バンドル開始を指定秒数遅延させて、複数のファイルへの編集をまとめて1回で対応するなどのオプションがあります。
Watch and WatchOptions | webpack

Watchモードの終了
Watchモードを終了させるには、開始させたコマンドラインにてCtrl+Cを実行してプロセスを終了させます。

npmにコマンドを登録する

npmには、コマンドのショートカットを登録する機能があるので、それを利用してみたいと思います。
package.jsonに以下の項目を追記します。

{
  "scripts": {
    "build": "webpack"
  },
  (略)
}

scriptという項目を設け、そこに任意のショートカット名と実行したいコマンドを入力します。慣例的にbuildと名づけられることが多いそうです。
(公式サイトのコンセプトの説明でbundleを使っていたのでずっとバンドルバンドル言っていますが、「ビルド」もよく使われています。どちらを使うべきかは正直よく分かりません。)
そして、以下のコマンドを実行すると、これまでと同様の結果が出力されます。

$ npm run build

npx webpackより文字数多いじゃないかと思われるでしょう。確かに利用するコマンドがそれだけならばさほどメリットは無いかもしれません。例えば、本番用と開発用でそれぞれ異なる設定ファイルを用意して使い分けたい場合などには便利です。

{
  "scripts": {
    "start": "webpack -w --config webpack.dev.config",
    "build": "webpack --config webpack.pro.config"
  },
  (略)
}

scripts.startはショートカット可能なキー名として予め用意されていて、$ npm start(runを省略)でも呼び出せます。

webpackはオプションが多岐にわたるのでややこしいのですが、とりあえず上記の内容で最低限のことは出来ると思います。

参考情報

Node.jsでimport/exportを試してみた。

koedamon
WEBフロント
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