Consuming Packages
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.
resolve.alias
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 method to adapt a RequireJS project to work with webpack.
resolve.extensions
By default, webpack will resolve only against .js
and .json
files while importing without an extension, to tune this to include JSX files, adjust as below:
{
resolve: {
extensions: [".js", ".json", ".jsx"],
},
},
resolve.modules
The module resolution process can be altered by changing where webpack looks for modules. By default, it will look only within the node_modules directory. If you want to override packages there, you could tell webpack to look into other directories first:
{
resolve: {
modules: ["my_modules", "node_modules"],
},
},
After the change, webpack will try to look into the my_modules directory first. The method can be applicable in large projects where you want to customize behavior.
resolve.plugins
Webpack allows you to customize the module resolution behavior using the resolve.plugins
field. Consider the following plugin examples:
- directory-named-webpack-plugin maps imports made against directories to files matching the directory name. For example, it would map
import foo from "./foo";
toimport foo from "./foo/foo.js";
. The pattern is popular with React and using the plugin will allow you to simplify your code. babel-plugin-module-resolver achieves the same behavior through Babel. - webpack-resolve-short-path-plugin was designed to avoid deeply nested imports like
import foo from "../../../foo";
by adding support for tilde (~
) syntax.import foo from "~foo"
would resolve against the project root if the plugin is used.
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"></scrip>
<scrip>
window.jQuery || document.write('<script src="js/jquery-3.1.1.min.js"><\/scrip>')
</scrip>
html-webpack-cdn-plugin is one option if you are using
HtmlWebpackPlugin
and want to inject ascript
tag automatically.
Dealing with Globals
Sometimes modules depend on globals. $
provided by jQuery is a good example. Webpack offers 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. See also Ivan Akulov's explanation of
ContextReplacementPlugin
.
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/,
},
},
W> 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 allows 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 as 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
asfalse
.
Getting Insights on Packages
To get more information, 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. Consider the following tools as well:
- 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 specific dependencies expect globals, you can inject them. You can also expose modules as globals as this is necessary for certain development tooling to work.