The World of TomasRan

webpack中文文档(一):概念

原文链接:https://webpack.js.org/concepts/

介绍

webpack是为了现代Javascript应用而诞生的一个模块打包器。它拥有惊人的可配置性,不过,在使用之前我们觉得你必须理解四个核心的概念。

作为webpack学习之旅的一部分,我们撰写这篇文档旨在给大家展示这些概念的高度概览,不过我们也会提供和概念相关的特定用例的链接。

入口

webpack会根据你的应用的依赖关系创建一张图。这张图的起点被视为入口。入口告诉webpack从哪里开始,以及按照依赖关系图如何去打包。你可以将你的应用的入口看做是上下文根节点或者访问你的应用的第一个文件。

在webpack中,我们通过在 webpack配置对象 中使用 entry 属性定义入口。

下面是一个最简单的例子:

webpack.config.js

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

根据你的应用的需求,可以有多种方法去声明入口。

了解更多!

输出

当你打包完了所有的资源文件,我们仍然需要告诉webpack哪里去放置我们的应用。webpack的 ouput 属性描述了webpack如何去处理打包后的代码。

webpack.config.js

module.exports = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
}
};

上面的例子,通过我们定义的 output.filenameouput.path 属性去命名我们的包以及确定我们想要将它放置的位置。

ouput 属性拥有很多的配置功能,但是我们还是应该花一些时间去理解 output 属性更为通用的配置用例。

了解更多!

加载器

目标是使得你项目中的所有资源文件成为webpack的关注点,而浏览器无需关心(这并不意味着所有的资源文件都需要一起打包)。webpack 将 每一个文件(.css,.html,scss,.jpg,etc)视为一个模块。然而,webpack只能理解Javascript代码。

当将资源文件添加到你的依赖图中时,webpack中的加载器会将涉及的所有文件统统转化成为模块。

从高层次上来讲,你的webpack配置主要有两个目的:

  1. 通过特定的加载器识别需要进行转换的文件。(test 属性)
  2. 将文件进行装换以便于它能够被添加到你的依赖图中(最终添加到你的包中)。(use 属性)

webpack.config.js

const config = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{test: /\.(js|jsx)$/, use: 'babel-loader'}
]
}
};

上面的配置中我们定义了使用加载器的规则,设置了它的两个必须属性:testuse 。它告诉webpack编译器下面的事情:

“webpack编译器,当你在分析 require()/import 声明中的路径时,遇到了 ‘.js’ 或者 ‘.jsx’文件,则在将它添加到打包结果之前,使用 babel-loader 去将它转换为模块。”

当在你的webpack配置中定义规则时,需要切记你应该在 module.rules 下面进行定义,而不是 rules。不过如果你采用了错误的方式webpack也会发出提醒。

关于加载器还有很多我们没有谈及的特定属性可以进行定义。

了解更多!

插件

加载器是基于每个文件执行转换,而plugins 一般用来(但不局限于)在你的已经打包好的模块的“编译”或是“分块” 期间执行一些操作和自定义的功能(或者更多)。webpakc的插件系统十分强大并且可定制

为了使用一个插件,你只需要 require() 它并且将它添加到 plugins 数组。大多数的插件都是可以通过参数进行定制的。因为你可以根据不同目的在同一配置文件中多次使用同一个插件,所以你需要使用 new 去初始化该插件的实例。

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
const webpack = require('webpack'); //to access built-in plugins

const config = {
entry: './path/to/my/entry/file.js',
output: {
filename: 'my-first-webpack.bundle.js',
path: './dist'
},
module: {
rules: [
{test: /\.(js|jsx)$/, use: 'babel-loader'}
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
]
};

module.exports = config;

webpack提供了很多插入即用的插件!可以查看我们的 插件列表 以获取更多信息。

在你的webpack配置中使用插件相当轻松,然而有很多的用例更值得进一步讨论。

了解更多!

入口

正如我们在 介绍 中提及的,在你的webpack配置文件中有多种方式去定义 entry 属性。我们将要介绍配置 entry 属性的方法,并且解释为什么它于你可能有用。

单入口 [简写] 语法

用法:entry: String|Array<String>

webpack.config.js

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

module.exports = config;

以上针对 entry 属性的单入口语法其实是下面的简写形式:

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

当你传递一个数组给 entry 属性会发生什么?传递一个文件路径的数组给 entry 属性会建立一个所谓的“多主入口(multi-main entry)”。当你想要将多个独立的文件以及它们的依赖关系图一起注入到某个“分块(chunk))”中的时候,这会非常有用。

