webpack 笔记 概念篇

概念

本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。
它会递归地构建一个依赖关系图(dependency graph),包含应用程序需要的每个模块,然后将这些模块打包成一个或多个 bundle。

一、入口(entry)

  • 口起点(entry point)指示 webpack 应该使用哪个模块,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。每个依赖项随即被处理,最后输出到称之为 bundles 的文件中,
  • 可以通过在 webpack 配置中配置 entry 属性,来指定一个入口起点(或多个入口起点)。默认值为 ./src
    webpack.config.js
module.exports = {
  entry: './path/to/my/entry/file.js'
};

entry 属性的单个入口语法,是下面的简写:

const config = {
  entry: {
    main: './path/to/my/entry/file.js'
  }
};

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

const config = {
  entry: {
    app: './src/app.js',
    vendors: './src/vendors.js'
  }
};

这种方式比较常见于,只有一个入口起点(不包括 vendor)的单页应用程序中。

多页面应用程序
const config = {
  entry: {
    pageOne: './src/pageOne/index.js',
    pageTwo: './src/pageTwo/index.js',
    pageThree: './src/pageThree/index.js'
  }
};

为每个页面间的应用程序共享代码创建 bundle。由于入口起点增多,多页应用能够复用入口起点之间的大量代码/模块

根据经验:每个 HTML 文档只使用一个入口起点。

二、出口(output)

  • output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。
  • 基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。
  • 即使可以存在多个入口起点,但只指定一个输出配置。
  • 你可以通过在配置中指定一个 output 字段,来配置这些处理过程:
    webpack.config.js
const config = {
  output: {
    filename: 'bundle.js',
    path: '/home/proj/public/assets'
  }
};
module.exports = config;

在 webpack 中配置 output 属性的最低要求是,将它的值设置为一个对象,包括以下两点:

  • filename 用于输出文件的文件名。
  • 目标输出目录 path 的绝对路径。
多个入口起点

如果配置创建了多个单独的 "chunk"(例如,使用多个入口起点或使用像 CommonsChunkPlugin 这样的插件),则应该使用占位符来确保每个文件具有唯一的名称。

{
  entry: {
    app: './src/app.js',
    search: './src/search.js'
  },
  output: {
    filename: '[name].js',
    path: __dirname + '/dist'
  }
}
// 写入到硬盘:./dist/app.js, ./dist/search.js

三、模式

  • 提供 mode 配置选项,告知 webpack 使用相应模式的内置优化。
  • 通过选择 development 或 production 之中的一个。
module.exports = {
  mode: 'production'
};
  • 或者从 [CLI]参数中传递:
webpack --mode=production

四、loader

  • loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。
  • loader 能够 import 导入任何类型的模块(例如 .css 文件),这是 webpack 特有的功能
  • 在 webpack 的配置中 loader 有两个目标:
    1、test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。
    2、use 属性,表示进行转换时,应该使用哪个 loader。
  • 你可以使用 loader 告诉 webpack 加载 CSS 文件,或者将 TypeScript 转为 JavaScript。为此,首先安装相对应的 loader:
npm install --save-dev css-loader
npm install --save-dev ts-loader

