Newbie Learning | In-depth Explanation of Webpack Documentation 01

97 阅读8分钟

In this article, I will extract and explain certain concepts and usage of webpack to enhance my own understanding. All references are sourced from the official website of webpack.

What's Webpack?

Webpack is a static module bundler for modern JS applications. When webpack processes your application, it internally builds a dependency graph from one or more entry points and then combines every module your project needs into one or more bundles.

Concepts

There're six Core Concepts of webpack:

I'll explain them further, each one separately.

Entry

An entry point indicates which module webpack should use to begin building out its internal dependency graph.

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',
};

Output

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'),//Store the output file in the dist directory of the current file, 'path.resolve()' is a method in Node.js that resolves(解析) paths
    filename: 'my-first-webpack.bundle.js',//Output 'my-first-webpack.bundle.js' in the dist directory
  },
};

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.

Loaders

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.

In my own words, loaders can transform files into JavaScript or JSON format.

loaders have two properties in your webpack configuration:

  1. The test property identifies which file or files should be transformed.
  2. The use property indicates which loader should be used to do the transforming.

For example:

webpack.config.js

const path = require('path');

module.exports = {
  output: {
    filename: 'my-first-webpack.bundle.js',
  },
  module: {
    rules: [{ test: /.txt$/, use: 'raw-loader' }],// '/' is used to indicate the beginning and end of a regex. '\' is used for escaping, as the dot '.' has a special meaning, representing any single character. '$' indicates the end of a string. In this regex, $ indicates that the string ends with '.txt'. Remember not to enclose `/.txt$/` in quotes!
  },
};

Plugins

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.

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin'); //a plugin to generate HTML format automatically
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' })], //the location of a template file, when webpack builds a project, the HtmlWebpackPlugin generates a new HTML file based on this template file
};

Mode

By setting the mode parameter to either developmentproduction(生产环境) or none, you can enable webpack's built-in optimizations that correspond to each environment. The default value is production.

Development(开发环境) is usually used during the development phase of a project.The code in the development environment is not minimized, compressed, or obfuscated(混淆) so that developers can debug and view the code.

Production(生产环境) is usually used after the project is officially deployed online.

In this environment, some development tools and debugging tools are usually closed to reduce the size of the code and improve the page load speed.

Code in Production is often minimized, compressed, and obfuscated to reduce file size and increase load speed.

The configuration of the Production may also include some performance optimization strategies, such as using CDN, server side caching, etc., to improve user experience and site performance.

module.exports = {
  mode: 'production',
};

Browser Compatibility

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.

Environment

Webpack 5 runs on Node.js version 10.13.0+.

Entry Points

Single Entry (Shorthand) Syntax

Usage: entry: string | [string] //This is a representation of TS, indicating that the entry property can be either a string or an array of strings

  1. For a string, there are two ways to write it, one for each situation:

webpack.config.js

module.exports = {
  entry: './path/to/my/entry/file.js',
};

webpack.config.js

module.exports = {
  entry: {
    main: './path/to/my/entry/file.js',
  },
};

We can also pass an array of file paths to the entry property which 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".

  1. Here's the configuration for an array of strings:

webpack.config.js

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

Single Entry Syntax is a great choice when you are looking to quickly set up 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.

Object Syntax

Usage: entry: { <entryChunkName> string | [string] } | {}

What's entryChunk?

EntryChunks are the starting point of webpack construction, and they define the root nodes of the module dependency tree that webpack needs to build.

When webpack starts building, it starts with these entrychunks and gradually builds the packaged file of the entire application based on the dependencies between modules.

About the TS statement

<entryChunkName> is a Generics(泛型). <entryChunkName> string | [string] indicates a key-value pair where the key is (a placeholder for the name of the entrychunk) and the value can be a string or an array of strings.

p.s. When you use object literals(对象字面量, means that use curly braces {} to define an object) to declare an object, you can omit colons(冒号) in key-value pairs. Because TS can automatically infer the type of the key and the type of the value based on the value you provide.

You can pass empty object {} to entry when you have only entry points generated by plugins.

webpack.config.js

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

EntryDescription object

An object of entry point description. You can specify the following properties.

  • dependOn: The entry points that the current entry point depends on. They must be loaded before this entry point is loaded.
  • filename: Specifies the name of each output file on disk.
  • import: Module(s) that are loaded upon startup.
  • library: Specify library options to bundle a library from current entry.
  • runtime: The name of the runtime chunk. When set, a new runtime chunk will be created. It can be set to false to avoid a new runtime chunk since webpack 5.43.0.
  • publicPath: Specify a public URL address for the output files of this entry when they are referenced in a browser. Also, see output.publicPath.

webpack.config.js

//This code is a CommonJS module export statement(表述), which is commonly used to define module exports in tools such as Node.js or webpack.
module.exports = {
  entry: {
    a2: 'dependingfile.js',
    b2: {
      dependOn: 'a2',
      import: './src/app.js',
    },
  },
};