当你正在为仅拥有一个入口的应用或者工具(例如:一个库)寻找快速构建webpack应用的方法时,单入口语法将是很不错的选择。然而,当你需要为配置文件进行扩展时,这种语法的灵活性不足。

对象语法

用法:entry: {[entryChunkName: String]: String|Array<String>}

webpack.config.js

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

这种语法较为冗长。然而,在你的应用中使用这种方式定义单个/多个入口最具有可伸缩性。

“可伸缩的webpack配置(scalable webpack configurations)”指的是可以被复用以及能够由其它部分配置组合生成的配置。这是一个很受欢迎的技术,用来将环境,构建目标和运行环境分离。之后在用特定的工具将它们进行合并,例如 webpack-merge

场景

下面是入口配置的一个列表以及在现实中的用例。

分离应用程序和第三方库入口

webpack.config.js

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

它做了什么?从表面上来看,它告诉webpack分别从 app.jsvendors.js 这两个文件开始建立依赖关系图。这两张图完全是分开的,彼此独立(在每个bundle里都有一个webpack引导程序)。在只拥有一个入口的单页应用中通常都是这种情况(除了第三方库之外)。

为什么?这种配置方式允许你利用 CommonsChunkPlugin 以及从你的应用程序bundle中提取第三方库的参考文献到第三方库的bundle中,并以 __webpack_require__() 调用代之。如果在你的应用程序bundle中没有第三方库的代码,你可以在webpack中实现一个公共模式,即所谓的 长期的第三方库缓存(long-term vendor-caching)

多页应用

webpack.config.js

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

它做了什么?我们正在告诉webpack我们想要三个独立的依赖关系图(就像上面例子的配置)。

为什么?在多页应用中,服务器会取得新的HTML文档给你。页面重新加载新的文档,资源文件也会被重新下载。然而,这给了我们一个特别的机会去做很多事情:

使用 CommonsChunkPlugin 在页面之间创建它们所共享的应用程序代码的bundle文件。对于在不同的入口之间复用大量代码和模块的多页应用程序,当入口数量增加的时候,会从此技术上获取很大的益处。

首要规则:一个HTML文件只使用一个入口。

输出

选项影响了编译的输出。output 选项告诉webpack如何将编译后的文件写入磁盘。注意,虽然在配置中可能会有多个 entry 节点,但是 output 配置是唯一指定的。

用法

你可以简单地通过在你的webpack配置中设置 output 属性的值来使用该属性。

webpack.config.js

const config = {
output: 'bundle.js'
};

module.exports = config;

选项

下面是你可以传递给 output 属性的值的列表。

output.chunkFilename

output.path指定的文件夹中以一个相对路径形式给出的的非入口块的文件名。

[id] 被分块的id取代

[name] 被分块的名字取代(或者当分块没有名字时被id取代)

[hash] 被编译后的hash值取代

[chunkhash] 被分块的hash取代

output.crossOriginLoading

这个选项运行分块的跨域加载。
可能的值有:

false - 禁止跨域加载。

"anonymous" - 跨域加载是激活的。当使用 anonymous 时发送请求无需凭证。

"use-credentials" - 跨域加载是激活的并且发送请求时需要凭证。

期望了解关于跨域的更多信息请访问 MDN

默认值: false

output.devtoolLineToLine

为每个模块建立行到行的映射模型。行到行的映射模型是指,使用一个简单的源代码映射关系将生成的每一行代码和相同行的源代码对应起来。这是一种性能优化的方式。只有在你需要进行性能优化以及确定输入的行和生成的行是匹配的时候,你才应该启用它。

设置为 true 可以激活所有模块的行到行映射模型建立(不推荐)。

就像 module.loaders 一样,我们可以在一个对象 {test, include, exclude} 根据指定文件激活它。

默认值:false

output.filename

指定输出到磁盘上的每个文件的名字。这里你切记不要设置绝对路径!output.path 选项决定了文件被写入磁盘的位置。filename 仅仅用来命名单个的文件。

单入口

{
entry: './src/app.js',
output: {
fliename: 'bundle.js',
path: __dirname + '/build'
}
}

// 写入磁盘:./build/bundle.js

多入口
如果你的配置建立了多个分块(当拥有多个入口或者使用了类似 CommonsChunkPlugin 插件的时候),你应该是用占位符去确保每个文件拥有自己独一无二的名字。

