ストーリー
Webpack便利ですね。
複数のNodeモジュールをさくっとひとまとめにしてくれます。
各種ローダーを入れれば、ECMAScriptを変換したり、CSSも読み込んだりできます。
minifiedもできちゃいます。
どんなものなのか理解しないまま、日々使っていたのですが、ここに来てjQueryプラグインを使う必要が出てきました。
slick-carousel
です。
いつものように読み込んだところ、
Uncaught TypeError: t(...).slick is not a function
と、何やら不穏な動き。
重い腰を上げて、Webpackが何をしてくれるものなのかを理解することにしたのでした。
導入
シンプルな環境で試してみます。
mkdir webpack-exp
cd webpack-exp
npm init
npm install --save-dev webpack
これで、node_modules/.bin/webpack
コマンドが使えるようになりました。
設定ファイル
webpackの設定ファイルを作成します。
const path = require('path');
module.exports = {
entry: './src/app.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
空っぽのソースはどうなる?
まずは空っぽのソースを作ってwebpackしてみました。
/* I am app.js */
(function(modules) {
/*
*
* (省略) Webpack初期化コード
*
*/
})([
(function(module, exports) {
/* I am app.js */
})
]);
app.js
の内容が、module
とexports
を引数とする関数でラッピングされています。
読み込んだソース中でmodule
とexports
が使えるのはこういうわけでしたか。
ラッピングされた関数は、webpack初期化コードに渡されて順番に呼び出されています。
jQueryを読み込んでみる
試してみたかったjQueryを読み込んでみます。
npm install --save-dev jquery
ソースは次のようにしてみました。
import jQuery from 'jquery';
/* I am app.js */
(function(modules) {
/*
*
* (省略) Webpack初期化コード
*
*/
})([
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
var __WEBPACK_IMPORTED_MODULE_0_jquery__ =
__webpack_require__(1);
var __WEBPACK_IMPORTED_MODULE_0_jquery___default =
__webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_jquery__);
/* I am app.js */
}),
(function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;
/*
* (省略) jQuery本体のコード
*/
})
]);
import
に書いたjQuery
はどこにいっちゃいましたかね?
あと、ラッピング関数の引数が変わってますね。exports
がなくなっちゃいました。
jQueryを使ってみる
中身のあるソースにしてみました。
import jQuery from 'jquery';
/* I am app.js */
;(function($) {
$(function() {
console.log('Hello I am app.js');
});
})(jQuery);
(function(modules) {
/* (snip) */
})([
(function(module, __webpack_exports__, __webpack_require__) {
(snip)
var __WEBPACK_IMPORTED_MODULE_0_jquery___default =
__webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_jquery__);
/* I am app.js */
;(function($) {
$(function() {
console.log('Hello I am app.js');
});
})(__WEBPACK_IMPORTED_MODULE_0_jquery___default.a);
}),
(function(module, exports, __webpack_require__) {
(snip)
})
]);
元ソースにあったjQuery
が、いい感じに置き換えられています。
ブラウザで読み込めば、ちゃんと動きますよー。
import
とrequire
って違うのか?
ところで、外部モジュール読み込みに使うrequire
は、import
と違うのかどうか?
const jQuery = require('jquery');
/* I am app.js */
;(function($) {
$(function() {
console.log('Hello, I am app.js');
});
})(jQuery);
ラッピングする関数は、このようになりました。
function(module, exports, __webpack_require__) {
const jQuery = __webpack_require__(1);
/* I am app.js */
;(function($) {
$(function() {
console.log('Hello, I am app.js');
});
})(jQuery);
}
import
よりシンプルな中身になりました。
jQueryプラグインを使ってみる
npmで入るなにか簡単なプラグインということでjquery.marqueeを試してみます。
npm install --save-dev jquery.marquee
const jQuery = require('jquery');
require('jquery.marquee');
/* I am app.js */
;(function($) {
$(function() {
$('.marquee').marquee({
duration: 5000
});
});
})(jQuery);
非情なエラー
ブラウザで読み込んだところ、エラーになってしまいました。
bundle.js:10828 Uncaught ReferenceError: jQuery is not defined
at Object.<anonymous> (bundle.js:10828)
at __webpack_require__ (bundle.js:20)
at Object.<anonymous> (bundle.js:71)
at __webpack_require__ (bundle.js:20)
at bundle.js:63
at bundle.js:66
非情な原因
jquery.marqueeのソースを眺めてみると、window.jQuery
ではなく、jQuery
を参照しているのが原因のようです。むむむ。。。
温厚(?)な解決策
jQueryをglobalオブジェクトに入れてあげればいいようです。
global.jQuery = require('jquery');
ラッピング関数部分が、このように変わりました。
function(module, exports, __webpack_require__) {
(function(global) {
global.jQuery = __webpack_require__(2);
__webpack_require__(3);
/* I am app.js */
;(function($) {
$(function() {
$('.marquee').marquee({
duration: 5000
});
});
})(jQuery);
}.call(exports, __webpack_require__(1)))
}
__webpack_require__(1)
で返される関数の中でグローバルなオブジェクトを定義しています。
function(module, exports) {
var g;
// This works in non-strict mode
g = (function() {
return this;
})();
try {
// This works if eval is allowed (see CSP)
g = g || Function("return this")() || (1,eval)("this");
} catch(e) {
// This works if the window reference is available
if(typeof window === "object")
g = window;
}
// g can still be undefined, but nothing to do about it...
// We return undefined, instead of nothing here, so it's
// easier to handle this case. if(!global) { ...}
module.exports = g;
}
なるほどねー。
さて、Slick Carousel
カルーセル、、、(オヤジギャグ審議中)
インストールします。
npm install --save-dev slick-carousel
const jQuery = require('jquery');
require('slick-carousel');
/* I am app.js */
;(function($) {
$(function() {
$('.myslick').slick();
});
})(jQuery);
再現しないエラー
・・・なんか動いてしまいました。いや、動いていいんですけどね。
元エラーの原因
jQueryをscriptタグでもも読み込んでた。。。
まとめ
Webpackは読み込んだモジュールをいい塩梅に関数内に分離してくれて、必要があればそれを各モジュールに渡してくれることがわかりました。
さらに得られた知見としては、
- globalオブジェクトを恐れず使え
- jQueryを何度も読み込まない orz
というところでしょうか。
環境
最後に試した環境です。
- node 8.6.0
- npm 5.3.0
- webpack 3.8.1
- jquery 3.2.1
- slick-carousel 1.8.1