These links

  • How to implement Mock 01?
  • Vue- Element-admin – 02 How are network requests encapsulated?
  • 【 Vue-element-admin Analysis 】 -03 How to implement permission management?

Analysis of the

There are many clues to the icon component, starting with main.js, you can see that an ICONS from the current directory have been introduced:

Just follow through, the code is very simple:

import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon' // svg component

// register globally
Vue.component('svg-icon', SvgIcon)

const req = require.context('./svg'.false./\.svg$/)
const requireAll = requireContext= >
  requireContext.keys().forEach(requireContext)
requireAll(req)
Copy the code

The previous section makes sense at a glance, registering a global component of VUE. The last part is designed into the WebPack API, we can look at: dependency management, combined with the current directory file:

The function of this code is to import all icon files under./ SVG.

Next, jump to SvgIcon to see how it works again:

<template>
  <div
    v-if="isExternal"
    :style="styleExternalIcon"
    class="svg-external-icon svg-icon"
    v-on="$listeners"
  />
  <svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
    <use :xlink:href="iconName" />
  </svg>
</template>

<script>
import { isExternal } from '@/utils/validate'

export default {
  name: 'SvgIcon'.props: {
    iconClass: {
      type: String.required: true
    },
    className: {
      type: String.default: ' '}},computed: {
    isExternal() {
      return isExternal(this.iconClass)
    },
    iconName() {
      return `#icon-The ${this.iconClass}`
    },
    svgClass() {
      if (this.className) {
        return 'svg-icon ' + this.className
      } else {
        return 'svg-icon'}},styleExternalIcon() {
      return {
        mask: `url(The ${this.iconClass}) no-repeat 50% 50%`.'-webkit-mask': `url(The ${this.iconClass}) no-repeat 50% 50%`}}}}</script>

<style scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15 em;
  fill: currentColor;
  overflow: hidden;
}

.svg-external-icon {
  background-color: currentColor;
  mask-size: cover ! important;
  display: inline-block;
}
</style>
Copy the code

The code is not complicated, as you can see, it divides ICONS into internal and external types as follows:

  • It accepts two character attributes,iconClassclassName
  • throughiconClass, it evaluates to three calculated properties:
    • isExternal: Determines whether it is an external icon
    • iconName: used to specifyusexlink:hrefattribute
    • styleExternalIcon: Sets the style of the external icon
  • The fourth calculated property is computed by className:
    • svgClass: Obviously this is the name of the icon class

💡 : The important note here is that v-on=”$listeners” transparently pass events, which are often used in general-purpose component design, and v-on=”$attrs” transparently pass events.

The overall principle is not too complicated, just use Vue.component to register the global component and require.context load file, let’s try to change it to the vue3 version.

Vue3 version

The component registration

Vue3 global components need to be registered with an instance created by createApp, so import SvgIcon directly from main:

import { createApp } from "vue";
import App from "./App.vue";
import SvgIcon from "@/components/SvgIcon/index.vue";
import "./icons";

export const app = createApp(App);

app
  .component("svg-icon", SvgIcon)
  .mount("#app");
Copy the code

Icon component

In fact, there is not much change, we can change the calculated attribute:

<template>
  <div
    v-if="isExternal"
    :style="styleExternalIcon"
    class="svg-external-icon svg-icon"
    v-on="$attrs"
  />
  <svg v-else :class="svgClass" aria-hidden="true">
    <use :xlink:href="iconName" v-on="$attrs" />
  </svg></template> <script lang="ts"> import { computed, defineComponent, PropType } from "vue"; import { isExternal as isExternalUtil } from "@/utils/validate"; export default defineComponent({ name: "index", props: { iconClass: { type: String as PropType<string>, required: true }, className: { type: String as PropType<string> } }, setup(props) { const iconName = computed(() => `#icon-${props.iconClass}`); const isExternal = computed(() => isExternalUtil(props.iconClass)); const styleExternalIcon = computed(() => { return { mask: `url(${props.iconClass}) no-repeat 50% 50%`, "-webkit-mask": `url(${props.iconClass}) no-repeat 50% 50%` }; }); const svgClass = computed(() => { if (props.className) { return "svg-icon " + props.className; } else { return "svg-icon"; }}); return { isExternal, styleExternalIcon, iconName, svgClass }; }}); </script> <style scoped lang="scss"> .svg-icon { width: 1em; height: 1em; Vertical - align: 0.15 em. fill: currentColor; overflow: hidden; } .svg-external-icon { background-color: currentColor; mask-size: cover ! important; display: inline-block; } </style>Copy the code

💡 : Careful readers may have noticed that in vue3, passthrough events are also written as attrs. For details, please refer to the documentation: [Remove attrs, refer to the documentation: [remove listeners] (v3.cn.vuejs.org/guide/migra…).

Last question

Why is it that the icon component is still unavailable when you run the project after everything is done properly?

The reason is that there is no SVG loader, please refer to the official documentation for details.

Here we can also copy the configuration from vue-element-admin directly:

chainWebpack(config) {
    // it can improve the speed of the first screen, it is recommended to turn on preload
    config.plugin("preload").tap(() = >[{rel: "preload".// to ignore runtime.js
        // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
        fileBlacklist: [/\.map$/./hot-update\.js$/./runtime\.. *\.js$/].include: "initial"}]);// when there are many pages, it will cause too many meaningless requests
    config.plugins.delete("prefetch");

    // set svg-sprite-loader
    config.module
      .rule("svg")
      .exclude.add(resolve("src/icons"))
      .end();
    config.module
      .rule("icons")
      .test(/\.svg$/)
      .include.add(resolve("src/icons"))
      .end()
      .use("svg-sprite-loader")
      .loader("svg-sprite-loader")
      .options({
        symbolId: "icon-[name]"}) .end(); config.when(process.env.NODE_ENV ! = ="development".config= > {
      config
        .plugin("ScriptExtHtmlWebpackPlugin")
        .after("html")
        .use("script-ext-html-webpack-plugin"[{// `runtime` must same as runtimeChunk name. default is `runtime`
            inline: /runtime\.. *\.js$/
          }
        ])
        .end();
      config.optimization.splitChunks({
        chunks: "all".cacheGroups: {
          libs: {
            name: "chunk-libs".test: /[\\/]node_modules[\\/]/,
            priority: 10.chunks: "initial" // only package third parties that are initially dependent
          },
          elementUI: {
            name: "chunk-elementUI".// split elementUI into a single package
            priority: 20.// the weight needs to be larger than libs and app or it will be packaged into libs or app
            test: /[\\/]node_modules[\\/]_? element-ui(.*)/ // in order to adapt to cnpm
          },
          commons: {
            name: "chunk-commons".test: resolve("src/components"), // can customize your rules
            minChunks: 3.// minimum common number
            priority: 5.reuseExistingChunk: true}}});// https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
      config.optimization.runtimeChunk("single");
    });
  }
Copy the code