概念
本质上,webpack
是一个现代javascript
应用程序的静态模块打包器(module bundler).当webpack
处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle
.
主要有四个核心概念:
- 入口
retry
- 输出
output
- loader
- 插件
plugins
入口(retry)
入口起点(retry point)指示webpack
应该使用哪个模块来作为构建其内部依赖图的开始.进入入口起点后,webpack
会找出有哪些模块和库是入口起点(直接和间接)依赖的.每个依赖项随机被处理,最后输出到称之为bundles
的文件中.
- 单个入口语法(字符串形式)
const config = {
entry: './path/to/my/entry/file.js'
};
module.exports = config;
相当于
const config = {
entry: {
main: './path/to/my/entry/file.js'
}
};
当向entry
传递一个数组时,将创建"多个主入口".在需要多个依赖文件一起注入,并且将它们的依赖导向到一个"chunk"时.
- 对象语法
const config = {
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
}
};
对象语法是应用程序中定义入口的最可拓展的方式.
- 常见场景
分离 应用程序(app) 和 第三方库(vendor) 入口.
这种方式比较常见于只有一个入口起点(不包括vendor)的单页应用程序(spa)中.此设置允许你使用CommonsChunkPlugin
从[应用程序bundle]中提取vendor
引用(vendor reference)到vendor bundle. - 多页面应用程序
const config = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
}
};
这是告诉webpack需要3个独立分离的依赖图
输出(output)
即使可以存在多个入口起点,但只能指定一个输出配置
- 用法
最低要求是将输出的值设置为一个对象,包括以下两个属性
filename
用于输出文件的文件名
目标输出目录path
的绝对路径 - 多个入口起点
如果配置了创建了多个单独的"chunk",则应该使用占位符
来确保每个文件具有唯一的名称
{
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
}
- 高级进阶
output: {
path: "/home/proj/cdn/assets/[hash]",
publicPath: "http://cdn.example.com/assets/[hash]/"
}
在编译时不知道最终输出文件的 publicPath 的情况下,publicPath 可以留空,并且在入口起点文件运行时动态设置.如果你在编译时不知道 publicPath,你可以先忽略它,并且在入口起点设置__webpack_public_path__
loader
loader用于对模块的源代码进行转换.loader可以使你在import
或"加载"模块时预处理文件.因此,loader类似于其它构建工具中的"任务(task)",并提供了处理前端构建步骤的强大方法.
- 使用loader
- 配置(推荐): 在
webpack.config.js
文件中指定loader - 内联: 在每个
import
语句中显示指定loader - CLI: 在shell命令中指定它们
- 配置(推荐): 在
- 配置[Configuration]
module.rules
荀彧你在webpack设置中指定多个loader. - 内联
可以在import
语句或任何等效于"import"的方式中指定loader.使用!
将资源中的loader分开
import Styles from 'style-loader!css-loader?modules!./styles.css';
通过前置所有规则以及使用!
,可以对应覆盖到配置的任意loader
- CLI
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
这会对 .jade
文件使用 jade-loader
,对 .css
文件使用 style-loader
和 css-loader
- loader特新
- 支持链式传递,能够对资源使用流水线
- 可以是同步的,也可以是异步的
- 运行在node.js中,并且能够执行任何可能的操作
- 接受查询参数,用于传递配置
- 也能够使用
options
对象进行配置 - 处理使用
package.json
常见的main属性,可以将普通的npm模块导出为loader,做法是在package.json
中定义一个loader字段 - 插件可以为loader带来更多特性
- loader能够产生额外的任意文件
- 解析loader
loader遵循标准的模块解析,多数情况下,loader将从模块路径解析.
loader模块需要导出为一个函数,并且使用Node.js兼容的JavaScript编写.通常使用npm进行管理,但是也可以将自定义loader作为应用程序中的文件
插件(Plugins)
webpack的支柱功能,其自身也是构建于你在webpack配置中用到的相同的插件系统之上
插件目的在于解决与loader无法实现的其他事
- 剖析
webpack插件是一个具有apply
属性的JavaScript对象.apply
属性会被webpack compiler调用,并且compiler对象可在整个编译声明周期访问 - 用法
由于插件可以携带参数/选项,你必须在webpack配置中,向plugins
属性传入new
实例 - 配置
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
]
- Node API
即便使用Node API,用户也应该在配置中传入plugins属性.webpack compiler并不是推荐的使用方式
some-node-script.js
const webpack = require('webpack'); //访问 webpack 运行时(runtime)
const configuration = require('./webpack.config.js');
let compiler = webpack(configuration);
compiler.apply(new webpack.ProgressPlugin());
compiler.run(function(err, stats) {
// ...
});
配置
webpack配置是标准的Node.js CommonJS模块:
- 通过
require(...)
导入其他文件 - 通过
require(...)
使用npm的工具函数 - 使用JavaScript控制流表达式,例如三元表达式
- 对常用值使用常量或变量
- 编写并执行函数来生成部分配置
模块
在模块化编程中,开发者将程序分解成离散功能块,并称之为模块
- 什么是webpack模块
对比Node.js模块,webpack模块能够以各种方式表达它们的依赖关系- ES2015
import
语句 - CommonJS
require()
语句 - AMD
define
和require
语句 - css/sass/less文件中的
@import
语句 - 样式(
url(...)
)或html文件(<img src=...>
)中的图片链接
- ES2015
- 支持的模块类型
webpack通过loader可以支持各种语句和预处理器编写模块.loader描述了webpack如何处理 非JavaScript模块,并且在bundle中引入这些依赖
模块解析
resolver是一个库,用于帮助找到模块的绝对路径.一个模块可以作为另一个模块的依赖模块,然后被后者引用.当打包模块时,webpack使用enhanced-resolve
来解析文件路径
-
webpack中的解析规则
使用enhanced-resolve
,webpack能够解析三种文件路径
1 绝对路径
2 相对路径
这种情况下,使用import
或require
的资源文件所在的目录认为是上下文目录.在import/require
中给定的相对路径会添加上下文路径来产生模块的绝对路径
3 模块路径
模块将在resolve.modules
中指定的所有目录内搜索.可以使用resolve.alias
配置选项来创建一个别名.
一旦根据上述规则解析路径后,解析器(resolve)将检查路径是否指向文件或目录.
如果路径指向一个文件:- 如果路径具有文件拓展名,则被直接打包
- 否则,将使用
resolve.extensions
选项作为文件拓展名来解析(接受哪些拓展名)
如果路径指向一个文件夹:
- 如果文件夹包含
package.json
文件,则按照顺序查找resolve.mainFiles
配置选项中指定的字段.并且package.json
中的第一个这样的字段确定文件路径. - 如果
package.json
文件不存在或者文件中的main字段没有返回一个有效路径,则按照顺序查找resolve.mainFiles
配置选项中指定的文件名,看是否能在import/require目录下匹配到一个存在的文件名 - 文件拓展名通过
resolve.extensions
选择采用类似的方法进行解析
webpack根据构建目标为这些选项提供了合理的默认配置
构建目标
因为服务器和浏览器代码都可以使用JavaScript编写,所以webpack提供了多种构建目标.
- 用法
只需要在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
文件
Manifest
使用webpack构建的典型应用程序或站点中,有三种主要的代码类型
- 编写的源码
- 源码依赖的任何第三方的library或vendor代码
- webpack的runtime和manifest,管理所有模块的交互
- Runtime
包含:在模块交互时,连接模块所需的加载和解析逻辑.包括浏览器中的已加载模块的连接以及懒加载模块的执行逻辑 - Manifest
当编译器开始执行,解析和映射应用程序时,它会保留所有模块的详细要点,这个数据集合成为"Manifest",当完成打包发送到浏览器时,会在运行时通过Manifest来解析和加载模块.无论选择哪种模块语法,那些语句现在都已经转换为__webpack_require__
方法,此方法指向模块标识符.通过使用manifest中的数据,runtime将能够查询模块标识符,检索出背后对应的模块.
模块热替换
在应用程序运行过程中替换,添加或删除模块,而无需重新加载整个页面.主要通过以下几种方式:
- 保留在完全重新加载页面时丢失的应用程序状态
- 只更新变更内容,以节省宝贵的开发时间
- 调整样式更加快速