官网:http://www.css88.com/doc/webpack2/concepts/
webpack是js的模块打包器(module bundler)。
入口(Entry)
webpack将创建所有应用程序的依赖关系图标(dependency graph)。
入口起点(entry point):图表的起点。根上下文(contextual root) app第一个启动文件。
入口起点告诉webpack从哪里开始,遵循依赖图表打包文件。
webpack中使用webpack配置对象的entry属性定义入口。
单个入口(简写)语法
用法: entry: string | Array<string>
webpack.config.js
1 module.exports = { 2 entry: './path/to/my/entry/file.js' 3 }; 4 module.exports = config;
entry属性的单个入口语法,是下面的简写:
1 module.exports = { 2 entry: { 3 main: './path/to/my/entry/file.js' 4 } 5 };
向entry传入一个数组会发生什么?将创建多个入口。
对象语法
用法:entry: {[entryChunkName: string]: string | Array<string>}
const config = { entry: { app: './src/app.js', // 应用程序(app) vendors: './src/vendors.js' // 公共库(vendor) } };
对象语法是应用程序中定义入口的最可扩展的方式。
可扩展的webpack配置:可重用并可与其他配置组合使用。用于将关注点从环境、构建目标、运行时中分离,然后用专门的工具(如 webpack-merge)将它们合并。
输出(Output)
所有的资源(assets)归拢在一起后,需要告诉webpack在哪里打包应用程序。
output属性,描述了如何处理归拢在一起的代码(bundled code)。
webpack.config.js
module.exports = { entry: './path/to/my/entry/file.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'my-first-webpack.bundle.js' } };
用法(Usage)
将output设置为一个对象,包括以下两点:
1.编译文件的文件名(filename),推荐:main.js || bundle.js || index.js
2.output.path对应一个绝对路径(一次性打包的目录)。
webpack.config.js
const config = { output: { filename: 'bundle.js', path: '/home/proj/public/assets' } }; exports.module = config;
选项(Options)
可以向output属性传入的值。
output.chuckFilename
非入口的 chunk(non-entry chunk) 的文件名,路径相对于 output.path
目录。
[id]
被 chunk 的 id 替换。
[name]
被 chunk 的 name 替换(或者,在 chunk 没有 name 时使用 id 替换)。
[hash]
被 compilation 生命周期的 hash 替换。
[chunkhash]
被 chunk 的 hash 替换。
output.crossOriginLoading
此选项启用跨域加载(cross-origin loading) chunk。
可选的值有:
false
- 禁用跨域加载
"anonymous"
- 启用跨域加载。当使用 anonymous
时,发送不带凭据(credential)的请求。
"use-credentials"
- 启用跨域加载。发送带凭据(credential)的请求。
output.devtoolLineToLine
所有/指定模块启用行到行映射(line-to-line mapped)模式。
行到行映射模式使用一个简单的 SourceMap,即生成资源(generated source)的每一行都映射到原始资源(original source)的同一行。这是一个可做性能优化之处。
true
在所有模块启用(不推荐)
{test, include, exclude}
对象,对特定文件启用(类似于 module.loaders
)。
默认值:false
output.filename
指定硬盘每个输出文件的名称。
单个入口
{ entry: './src/app.js', output: { filename: 'bundle.js' path: __dirname + 'build', } } // 写入到硬盘: ./build/bundle.js
多个入口
如配置创建多个“chunk”(例如使用多个入口起点或使用类似CommonsChunkPlugin(提取公共代码)的插件),为确保每个文件名都不重复,应使用以下替换方式:
[name]
被 chunk 的 name 替换。
[hash]
被 compilation 生命周期的 hash 替换。
[chunkhash]
被 chunk 的 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
热更新 chunk(Hot Update Chunk) 的文件名。在 output.path
目录中。
[id]
被 chunk 的 id 替换。
[hash]
被 compilation 生命周期的 hash 替换。(最后一个 hash 存储在记录中)
默认值:"[id].[hash].hot-update.js"
output.hotUpdateFunction
webpack 中用于异步加载(async load)热更新(hot update) chunk 的 JSONP 函数。
默认值:"webpackHotUpdate"
output.hotUpdateMainFilename
热更新主文件(hot update main file)的文件名。
[hash]
被 compilation 生命周期的 hash 替换。(最后一个 hash 存储在记录中)
默认值:"[hash].hot-update.json"
output.jsonpFunction
webpack 中用于异步加载(async loading) chunk 的 JSONP 函数。
较短的函数可能会减少文件大小。当单页有多个 webpack 实例时,请使用不同的标识符(identifier)。
默认值:"webpackJsonp"
output.library
如果设置此选项,会将 bundle 导出为 library。output.library
是 library 的名称。
如果你正在编写 library,并且需要将其发布为单独的文件,请使用此选项。
output.libraryTarget
library 的导出格式
"var"
- 导出为一个变量:var Library = xxx
(默认)
"this"
- 导出为 this
的一个属性:this["Library"] = xxx
"commonjs"
- 导出为 exports
的一个属性:exports["Library"] = xxx
"commonjs2"
- 通过 module.exports
:module.exports = xxx
导出
"amd"
- 导出为 AMD(可选命名 - 通过 library 选项设置名称)
"umd"
- 导出为 AMD,CommonJS2 或者导出为 root 的属性
默认值:"var"
如果 output.library
未设置,但是 output.libraryTarget
被设置为 var
以外的值,则「所导出对象」的每个属性都被复制到「对应的被导出对象」上(除了 amd
,commonjs2
和 umd
)。
output.path
导出目录为绝对路径(必选项)。
[hash]
被 compilation 生命周期的 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
可以留空,并且在入口起点文件运行时动态设置。如果你在编译时不知道 publicPath
,你可以先忽略它,并且在入口起点设置 __webpack_public_path__
。
__webpack_public_path__ = myRuntimePublicPath // 其他的应用程序入口
output.sourceMapFilename
JavaScript 文件的 SourceMap 的文件名。它们在 output.path
目录中。
[file]
被 JavaScript 文件的文件名替换。
[id]
被 chunk 的 id 替换。
[hash]
被 compilation 生命周期的 hash 替换。
默认值:"[file].map"
加载器(Loader)
webpack的目标是让webpack聚焦于项目中的所有资源(asset),而浏览器不需要关注这些(并不是资源都必须打包在一起)。webpack把每个文件(.css, .html, .scss, .jpg, etc.) 都作为模块处理。而且webpack只理解js。
loader是对应用程序中资源文件进行转换。它们是(运行在Node.js中的)函数,可以将资源文件作为参数的来源,然后返回到新的资源文件。
webpack配置的目标:
1.识别(identity)被对应loader转换(transform)的文件
2. 进行过文件转换,可以将被转换的文件添加到依赖图表(最终添加到bundle中)(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: [ // 当遇到【在require() / import语句中被解析为'.js'或'.jsx'的路径】时,把它们添加并打包前,要先使用babel-loader转换 {test: /\.(js | jsx)$/, use: 'babel-loader'} ] } }; module.exports = config;
在webpack配置中定义loader时,要定义在module.rules中,而不是rules。定义错时webpack会提出严重警告。
示例
可使用 loader 告诉 webpack 加载 CSS 文件,或者将 TypeScript 转为 JavaScript。
首先,安装相对应的 loader:
npm install --save-dev css-loader
npm install --save-dev ts-loader
其次,配置 webpack.config.js
,对每个 .css
文件使用 css-loader
,对每个 .ts
文件使用 ts-loader
:
webpack.config.js
module.exports = { module: { rules: [ {test: /\.css$/, use: ['css-loader'](loaders/css-loader)}, {test: /\.ts$/, use: ['ts-loader']((https://github.com/TypeStrong/ts-loader))} ] } };
注意,根据配置选项,下面的规范定义了同等的 loader 用法:
{test: /\.css$/, [loader](/configuration/module#rule-loader): 'css-loader'} // or equivalently {test: /\.css$/, [use](/configuration/module#rule-use): 'css-loader'} // or equivalently {test: /\.css$/, [use](/configuration/module#rule-use): { loader: 'css-loader', options: {} }}
配置
在你的应用程序中,有三种方式使用 loader:
- 通过配置
- 在
require
语句中显示使用 - 通过 CLI
通过 webpack.config.js
module.rules
允许在 webpack 配置中指定几个 loader。
这是展示 loader 的一种简明的方式,并且有助于简洁代码,以及对每个相应的 loader 有一个完整的概述。
module: { rules: [ { test: /\.css$/, use: [ {loader: 'style-loader'}, { loader: 'css-loader', options: { module: true } } ] } ] }
通过 require
可以在 require
语句(或 define
, require.ensure
, 等语句)中指定 loader。使用 !
将资源中的 loader 分开。分开的每个部分都相对于当前目录解析。
require('style-loader!css-loader?modules!./styles.css');
通过前置所有规则及使用 !
,可以对应覆盖到配置中的任意 loader。
选项可以传递查询参数,就像在 web 中那样(?key=value&foo=bar
)。也可以使用 JSON 对象(?{"key":"value","foo":"bar"}
)。
尽可能使用 module.rules
,因为可以在源码中减少引用,并且更快调试和定位 loader,避免代码越来越糟。
通过 CLI
可选项,通过 CLI 使用 loader:
webpack --module-bind jade --module-bind 'css=style!css'
对 .jade
文件使用 jade-loader
,对 .css
文件使用 style-loader
和 css-loader
。
Loader 特性
- loader 支持链式传递。可对资源使用流水线(pipeline)。loader 链式地按照先后顺序进行编译。loader 链中的第一个 loader 返回值给下一个 loader。在最后一个 loader,返回 webpack 所预期的 JavaScript。
- loader 可以是同步或异步函数。
- loader 运行在 Node.js 中,可执行任何可能的操作。
- loader 接收查询参数。用于 loader 间传递配置。
- loader 也能够使用
options
对象进行配置。 - 除了使用
package.json
常见的main
属性,还可以将普通的 npm 模块导出为 loader,做法是在package.json
里定义一个 loader 字段。 - 插件(plugin)可以为 loader 带来更多特性。
- loader 能够产生额外的任意文件。
loader 通过(loader)预处理函数,为 JavaScript 生态系统提供了更多有力功能。用户现在可以更加灵活的引入细粒度逻辑,例如压缩(compression)、打包(package)、语言翻译(language translation)和其他更多。
解析 Loader
loader 遵循标准的模块解析。多数情况下,loader 将从模块路径(通常是 npm install
, node_modules
)解析。
如何编写模块?
loader 模块需要导出为一个函数,并且使用 Node.js 兼容的 JavaScript 编写。通常使用 npm 管理 loader,也可以将 loader 模块作为应用程序中的文件。
按照约定,loader 通常被命名为 XXX-loader
,其中 XXX
是上下文的名称,例如 json-loader
。
loader 的名称约定和优先搜索顺序,由 webpack 配置 API 中的 resolveLoader.moduleTemplates
定义。
插件(Plugins)
loader仅在每个文件的基础上执行转换,插件目的在于解决loader无法实现的事情。
插件(plugins)最常用(但不限)于在打包模块的“compilation”和“chunk”生命周期执行操作和自定义功能。
webpack的插件系统强大且可定制化。
在一个配置中,多次使用一个插件,用于不同的目的。
1.想使用一个插件,只需require()它,然后把它添加到plugins数组中。多数插件可以通过选项(option)自定义。
2.需要使用new来创建插件的实例,并且通过实例来调用插件。
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); // installed via npm(通过npm安装) const webpack = require('webpack'); //to access built-in plugins(访问内置插件) const path = require('path'); 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'} ] }, plugins: [ new webpack.optimize.UglifyJsPlugin(), // 优化js组件 new HtmlWebpackPlugin({template: './src/index.html'}) ] }; module.exports = config;
剖析
webpack 插件是一个具有 apply
属性的 JavaScript 对象。 apply
属性会被 webpack compiler 调用,并且 compiler 对象可在整个 compilation 生命周期访问。
ConsoleLogOnBuildWebpackPlugin.js
function ConsoleLogOnBuildWebpackPlugin() { }; ConsoleLogOnBuildWebpackPlugin.prototype.apply = function(compiler) { compiler.plugin('run', function(compiler, callback) { console.log("webpack 构建过程开始!!!"); callback(); }); };
通过Function.prototype.apply方法可以把任意函数作为插件传递(this
将指向 compiler
)。在配置中使用这样的方式来内联自定义插件。
用法
plugin 可以携带参数/选项,在 wepback 配置中,需向 plugins
属性传入 new
实例。
配置
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); // installed via npm(通过npm安装) const webpack = require('webpack'); //to access built-in plugins(访问内置插件) const path = require('path'); const config = { entry: './path/to/my/entry/file.js'; output: { filename: 'my-first-webpack.bundle.js', path: path.resolve(__dirname, 'dist') }, module: { loaders: [ { test: /\.(js | jsx)$/, loader: 'babel-loader' } ] }, plugins: [ new webpack.optimize.UglifyJsPlugin(), // 优化js组件 new HtmlWebpackPlugin({template: './src/index.html'}) ] }; module.exports = config;
配置(Configuration)
webpack 的配置文件是 JavaScript 文件导出的一个对象。此对象,由 webpack 根据对象定义的属性进行解析。
因为 webpack 配置是标准的 Node.js CommonJS 模块,可以如下:
- 通过
require(...)
导入其他文件 - 通过
require(...)
使用 npm 的工具函数 - 使用 JavaScript 控制流表达式,例如
?:
操作符 - 对常用值使用常量或变量
- 编写并执行函数来生成部分配置
不应该使用以下。从技术上讲可以这么做,但是并不推荐:
- 在使用 webpack 命令行接口(CLI)(应该编写自己的命令行接口(CLI),或使用
--env
)时,访问命令行接口(CLI)参数 - 导出不确定的值(调用 webpack 两次应该产生同样的输出文件)
- 编写很长的配置(应该将配置拆分为多个文件)
最简单的配置
webpack.config.js
var path = require('path'); module.exports = { entry: './foo.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'foo.bundle.js' } };
多个 Target
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' }, plugin: [ 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, 'diat/' + target), filename: '[name].' + target + '.js' } }); return base; }); module.exports = targets;
模块(Modules)
在模块化编程,开发者将程序分解成称为模块的离散功能块。
什么是 webpack 模块
对比 Node.js 模块,webpack 模块能够以各种方式表达它们的依赖关系,几个例子如下:
- ES2015
import
语句 - CommonJS
require()
语句 - AMD
define
和require
语句 - css/sass/less 文件中的
@import
语句。 - 样式(
url(...)
)或 HTML 文件(<img src=...>
)中的图片链接(image url)
webpack 1 需要特定的 loader 来转换 ES 2015 import
,然而通过 webpack 2 可以开箱即用。
模块解析(Module Resolution)
解析器是一个通过绝对路径来帮助定位模块的库(library)。 一个模块可以作为另一个模块的依赖模块,然后被后者引用,如下:
import foo from 'path/to/module' // or require('path/to/module')
依赖模块可以来自应用程序代码或第三方库。解析器帮助 webpack
找到 bundle 中需要引入的模块代码,这些代码包含在 require
/import
语句中。 当打包模块时,webpack
使用增强解析来解析文件路径
webpack 中的解析规则
使用 enhanced-resolve
,webpack 能够解析三种文件路径:
绝对路径
import "/home/me/file";
import "C:\\Users\\me\\file";
已经有了文件的绝对路径,不需要进一步解析。
相对路径
import "../src/file1";
import "./file2";
出现 import
或 require
的资源文件的目录被认为是上下文目录(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
中的第一个这样的字段确定文件路径。
webpack 根据构建目标(build target)为这些选项提供了合理的默认配置。
解析加载器(Resolving Loaders)
Loader 解析遵循与文件解析器指定的规则相同的规则。但是 resolveLoader
配置选项可以用来为 Loader 提供独立的解析规则。
缓存
每个文件系统访问都被缓存,以便更快触发对同一文件的多个并行或穿行请求。在观察模式下,只有修改过的文件会从缓存中摘出。如果关闭观察模式,在每次编译前清理缓存。
依赖图表(Dependency Graph)
任何时候一个文件依赖于另一个文件,webpack 把这个文件当作依赖处理。这使得 webpack 可以接收非代码资源(non-code asset)(例如图像或 web 字体),并且也能把它们作为依赖提供给应用。
webpack 从命令行或配置文件定义的一个模块列表,开始处理应用。 从这些入口点开始,webpack 递归地构建一个依赖图表,这个依赖图表包括应用所需的每个模块,然后将所有模块打包为少量的包(bundle) - 通常只有一个包 - 可由浏览器加载。
对于 HTTP/1.1 客户端,打包应用会尤其强大,因为当浏览器发起一个新请求时,它能够最大限度地减少应用的等待次数。对于 HTTP/2,可以通过 webpack 使用代码拆分(Code Splitting)和打包实现最佳优化。
构建目标(Targets)
因为服务器和浏览器代码都可以用 JavaScript 编写,所以 webpack 提供了多种构建目标(target),你可以在webpack 配置中设置。
用法
要设置 target
属性,只需要在你的 webpack 配置中设置 target 的值。
webpack.config.js
module.exports = { target: 'node' };
在上面例子中,使用 node
webpack 会编译为用于「类 Node.js」环境(使用 Node.js 的 require
,而不是使用任意内置模块(如 fs
或 path
)来加载 chunk)。
每个target都有各种部署(deployment)/环境(environment)特定的附加项,以支持满足其需求。查看target 的可用值。
多个 Target
尽管 webpack 不支持向 target
传入多个字符串,你可以通过打包两份分离的配置来创建同构的库:
webpack.config.js
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)
模块热替换功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载页面。在独立模块变更后,无需刷新整个页面,就可以更新这些模块,极大地加速了开发时间。
这一切是如何运行的?
站在 App 的角度
- app 代码要求 HMR runtime 检查更新。
- HMR runtime (异步)下载更新,然后通知 app 代码更新可用。
- app 代码要求 HMR runtime 应用更新。
- HMR runtime (异步)应用更新。
可以设置 HMR,使此进程自动触发更新,或者选择要求在用户交互后进行更新。
站在编译器(webpack) 的角度
除了普通资源,编译器(compiler)需要发出 "update",以允许更新之前的版本到新的版本。"update" 由两部分组成:
- 待更新 manifest (JSON)
- 一个或多个待更新 chunk (JavaScript)
manifest 包括新的编译 hash 和所有的待更新 chunk 目录。
每个待更新 chunk 包括用于与所有被更新模块相对应 chunk 的代码(或一个 flag 用于表明模块要被移除)。
编译器确保模块 ID 和 chunk ID 在这些构建之间保持一致。通常将这些 ID 存储在内存中(例如,当使用 webpack-dev-server 时),但是也可能将它们存储在一个 JSON 文件中。
站在模块的角度
HMR 是可选功能,只会影响包含 HRM 代码的模块。举个例子,通过 style-loader
为 style 样式追加补丁。 为了运行追加补丁,style-loader
实现了 HMR 接口;当它通过 HRM 接收到更新,它会使用新的样式替换旧的样式。
类似的,当在一个模块中实现了 HMR 接口,你可以描述出当模块被更新后发生了什么。然而在多数情况下,不需要强制在每个模块中写入 HMR 代码。如果一个模块没有 HMR 处理函数,更新就会冒泡。这意味着一个简单的处理函数能够对整个模块树(complete module tree)进行处理。如果在这个模块树中,一个单独的模块被更新,那么整个模块树都会被重新加载(只会重新加载,不会迁移)。
站在 HMR Runtime 的角度 (Technical)
对于模块系统的 runtime,附加的代码被发送到 parents
和 children
跟踪模块。
在管理方面,runtime 支持两个方法 check
和 apply
。
check
发送 HTTP 请求来更新 manifest。如果请求失败,说明没有可用更新。如果请求成功,待更新 chunk 会和当前加载过的 chunk 进行比较。对每个加载过的 chunk,会下载相对应的待更新 chunk。当所有待更新 chunk 完成下载,就会准备切换到 ready
状态。
apply
方法将所有被更新模块标记为无效。对于每个无效模块,都需要在模块中有一个更新处理函数,或者在它的父级模块们中有更新处理函数。否则,无效标记冒泡,并将父级也标记为无效。每个冒泡继续直到到达应用程序入口起点,或者到达带有更新处理函数的模块(以最先到达为准)。如果它从入口起点开始冒泡,则此过程失败。
之后,所有无效模块都被(通过 dispose 处理函数)处理和解除加载。然后更新当前 hash,并且调用所有 "accept" 处理函数。runtime 切换回闲置
状态,一切照常继续。
能使用 HMR 做什么?
可以在开发过程中将 HMR 作为 LiveReload 的替代。webpack-dev-server 支持热模式,在试图重新加载整个页面之前,热模式会尝试使用 HMR 来更新。查看如何实现在 React 项目中使用 HRM 为例。
一些 loader 已经生成可热更新的模块。例如,style-loader
能够置换出页面的样式表。对于这样的模块,不需要做任何特殊处理。