Remember these following tips:

  1. runtime and dependOn should not be used together on a single entry.

  2. Make sure runtime must not point to an existing entry point name.(运行中的模块不能和现有入口名重名)

  3. dependOn must not be circular(循环的).

Scenarios(脚本)

Separate App and Vendor Entries 单独的入口加快浏览器加载速度

以下代码是一个基本的 webpack 配置示例,分为三个文件:webpack.config.jswebpack.prod.jswebpack.dev.js,分别用于配置 webpack 在不同环境下的运行:

webpack.config.js

entry 属性指定了 webpack 的入口文件,其中 mainvendor 是两个入口 chunk 的名称,分别对应 ./src/app.js./src/vendor.js 这两个入口文件。

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

webpack.prod.js

output 属性指定了 webpack 打包后输出的文件名格式,使用了占位符 [name][contenthash],其中 [name] 表示入口 chunk 的名称,[contenthash] 表示根据文件内容生成的 hash 值,用于缓存控制。

p.s. webpack 的配置语法使用方括号 [] 来表示占位符

module.exports = {
  output: {
    filename: '[name].[contenthash].bundle.js',
  },
};

webpack.dev.js

定义了 output 属性,但只使用了占位符 [name],没有使用 [contenthash],这意味着在开发环境下生成的文件名不包含 hash 值,方便调试和开发。

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

What does this do?  We are telling webpack that we would like 2 separate entry points (like the above example).

Why?  With this, you can import required libraries or files that aren't modified (e.g. Bootstrap, jQuery, images, etc) inside vendor.js and they will be bundled together into their own chunk. Content hash remains the same, which allows the browser to cache them separately thereby reducing load time. 这使得浏览器能够单独缓存它们,从而减少加载时间。

但在Wp4中,已经不需要为vendor单独创造入口点,通常情况下,只需要指定一个主入口点(例如 main: './src/app.js'),然后使用 optimization.splitChunks 选项来自动分离 vendors 和应用程序模块,并将它们打包到单独的文件中。

Multi-Page Application

webpack.config.js

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

在多页面应用中,服务器会为每个页面提供一个独立的 HTML 文档。当用户访问不同页面时,浏览器会加载相应的 HTML 文档,并下载其中包含的资源文件。这样的设计使得我们可以利用 webpack 的优化功能(如 optimization.splitChunks)来创建共享应用程序代码的捆绑包。通过在多个入口点之间共享代码,可以减少每个页面需要加载的文件数量,从而提高页面加载速度和性能。

延申:

多页面应用(MPA)和单页面应用(SPA)各适用于哪些不同的场景?

多页面应用(MPA)适用于:

  1. 需要独立的页面,每个页面都有自己的 HTML 文档和独立的 URL。
  2. 对 SEO(搜索引擎优化)友好,因为每个页面都有自己的 URL,可以被搜索引擎索引。
  3. 需要独立加载和渲染的页面,每次页面切换时需要重新加载整个页面。
  4. 传统的 Web 应用程序,如电子商务网站、新闻门户、博客等。

单页面应用(SPA)适用于:

  1. 需要动态交互和无缝用户体验的应用程序。
  2. 需要快速响应用户操作,避免页面刷新和重新加载。
  3. 需要通过 AJAX 或 WebSocket 等技术来实现实时更新和数据交换的应用程序。
  4. 对于内容较少的应用,可以在客户端实现路由、状态管理和页面渲染等功能。
  5. 需要在移动端和桌面端提供类似应用体验的应用程序,如在线工具、社交网络应用、单页游戏等。

Output

Note that, while there can be multiple entry points, only one output configuration is specified. 多个输入,一个输出

Usage

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) 将值设为对象 并使用output.filename进行输出:

webpack.config.js

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

This configuration would output a single bundle.js file into the dist directory.  bundle.js会在dist目录下

However, when creating multiple bundles via more than one entry point, code splitting, or various plugins, you should use one of the following substitutions to give each bundle a unique name... 当有多个入口点等特殊情况时

Using entry name:

webpack.config.js

module.exports = {
  //...
  output: {
    filename: '[name].bundle.js',
  },
};

Using internal chunk id:

webpack.config.js

module.exports = {
  //...
  output: {
    filename: '[id].bundle.js',
  },
};

Using hashes generated from the generated content:

webpack.config.js

module.exports = {
  //...
  output: {
    filename: '[contenthash].bundle.js',
  },
};

Combining multiple substitutions:

webpack.config.js

module.exports = {
  //...
  output: {
    filename: '[name].[contenthash].bundle.js',
  },
};

Using the function to return the filename:

webpack.config.js

//
以下代码表示,对于入口点为 `'main'` 的 chunk,它会生成一个文件名为 `[name].js` 的文件,而对于其他入口点生成的 chunk,则会生成一个目录,目录名为入口点的名称,里面包含一个同名的 JS 文件。
module.exports = {
  //...
  output: {
    filename: (pathData) => {
      return pathData.chunk.name === 'main' ? '[name].js' : '[name]/[name].js';
    },
  },
};

tbc.