webpack构建流程
从启动webpack构建到输出结果经历了一系列过程:
- 解析webpack.config.js配置参数,调用shell并追加命令行参数,通过 optimist将前两者参数整合成 options 对象传到了下一个流程的控制对象中
- 注册所有配置的插件,让插件监听webpack构建生命周期的事件节点,以做出对应的反应。
- 从配置的entry入口文件开始解析文件构建AST语法树,找出每个文件所依赖的文件,递归下去。
- 在解析文件递归的过程中根据文件类型和loader配置找出合适的loader用来对文件进行转换。
- 递归完后得到每个文件的最终结果,根据entry配置生成代码块chunk。
- 输出所有chunk到文件系统。
入口和上下文(entry and context)
entry
指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始
-
单入口
entry: './src/app.js'
等同于下面写法:
entry: {
main: './src/app.js'
}
-
对象语法
分离 应用程序(app) 和 第三方库(vendor) 入口
entry: {
app: './src/main.js',
vendor: './src/jquery.js'
}
这告诉我们 webpack 从 main.js 和 jquery.js 开始创建依赖图(dependency graph)。这些依赖图是彼此完全分离、互相独立的。
context
webpack 编译时的基础目录,entry和loader 会相对于此目录查找
默认值为当前目录,不建议修改
出口(output)
告诉 webpack 在哪里输出它所创建的 bundles
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/',
filename: 'js/[name].js'
}
publicPath
用于指定打包后的文件需要加载的外部资源(如图片、js、css等)的跟路径
默认值是一个空字符串 "",通常设置成"/"
静态资源最终访问路径 = output.publicPath + 资源loader或插件等配置路径
- loader 输出图片文件配置
{ name: 'imgs/[name].[ext]' }
// 那么图片最终的访问路径为
output.publicPath('/') + 'imgs/[name].[ext]' = '/imgs/[name].[ext]'
- plugin 提取css文件配置:
new ExtractTextPlugin('css/[name].[contenthash:10].css')
// html中加载css打包后代码
<link href="/css/app.9502b0c565.css" rel="stylesheet">
- html中加载js打包后代码
<script type="text/javascript" src="/js/runtime.4ece365fd5.js"></script>
path
打包文件输出的目录
建议绝对路径;默认值为当前路径。
path 中用使用 [hash] 模板可用于版本回归
output: {
path: path.resolve('./dist/[hash:8]/')
}
loader
loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)
注意:module.loaders 改为 module.rules;链式loader
- webpack1语法
module: {
loaders: [{
test: /\.less$/,
loader: "style!css!less"
})
}
- webpack2、3语法
module: {
rules: [{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
"less-loader"
]
}]
}
文件类
raw-loader 加载文件原始内容,比如.txt文件
file-loader 将文件发送到打包后文件夹中
{
test: /\.(gif|png|jpe?g|svg)$/,
use: [{
loader: 'file-loader',
options:{}
}]
}
默认输出到output的根目录下,name为32为hash值
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
name | {String | Funciton} | [hash].[ext] | 自定义文件名 |
placeholders
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
[ext] | {String} | file.extname | 资源扩展名 |
[name] | {String} | file.name | 资源名 |
[path] | {String} | file.dirname | 资源路径 |
[hash] | {String} | md5 | 内容的哈希值 |
url-loader 功能类似file-loader
对file-loader的扩展,可以设置小图片转换base64图片
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit:5000,
name: 'img/[name].[ext]?[hash]'
}
}
]
}
转换编译类
script-loader
在全局上下文(global context)执行一次 JS脚本,就像你在网页上通过<script>把它们引进来一样。
babel-loader
由于浏览器只能读懂ES5语法,需要babel将ES2015+语法编译为ES5语法
- 安装
npm install babel-loader babel-core babel-preset-env webpack
- 用法
{
test: /\.js$/,
use: {
loader: 'babel-loader',
exclude: /node_modules/,
options: {}
}
}
options
- cacheDirectory:默认值为 false。当有设置时,指定的目录将用来缓存 loader 的执行结果
- babelrc 默认true,设置false,.babelrc将不会启用
ts-loader 像 JavaScript 一样加载 TypeScript 2.0+
模版类
html-loader 解决html里加载图片问题
minimize:true 压缩html文件
handlebars-loader 加载handlebars文件并编译为html文件
handlebars-template-loader 解决handlebars图片路径问题
markup-inline-loader
将内联的 SVG/MathML 文件转换为 HTML。在应用于图标字体,或将 CSS 动画应用于 SVG 时非常有用
{
test: /\.html$/,
use: [
'html-loader',
'markup-inline-loader'
]
}
html中使用:
<img markup-inline src="./_images/camera.svg" />
<img data-markup-inline src="./_images/camera.svg" />
样式类
style-loader 将模块的导出作为样式添加到 DOM 中
css代码放入js代码里再加入到html里
css-loader 解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码
得到css代码
less-loader 加载和转译 LESS 文件
sass-loader 加载和转译 SASS/SCSS 文件
得到css文件
postcss 使用 PostCSS 加载和转译 CSS 文件
后处理css文件,对css文件做语法分析,真正的核心操作,依赖于postcss庞大的插件群体
postcss插件
比如css的语法验证,压缩,支持变量和混入语法
- autoprefixer 补全浏览器前缀
- postcss-import css文件中使用@import引入其他样式文件,但是使用autoprefixer发现,import进来的样式没有处理
postcss: function(webpack) {
return [
postcssImport({
addDependencyTo: webpack
}),
autoprefixer
]
}
清理和测试类
mocha-loader 使用 mocha 测试(浏览器/NodeJS)
eslint-loader 使用 ESLint 清理代码
jshint-loader 使用 JSHint 清理代码
框架类
vue-loader 加载和转译 Vue 组件
angular2-template-loader 加载和转译 Angular 组件
插件
loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量
html-webpack-plugin 生成html文件
- 为html文件中引入的外部资源如script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题
- 可以生成创建html入口文件,比如单页面可以生成一个html文件入口,配置N个html-webpack-plugin可以生成N个页面入口
new htmlWebpackPlugin(options)
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
title | {String} | `` | 用于生成的HTML文档的标题 |
filename | {String} | 'index.html' | 生成html的文件名 |
template | {String} | `` | 模版路径及文件名(路径相对与output.context) |
inject | {Boolean|String} | true | 当传递true或'body'所有JavaScript资源将被放置在正文元素的底部。'head'将脚本放置在head元素中,false不会将脚本放进html中 |
favicon | {String} | `` | 将给定的图标路径添加到输出HTML |
minify | {Boolean|Object} | true | 缩小输出html html-minifier |
hash | {Boolean} | false | true:将webpack所有包含的脚本和CSS文件附加一个独特的编译哈希。这对缓存清除非常有用 |
cache | {Boolean} | true | 仅在文件被更改时才发出文件 |
showErrors | {Boolean} | true | 错误细节将写入HTML页面 |
chunks | {?} | ? | 允许添加的chunk名 |
chunksSortMode | {String|Function} | auto | 允许控制chunk在被包含到HTML之前应该如何排序。允许的值是'none'不分类 | 'auto'按块ID排序 | 'dependency'通过其“父项”属性对块之间的依赖关系进行排序 | 'manual' | {Function} |
excludeChunks | {String} | `` | 允许您跳过一些chunk |
xhtml | {Boolean} | false | 如果true将link标签呈现为自动关闭(符合XHTML) |
- chunks按照顺序加载
chunksSortMode: function (chunk1, chunk2) {
var order = ['common', 'public', 'index'];
var order1 = order.indexOf(chunk1.names[0]);
var order2 = order.indexOf(chunk2.names[0]);
return order1 - order2;
}
extract-text-webpack-plugin
webpack 把所有的资源都当成了一个模块, CSS,Image, JS 字体文件资源, 都打包到一个 bundle.js 文件中
它将*.css输入块中的所有必需模块移动到单独的CSS文件中
new ExtractTextPlugin([id: string], filename: string, [options])
- id 该插件实例的唯一标志,一般是不会传的,其自己会生成。
- filename 文件名。可以是[name]、[id]、[contenthash] [name]:将会和entry中的chunk的名字一致 [id]:chunk 的数量 [contenthash]:根据内容生成hash值
- options allchunk:是否将所有额外的chunk都压缩成一个文件 disable:禁止使用插件 ignoreOrder: 禁用顺序检查 (这对 CSS 模块很有用!),默认 false
- extract 配置:
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",//编译后用什么loader来提取css文件
publicPath:, //重写此 loader 的 publicPath 配置
use: "css-loader" //需要什么样的loader去编译文件
})
}
]
}
CopyWebpackPlugin 拷贝资源插件
CopyWebpackPlugin([
{
context: 'global/img',
from: '**/*',
to:'img/common'
},
{
from: 'img',
to:'img'
},
{
from :'global/lib/es5-shim-sham.js'
}
])
from 定义要拷贝的源目录.
to 定义要拷盘的目标目录.
context 上下文.
flatten 只拷贝文件不管文件夹 默认是false.
ignore 忽略拷贝指定的文件 可以用模糊匹配.
force 强制覆盖先前的插件 可选 默认false.
UglifyJsPlugin 压缩js文件
new webpack.optimize.UglifyJsPlugin(options)
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
test | {RegExp|Array<RegExp>} | /.js$/i | 匹配文件 |
include | {RegExp|Array<RegExp>} | undefined | 包含目录 |
exclude | {RegExp|Array<RegExp>} | undefined | 非匹配目录 |
cache | {Boolean|String} | false | 启用文件缓存;cache: 'path/to/cache' |
parallel | {Boolean|Number} | false | 使用多进程并行运行来提高构建速度 |
sourceMap | {Boolean} | false | 使用源映射将错误消息位置映射到模块(这会减慢编译速度) ⚠️ cheap-source-map 选项不适用于这个插件 |
uglifyOptions | {Object} | {...defaults} | uglify 选项 |
webpack.optimize.UglifyJsPlugin()遇到的
Unexpected token: operator (>) from UglifyJs
问题可以使用 uglifyjs-webpack-plugin插件
DefinePlugin 允许在编译时(compile time)配置的全局常量
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
CleanWebpackPlugin 打包前清理dist目录
new CleanWebpackPlugin(['./dist'])
webpack自带插件
CommonsChunkPlugin 提取 chunks 之间共享的通用模块
webpack将多个模块打包之后的代码集合称为chunk
将公共js代码提取到单独文件里
- 是一个可选的用于建立一个独立文件(又称作 chunk)的功能
- 通过将公共模块拆出来,最终合成的文件能够在最开始的时候加载一次,便存到缓存中供后续使用
- 语法:
new webpack.optimize.CommonsChunkPlugin(options)
- 配置
- 决定生成chunk的参数: name, names, async
- name: string: 公共chunk的名字
- names: string[]: 和name一样,不过传入的是一个数组
- async: boolean|string: 把公共代码提取到一个懒加载的chunk,在被使用到时才进行下载
- 决定被提取的chunk: chunks, children, deepChildren
- 决定提取条件: minChunks
- minChunks: number|infinity|function(module,count)->boolean: 如果传入数字(默认值为3),就是告诉webpack,只有当模块重复的次数大于等于该数字时,这个模块才会被提取出来;Infinity表示不会打包多余的第三方库;当传入为函数时,所有符合条件的chunk中的模块都会被传入该函数做计算,返回true的模块会被提取到目标chunk
module.exports = {
entry: {
main1: '/src/main1.js',
main2: '/src/main2.js',
jquery:["jquery"],
vue:["vue"]
},
plugins: [
new CommonsChunkPlugin({
name: ["common",'jquery','vue'],//对应于上面的entry的key
minChunks:2
})
]
};
打包后jquery和vue会生成独立chunk,main1和main2中的公共业务模块会打包到common.js中;minChunks为infinity时,公共业务模块会分别打包到main1.js和main2.js中
webpack用插件CommonsChunkPlugin进行打包的时候 将符合引用次数(minChunks)的模块打包到name参数的数组的第一个块里(chunk),然后数组后面的块依次打包,最后一个块包含webpack生成的在浏览器上使用各个块的加载代码
HotModuleReplacementPlugin 热模块替换
new webpack.HotModuleReplacementPlugin()
devServer: {
hot: true // 激活服务器的HMR
}
其他
cross-env
在不同系统环境下设置变量
webpack-dev-server
- 开发环境用于调试报错信息等,生成一个开发用的服务器,在文件有变化的时候自动给我们打包,然后刷新页面
- 它还有个模块热替换的功能 .. 就是它可以只替换有变化的地方 .. 不需要刷新整个页面 ...
优化
优化输出
压缩css
css-loader?minimize
去除css文件里有很多空格和tab
tree-shaking
借助es6 import export
语法静态性的特点来删掉export但是没有import过的东西
- 配置babel让它在编译转化es6代码时不把import export转换为cmd的module.export,配置如下:
"presets": [
[
"es2015",
{
"modules": false
}
]
]
优化 UglifyJsPlugin
new UglifyJsPlugin({
// 最紧凑的输出
beautify: false,
// 删除所有的注释
comments: false,
compress: {
// 在UglifyJs删除没有用到的代码时不输出警告
warnings: false,
// 删除所有的 `console` 语句
// 还可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
})
用imagemin-webpack-plugin
压缩图片
更快的构建
缩小文件搜索范围
- 配置模块库.
在js里出现import 'redux'这样不是相对也不是绝对路径的写法时会去node_modules目录下找。但是默认的配置会采用向上递归搜索的方式去寻找node_modules,但通常项目目录里只有一个node_modules在项目根目录
module.exports = {
resolve: {
modules: [path.resolve(__dirname, 'node_modules')]
}
};
- 配置loader
{
test: /\.js$/,
loader: 'babel-loader',
include: path.resolve(__dirname, 'src')
}
开启 babel-loader 缓存
babel编译过程很耗时,好在babel-loader提供缓存编译结果选项,在重启webpack时不需要创新编译而是复用缓存结果减少编译流程。babel-loader缓存机制默认是关闭的,打开的配置如下:
{
test: /\.js$/,
loader: 'babel-loader?cacheDirectory',
}
使用 alias
resolve.alias 配置路径映射。
发布到npm的库大多数都包含两个目录,一个是放着cmd模块化的lib目录,一个是把所有文件合成一个文件的dist目录,多数的入口文件是指向lib里面下的。
默认情况下webpack会去读lib目录下的入口文件再去递归加载其它依赖的文件这个过程很耗时,alias配置可以让webpack直接使用dist目录的整体文件减少文件递归解析。配置如下:
module.exports = {
resolve: {
alias: {
'moment': 'moment/min/moment.min.js',
'react': 'react/dist/react.js',
'react-dom': 'react-dom/dist/react-dom.js'
}
}
};
使用 noParse
module.noParse
配置哪些文件可以脱离webpack的解析。
有些库是自成一体不依赖其他库的没有使用模块化的,比如jquey、momentjs、chart.js,要使用它们必须整体全部引入。
webpack是模块化打包工具完全没有必要去解析这些文件的依赖,因为它们都不依赖其它文件体积也很庞大,要忽略它们配置如下:
module.exports = {
module: {
noParse: /node_modules\/(jquey|moment|chart\.js)/
}
};