然后指示 webpack 对每个 .css 使用 css-loader,以及对所有 .ts 文件使用 `ts-loadert

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

在 webpack 配置中定义 loader 时,要定义在 module.rules 中
module.rules允许你在 webpack 配置中指定多个 loader。

在你的应用程序中,有三种使用 loader 的方式:

  • 配置(推荐):在 webpack.config.js 文件中指定 loader。
  • 内联:在每个 import 语句中显式指定 loader。
  • CLI:在 shell 命令中指定它们。

可以在 import 语句或任何等效于 "import" 的方式中指定 loader。
使用 ! 将资源中的 loader 分开。分开的每个部分都相对于当前目录解析。

import Styles from 'style-loader!css-loader?modules!./styles.css';

通过前置所有规则及使用 !,可以对应覆盖到配置中的任意 loader。

尽可能使用 module.rules,因为这样可以减少源码中的代码量,并且可以在出错时,更快地调试和定位 loader 中的问题。

loader 特性
  • loader 支持链式传递。能够对资源使用流水线(pipeline)。一组链式的 loader 将按照相反的顺序执行。
  • loader 可以是同步的,也可以是异步的。
  • loader 接收查询参数。用于对 loader 传递配置。
  • loader 也能够使用 options 对象进行配置。
  • 除了使用 package.json 常见的 main 属性,还可以将普通的 npm 模块导出为 loader,做法是在 package.json 里定义一个 loader 字段。
  • 插件(plugin)可以为 loader 带来更多特性。
  • loader 能够产生额外的任意文件。
  • loader 通常被命名为 xxx-loader(例如 json-loader)。

五、插件(plugins)

  • 插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。
  • 想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。
  • 你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。
module.exports = {
 plugins: [
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};

webpack 插件是一个具有 apply属性的 JavaScript 对象。apply 属性会被 webpack compiler 调用,并且 compiler 对象可在整个编译生命周期访问。

即便使用 Node API,用户也应该在配置中传入 plugins 属性。compiler.apply 并不是推荐的使用方式。

六、配置(configuration)

因为 webpack 配置是标准的 Node.js CommonJS 模块,你可以做到以下事情:

  • 通过 require(...) 导入其他文件
  • 通过 require(...) 使用 npm 的工具函数
  • 使用 JavaScript 控制流表达式,例如 ?: 操作符
  • 对常用值使用常量或变量
  • 编写并执行函数来生成部分配置

虽然技术上可行,但应避免以下做法

  • 导出不确定的值(调用 webpack 两次应该产生同样的输出文件)
  • 编写很长的配置(应该将配置拆分为多个文件)

你可能已经注意到,很少有 webpack 配置看起来很完全相同。这是因为 webpack 的配置文件,是导出一个对象的 JavaScript 文件。此对象,由 webpack 根据对象定义的属性进行解析。

七、模块(modules)

  • 在模块化编程中,开发者将程序分解成离散功能块(discrete chunks of functionality),并称之为模块
  • 每个模块具有比完整程序更小的接触面,使得校验、调试、测试轻而易举。

什么是 webpack 模块
对比 Node.js 模块,webpack 模块能够以各种方式表达它们的依赖关系,几个例子如下:

  • ES2015 import 语句
  • CommonJS require() 语句
  • AMD define 和 require 语句
  • css/sass/less 文件中的 @import 语句。
  • 样式(url(...))或 HTML 文件()中的图片链接(image url)

支持的模块类型
webpack 通过 loader 可以支持各种语言和预处理器编写模块。 webpack 社区已经为各种流行语言和语言处理器构建了 loader,包括:

  • CoffeeScript
  • TypeScript
  • ESNext (Babel)
  • Sass
  • Less
  • Stylus

总的来说,webpack 提供了可定制的、强大和丰富的 API,允许任何技术栈使用 webpack,保持了在你的开发、测试和生成流程中无侵入性(non-opinionated)。

八、模块解析(module resolution)

使用 enhanced-resolve,webpack 能够解析三种文件路径:
绝对路径

import "/home/me/file";
import "C:\\Users\\me\\file";
  • 由于我们已经取得文件的绝对路径,因此不需要进一步再做解析。

相对路径

import "../src/file1";
import "./file2";
  • 在这种情况下,使用 import 或 require 的资源文件(resource file)所- 在的目录被认为是上下文目录(context directory)。
  • 在 import/require 中给定的相对路径,会添加此上下文路径(context path),以产生模块的绝对路径(absolute path)。

模块路径

import "module";
import "module/lib/file";
  • 模块将在 resolve.modules 中指定的所有目录内搜索。 你可以替换初始模块路径,此替换路径通过使用 resolve.alias 配置选项来创建一个别名。

  • 根据上述规则解析路径后,解析器(resolver)将检查路径是否指向文件或目录。

如果路径指向一个文件

  • 如果路径具有文件扩展名,则被直接将文件打包。
  • 否则,将使用 [resolve.extensions] 选项作为文件扩展名来解析,此选项告诉解析器在解析中能够接受哪些扩展名(例如 .js, .jsx)。

如果路径指向一个文件夹,则采取以下步骤找到具有正确扩展名的正确文件:

  • 如果文件夹中包含 package.json 文件,则按照顺序查找 resolve.mainFields配置选项中指定的字段。并且 package.json 中的第一个这样的字段确定文件路径。
  • 如果 package.json 文件不存在或者 package.json 文件中的 main 字段没有返回一个有效路径,则按照顺序查找 [resolve.mainFiles]配置选项中指定的文件名,看是否能在 import/require 目录下匹配到一个存在的文件名。
  • 文件扩展名通过 resolve.extensions 选项采用类似的方法进行解析。

九、构建目标(targets)

因为服务器和浏览器代码都可以用 JavaScript 编写,所以 webpack 提供了多种构建目标(target),你可以在你的 webpack 配置中设置。
要设置 target 属性,只需要在你的 webpack 配置中设置 target 的值。

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

在上面例子中,使用 node webpack 会编译为用于「类 Node.js」环境(使用 Node.js 的 require ,而不是使用任意内置模块(如 fs 或 path)来加载 chunk)。

多个 Target

尽管 webpack 不支持向 target 传入多个字符串,你可以通过打包两份分离的配置来创建同构的库:

var path = require('path');
var serverConfig = {
  target: 'node',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'lib.node.js'
  }
  //…
};
var clientConfig = {
  target: 'web', // <=== 默认是 'web',可省略
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'lib.js'
  }
  //…
};
module.exports = [ serverConfig, clientConfig ];

上面的例子将在你的 dist 文件夹下创建 lib.js 和 lib.node.js 文件。

十、模块热替换(hot module replacement)

模块热替换(HMR - Hot Module Replacement)功能会在应用程序运行过程中替换、添加或删除模块而无需重新加载整个页面。

主要是通过以下几种方式,来显著加快开发速度:

  • 保留在完全重新加载页面时丢失的应用程序状态。
  • 只更新变更内容,以节省宝贵的开发时间。
  • 调整样式更加快速 - 几乎相当于在浏览器调试器中更改样式。
在应用程序中

通过以下步骤,可以做到在应用程序中置换(swap in and out)模块:

  • 应用程序代码要求 HMR runtime 检查更新。
  • HMR runtime(异步)下载更新,然后通知应用程序代码。
  • 应用程序代码要求 HMR runtime 应用更新。
  • HMR runtime(同步)应用更新。

你可以设置 HMR,以使此进程自动触发更新,或者你可以选择要求在用户交互时进行更新。

在编译器中

除了普通资源,编译器(compiler)需要发出 "update",以允许更新之前的版本到新的版本。"update" 由两部分组成:

  • 更新后的 manifest(JSON)
  • 一个或多个更新后的 chunk (JavaScript)

manifest 包括新的编译 hash 和所有的待更新 chunk 目录。每个更新 chunk 都含有对应于此 chunk 的全部更新模块(或一个 flag 用于表明此模块要被移除)的代码。

在模块中
  • HMR 是可选功能,只会影响包含 HMR 代码的模块。
    举个例子,通过 style-loader为 style 样式追加补丁。为了运行追加补丁,style-loader 实现了 HMR 接口;当它通过 HMR 接收到更新,它会使用新的样式替换旧的样式。
  • 如果一个模块没有 HMR 处理函数,更新就会冒泡(bubble up)。这意味着一个简单的处理函数能够对整个模块树(complete module tree)进行更新。
  • 如果在这个模块树中,一个单独的模块被更新,那么整组依赖模块都会被重新加载。

你可能感兴趣的:(webpack 笔记 概念篇)