[name 被分块名称替换

hash 被编译阶段的hash替换

chunkhash 被分块的hash替换

{
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: __dirname + '/build'
}
}

// 写入磁盘:./build/app.js, ./build/search.js

output.hotUpdateChunkFilename

热更新分块(the Hot Update Chunks)的文件名。它们在 output.path 指定的目录下。

[id] 被分块id替换

[hash] 被编译的hash替换(最后的hash值存储在记录中)

默认值:"[id].[hash].hot-update.js"

output.hotUpdateFunction

在webpack中使用的用来异步加载热更新分块的JSONP函数。

默认值:"webpackHotUpdate"

output.hotUpdateMainFilename

热更新主文件的文件名。在 output.path 指定的目录下。

[hash] 被编译的hash值替换。(最后的hash值存储在记录中)

默认值:"[hash].hot-update.json"

output.jsonpFunction

在webpack中用来异步加载分块的JSONP函数。

这是一个较短的函数,也许会稍微减少文件大小。当单页拥有多个webpack实例的视乎使用不同的标识符。

默认值:"webpackJsonp"

output.library

如果设置了这个,会将包以类库的形式导出。output.library 将会是它的名字。

只有在你写了一个类库并且想要将它作为单个文件发布的时候使用。

output.libraryTarget

决定了导出库的格式:

"var" - 通过设置一个变量导出:var Library = xxx(默认)

"this" - 通过设置 this 属性导出:this["Library"] = xxx

"commonjs" - 通过设置 exports 的属性进行导出:exports["Library"] = xxx

"commonjs2" - 通过设置 module.exports 导出:module.exports = xxx

"amd" - 导出为AMD格式(可选的名称 - 通过库选项设置名称)

"umd" - 导出为AMD,CommonJS2,或者根属性

默认:"var"

如果 output.library 没有被设置,但是 output.libraryTarget 设置了非 var 以外的值,则导出对象的每一个属性会被复制(除了 amdcommonjs2umd)。

output.path

绝对路径形式的输出文件夹(必须)。

[hash] 被编译的hash替换。

config.js

output: {
path: "/home/proj/public/assets",
publicPath: "/assets/"
}

index.html

<head>
<link href="/assets/spinner.gif"/>
</head>

下面是一个使用了CDN以及资源hash的更为复杂的例子。

config.js

output: {
path: "/home/proj/cdn/assets/[hash]",
publicPath: "http://cdn.example.com/assets/[hash]/"
}

注意:如果输出文件的 publicPath 在编译阶段并不明确的话,该字段可以留空以及在入口文件运行时动态设置。如果编译的时候你并不知道 publicPath 的值,你可以忽略它并在你的入口点设置 __webpack_public_path

 __webpack_public_path__ = myRuntimePublicPath

// 余下的应用入口

output.sourceMapFilename

为Javascript文件生成的源文件映射关系文件的名字。它们在 output.path
指定的文件夹下。

[file] 被Javascript文件的名字替换。

[id] 被分块的id替换

[hash] 被编译的hash替换。

默认值:"[file].map"

加载器

加载器是用来对你的应用程序中的资源文件进行转换的工具。它们是一些接收原始资源文件源码为参数并返回新的源码的函数(运行在Nodejs中)。

举例来说,你可以使用加载器告诉webpack去加载一个CSS文件或是将TypeScript代码转换成Javascript代码。

加载器特性

  • 加载器可以被链式调用。它们以管道的形式应用于资源文件。加载器链按照时间顺序执行。第一个加载器将返回值作为下一个加载器的输入,在加载器链的结尾,webpack期望它返回Javascript代码。
  • 加载器可以是同步也可以是异步的。
  • 加载器在Nodejs中运行,并且可以执行所有可能的操作。
  • 加载器接受查询参数。可以通过这个传递配置参数给加载器。
  • 加载器也可以通过 options 对象进行配置。
  • 除了常规的package.json中的 main 字段将加载器导出之外,还可以通过 loader字段进行导出。
  • 插件可以赋予加载器更多特性。
  • 加载器可以产生额外的其他任意文件。

通过预处理函数(加载器),它们使得Javascript生态系统拥有更加强大的能力。现在用户处理强密度的逻辑时拥有更多的灵活性,例如压缩,打包,语言翻译以及更多

解析加载器

