Webpack

概念

本质上,webpack 是一个现代 JavaScript 应用程序的 static module bundler(静态模块打包器)。当 webpack 处理应用程序时,它内置从 一个或多个入口 构建一个 依赖图(dependency graph),然后将项目所需的每个模块组合成一个或多个 bundles ,它们均是静态资源,为您的内容提供服务。

核心概念

为了更好理解模块打包器背后的理念及地层中如何运作的,请参考以下资源:

Entry

入口起点指示 webpack 用于开始构建内置 dependency graph 的模块。Webpack 将找出入口起点直接或间接以来的模块和库。

单入口(简写)语法

module.exports = {
  entry: "./path/to/my/entry/file.js", // 这是下面语法的简写
  entry: {
    main: './path/to/my/entry/file.js',
  },
}

当然,你可以传递一个 path[]entry 属性,这讲创建一个所谓的 "multi-main entry"。当你想将多个依赖一起注入并且将它们依赖关系绘制成一个 chunk

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

当您希望通过一个入口点(即库)快速为应用程序或工具设置 Webpack 配置时,单入口语法是一个不错的选择。但是,使用此语法扩展或扩展配置没有太大的灵活性。

对象语法

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

Tip
当您只有插件生成的入口点时,您可以将空对象 传递给 entry

更多信息点击查看

常见场景

分离 app(应用程序) 和 vendor(第三方库)入口

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

上述代码告诉 webpack 我们配置 2 个单独的入口点。
这样可以在 vendor.js 中存入未做修改的必要 library 或文件(例如 Bootstrap,jQuery,图片等),然后将它们打包在一起成为单独的chunk。内容哈希保持不变,这使浏览器可以独立地缓存它们,从而减少了加载时间。

提示:
在 webpack < 4 的版本中,通常将 vendor 作为一个单独的入口起点添加到 entry 选项中,以将其编译为一个单独的文件(与 CommonsChunkPlugin 结合使用)。

而在 webpack 4 中不鼓励这样做。而是使用 optimization.splitChunks 选项,将 vendor 和 app(应用程序) 模块分开,并为其创建一个单独的文件。不要 为 vendor 或其他不是执行起点创建 entry。

多页面应用程序

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

上面的示例中,告诉 webpack 需要三个独立分离的依赖图。

在多页面应用程序中,server 会拉取一个新的 HTML 文档给你的客户端。页面重新加载此新文档,并且资源被重新下载。然而,这给了我们特殊的机会去做很多事,例如使用 optimization.splitChunks 为页面间共享的应用程序代码创建 bundle。由于入口起点数量的增多,多页应用能够复用多个入口起点之间的大量代码/模块,从而可以极大地从这些技术中受益。

Output

Output 属性告诉 webpack 在哪里输出它创建的 bundles ,并决定如何命名它们。

注意,即使可以存在多个 entry 起点,但只能指定一个 output 配置。

高级用法

以下是对资源使用 CDN 和 hash 的复杂实例:

module.exports = {
  //...
  output: {
    path: '/home/proj/cdn/assets/[fullhash]',
    publicPath: 'https://cdn.example.com/assets/[fullhash]/',
  },
};

如果在编译时,不知道最终输出文件的 publicPath 是什么地址,则可以将其留空,并且在运行时通过入口起点文件中的 __webpack_public_path__ 动态设置。

__webpack_public_path__ = myRuntimePublicPath;

// 应用程序入口的其余部分

Loader

开箱即用,webpack 仅理解 JavaScript 和 JSON 文件。Loaders 允许 webpack 处理其他类型的文件,并转化为有效的  modules ,以供你的应用程序使用,且添加到 dependency graph 中。

Webpack 一个特殊的功能就是可以 import 任何类型的模块,例如.css文件,这在其他打包程序或任务执行器中可能并不支持。
官方认为这种语言扩展很有必要,这可以允许开发人员创建出更准确的依赖关系图。

loader 用于对模块的源代码进行转换。loader 可以使你在 import 或 "load(加载)" 模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的得力方式。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript 或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS 文件!

配置

Plugins

虽然 loaders 被用于转换某些类型的模块,plugins 可以用来执行更广泛的任务,例如 打包优化, 资源管理注入环境变量

由于你可以根据不同的目的使用一个插件好几次,因此你需要通过 new 来创建它的实例。

在 webpack 配置中使用插件非常简单。然而,许多使用用例都值得进一步探索。Learn more about them here

Mode

通过设置 mode 字段为 developmentproduction 或者 none,你可以启用 webpack 的与每个环节相对应的内置优化,默认值为 production

Browser Compatibility

Webpack 支持所有符合  ES5-compliant (IE8及以下版本不支持)的浏览器。Webpack 需要 Promise 来实现  import() and require.ensure()。如果你想支持旧版浏览器,你需要在使用这些表达式之前  load a polyfill

Targets

不要将 targetoutput.libraryTarget 混淆。
相关链接:WebpackLibrary和LibraryTarget详解

Manifest

在使用 webpack 构建的典型应用程序或站点中,有三种主要的代码类型:

  1. 你或你的团队编写的源码。
  2. 你的源码会依赖的任何第三方的 library 或 "vendor" 代码。
  3. webpack 的 runtime 和 manifest,管理所有模块的交互。

Runtime

runtime,以及伴随的 manifest 数据,主要是指:在浏览器运行过程中,webpack 用来连接模块化应用程序所需的所有代码。它包含:在模块交互时,连接模块所需的加载和解析逻辑。包括:已经加载到浏览器中的连接模块逻辑,以及尚未加载模块的延迟加载逻辑。

manifest

一旦你的应用在浏览器中以 index.html 文件的形式被打开,一些 bundle 和应用需要的各种资源都需要用某种方式被加载与链接起来。在经过打包、压缩、为延迟加载而拆分为细小的 chunk 这些 webpack 优化 之后,你精心安排的 /src 目录的文件结构都已经不再存在。所以 webpack 如何管理所有所需模块之间的交互呢?这就是 manifest 数据用途的由来……

当 compiler 开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 "manifest",当完成打包并发送到浏览器时,runtime 会通过 manifest 来解析和加载模块。无论你选择哪种 模块语法,那些 import 或 require 语句现在都已经转换为 __webpack_require__ 方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够检索这些标识符,找出每个标识符背后对应的模块。