あくびのあくびしないブログ

エンジニアを目指す学生のブログ兼備忘録

JavaScriptでモジュールを作ろうとして詰まった話

タイトル通り!jsでモジュールをつくろーっと思って色々詰まったので備忘録.

作りたいモジュールはこんな感じ.簡易化として今回は,index.htmlmain.jssub.jsで構成.

f:id:yawn_yawn_yawn:20191014104244j:plain

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とかも気になってるんですけど,闇が深そう.マイペースに勉強していきます....

ここまで読んでいただき,ありがとうございます!