Help us understand the problem. What is going on with this article?

ブラウザとnode.jsに両対応したライブラリを作りたいときのWebpackレシピ集

image.png

概要

  • Webpackを使ってNode.jsとブラウザに両対応したライブラリを作るときのレシピ集です

  • いろいろなレシピを見てWebpackによるライブラリ生成の挙動を理解することを目的としています

ブラウザにもNode.jsにも両対応したライブラリを作りたい

image.png

ブラウザとNode.jsに両対応したいとおもったとき、
両方同時に対応した1つのバンドルjsを作る【統合型】と、
ブラウザ用、Node.js用と別々にライブラリを出し分ける【出し分け型】の2パターンある。

本稿では主に【統合型】について説明する

ライブラリのビルドに関するWebpack設定項目

レシピ集に行く前に、
まず、ライブラリのビルドに関する設定項目をざっくりみていく。

webpack.config.js

以下のような典型的なwebpack.config.jsの中でoutput以下の項目でライブラリ生成のためのパラメータを決める。

webpack.config.js
module.exports = {
    entry: {
        'mylib': [`./src/family.js`],
    },
    output: {
        path: path.join(__dirname, '/'),
        publicPath: '/',
        filename: `[name].min.js`,
        library: '',
        libraryExport: '',
        libraryTarget: 'umd',
        globalObject: 'this',
    },
    module: {
        rules: [{
            test: /\.js$/,
            exclude: /(node_modules|bower_components)/,
            use: [{loader: 'babel-loader'}]
        }]
    },
    devtool: 'inline-source-map',
};

ライブラリ生成のためのパラメータは以下の部分

    entry: {
        'mylib': [`./src/family.js`],
    },
    output: {
        filename: `[name].min.js`,
        library: '',
        libraryExport: '',
        libraryTarget: 'umd',
        globalObject: 'this',
    },

entryには、エントリーポイントになるjsファイルを指定する。ライブラリを作る時には、クラスや関数がexportされているjsファイルを指定する。

output以下はライブラリ生成にかかわるプロパティを設定する

設定 意味 設定値(例)
output.filename 生成されるバンドルのファイル名。
entryにキー名が指定されている。
それが[name]に代入される。よって、上の例では、family.min.jsというバンドルが生成される。
output.libraryTarget ライブラリの公開方法。ブラウザ・Node.js両対応なら'umd'を指定する。 'umd' 'var' 'commonjs' など
output.library ライブラリ名を指定する。
配列にすると a.b のような名前空間のようにできる。
また、モジュール形式ごとにライブラリ名を変えることもできる。
'mylib' ['mylib','util']など
output.libraryExport 明示的にエクスポートするモジュールを指定できる。
default指定されたモジュールをトップレベルにしたければ'default'を指定する
'default'など
output.globalObject rootに設定する値。libraryTargetが'umd'の場合ライブラリ実行時の即時関数の第一引数に指定される 'this'など

レシピ集

レシピ集の目次

1.クラス1つをライブラリにする

1-1.export defaultなクラスを1つ公開する

export defaultなクラスを公開する方法

ライブラリ側(機能を提供する側)のソースコードとWebpackによるビルド設定利用側(ライブラリの機能を使う側)に分けてコードをみていく。

【ビルド設定】

ライブラリ関連プロパティを以下のようにする

family.jsというのは、これから公開したいクラスを含むjsファイル

webpack.config.js
entry: {
    'mylib': [`./src/family.js`],
},
output: {
    filename: `[name].min.js`,
    library: '',
    libraryExport: '',
    libraryTarget: 'umd',
    globalObject: 'this',
},

webpack.config.jsのフルソースコードを見る

【ライブラリ側ソースコード】

family.jsには、TomというsayHelloというメソッドがあるだけのシンプルなクラスがある。
このクラス1つをライブラリとして外部公開する。

family.js
export default class Tom {
    sayHello() {
        return 'Hi, I am Tom.'
    }
}

