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

Posted

技术标签:

【中文标题】Storybook 不会在 Vue 项目的组件中加载 SVG【英文标题】:Storybook does not load SVGs in Components of Vue project 【发布时间】:2019-11-20 03:05:24 【问题描述】:

我用vue-cli 创建了一个Vue 项目,后来通过npm install --save-dev storybook 安装了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 所在的部分未显示 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 在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>

【问题讨论】:

Alert.vue 长什么样子? @Neil.Work 请查看我编辑的答案。 那么如果你注释掉你试图用来渲染图标的动态组件,那么硬编码的TimesIcon渲染成功了吗? @Neil.Work 我不确定,但它在下面得到了解决。 太好了,这就是我想要的。 【参考方案1】:

因为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, '../')
    
  )

【讨论】:

感谢您的评论。欣赏它。我已经尝试了您推荐的解决方案,但我收到了一个新错误Error in parsing SVG: Non-whitespace before first tag。我也继续将其改回file-loader,这给了我与原始帖子完全相同的错误。 您还应该从文件加载器中排除 svg。【参考方案2】:

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

你有错误

解析 SVG 时出错:第一个标签前没有空格

用于与使用字节顺序标记 (BOM) 的编码相关。

Windows 系统中的“零宽度不间断空格”unicode 字符 自动添加到 utf-8 文件

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

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

如果您使用了编辑器,您可能已经针对特定的“带 BOM”编码进行了配置。例如在记事本++

添加 jsx 选项的代码

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

【讨论】:

感谢您的回答。我已经开始编辑我的 OP 以包含 SVG。我在那里看不到任何without BOM。顺便说一句,它是 Fontawesomes SVG。 它是内部文件。尽管它是 SVG,但它具有内部编码。可能您使用的工具或创建 SVG 时使用的工具已使用带有 BOM 的 UTF-8 编码,那么现在您正在尝试解析并找到无法解析然后失败的 BOM 字符。没有 BOM 的 UTF-8 也存在。不需要 BOM。 SVG 文件的编码必须是您的接收器可以管理的。编码是地狱。如果您可以更改编码,请尝试使用 Base64 简单的一种。你在用记事本++吗? 我没有使用Notepad++,我实际上根本没有触摸文件。它来自 Fontawesome,所以我刚刚下载了这个文件:https://fontawesome.com/icons/exclamation-triangle?style=solid 并导入了它。 你最终使用的是file-loader还是svg-loader?使用文件加载器并添加选项 jsx: true。我编辑帖子以添加代码。 我已经尝试过vue-svg-loaderfile-loader,它们都在我的 OP 中给出了错误。使用哪一个并不重要。立即尝试 JSX。【参考方案3】:

@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 的方法是指定一个 inline 加载器,当某些组件已经在 CSS 或 html 中使用 SVG 作为文件路径时,这有助于避免错误.示例:

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

【讨论】:

老兄,非常感谢,终于成功了。你能详细说明问题是什么吗?只是为了让我彻底理解。 @supersize 当然,您的 svg 文件是用 file-loader 处理的,它只是为该文件创建 url 并将该文件作为文件路径字符串导入(您在控制台错误中看到了这一点) .所以应该从file-loader规则test正则表达式中删除svg部分,以禁用对此类文件的处理,并允许我们添加自定义vue-svg-loader加载器。 @supersize 至于解析 SVG 问题,我认为 vue-svg-loader 尝试处理来自 file-loader 的输出 URL 字符串,因为 svg 部分不会从其 test reg exp 中正确删除。 但是为什么它首先不能只使用file-loader 呢? @supersize 根据您的代码,我认为您希望将 SVG 作为 Vue 组件加载(您正在导入 svg 并将它们放置到 components 部分)但 file-loader 具有完全不同的目的。跨度>

以上是关于Storybook 不会在 Vue 项目的组件中加载 SVG的主要内容,如果未能解决你的问题,请参考以下文章

在 Vue Storefront Next 项目中使用 @nuxtjs/storybook 时,Vue 组件未呈现 - 可能是 Typescript 问题?

在 storybook v6.4 中加载 css 模块类的问题

如何在 Storybook 中注册全局 Vue 组件?

写vue3+ jsx+ts语法+ storybook展示的组件库

写vue3+ jsx+ts语法+ storybook展示的组件库

写vue3+ jsx+ts语法+ storybook展示的组件库