Introduces how to build a library that supports both browser and Node.js using webpack4 and ES6, and how to use the created library.
There are two ways to create a library that supports both browser and node.js.
-
One bundle:
The first is a method that covers both browser and Node.js with one bundle. In this article, we'll take a closer look at how to create one bundle for both browser and Node.js. -
Two bundles:
The second is to build libraries for browser and node.js separately.
Step 1.Clone this repository.
Step2.Go to example directory like "part_1_1"
cd part_1_1
Step3.Run 'npm start' after npm install
to start examples.
npm install
npm start
Build configuration is as follows
family.js is the source code of the library to be published
webpack.config.js
entry: {
'mylib': [`./src/family.js`],
},
output: {
filename: `[name].min.js`,
library: '',
libraryExport: '',
libraryTarget: 'umd',
globalObject: 'this',
},
See full source code of webpack.config.js
family.js has a class named Tom with a single method sayHello. We will see how to turn this into a library.
family.js
export default class Tom {
sayHello() {
return 'Hi, I am Tom.'
}
}
●Using from Browser
<script src="./mylib.min.js"></script>
<script>
const Tom = window.default;
const tom = new Tom();
console.log(tom.sayHello());
</script>
●Using from Node.js
const Lib = require('./mylib.min.js');
const Tom = Lib.default;
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
Also works with the following code,
const Tom = require('./mylib.min.js').default;
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
●Example of using ES6's import statement
import * as Lib from './mylib.min.js';
const Tom = Lib.default;
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
Also works with the following code,
import {default as Tom} from './mylib.min.js';
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
or
import Tom from './mylib.min.js';//Pick default
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
●Tips for this recipe
Point 1:What does globalObject: 'this' mean?
The webpacked bundle mylib.min.js is as follows.
(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() {...});
See full source code of bundle(develop build)
This is an immediate function with (this, function ())
as its argument.
This this
is caused by setting globalObject: 'this'.
If you do not specify globalObject:,
the argument of this immediate function will be (window, function ())
.
It works on browsers that have a window
object,
but cannot be run on node.js that does not have a window object.
As you might expect, if you try the above, you will get ReferenceError: window is not defined
.
So if you want to support both browser and node.js,
don't forget to include globalObject: 'this'.
Point 2:Classes you want to publish are stored with the key "default"
If you want to access the classes published in the library,
Use require('./mylib.min.js').default
on node.js and use window.default
(=window["default"]) on the browser.
Remember that in this configuration, the class is identified by the key default.
The library name (namespace) can be set by specifying output.library: 'MyLibrary' in webpack.config.js.
webpack.config.js
entry: {
'mylib': [`./src/family.js`],
},
output: {
filename: `[name].min.js`,
library: 'MyLibrary',
libraryExport: '',
libraryTarget: 'umd',
globalObject: 'this',
},
See full source code of webpack.config.js
family.js
export default class Tom {
sayHello() {
return 'Hi, I am Tom.'
}
}
●Using from Browser
See below. Tom class can be used as MyLibrary.default.
<script src="./mylib.min.js"></script>
<script>
const Tom = MyLibrary.default;
const tom = new Tom();
console.log(tom.sayHello());
</script>
●Using from Node.js
Note that in case of node.js (CommonJS2), library name is ignored . So output.library: 'MyLibrary' does not work for node.js.
const Lib = require('./mylib.min.js');
const Tom = Lib.default;
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
●Example of using ES6's import statement
import * as Lib from './mylib.min.js';
const Tom = Lib.default;
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
You want to access a class without using "default" which looks redundant like below.
const Tom = MyLibrary.default;
Try to set output.libraryExport: 'default'
webpack.config.js
entry: {
'mylib': [`./src/family.js`],
},
output: {
filename: `[name].min.js`,
library: 'MyLibrary',
libraryExport: 'default',
libraryTarget: 'umd',
globalObject: 'this',
},
See full source code of webpack.config.js
family.js
export default class Tom {
sayHello() {
return 'Hi, I am Tom.'
}
}
●Using from Browser
Let's build the library with this configuration.
Then, instead of MyLibrary.default
,
MyLibrary
itself equals to a reference of Tom
class.
<script src="./mylib.min.js"></script>
<script>
const Tom = MyLibrary;
const tom = new Tom();
console.log(tom.sayHello());
</script>
●Using from Node.js
As mentioned above, in case of node.js (CommonJS2), library name is ignored .
const Tom = require('./mylib.min.js');
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
●Example of using ES6's import statement
import Tom from './mylib.min.js';
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
- Set
output.libraryExport: 'default'
- Make library name the same as class name like
output.library: 'Tom'
webpack.config.js
entry: {
'mylib': [`./src/family.js`],
},
output: {
filename: `[name].min.js`,
library: 'Tom',
libraryExport: 'default',
libraryTarget: 'umd',
globalObject: 'this',
},
See full source code of webpack.config.js
family.js
export default class Tom {
sayHello() {
return 'Hi, I am Tom.'
}
}
●Using from Browser
<script src="./mylib.min.js"></script>
<script>
const tom = new Tom();
console.log(tom.sayHello());
</script>
●Using from Node.js
const Tom = require('./mylib.min.js');
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
●Example of using ES6's import statement
import Tom from './mylib.min.js';
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
●Tips for this recipe
It can be accessed from the browser and node.js with the symbol Tom. This configuration is one of my favorites.
Publish the library using re-export. Re-export means exporting one module from another.
Change entry to index.js to re-export from index.js.
webpack.config.js
entry: {
'mylib': [`./src/index.js`],
},
output: {
filename: `[name].min.js`,
library: '',
libraryExport: '',
libraryTarget: 'umd',
globalObject: 'this',
},
See full source code of webpack.config.js
Now, let's create index.js and re-export the Tom class in family.js from there.
export {default as Tom} from './family.js';
Tom is "export"ed by {default as Tom} when reexporting by index.js. So, strictly speaking, this method is no longer "default export".
family.js
export default class Tom {
sayHello() {
return 'Hi, I am Tom.'
}
}
●Using from Browser
<script src="./mylib.min.js"></script>
<script>
const tom = new Tom();
console.log(tom.sayHello());
</script>
●Using from Node.js
We use destructuring assignment to get the Tom class.
const {Tom} = require('./mylib.min.js');
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
●Example of using ES6's import statement
import {Tom} from './mylib.min.js';
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
Let's look at some examples of publishing multiple classes. (You can publish not just classes but functions or variables in the same way.)
As you can see, the following family.js contains two classes Tom and Jack.
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',
},
See full source code of webpack.config.js
●Using from Browser
<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>
●Using from 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.
Also works with the following code,
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.
●Example of using ES6's import statement
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.
OR
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.
By specifying library:'GreatFamily'
,
you can add a library name (like namespace) like as follows.
webpack.config.js
entry: {
'mylib': [`./src/family.js`],
},
output: {
filename: `[name].min.js`,
library: 'GreatFamily',
libraryExport: '',
libraryTarget: 'umd',
globalObject: 'this',
},
See full source code of webpack.config.js
family.js
export class Tom {
sayHello() {
return 'Hi, I am Tom.'
}
}
export class Jack {
sayHello() {
return 'Hi, I am Jack.'
}
}
●Using from Browser
When running on a browser, each class(Tom and Jack) is stored in 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>
●Using from Node.js
Note that in case of node.js (CommonJS2), library name is ignored . So output.library: 'GreatFamily' does not work for node.js.
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.
●Example of using ES6's import statement
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.Publish multiple classes including "export default" class
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',
},
See full source code of webpack.config.js
●Using from Browser
<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>
●Using from 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.
Also works with the following code,
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.
●Example of using ES6's import statement
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.
or
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.
or
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.Publish only "export default" class from multiple classes.
Here's a rare pattern, but let's take a look to get a better understanding of what happens when building.
webpack.config.js
entry: {
'mylib': [`./src/family.js`],
},
output: {
filename: `[name].min.js`,
library: 'Tom',
libraryExport: 'default',
libraryTarget: 'umd',
globalObject: 'this'
},
See full source code of webpack.config.js
family.js
export default class Tom {
sayHello() {
return 'Hi, I am Tom.'
}
}
export class Jack {
sayHello() {
return 'Hi, I am Jack.'
}
}
●Using from Browser
Jack class becomes inaccessible from outside code.
<script src="./mylib.min.js"></script>
<script>
const tom = new Tom();
console.log(tom.sayHello());
</script>
●Using from Node.js
const Tom = require('./mylib.min.js');
const tom=new Tom();
console.log(tom.sayHello());//->Hi, I am Tom.
●Example of using ES6's import statement
import Tom from './mylib.min.js';
const tom=new Tom();
console.log(tom.sayHello());//->Hi, I am Tom.
●Tips for this recipe
The Jack class is included as code in the bundle even though it is not accessible from outside. This is purely wasteful, so if your Jack class isn't used by anyone, don't put it in your source code.
When libraryTarget: 'umd'
is specified
Root, AMD, and CommonJS can have different library names (namespaces).
However, you cannot specify a library name for CommonJS2 (for node.js) as before, it will be ignored.
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,
}
See full source code of webpack.config.js
library: {
root: 'GreatFamily',
amd: 'great-family',
commonjs: 'common-great-family',
},
In the above part, the library name is given for each module type.
Be careful if you want to use the AMD module type.
Specify umdNamedDefine: trueP
if you want to add library name to AMD.
Let's see the result of building with this setting. The bundle is shown below. As you can see, each module type has a specified library name.
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() {...}
See full source code of bundle(develop build)
By writing auxiliaryComment, you can add comments to the source code of each module type definition of the generated code of bundle.
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: 'Comment for Root',
commonjs: 'Comment for CommonJS',
commonjs2: 'Comment for CommonJS2',
amd: 'Comment for AMD'
}
}
See full source code of webpack.config.js
As you can see below, you can see the comments in the bundle.
family.min.js
(function webpackUniversalModuleDefinition(root, factory) {
//Comment for CommonJS2
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
//Comment for AMD
else if(typeof define === 'function' && define.amd)
define("great-family", [], factory);
//Comment for CommonJS
else if(typeof exports === 'object')
exports["common-great-family"] = factory();
//Comment for Root
else
root["GreatFamily"] = factory();
})(this, function() {...}
See full source code of bundle(develop build)
If you want to name the library "org.riversun.GreatFamily",
for example, specify an array like 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,
},
See full source code of webpack.config.js
family.js
export class Tom {
sayHello() {
return 'Hi, I am Tom.'
}
}
export class Jack {
sayHello() {
return 'Hi, I am Jack.'
}
}
●Using from Browser
<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>
As mentioned above, in case of node.js (CommonJS2), library name is ignored .
If your library uses an external library (if it has dependencies), there are two types of build methods.
- One method is to bundle an external library into your own library.
- The other method is to externalize an external library.
This section describes "externalization" .
Here is an example of code where the Tom class depends on external library @riversun/simple-date-format.
**Install an external library
Install an external library to be used as development dependencies.
npm install --save-dev @riversun/simple-date-format
Add externals into webpack.config.js as below.
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'
}
}
See full source code of webpack.config.js
In the following part, the part specified as "SDF" means the property name for referring to the external library from the source code.
externals: {
SDF: {
commonjs: '@riversun/simple-date-format',
commonjs2: '@riversun/simple-date-format',
amd: '@riversun/simple-date-format',
root: 'SimpleDateFormat'
}
}
Write "Library type name:Library name" (same as npm install
) as shown below in the child element of SDF.
commonjs: '@riversun/simple-date-format',
commonjs2: '@riversun/simple-date-format',
amd: '@riversun/simple-date-format',
Library name can be set for each module type like commonjs, commonjs2, amd. SDF in the example above acts as an alias. What the SDF actually points to is an external library specified for each module type.
Look at this at the bottom,
root: 'SimpleDateFormat'
When using your own library on the browser, SDF is built to reference SimpleDateFormat(=window.["SimpleDateFormat"]).
● Generated bundle
When building, the following bundle is generated,
(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__) {...})
See full source code of bundle(develop build)
You can see that the code that loads the external library is generated for each module type.
This way you can avoid bundling external libraries.
Let's look at the source code of "my" library.
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)}`;
}
}
You can see that SDF in import SimpleDateFormat from" SDF ";
is an alias of the original import SimpleDateFormat from"@riversun/simple-date-format";
.
Next, let's look at using my library created by separating the external library.
●Using from Browser
- When using from a browser, read the external library from the CDN as follows
- Note that dependent libraries (external libraries) are loaded "before" my library.
<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>
By the way, the external library used this time was also built by the method of 1-4 in this article.
●Using from Node.js
const Tom = require('./mylib.min.js');
const tom = new Tom();
console.log(tom.sayHello());//-> Hi, I am Tom.
Written by Tom Misawa (riversun.org@gmail.com) on 2020-02-28