概要
-
Webpackを使ってNode.jsとブラウザに両対応したライブラリを作るときのレシピ集です
-
いろいろなレシピを見てWebpackによるライブラリ生成の挙動を理解することを目的としています
ブラウザにもNode.jsにも両対応したライブラリを作りたい
ブラウザとNode.jsに両対応したいとおもったとき、
両方同時に対応した1つのバンドルjsを作る【統合型】と、
ブラウザ用、Node.js用と別々にライブラリを出し分ける【出し分け型】の2パターンある。
本稿では主に**【統合型】**について説明する
ライブラリのビルドに関するWebpack設定項目
レシピ集に行く前に、
まず、ライブラリのビルドに関する設定項目をざっくりみていく。
webpack.config.js
以下のような典型的なwebpack.config.jsの中でoutput以下の項目でライブラリ生成のためのパラメータを決める。
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ファイル
entry: {
'mylib': [`./src/family.js`],
},
output: {
filename: `[name].min.js`,
library: '',
libraryExport: '',
libraryTarget: 'umd',
globalObject: 'this',
},
【ライブラリ側ソースコード】
family.jsには、TomというsayHelloというメソッドがあるだけのシンプルなクラスがある。
このクラス1つをライブラリとして外部公開する。
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から利用する場合
const Lib = require('./mylib.min.js');
const Tom = Lib.default;
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
または、こう書いても良い
const Tom = require('./mylib.min.js').default;
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
●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.
または、こう書いても良い
import {default as Tom} from './mylib.min.js';
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
これでもOK
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は以下のようになっている
(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())
をとっている即時関数だが、このthis
はglobalObject: '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というライブラリ名を設定できる。
【ビルド設定】
ライブラリ関連プロパティを以下のようにする
entry: {
'mylib': [`./src/family.js`],
},
output: {
filename: `[name].min.js`,
library: 'MyLibrary',
libraryExport: '',
libraryTarget: 'umd',
globalObject: 'this',
},
【ライブラリ側ソースコード】
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.
●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'
にする
entry: {
'mylib': [`./src/family.js`],
},
output: {
filename: `[name].min.js`,
library: 'MyLibrary',
libraryExport: 'default',
libraryTarget: 'umd',
globalObject: 'this',
},
【ライブラリ側ソースコード】
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.
●ES6のimport文から利用する場合
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'
entry: {
'mylib': [`./src/family.js`],
},
output: {
filename: `[name].min.js`,
library: 'Tom',
libraryExport: 'default',
libraryTarget: 'umd',
globalObject: 'this',
},
【ライブラリ側ソースコード】
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.
●ES6のimport文から利用する場合
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すること。
【ビルド設定】
entryをindex.jsにする
entry: {
'mylib': [`./src/index.js`],
},
output: {
filename: `[name].min.js`,
library: '',
libraryExport: '',
libraryTarget: 'umd',
globalObject: 'this',
},
【ライブラリ側ソースコード】
index.jsを作り、family.jsにあるTomクラスを再エクスポート(※)する。
export {default as Tom} from './family.js';
(※)再エクスポートの際に、**{default as Tom}**とすることで、実際には、Tomを「export」しており、この時点で「default export」では無くなる。
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.
●ES6のimport文から利用する場合
import {Tom} from './mylib.min.js';
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
2.複数のクラスを含むライブラリにする
2-1.複数のクラスを公開する
ライブラリとして複数のクラス(クラスじゃなくても関数でも変数でも同様)を外部から使えるようにしたい場合をみていく。
【ライブラリ側ソースコード】
以下はfamily.jsの中に、2つのクラスTomとJackが含まれている。
export class Tom {
sayHello() {
return 'Hi, I am Tom.'
}
}
export class Jack {
sayHello() {
return 'Hi, I am Jack.'
}
}
【ビルド設定】
entry: {
'mylib': [`./src/family.js`],
},
output: {
filename: `[name].min.js`,
library: '',
libraryExport: '',
libraryTarget: 'umd',
globalObject: 'this',
},
【利用側ソースコード】
●ブラウザから利用する場合
<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.
●ES6のimport文から利用する場合
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'
のようにライブラリ名(≒名前空間)をつける
【ビルド設定】
entry: {
'mylib': [`./src/family.js`],
},
output: {
filename: `[name].min.js`,
library: 'GreatFamily',
libraryExport: '',
libraryTarget: 'umd',
globalObject: 'this',
},
【ライブラリ側ソースコード】
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.
●ES6のimport文から利用する場合
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なクラスを含む場合
【ライブラリ側ソースコード】
export default class Tom {
sayHello() {
return 'Hi, I am Tom.'
}
}
export class Jack {
sayHello() {
return 'Hi, I am Jack.'
}
}
【ビルド設定】
entry: {
'mylib': [`./src/family.js`],
},
output: {
filename: `[name].min.js`,
library: '',
libraryExport: '',
libraryTarget: 'umd',
globalObject: 'this',
},
【利用側ソースコード】
●ブラウザから利用する場合
<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.
●ES6のimport文から利用する場合
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なクラスだけ**を公開する
(こういうことが必要になることはあまり無さそうだが、挙動としてみておく)
【ビルド設定】
entry: {
'mylib': [`./src/family.js`],
},
output: {
filename: `[name].min.js`,
library: 'Tom',
libraryExport: 'default',
libraryTarget: 'umd',
globalObject: 'this'
},
【ライブラリ側ソースコード】
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.
●ES6のimport文から利用する場合
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用)では無視される。
【ビルド設定】
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,
}
library: {
root: 'GreatFamily',
amd: 'great-family',
commonjs: 'common-great-family',
},
の部分でそれぞれのモジュール形式ごとの名前をつけている。
また
umdNamedDefine:trueにすると、AMDにもライブラリ名(名前空間)が付与される
この設定でビルドすると、バンドル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のそれぞれのモジュール形式の定義部分にコメントを入れられる
【ビルド設定】
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用のコメントです'
}
}
以下のようにバンドル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']
のようにライブラリ名を配列にする。
【ビルド設定】
entry: {
'mylib': [`./src/family.js`],
},
output: {
filename: `[name].min.js`,
library: ['org', 'riversun', 'GreatFamily'],
libraryExport: '',
libraryTarget: 'umd',
globalObject: 'this',
umdNamedDefine: true,
},
【ライブラリ側ソースコード】
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を使って外部ライブラリを分離する
自作ライブラリに外部ライブラリへの依存性があるとき、外部ライブラリを自作ライブラリに一体化させて配布する方法と、外部ライブラリと自作ライブラリは分離して配布する方法がある。
ふつうに作ると一体化するので、ここでは**分離して**する方法「外部ライブラリ分離方式」(勝手に命名しました)をみていく。
ここでは、自作ライブラリTomクラスが外部ライブラリ@riversun/simple-date-formatを参照する例でみる。
**外部ライブラリをインストール
ライブラリ開発時に参照するために、使用する外部ライブラリをインストールする
npm install --save-dev @riversun/simple-date-format
【ビルド設定】
externalsを以下のように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'
}
}
以下の部分で"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】
上述したビルド設定でライブラリをビルドすると以下のようなバンドルが生成される
(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なりされていれば、自作ライブラリ内に外部ライブラリを取り込まずとも動作する。
それでは、自作ライブラリ側のソースコードをみていく。
【ライブラリ側ソースコード】
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ライブラリを公開しています。ソースコードもシンプルな構成のライブラリなので、これからライブラリを作る場合はご参考になれば幸いです。