【利用側ソースコード】

ブラウザから利用する場合

<script src="./mylib.min.js"></script>
<script>
    const Tom = window.default;
    const tom = new Tom();
    console.log(tom.sayHello());
</script>

デモはこちら

Node.jsから利用する場合

Node.jsから利用する場合①
const Lib = require('./mylib.min.js');
const Tom = Lib.default;
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.

または、こう書いても良い

Node.jsから利用する場合②
const Tom = require('./mylib.min.js').default;
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.

ES6import文から利用する場合

ES6のimport文から利用する①
import * as Lib from './mylib.min.js';
const Tom = Lib.default;
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.

または、こう書いても良い

ES6のimport文から利用する②
import {default as Tom} from './mylib.min.js';
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.

これでもOK

ES6のimport文から利用する③
import Tom from './mylib.min.js';//Pick default
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.

●このレシピのポイント

ポイント1:globalObject: 'this'の意味は何か。

Webpackして実際に生成されたmylib.min.jsは以下のようになっている

minifyしないで表示
(function webpackUniversalModuleDefinition(root, factory) {
    if(typeof exports === 'object' && typeof module === 'object')
        module.exports = factory();
    else if(typeof define === 'function' && define.amd)
        define([], factory);
    else {
        var a = factory();
        for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
    }
})(this, function() {});

mylib.min.js(developビルド版)のフルソースコードを見る

これは引数に(this,function())をとっている即時関数だが、このthisglobalObject: 'this'を指定した結果として設定されている。もしglobalObject:の指定を省略すると、この即時関数の引数は(window,function())となる。つまりwindowが自動的に入るため、ブラウザでなら動作するがwindowオブジェクトの無いNode.jsで動作させるとReferenceError: window is not definedとなりNode.jsで動作しなくなってしまう。ゆえに、両対応したい場合はglobalObject: 'this'を入れる。

ポイント2:defaultというキー以下にクラスがぶるさがる

Node.jsで使うときはrequire('./mylib.min.js').default、ブラウザで使うときはwindow.default(=window["default"])のようにdefaultというキー名以下に公開したいクラスがぶるさがる様になる

<目次に戻る>

1-2.export defaultなクラスをライブラリ名(≒名前空間)つきで1つ公開する

ライブラリ名(≒名前空間)を指定するには、webpack.config.jsにoutput.library:'MyLibrary'のように指定する。これで、ライブラリにMyLibraryというライブラリ名を設定できる。

【ビルド設定】

ライブラリ関連プロパティを以下のようにする

webpack.config.js
entry: {
    'mylib': [`./src/family.js`],
},
output: {
    filename: `[name].min.js`,
    library: 'MyLibrary',
    libraryExport: '',
    libraryTarget: 'umd',
    globalObject: 'this',
},

webpack.config.jsのフルソースコードを見る

【ライブラリ側ソースコード】

family.js
export default class Tom {
    sayHello() {
        return 'Hi, I am Tom.'
    }
}

【利用側ソースコード】

ブラウザから利用する場合

以下のように、Tomクラスは MyLibrary.default として公開される。

<script src="./mylib.min.js"></script>
<script>
    const Tom = MyLibrary.default;
    const tom = new Tom();
    console.log(tom.sayHello());
</script>

デモはこちら

Node.jsから利用する場合

Node.js用の場合(CommonJS2)はライブラリ名は無視されるので注意。
よってoutput.library:'MyLibrary'を指定しなかった場合と使い方は同じ。

const Lib = require('./mylib.min.js');
const Tom = Lib.default;
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.

ES6import文から利用する場合

ES6のimport文から利用する①
import * as Lib from './mylib.min.js';
const Tom = Lib.default;
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.

<目次に戻る>

1-3.export defaultなクラスを1つ、ライブラリ名つきだけどdefaultプロパティを使わず公開する

以下のように"default"を使わなくてもクラスにアクセスできるようにする。

const Tom = MyLibrary.default; 

