SoFunction
Updated on 2025-04-10

The principle and implementation of webpack organization module packaging library

A previous article analyzedWebpack package JS moduleThe basic principle of the case introduced is the most common case, that is, multiple JS modules and an entry module are packaged into a bundle file, which can be directly executed by the browser or other JavaScript engines, which is equivalent to directly compiling and generating a complete executable file. However, there is another very common situation, which is that we want to build and publish a JavaScript library. For example, if you publish your own library in the npm community, Webpack needs corresponding configuration, and the compiled and generated code will be slightly different.

Like the previous article, this article mainly analyzes the generated code of Webpack and combines it to explain the specific role of Webpack's configuration options for library when compiling the library. The corresponding official documentation ishere

Write a library for JS

Let's start with simple cases, and we just write a simple library:

import $ from 'jquery'

function sayHello() {
 ("Hello");
}

function hideImages() {
 $('img').hide();
}

export default {
 sayHello: sayHello,
 hideImages: hideImages
}

Two functions are provided, of course they have no relationship and are actually useless. They are purely for teaching reference only. . .

Next, write the configuration of Webpack:

// Entry fileentry: {
 util: './',
}

// Output fileoutput: {
 path: './dist',
 filename: '[name].'
}

But this is not enough. The output file is an immediate execution function, and the exports will be returned in the end. Refer to the analysis in the previous article, the final generated bundle code structure is roughly as follows:

(function(modules) {
 var installedModules = {};
 
 function webpack_require(moduleId) {
   // ...
 }

 return webpack_require('./');
}) ({
 './': generated_util,
 '/path/to/': generated_jquery
});

If it is executed, it will end and return the export part. What we need is to hand over this return value to the compiled file, so that the compiled file becomes a library that can be imported by others. So the compiled file we want should look like this:

 = (function(modules) {
 var installedModules = {};
 function webpack_require(moduleId) {
   // ...
 }
 return webpack_require('./');
}) ({
 './': generated_util,
 '/path/to/': generated_jquery
});

To get such a result, the Webpack configuration output part needs to add library information:

// Entry fileoutput: {
 path: './dist',
 filename: '[name].',

 library: 'util',
 libraryTarget: commonjs2
}

The most important thing here is libraryTarget. Now we use the commonjs2 format and we will get the above compilation result. That is to say, Webpack will export the last output in the form of CommonJS, which will realize the release of a library.

Other publishing formats

In addition to commonjs2, libraryTarget has other options:

var (default value,Published as global variable)
commonjs
commonjs2
amd
umd

Using different options, compiled files can be used in different JavaScript execution environments. Here we directly look at the output of the Wanjinyou umd format:

(function webpackUniversalModuleDefinition(root, factory) {
 if(typeof exports === 'object' && typeof module === 'object') // commonjs2
   = factory();
 else if(typeof define === 'function' && )
  define("util", [], factory); // amd
 else if(typeof exports === 'object')
  exports["util"] = factory(); // commonjs
 else
  root["util"] = factory(); // var
}) (window, function() {
 return (function(modules) {
  var installedModules = {};
  function webpack_require(moduleId) {
    // ...
  }
  return webpack_require('./');
 }) ({
  './': generated_util,
  '/path/to/': generated_jquery
 });
}

It is much more complicated than the previous commonjs2 situation, because it needs to deal with various different cases, but in fact the latter part is the same, the most important thing is the first few lines, which is the standard writing method of the umd module. It runs the passed factory function, which is actually the function that loads the module, and then handes the returned result to the corresponding object according to the different running environment. For example, var will set the result to a global variable, which is used by the browser to directly import the JS file through the <script> tag; if it is CommonJS, it will be handed over to the exports object; if it is AMD environment, it will also use the standard AMD writing method. In this way, the published JS library can be used by others in any environment.

targetExport controls output content

If you package it in umd format, there may be a pitfall that needs to be noted. If the source code of your library is output using ES6 format export default, as in the example above, you can directly put the compiled JS library file into the browser, which can be <script> or RequireJS, and you may not get the results you want. This is because the object returned to your JS file is like this:

{
 'default': {
  sayHello: sayHello,
  hideImages: hideImages
 }
}

Instead of what you expect:

{
 sayHello: sayHello,
 hideImages: hideImages
}

This problem occurs not only in browsers, but also in module systems that do not support ES6 because they do not recognize default. So the compiled JS file should actually only output default, which requires targetExport to be controlled in the Webpack configuration:

library: 'util',
libraryTarget: umd,
targetExport: 'default'

In this way, the above module loading function factory will add a ['default'] after the return value, so that only the default part of exports is returned.

This pit is actually quite easy to touch in the umd format. For example, if you publish a Vue component, the JavaScript part in the .vue file generally exports the Component object in the export default format, just like this:

export default {
 name: 'xxx',
 data: {
  return // ...
 },
 props: {
  // ...
 }
 methods: {
  // ...
 }
}

If you put the compiled JS file directly in the browser and load Vue through <script> using CDN, you will find that Vue cannot recognize the Component because the object you get has an unnecessary default.

You may ask if I change the output content to default, will it affect the use of this module in the ES6 environment? Generally speaking, it won't. As mentioned in a previous article, when the Webpack generated code introduces a module, it will set and judge whether it is an export in ES6 format through a value called __esModule. Now if only the default part is exported, then this object is considered non-ES6 because it does not contain __esModule. In this way, when other modules introduce this module through import, the entire object will be introduced, which is actually equivalent to introducing only the export default part of the original module.

Of course, the premise of the above discussion is that all the content you need to export is in the export default. If you have both default and normal export, it is obviously not possible to export only the default part of the compiled file.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.