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 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 installwebpack,webpack-cli andlodash:
npm init -ynpminstall --save-dev webpack webpack-cli lodashWe installlodash asdevDependencies instead ofdependencies because we don't want to bundle it into our library, or our library could be easily bloated.
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 numReffrom'./ref.json';exportfunctionnumToWord(num){return _.reduce( numRef,(accum, ref)=>{return ref.num=== num? ref.word: accum;},'');}exportfunctionwordToNum(word){return _.reduce( numRef,(accum, ref)=>{return ref.word=== word&& word.toLowerCase()? ref.num: accum;},-1);}Let's 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',},};In the above example, we're telling webpack to bundlesrc/index.js intodist/webpack-numbers.js.
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 throughoutput.library option.
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", }, };We exposed the entry point aswebpackNumbers so users can use it through script tag:
<scriptsrc="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'],function(webpackNumbers){// ... webpackNumbers.wordToNum('Two');});script tag:
<!DOCTYPEhtml><html> ...<scriptsrc="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 theoutput.library option with itstype set to'umd':
const path = require('path');module.exports = { 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.
Note that thelibrary setup is tied to theentry configuration. For most libraries, specifying a single entry point is sufficient. Whilemulti-part libraries are possible, it is more straightforward to expose partial exports through anindex script that serves as a single entry point. Using anarray as anentry point for a library isnot recommended.
Now, if you runnpx 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 treatlodash as apeer dependency. Meaning that the consumer should already havelodash installed. Hence you would want to give up control of this external library to the consumer of your library.
This can be done using theexternals configuration:
webpack.config.js
const path = require('path'); module.exports = { 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 means that your library expects a dependency namedlodash to be available in the consumer's environment.
For libraries that use several files from a dependency:
importAfrom'library/one';importBfrom'library/two';// ...You won't be able to exclude them from the bundle by specifyinglibrary 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\/.+$/,],};Optimize your output for production by following the steps mentioned in theproduction guide. Let's also add the path to your generated bundle as the package'smain field in with thepackage.json
package.json
{ ..."main":"dist/webpack-numbers.js", ...}Or, to add it as a standard module as perthis guide:
{ ..."module":"src/index.js", ...}The keymain refers to thestandard frompackage.json, andmodule toaproposal 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 viatree shaking if users are only consuming certain parts of the library.
Now you canpublish it as an npm package and find it atunpkg.com to distribute it to your users.
To expose stylesheets associated with your library, theMiniCssExtractPlugin should be used. Users can then consume and load these as they would any other stylesheet.