【ビルド設定】
output.libraryExport: 'default'にする

webpack.config.js
entry: {
    'mylib': [`./src/family.js`],
},
output: {
    filename: `[name].min.js`,
    library: 'MyLibrary',
    libraryExport: 'default',
    libraryTarget: 'umd',
    globalObject: 'this',
},

webpack.config.jsのフルソースコードを見る

【ライブラリ側ソースコード】

family.js
export default class Tom {
    sayHello() {
        return 'Hi, I am Tom.'
    }
}

【利用側ソースコード】

ブラウザから利用する場合

この設定でライブラリをビルドすると、MyLibrary.defaultではなく、MyLibrary自体がTomクラスの参照を示すようになる。

<script src="./mylib.min.js"></script>
<script>
    const Tom = MyLibrary;
    const tom = new Tom();
    console.log(tom.sayHello());
</script>

デモはこちら

Node.jsから利用する場合

Node.js用の場合(CommonJS2)はライブラリ名は無視される

const Tom = require('./mylib.min.js');
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.

ES6import文から利用する場合

import Tom  from './mylib.min.js';
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.

<目次に戻る>

1-4.export defaultなクラスを1つ、ライブラリ名=クラス名にして公開する

【ビルド設定】

  • output.libraryExport: 'default'にする
  • ライブラリ名をクラス名(Tom)と同一にする→output.library: 'Tom'
webpack.config.js
entry: {
    'mylib': [`./src/family.js`],
},
output: {
    filename: `[name].min.js`,
    library: 'Tom',
    libraryExport: 'default',
    libraryTarget: 'umd',
    globalObject: 'this',
},

webpack.config.jsのフルソースコードを見る

【ライブラリ側ソースコード】

family.js
export default class Tom {
    sayHello() {
        return 'Hi, I am Tom.'
    }
}

【利用側ソースコード】

ブラウザから利用する場合

<script src="./mylib.min.js"></script>
<script>
    const tom = new Tom();
    console.log(tom.sayHello());
</script>

デモはこちら

Node.jsから利用する場合

const Tom = require('./mylib.min.js');
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.

ES6import文から利用する場合

import Tom from './mylib.min.js';
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.

●このレシピのポイント

ブラウザからもNode.jsからも、Tomクラスだけを意識すればOKなので、使う側からみると違和感が無い。

<目次に戻る>

1-5.export defaultなクラスを1つ、再エクスポート(reexport)をつかって公開する

re-export(再エクスポート)を使って公開する。
再エクスポートとは、あるモジュールを別のモジュールの中で再度exportすること。

【ビルド設定】

entryindex.jsする

webpack.config.js
entry: {
    'mylib': [`./src/index.js`],
},
output: {
    filename: `[name].min.js`,
    library: '',
    libraryExport: '',
    libraryTarget: 'umd',
    globalObject: 'this',
},

webpack.config.jsのフルソースコードを見る

【ライブラリ側ソースコード】

index.jsを作り、family.jsにあるTomクラスを再エクスポート(※)する。

index.js
export {default as Tom} from './family.js';

(※)再エクスポートの際に、{default as Tom}とすることで、実際には、Tomを「export」しており、この時点で「default export」では無くなる。

family.js
export default class Tom {
    sayHello() {
        return 'Hi, I am Tom.'
    }
}

【利用側ソースコード】

ブラウザから利用する場合

<script src="./mylib.min.js"></script>
<script>
    const tom = new Tom();
    console.log(tom.sayHello());
</script>

デモはこちら

Node.jsから利用する場合

この場合、Tomクラスを取得するために{ }で囲った分割代入 (Destructuring assignment) 構文を使っている。

const {Tom} = require('./mylib.min.js');
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.

ES6import文から利用する場合

import {Tom} from './mylib.min.js';
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.

2.複数のクラスを含むライブラリにする

2-1.複数のクラスを公開する

ライブラリとして複数のクラス(クラスじゃなくても関数でも変数でも同様)を外部から使えるようにしたい場合をみていく。

