At its core, webpack is a static module bundler for modern JavaScript applications. When webpack processes your application, it internally builds a dependency graph which maps every module your project needs and generates one or more bundles.
Learn more about JavaScript modules and webpack modules here.
Since version 4.0.0, webpack does not require a configuration file to bundle your project. Nevertheless, it is incredibly configurable to better fit your needs.
To get started you only need to understand its Core Concepts:
This document is intended to give a high-level overview of these concepts, while providing links to detailed concept-specific use cases.
For a better understanding of the ideas behind module bundlers and how they work under the hood, consult these resources:
An entry point indicates which module webpack should use to begin building out its internal dependency graph. webpack will figure out which other modules and libraries that entry point depends on (directly and indirectly).
By default its value is ./src/index.js
, but you can specify a different (or multiple entry points) by setting an entry
property in the webpack configuration. For example:
webpack.config.js
module.exports = {
entry: './path/to/my/entry/file.js'
};
Learn more in the entry points section.
The output property tells webpack where to emit the bundles it creates and how to name these files. It defaults to ./dist/main.js
for the main output file and to the ./dist
folder for any other generated file.
You can configure this part of the process by specifying an output
field in your configuration:
webpack.config.js
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
}
};
In the example above, we use the output.filename
and the output.path
properties to tell webpack the name of our bundle and where we want it to be emitted to. In case you're wondering about the path module being imported at the top, it is a core Node.js module that gets used to manipulate file paths.
The
output
property has many more configurable features. If you want to learn about the concepts behind it, you can read more in the output section.
Out of the box, webpack only understands JavaScript and JSON files. Loaders allow webpack to process other types of files and convert them into valid modules that can be consumed by your application and added to the dependency graph.
Note that the ability to
import
any type of module, e.g..css
files, is a feature specific to webpack and may not be supported by other bundlers or task runners. We feel this extension of the language is warranted as it allows developers to build a more accurate dependency graph.
At a high level, loaders have two properties in your webpack configuration:
test
property identifies which file or files should be transformed.use
property indicates which loader should be used to do the transforming.webpack.config.js
const path = require('path');
module.exports = {
output: {
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
}
};
The configuration above has defined a rules
property for a single module with two required properties: test
and use
. This tells webpack's compiler the following:
"Hey webpack compiler, when you come across a path that resolves to a '.txt' file inside of a
require()
/import
statement, use theraw-loader
to transform it before you add it to the bundle."
It is important to remember that when defining rules in your webpack config, you are defining them under
module.rules
and notrules
. For your benefit, webpack will warn you if this is done incorrectly.
Keep in mind that when using regex to match files, you may not quote it. i.e
/\.txt$/
is not the same as'/\.txt$/'
or"/\.txt$/"
. The former instructs webpack to match any file that ends with .txt and the latter instructs webpack to match a single file with an absolute path '.txt'; this is likely not your intention.
You can check further customization when including loaders in the loaders section.
While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset management and injection of environment variables.
Check out the plugin interface and how to use it to extend webpack's capabilities.
In order to use a plugin, you need to require()
it and add it to the plugins
array. Most plugins are customizable through options. Since you can use a plugin multiple times in a config for different purposes, you need to create an instance of it by calling it with the new
operator.
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
const webpack = require('webpack'); //to access built-in plugins
module.exports = {
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
},
plugins: [
new HtmlWebpackPlugin({template: './src/index.html'})
]
};
In the example above, the html-webpack-plugin
generates an HTML file for your application by injecting automatically all your generated bundles.
There are many plugins that webpack provides out of the box! Check out the list of plugins.
Using plugins in your webpack config is straightforward. However, there are many use cases that are worth further exploration. Learn more about them here.
By setting the mode
parameter to either development
, production
or none
, you can enable webpack's built-in optimizations that correspond to each environment. The default value is production
.
module.exports = {
mode: 'production'
};
Learn more about the mode configuration here and what optimizations take place on each value.
webpack supports all browsers that are ES5-compliant (IE8 and below are not supported). webpack needs Promise
for import()
and require.ensure()
. If you want to support older browsers, you will need to load a polyfill before using these expressions.
As mentioned in Getting Started, there are multiple ways to define the entry
property in your webpack configuration. We will show you the ways you can configure the entry
property, in addition to explaining why it may be useful to you.
Usage: entry: string | [string]
webpack.config.js
module.exports = {
entry: './path/to/my/entry/file.js'
};
The single entry syntax for the entry
property is a shorthand for:
webpack.config.js
module.exports = {
entry: {
main: './path/to/my/entry/file.js'
}
};
What happens when you pass an array to
entry
? Passing an array of file paths to theentry
property creates what is known as a "multi-main entry". This is useful when you would like to inject multiple dependent files together and graph their dependencies into one "chunk".
This is a great choice when you are looking to quickly setup a webpack configuration for an application or tool with one entry point (i.e. a library). However, there is not much flexibility in extending or scaling your configuration with this syntax.
Usage: entry: { <entryChunkName> string | [string] }
webpack.config.js
module.exports = {
entry: {
app: './src/app.js',
adminApp: './src/adminApp.js'
}
};
The object syntax is more verbose. However, this is the most scalable way of defining entry/entries in your application.
"Scalable webpack configurations" are ones that can be reused and combined with other partial configurations. This is a popular technique used to separate concerns by environment, build target, and runtime. They are then merged using specialized tools like webpack-merge.
Below is a list of entry configurations and their real-world use cases:
In webpack version < 4 it was common to add vendors as a separate entry point to compile it as a separate file (in combination with the
CommonsChunkPlugin
).
This is discouraged in webpack 4. Instead, theoptimization.splitChunks
option takes care of separating vendors and app modules and creating a separate file. Do not create an entry for vendors or other stuff that is not the starting point of execution.
webpack.config.js
module.exports = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
}
};
What does this do? We are telling webpack that we would like 3 separate dependency graphs (like the above example).
Why? In a multi-page application, the server is going to fetch a new HTML document for you. The page reloads this new document and assets are redownloaded. However, this gives us the unique opportunity to do things like using optimization.splitChunks
to create bundles of shared application code between each page. Multi-page applications that reuse a lot of code/modules between entry points can greatly benefit from these techniques, as the number of entry points increases.
As a rule of thumb: Use exactly one entry point for each HTML document.
Configuring the output
configuration options tells webpack how to write the compiled files to disk. Note that, while there can be multiple entry
points, only one output
configuration is specified.
The minimum requirement for the output
property in your webpack configuration is to set its value to an object and provide an output.filename
to use for the output file(s):
webpack.config.js
module.exports = {
output: {
filename: 'bundle.js',
}
};
This configuration would output a single bundle.js
file into the dist
directory.
If your configuration creates more than a single "chunk" (as with multiple entry points or when using plugins like CommonsChunkPlugin), you should use substitutions to ensure that each file has a unique name.
module.exports = {
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
};
// writes to disk: ./dist/app.js, ./dist/search.js
Here's a more complicated example of using a CDN and hashes for assets:
config.js
module.exports = {
//...
output: {
path: '/home/proj/cdn/assets/[hash]',
publicPath: 'https://cdn.example.com/assets/[hash]/'
}
};
In cases where the eventual publicPath
of output files isn't known at compile time, it can be left blank and set dynamically at runtime via the __webpack_public_path__
variable in the entry point file:
__webpack_public_path__ = myRuntimePublicPath;
// rest of your application entry
Loaders are transformations that are applied on the source code of a module. They allow you to pre-process files as you import
or “load” them. Thus, loaders are kind of like “tasks” in other build tools and provide a powerful way to handle front-end build steps. Loaders can transform files from a different language (like TypeScript) to JavaScript or inline images as data URLs. Loaders even allow you to do things like import
CSS files directly from your JavaScript modules!
For example, you can use loaders to tell webpack to load a CSS file or to convert TypeScript to JavaScript. To do this, you would start by installing the loaders you need:
npm install --save-dev css-loader ts-loader
And then instruct webpack to use the css-loader
for every .css
file and the ts-loader
for all .ts
files:
webpack.config.js
module.exports = {
module: {
rules: [
{ test: /\\.css$/, use: 'css-loader' },
{ test: /\\.ts$/, use: 'ts-loader' }
]
}
};
There are three ways to use loaders in your application:
import
statement.module.rules
allows you to specify several loaders within your webpack configuration.
This is a concise way to display loaders, and helps to maintain clean code. It also offers you a full overview of each respective loader.
Loaders are evaluated/executed from right to left (or from bottom to top). In the example below execution starts with sass-loader, continues with css-loader and finally ends with style-loader. See "Loader Features" for more information about loaders order.
module.exports = {
module: {
rules: [
{
test: /\\.css$/,
use: [
// [style-loader](/loaders/style-loader)
{ loader: 'style-loader' },
// [css-loader](/loaders/css-loader)
{
loader: 'css-loader',
options: {
modules: true
}
},
// [sass-loader](/loaders/sass-loader)
{ loader: 'sass-loader' }
]
}
]
}
};
It's possible to specify loaders in an import
statement, or any equivalent "importing" method. Separate loaders from the resource with !
. Each part is resolved relative to the current directory.
import Styles from 'style-loader!css-loader?modules!./styles.css';
It's possible to override any loaders, preLoaders and postLoaders from the configuration by prefixing the inline import
statement:
!
will disable all configured normal loadersimport Styles from '!style-loader!css-loader?modules!./styles.css';
!!
will disable all configured loaders (preLoaders, loaders, postLoaders)import Styles from '!!style-loader!css-loader?modules!./styles.css';
-!
will disable all configured preLoaders and loaders but not postLoadersimport Styles from '-!style-loader!css-loader?modules!./styles.css';
Options can be passed with a query parameter, e.g. ?key=value&foo=bar
, or a JSON object, e.g. ?{"key":"value","foo":"bar"}
.
Use
module.rules
whenever possible, as this will reduce boilerplate in your source code and allow you to debug or locate a loader faster if something goes south.
You can also use loaders through the CLI:
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
This uses the jade-loader
for .jade
files, and the style-loader
and css-loader
for .css
files.
options
object (using query
parameters to set options is still supported but has been deprecated).main
via package.json
with the loader
field.Loaders provide a way to customize the output through their preprocessing functions. Users now have more flexibility to include fine-grained logic such as compression, packaging, language translations and more.
Loaders follow the standard module resolution. In most cases it will be loaded from the module path (think npm install
, node_modules
).
A loader module is expected to export a function and be written in Node.js compatible JavaScript. They are most commonly managed with npm, but you can also have custom loaders as files within your application. By convention, loaders are usually named xxx-loader
(e.g. json-loader
). See "Writing a Loader" for more information.
Plugins are the backbone of webpack. webpack itself is built on the same plugin system that you use in your webpack configuration!
They also serve the purpose of doing anything else that a loader cannot do.
A webpack plugin is a JavaScript object that has an apply
method. This apply
method is called by the webpack compiler, giving access to the entire compilation lifecycle.
ConsoleLogOnBuildWebpackPlugin.js
const pluginName = 'ConsoleLogOnBuildWebpackPlugin';
class ConsoleLogOnBuildWebpackPlugin {
apply(compiler) {
compiler.hooks.run.tap(pluginName, compilation => {
console.log('The webpack build process is starting!!!');
});
}
}
module.exports = ConsoleLogOnBuildWebpackPlugin;
The first parameter of the tap method of the compiler hook should be a camelized version of the plugin name. It is advisable to use a constant for this so it can be reused in all hooks.
Since plugins can take arguments/options, you must pass a new
instance to the plugins
property in your webpack configuration.
Depending on how you are using webpack, there are multiple ways to use plugins.
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
const webpack = require('webpack'); //to access built-in plugins
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
filename: 'my-first-webpack.bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: 'babel-loader'
}
]
},
plugins: [
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
]
};
When using the Node API, you can also pass plugins via the plugins
property in the configuration.
some-node-script.js
const webpack = require('webpack'); //to access webpack runtime
const configuration = require('./webpack.config.js');
let compiler = webpack(configuration);
new webpack.ProgressPlugin().apply(compiler);
compiler.run(function(err, stats) {
// ...
});
Did you know: The example seen above is extremely similar to the webpack runtime itself! There are lots of great usage examples hiding in the webpack source code that you can apply to your own configurations and scripts!
You may have noticed that few webpack configurations look exactly alike. This is because webpack's configuration file is a JavaScript file that exports a webpack configuration. This configuration is then processed by webpack based upon its defined properties.
Because it's a standard Node.js CommonJS module, you can do the following:
require(...)
require(...)
?:
operatorUse these features when appropriate.
While they are technically feasible, the following practices should be avoided:
--env
)The most important part to take away from this document is that there are many different ways to format and style your webpack configuration. The key is to stick with something consistent that you and your team can understand and maintain.
The examples below describe how webpack's configuration can be both expressive and configurable because it is code:
webpack.config.js
var path = require('path');
module.exports = {
mode: 'development',
entry: './foo.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'foo.bundle.js'
}
};
See: Configuration section for all supported configuration options
Along with exporting a single configuration as an object, function or Promise, you can export multiple configurations.
See: Exporting multiple configurations
webpack accepts configuration files written in multiple programming and data languages.
In modular programming, developers break programs up into discrete chunks of functionality called a module.
Each module has a smaller surface area than a full program, making verification, debugging, and testing trivial. Well-written modules provide solid abstractions and encapsulation boundaries, so that each module has a coherent design and a clear purpose within the overall application.
Node.js has supported modular programming almost since its inception. On the web, however, support for modules has been slow to arrive. Multiple tools exist that support modular JavaScript on the web, with a variety of benefits and limitations. webpack builds on lessons learned from these systems and applies the concept of modules to any file in your project.
In contrast to Node.js modules, webpack modules can express their dependencies in a variety of ways. A few examples are:
import
statementrequire()
statementdefine
and require
statement@import
statement inside of a css/sass/less file.url(...)
or HTML <img src=...>
file.webpack supports modules written in a variety of languages and preprocessors, via loaders. Loaders describe to webpack how to process non-JavaScript modules and include these dependencies into your bundles. The webpack community has built loaders for a wide variety of popular languages and language processors, including:
And many others! Overall, webpack provides a powerful and rich API for customization that allows one to use webpack for any stack, while staying non-opinionated about your development, testing, and production workflows.
For a full list, see the list of loaders or write your own.
A resolver is a library which helps in locating a module by its absolute path. A module can be required as a dependency from another module as:
import foo from 'path/to/module';
// or
require('path/to/module');
The dependency module can be from the application code or a third-party library. The resolver helps
webpack find the module code that needs to be included in the bundle for every such require
/import
statement.
webpack uses enhanced-resolve to resolve file paths while bundling modules.
Using enhanced-resolve
, webpack can resolve three kinds of file paths:
import '/home/me/file';
import 'C:\\Users\\me\\file';
Since we already have the absolute path to the file, no further resolution is required.
import '../src/file1';
import './file2';
In this case, the directory of the resource file where the import
or require
occurs is taken to be the context directory. The relative path specified in the import/require
is joined to this context path to produce the absolute path to the module.
import 'module';
import 'module/lib/file';
Modules are searched for inside all directories specified in resolve.modules
.
You can replace the original module path by an alternate path by creating an alias for it using the resolve.alias
configuration option.
Once the path is resolved based on the above rule, the resolver checks to see if the path points to a file or a directory. If the path points to a file:
resolve.extensions
option, which tells the resolver which extensions are acceptable for resolution e.g. .js
, .jsx
.If the path points to a folder, then the following steps are taken to find the right file with the right extension:
package.json
file, then fields specified in resolve.mainFields
configuration option are looked up in order, and the first such field in package.json
determines the file path.package.json
or if the resolve.mainFields
do not return a valid path, file names specified in the resolve.mainFiles
configuration option are looked for in order, to see if a matching filename exists in the imported/required directory .resolve.extensions
option.webpack provides reasonable defaults for these options depending on your build target.
This follows the same rules as those specified for file resolution. But the resolveLoader
configuration option can be used to have separate resolution rules for loaders.
Every filesystem access is cached, so that multiple parallel or serial requests to the same file occur faster. In watch mode, only modified files are evicted from the cache. If watch mode is off, then the cache gets purged before every compilation.
See Resolve API to learn more on the configuration options mentioned above.
Any time one file depends on another, webpack treats this as a dependency. This allows webpack to take non-code assets, such as images or web fonts, and also provide them as dependencies for your application.
When webpack processes your application, it starts from a list of modules defined on the command line or in its config file. Starting from these entry points, webpack recursively builds a dependency graph that includes every module your application needs, then bundles all of those modules into a small number of bundles - often, just one - to be loaded by the browser.
Bundling your application is especially powerful for HTTP/1.1 clients, as it minimizes the number of times your app has to wait while the browser starts a new request. For HTTP/2, you can also use Code Splitting to achieve best results.
Because JavaScript can be written for both server and browser, webpack offers multiple deployment targets that you can set in your webpack configuration.
The webpack
target
property is not to be confused with theoutput.libraryTarget
property. For more information see our guide on theoutput
property.
To set the target
property, you simply set the target value in your webpack config:
webpack.config.js
module.exports = {
target: 'node'
};
In the example above, using node
webpack will compile for usage in a Node.js-like environment (uses Node.js require
to load chunks and not touch any built in modules like fs
or path
).
Each target has a variety of deployment/environment specific additions, support to fit its needs. See what targets are available.
Further expansion for other popular target values
Although webpack does not support multiple strings being passed into the target
property, you can create an isomorphic library by bundling two separate configurations:
webpack.config.js
const path = require('path');
const serverConfig = {
target: 'node',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'lib.node.js'
}
//…
};
const clientConfig = {
target: 'web', // <=== can be omitted as default is 'web'
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'lib.js'
}
//…
};
module.exports = [ serverConfig, clientConfig ];
The example above will create a lib.js
and lib.node.js
file in your dist
folder.
As seen from the options above, there are multiple deployment targets that you can choose from. Below is a list of examples and resources that you can refer to.
Need to find up to date examples of these webpack targets being used in live code or boilerplates.
In a typical application or site built with webpack, there are three main types of code:
This article will focus on the last of these three parts: the runtime and, in particular, the manifest.
The runtime, along with the manifest data, is basically all the code webpack needs to connect your modularized application while it's running in the browser. It contains the loading and resolving logic needed to connect your modules as they interact. This includes connecting modules that have already been loaded into the browser as well as logic to lazy-load the ones that haven't.
Once your application hits the browser in the form of index.html
file, some bundles and a variety of other assets required by your application must be loaded and linked somehow. That /src
directory you meticulously laid out is now bundled, minified and maybe even split into smaller chunks for lazy-loading by webpack's optimization
. So how does webpack manage the interaction between all of your required modules? This is where the manifest data comes in...
As the compiler enters, resolves, and maps out your application, it keeps detailed notes on all your modules. This collection of data is called the "Manifest," and it's what the runtime will use to resolve and load modules once they've been bundled and shipped to the browser. No matter which module syntax you have chosen, those import
or require
statements have now become __webpack_require__
methods that point to module identifiers. Using the data in the manifest, the runtime will be able to find out where to retrieve the modules behind the identifiers.
So now you have a little bit of insight about how webpack works behind the scenes. "But, how does this affect me?", you might ask. The simple answer is that most of the time it doesn't. The runtime will do its thing, utilizing the manifest, and everything will appear to just magically work once your application hits the browser. However, if you decide to improve the performance of your projects by utilizing browser caching, this process will all of a sudden become an important thing to understand.
By using content hashes within your bundle file names, you can indicate to the browser when the content of a file has changed, thus invalidating the cache. Once you start doing this though, you'll immediately notice some funny behavior. Certain hashes change even when their content apparently does not. This is caused by the injection of the runtime and manifest, which changes every build.
See the manifest section of our Output management guide to learn how to extract the manifest, and read the guides below to learn more about the intricacies of long term caching.
Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running, without a full reload. This can significantly speed up development in a few ways:
Let's go through some different viewpoints to understand exactly how HMR works...
The following steps allow modules to be swapped in and out of an application:
You can set up HMR so that this process happens automatically, or you can choose to require user interaction for updates to occur.
In addition to normal assets, the compiler needs to emit an "update" to allow updating from the previous version to the new version. The "update" consists of two parts:
The manifest contains the new compilation hash and a list of all updated chunks. Each of these chunks contains the new code for all updated modules (or a flag indicating that the module was removed).
The compiler ensures that module IDs and chunk IDs are consistent between these builds. It typically stores these IDs in memory (e.g. with webpack-dev-server), but it's also possible to store them in a JSON file.
HMR is an opt-in feature that only affects modules containing HMR code. One example would be patching styling through the style-loader
. In order for patching to work, the style-loader
implements the HMR interface; when it receives an update through HMR, it replaces the old styles with the new ones.
Similarly, when implementing the HMR interface in a module, you can describe what should happen when the module is updated. However, in most cases, it's not mandatory to write HMR code in every module. If a module has no HMR handlers, the update bubbles up. This means that a single handler can update a complete module tree. If a single module from the tree is updated, the entire set of dependencies is reloaded.
See the HMR API page for details on the module.hot
interface.
Here things get a bit more technical... if you're not interested in the internals, feel free to jump to the HMR API page or HMR guide.
For the module system runtime, additional code is emitted to track module parents
and children
. On the management side, the runtime supports two methods: check
and apply
.
A check
makes an HTTP request to the update manifest. If this request fails, there is no update available. If it succeeds, the list of updated chunks is compared to the list of currently loaded chunks. For each loaded chunk, the corresponding update chunk is downloaded. All module updates are stored in the runtime. When all update chunks have been downloaded and are ready to be applied, the runtime switches into the ready
state.
The apply
method flags all updated modules as invalid. For each invalid module, there needs to be an update handler in the module or in its parent(s). Otherwise, the invalid flag bubbles up and invalidates parent(s) as well. Each bubble continues until the app's entry point or a module with an update handler is reached (whichever comes first). If it bubbles up from an entry point, the process fails.
Afterwards, all invalid modules are disposed (via the dispose handler) and unloaded. The current hash is then updated and all accept
handlers are called. The runtime switches back to the idle
state and everything continues as normal.
HMR can be used in development as a LiveReload replacement. webpack-dev-server supports a hot
mode in which it tries to update with HMR before trying to reload the whole page. See the Hot Module Replacement guide for details.
As with many other features, webpack's power lies in its customizability. There are many ways of configuring HMR depending on the needs of a particular project. However, for most purposes,
webpack-dev-server
is a good fit and will allow you to get started with HMR quickly.
To understand why you should use webpack, let's recap how we used JavaScript on the web before bundlers were a thing.
There are two ways to run JavaScript in a browser. First, include a script for each functionality; this solution is hard to scale because loading too many scripts can cause a network bottleneck. The second option is to use a big .js
file containing all your project code, but this leads to problems in scope, size, readability and maintainability.
IIFEs solve scoping issues for large projects; when script files are wrapped by an IIFE, you can safely concatenate or safely combine files without worrying about scope collision.
The use of IIFEs led to tools like Make, Gulp, Grunt, Broccoli or Brunch. These tools are known as task runners, and they concatenate all your project files together.
However, changing one file means you have to rebuild the whole thing. Concatenating makes it easy to reuse scripts across files but makes build optimizations more difficult. How can you find out if code is actually being used or not?
Even if you only use a single function from lodash, you have to add the entire library and then squish it together. How do you treeshake the dependencies on your code? Lazy loading chunks of code can be hard to do at scale and requires a lot of manual work from the developer.
webpack runs on Node.js, a JavaScript runtime that can be used in computers and servers outside a browser environment.
When Node.js was released a new era started, and it came with new challenges. Now that JavaScript is not running in a browser, how are Node applications supposed to load new chunks of code? There are no html files and script tags that can be added to it.
CommonJS came out and introduced require
, which allows you to load and use a module in the current file. This solved scope issues out of the box by importing each module as it was needed.
JavaScript is taking over the world as a language, as a platform and as a way to rapidly develop and create fast applications.
But there is no browser support for CommonJS. There are no live bindings. There are problems with circular references. Synchronous module resolution and loading is slow. While CommonJS was a great solution for Node.js projects, browsers didn't support modules, so bundlers and tools like Browserify, RequireJS and SystemJS were created, allowing us to write CommonJS modules that run in a browser.
The good news for web projects is that modules are becoming an official feature in the ECMAScript standard. However, browser support is incomplete and bundling is still faster and currently recommended over these early module implementations.
...to have something that will not only let us write modules but also support any module format (at least until we get to ESM) and handle resources and assets at the same time?
This is why webpack exists. It's a tool that lets you bundle your JavaScript applications (supporting both ESM and CommonJS), and it can be extended to support many different assets such as images, fonts and stylesheets.
webpack cares about performance and load times; it's always improving or adding new features, such as async chunk loading and prefetching, to deliver the best possible experience for your project and your users.