Shimming: what it is, approaches and how it works

Author: IT Sectr Published: 2026-05-19 Reading time: 8 min

Shimming is a technique for ensuring compatibility of modules that expect certain global variables or APIs. In the Webpack ecosystem, shimming is implemented through ProvidePlugin, imports-loader and exports-loader, allowing legacy libraries to be connected without changing their source code. According to Webpack Documentation (2026), shimming remains a key tool for integrating jQuery plugins and other dependencies that do not support a modular system.

Key Takeaways

  • Shimming is a technique for substituting global variables and APIs to ensure module compatibility in the build.
  • ProvidePlugin automatically imports a module when it detects a reference to a global variable in the code.
  • imports-loader and exports-loader manage module scopes by adding or changing their interfaces.
  • Shim differs from polyfill in that it does not implement missing functionality, but redirects existing calls.
  • Webpack provides built-in shimming mechanisms without the need to install additional packages.

What is Shimming?

Shimming is a software technique that embeds a compatibility layer between the code and the environment without modifying the module's source code. In the context of a JavaScript build, shimming solves the problem when a module references global variables (window.$, global.process) that are absent in the module environment.

Shim and polyfill: key differences

Polyfill implements missing functionality from scratch, adding new capabilities to the environment. For example, core-js adds Array.prototype.flatMap for older browsers. Shim, in turn, redirects existing calls to available implementations or substitutes the expected global objects. In Webpack, ProvidePlugin automatically inserts import $ from 'jquery' everywhere a reference to the global variable $ is found, without requiring code changes.

The main difference lies in the goal. Polyfill adds what does not exist, while shim makes existing code compatible with the environment in which it runs. The choice between them depends on which problem is being solved: a missing API or interface incompatibility.

How Shimming works in Webpack

Webpack treats each module as an isolated unit with its own scope. If a library references the global variable jQuery as window.$, the build will fail with an error because this variable does not exist in the module context. ProvidePlugin solves the problem at the compilation stage: when the identifier $ is detected in the code, the plugin automatically inserts import $ from 'jquery' at the beginning of the file.

js
// Original code (legacy module references global jQuery)
$('.element').hide();

// After ProvidePlugin processing (Webpack inserts import)
import $ from 'jquery';
$('.element').hide();

In addition, imports-loader lets you explicitly specify which dependencies a module should receive. This is useful when a library uses this at the top level, expecting this to refer to window rather than module.exports.

ProvidePlugin: global variables for modules

ProvidePlugin is a built-in Webpack plugin that automatically loads modules when it detects references to specified identifiers. The configuration is an object where the key is the variable name and the value is the path to the module and the exported field.

Plugin configuration

js
// webpack.config.js
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
      _: 'lodash',
      'window.$': 'jquery',
    }),
  ],
};

ProvidePlugin supports partial imports using array syntax. For example, [lodash, debounce] imports only the debounce function from lodash, which reduces the size of the final bundle. This is especially important for mobile projects, where every kilobyte affects load time.

imports-loader and exports-loader

imports-loader adds the necessary imports to the beginning of a module, while exports-loader defines exported values for modules that do not use module.exports explicitly. These loaders work at the level of individual files, not globally like ProvidePlugin.

Fixing dependencies with imports-loader

js
// webpack.config.js — imports-loader configuration
module.exports = {
  module: {
    rules: [
      {
        test: /legacy-module\.js$/,
        use: [
          {
            loader: 'imports-loader',
            options: {
              imports: [
                'jquery',
                '$',
              ],
            },
          },
        ],
      },
    ],
  },
};

exports-loader is used when a library assigns a value to a global variable but does not export it through the module system. The loader extracts the value and turns it into a module export, allowing other modules to import it via import.

Configuring shimming in the Webpack configuration

Shimming is configured in webpack.config.js through a combination of plugins and loaders. A typical scenario includes ProvidePlugin for global variables and imports-loader for specific modules that require scope changes.

Basic Webpack configuration for shimming

js
const webpack = require('webpack');
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    globalObject: 'this',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules\/(?!legacy-lib)/,
        use: [
          {
            loader: 'imports-loader',
            options: {
              type: 'commonjs',
              imports: ['jquery', '$'],
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
    }),
  ],
};

The globalObject field in output sets the context for top-level this references. For a browser environment, the value 'this' refers to window, while for React Native or Node.js it refers to global. Choosing the right value prevents runtime errors in the target environment.

