Aside from applications, webpack can also be used to bundle JavaScript libraries. The following guide is meant for library authors looking to streamline their bundling strategy.
Let's assume that you are writing a small library ,webpack-numbers
, that allows users to convert the numbers 1 through 5 from their numeric representation to a textual one and vice-versa, e.g. 2 to 'two'.
The basic project structure may look like this:
project
+ |- webpack.config.js
+ |- package.json
+ |- /src
+ |- index.js
+ |- ref.json
Initialize npm, install webpack and lodash:
npm init -y
npm install --save-dev webpack lodash
src/ref.json
[{
"num": 1,
"word": "One"
}, {
"num": 2,
"word": "Two"
}, {
"num": 3,
"word": "Three"
}, {
"num": 4,
"word": "Four"
}, {
"num": 5,
"word": "Five"
}, {
"num": 0,
"word": "Zero"
}]
src/index.js
import _ from 'lodash';
import numRef from './ref.json';
export function numToWord(num) {
return _.reduce(numRef, (accum, ref) => {
return ref.num === num ? ref.word : accum;
}, '');
};
export function wordToNum(word) {
return _.reduce(numRef, (accum, ref) => {
return ref.word === word && word.toLowerCase() ? ref.num : accum;
}, -1);
};
The usage specification for the library use will be as follows:
// ES2015 module import
import * as webpackNumbers from 'webpack-numbers';
// CommonJS module require
var webpackNumbers = require('webpack-numbers');
// ...
// ES2015 and CommonJS module use
webpackNumbers.wordToNum('Two');
// ...
// AMD module require
require(['webpackNumbers'], function ( webpackNumbers) {
// ...
// AMD module use
webpackNumbers.wordToNum('Two');
// ...
});
The consumer also can use the library by loading it via a script tag:
<!doctype html>
<html>
...
<script src="https://unpkg.com/webpack-numbers"></script>
<script>
// ...
// Global variable
webpackNumbers.wordToNum('Five')
// Property in the window object
window.webpackNumbers.wordToNum('Five')
// ...
</script>
</html>
Note that we can also configure it to expose the library in the following ways:
this
object.For full library configuration and code please refer to webpack-library-example.
Now let's bundle this library in a way that will achieve the following goals:
lodash
, but requiring it to be loaded by the consumer using externals
.webpack-numbers
.webpackNumbers
.Also, the consumer should be able to access the library the following ways:
import webpackNumbers from 'webpack-numbers'
.require('webpack-numbers')
.script
tag.We can start with this basic webpack configuration:
webpack.config.js
var path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js'
}
};
Now, if you run webpack
, you will find that a largish bundle is created. If you inspect the file, you'll see that lodash has been bundled along with your code. In this case, we'd prefer to treat lodash
as a peerDependency
. Meaning that the consumer should already have lodash
installed. Hence you would want to give up control of this external library to the consumer of your library.
This can be done using the externals
configuration:
webpack.config.js
var path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js'
- }
+ },
+ externals: {
+ lodash: {
+ commonjs: 'lodash',
+ commonjs2: 'lodash',
+ amd: 'lodash',
+ root: '_'
+ }
+ }
};
This means that your library expects a dependency named lodash
to be available in the consumer's environment.
Note that if you only plan on using your library as a dependency in another webpack bundle, you may specifyexternals
as an array.
For libraries that use several files from a dependency:
import A from 'library/one';
import B from 'library/two';
// ...
You won't be able to exclude them from bundle by specifying library
in the externals. You'll either need to exclude them one by one or by using a regular expression.
externals: [
'library/one',
'library/two',
// Everything that starts with "library/"
/^library\/.+$/
]
For widespread use of the library, we would like it to be compatible in different environments, i.e. CommonJS, AMD, Node.js and as a global variable. To make your library available for consumption, add the library
property inside output
:
webpack.config.js
var path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
- filename: 'webpack-numbers.js'
+ filename: 'webpack-numbers.js',
+ library: 'webpackNumbers'
},
externals: {
lodash: {
commonjs: 'lodash',
commonjs2: 'lodash',
amd: 'lodash',
root: '_'
}
}
};
Note that thelibrary
setup is tied to theentry
configuration. For most libraries, specifying a single entry point is sufficient. While multi-part libraries are possible, it is simpler to expose partial exports through an index script that serves as a single entry point. Using anarray
as anentry
point for a library is not recommended.
This exposes your library bundle available as a global variable named webpackNumbers
when imported. To make the library compatible with other environments, add libraryTarget
property to the config. This will add the different options about how the library can be exposed.
webpack.config.js
var path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
- library: 'webpackNumbers'
+ library: 'webpackNumbers',
+ libraryTarget: 'umd'
},
externals: {
lodash: {
commonjs: 'lodash',
commonjs2: 'lodash',
amd: 'lodash',
root: '_'
}
}
};
You can expose the library in the following ways:
script
tag (libraryTarget:'var'
).this
object (libraryTarget:'this'
).window
object, in the browser (libraryTarget:'window'
).require
(libraryTarget:'umd'
).If library
is set and libraryTarget
is not, libraryTarget
defaults to var
as specified in the output configuration documentation. See output.libraryTarget
there for a detailed list of all available options.
With webpack 3.5.5, usinglibraryTarget: { root:'_' }
doesn't work properly (as stated in issue 4824). However, you can setlibraryTarget: { var: '_' }
to expect the library as a global variable.
Optimize your output for production by following the steps in the production guide. Let's also add the path to your generated bundle as the package's main
field in with our package.json
package.json
{
...
"main": "dist/webpack-numbers.js",
...
}
Or, to add as standard module as per this guide:
{
...
"module": "src/index.js",
...
}
The key main
refers to the standard from package.json
, and module
to a proposal to allow the JavaScript ecosystem upgrade to use ES2015 modules without breaking backwards compatibility.
Themodule
property should point to a script that utilizes ES2015 module syntax but no other syntax features that aren't yet supported by browsers or node. This enables webpack to parse the module syntax itself, allowing for lighter bundles via tree shaking if users are only consuming certain parts of the library.
Now you can publish it as an npm package and find it at unpkg.com to distribute it to your users.
To expose stylesheets associated with your library, theExtractTextPlugin
should be used. Users can then consume and load these as they would any other stylesheet.