Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to have a dynamic import SVG component #66

Closed
SirMishaa opened this issue Sep 1, 2022 · 8 comments
Closed

How to have a dynamic import SVG component #66

SirMishaa opened this issue Sep 1, 2022 · 8 comments

Comments

@SirMishaa
Copy link

SirMishaa commented Sep 1, 2022

Hello,

I have an issue when building the Vue (2.7) app:

[plugin:vite:dynamic-import-vars] invalid import "/icons/${this.name}.svg?raw". Variable absolute imports are not supported, imports must start with ./ in the static part of the import. For example: import(./foo/${bar}.js).

We're using this component everywhere in our application.
SwIcon.vue :

<template lang="pug">
  div
    span.block(
      :class="[styleClass, sizeClass]"
      :style="{strokeLinecap: 'round', strokeLinejoin: 'round'}"
      @click="$emit('click')"
      v-html="svgRawContent"
    )
</template>

<script>
export default {
  name: 'SwIcon',
  props: {
    name: {
      type: String,
      default: '',
    },
    unstyled: {
      type: Boolean,
      default: false,
    },
    size: {
      type: String,
      default: 'w-6 h-6',
    },
  },
  data () {
    return {
      svgRawContent: '',
    };
  },
  computed: {
    sizeClass () {
      return this.size;
    },
    styleClass () {
      return this.unstyled ? '' : 'fill-none stroke-current stroke-2';
    },
  },
  // Todo: Replace by Suspense or something else when upgrading to Vue 3 to avoid async lifecycle method
  async mounted () {
    const svgObject = await import(`/icons/${this.name}.svg?raw`);
    this.svgRawContent = svgObject.default;
  },
};
</script>

Like you can see, we have this import await import(`/icons/${this.name}.svg?raw`); which is working good in development.

I guess the problem comes from the fact that during the build phase, since it is dynamic, it is not able to bundle the svg since it does not know them.

But then, how can I keep a similar component and make it work? Even if it has to bundle the whole icon's folder, it's not a big deal if it's lazy loaded.

Thanks in advance, I appreciate it ❤️

PS: I have already check #24, but I don't understand well for my case :/

@SirMishaa
Copy link
Author

SirMishaa commented Sep 1, 2022

Folder architecture is the following :
image

Basically, SVG component is here : frontend/components/swComponents/SwIcon.vue
Svg are in this folder: frontend/icons but we have some icons in subfolder, so frontend/car_discounts for example. (I think, that's maybe the tricky part)

@jpkleemans
Copy link
Owner

jpkleemans commented Sep 1, 2022

Hi, does it work when you use a relative path?

await import(`../../icons/${this.name}.svg`)

@SirMishaa
Copy link
Author

SirMishaa commented Sep 1, 2022

Hi, does it work when you use a relative path?

await import(`../../icons/${this.name}.svg`)

No, I got the following error message :

Unknown variable dynamic import: ../../icons/car_discounts/car_lane_departure.svg

I think It's because the "name", is not a name, but sometime a path + name (yeah, bad naming, we're doing a lot of refactorings 😅)

@jpkleemans
Copy link
Owner

Unknown variable dynamic import: ../../icons/car_discounts/car_lane_departure.svg

It seems that the file can't be found. Are you sure icons/car_discounts/car_lane_departure.svg exists?

@SirMishaa
Copy link
Author

image

Yep : frontend/icons/car_discounts/car_lane_departure.svg (My IDE generate the path, and I've double-checked)

I'm not sure the error message mean that, I don't know where does it come from

@SirMishaa
Copy link
Author

Any idea? :(

@jpkleemans
Copy link
Owner

jpkleemans commented Sep 7, 2022

Can you try to use defineAsyncComponent to see if that works?

import { defineAsyncComponent } from 'vue'

export default {
  mounted () {
    const svgObject = defineAsyncComponent(() => import(`/icons/${this.name}.svg?raw`))
  }
}

@adrianrivers
Copy link

adrianrivers commented Sep 16, 2022

I'm using this code to render icons from a subfolder dynamically

<template>
  <IconComponent  :viewBox="viewBox" />
</template>

<script setup lang="ts">
import type { Component } from 'vue'
import { IconName } from '@/types/IconName'

export interface IconProps {
  name: IconName
  fill?: Color
  viewBox?: string | null
}

const props = withDefaults(defineProps<IconProps>(), {
  fill: 'default',
  viewBox: null,
})

const { loader } = createIconMap().get(props.name) ?? {}
const IconComponent = loader ? defineAsyncComponent(loader) : null

function getIconNameFromPath(path: string) {
  const pathSplit = path.split('/')
  const filename = pathSplit[pathSplit.length - 1] || ''
  const iconName = filename.replace('.svg', '')

  return iconName
}

function createIconMap() { 
  const importGlob = import.meta.glob('@/assets/icons/**/*.svg')
  const iconMap = new Map<string, { loader: () => Promise<Component> }>([])

  for (const path in importGlob) {
    const iconName = getIconNameFromPath(path)
    iconMap.set(iconName, { loader: importGlob[path] })
  }

  return iconMap
}
</script>

Sorry I haven't worked much with Vue 2 but I'm sure something for Vue 2 could be adapted from the code above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants