Build umd artifacts

The full name of umd is Universal Module Definition, and JS files in this format can run in multiple runtime environments: the

  • Browser environment: module loading based on AMD specification
  • Node.js environment: module loading based on CommonJS
  • Other cases: mount the module on a global object.

We can therefore specify the build artifacts of the project as an umd artifact in the following way:

export default defineConfig({
  buildConfig: {
    format: 'umd',
  },
});

Third-party dependency handling for umd artifacts

In the "How to handle third-party dependencies" chapter, we know that we can control whether or not the project packages third-party dependencies via the autoExternals and externals APIs. So when building umd artifacts, we can also use it like this:

Example

  • If the project depends on react:
package.json
{
  "dependencies": {
    "react": "^17"
  }
}
  • modern.config.ts:
export default defineConfig({
  buildConfig: {
    format: 'umd',
    autoExternal: false,
    externals: ['react'],
  },
});
  • When a react dependency is used in the source code:
src/index.ts
import React from 'react';
console.info(React);
  • The react code is not bundled into the artifact at this point:
dist/index.js
(function (global, factory) {
  if (typeof module === 'object' && typeof module.exports === 'object')
    factory(exports, require('react'));
  else if (typeof define === 'function' && define.amd)
    define(['exports', 'react'], factory);
  else if (
    (global = typeof globalThis !== 'undefined' ? globalThis : global || self)
  )
    factory((global.index = {}), global.react);
})(this, function (exports, _react) {
  'use strict';
  Object.defineProperty(exports, '__esModule', {
    value: true,
  });
  _react = /*#__PURE__*/ _interopRequireDefault(_react);
  function _interopRequireDefault(obj) {
    return obj && obj.__esModule
      ? obj
      : {
          default: obj,
        };
  }
  console.info(_react.default);
});

We know from the above example that when using the autoExternal and externals APIs.

  • In a Node.js environment, you can get the react dependency with require('react').
  • In a browser environment, you can get the react dependency via global.react.

Global variable names of third-party dependencies

However, in the browser environment, when getting third-party dependencies, global variable names are not necessarily identical to the dependency names, so you have to use the buildConfig.umdGlobals API.

Again using the previous example, when the react dependency exists in the browser environment as a windows.React or global.React global variable, then:

  • modern.config.ts config file:
export default defineConfig({
  buildConfig: {
    format: 'umd',
    umdGlobals: {
      react: 'React',
    },
  },
});

When a react dependency is used in the source code:

src/index.ts
import React from 'react';
console.info(React);

At this point we will see the output code like this:

dist/index.js
(function (global, factory) {
  if (typeof module === 'object' && typeof module.exports === 'object')
    factory();
  else if (typeof define === 'function' && define.amd) define([], factory);
  else if (
    (global = typeof globalThis !== 'undefined' ? globalThis : global || self)
  )
    factory();
})(this, function () {
  // ...
  // libuild:globals:react
  var require_react = __commonJS({
    'libuild:globals:react'(exports, module1) {
      module1.exports = Function('return this')()['React'];
    },
  });
  // src/index.ts
  var import_react = __toESM(require_react());
  console.info(import_react.default);
});

The project can then run in the browser and use the React variables that exist on the global object.

Changing the name of a global variable in a project

When we package the following code into an umd artifact and run it in the browser, we can use the module via window.index.

./src/index.ts
export default () => {
  console.info('hello world');
};

** By default, the name of the source file is used as the name of the module's global variable in the browser. **For the above example, the artifact would read as follows:

./dist/index.js
(function (global, factory) {
  if (typeof module === 'object' && typeof module.exports === 'object')
    factory(exports);
  else if (typeof define === 'function' && define.amd)
    define(['exports'], factory);
  else if (
    (global = typeof globalThis !== 'undefined' ? globalThis : global || self)
  )
    factory((global.index = {}));
})(this, function (exports) {
  //...
});

If you need to modify it, you need to use the buildConfig.umdModuleName API.

When this API is used:

export default defineConfig({
  buildConfig: {
    format: 'umd',
    umdModuleName: 'myLib',
  },
});

The build artifact at this point are:

./dist/index.js
(function (global, factory) {
  if (typeof module === 'object' && typeof module.exports === 'object')
    factory(exports);
  else if (typeof define === 'function' && define.amd)
    define(['exports'], factory);
  else if (
    (global = typeof globalThis !== 'undefined' ? globalThis : global || self)
  )
    factory((global.myLib = {}));
})(this, function (exports) {
  //...
});