Storybook 不会在 Vue 项目的组件中加载 SVG

Storybook does not load SVGs in Components of Vue project

我用 vue-cli 创建了一个 Vue 项目,稍后通过 npm install --save-dev storybook 安装了 storybook 作为依赖项来显示我创建的组件。

故事书 webpack 配置如下所示:

const path = require('path')

module.exports = async ({ config, mode }) => {
  config.module.rules.push(
    {
      test: /\.svg$/,
      use: ['vue-svg-loader']
    },
    {
      test: /\.scss$/,
      use: [
        'style-loader',
        'css-loader',
        {
          loader: 'sass-loader',
          options: {
            sourceMap: false,
            data: '@import "./src/assets/styles/main.scss";'
          }
        }
      ],
      include: path.resolve(__dirname, '../')
    }
  )

  return config
}

故事 index.js 是这样的:

import Vue from 'vue';
import { storiesOf } from '@storybook/vue';
import Alert from '../src/components/buttons/Alert.vue';

storiesOf('Components', module)
  .add('Alert', () => ({
    components: { Alert },
    template: '<Alert />'
  }))

出于某种原因,当我尝试加载由 SVG 组成的组件时,我得到:

显示了组件本身,但没有显示 SVG 所在的部分。

有趣的是,尽管它在尝试在 Vue 的主应用程序中显示组件时工作得很好。 vue.config 看起来像这样:

module.exports = {
  css: {
    loaderOptions: {
      sass: {
        data: `@import "./src/assets/styles/main.scss";`
      }
    }
  },
  chainWebpack: config => {
    const svgRule = config.module.rule("svg");

    svgRule.uses.clear();

    svgRule.use("vue-svg-loader").loader("vue-svg-loader");
  }
};

为什么 storybook 无法正确加载 svg 而主 Vue 应用程序可以?

编辑: 我已经继续并简单地使用了 webpacks file-loader 以确保它与 vue-svg-loader:

{
  test: /\.(png|jpg|gif|svg)$/,
  use: [
    {
      loader: 'file-loader',
      options: {},
    },
  ],
},

但我遇到了同样的错误。 在尝试应用第一个答案将 push() 替换为 unshift() 规则后,我收到此错误:Error in parsing SVG: Non-whitespace before first tag.

编辑2:

这是我要加载的 SVG 文件之一:

<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="exclamation-triangle" class="svg-inline--fa fa-exclamation-triangle fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"></path></svg>

Edit3: *Alert.vue in projectroot/src/components/banners/Alert.vue, Storybook 的 Webpack 配置在 projectroot/.storybook/webpack.config.js

<template>
  <div v-if="show" :class="`alert_wrap ${model}`">
    <IconBase>
      <component :is="iconComponent" />
    </IconBase>
    <div class="alert_text">
      <p v-if="title">{{ title }}</p>
      <p>{{ msg }}</p>
    </div>
    <IconBase>
      <TimesIcon @click="show = !show" aria-hidden="Close" />
    </IconBase>
  </div>
</template>

<script>
import IconBase from "../icons/IconBase.vue";
import CautionIcon from "../../assets/icons/exclamation-triangle-solid.svg";
import InfoIcon from "../../assets/icons/info-circle-solid.svg";
import SuccessIcon from "../../assets/icons/check-circle-solid.svg";
import TimesIcon from "../../assets/icons/times-solid.svg";

export default {
  name: "Alert",
  components: {
    CautionIcon,
    InfoIcon,
    SuccessIcon,
    TimesIcon,
    IconBase
  },
  props: {
    msg: {
      type: String,
      required: true
    },
    title: {
      type: String
    },
    model: {
      type: String,
      default: "info",
      validator(type) {
        return ["caution", "info", "success"].includes(type);
      }
    }
  },
  computed: {
    iconComponent() {
      return `${this.model}-icon`;
    }
  },
  data() {
    return {
      show: true
    };
  }
};
</script>

<style scoped lang="scss">
  /* some styles depending on the props passed */
</style>

因为 storybook 已经有一个 file-loader 可以将您的 svg 作为资产发出。我们应该首先从 file-loader 中排除 svg

// exclude svg from existing rule
config.module.rules = config.module.rules.map(rule => {
  if (rule.loader && rule.loader.endsWith('file-loader')) {
    return {
      ...rule,
      test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani)(\?.*)?$/,
    };
  }

  return rule;
});

// now we can add our svg loader
config.module.rules.push(
    {
      test: /\.svg$/,
      use: ['vue-svg-loader']
    },
    {
      test: /\.scss$/,
      use: [
        'style-loader',
        'css-loader',
        {
          loader: 'sass-loader',
          options: {
            sourceMap: false,
            data: '@import "./src/assets/styles/main.scss";'
          }
        }
      ],
      include: path.resolve(__dirname, '../')
    }
  )

您使用的是 UTF-8 编码吗?

你有

的错误

Error in parsing SVG: Non-whitespace before first tag

用于与使用 Byte-Order-Mark (BOM) 的编码相关。

"Zero width no-break space" unicode character which Windows systems automatically prepend to utf-8 files

如果您使用十六进制编辑器检查文件,您可以看到罪魁祸首。

您可以在加载文件之前手动将编码更改为 "without BOM" 或使用 replace("\ufeff", "") 之类的内容进行替换。

如果您使用的是编辑器,那么您可能具有特定 "with BOM" 编码的配置。例如在记事本++

添加 jsx 选项的代码

 loader: 'file-loader',
      options: {
        jsx: true,
      },

@liximomo 的回答几乎是正确的。您应该从 file-loader 处理规则中删除 svg 文件,但规则搜索条件应该略有不同:

module.exports = ({ config }) => {

    let rule = config.module.rules.find(r =>
        // it can be another rule with file loader 
        // we should get only svg related
        r.test && r.test.toString().includes('svg') &&
        // file-loader might be resolved to js file path so "endsWith" is not reliable enough
        r.loader && r.loader.includes('file-loader')
    );
    rule.test = /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani)(\?.*)?$/;

    config.module.rules.push(
        {
            test: /\.svg$/,
            use: ['vue-svg-loader']
        }
    )

    // ...

    return config;
}

另一种在不改变 Webpack 配置规则的情况下导入 SVG 的方法是指定一个 内联 加载器,当某些组件已经在 [=20] 中使用 SVG 时,这有助于避免错误=] 或 HTML 作为文件路径。示例:

// disable all rules with !! and use just vue-svg-loader
import CautionIcon from "!!vue-svg-loader!../../assets/icons/exclamation-triangle-solid.svg";