JavaScriptでモジュールを作ろうとして詰まった話
タイトル通り!jsでモジュールをつくろーっと思って色々詰まったので備忘録.
作りたいモジュールはこんな感じ.簡易化として今回は,index.html
,main.js
,sub.js
で構成.
index.htmlでオブジェクトを作成(コンストラクタを呼び出すだけ).他処理は全部jsで行う.
requireが通らない
とりあえず以下のコードを書いてみた.
- index.html
<html> <head> <script type="text/javascript" src="./src/bundle.js"></script> </head> <body> <script> var obj = new MainObject("new object!"); console.log("html: ", obj); obj.callSub(); </script> </body> </html>
- main.js
var subFunction = require("./sub.js"); function MainObject(str) { this.string = str; console.log("main string: ", this.string); } MainObject.prototype.callSub = function () { subFunction(); } module.exports = MainObject;
- sub.js
function subFunction(){ console.log("sub call!"); } module.exports = subFunction;
この状態だとブラウザでは,main.js:1 Uncaught ReferenceError: require is not defined
という感じでエラーになります.
requireが使えないみたい.当然sub.jsに定義された関数は使えません.
なんでrequireが使えないのか?
requireはNodeのモジュールシステムで,モジュールをサーバサイドで使うための宣言らしい.なので,フロントでは使えない.
browserifyでビルドしてみる
browserifyって?
Nodeでrequireを使うのと同じように,requireを使ってコードをかけるようになるツール.
ビルドして依存関係を解決する感じです.
インストール
$ npm install -g browserify
でインストール
ビルド
$ browserify main.js -o bundle.js
で,main.js
でインポートされているモジュールを含めた,bundle.js
というファイルを生成します.
できたものはこんな感じ.
- bundle.js
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ var subFunction = require("./sub.js"); function MainObject(str) { this.string = str; console.log("main string: ", this.string); } MainObject.prototype.callSub = function () { subFunction(); } module.exports = MainObject; },{"./sub.js":2}],2:[function(require,module,exports){ function subFunction(){ console.log("sub call!"); } module.exports = subFunction; },{}]},{},[1]);
見覚えのあるコードも混ざってますね.
この状態で,index.html
からbundle.js
を呼び出してみます.
変更箇所だけ.
- index.html
<head> <!-- <script type="text/javascript" src="./src/main.js"></script> --> <script type="text/javascript" src="./src/bundle.js"></script> </head>
すると,
index.html:8 Uncaught ReferenceError: MainObject is not defined
っていうエラー.
MainObjectがないらしい.なんで?
ブラウザからモジュールが使えない理由
browserifyでビルドしても,フロントで使える仕様にはならないっぽい.
JSのモジュール定義って色々種類があるみたいなんですよね.AMDとか,CommonJSとか.初めて知った.
それらを総括しているのがUMDっていうらしい.
AMD(Asynchronous Module Definition)
モジュールを非同期でロードしてくれる.依存関係の解決をビルドするとかじゃなく,実行時に行う. フロントでモジュールをまとめて読み込むときに使えそう.
requireJSっていうフレームワークがあるので,実装する時は使おう...
require()
は使わない.CommonJS
主にサーバサイドを開発するための仕様.モジュールを,exportしてrequireで使用する.
browserifyでビルドするとこの仕様になるのかな.UMD(Universal Model Identifinition)
AMDとCommonJSの総括.
UMDのモジュールはクライアント,サーバの両方で利用可能.
standaloneオプションを使ってみる
browserifyでビルドする際,standaloneオプションを付加します.
standaloneオプションってなんぞ?
モジュールの名前を指定して,UMDのモジュールを作成するためのオプションです.
フロントでもサーバサイドでも使えるモジュールを作成してくれるってことか.
今回は,main.js
からMainObject
っていう名前でモジュールを書き出してみる.ややこしいネーミングした感じが否めない.
$ browserify main.js -s MainObject -o bundle.js
これで,作成されたbundle.js
はこんな感じ.
- bundle.js
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.MainObject = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ var subFunction = require("./sub.js"); function MainObject(str) { this.string = str; console.log("main string: ", this.string); } MainObject.prototype.callSub = function () { subFunction(); } module.exports = MainObject; },{"./sub.js":2}],2:[function(require,module,exports){ function subFunction(){ console.log("sub call!"); } module.exports = subFunction; },{}]},{},[1])(1) });
前のbundle.js
と似てるかもだけど,ちょこちょこ違いますね....
以上で,ちゃんと動いてくれます.
ブラウザとかのデベロッパーツールで確認してみると,コンソールがこんな感じになります.
main string: new object! html: MainObject {string: "new object!"} sub call!
htmlから,main,subのモジュールが使えてる.よしよし.いい感じですね.
おまけ:minify化を含めて自動化する
gulp + browserifyでビルドとminify化を一発でできるようにしてみました.
ちょっとmain.js
に追加している部分があります.
- main.js
MainObject.prototype.count = function () { const names = [ "Amy", "Basil", "Clara", "Desmond", "Ernest", "Fanny", "George", ] names.map(name => console.log(name.length)); }
gulpfileを作ります.大まかな流れは,ビルド -> トランスパイル -> minify化っていう感じ.
- gulpfile.js
var gulp = require('gulp'), uglify = require('gulp-uglify'), source = require('vinyl-source-stream'), buffer = require('vinyl-buffer'), browserify = require('browserify'), babel = require('gulp-babel') gulp.task("build", function() { return ( browserify("./src/main.js", {standalone:"MainObject"}) .bundle() .pipe(source("bundle.js")) .pipe(buffer()) .pipe(babel({presets: ['@babel/env']})) .pipe(uglify()) .pipe(gulp.dest("./src/")) ); });
なぜトランスパイルするのか?
ES6表記(アロー関数とかconstとか)を含んでいると,minify化する部分(uglify)でエラーがおこるっぽい.なので,トランスパイルする必要があります.うーん厄介. syntaxエラーを出されると少し落ち込む...
まとめ
js沼すぎ!!!モジュール作ってみようかなーと思い立ってから,動く形にするまで結構苦労した気がします.jsだけでフロントやるのもなかなか大変ですね.
向き合ったの初めてなんですけど,記述自体,アロー関数とか変数のスコープとか,なかなか曲者かな....
色々盛りすぎて底なし沼みたいになってませんこれ?
TypeScriptとかも気になってるんですけど,闇が深そう.マイペースに勉強していきます....
ここまで読んでいただき,ありがとうございます!