加载器被近似解析成模块。一个加载器模块期望导出一个函数,并以Nodejs 兼容的Javascript书写。在一般情况下你通过npm管理加载器,但是你也可以添加加载器文件到你的应用中。

参考的加载器

按照惯例,加载器通常以 XXX-loader 命名,其中 XXX 代表上下文名称。举个例子:json-loader

加载器名称约定和优先搜索顺序由webpack配置API中的resolveLoader.moduleTemplates定义。

插件

插件是webpack的支柱。webpack本身的构建使用的都是和你的应用中webpack配置相同的一套插件系统。

它们还可以做任何加载器不能完成的任务。

剖析

一个webpack插件是一个拥有 apply 属性的Javascript对象。这个 apply 属性会被webpack编译器调用,允许访问编译的整个生命周期。

ConsoleLogOnBuildWebpackPlugin.js

function ConsoleLogOnBuildWebpackPlugin() {

};

ConsoleLogOnBuildWebpackPlugin.prototype.apply = function(compiler) {
compiler.plugin('run', function(compiler, callback) {
console.log("The webpack build process is starting!!!");
callback();
});
};

作为一个机智的Javascript开发者你可能记得 Function.prototype.apply 方法。因为这个方法的存在,你可以将任何函数作为插件进行传递(this 将指向 compiler)。你也可以在你的配置中使用这种风格去嵌入自定义的插件。

用法

因为插件可以接收 arguments/options,你必须在你的webpack配置中传递一个 new 实例给 plugins 属性。

这决定于你怎样去使用webpack,我们拥有很多方式去使用插件。

配置

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
const webpack = require('webpack'); //to access built-in plugins

const config = {
entry: './path/to/my/entry/file.js',
output: {
filename: 'my-first-webpack.bundle.js',
path: './dist'
},
module: {
loaders: [{
test: /\.(js|jsx)$/,
loader: 'babel-loader'
}]
},
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
]
};

module.exports = config;

Node API

即便是使用Node API的时候,用户也需要通过配置中的 plugins 属性传递插件。使用 compiler.apply 并不推荐。

some-node-script.js

const configuration = require('./webpack.config.js');

let compiler = webpack(configuration);
compiler.apply(new webpack.ProgressPlugin());

compiler.run(function(err, stats) {
// ...
});

你知道吗:上面的例子和 webpack自身运行时 极其相似!在 webpack源码 中隐藏了大量优秀的例子,你完全可以将它们应用在你自己的配置和脚本中!

配置

你也许已经注意到几乎没有完全相同的webpack配置。这是因为webpack的配置文件是导出一个对象的JavaScript文件。 webpack将会基于这个对象定义的属性进行一系列处理。

因为它是一个标准的符合nodejs中CommonJs规范的模块,所以你可以做下面的事情:

  • 通过 require(...) 导入其它文件
  • 通过 require(...) 使用npm库中的工具集
  • 使用Javascrpt控制流表达式,比如 ?: 操作符
  • 对经常使用的值用常量或变量存储
  • 编写和执行函数去生成部分配置

因地制宜。

你不应该使用下面的这些。其实从技术角度而言你可以使用它们,但是强烈不推荐使用。

  • 当使用webpack命令行的时候访问命令行参数(而不是写你自己的命令行,或者使用 --env
  • 导出不确定的值(多次调用webpack应该输出相同的文件)
  • 配置代码过于冗长(而不是将配置划分成多个文件)

下面的例子描述了webpack配置对象的丰富的表现力和可配置性,因为它就是代码:

最简单的配置

webpack.config.js

module.exports = {
entry: './foo.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'foo.bundle.js'
}
};

多个目标

webpack.config.js

var path = require('path');
var webpack = require('webpack');
var webpackMerge = require('webpack-merge');

var baseConfig = {
target: 'async-node',
entry: {
entry: './entry.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'inline',
filename: 'inline.js',
minChunks: Infinity
}),
new webpack.optimize.AggressiveSplittingPlugin({
minSize: 5000,
maxSize: 10000
})
]
};

let targets = ['web', 'webworker', 'node', 'async-node', 'node-webkit', 'electron-main'].map((target) => {
let base = webpackMerge(baseConfig, {
target: target,
output: {
path: path.resolve(__dirname, 'dist/' + target),
filename: '[name].' + target + '.js'
}
});
return base;
});

module.exports = targets;

