Package Consuming Techniques

Kea - High level abstraction between React and Redux - Interview with Marius Andra

Sometimes packages have not been packaged the way you expect and you have to tweak the way webpack interprets them. Webpack provides multiple ways to achieve this.

Tweaking Module Resolution#

Sometimes packages do not follow the standard rules and their package.json contains a faulty main field. It can be missing altogether. resolve.alias is the field to use here as in the example below:

{
  resolve: {
    alias: {
      demo: path.resolve(
        __dirname, 'node_modules/demo/dist/demo.js'
      ),
    },
  },
},

The idea is that if webpack resolver matches demo in the beginning, it resolves from the target. You can constrain the process to an exact name by using a pattern like demo$.

Light React alternatives, such as Preact, react-lite, or Inferno, offer smaller size while trading off functionality like propTypes and synthetic event handling. Replacing React with a lighter alternative can save a significant amount of space but you should test well if you do this.

If you are using react-lite, configure it as below:

{
  resolve: {
    alias: {
      // Swap the target based on your need.
      'react': 'react-lite',
      'react-dom': 'react-lite',
    },
  },
},
The same technique works with loaders too. You can use resolveLoader.alias similarly. You can use the technique to adapt a RequireJS project to work with webpack.
Specific plugins like directory-named-webpack-plugin allow you to enhance webpack's module resolution. You can attach these to your setup through the resolve.plugins field.

Consuming Packages Outside of Webpack#

Browser dependencies, like jQuery, are often served through publicly available Content Delivery Networks (CDN). CDNs allow you to push the problem of loading popular packages elsewhere. If a package has been already loaded from a CDN and it's in the user cache, there is no need to load it.

To use this technique, you should first mark the dependency in question as an external:

externals: {
  'jquery': 'jquery',
},

You still have to point to a CDN and ideally provide a local fallback so there is something to load if the CDN does not work for the client:

<script src="//ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
    window.jQuery || document.write('<script src="js/jquery-3.1.1.min.js"><\/script>')
</script>
html-webpack-cdn-plugin is one option if you are using HtmlWebpackPlugin and want to inject a script tag automatically.

Dealing with Globals#

Sometimes modules depend on globals. $ provided by jQuery is a good example. Webpack provides a few ways that allow you to handle them.

Injecting Globals#

imports-loader allows you to inject globals as below:

{
  module: {
    rules: {
      // Resolve against package path.
      // require.resolve returns a path to it.
      test: require.resolve('jquery-plugin'),
      loader: 'imports-loader?$=jquery',
    },
  },
},

Resolving Globals#

Webpack's ProvidePlugin allows webpack to resolve globals as it encounters them:

{
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
    }),
  ],
},

Exposing Globals to the Browser#

Sometimes you have to expose packages to third party scripts. expose-loader allows this as follows:

{
  test: require.resolve('react'),
  use: 'expose-loader?React',
},

With the small extra tweak, the technique can be used to expose React performance utilities to the browser through React.Perf global. You have to insert the following code to your application entry point for this to work:

if (process.env.NODE_ENV !== 'production') {
  React.Perf = require('react-addons-perf');
}
It can be a good idea to install React Developer Tools to Chrome for even more information as it allows you to inspect props and state of your application.
script-loader allows you to execute scripts in a global context. You have to do this if the scripts you are using rely on a global registration setup.

Removing Unused Modules#

Even though packages can work well out of the box, they bring too much code to your project sometimes. Moment.js is a popular example. It brings locale data to your project by default.

The easiest method to disable that behavior is to use IgnorePlugin to ignore locales:

{
  plugins: [
    new webpack.IgnorePlugin(
      /^\.\/locale$/,
      /moment$/
    ),
  ],
},
You can use the same mechanism to work around problematic dependencies. Example: new webpack.IgnorePlugin(/^(buffertools)$/).

To bring specific locales to your project, you should use ContextReplacementPlugin:

{
  plugins: [
    new webpack.ContextReplacementPlugin(
      /moment[\/\\]locale$/,
      /de|fi/
    ),
  ],
},
There's a Stack Overflow question that covers these ideas in detail.

Managing Pre-built Dependencies#

It's possible webpack gives the following warning with certain dependencies:

WARNING in ../~/jasmine-promises/dist/jasmine-promises.js
Critical dependencies:
1:113-120 This seems to be a pre-built javascript file. Though this is possible, it's not recommended. Try to require the original source to get better results.
 @ ../~/jasmine-promises/dist/jasmine-promises.js 1:113-120

The warning can happen if a package points at a pre-built (i.e., minified and already processed) file. Webpack detects this case and warns against it.

The warning can be eliminated by aliasing the package to a source version as discussed above. Given sometimes the source is not available, another option is to tell webpack to skip parsing the files through module.noParse. It accepts either a RegExp or an array of RegExps and can be configured as below:

{
  module: {
    noParse: /node_modules\/demo-package\/dist\/demo-package.js/,
  },
},
Take care when disabling warnings as it can hide underlying issues. Consider alternatives first. There's a webpack issue that discusses the problem in detail.

Managing Symbolic Links#

Symbolic links, or symlinks, are an operating system level feature that allow you to point to other files through a file system without copying them. You can use npm link to create global symlinks for packages under development and then use npm unlink to remove the links.

Webpack resolves symlinks to their full path like Node does. The problem is that if you are unaware of this fact, the behavior can surprise you especially if you rely on webpack processing. It's possible to work around the behavior as discussed in webpack issues #1643 and #985. Webpack core behavior may improve in the future to make these workarounds unnecessary.

You can disable webpack's symlink handling by setting resolve.symlinks as false.

Getting Insights on Packages#

To get more information about packages, npm provides npm info <package> command for basic queries. You can use it to check the metadata associated with packages while figuring out version related information.

package-config-checker goes a step further. It allows you to understand better which packages of your project have updated recently and it provides means to get insight into your dependencies. It can reveal which packages could use download size related improvements for example.

slow-deps can reveal which dependencies of a project are the slowest to install.

weigh can be used figure out the approximate size of a package when it's served to a browser in different ways (uncompressed, minified, gzipped).

Conclusion#

Webpack can consume most npm packages without a problem. Sometimes, though, patching is required using webpack's resolution mechanism.

To recap:

  • Use webpack's module resolution to your benefit. Sometimes you can work around issues by tweaking resolution. Often it's a good idea to try to push improvements upstream to the projects themselves, though.
  • Webpack allows you to patch resolved modules. Given certain dependencies expect globals, you can inject them. You can also expose modules as globals as this is necessary for certain development tooling to work.

In the next chapter, you'll learn to author npm packages. It's the other side of the same coin and worth understanding even if you don't end up authoring packages of your own.

Previous chapterConsuming Packages
Next chapterAuthoring Packages

This book is available through Leanpub (digital), Amazon (paperback), and Kindle (digital). By purchasing the book you support the development of further content. A part of profit (~30%) goes to Tobias Koppers, the author of webpack.

Need help?