【ライブラリ側ソースコード】

以下はfamily.jsの中に、2つのクラスTomJackが含まれている。

family.js
export class Tom {
    sayHello() {
        return 'Hi, I am Tom.'
    }
}
export class Jack {
    sayHello() {
        return 'Hi, I am Jack.'
    }
}

【ビルド設定】

webpack.config.js
entry: {
    'mylib': [`./src/family.js`],
},
output: {
    filename: `[name].min.js`,
    library: '',
    libraryExport: '',
    libraryTarget: 'umd',
    globalObject: 'this',
},

webpack.config.jsのフルソースコードを見る

【利用側ソースコード】

ブラウザから利用する場合

<script src="./mylib.min.js"></script>
<script>
    const tom = new Tom();//means window["Tom"]
    const jack = new Jack();//means window["Jack"]

    console.log(tom.sayHello());//-> Hi, I am Tom.
    console.log(jack.sayHello());//-> Hi, I am Jack.

</script>

デモはこちら

Node.jsから利用する場合

const {Tom, Jack} = require('./mylib.min.js');

const tom = new Tom();
const jack = new Jack();

console.log(tom.sayHello());//-> Hi, I am Tom.
console.log(jack.sayHello());//-> Hi, I am Jack.

または、こう書いても良い

const Lib = require('./mylib.min.js');

const Tom = Lib.Tom;
const Jack = Lib.Jack;

const tom = new Tom();
const jack = new Jack();

console.log(tom.sayHello());//-> Hi, I am Tom.
console.log(jack.sayHello());//-> Hi, I am Jack.

ES6import文から利用する場合

import * as Lib from './mylib.min.js';

const Tom = Lib.Tom;
const Jack = Lib.Jack;

const tom = new Tom();
const jack = new Jack();

console.log(tom.sayHello());//-> Hi, I am Tom.
console.log(jack.sayHello());//-> Hi, I am Jack.

または、こう書いても良い

{ }で囲った分割代入 (Destructuring assignment) 構文を使って以下のようにできる。

import {Tom, Jack} from './mylib.min.js';

const tom = new Tom();
const jack = new Jack();

console.log(tom.sayHello());//-> Hi, I am Tom.
console.log(jack.sayHello());//-> Hi, I am Jack.

<目次に戻る>

2-2.ライブラリ名つきで複数のクラスを公開する

以下に示すとおりlibrary: 'GreatFamily'のようにライブラリ名(≒名前空間)をつける

【ビルド設定】

webpack.config.js
entry: {
    'mylib': [`./src/family.js`],
},
output: {
    filename: `[name].min.js`,
    library: 'GreatFamily',
    libraryExport: '',
    libraryTarget: 'umd',
    globalObject: 'this',
},

webpack.config.jsのフルソースコードを見る

【ライブラリ側ソースコード】

family.js
export class Tom {
    sayHello() {
        return 'Hi, I am Tom.'
    }
}
export class Jack {
    sayHello() {
        return 'Hi, I am Jack.'
    }
}

【利用側ソースコード】

ブラウザから利用する場合

ブラウザで動作させる場合はwindow["GreatFamily"]の下に各クラスがぶるさがる。

<script src="./mylib.min.js"></script>
<script>
    const tom = new GreatFamily.Tom();
    const jack = new GreatFamily.Jack();

    console.log(tom.sayHello());
    console.log(jack.sayHello());

</script>

デモはこちら

Node.jsから利用する場合

Node.js用の場合(CommonJS2)はライブラリ名は無視される
よってoutput.library:'GreatFamily'を指定しなかった場合と使い方は同じ。

const Lib = require('./mylib.min.js');

const Tom = Lib.Tom;
const Jack = Lib.Jack;

const tom = new Tom();
const jack = new Jack();

console.log(tom.sayHello());//-> Hi, I am Tom.
console.log(jack.sayHello());//-> Hi, I am Jack.

 

