Authoring Libraries
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.
Authoring a Library
Let's assume that we 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 would look like this:
project
+ ├── webpack.config.js
+ ├── package.json
+ └── /src
+ ├── index.js
+ └── ref.jsonInitialize the project with npm, then install webpack, webpack-cli as dev dependencies, and lodash as a regular dependency:
npm init -y
npm install --save-dev webpack webpack-cli
npm install lodashWe install lodash as a regular dependency because our library uses it in its code. However, we don't want to bundle it into our output bundle because users of our library will likely have their own copy of lodash, and bundling it would create unnecessary duplication and bloat.
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) => (ref.num === num ? ref.word : accum),
"",
);
}
export function wordToNum(word) {
return _.reduce(
numRef,
(accum, ref) => (ref.word === word && word.toLowerCase() ? ref.num : accum),
-1,
);
}Webpack Configuration
Let's start with this basic webpack configuration:
webpack.config.js
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 the above example, we're telling webpack to bundle src/index.js into dist/webpack-numbers.js.
Adding Source Maps
When bundling a library, it is recommended to generate source maps. Source maps
allow consumers of your library to debug your original source code rather than
the minified bundle. This can be done using the
devtool option:
webpack.config.js
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',
},
};Expose the Library
So far everything should be the same as bundling an application, and here comes the different part – we need to expose exports from the entry point through output.library option.
webpack.config.js
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 users can use it through script tag:
<script src="https://example.org/webpack-numbers.js"></script>
<script>
window.webpackNumbers.wordToNum("Five");
</script>However it only works when it's referenced through script tag, it can't be used in other environments like CommonJS, AMD, Node.js, etc.
As a library author, we want it to be compatible in different environments, i.e., users should be able to consume the bundled library in multiple ways listed below:
-
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>
Let's update the output.library option with its type set 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 will bundle a library that can work with CommonJS, AMD, and script tag.
Externalize Lodash
Now, if you run npx 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. To avoid this, we can externalize lodash using the externals configuration. This tells webpack not to bundle lodash, but instead to expect it to be provided by the consumer's environment.
In practice, when users bundle their own applications with webpack or another bundler, they will have their own copy of lodash installed. The bundler will automatically deduplicate and reuse the version they're already using, eliminating the need for multiple copies. If the consumer is using your library via a script tag, they will need to ensure lodash is available separately in the global scope.
This can be done using the externals configuration:
webpack.config.js
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 configuration tells webpack not to bundle lodash. Instead, it will be resolved at runtime from the consumer's environment — either from their own node_modules (when using a module bundler) or from the global scope (when using a script tag).
External Limitations
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.
export default {
// ...
externals: [
"library/one",
"library/two",
// Everything that starts with "library/"
/^library\/.+$/,
],
};Final Steps
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.
Now you can publish it as an npm package and find it at unpkg.com to distribute it to your users.

