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:
const config = {
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 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.
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.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:
const config = { resolve: { modules: ["demo", "node_modules"] } };
After the change, webpack will try to look into the _mymodules directory first. The method can be applicable in large projects where you want to customize behavior.
resolve.extensions
#By default, webpack will resolve only against .js
, .mjs
, and .json
files while importing without an extension, to tune this to include JSX files, adjust as below:
const config = { resolve: { extensions: [".js", ".jsx"] } };
resolve.plugins
#resolve.plugins
field allows you to customize the way webpack resolves modules. directory-named-webpack-plugin is a good example as it's mapping import foo from "./foo";
to import 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.
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:
const config = { 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>
Starting from webpack 5, the tool supports externalsType field to customize the loading behavior. For example, using"promise"
string as its value would load the externals asynchronously and"import"
would use browserimport()
to load the externals. This can be configured per external as well instead of using a global setting. To load jQuery asynchronously, you would set it to["jquery", "promise"]
in the example above.
Sometimes modules depend on globals. $
provided by jQuery is a good example. Webpack offers a few ways that allow you to handle them.
imports-loader allows you to inject globals to modules. In the example below, import $ from 'jquery';
is injected as a global to each:
const config = {
module: {
rules: [
{
test: /\.js$/,
loader: "imports-loader",
options: {
imports: ["default jquery $"],
},
},
],
},
};
Webpack's ProvidePlugin
allows webpack to resolve globals as it encounters them:
const config = {
plugins: [new webpack.ProvidePlugin({ $: "jquery" })],
};
Sometimes you have to expose packages to third-party scripts. expose-loader allows this as follows:
const config = {
test: require.resolve("react"),
loader: "expose-loader",
options: {
exposes: ["React"],
},
};
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.
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 issue #985. Webpack core behavior may improve in the future to make a workaround unnecessary. You can disable webpack's symlink handling by setting resolve.symlinks
as false
.
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:
const config = {
plugins: [
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/,
}),
],
};
You can use the same mechanism to work around problematic dependencies. Example: new webpack.IgnorePlugin({ resourceRegExp: /^(buffertools)$/ })
.
To bring specific locales to your project, you should use ContextReplacementPlugin
:
const config = {
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
.
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:
const config = {
module: { noParse: /node_modules\/demo\/index.js/ },
};
Take care when disabling warnings as it can hide underlying issues. Consider alternatives first. There's a webpack issue that discusses the problem.
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.
Webpack can consume most npm packages without a problem. Sometimes, though, patching is required using webpack's resolution mechanism.
To recap:
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.