ES6import文から利用する場合

import * as Lib from './mylib.min.js';

const Tom = Lib.Tom;
const Jack = Lib.Jack;

const tom = new Tom();
const jack = new Jack();

console.log(tom.sayHello());//-> Hi, I am Tom.
console.log(jack.sayHello());//-> Hi, I am Jack.

<目次に戻る>

 

2-3.export defaultなクラスを含む複数のクラスを公開する

以下のようにexport defaultなクラスを含む場合

【ライブラリ側ソースコード】

family.js
export default class Tom {
    sayHello() {
        return 'Hi, I am Tom.'
    }
}
export class Jack {
    sayHello() {
        return 'Hi, I am Jack.'
    }
}

【ビルド設定】

webpack.config.js
entry: {
    'mylib': [`./src/family.js`],
},
output: {
    filename: `[name].min.js`,
    library: '',
    libraryExport: '',
    libraryTarget: 'umd',
    globalObject: 'this',
},

webpack.config.jsのフルソースコードを見る

【利用側ソースコード】

ブラウザから利用する場合

<script src="./mylib.min.js"></script>
<script>

    const Tom = window.default;//means window["default"]

    const tom = new Tom();
    const jack = new Jack();//means window["Jack"]

    console.log(tom.sayHello());
    console.log(jack.sayHello());

</script>

デモはこちら

Node.jsから利用する場合

const Lib = require('./mylib.min.js');

const Tom = Lib.default;
const Jack = Lib.Jack;

const tom = new Tom();
const jack = new Jack();

console.log(tom.sayHello());//-> Hi, I am Tom.
console.log(jack.sayHello());//-> Hi, I am Jack.

または、こう書いても良い

const Tom = require('./mylib.min.js').default;
const {Jack} = require('./mylib.min.js');

const tom = new Tom();
const jack = new Jack();

console.log(tom.sayHello());//-> Hi, I am Tom.
console.log(jack.sayHello());//-> Hi, I am Jack.

ES6import文から利用する場合

import * as Lib from './mylib.min.js';

const Tom=Lib.default;
const Jack=Lib.Jack;

const tom = new Tom();
const jack = new Jack();

console.log(tom.sayHello());//-> Hi, I am Tom.
console.log(jack.sayHello());//-> Hi, I am Jack.

または、こう書いても良い

import {default as Tom, Jack} from './mylib.min.js';

const tom = new Tom();
const jack = new Jack();

console.log(tom.sayHello());//-> Hi, I am Tom.
console.log(jack.sayHello());//-> Hi, I am Jack.

これでもOK

import Tom2 from './mylib.min.js';
import {Jack as Jack2} from './mylib.min.js';

const tom2 = new Tom2();
const jack2 = new Jack2();

console.log(tom2.sayHello());//-> Hi, I am Tom.
console.log(jack2.sayHello());//-> Hi, I am Jack.

<目次に戻る>

2-4.複数のクラスの中でexport defaultなクラスだけを公開する

(こういうことが必要になることはあまり無さそうだが、挙動としてみておく)

【ビルド設定】

webpack.config.js
entry: {
    'mylib': [`./src/family.js`],
},
output: {
    filename: `[name].min.js`,
    library: 'Tom',
    libraryExport: 'default',
    libraryTarget: 'umd',
    globalObject: 'this'
},

webpack.config.jsのフルソースコードを見る

【ライブラリ側ソースコード】

family.js
export default class Tom {
    sayHello() {
        return 'Hi, I am Tom.'
    }
}
export class Jack {
    sayHello() {
        return 'Hi, I am Jack.'
    }
}

【利用側ソースコード】

ブラウザから利用する場合

Jackクラスは使えなくなる

<script src="./mylib.min.js"></script>
<script>
    const tom = new Tom();
    console.log(tom.sayHello());
</script>

デモはこちら

Node.jsから利用する場合

Jackクラスは使えなくなる

