需求分析
- 项目引用了大量的svg-icon图表,正常react使用icon要这样:
<img src="/xxx/yyy/zzz/icon.svg" />
- 现在想类似element一样,通过某个组件一行引入,类似:
<Icon name="icon-file-name"></Icon>
- 过程中使用到2个loader: svg-sprite-loader、svgo-loader
先了解下sprite雪碧图
1. symbol
- SVG Sprite最佳实践是使用symbol元素
- 对于一个集合了三个SVG图标的SVG元素的代码结构会是这样:
<svg>
<symbol>
<!-- 第1个图标路径形状之类代码 -->
</symbol>
<symbol>
<!-- 第2个图标路径形状之类代码 -->
</symbol>
<symbol>
<!-- 第3个图标路径形状之类代码 -->
</symbol>
</svg>
- 每一个symbol就是一个图标元件
2. use
- 只是放置了图标,如果你不使用(use),是看不见的
- 这样使用
<use xlink:href="#symbolId"></use>
svg-sprite-loader
一个生成雪碧图的工具,它是一个 webpack loader ,可以将多个 svg 打包成 svg-sprite 。
原理
- svg-sprite-loader会把你的icon塞到一个个symbol中,
- symbol的id如果不特别指定,就是你的文件名。
- 它最终会在你的html中嵌入一个大的svg,
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="__SVG_SPRITE_NODE__">
<symbol xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 1024 1024" id="xxx">// id 是 icon 名
<!-- 这块是 path -->
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 1024 1024" id="xxx">// id 是 icon 名
<!-- 这块是 path -->
</symbol>
</svg>
</body>
- 然后就可以像上面
use
一样使用了
<svg>
<use xlink:href="#xxx"/> // xxx 为id
</svg>
- 总结下就是:
symbol
+use
=>SVG Sprite
使用和配置
- 安装
yarn add --dev svg-sprite-loader
- 配置webpack
{
test: /\.svg$/,
loader: 'svg-sprite-loader',
}
- 使用 icon组件里
<svg>
<use xlink:href="#svgName"/>
</svg>
svgo-loader
介绍
SVG files, especially exported from various editors, usually contain a lot of redundant and useless information such as editor metadata, comments, hidden elements, default or non-optimal values and other stuff that can be safely removed or converted without affecting SVG rendering result.
- 参考张鑫旭大佬的博客
- github
- 现在想改变svg-icon元素的样式和颜色等,可以使用svgo-loader
- SVGO 是 svg 优化器,包含很多插件。
- 它可以删除和修改SVG元素,折叠内容,移动属性等等等等。
原理
-
SVGO 将 SVG-as-XML 数据转换为 SVG-as-JS AST 表示形式。
-
然后在所有AST数据项上运行并执行一些操作,最后,SVGO 再将 AST 转换回 SVG-as-XML 数据字符串。
使用和配置
- 安装
yarn add --dev svgo-loader
- webpack里配置
{
test: /\.svg$/,
use: [
{loader: 'svg-sprite-loader', options: {}},
{
loader: 'svgo-loader', options: {
// plugins: [
// {removeAttrs: {attrs: 'fill'}}
// ]
}
}
]
},
- 注意:先svgo-loader,再svg-sprite-loader。先处理svg图像,在生成雪碧图
- 改颜色通过fill属性实现
基于以上,封装Icon组件
import React from "react";
require('icons/money.svg')
require('icons/tag.svg')
require('icons/chart.svg')
type Props = {
name:string
}
const Icon =(props:Props)=>{
return (
<svg fill='red' className="icon">
<use xlinkHref={'#'+props.name}></use>
</svg>
)
};
export default Icon
1. 为什么使用require引入icon?
- 避免tree-shaking
- 使用import的话,会导致tree-shaking,而无法使用
2. 如果我引入的icon很多,那是不是要一个一个写呢?能不能一次性引入文件夹呢?
- 可以
- 代码
//require一个目录/文件夹
let importAll = (requireContext: __WebpackModuleApi.RequireContext) =>
requireContext.keys().forEach(requireContext);
try {
importAll(require.context('icons', true, /\.svg$/));
}
catch (error)
{console.log(error);
}
- 参考:webpack的require.context
- require.context:
- webpack提供的API,用来创建自己的context module,动态引入icon
- 支持三个参数:
- 要搜索的文件夹目录
- 是否还应该搜索它的子目录,
- 以及一个匹配文件的正则表达式。
- 所以就是:搜索icons文件夹下,包含其中的子目录的所有svg格式的文件
- 一个 context module会导出一个(require)函数,此函数可以接收一个参数:request。
- 此导出函数有三个属性:resolve, keys, id。
- resolve 是一个函数,它返回 request 被解析后得到的模块 id。
- keys 也是一个函数,它返回一个数组,由所有可能被此 context module 处理的请求(译者注:参考下面第二段代码中的 key)组成。
- 如果想引入一个文件夹下面的所有文件,或者引入能匹配一个正则表达式的所有文件,这个功能就会很有帮助,例如:
function importAll(r) {
r.keys().forEach(r);
}
importAll(require.context('../components/', true, /\.js$/));
-
总的来说,就是说 require.context 帮我们创建一个上下文。
-
比如在这里我们的上下文就是 ./src/assets/icons。
-
随后我们就可以通过 require.resolve 来引入该上下文内的文件了。
-