一、在 webpack 中有各种 loader 可以加载 SVG:
1、url-loader
官方文档:https://webpack.docschina.org/loaders/url-loader/
可以把 svg 当作普通 jpg、png 图片来使用。
安装:
代码语言:javascript复制npm install url-loader --save-dev
webpack 配置:
代码语言:javascript复制test: /.(png|jpg|gif|svg)$/i,
use: [
{
loader: 'url-loader',
}
]
在 React 组件中的引入方式:
代码语言:javascript复制import test from './test.svg'
export default class App extends Component {
render() {
return (
<img src={test} />
);
}
};
经过 url-loader 处理后,test.svg 文件被处理为 “data:image/svg xml;base64,PHN2ZyBpZ…..” 这样的 base64 编码。
代码语言:javascript复制1
module.exports = "data:image/svg xml;base64,PHN2ZyBpZ..
我们用 webpack-bundle-analyzer 插件测试看看 svg 文件被打包后的大小:
此外 file-loader 也可以。
这种方式的优点: SVG 资源可被缓存,SVG 资源可单独加载。
缺点:加载多个 SVG 文件时,会产生多个 http 请求,影响页面加载性能。
2、svg-react-loader
安装:
代码语言:javascript复制npm install svg-react-loader --save-dev
webpack 配置:
代码语言:javascript复制{
test: /.svg$/,
loader: 'svg-react-loader',
}
在 React 组件中的引入方式:
代码语言:javascript复制import Test from "./svg/test.svg";
export default class App extends Component {
render() {
return <Test className={iconType} />
}
}
svg-react-loader 会将 svg 文件处理成 React 组件,最后会以 svg 标签的形式渲染到 html 中。从最后渲染到 html 中的代码来看,svg-react-loader 是有对 svg 原文件进行优化的。从打包后的文件大小可以看出来文件有被压缩:
这种方式的缺点:SVG 资源不可被缓存。且会将 svg 资源的处理逻辑与页面的交互逻辑一起打包。
最好的方式是:SVG 资源与 JS 资源分离,图片的加载不影响页面的主要执行逻辑。并且所有的 SVG 资源希望能一次 HTTP 请求就能搞定。
3、svg-inline-loader
官方文档:https://webpack.js.org/loaders/svg-inline-loader/
svg-inline-loader 会根据配置项去除 SVG 中冗余的代码或者不必要的代码,以减少 SVG 的文件大小。
安装:
代码语言:javascript复制npm install svg-inline-loader --save-dev
webpack 配置:
代码语言:javascript复制{
test: /.svg$/,
loader: 'svg-inline-loader'
}
在 React 组件中的引入方式:
代码语言:javascript复制import test from "./test.svg";
export default class App extends Component {
render() {
return (
<span dangerouslySetInnerHTML={{__html: test}} />
);
}
};
经过 svg-inline-loader 处理后 svg 文件被处理为这样的字符串:
代码语言:javascript复制module.exports = "<svg xmlns=\"http://www.w3.org/2000/svg\">....
看下打包大小,相对比,这个体积优化的效果还不错。
此外还有 raw-loader 等都可以处理 SVG 文件。
二、SVG 优化
1. SVG 文件压缩
一般设计师会使用 Adobe suite 或者 Sketch 等工具导出 SVG 文件,但这样的 SVG 一般是有属性冗余的,所以需要对 SVG 进行一定的压缩。
手动压缩:当然,也不需要手动压缩,但是可以看看哪些属性是有冗余的。
工具压缩:推荐使用 SVGO。
webpack 项目中引入 SVGO:
安装:
代码语言:javascript复制npm install svgo svgo-loader --save-dev
webpack 配置:
代码语言:javascript复制{
test: /.svg$/,
exclude: /node_modules/,
loaders: [
'url-loader',
'svgo-loader'
],
},
在单独使用 url-loader 时,文件 test.svg 打包后的大小是:17.82 KB。在使用 svgo-loader 后,我们看下打包大小,确实是有很大幅度的压缩。
2. SVG 雪碧图
当项目需要加载多个 SVG 文件时,上述加载方式就需要优化了。我们考虑以下几种情况:
1)使用 url-loader 加载多个 svg 文件
这种方式会产生多次 http 请求,而浏览器对并发请求数目是有限制的,请求太多会影响页面加载性能。这种情况就需要引入雪碧图,将多个 svg 文件合成一个 svg 文件。
2)使用 svg-react-loader ,当一个组件需要加载多个 svg 文件时,所有的 svg 文件都会被打包到 index.js 文件中。如果 svg 文件过多,就会增大 index.js 文件体积。而 SVG 作为图片资源,最好是作为一个单独文件异步加载,与页面主要执行逻辑分离。当然这里也同样需要引入雪碧图。
第一种方法
第一种方法是把所有的图标通过<symbol>
元素定义在 SVG 代码中,嵌入在<symbol>
元素中的图标是不会被直接显示的。通过使用<use>
元素的 xlink:href=”#id” 来引用单个图标。
也就是说合成后的大 SVG 会是这样:
代码语言:javascript复制<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="__SVG_SPRITE_NODE__">
<symbol class="icon" viewBox="0 0 1024 1024" id="icon名1">{{省略的icon path}}</symbol>
<symbol class="icon" viewBox="0 0 1024 1024" id="icon名2">{{省略的icon path}}</symbol>
</svg>
在需要引入单个图标的时候:
代码语言:javascript复制<use xlink:href="#symbolId"></use>
这里我们使用 svg-sprite-loader 来自动生成 svg 雪碧图。
安装:
代码语言:javascript复制npm install svg-sprite-loader --save-dev
loader 配置:
代码语言:javascript复制{
test: /.svg$/,
exclude: /node_modules/,
use: [{
loader: 'svg-sprite-loader',
}],
}
react 组件中引入:
代码语言:javascript复制import "./svg/node.svg";
import "./svg/test.svg";
import "./svg/logo.svg";
export default class App extends Component {
render() {
return (
<div>
<svg><use xlinkHref="#node" /></svg>
<svg><use xlinkHref="#test" /></svg>
<svg><use xlinkHref="#logo" /></svg>
</div>
);
}
}
这样配置打包后,body 下会自动注入一个大的 SVG 元素,达到了雪碧图的效果。可是这样还是有两个缺点:1)当需要使用这些 svg 的时候,需要在组件中单独 import。2)这个大的 SVG 资源并没有与 JS 资源分离。
为了解决第一个缺点:在组件加载前,增加自动 import 所有 svg 的功能:
代码语言:javascript复制// requires and returns all modules that match
const requireAll = requireContext => requireContext.keys().map(requireContext);
// import all svg
const req = require.context('./svg', true, /.svg$/);
requireAll(req);
现在可以自动导入了,不用到每个组件中去单独 import 了。可是还需要解决第二个问题:SVG 资源与 JS 资源分离。
从 svg-sprite-loader 文档中看到可以利用 spriteloaderplugin 插件将所有 SVG 文件打包输出为一个大的 SVG:
这里的 loader 配置:
代码语言:javascript复制{
test: /.svg$/,
exclude: /node_modules/,
loaders: [
{
loader: 'svg-sprite-loader',
options: {
extract: true,
spriteFilename: 'sprite.svg', // 生成的 SVG 雪碧图资源路径
}
},
'svgo-loader'
],
},
插件配置:
代码语言:javascript复制plugins: [
new SpriteLoaderPlugin({
plainSprite: true
})
]
这种方式会在打包目录下生成 sprite.svg 文件,我们可以通过 ajax 请求的方式获取到该 svg 资源,然后将其注入到 html 中去,这样就将 svg 资源与 js 资源分离了。
代码语言:javascript复制fetch(`sprite.svg`).then(res => {
return res.text();
}).then(data => {
document.getElementById('svg-sprite').innerHTML = data;
});
这样的方式是我们自己手动去请求的,思考下能否使用 webpack 配置或者一些插件就能达到 svg 与 js 资源分离的目的呢。于是想到了 webpack 的 SplitChunksPlugin 插件提供的抽取公共代码的能力。
于是尝试把 import 所有 svg 的逻辑抽离出来成为一个单独的 js 文件。配置好 SplitChunks 后,webpack 就会把这部分逻辑抽离出来,单独打包成一个 js 文件。这样就实现了 svg 与 js 资源分离了。
webpack 配置:
代码语言:javascript复制optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: 'commons',
chunks: 'initial',
minChunks: 2
}
}
}
},
第二种方法
第二种方法是使用 SVG 的 viewbox 属性来指定显示 SVG 画布的区域,跟 background-position 原理差不多。待探索~