一、概述
对于像Browserify这样的打包工具而言,仅仅能够处理 JavaScript。然而我们的工程不仅仅有 JavaScript,还有模板、样式文件、图片等等其它类型的资源,这就意味着我们还需要使用别的工具去管理它们。在 Webpack 的思想中一切皆模块,所有模板、样式、图片等等这些资源都是模块,因为这些资源也具备模块的特性——它们都负责特定的职能,并且具有可复用性。因此,我们可以使用 Webpack 去管理所有这些资源,并且把它们都当做模块来处理。
静态资源指前端中常用的图片、富媒体(Video、Audio 等)、字体文件等。Webpack 中静态资源也是可以作为模块直接使用的。Webpack 提供了很多插件和 loader 对图片进行压缩、合并(CSS Sprite)。Webpack 还会使用url-loader等插件,将较小的资源通过 Base64 的方式引入。当项目足够大了之后,配置太多的静态资源处理流程也会影响 Webpack 的打包速度,想突破压缩和合并这类前端常见优化,我们可以通过让UI工程师提供最优图片格式等方式来人工解决。当然如果没专人优化,为避免上到线上图片太大,那么可以使用 Webpack 来优化图片。
1) 图片:借助 loader 让 Webpack 识别图片并且能够打包输出,比如file-loader和url-loader。
2) SVG图片:SVG 文件是纯文本,借助svg-url-loader 来打包;
3) 压缩-图片优化:借助image-webpack-loader来对使用到的图片进行优化。它支持 JPG、PNG、GIF 和 SVG 格式的图片,必须和url-loader以及svg-url-loader一起使用。
4) 合并-CSS雪碧图:借助 PostCSS 的postcss-sprites来给图片做雪碧图,经过简单的配置之后,生成雪碧图就是全自动的过程了。
5) 字体、富媒体文件:对于字体、富媒体等静态资源可以直接使用url-loader或者file-loader进行配置即可,不需要额外的操作。
6) 数据:要导入 CSV,TSV 和 XML,可以使用csv-loader和xml-loader。
二、webpack相关配置
2.1 图片loader
file-loader:能够根据配置项复制使用到的资源(不局限于图片)到构建之后的文件夹,并且能够更改对应的链接;The file-loader resolves import/require() on a file into a url and emits the file into the output directory.
url-loader:能够根据配置将符合配置的文件转换成 Base64 方式引入,将小体积的图片 Base64 引入项目可以减少 http 请求,也是一个前端常用的优化方式。A loader for webpack which transforms files into base64 URIs.
{
test: /.(png|svg|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 3*1024 // 3k 不超过一定限制的图片才使用Base64,
// 若超过了limit=3*1024不会被处理成Base64,直接用file-loader处理。
}
}
}
2.2 svg-url-loader:工作原理类似url-loader,除了它利用URL encoding而不是 Base64 对文件编码。对于 SVG 图片这是有效的,因为 SVG 文件恰好是纯文本,这种编码规模效应更加明显,使用方法如下:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /.svg$/,
loader: 'svg-url-loader',
options: {
limit: 10 * 1024, // 小于 10kB(10240字节)的内联文件
noquotes: true // 移除 url 中的引号(在大多数情况下它们都不是必要的)
}
}
]
}
}; // svg-url-loader 拥有改善 IE 浏览器支持的选项,但是在其他浏览器中更糟糕。如果你需要兼容 IE 浏览器,设置 iesafe: true选项。
2.3 img-webpack-loader:压缩图片
# 安装
npm install image-webpack-loader --save-dev
这个 loader 不能将图片嵌入应用,所以它必须和url-loader以及svg-url-loader一起使用。为了避免同时将它复制粘贴到两个规则中(一个针对 JPG/PNG/GIF 图片, 另一个针对 SVG ),我们使用enforce:'pre'作为单独的规则涵盖在这个 loader:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /.(jpe?g|png|gif|svg)$/,
loader: 'image-webpack-loader',
// 这会应用该 loader,在其它之前
enforce: 'pre' // 提高了 img-webpack-loader 的优先级,保证在url-loader和svg-url-loader之前就完成了图片的优化。
}
]
}
};
2.4 postcss-sprites:
CSS 使用小图标图片的时候,我们经常做的优化项目是将小图标的图片合并成雪碧图(CSS Sprite),雪碧图的好处是将页面用到的小图片合并到一张大图中,然后使用background-position重新定位,这样节省了 HTTP 的请求数。在 Webpack 中我们可以借助 PostCSS 来给图片做雪碧图,经过简单的配置之后,生成雪碧图就是全自动的过程了。
npm install postcss-sprites -D
# 如果没有安装 postcss-loader 那么也安装它
npm install postcss-loader -D
修改 PostCSS 的postcss.config.js,增加插件的调用:
// postcss.config.js
const postcssSprites = require('postcss-sprites');
module.exports = {
plugins: [
postcssSprites({
spritePath: './src/assets/img/' // 把合成后的雪碧图放在这个路径下
})
]
};
修改webpack.config.js在css-loader之前配置上postcss-loader(注意 loader 加载顺序,从后往前):
{
test: /.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader'
}
]
}
2.5 字体、富媒体文件:对于字体、富媒体等静态资源可以直接使用url-loader或者file-loader进行配置即可,不需要额外的操作。如果不需要 Base64,那么可以直接使用 file-loader,需要的话就是用url-loader
,还需要注意,如果将正则(test)放在一起,那么需要使用[ext]
配置输出的文件名。
{
// 文件解析
test: /.(eot|woff|ttf|woff2|appcache|mp4|pdf)(?|$)/,
loader: 'file-loader',
query: {
// 这么多文件,ext不同,所以需要使用[ext]
name: 'assets/[name].[hash:7].[ext]'
}
},
2.6 数据:如果我们项目需要加载的类似 JSON、CSV、TSV 和 XML 等数据,那么我们需要单独给它们配置相应的 loader。对 JSON 的支持实际上是内置的,类似于 Node.js,这意味着import Data from './data.json'导入数据默认情况将起作用。要导入 CSV,TSV 和 XML,可以使用csv-loader和xml-loader。
首先是安装它们的 loader: npm install xml-loader csv-loader --save-dev,然后增加文件 loader 配置如下:
{
test: /.(csv|tsv)$/,
use: [
'csv-loader'
]
},
{
test: /.xml$/,
use: [
'xml-loader'
]
}
现在可以导入这四种类型的数据中的任何一种(JSON,CSV,TSV,XML),并且导入它的 Data 变量将包含已解析的 JSON 以便于使用。
三、代码实践
3.1 图片loader
3.1.1 新建如下结构新项目文件夹
├── src
│ ├── assets
│ │ └── img
│ │ ├── large.png # 大图片-超过1
│ │ └── small.png # 小图片
│ ├── index.css
│ ├── index.html
│ └── index.js # webpack配置
└── webpack.config1.js
webpack.config1.js
const path = require('path')
module.exports = {
resolve: {
alias: {
'@assets': path.resolve('./src/assets') // 使用别名简化相对路径
}
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>静态资源</title>
</head>
<body class="bg-img">
<img src="~@assets/img/large.png" width="200" height="80" alt="背景图">
</body>
</html>
index.js
import img from './assets/img/large.png';
import style from './index.css';
console.log('index.js', img, style);
index.css
.bg-img {
background: url(~@assets/img/small.png) no-repeat;
}
3.1.2 当前文件夹下执行npm init初始化package.json,并下载相关依赖:
npm init
npm install webpack webpack-cli --save-dev
npm install file-loader url-loader --save-dev
npm install style-loader css-loader --save-dev
npm install html-loader html-webpack-plugin --save-dev
3.1.3 完善webpack.config1.js文件:
const path = require('path')
const HTMLPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
resolve: {
alias: {
'@assets': path.resolve(__dirname, './src/assets')
}
},
module: {
rules: [
{
test: /.html$/,
use: ['html-loader']
},
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.(png|svg|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 3*1024
}
}
}
]
},
plugins: [
new HTMLPlugin({
template: './src/index.html'
})
]
}
3.1.4 package.json文件里scripts添加webpck命令:
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
+ "build1": "webpack --config webpack.config1.js"
},
3.1.5 执行npm run build1命令(注:期间若报错sh: webpack: command not found,建议尝试npm uninstall webpack再重新下载npm install webpack --save-dev)
可以看到1.2MB的large.png因为超过了limit=3*1024所以没有被处理成Base64,其地址被webpack自动替换成了新的路径18d30cbadbf7750aff266ee9376b1401.png。而2KB的small.png则被打包成了Base64路径。
3.2 SVG图片loader
3.2.1 在例3.1工程文件夹的基础上操作,删除dist打包文件夹,新增一个SVG文件并在css文件中使用它。
├── package-lock.json
├── package.json
├── src
│ ├── assets
│ │ ├── img
│ │ │ ├── large.png
│ │ │ └── small.png
│ │ └── smile.svg # 新增一个svg图片资源
│ ├── index.css # 在css文件里以background形式用到svg
│ ├── index.html # 在html文件里以img标签形式用到svg
│ └── index.js
├── webpack.config1.js
└── webpack.config2.js # 新的webpack配置文件
新增3KB的smile.svg文件:
<svg width="128" height="128" style="enable-background:new 0 0 128 128;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M63.89,9.64C1.58,9.64,0.1,79.5,0.1,93.33c0,13.83,28.56,25.03,63.79,25.03 c35.24,0,63.79-11.21,63.79-25.03C127.68,79.5,126.21,9.64,63.89,9.64z" style="fill:#FCC21B;"/>
<g>
<defs>
<path id="SVGID_1_" d="M63.89,98.06c23.15,0.05,40.56-12.97,41.19-29.05c-27.24,4.91-55.14,4.91-82.38,0 C23.33,85.09,40.74,98.11,63.89,98.06z"/>
</defs>
<use style="overflow:visible;fill:#FFFFFF;" xlink:href="#SVGID_1_"/>
<clipPath id="SVGID_2_">
<use style="overflow:visible;" xlink:href="#SVGID_1_"/>
</clipPath>
<g style="clip-path:url(#SVGID_2_);">
<path d="M78.05,108c-1.1,0-2-0.9-2-2V61.07c0-1.1,0.9-2,2-2s2,0.9,2,2V106C80.05,107.1,79.16,108,78.05,108 z" style="fill:#2F2F2F;"/>
</g>
<g style="clip-path:url(#SVGID_2_);">
<path d="M92.21,108c-1.1,0-2-0.9-2-2V61.07c0-1.1,0.9-2,2-2s2,0.9,2,2V106C94.21,107.1,93.32,108,92.21,108 z" style="fill:#2F2F2F;"/>
</g>
<g style="clip-path:url(#SVGID_2_);">
<path d="M63.89,108c-1.1,0-2-0.9-2-2V61.07c0-1.1,0.9-2,2-2s2,0.9,2,2V106 C65.89,107.1,64.99,108,63.89,108z" style="fill:#2F2F2F;"/>
<path d="M49.72,108c-1.1,0-2-0.9-2-2V61.07c0-1.1,0.9-2,2-2s2,0.9,2,2V106 C51.72,107.1,50.83,108,49.72,108z" style="fill:#2F2F2F;"/>
<path d="M35.56,108c-1.1,0-2-0.9-2-2V61.07c0-1.1,0.9-2,2-2s2,0.9,2,2V106 C37.56,107.1,36.67,108,35.56,108z" style="fill:#2F2F2F;"/>
</g>
</g>
<path d="M64.01,100.56h-0.25c-24.13,0-42.86-13.52-43.56-31.46c-0.03-0.76,0.29-1.49,0.86-1.98 c0.57-0.5,1.33-0.71,2.08-0.57c26.82,4.84,54.67,4.84,81.5,0c0.75-0.14,1.51,0.08,2.08,0.57c0.57,0.5,0.89,1.23,0.86,1.98 C106.87,87.04,88.14,100.56,64.01,100.56z M63.88,95.56h0.13c19.55,0,35.56-10.1,38.2-23.52c-25.29,4.18-51.36,4.18-76.65,0 c2.64,13.42,18.65,23.52,38.2,23.52H63.88z" style="fill:#2F2F2F;"/>
<path d="M31.96,54.45c-0.78,1.28-2.44,1.7-3.73,0.93c-1.29-0.77-1.71-2.42-0.96-3.71 c0.18-0.31,4.6-7.62,14.37-7.62c9.78,0,14.2,7.31,14.39,7.62c0.76,1.29,0.32,2.97-0.97,3.73c-0.44,0.26-0.91,0.38-1.39,0.38 c-0.92,0-1.83-0.47-2.34-1.32c-0.13-0.22-3.12-4.96-9.69-4.96C35.07,49.49,32.1,54.24,31.96,54.45z" style="fill:#2F2F2F;"/>
<path d="M100,55.39c-0.43,0.26-0.91,0.38-1.37,0.38c-0.94,0-1.85-0.49-2.36-1.34 c-0.11-0.2-3.08-4.94-9.66-4.94c-6.69,0-9.66,4.89-9.69,4.94c-0.77,1.29-2.43,1.73-3.73,0.96c-1.29-0.76-1.73-2.44-0.96-3.73 c0.18-0.31,4.6-7.62,14.38-7.62c9.77,0,14.18,7.31,14.36,7.62C101.73,52.96,101.29,54.63,100,55.39z" style="fill:#2F2F2F;"/>
</svg>
index.html改动:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>静态资源</title>
</head>
<body class="bg-img">
<img src="~@assets/img/large.png" width="200" height="80" alt="背景图">
+ <div class="smile"></div>
+ <img src="~@assets/smile.svg" alt="emoj">
</body>
</html>
index.css改动:
.bg-img {
background: url(~@assets/img/small.png) no-repeat;
}
+.smile {
+ width: 100px;
+ height: 82px;
+ background: url(~@assets/smile.svg);
+ background-size: 100px 82px;
+}
新增webpack.config2.js文件:
const path = require('path')
const HTMLPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
resolve: {
alias: {
'@assets': path.resolve(__dirname, './src/assets')
}
},
module: {
rules: [
{
test: /.html$/,
use: ['html-loader']
},
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.(png|jpg|gif)$/, // 使用了svg-url-loader的.svg文件需要排除掉url-loader的rules
use: {
loader: 'url-loader',
options: {
limit: 2.5*1024
}
}
},
{
test: /.svg$/,
loader: 'svg-url-loader',
options: {
// 小于 10kB(10240字节)的内联文件
limit: 10 * 1024,
// 移除 url 中的引号
// (在大多数情况下它们都不是必要的)
noquotes: true
}
}
]
},
plugins: [
new HTMLPlugin({
template: './src/index.html'
})
]
}
package.json针对新配置文件添加命令:
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"build1": "webpack --config webpack.config1.js",
+ "build2": "webpack --config webpack.config2.js"
},
注意:使用了svg-url-loader的.svg文件需要排除掉url-loader的rules(原因见原作者https://github.com/bhovhannes/svg-url-loader/issues/28)
3.2.2 命令行运行npm run build2得到打包结果:
3.3 image-webpack-loader压缩图片
3.3.1 我们先看一下只使用url-loader和file-loader的打包结果:
现在我们再使用image-webpack-loader来对图片大小做压缩看看效果:
3.3.2 npm install image-webpack-loader --save-dev安装好后在webpack.config2.js的基础上另写一套使用了image-webpack-loader做图片压缩的webpack配置:
// webpack.config3.js
const path = require('path')
const HTMLPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
resolve: {
alias: {
'@assets': path.resolve(__dirname, './src/assets')
}
},
module: {
rules: [
{
test: /.html$/,
use: ['html-loader']
},
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.(gif|png|jpe?g|svg)$/i,
use: [
'file-loader',
{
loader: 'image-webpack-loader',
options: {
// optipng.enabled: false will disable optipng
optipng: {
enabled: true,
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
}
}
},
],
}
]
},
plugins: [
new HTMLPlugin({
template: './src/index.html'
})
]
}
在pakage.json的scripts中加入"build3": "webpack --config webpack.config3.js"命令并执行npm run build3得到打包后的dist文件夹,可以看到图片大小被压缩了:比如原来1.2MB的large.png变成了450KB的58fe00f105867c009b85491e96f302bb.png。
注:如果执行npm run build3后报错——Module build failed (from ./node_modules/image-webpack-loader/index.js): Error
则是下载的image-webpack-loader出了问题(因墙的缘故npm源下载可能有问题),此时需要npm uninstall image-webpack-loader再cnpm image-webpack-loader --save-dev(自行设置cnpm淘宝源使用国内网)便可解决问题。(npm config set registry https://registry.npm.taobao.org npm install -g cnpm --registry=https://registry.npm.taobao.org)
3.4 postcss-sprites合并雪碧图
3.4.1 在3.3实验的基础上删掉dist目录,新增4张图片用以雪碧图实验:
├── package-lock.json
├── package.json
├── postcss.config.js # 新增postcss配置文件
├── src
│ ├── assets
│ │ ├── img
│ │ │ ├── large.png
│ │ │ ├── small-02.png # 新增图片1
│ │ │ ├── small-03.png # 新增图片2
│ │ │ ├── small-04.png # 新增图片3
│ │ │ ├── small-05.png # 新增图片4
│ │ │ └── small.png
│ │ └── smile.svg
│ ├── index.css
│ ├── index.html # 新增显示内容用到以上新增图片资源
│ ├── index.js
│ ├── sprite.css # html内新增DOM的样式分别设置背景为以上新增资源
│ └── sprite.js # 新增逻辑入口
├── webpack.config1.js
├── webpack.config2.js
├── webpack.config3.js
└── webpack.config4.js # 新增webpack配置
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>静态资源</title>
</head>
<body class="bg-img">
<img src="~@assets/img/large.png" width="200" height="80" alt="背景图">
<div class="smile"></div>
<img src="~@assets/smile.svg" alt="emoj">
+ <div class="bg-img02"></div>
+ <div class="bg-img03"></div>
+ <div class="bg-img04"></div>
+ <div class="bg-img05"></div>
</body>
</html>
sprite.css
.bg-img02 {
background: url(./assets/img/small-02.png) no-repeat;
}
.bg-img03 {
background: url(./assets/img/small-03.png) no-repeat;
}
.bg-img04 {
background: url(./assets/img/small-04.png) no-repeat;
}
.bg-img05 {
background: url(./assets/img/small-05.png) no-repeat;
}
div {
width: 160px;
height: 160px;
border: 1px solid black;
}
sprite.js
import style from './sprite.css';
console.log(style);
webpack.config4.js
const path = require('path')
const HTMLPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: './src/sprite.js',
resolve: {
alias: {
'@assets': path.resolve(__dirname, './src/assets')
}
},
module: {
rules: [
{
test: /.html$/,
use: ['html-loader']
},
{
test: /.css$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader'
}
]
},
{
test: /.(png|svg|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 3*1024
}
}
}
]
},
plugins: [
new HTMLPlugin({
template: './src/index.html'
})
]
}
postcss.config.js
const postcssSprites = require('postcss-sprites')
module.exports = {
plugins: [
postcssSprites({
spritePath: 'dist/sprite/'
})
]
}
package.json
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"build1": "webpack --config webpack.config1.js",
"build2": "webpack --config webpack.config2.js",
"build3": "webpack --config webpack.config3.js",
+ "build4": "webpack --config webpack.config4.js"
},
3.4.2 我们先直接在index.html里引入sprite.css看一下不打包前的设计效果:
3.4.3 去掉3.4.2里的<link rel="stylesheet" href="./sprite.css">复原html,安装postcss-sprites :
cnpm install postcss-sprites --save-dev
cnpm install postcss-loader --save-dev
然后命令行执行npm run build4打包:
可以看到postcss-sprites将页面用到的小图片合并到一张大图中并使用background-position自动重新定位:
以上代码见github
本文参考引用:
12 使用 Webpack 管理项目中的静态资源-慕课专栏www.imooc.com Webpack 前端工程化入门gitbook.cn