フロントエンドを簡単に作成しようとフレームワーク無しで簡単なサイト構築をしていたら、
js のバンドルで詰まったのでメモ。
目次
何で詰まったか
例えば以下のようにボタンに onclick に関数を割り当てた html ファイルを作成したとする。
index.html1 2 3 4 5 6 7 8 9 10 11 12
| <!DOCTYPE html> <html> <head> <script type="text/javascript" src="app.js"></script> </head> <body> <button id="test" onclick="test()">TEST</button> </body> </html>
|
この時onclick
に割り当てたtest()
が src.js に定義されている
src.js1 2 3 4 5 6 7 8 9
| "use strict";
const Lib = require("./lib.js"); let lib = new Lib();
function test() { console.log(`test`); lib.func(); }
|
このsrc.js
は、Webpack + babel で app.js にバンドルする。
実行
動かない!
コンソールを見たら
「Uncaught ReferenceError: test is not defined」
えっ、なんで?!!
というわけで調べた
webpack + babel でバンドルされた app.js の中を見ると、確かにtest()
の定義自体はある。
しかし、test()の定義が、即時関数に包まれて evel()で実行される状態に
app.js(webpack + babel)1 2 3 4 5 6 7 8 9
| (function (modules) { eval( '\n\nvar Lib = __webpack_require__(/*! ./lib.js */ "./src/lib.js");\n\nvar lib = new Lib();\n\nfunction test() {\n console.log("test");\n lib.func();\n}\n\n//# sourceURL=webpack:///./src/index.js?' );
});
|
Browserify でバンドルした app.js だと次のようになる。
こっちも即時関数に包まれる
app.js(Browserify)1 2 3 4 5 6 7 8 9 10
| (function () {
function test() { console.log(`test`) lib.func() } );
|
この点について説明されているサイトが有った。
ブックマークし忘れて、見つけられなくなってしまった。
この状態だと、グローバル空間に関数の定義が無いので html 側で、関数の割り当てしても見つからないそうだ。
グローバル空間に関数が露出しない(この表現でいいのか?)らしい。
実際、html で src.js を参照させて実行すると関数自体は見つかる。(もちろんそのままではエラーだけど)
こういう場合以下のように変更する必要があった。
index.html1 2 3 4 5 6 7 8 9 10 11 12
| <!DOCTYPE html> <html> <head> </head> <body> <button id="test">TEST</button> <script type="text/javascript" src="app.js"></script> </body> </html>
|
src.js1 2 3 4 5 6 7 8 9 10 11
| "use strict";
const Lib = require("./lib.js"); let lib = new Lib();
function test() { console.log(`test`); lib.func(); }
document.getElementById("test").onclick = test;
|
javascript の処理として、イベント割り当てをすればうまくいった。
この場合は、変換後に即時関数に包まれていても大丈夫。
それでも html 側で関数割り当てをしようと思ったら
バンドルをやめる方法しか見つからなかった。
もう一件方法を見つけたのでそれは後述。
今回やりたかったのは、class とかアロー関数とかの新しい構文の利用だったから、 babel だけ使うことにした。
バンドルをやめたので、別ファイルに書いてインポートしていたクラス定義も src.js に書くことになった。
src.js は以下のように書き変えた。
src.js1 2 3 4 5 6 7 8 9 10 11 12 13 14
| "use strict";
class Lib { constructor() {} func() { console.log("Lib.js"); } } let lib = new Lib();
function test() { console.log(`test`); lib.func(); }
|
これが、babel で変換すると次のようになった。
app.js1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| "use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
var Lib = (function () { function Lib() { _classCallCheck(this, Lib); }
_createClass(Lib, [ { key: "func", value: function func() { console.log("Lib.js"); }, }, ]);
return Lib; })();
var lib = new Lib();
function test() { console.log("test"); lib.func(); }
|
この babel で変換しただけの app.js を変更前の index.html で読み込むと、
ボタンを押して関数test()
が実行できる。
今回の件で js のバンドルについて自身の理解が足りていないことに気がつけた。
vue-cli での webpack 利用とか、使ってる気で使われているなと・・・。
反省
今回 webpack もしくは Browserify でのバンドルをやめるという方法しか取れなかったけど、
他に方法がないのが気になった。
2/10 追記 もう一つ方法を見つけた
LCL Engineers’ Blog - ES5 から Webpack 管理下の関数を呼び出す方法
に記載があった。
グローバル空間に関数登録をしてしまう方法になる。
以下のように作った関数 test()を、window.test = test
でグローバル空間に関連付けする。
src.js1 2 3 4 5 6 7 8 9 10 11
| "use strict";
const Lib = require("./lib.js"); let lib = new Lib();
function test() { console.log(`test`); lib.func(); }
window.test = test;
|
この src.js を webpack + babel で処理した app.js なら、
以下の html 側でイベントへの関数割り当てした場合も動作する。
index.html1 2 3 4 5 6 7 8 9 10 11 12
| <!DOCTYPE html> <html> <head> <script type="text/javascript" src="app.js"></script> </head> <body> <button id="test" onclick="test()">TEST</button> </body> </html>
|
LCL Engineers’ Blog - ES5 から Webpack 管理下の関数を呼び出す方法
にも
グローバルに露出させるのであまりよくないかもしれませんが
とあるけど変数・関数の名前がぶつかる危険性など考えると、
安全性が担保できないのだろうなと。
少なくとも test とか使ってそうな名前でやるのは避けたほうがよさそうだ。