クモのようにコツコツと

フロントエンドエンジニア イイダリョウのブログ。略称「クモコツ」

webpackでJSのモジュールファイルをバンドルする

webpackの続きです。これまでフロント開発3銃士のうちGulpとBabelを触ってみたので、今回は最後の一つ、webpackを触ってみる。以前のJSモジュールの記事のjsファイルをバンドルしてみました。それでは行きましょう!

【目次】

前回記事
※参考:webpackを理解するために調べたこと(Webデザイナー→フロントエンドエンジニアへの脱皮) - クモのようにコツコツと

Web開発環境まとめ
qiita.com

JSモジュールのおさらい

以前のモジュールの記事ではHTMLファイルの中にimportのJSコードを書いていた。

exsportを書いたJSファイル(export.js)を…

export default function (massage) {
    alert(massage);
}

HTMLファイルで読み込むと…

<script type="module">
    import aisatsu from "./js/export.js";
    aisatsu("また会えたね、モジュール君♪");
</script>

アラートが表示された。

f:id:idr_zz:20191108061915j:plain

詳細はこちらの記事を参照

※参考:【JS】モジュール(import / export)でどんなことができるのか事始めてみた - クモのようにコツコツと

importモジュールのjsファイルを読み込むとエラーになる

この形を少し変えて、import.jsにimportモジュールを書く。

    import  aisatsu  from "./export.js";
    aisatsu("親子で再開、モジュール君♪");

このimport.jsをHTMLファイルで読み込むと…

<script type="module" src="js/import.js"></script>

シーン…返事がない。ただの屍のようだ。

デベロッパーツールを確認すると下記のエラーになっている。

Access to script at '(ファイルのパス)/js/import.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.

Failed to load resource: net::ERR_FAILED

Google先生で翻訳すると

オリジン「null」から「(ファイルのパス)/js/import.js」のスクリプトへのアクセスはCORSポリシーによってブロックされています。クロスオリジンリクエストは、プロトコルスキームでのみサポートされています:http、data、chrome、chrome-extension、https 。

リソースの読み込みに失敗しました:net :: ERR_FAILED

うむ、よくわかんない。。

モジュール読み込みはローカルだと動かない(異ドメイン間と認識)

エラーコードをもとに調べると、こちらのQAと同じ現象のようだった。

※参考:JavaScript - JavaScript 別ファイルからクラスをインポートしたい|teratail

原因がわかりました(中略)CORSエラーです。
ES Moduleは、どうやら、JavaScript上での直接ファイルアクセスとなるようですね。
つまりは、ローカルでは、同じディレクトリ上でも、そもそもドメインがないので、
異ドメイン間と認識されるのかと思われます。
そうなると、異ドメイン間のスクリプトからのファイル直でのアクセスは、ブラウザによってセキュリティが働くため、基本行えません。

サーバ上かMAMPなどのローカルサーバなら動きそうとのことで、MAMPにフォルダを投げてみると…

アラートが表示される! f:id:idr_zz:20191108061915j:plain

caniuseによればモジュールはIE以外のモダンブラウザは全て対応済みなのだが、ローカルでの挙動は注意が必要っぽい。

※参考:Can I use... Support tables for HTML5, CSS3, etc

ブラウザのセキュリティ対策による結果のため、これはまぁ仕方がないかなぁと思う。

webpackでモジュールをバンドルするんですよ!キミィ

そこでwebpackの登場と相成るわけですな!

webpackとは何ぞや?以前も調べた。

※参考:webpackを理解するために調べたこと(Webデザイナー→フロントエンドエンジニアへの脱皮) - クモのようにコツコツと

が、この時点では「webpack一つあればフロントエンド開発のいろんなことができるぞわ〜い」というテンションだった。

改めてwebpackの中心となる複数ファイルのバンドル機能に絞って調べてみる。

こちらの解説がとてもわかりやすかった!

※参考:最新版で学ぶwebpack 4入門 - JavaScriptのモジュールバンドラ - ICS MEDIA