Common shimming mistakes

Shimming is a powerful but dangerous tool. Incorrect configuration leads to duplicated code in the bundle, name conflicts and unexpected runtime errors. Developers often forget that ProvidePlugin works at the compilation stage and cannot handle dynamic variable references.

Global variable conflicts

If two plugins use different versions of jQuery, ProvidePlugin will substitute only one of them, the one specified first in the configuration. The second library will get an incompatible version, which will cause hard-to-debug errors. The solution is to use exports-loader for each library with an explicit version or apply webpack.IgnorePlugin to exclude duplicate modules.

Another common mistake is trying to shim modules that use CommonJS synchronous require calls in a dynamic context. ProvidePlugin processes only static identifiers, so dynamic references must be replaced manually or with NormalModuleReplacementPlugin.

Performance problems with incorrect shimming

Incorrect shimming configuration can lead to a significant increase in bundle size. If ProvidePlugin is configured for dozens of global variables, Webpack will insert the corresponding imports into all project files, regardless of whether those variables are used in each particular file. This creates redundant code, especially in large projects with thousands of modules.

Use webpack-bundle-analyzer to diagnose shimming problems — a tool for visualizing the bundle composition. If jQuery or another library appears in the bundle several times, different versions are likely conflicting or ProvidePlugin is configured for several identifiers leading to different versions of the package. The solution is to unify dependency versions via resolve.alias and verify that all shimmed identifiers point to the same module.

Shimming alternatives: refactoring and updating dependencies

Before applying shimming, consider whether the library can be updated to a version that supports the module system. Many legacy packages have modern alternatives that do not require shimming. For example, jQuery plugins can be replaced with native browser APIs: $.ajaxfetch, $.eachArray.forEach. Refactoring provides a long-term maintenance benefit, whereas shimming is a temporary solution that complicates the configuration.

If an update is not possible, consider NormalModuleReplacementPlugin, which lets you replace one module with another at the resolution level without changing the source code. This plugin works at the dependency graph building stage, before loaders are applied, and handles all references to the module regardless of context. It is a cleaner solution for replacing entire libraries than point loaders.

Shimming in modern JavaScript: ESM and import maps

With the development of native ES modules in browsers and the emergence of import maps, some shimming scenarios can be solved without Webpack. Import maps allow you to remap module names on the fly at the browser level, without a build stage. However, this approach is not supported in React Native and other environments without browser ESM, so shimming through Webpack remains relevant for production builds that require full control over dependencies and their versions. The choice between import maps and Webpack shims depends on the target platform and compatibility requirements with older browsers.

Frequently asked questions

How does shimming differ from tree shaking?

Shimming adds code to ensure compatibility, while tree shaking removes unused code. These techniques are opposite in purpose: shimming increases bundle size, tree shaking reduces it. In a production build, both are applied sequentially.

Can shimming be used without Webpack?

Yes, shimming exists as a technique independently of Webpack — for example, through global scripts in HTML or through ES modules with re-export. However, Webpack provides the most convenient automation tools: ProvidePlugin and loaders that do not require manual code changes.

How does shimming affect build performance?

ProvidePlugin does not affect build speed because it works at the AST compilation stage. imports-loader and exports-loader add a small processing time for each file. When used on hundreds of files, the difference can be 5–15% of total build time.

When should you give up shimming?

If all dependencies support ES modules and the module system, shimming is redundant. Giving up shimming simplifies configuration, reduces bundle size and lowers the risk of name conflicts. It is recommended to check dependencies on caniuse.com.

How does shimming work with TypeScript?

TypeScript requires additional type declarations for shimmed variables. You need to add declare const $: any or install types via @types/jquery. ProvidePlugin inserts imports at the JavaScript level after TypeScript compilation, so types are checked separately.

Summary

  • Shimming is a technique for ensuring module compatibility with the environment by substituting global variables and APIs.
  • ProvidePlugin automatically imports modules when it detects references to specified identifiers in the code.
  • imports-loader adds imports to the beginning of specific files, while exports-loader defines exported values.
  • Shim differs from polyfill in that it does not implement functionality, but redirects calls to existing implementations.
  • ProvidePlugin works at the compilation stage and does not handle dynamic variable references.
  • The globalObject field in output sets the correct context for the top level in the target environment.
  • Use shimming only for modules that do not support the modern module system, and give it up when ES modules are fully supported.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also