从这篇文档中你所应该明白的是,有很多不同的方式去格式化和风格化你的webpack配置文件。关键是要保持一致,好便于你和你的团队去理解和维护。

使用 TypeScript

在下面的例子中我们使用TypeScript创建一个angular-cli使用的类去生成配置

webpack.config.js

import * as webpackMerge from 'webpack-merge';
import { CliConfig } from './config';
import {
getWebpackCommonConfig,
getWebpackDevConfigPartial,
getWebpackProdConfigPartial,
getWebpackMobileConfigPartial,
getWebpackMobileProdConfigPartial
} from './';

export class NgCliWebpackConfig {
// TODO: When webpack2 types are finished lets replace all these any types
// so this is more maintainable in the future for devs
public config: any;
private webpackDevConfigPartial: any;
private webpackProdConfigPartial: any;
private webpackBaseConfig: any;
private webpackMobileConfigPartial: any;
private webpackMobileProdConfigPartial: any;

constructor(public ngCliProject: any, public target: string, public environment: string, outputDir?: string) {
const config: CliConfig = CliConfig.fromProject();
const appConfig = config.config.apps[0];

appConfig.outDir = outputDir || appConfig.outDir;

this.webpackBaseConfig = getWebpackCommonConfig(this.ngCliProject.root, environment, appConfig);
this.webpackDevConfigPartial = getWebpackDevConfigPartial(this.ngCliProject.root, appConfig);
this.webpackProdConfigPartial = getWebpackProdConfigPartial(this.ngCliProject.root, appConfig);

if (appConfig.mobile){
this.webpackMobileConfigPartial = getWebpackMobileConfigPartial(this.ngCliProject.root, appConfig);
this.webpackMobileProdConfigPartial = getWebpackMobileProdConfigPartial(this.ngCliProject.root, appConfig);
this.webpackBaseConfig = webpackMerge(this.webpackBaseConfig, this.webpackMobileConfigPartial);
this.webpackProdConfigPartial = webpackMerge(this.webpackProdConfigPartial, this.webpackMobileProdConfigPartial);
}
this.generateConfig();
}

generateConfig(): void {
switch (this.target) {
case "development":
this.config = webpackMerge(this.webpackBaseConfig, this.webpackDevConfigPartial);
break;
case "production":
this.config = webpackMerge(this.webpackBaseConfig, this.webpackProdConfigPartial);
break;
default:
throw new Error("Invalid build target. Only 'development' and 'production' are available.");
break;
}
}
}

使用JSX

下面的例子中使用了JSX和Babel创建了一个webpack可以理解的JSON配置。

import h from 'jsxobj';

// example of an import'd plugin
const CustomPlugin = config => ({
...config,
name: 'custom-plugin'
});

export default (
<webpack target="web" watch>
<entry path="src/index.js" />
<resolve>
<alias {...{
react: 'preact-compat',
'react-dom': 'preact-compat'
}} />
</resolve>
<plugins>
<uglify-js opts={{
compression: true,
mangle: false
}} />
<CustomPlugin foo="bar" />
</plugins>
</webpack>
);

模块

模块化编程 中,开发者会将项目拆分成独立的功能模块。

相比于每个项目而言,每个模块的覆盖面积较小,这使得验证,调试以及测试更加轻而易举。良好编写的模块提供了可靠的抽象和边界封装,因此从应用地整体上来看,每个模块都会拥有一致的设计和清晰的目标。

Nodejs几乎从诞生开始就已经支持模块化编程。然而,web端的模块化却迟迟未能实现。在web端有很多不同的工具支持模块化的Javascript代码,它们拥有不同的益处和局限。webpack从以往的这些系统中汲取经验和教训,实现了将模块化的概念应用于项目中的任何一个文件。

webpack模块是什么

和Node.js的模块相比,webpack的模块能通过很多不同的方式表达它们的依赖关系。下面是一些例子:

  • ES2015 的 import 声明
  • Javascript 的 require() 声明
  • AMD的 definerequire() 声明
  • css/sass/less 文件中的 @import 声明
  • 样式表(url(...))或者html(<img src=...>)中的图片url

webpack 1 需要使用指定的加载器去转换ES2015的 import,然而webpack 2可能不再需要。

支持的模块类型

webpack通过加载器可以支持使用不同语言和预处理器编写的模块。加载器告诉webpack怎样去处理非Javascript的模块,以及如何将它们的依赖关系包含进你的包里。webpack社区已经为各种广受欢迎的语言和语言处理器构建了加载器,包括:

