开始
官网是最好的学习资料,本篇文章略过 入门配置这些内容,整理了一些常用的配置点。
在 webpack 打包过程查询的依赖关系:
- ES2015 import 语句
- CommonJS require() 语句
- AMD define 和 require 语句
- css/sass/less 文件中的 @import 语句。
- 样式(url(...))或 HTML 文件
()
中的图片链接
从入口起点(entry) 开始,webpack 根据文件的这些依赖递归地构建一个依赖图 ,将这个依赖图中所有这些模块打包输出(output)为少量的 bundle 到 /dist 或 /build 目录中。
一、module 的加载
webpack 默认从入口开始,一切文件都是模块,通过 module 配置处理各种类型的文件(js, css, sass, jpg, png....)。
加载 webpack 依赖图中的不同类型模块,需要不同的预处理器(loader)。果不加loader 会有“ ModuleParseError: Module parse failed "等错误。
需要在 module.rules
中为各类文件配置 loader,例如加载 css 和图片时 loader 安装和配置:
npm install --save-dev style-loader css-loader file-loader
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader','css-loader']
},
{
test: /\.(png|svg|jpg|gif)$/,
use: ['file-loader']
}
]
}
};
在打包中遇到非JS模块时,会在 module.rules
规则中匹配文件后缀名, 然后用 Rules.use 指定的的预处理器(loader)解析该文件。use 加载器可以链式传递,从右向左进行应用到模块上。
可以尝试打包自定义文件类型,只需要安装配置对应的预处理器(loader)。
忽略匹配的文件
有一种场景是项目依赖的资源不需要加载解析,例如一些地图的API或JS库。
这时可以使用 module.noParse
防止 webpack 解析与正则表达式相匹配的文件,忽略的文件中的 import, require, define 等导入机制,配置方式如下:
module: {
noParse: /jquery|lodash/,
// 从 webpack 3.0 开始,可以使用函数,如下所示:
// noParse: function(content) {
// return /jquery|lodash/.test(content);
// }
}
忽略大型的 library 可以提高构建性能。
二、优化 CSS 相关配置
2.1 PostCss 预处理
PostCss 是一个 CSS 的预处理工具,可以帮助我们给 CSS3 的属性添加前缀(autoprefixer),样式格式校验(stylelint),提前使用 css 的新特性(postcss-cssnext),更重要的是可以实现 CSS 的模块化,防止 CSS 样式冲突。
安装 postcss-loader 和 一些 PostCss 的插件:
npm i -D postcss-loader
npm i -D autoprefixer postcss-cssnext
可以给 postcss-loader 设置多个插件:
rules: [
{
test: /\.(sc|c|sa)ss$/,
use: ['style-loader', 'css-loader', 'sass-loader',
{
loader: "postcss-loader",
options: {
ident: 'postcss',
sourceMap: true,
plugins: loader => [
require('autoprefixer')(),
require('postcss-cssnext')()
]
}
}
]
}
]
PostCss 还有很多丰富的插件可以使用。
2.2 样式拆分提取
在生产环境下将样式表抽离成专门的单独文件,使每个包含css的js文件都会创建一个CSS文件,支持按需加载css。
webpack4 使用 mini-css-extract-plugin 插件拆分样式, webpack3 之前版本可以用 extract-text-webpack-plugin 插件:
npm i -D mini-css-extract-plugin
使用 mini-css-extract-plugin 就不能再用 style-loader 注入到 html 中了。除了修改 module 配置,还需要修改plugins配置:
// webpack.product.config.js
module: {
rules: [ // 规则数组,修改模块的创建方式
{
test: /\.(sc|c|sa)ss$/, // 正则表达式,处理scss sass css
use: [
{
MiniCssExtractPlugin.loader,
options: {
// 这里可以指定一个 publicPath
// 默认使用 webpackOptions.output中的publicPath
publicPath: '../'
}
},
'css-loader',
//...
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css', // 设置最终输出的文件名
chunkFilename: '[id].css'
})
]
其中的 name 是配置 output.filename 时的 name,
可以配置 pakeage.json 中的 script 命令运行生产环境下的打包配置,使用--config 指定webpack的执行脚本
"scripts": {
"build": "npx webpack",
"dist": "npx webpack --config webpack.product.config.js"
}
打包后新增 dist/main.css,而 html 中的样式失效了,因为没有 style 注入了,这时需要手动在 html 中引入 main.css 文件。
注意这个插件应该只用在 mode: 'production' 配置中,并且在 loaders 链中不能使用 style-loader, 特别是不支持开发时的 HMR。
对于 JS 代码拆分可以使用 CommonsChunkPlugin 插件,配置方式都类似。
2.3 文件压缩
webpack4 可以使用插件 optimize-css-assets-webpack-plugin 压缩文件,注意 webpack4 才有minimizer这个配置项:
npm i -D optimize-css-assets-webpack-plugin
// webpack.product.config.js
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
//...
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})] // // 压缩 css 文件
},
plugins: [
new MiniCssExtractPlugin({ // 拆分 CSS
filename: "[name].css",
chunkFilename: "[id].css"
}),
],
}
同理压缩 JS 也需要一个插件 uglifyjs-webpack-plugin, 这些压缩插件插件需要一个前提就是:mode: 'production':
npm i -D uglifyjs-webpack-plugin
// webpack.product.config.js
+ const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); // 压缩js
module.exports = {
...
optimization: {
minimizer: [
+ new UglifyJsPlugin({ // 压缩 js
cache: true,
parallel: true,
sourceMap: true // set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({}) // 压缩 css 文件
]
}
···
};
这里需要注意的是,如果没有 bable 兼容 ES6 语法,则会报" ERROR Unexpected token "等。
三、图片处理与优化
3.1 加载图片与字体
使用file-loader处理文件的导入:
npm install --save-dev file-loader
module.exports = {
...
module: {
rules: [
...
{
test: /\.(png|svg|jpg|gif)$/,
use: ['file-loader']
}
···
]
}
}
由于 css 中可能引用到自定义的字体,处理也是跟图片一致。
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [ 'file-loader' ]
3.2 压缩图片
file-loader将图片拷贝到dist目录并更新引用路径,进一步使用 image-webpack-loader 对图片进行压缩和优化,按照 NPM 官网文档进行安装配置:
npm install image-webpack-loader --save-dev
module.exports = {
...
module: {
rules: [
...
{
test: /\.(png|svg|jpg|gif)$/,
use: ['file-loader',
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false,
},
pngquant: {
quality: '65-90',
speed: 4
},
gifsicle: {
interlaced: false,
},
// the webp option will enable WEBP
webp: {
quality: 75
}
}
}
]
}
···
]
}
}
原始图片content-length: 557478,大概557K,重新打包压缩后171K
3.3 小图片处理为 base64 减少 http 请求
url-loader 功能类似于 file-loader,可以把 url 地址对应的文件,打包成 base64 的 Data URI scheme,提高访问的效率。使用base64编码的图片和超链接方式的代码分别如下:
对于比较小的图片可以直接打包成 base64 从而减少http请求次数,在最新版的浏览器特别是移动端对base64的兼容性都非常好,可以放心使用。
npm install --save-dev url-loader
module.exports = {
...
module: {
rules: [
...
{
test: /\.(png|svg|jpg|gif)$/,
use: [
// 'file-loader',
{
loader: 'url-loader', // 根据图片大小,把图片优化成base64
options: {
limit: 10000 // 10KB
}
},
···
]
}
···
]
}
}
四、解决缓存问题
4.1 配置文件 Hash 名
因为浏览器的缓存策略,如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就会使用它的缓存版本,所以在打包新版本后浏览器可能依旧使用之前的文件。
解决缓存的问题,一种方式就是修改文件名,每次打包文件都更新文件名。在出口(output)和管理输出的 plugins 中配置文件的 Hash 名:
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'main.[hash].js',
path: path.resolve(__dirname, './dist')
},
···
plugins: [
new MiniCssExtractPlugin({ // 拆分css
filename: '[name].[hash].css', // 设置最终输出的文件名
chunkFilename: '[id].[hash].css'
})
],
···
};
修改filename: '[name].[hash].css'
> npx webpack --config webpack.product.config.js
Hash: 10c0c7348792960894f6
Version: webpack 4.30.0
这时 dist/main.10c0c7348792960894f6.css出现。
4.2 文件 Hash 名的注入
在前面拆分CSS或JS为单独文件时,HTML是手动引入的,但Hash生成的文件每次都不同,要如何自动引入?
使用 HtmlWebpackPlugin 插件,可以把打包后的 CSS 或者 JS 文件引用直接注入到 HTML 模板中,这样就不用每次手动修改文件引用了。
npm i -D html-webpack-plugin
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
···
module.exports = {
mode: 'production',
···
plugins: [
···
+ new HtmlWebpackPlugin({
title: 'Learn webpack', // 默认值:Webpack App
filename: 'index.html', // 最终生成的文件,默认值: 'index.html'
template: path.resolve(__dirname, 'src/index.html'), // 模版
minify: {
collapseWhitespace: true, // 折叠空白
removeComments: true, // 移除注释
removeAttributeQuotes: true // 移除属性的引号
}
})
],
···
};
可以在新建 src/index.html 作为模版,执行打包命令:
> npm run dist
> [email protected] dist /Users/TJing/Documents/webProjects/testWebpack
> npx webpack --config webpack.product.config.js
Hash: b6f4a880e0371e2f8ad3
最终打包生成 main.b6f4a880e0371e2f8ad3.css、main.b6f4a880e0371e2f8ad3.js 和 index.html,此时HTML自动注入了CSS和JS:
4.3 清理 dist 打包目录
每次编译后 /dist 文件夹都会保存生成的文件,特别是配置文件民Hash值后。通常推荐在每次构建前清理 /dist 文件夹。
clean-webpack-plugin 是一个比较普及的管理插件,安装和配置如下:
npm i -D clean-webpack-plugin
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
mode: 'production',
···
plugins: [
+ new CleanWebpackPlugin()
...
],
···
};
五、开发辅助
5.1 Sourse map 源码追踪
webpack会把三个源文件(a.js, b.js 和 c.js)打包到一个 bundle(bundle.js)中,在开发环境下很难 debug 到原始的开发代码,这时就要使用 source map 解决开发代码与实际运行代码不一致的问题。
在 devtool 设置 source map 增强调试过程,source map 类型传送门。
// webpack.config.js
module.exports = {
entry: './src/index.js',
// ...
devtool: 'inline-source-map',
};
还可以使用 SourceMapDevToolPlugin 插件替代 devtool 选项进行更细粒度的配置;
对于样式调试,webpack 会把 css、sass 模块打包到 style 模块中,css-loader 和 sass-loader 都可以通过该 options 设置启用 sourcemap。
// webpack.config.js
rules: [ // 规则数组,修改模块的创建方式
{
test: /\.(sc|c|sa)ss$/, // 正则表达式,处理scss sass css
// use: ['style-loader', 'css-loader','sass-loader']
use: ['style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true}},
{
loader: 'sass-loader',
options: {
sourceMap: true}}]
}
]
5.2 --watch 自动编译
每次修改完毕后都手动编译太麻烦,最简单解决的办法就是启动 watch 监控更新自动编译。
可以在启动编译时增加 --watch 开启
"scripts": {
"watch": "npx webpack --watch --config webpack.dev.js",
···
},
增加 --watch 后,每次修改后手动刷新浏览器页面,就可以看到更新内容。但如何能不刷新页面,自动更新变化呢?
5.3 webpack-dev-server 热更新
使用 webpack-dev-server ,实际就是创建一个简单的 web 服务器,能够实时重新加载(live reloading)。
npm install --save-dev webpack-dev-server
配置只在开发环境,,除了要配置 devServer 属性,还需要在 plugins 中增加两个插件:
const webpack = require('webpack');
module.exports = {
mode: 'development',
devtool: 'inline-source-map', // js 的 sourcemap
devServer: {
contentBase: './dist',
hot: true,
port: 9000
},
plugins: [
new webpack.NamedModulesPlugin(), // 更容易查看(patch)的依赖
new webpack.HotModuleReplacementPlugin() // 替换插件
]
···
执行以下命令就会在 http://localhost:9000/ 中看到主页面。注意到,使用 webpack-dev-server 编译后的文件直接在内存中,不会输出到dist目录,但可以使用dist目录中的文件。
npx webpack-dev-server --config webpack.dev.js
dev-server官网 中其他的配置:
devServer: {
clientLogLevel: 'warning', // 可能的值有 none, error, warning 或者 info(默认值)
hot: true, // 启用 webpack 的模块热替换特性, 这个需要配合: webpack.HotModuleReplacementPlugin插件
contentBase: path.join(__dirname, "dist"), // 告诉服务器从哪里提供内容, 默认情况下,将使用当前工作目录作为提供内容的目录
compress: true, // 一切服务都启用gzip 压缩
host: '0.0.0.0', // 指定使用一个 host。默认是 localhost。如果你希望服务器外部可访问 0.0.0.0
port: 8080, // 端口
open: true, // 是否打开浏览器
overlay: { // 出现错误或者警告的时候,是否覆盖页面线上错误消息。
warnings: true,
errors: true
},
publicPath: '/', // 此路径下的打包文件可在浏览器中访问。
proxy: { // 设置代理
"/api": { // 访问api开头的请求,会跳转到 下面的target配置
target: "http://192.168.0.102:8080",
pathRewrite: {"^/api" : "/mockjsdata/5/api"}
}
},
quiet: true, // necessary for FriendlyErrorsPlugin. 启用 quiet 后,除了初始启动信息之外的任何内容都不会被打印到控制台。这也意味着来自 webpack 的错误或警告在控制台不可见。
watchOptions: { // 监视文件相关的控制选项
poll: true, // webpack 使用文件系统(file system)获取文件改动的通知。在某些情况下,不会正常工作。例如,当使用 Network File System (NFS) 时。Vagrant 也有很多问题。在这些情况下,请使用轮询. poll: true。当然 poll也可以设置成毫秒数,比如: poll: 1000
ignored: /node_modules/, // 忽略监控的文件夹,正则
aggregateTimeout: 300 // 默认值,当第一个文件更改,会在重新构建前增加延迟
}
}
如果想要重温Webpack的基础配置可以从官网指南入手,想要了解更复杂灵活的功能可以添加各种插件,如果有兴趣自己写一个loader,送个666给你~