低头要有勇气,抬头要有底气。
——韩寒
前端项目离不开各种静态资源,静态资源指前端中常用的图片、富媒体(Video、Audio 等)、字体文件等。Webpack 中静态资源也是可以作为模块直接使用的,本小节将介绍下 Webpack 中对静态资源的管理。
图片引入方式
图片是前端项目必不可少的静态资源,在日常开发中,我们可能会在下面三种情况使用图片:
- HTML 中通过<img>标签等方式引入;
- CSS 中通过src等方式引入;
- JavaScript 中使用图片的 URL 或者内容(比如 Canvas 等)。
最笨最直接的方式就是直接写死线上的地址,例如在页面中,我们引入<img>如下:
1 | <img src="http://s.bxstatic.com/foo/bar.png" /> |
上面地址的http://s.bxstatic.com
是一个 CDN 静态域名,后面是我们完整的路径,这样我们上线的时候地址就可以直接看了,我们线下开发的时候可以提前将静态资源打包好上传到线上。这样操作想想就很费劲,而且 CDN 每次静态资源更新都要需要刷新缓存,如果我们使用 MD5 命名图片的时候就更麻烦了。
我们在 Webpack 中,则可以使用 loader 的方式完成图片的引入。例如在 CSS 文件中,直接相对路径使用背景图片:
1 | .bg-img { |
在 HTML 中也可以直接使用相对路径:
1 | <img src="../../foo/bar.png" /> |
还记得我们之前学过的resolve.alias方式创建一个目录的alias引用,这种方式不仅仅可以在 JavaScript 中使用,在 HTML 和 CSS 中也可以使用的:
1 | // webpack.confg.js |
使用 loader 来加载图片资源
怎么使用图片我们了解了,但是怎么让 Webpack 识别图片,并且能够打包输出呢?这时候就需要借助 loader 了,这里有两个 loader 可以使用:file-loader和url-loader。
file-loader和url-loader是经常在一些 Webpack 配置中看到的两个 loader,并且两个 loader 在一定应用场景上是可以相互替代的,但是对于两者的区别,很少有人能够说得清楚,下面介绍下两者的区别。
- file-loader:能够根据配置项复制使用到的资源(不局限于图片)到构建之后的文件夹,并且能够更改对应的链接;
- url-loader:包含 file-loader 的全部功能,并且能够根据配置将符合配置的文件转换成 Base64 方式引入,将小体积的图片 Base64 引入项目可以减少 http 请求,也是一个前端常用的优化方式。
下面以url-loader为例说明下 Webpack 中使用方法。首先是安装对应的 loader:npm install -D url-loader
。
下面我们创建一个项目,目录结构如下:
1 | ├── package.json # npm package.json |
首先我们在index.css中引入 small.png,
1 | .bg-img { |
然后在index.js中引入了index.css和large.png:
1 | import img from './assets/img/large.png'; |
最后我们在index.html中通过<img>引入large.png:
1 |
|
这时候我们修改webpack.config.js:
1 | // webpack.config.js |
执行webpack之后的 log:
这时候发现,打包出来的文件都比较大,通过查看内容发现,我们的图片被Base64处理了,然后直接引入了:
这是因为url-loader本身优先是将资源Base64引入的。虽然图片Base64可以减少 http 请求,但是对于1M+这么大的图片都Base64处理,范围增加了 CSS、JavaScript 等文件的大小,而且将这么大的Base64反解成可以使用的图片渲染出来,时间消耗也是很大的。
所以这时候需要使用url-loader的limit选项来控制不超过一定限制的图片才使用Base64:
1 | { |
这时候再执行webpack,发现多打出一个ad19429dc9b3ac2745c760bb1f460892.png的图片,这张图片就是large.png的图片,因为超过了limit=3*1024显示所以没有被处理成Base64。
继续查看index.html和main.js(index.js打包出来的文件),发现我们使用large.png的地址都被 Webpack 自动替换成了新的路径ad19429dc9b3ac2745c760bb1f460892.png。
配置 CDN 域名
一般静态资源上线的时候都会放到 CDN,假设我们的 CDN 域名和路径为:http://bd.bxstatic.com/img/
,这时候只需要修改output.publicPath即可:
1 | module.exports = { |
修改后执行webpack打包后的结果如下:
1 | <body> |
说明 Webpack 为我们自动替换了路径,并且加上了 CDN 域名。
HTML 和 CSS 中使用 alias
前面提到过,除了使用相对路径的方式引入静态资源,还可以使用别名(alias)的方式,url-loader也会给我们处理这种情况的引用。
修改index.html和index.css:
1 | <img src="@assets/img/large.png" alt="背景图" /> |
然后修改webpack.config.js增加resolve.alias:
1 | module.exports = { |
这时候执行webpack发现报错了:
1 | ERROR in ./src/index.css (/webpack-tutorial/node_modules/css-loader/dist/cjs.js!./src/index.css) |
这是因为在 HTML 和 CSS 使用alias必须要前面添加~,即:
1 | <img src="~@assets/img/large.png" alt="背景图" /> |
修改完后,直接执行webpack既可以看到正确的结果了。
Tips: HTML 中使用<img>引入图片等静态资源的时候,需要添加html-loader配置,不然也不会处理静态资源的路径问题。
svg-url-loader 的工作原理类似于 url-loader,除了它利用 URL encoding 而不是 Base64 对文件编码。对于 SVG 图片这是有效的,因为 SVG 文件恰好是纯文本,这种编码规模效应更加明显,使用方法如下:
1 | // webpack.config.js |
Tips: svg-url-loader 拥有改善 IE 浏览器支持的选项,但是在其他浏览器中更糟糕。如果你需要兼容 IE 浏览器,设置 iesafe: true选项。
图片优化
图片体积是个经常诟病的问题,一个页面中,完全一样内容的图片,在肉眼可见的范围内并不一定有差异但是体积却相差甚大,例如下面的图片:
所以图片优化也是我们在前端项目中经常做的事情,在 Webpack 中可以借助img-webpack-loader来对使用到的图片进行优化。它支持 JPG、PNG、GIF 和 SVG 格式的图片,因此我们在碰到所有这些类型的图片都会使用它。
1 | 安装 |
image-webpack-loader这个 loader 不能将图片嵌入应用,所以它必须和 url-loader 以及 svg-url-loader 一起使用。为了避免同时将它复制粘贴到两个规则中(一个针对 JPG/PNG/GIF 图片, 另一个针对 SVG ),我们使用 enforce: ‘pre’ 作为单独的规则涵盖在这个 loader:
1 | // webpack.config.js |
通过enforce: ‘pre’我们提高了 img-webpack-loader 的优先级,保证在url-loader和svg-url-loader之前就完成了图片的优化。
另外img-webpack-loader默认的配置就已经适用于日常开发图片的压缩优化需求了,但是如果你想更进一步去配置它,参考插件选项。要选择指定选项,请查看国外牛人写的一个图像优化指南。
CSS Sprite 雪碧图
CSS 使用小图标图片的时候,我们经常做的优化项目是将小图标的图片合并成雪碧图(CSS Sprite),雪碧图的好处是将页面用到的小图片合并到一张大图中,然后使用background-position重新定位,这样节省了 HTTP 的请求数。
在 Webpack 中我们可以借助 PostCSS 来给图片做雪碧图,经过简单的配置之后,生成雪碧图就是全自动的过程了。下面来看看怎么操作。
首先安装 postcss-sprites
1 | npm install postcss-sprites -D |
Tips: postcss-sprites 安装需要安装phantomjs可能需要正确上网。
然后修改 PostCSS 的postcss.config.js,增加插件的调用:
1 | // postcss.config.js |
然后修改webpack.config.js在css-loader之前配置上postcss-loader(注意 loader 加载顺序,从后往前):
1 | //webpack.config.js |
好了,下面我们的 CSS 中使用了spritePath: ‘./src/assets/img/‘路径的图片就会被处理了,例如下面的文件:
1 | .bg-img02 { |
经过打包之后,输出 log 如下,可见生成了一个新的图片文件99b0de3534d3e852ea4ce83b15cbad60.png:
打开99b0de3534d3e852ea4ce83b15cbad60.png文件,我们看到图片被合并到了一起:
在打开打包之后的 CSS 文件,发现内容被主动替换成了 CSS Sprite 写法,并且设置了正确的background-position和background-size了:
1 | .bg-img02 { |
其他资源处理
字体、富媒体
对于字体、富媒体等静态资源,可以直接使用url-loader或者file-loader进行配置即可,不需要额外的操作,具体配置内容如下:
1 | { |
Tips: 如果不需要 Base64,那么可以直接使用 file-loader,需要的话就是用url-loader,还需要注意,如果将正则(test)放在一起,那么需要使用[ext]配置输出的文件名。
数据
如果我们项目需要加载的类似 JSON、CSV、TSV 和 XML 等数据,那么我们需要单独给它们配置相应的 loader。对 JSON 的支持实际上是内置的,类似于 Node.js,这意味着import Data from’./data.json’导入数据默认情况将起作用。要导入 CSV,TSV 和 XML,可以使用csv-loader和xml-loader。
首先是安装它们的 loader:npm i -D xml-loader csv-loader
,然后增加文件 loader 配置如下:
1 | { |
现在,您可以导入这四种类型的数据中的任何一种(JSON,CSV,TSV,XML),并且导入它的 Data 变量将包含已解析的 JSON 以便于使用。
小结
本小节主要介绍 Webpack 中的图片、字体、富媒体、数据等多种静态资源的管理方式。页面经常用到的图片是页面的重点,Webpack 提供了很多插件和 loader 对图片进行压缩、合并(CSS Sprite)。Webpack 还会使用url-loader等插件,将较小的资源通过 Base64 的方式引入。
当项目足够大了之后,配置太多的静态资源处理流程也会影响 Webpack 的打包速度,想突破压缩和合并这类前端常见优化,我们可以通过让视觉人员提供最优图片格式等方式来人工解决。当然如果项目组一直没有优化的意识,担心一不小心上到线上一个很大的图片,那么使用 Webpack 来兜底也是个很不错的方案。
本小节 Webpack 相关面试题:
- Webpack 中怎么给静态资源添加上 CDN 域名?
- url-loader 和 file-loader 有什么区别?