webpackとはウェブコンテンツを構成するファイルをまとめてしまうツールです。一番多い使い方は、複数のJavaScriptを1つにまとめることでしょう。複数のJavaScriptをまとめるのは、いろんな利点があります。

そのメリットとは…

  • 転送の最適化
  • モジュールが使える
  • JSだけでなく、CSSや画像もバンドルできる
  • 包括的な開発環境が整う

こちらの概念図もわかりやすい!

f:id:idr_zz:20200113195655p:plain

CSSや画像のバンドルについては個人的にはまだあまり必要性を感じていないのだが、手始めとしてJSのモジュールファイルをバンドルしたい!

なお、webpackはGulp、Babelとともに「フロント開発3銃士」と呼ばれるツール。

※参考:gulpとbabelとwebpackというフロント開発3銃士 - Qiita

webpack以外はすでに触れているので本記事をもって3つ全てを体験したことになる!それぞれの位置付けの理解もより深まるはず!

webpackをインストール

先ほどのページを参考に進めてみる。

※参考:最新版で学ぶwebpack 4入門 - JavaScriptのモジュールバンドラ - ICS MEDIA

node.jsとnpmは入っている前提(下記のコマンドでバージョン番号が出る)

node -v
npm -v

webpackを入れるフォルダを作る。今回はwebpack_testとした。

cdコマンドでフォルダに移動。

cd (フォルダのパス)/webpack_test

package.jsonを作成する。

npm init -y

ここまではここ最近のフロントエンド環境の記事と共通する内容。

webpackwebpack-cliをインストールする。

npm i -D webpack webpack-cli

二つ繋げて書けば同時にインストールされる。

成功するとpackage.jsonに追記される

  "devDependencies": {
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10"
  }

モジュールファイルを作る

srcというディレクトリを作り、その中にindex.jssub.jsを作成。

.
├node_modules(中身は省略)
├src
│├index.js
│└sub.js
├package-lock.json
└package.json

sub.jsにはexportするモジュールを書く。

export default function (massage) {
    alert(massage);
}

index.jsにはinportするモジュールを書く。

import aisatsu from "./sub.js";
aisatsu("こんにちは、うぇぶぱっく。");

先ほどのモジュールをベースにしてファイル名を変更。

webpackを実行!

それではいよいよ、webpackを実行!下記のコマンド

npx webpack

お、distというフォルダがでけた!

.
├dist
│└main.js
├node_modules(中身は省略)
├src
│├index.js
│└sub.js
├package-lock.json
└package.json