const Tom = require('./mylib.min.js');
const tom=new Tom();
console.log(tom.sayHello());//->Hi, I am Tom.

ES6import文から利用する場合

import Tom  from './mylib.min.js';
const tom=new Tom();
console.log(tom.sayHello());//->Hi, I am Tom.

●このレシピのポイント

ライブラリとして外部から利用できないにもかかわらず、生成されたバンドルにはJackクラスも混入される。もしJackクラスが誰からも利用されないのであれば、純粋にムダなコードがバンドルに入ることになる。

<目次に戻る>

3.その他のオプション

3-1.モジュール形式ごとに名前空間を分ける

libraryTarget: 'umd'のとき、以下のようにするとRoot,AMD,CommonJS,にそれぞれ別のライブラリ名(名前空間)をつけることができる。
ただしCommonJS2(Node.js用)では無視される。

【ビルド設定】

webpack.config.js
entry: {
    'mylib': [`./src/family.js`],
},
output: {
    filename: `[name].min.js`,
    library: {
         root: 'GreatFamily',
         amd: 'great-family',
         commonjs: 'common-great-family',
    },
     libraryExport: '',
     libraryTarget: 'umd',
     globalObject: 'this',
     umdNamedDefine: true,
}

webpack.config.jsのフルソースコードを見る

    library: {
         root: 'GreatFamily',
         amd: 'great-family',
         commonjs: 'common-great-family',
    },

の部分でそれぞれのモジュール形式ごとの名前をつけている。

また
umdNamedDefine:trueにすると、AMDにもライブラリ名(名前空間)が付与される

この設定でビルドすると、バンドルjsは以下のようになり、各モジュール形式ごとのライブラリ名が反映されていることがわかる。

