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:
import * as webpackNumbers from 'webpack-numbers';
// ...
webpackNumbers.wordToNum('Two');
const webpackNumbers = require('webpack-numbers');
// ...
webpackNumbers.wordToNum('Two');
require(['webpackNumbers'], function (webpackNumbers) {
// ...
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:
externals
to avoid bundling lodash
, so the consumer is required to load it.webpack-numbers
.webpackNumbers
.Also, the consumer should be able to access the library in the following ways:
import webpackNumbers from 'webpack-numbers'
.require('webpack-numbers')
.script
tag.We can start with this basic webpack configuration:
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
},
};
Source maps is a useful debugging tool that allows you to view where the minified code originated from.
webpack.config.js
const path = require('path');
module.exports = [
'source-map'
].map(devtool => ({
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js'
},
devtool,
optimization: {
runtimeChunk: true
}
}));
For more information about getting source maps setup and available options please refer to Devtool configuration
To see code examples please refer to webpack repository
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
const 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 specify
externals
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 the bundle by specifying library
in the externals. You'll either need to exclude them one by one or by using a regular expression.
module.exports = {
//...
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
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
+ library: 'webpackNumbers',
},
externals: {
lodash: {
commonjs: 'lodash',
commonjs2: 'lodash',
amd: 'lodash',
root: '_',
},
},
};
Note that the
library
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 various options about how the library can be exposed.
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
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, using
libraryTarget: { 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 mentioned in the production guide. Let's also add the path to your generated bundle as the package's main
field in with the package.json
package.json
{
...
"main": "dist/webpack-numbers.js",
...
}
Or, to add it as a 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.
The
module
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, the
MiniCssExtractPlugin
should be used. Users can then consume and load these as they would any other stylesheet.