Webpack与svg-sprite-loader集成实践:原理剖析与常见问题解决方案

469 阅读2分钟

1. svg-sprite-loader 介绍及原理

svg-sprite-loader 是一个 Webpack 加载器(loader),它的主要目的是处理 SVG(可缩放矢量图形)文件,并将它们合并成一个或多个 SVG 精灵图(Sprite)。这个过程简化了SVG图标的管理和使用,提升了网页性能。

原理

  • 匹配svg,合成一个包含多个<symbol> 原素的SVG文件(原来的SVG替换成了<symbol>)。
  • 通过 <use> 使用<symbol> (eg. <use xlink:href="#icon-id"></use>)。

优点

  • 通过ID来控制图标,十分Nice
  • 减少HTTP请求
  • 动态引用和样式控制
  • SVG配置过程自动化

缺点

  • 兼容性差(Internet Explorer 9 +)
  • 无法处理 多色、渐变色 SVG图标 改色

2. svg-sprite-loader 使用

安装

npm install --save-dev svg-sprite-loader

Webpack配置

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        use: [
          {
            loader: 'svg-sprite-loader',
            options: {
              extract: true,
              spriteFilename: 'svg-sprite.[hash].svg',
            },
          },
        ],
      },
    ],
  },
};

Vue组件

<template>
  <svg :class="svgClass" aria-hidden="true">
    <use :xlink:href="iconName"/>
  </svg>
</template>

<script>
export default {
  name: 'SvgIcon',
  props: {
    iconClass: {
      type: String,
      required: true
    },
    className: {
      type: String,
      default: ''
    }
  },
  computed: {
    iconName () {
      return `#za-icon-${this.iconClass}`
    },
    svgClass () {
      if (this.className) {
        return `za-svg-icon za-icon-${this.iconClass} ` + this.className
      } else {
        return `za-svg-icon za-icon-${this.iconClass}`
      }
    }
  }
}
</script>

<style lang="scss">
.za-svg-icon {
  width: 20px;
  height: 20px;
  vertical-align: middle;
  fill: currentColor;
  overflow: hidden;
}
</style>

自动导入 index.js

import Vue from 'vue'
import SvgIcon from '@/components/k-element/svg-icon/index.vue'
// 全局注册组件
Vue.component('svg-icon', SvgIcon)
// 定义一个加载目录的函数
const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('@/assets/icons', false, /\.svg$/)
// 加载目录下的所有 svg 文件
requireAll(req)

3. 踩坑

在集成开源项目时,开源项目中也使用了svg-sprite-loader ,如何解决webpack配置冲突 ?

原项目中的loader配置如下(weboack.config.js):

const webpackCommonConfig = {
  // ...
  module: {
    rules: [
      // ...
      {
        test: /\.svg$/,
        loader: "svg-sprite-loader",
        include: [resolve("src/assets/icons")],
        options: {
          symbolId: "icon-[name]",
        },
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/,
        type: "asset", // asset 资源类型可以根据指定的图片大小来判断是否需要将图片转化为 base64
        parser: {
          dataUrlCondition: {
            maxSize: 60 * 1024, // 限制于 60kb
          },
        },
        generator: {
          filename: "static/img/[name][ext]",
        },
        exclude: resolve("src/assets/icons")
      },
    ],
  },
};


开源项目配置如下(vue.config.js):

module.exports = {
  chainWebpack: (config) => {
    config.module
      .rule("svg")
      .exclude.add(resolve("packages"))
      // .add(resolve('xxxx'))
      // .add(resolve('xxxx'))
      // .add(resolve('xxxx'))
      .end();

    config.module
      .rule("icons")
      .test(/\.svg$/)
      .include.add(resolve("packages"))
      // .add(resolve('xxxx'))
      // .add(resolve('xxxx'))
      // .add(resolve('xxxx'))
      .end()
      .use("svg-sprite-loader")
      .loader("svg-sprite-loader")
      .options({
        symbolId: "icon-[name]",
      })
      .end();
  },
};


合并思路

  • 将packages等需要解析的SVG目录从Webpack5 自带的资源解析loader中排除(exclude)
  • svg-sprite-loader 中添加packages等需要解析的SVG目录(include)
  • 在自动导入SVG的index.js 中添加packages等需要加载的SVG目录

最终代码如下:

weboack.config.js:

const webpackCommonConfig = {
  // ...
  module: {
    rules: [
      // ...
      {
        test: /\.svg$/,
        loader: "svg-sprite-loader",
        include: [resolve("src/assets/icons"), resolve("packages")],
        options: {
          symbolId: "icon-[name]",
        },
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/,
        type: "asset", // asset 资源类型可以根据指定的图片大小来判断是否需要将图片转化为 base64
        parser: {
          dataUrlCondition: {
            maxSize: 60 * 1024, // 限制于 60kb
          },
        },
        generator: {
          filename: "static/img/[name][ext]",
        },
        exclude: [resolve("src/assets/icons"), resolve("packages")]
      },
    ],
  },
};

index.js

import Vue from 'vue'
import SvgIcon from '@/components/k-element/svg-icon/index.vue'
// 全局注册组件
Vue.component('svg-icon', SvgIcon)
// 定义一个加载目录的函数
const requireAll = requireContext => requireContext.keys().map(requireContext)
const req1 = require.context('@/assets/icons', false, /\.svg$/)
const req2 = require.context('@/packages', false, /\.svg$/)
// 加载目录下的所有 svg 文件
requireAll(req1)
requireAll(req2)

END

撒花 ✿✿ヽ(°▽°)ノ✿