family.min.js
(function webpackUniversalModuleDefinition(root, factory) {
    //for CommonJS2 environment
    if(typeof exports === 'object' && typeof module === 'object')
        module.exports = factory();
    //for AMD environment
    else if(typeof define === 'function' && define.amd)
        define("great-family", [], factory);
    //for CommonJS environment
    else if(typeof exports === 'object')
        exports["common-great-family"] = factory();
    //for Root
    else
        root["GreatFamily"] = factory();
})(this, function() {...}

mylib.min.js(developビルド版)のフルソースコードを見る

<目次に戻る>

3-2.モジュール形式ごとにコメントを入れる

auxiliaryCommentを書くとビルドするバンドルjsのそれぞれのモジュール形式の定義部分にコメントを入れられる

【ビルド設定】

webpack.config.js
entry: {
    'mylib': [`./src/family.js`],
},
output: {
    filename: `[name].min.js`,
    library: {
         root: 'GreatFamily',
         amd: 'great-family',
         commonjs: 'common-great-family',
    },
    libraryExport: '',
    libraryTarget: 'umd',
    globalObject: 'this',
    umdNamedDefine: true,
    auxiliaryComment: {
        root: 'Root用のコメントです',
        commonjs: 'CommonJS用のコメントです',
        commonjs2: 'CommonJS2用のコメントです',
        amd: 'AMD用のコメントです'
    }
}

webpack.config.jsのフルソースコードを見る

以下のようにバンドルjsにコメントが入る

family.min.js
(function webpackUniversalModuleDefinition(root, factory) {
    //CommonJS2用のコメントです
    if(typeof exports === 'object' && typeof module === 'object')
        module.exports = factory();
    //AMD用のコメントです
    else if(typeof define === 'function' && define.amd)
        define("great-family", [], factory);
    //CommonJS用のコメントです
    else if(typeof exports === 'object')
        exports["common-great-family"] = factory();
    //Root用のコメントです
    else
        root["GreatFamily"] = factory();
})(this, function() {...}

mylib.min.js(developビルド版)のフルソースコードを見る

3-3.ライブラリ名をピリオド区切りの("org.riversun.GereatFamily"のような)名前空間のようにする

例えば、"org.riversun.GreatFamily"というライブラリ名にするときは以下のlibrary: ['org', 'riversun', 'GreatFamily']のようにライブラリ名を配列にする。

【ビルド設定】

webpack.config.js
entry: {
    'mylib': [`./src/family.js`],
},
output: {
    filename: `[name].min.js`,
    library: ['org', 'riversun', 'GreatFamily'],
    libraryExport: '',
    libraryTarget: 'umd',
    globalObject: 'this',
    umdNamedDefine: true,
},

webpack.config.jsのフルソースコードを見る

【ライブラリ側ソースコード】

family.js
export class Tom {
    sayHello() {
        return 'Hi, I am Tom.'
    }
}
export class Jack {
    sayHello() {
        return 'Hi, I am Jack.'
    }
}

【利用側ソースコード】

ブラウザから利用する場合

<script src="./mylib.min.js"></script>
<script>
    const tom = new org.riversun.GreatFamily.Tom();
    const jack = new org.riversun.GreatFamily.Jack();
    console.log(tom.sayHello());
    console.log(jack.sayHello());
</script>

これまでと同様、Node.js(CommonJS2)ではパッケージ名は反映されない

<目次に戻る>

3-4.externalsを使って外部ライブラリを分離する

自作ライブラリに外部ライブラリへの依存性があるとき、外部ライブラリを自作ライブラリに一体化させて配布する方法と、外部ライブラリと自作ライブラリは分離して配布する方法がある。

image.png

ふつうに作ると一体化するので、ここでは分離してする方法「外部ライブラリ分離方式」(勝手に命名しました)をみていく。

ここでは、自作ライブラリTomクラスが外部ライブラリ@riversun/simple-date-formatを参照する例でみる。

**外部ライブラリをインストール

ライブラリ開発時に参照するために、使用する外部ライブラリをインストールする

npm install --save-dev @riversun/simple-date-format

【ビルド設定】

externalsを以下のようにwebpack.config.jsに追加する

webpack.config.js
entry: {
    'mylib': [`./src/family.js`],
},
output: {
    filename: `[name].min.js`,
    library: 'Tom',
    libraryExport: 'default',
    libraryTarget: 'umd',
    globalObject: 'this',

},
externals: {
    SDF: {
        commonjs: '@riversun/simple-date-format',
        commonjs2: '@riversun/simple-date-format',
        amd: '@riversun/simple-date-format',
        root: 'SimpleDateFormat'
    }
}

webpack.config.jsのフルソースコードを見る

以下の部分で"SDF"はライブラリ側ソースコードからライブラリを参照するためのプロパティ名。

externals: {
    SDF: {
        commonjs: '@riversun/simple-date-format',
        commonjs2: '@riversun/simple-date-format',
        amd: '@riversun/simple-date-format',
        root: 'SimpleDateFormat'
    }
}

SDF以下には下のように"ライブラリタイプ名:ライブラリ名"(npm instlalするときの)を書く。

commonjs: '@riversun/simple-date-format',
commonjs2: '@riversun/simple-date-format',
amd: '@riversun/simple-date-format',

commonjs,commonjs2,amdは、umd形式でライブラリを作るとき、それぞれCommonJS、CommonJS2、AMDにたいしてSDFで参照されるライブラリに対してどのライブラリを適用するかを指定する。

一番下の

root: 'SimpleDateFormat'

だが、これをやることで、ブラウザ上で自作ライブラリを使うときSDFで指定されたライブラリとして、SimpleDateFormat(window.["SimpleDateFormat"])を参照するようにビルドされる。

【生成されるバンドルjs】

上述したビルド設定でライブラリをビルドすると以下のようなバンドルが生成される

family.min.js(バンドル)
(function webpackUniversalModuleDefinition(root, factory) {
    //for CommonJS2
    if(typeof exports === 'object' && typeof module === 'object')
        module.exports = factory(require("@riversun/simple-date-format"));
    //for AMD
    else if(typeof define === 'function' && define.amd)
        define("Tom", ["@riversun/simple-date-format"], factory);
    //for CommonJS
    else if(typeof exports === 'object')
        exports["Tom"] = factory(require("@riversun/simple-date-format"));
    //for Root
    else
        root["Tom"] = factory(root["SimpleDateFormat"]);
})(this, function(__WEBPACK_EXTERNAL_MODULE_SDF__) {...})

mylib.min.js(developビルド版)のフルソースコードを見る

ここからもわかるように、それぞれのモジュール形式用にモジュールの読み込み方が定義される。

CommonJS,CommonJS2向けにはrequire("@riversun/simple-date-format")している、
AMD(RequireJS)向けには define()に配列でライブラリ名を指定している
ブラウザ向け(Root)には root["SimpleDateFormat"]している。(これは、つまりthis["SimpleDateFormat)ということであり、ブラウザ上で this=window なのでつまりwindow["SimpleDateFormat"])している。

この仕掛けにより、Webpackビルドでは外部ライブラリがバンドルに取り込まれなくなるので、外部ライブラリを分離することができる。

自作ライブラリ利用側で require("@riversun/simple-date-format") なり、window["SimpleDateFormat"]=SimpleDateFormatなりされていれば、自作ライブラリ内に外部ライブラリを取り込まずとも動作する。

それでは、自作ライブラリ側のソースコードをみていく。

【ライブラリ側ソースコード】

family.js
import SimpleDateFormat from "SDF";

export default class Tom {
    sayHello() {
        const date = new Date();
        const sdf = new SimpleDateFormat();
        return `Hi, I am Tom. Today is ${sdf.formatWith("EEE, MMM d, ''yy", date)}`;
    }
}

import SimpleDateFormat from "SDF";は本来import SimpleDateFormat from "@riversun/simple-date-format";とするところを、externalsで決めたプロパティ名としている。

次に、この外部ライブラリ分離方式で作った自作ライブラリを利用する場合についてみる。

【利用側ソースコード】

ブラウザから利用する場合

ブラウザから利用するときは、以下のように外部ライブラリをCDNから読み込んでいる。
先に外部ライブラリを読み込む。

<script src="https://cdn.jsdelivr.net/npm/@riversun/simple-date-format@1.1.0/dist/simple-date-format.js"></script>
<script src="./mylib.min.js"></script>
<script>
    const tom = new Tom();
    document.write(tom.sayHello());
</script>

デモはこちら

今回つかった拙作外部ライブラリも、本記事の1-4のお作法でビルドした。

1-4のお作法によりこの外部ライブラリのバンドルはSimpleDateFormatクラスをwindow["SimpleDateFormat"]で参照できるようにしているので、上述のように動作させることができる。

Node.jsから利用する場合

const Tom = require('./mylib.min.js');
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.

<目次に戻る>

まとめ

  • Webpack4をつかってライブラリつくるときの種々の設定方法と挙動についてまとめました
  • ハンズオンで実際に動かして挙動の違いをみるとWebpackのライブラリ生成機能について理解が深まるとおもいます
  • 長文お読みいただき、ありがとうございました。

サンプルコード実行方法

全ソースコードは https://github.com/riversun/making-library-with-webpack に置いてあります
ソースコードをつかって実際に試す場合には以下のようにします

ソースをクローンする

git clone https://github.com/riversun/making-library-with-webpack.git
cd making-library-with-webpack

チャプターごとのディレクトリでサンプルコードを実行する

チャプターごとのディレクトリを切って、その下をnpmプロジェクトにしてありますので、以下のようにディレクトリ内でnpm installおよびnpm startすれば、各チャプターの例を実行して確認できます。

cd part_1_1
npm install
npm start

実際のライブラリの公開

本記事1-4の方式を使って作った以下のOSSライブラリを公開しています。ソースコードもシンプルな構成のライブラリなので、これからライブラリを作る場合はご参考になれば幸いです。

https://www.npmjs.com/package/@riversun/simple-date-format

参考資料など

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした