Webpack 5 Documentation

Webpack 5 Documentation

Article Source: https://webpack.js.org/concepts/

Webpack is a static module bundler for modern JavaScript applications.

Concepts

Entry

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.

Single Entry (Shorthand) Syntax

Single Entry Syntax is useful for quickly setup a configuration for an application or tool with one entry point, such as a library.

module.exports = {
  entry: './src/file_1.js',
};

To inject multiple dependent files together, we could pass an array of file paths.

module.exports = {
  entry: ['./src/file_1.js', './src/file_2.js'],
  output: {
    filename: 'bundle.js',
  },
};

Object Syntax

Object syntax is the most scalable way of defining entry/entries in your application.

module.exports = {
  entry: {
    app: './src/app.js',
    adminApp: './src/adminApp.js',
  },
};

EntryDescription object

module.exports = {
  entry: {
    a2: 'dependingfile.js',
    b2: {
      dependOn: 'a2',
      import: './src/app.js',
    },
  },
};
  • dependOn: The entry points that the current entry point depends on. (Must not be circular)

  • filename: The name of each output file on disk.

  • import: Modules that are loaded upon startup.

  • library: Options for library.

Separate App and Vendor Entries

With this you can import required libraries that aren't modified (e.g. Bootstrap, jQuery, images, etc) inside vendor.js and the content hash remains the same, which allow the browser to cache them separately.

// webpack.config.js
module.exports = {
  entry: {
    main: './src/app.js',
    vendor: './src/vendor.js',
  },
  output: {
    filename: '[name].[contenthash].bundle.js',
  },
};

Multi Page Application

Use exactly one entry point for each HTML document.

module.exports = {
  entry: {
    pageOne: './src/pageOne/index.js',
    pageTwo: './src/pageTwo/index.js',
    pageThree: './src/pageThree/index.js',
  },
};

Output

Configuring the output configuration options tells webpack how to write the compiled files to disk.

module.exports = {
  output: {
    filename: 'bundle.js',
  },
};

This configuration would output a single bundle.js file into the dist directory.

Multiple Entry Points

module.exports = {
  entry: {
    app: './src/app.js',
    search: './src/search.js',
  },
  output: {
    filename: '[name].js',
    path: __dirname + '/dist',
  },
};

Loaders

Loaders are transformations that are applied to the source code of a module. They allow you to pre-process files as you import or "load" them. Loaders can transform files from a different language (like TypeScript) to JavaScript or load inline images as data URLs.

Example

To load CSS files and TypeScript files:

npm install --save-dev css-loader ts-loader

Instruct webpack to use the css-loader for every .css file and the ts-loader for all .ts files:

module.exports = {
  module: {
    rules: [
      { test: /\.css$/, use: 'css-loader' },
      { test: /\.ts$/, use: 'ts-loader' },
    ],
  },
};

Configuration

module.rules allows you to specify several loaders within your webpack configuration. Loaders are evaluated/executed from bottom to top.

Loader Features

  • Loaders can be chained. Each loader in the chain applies transformations to the processed resource.

  • Loaders can be configured with an options object.

  • Loaders run in Node.js and can do everything that’s possible there.

Plugins

Anatomy

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.

const pluginName = 'ConsoleLogOnBuildWebpackPlugin';

class ConsoleLogOnBuildWebpackPlugin {
  apply(compiler) {
    compiler.hooks.run.tap(pluginName, (compilation) => {
      console.log('The webpack build process is starting!!!');
    });
  }
}

module.exports = ConsoleLogOnBuildWebpackPlugin;

Usage

Since plugins can take arguments/options, you must pass a new instance to the plugins property in your webpack configuration.

Configuration

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  ...
  plugins: [
    new webpack.ProgressPlugin(),
    new HtmlWebpackPlugin({ template: './src/index.html' }),
  ],
}

Modules

What is a webpack Module

  • An ES2015 import statement

  • A CommonJS require() statement

  • An AMD define and require statement

  • An @import statement inside of a css/sass/less file.

  • An image url in a stylesheet url(...) or HTML <img src=...> file.

Supported Module Types

  • ECMAScript modules

  • CommonJS modules

  • AMD modules

  • Assets

  • WebAssembly modules

Module Resolution

A resolver is a library which helps in locating a module by its absolute path.

import foo from 'path/to/module';

require('path/to/module');

Resolving rules

  • Absolute paths

import '/home/me/file';
  • Relative paths

import '../src/file1';
  • Module paths

import 'module';

Dependency Graph

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.

When webpack processes your application, it starts from a list of modules defined on the command line or in its configuration 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.

Targets

module.exports = {
  target: 'node',
};

Available targets: https://webpack.js.org/configuration/target/

The Manifest

In a typical application or site built with webpack, there are three main types of code:

  • The source code

  • Any third-party library or "vendor" code

    A webpack runtime and manifest that conducts the interaction of all modules

Runtime

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.

Manifest

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."

The "Manifest" is 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.

The Problem

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.

However, 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.

Hot Module Replacement

Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running, without a full reload.

  • Retain application state which is lost during a full reload.

  • Save valuable development time by only updating what's changed.

  • Instantly update the browser when modifications are made to CSS/JS in the source code.

In the Application

  • The application asks the HMR runtime to check for updates.

  • The runtime asynchronously downloads the updates and notifies the application.

  • The application then asks the runtime to apply the updates.

  • The runtime synchronously applies the updates.

In the Compiler

In addition to normal assets, the compiler needs to emit an "update" to allow updating from the previous version to the new version.

  • The updated manifest (JSON)

  • One or more updated chunks (JavaScript)

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.

The compiler ensures that module IDs and chunk IDs are consistent between these builds.

In a Module

HMR is an opt-in feature that only affects modules containing HMR code. When implementing the HMR interface in a module, you can describe what should happen when the module is updated.

In the Runtime

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 parents. Otherwise, the invalid flag bubbles up and invalidates parents as well. Each bubble continues until the app's entry point or a module with an update handler is reached. If it bubbles up from an entry point, the process fails.

Afterwards, all invalid modules are disposed 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.

Under The Hood

The bundling is a function that takes some files and emits others. Between input and output, it also has modules, entry points, chunks, chunk groups, and many other intermediate parts.

The main parts

Every file used in your project is a Module.

By using each other, the modules form a graph (ModuleGraph).

During the bundling process, modules are combined into chunks. Chunks combine into chunk groups and form a graph (ChunkGraph) interconnected through modules. When you describe an entry point - under the hood, you create a chunk group with one chunk.

module.exports = {
  entry: {
    home: './home.js',
    about: './about.js',
  },
};

Two chunk groups with names home and about are created. Each of them has a chunk with a module - ./home.js for home and ./about.js for about.

Chunks

initial is the main chunk for the entry point. This chunk contains all the modules and its dependencies that you specify for an entry point.

non-initial is a chunk that may be lazy-loaded. It may appear when dynamic import or SplitChunksPlugin is being used.

Each chunk has a corresponding asset. The assets are the output files - the result of bundling.

Example

module.exports = {
  entry: './src/index.jsx',
};
import React from 'react';
import ReactDOM from 'react-dom';

import('./app.jsx').then((App) => {
  ReactDOM.render(<App />, root);
});

Initial chunk with name main is created. It contains:

  • ./src/index.jsx

  • react

  • react-dom

and all their dependencies, except ./app.jsx

Non-initial chunk for ./app.jsx is created as this module is imported dynamically.

Output:

/dist/main.js - an initial chunk
/dist/394.js - non-initial chunk

By default, there is no name for non-initial chunks so that a unique ID is used instead of a name.

Output

The names of the output files are affected by the two fields in the config:

  • output.filename - for initial chunk files

  • output.chunkFilename - for non-initial chunk files

A few placeholders are available in these fields. Most often:

  • [id] - chunk id ([id].js -> 485.js)

  • [name] - chunk name ([name].js -> app.js). If a chunk has no name, then its id will be used.

  • [contenthash] - md4-hash of the output file content ( [contenthash].js -> 4ea6ff1de66c537eb9b2.js)

Last updated