中にはmain.jsという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){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";r.r(t);alert("こんにちは、うぇぶぱっく。")}]);

改行なしの長〜いコードが書き出されている!!

よくライブラリであるminify(最小化)ファイルみたいね。jquery.min.jsとかのアレね。

package.jsonを書き換えて

package.jsonのscriptキーにwebpackのビルドコマンドを追加する。

{
  "scripts": {
    "build": "webpack",
  },

buildキーの値をwebpackに。

これでnpmコマンドで実行ができるようになる。

npm run build

書き出されるコードは同じ。

npmコマンドについてはこちらも参照

※参考:Node.jsユーザーなら押さえておきたいnpm-scriptsのタスク実行方法まとめ - ICS MEDIA

webpack.config.jsファイルで詳細設定

webpackの詳細設定をするためにwebpack.config.jsファイルを作る。

.
├dist
│└main.js
├node_modules(中身は省略)
├src
│├index.js
│└sub.js
├package-lock.json
├package.json
└webpack.config.js

webpack.config.jsにエントリーポイントと出力設定を書く。

module.exports = {
    // エントリーポイント
    entry: `./src/index.js`,
  
    // 出力設定
    output: {
      // ディレクトリ
      path: `${__dirname}/dist`,
      // ファイル名
      filename: "main.js"
    }
  };

エントリーポイントとはバンドルするときにメインにするファイル。依存関係があった時に優先される。

先ほどのフォルダ名、ファイル名はwebpackであらかじめ設定されている名前だが、webpack.config.jsを作ることでこのフォルダ名、ファイル名を独自なものに変えることができる。

module.exports = {
    // エントリーポイント
    entry: `./src/index.js`,
  
    // 出力設定
    output: {
      // ディレクトリ
      path: `${__dirname}/test/js`,
      // ファイル名
      filename: "script.js"
    },

    //圧縮(production)モードを解除
    mode: "development"
  };
  • 出力先を/test/js/script.jsに変更
  • 出力モードmodedevelopmentに変更(圧縮しない)

ちなみに圧縮モードはproduction

圧縮せずにjsファイルをバンドル

コマンドを再度実行!

npm run build

フォルダとファイルが書き出された!

.
├dist
│└main.js
├node_modules(中身は省略)
├src
│├index.js
│└sub.js
├test
│├js
│ └script.js
├package-lock.json
├package.json
└webpack.config.js

script.jsを開いてみると…

/******/ (function(modules) { // webpackBootstrap
/******/    // The module cache
/******/    var installedModules = {};

/*(中略)*/

"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _sub_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./sub.js */ \"./src/sub.js\");\n\nObject(_sub_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"こんにちは、うぇぶぱっく。\");\n\n//# sourceURL=webpack:///./src/index.js?");

/***/ }),

/*(後略)*/

うわぁすごい。。想像しているより結構な長さのコードになった。「ソースマップ」と言って元のファイルからのバンドルの過程の情報も含まれているからこんなに膨大になるらしい。

※参考:ソースマップを使用する - 開発ツール | MDN

詳しくは読み解けないのだが、だいぶ下の方にあるeval()の引数に「こんにちは、うぇぶぱっく。」の文字が見受けられる。

eval() は文字列として表された JavaScript コードを式として評価する関数です。

※参考:eval() - JavaScript | MDN

そうなんだ。

index.htmlを作ってscript.jsをリンク

testフォルダの直下にindex.htmlを作る。

.
├dist
│└main.js
├node_modules(中身は省略)
├src
│├index.js
│└sub.js
├test
│├js
││ └script.js
│└inde.html
├package-lock.json
├package.json
└webpack.config.js

index.htmlscript.jsをリンクする。

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>はじめてのwebpack</title>
</head>

<body>
    <script src="js/script.js"></script>
</body>
</html>

index.htmlをブラウザで開いてみると…

f:id:idr_zz:20200113225757p:plain
やた!アラートが表示された!!

ちなみに先ほど変更したモードを圧縮モードに戻しても結果は同じだった。

module.exports = {
    //(中略)
    //圧縮モードに戻す
    mode: "production"
  };

おまけ:モジュールの記述ミスで起きたエラー

実は最初にインポート側(index.js)で関数名を付ける書式(ブロック{ }無し)を間違えてブロック有りで書いていてしまっていた。

    import { aisatsu } from "./export.js";
    aisatsu("親子で再開、モジュール君♪");

そしたらwebpackのバンドル時にターミナルで「aisaatsu」という関数名が見つからない、という警告が出た。

WARNING in ./src/index.js 2:0-7
"export 'aisatsu' was not found in './sub.js'

それに気が付かずにブラウザで開いたらデベロッパーツール でエラーになった。

Uncaught TypeError: (void 0) is not a function

index.jsのブロックを削除したら、エラーが出なくなった。

    import  aisatsu  from "./export.js";
    aisatsu("親子で再開、モジュール君♪");

もしかしたら似た現象が起きる方がいるかもしれないので備忘録までに記録。

最後に

ということでwebpackでjsファイルのバンドルを初めて行いました!これでGulp、Babel、webpackのフロント開発3銃士が勢揃いしました(この呼び名、気に入ってますw)

Vue.jsやReactなどでかなりの数のモジュールファイルが分かれそうに思うので、この機能を連携することになりそうです。しかし、CSSや画像ファイルは実際のフロントエンド開発現場ではJSファイルにバンドルしているのでしょうかね?

他のツールとの連携なども含め、引き続き調べて行きたく思います。それではまた!


Web開発環境まとめ
qiita.com