数不胜数!总而言之,webpack提供了一套强大并且丰富的API去实现自定义需求,允许拥有任何技术栈的人使用,使你的开发、测试和生产工作流程不再一成不变。

想要看更详细的信息,请移步 加载器清单 或者 编写自己的加载器

模块解析

解析器是帮助寻找模块绝对地址的类库。一个模块可以以这样的形式被另一个模块引为依赖:

import mymodule from 'path/to/module'
// or
require('path/to/module')

依赖模块可以是应用程序的源码也可以是第三方类库。针对于每一个 require()/import 声明,解析器都会帮助 webpack 找到需要被打包的模块代码。 在打包模块时,webpack 使用 enhanced-resolve去解析文件路径。

webpack中的解析规则

webpack 解析三种文件路径。

绝对路径

import "/home/me/file";
import "C:\\Users\\me\\file";

既然我们已经拥有了绝对路径,就无需再继续解析了。

相对路径

import "../src/file";
import "./file";

在这种情况下,资源文件的目录被用作上下文目录(当前被处理的文件的目录)。给定的绝对路径被加入到上下文路径并由此得出文件的绝对路径。

模块路径

import "module";
import "module/lib/file";

在使用 resolve.modules 指定的文件目录内部搜索模块,它可以是不同路径的一个数组。混叠允许你在 require/import 的时候使用一个别名去替代模块路径,比如,设置 resolve.alias 值为一个现存的模块路径。

一旦路径是基于以上规则进行解析,解析器会检查路径是指向一个文件还是目录。如果路径指向一个文件那么该文件会被立即打包。但是如果路径指向一个文件夹,则会采取下面的步骤去找到拥有正确的扩展名的文件。

  • package.json 中的 main:"<filename>.js" 字段决定了正确的文件。
  • 如果没有package.json文件或者main字段缺失,则寻找 resolve.mainFiles 配置选项。
  • 如果这也失败的话,默认就会去寻找被命名为 index 的文件。
  • resolve.extensions 告诉解析器什么扩展名(例如:.js.jsx)是能够被合法解析的。

加载器解析

这个和文件解析的规则一样。但是 resolveLoader 配置可以针对加载器制定独立的解析规则。

缓存

对每个文件系统的访问都会被缓存,以便于多个并行或者串行的请求同一文件的任务可以被合并。在监听模式下只有发生改变的文件会从缓存中删除(监听器知道哪个文件发生了改变)。在非监听的模式下每一次编译之前都会清除缓存。

不安全的缓存

有一个配置项 resolve.unsafeCache,通过积极的缓存提高性能。每一个解析进程都会被缓存并且不会被清除。在大多数场合这样做是没问题的,但是在一些极端场景下却不适合(什么极端场景呢?)。

请查看 Resolve API 去获取关于上述配置的更多信息。

依赖图

任何时候只要一个文件取决于另一个文件,webpack就将其视为依赖。这使得webpack可以获取任何非代码的资源,比如图片或者web字体,并且将它们提供为你的应用程序的依赖。

当webpack处理你的应用的时候,它会从命令行下定义的模块列表或者它的配置文件开始。从这些入口开始,webpack递归地构造一个包含了你的应用需要的所有模块的依赖关系图,然后将所有的这些模块打包进少数的几个供浏览器加载的包中 – 经常只有一个。

将你的应用程序打包对于采用HTTP/1.1的客户端来说尤其有用,因为它缩减了当浏览器发送新请求时你的应用的等待次数。对于HTTP/2,你也可以使用webpack进行代码分离和打包以实现 最佳优化

目标

因为Javascript代码不仅可以在浏览器端写,也可以在服务器端写,所以在你的webpack 配置 文件中webpack提供了可供设置的多个部署目标。

不要将webpack target 属性和 output.libraryTarget 属性混淆了。详细信息可以查看 我们指导手册output 属性。

用法

设置 target 属性你只需要在你的webpack配置中简单地设置 target的值:

webpack.config.js

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

每个目标都有种种部署/环境特定的附加条件,以支持它的需求。查看 可使用的目标

多个目标

虽然webpack不支持将多个字符串传递给 target 属性,但是你可以通过包装两个独立的配置去创建一个形似的库。

webpack.config.js

var serverConfig = {
target: 'node',
output: {
path: 'dist',
filename: 'lib.node.js'
}
//…
};

