In addition to applications, webpack can bundle JavaScript libraries. This guide is aimed at library authors who want to streamline how they bundle and distribute their code.
Suppose we are building a small library called webpack-numbers that converts the numbers 1 through 5 between their numeric and textual representations, for example turning 2 into 'two'.
The basic project layout looks like this:
+ ├── webpack.config.js
+ ├── package.json
+ └── /src
+ ├── index.js
+ └── ref.jsonInitialize the project with npm, then install webpack, webpack-cli, and lodash as development dependencies:
npm init -y
npm install --save-dev webpack webpack-cli lodashWe install lodash as a devDependency because, to start with, we will bundle it directly into our library. Since it ends up in the final output, consumers of the library won't need to install it themselves.
[
{
"num": 1,
"word": "One"
},
{
"num": 2,
"word": "Two"
},
{
"num": 3,
"word": "Three"
},
{
"num": 4,
"word": "Four"
},
{
"num": 5,
"word": "Five"
},
{
"num": 0,
"word": "Zero"
}
]Let's begin with this basic webpack configuration:
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
},
};In this example, we tell webpack to bundle src/index.js into dist/webpack-numbers.js.
When bundling a library, it's a good idea to generate source maps. They let consumers debug against your original source code instead of the minified bundle. You can enable them with the devtool option:
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
+ devtool: 'source-map',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
},
};[!TIP] Setting
devtoolto'source-map'generates a separate.mapfile alongside your bundle. Be sure to publish this.mapfile too, so consumers can use it for debugging.
So far this matches how you'd bundle an application. Here's where things differ: we need to expose the entry point's exports through the output.library option.
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
+ library: 'webpackNumbers',
},
};We exposed the entry point as webpackNumbers so it can be used through a script tag:
<script src="https://example.org/webpack-numbers.js"></script>
<script>
window.webpackNumbers.wordToNum('Five');
</script>However, this only works when the library is referenced through a script tag. It can't be consumed in other environments such as CommonJS, AMD, or Node.js.
As library authors, we want our library to work across different environments. In other words, users should be able to consume it in all of the following ways:
-
CommonJS module require:
const webpackNumbers = require('webpack-numbers'); // ... webpackNumbers.wordToNum('Two'); -
AMD module require:
require(['webpackNumbers'], webpackNumbers => { // ... webpackNumbers.wordToNum('Two'); }); -
script tag:
<!DOCTYPE html> <html> ... <script src="https://example.org/webpack-numbers.js"></script> <script> // ... // Global variable webpackNumbers.wordToNum('Five'); // Property in the window object window.webpackNumbers.wordToNum('Five'); // ... </script> </html>
To support all of these, update the output.library option and set its type to 'umd':
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
- library: 'webpackNumbers',
+ globalObject: 'this',
+ library: {
+ name: 'webpackNumbers',
+ type: 'umd',
+ },
},
};Now webpack produces a library that works with CommonJS, AMD, and a script tag.
[!TIP] Note that the
librarysetup is tied to theentryconfiguration. For most libraries, a single entry point is enough. While multi-part libraries are possible, it's simpler to expose partial exports through an index script that acts as a single entry point. Using anarrayas theentryfor a library is not recommended.
If you run npx webpack now, you'll notice the resulting bundle is fairly large. Inspecting the file reveals that lodash has been bundled along with your code. To avoid bundling lodash and bloating the library, we can configure webpack to treat it as an external module. Because we're no longer bundling it, the consumer is responsible for providing it, so you should move lodash from devDependencies to dependencies (or peerDependencies) so package managers install it automatically for your library's consumers.
This is done with the externals configuration:
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
library: {
name: 'webpackNumbers',
type: 'umd',
},
},
+ externals: {
+ lodash: {
+ commonjs: 'lodash',
+ commonjs2: 'lodash',
+ amd: 'lodash',
+ root: '_',
+ },
+ },
};This tells webpack that your library expects a dependency named lodash to be available in the consumer's environment.
For libraries that import several files from a single dependency:
import A from 'library/one';
import B from 'library/two';
// ...You won't be able to exclude them from the bundle by listing library in externals. You'll need to exclude them individually, or with a regular expression:
export default {
// ...
externals: [
'library/one',
'library/two',
// Everything that starts with "library/"
/^library\/.+$/,
],
};Optimize your output for production by following the steps in the production guide. Let's also point the package's main field in package.json at your generated bundle:
{
...
"main": "dist/webpack-numbers.js",
...
}Or, to add it as a standard module per this guide:
{
...
"module": "src/index.js",
...
}The main key refers to the standard from package.json, while module refers to a proposal that lets the JavaScript ecosystem adopt ES2015 modules without breaking backwards compatibility.
[!WARNING] The
moduleproperty should point to a script that uses ES2015 module syntax but no other syntax features that browsers or Node.js don't yet support. This lets webpack parse the module syntax itself, enabling lighter bundles through tree shaking when users consume only certain parts of the library.
You can now publish it as an npm package and find it on unpkg.com to distribute it to your users.
[!TIP] To expose stylesheets associated with your library, use the
MiniCssExtractPlugin. Users can then consume and load them as they would any other stylesheet.