var clientConfig = {
target: 'web', // <=== can be omitted as default is 'web'
output: {
path: 'dist',
filename: 'lib.js'
}
//…
};

module.exports = [ serverConfig, clientConfig ];

上面的例子在你的 dist 文件夹下创建了一个 lib.jslib.node.js 文件。

资源

从上面的配置项来看,我们拥有多个不同的部署目标可选。下面是一个实例列表和你可以参考的资源。

打包输出组合

compare-webpack-target-bundles :一个优秀的资源,可以测试和查看不同的webpack 目标。也很方便报告bug。

热模块替换

当应用在运行时,Hot Module Replacement(HMR 热模块替换)可以在没有进行页面载入的情况下实现交换、添加和删除模块。当个别模块被改变的时候,热模块替换可以帮助我们实现在没有页面刷新的情况下更新那些模块,从而缩减开发时间。

它是如何工作的?

从应用视图来看

  1. 应用程序向HMR运行时环境请求检查更新。
  2. HMR运行时环境下载更新(异步地)并且告诉应用程序代码存在一个更新。
  3. 然后应用程序代码告诉HRM运行时环境应用更新。
  4. HMR运行时环境应用更新(同步地)。

你可以配置好HMR以便于上述进程能自动发生,或者你也可以选择要求用户交互去触发更新。

从编译器﹝webpack﹞ 视图来看

除了正常的资源之外,编译器需要发送一个“更新”来允许从之前版本进行更新操作。这个“更新”包含两个方面:

  1. 更新清单(JSON)
  2. 一个或多个更新块(JavaScript)

更新清单包含新生成的编译hash值和所有更新块的列表。

每个更新块包含用于相应块中的所有更新的模块的代码(或者一个显示模块被删除的标记)。

在不同的构建之间,编译器可以确保模块的ID和分块的ID是一致的。它通常在内存中存储了这些ID(举个例子,当使用webpack-dev-server的时候),但也有可能将它们存储到一个JSON文件中。

从模块视图来看

HMR是一个选择性功能,只会影响到包含了HMR代码的模块。比如通过 style-loader 修复样式。为了让所做的修改能够应用,style-loader继承了HMR的接口;当它接收到一个来自HMR的更新请求时,将会用新的样式取代老的样式。

类似得,当在一个模块中继承了HMR接口时,你可以定义模块被更新的时候发生哪些事情。然而,在多数情况下,在每个模块中都写入HMR代码不是强制的。如果一个模块没有HMR处理函数,更新消息将会冒泡。这意味着一个单一的处理函数就可以处理一个完整模块树的更新。如果模块树中的某个模块更新,所有的模块树都会重新加载(只是重新加载,而不是改变)。

从HMR运行时环境看﹝Technical﹞

对于模块系统运行时来说,它要执行额外的代码去跟踪模块的父级和子级。

在管理方面,运行时环境提供两个方法:checkapply

check 方法会发送一个http请求以获取更新清单。如果这个请求失败,则意味着没有更新。如果请求成功,则发生更新的块列表会和当前已加载的块列表进行对比。对于每一个已加载的块,与之对应的更新块会被下载。所有的模块更新都会存储在运行时环境中。当所有的需要更新的块下载完毕等待被应用时,运行时环境切换到 ready 状态。

apply 方法将所有的需要更新的模块标记为无效。对每一个失效的模块,在模块中需要有一个更新处理函数或者在它们的父级中存在对应的更新处理函数。否则,无效的标志会进行冒泡使得其父级模块也失效。当向上冒泡至应用的入口,或者遇到了有更新处理函数的模块(不管是哪个模块,只要遇到了就立刻停止)时,这一过程才会停止。如果冒泡过程持续最终到达应用入口,那么这个更新处理就失败了。

接着,所有失效的模块会被释放(通过dispose 处理函数)和卸载。当前的hash值会被更新,所有的“接受的”处理函数被调用。运行时环境切换到 idle 状态,一切如常。

我可以用它做什么?

你可以在开发环境中使用它进行。webpack-dev-server 支持一种热模式,就是通过尝试利用运行时环境进行更新,而不是试图重新加载整个页面。看看怎样在 React中继承HMR 的例子。

一些加载器已经生成了热更新的模块。举例来说,style-loader 就可以换出一个页面的样式。对于此类的模块来说,你不需要做任何特别的处理。

webpack的强大在于它的可定制性。配置运行时环境的方法数不胜数,都取决于特定的项目。