Storybook build - Unknown variable dynamic import: ./{filename}.svg?component
Closed this issue · 8 comments
I have a library built using Vite (in library mode) + Vue3 + Storybook + TypeScript and I am facing problems building the project and/or building Storybook when Vite-SVG-Loader is used.
The component that uses a SVG:
<script setup lang="ts">
import { computed, defineAsyncComponent } from "vue";
const props = withDefaults(defineProps<{ name: string }>(), {
name: 'add'
});
const iconComponent = computed(() => {
return defineAsyncComponent(
() => import(`./assets/${props.name}.svg?component`)
);
});
</script>
<template>
<component :is="iconComponent" />
</template>
Scenario 1
By keeping the ?component
on the URL for the defineAsyncComponent
function, vite build
and start-storybook
works perfectly fine but although build-storybook
runs fine, when you try to access a component on storybook that uses the async loaded SVG you get the following error:
Unknown variable dynamic import: ./assets/add.svg?component
Error: Unknown variable dynamic import: ./assets/add.svg?component
at http://127.0.0.1:8080/assets/iframe.d31911ec.js:930:5989
at new Promise ()
at variableDynamicImportRuntime0 (http://127.0.0.1:8080/assets/iframe.d31911ec.js:930:5886)
at http://127.0.0.1:8080/assets/iframe.d31911ec.js:930:6317
at pe (http://127.0.0.1:8080/assets/iframe.d31911ec.js:119:21340)
at setup (http://127.0.0.1:8080/assets/iframe.d31911ec.js:119:22194)
at callWithErrorHandling (http://127.0.0.1:8080/assets/iframe.d31911ec.js:119:848)
at setupStatefulComponent (http://127.0.0.1:8080/assets/iframe.d31911ec.js:119:68463)
at setupComponent (http://127.0.0.1:8080/assets/iframe.d31911ec.js:119:68106)
at Ht (http://127.0.0.1:8080/assets/iframe.d31911ec.js:119:47236)
Scenario 2
On the other hand, by removing the ?component
, everything Storybook related works but the vite build
throws the following error:
Invalid value "umd" for option "output.format" - UMD and IIFE output formats are not supported for code-splitting builds.
error during build:
Error: Invalid value "umd" for option "output.format" - UMD and IIFE output formats are not supported for code-splitting builds.
at error (./node_modules/rollup/dist/shared/rollup.js:198:30)
at validateOptionsForMultiChunkOutput (./node_modules/rollup/dist/shared/rollup.js:16207:16)
at Bundle.generate (./node_modules/rollup/dist/shared/rollup.js:16041:17)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async ./node_modules/rollup/dist/shared/rollup.js:23679:27
at async catchUnfinishedHookActions (./node_modules/rollup/dist/shared/rollup.js:23121:20)
at async doBuild (./node_modules/vite/dist/node/chunks/dep-27bc1ab8.js:39180:26)
at async build (./node_modules/vite/dist/node/chunks/dep-27bc1ab8.js:39011:16)
at async CAC. (./node_modules/vite/dist/node/cli.js:738:9)
My project configuration
Packages used:
- storybook-builder-vite:
^0.1.23
- @storybook/vue3:
^6.5.0-alpha.55
- typescript:
~4.5.5
- vite:
^2.9.5
- vite-plugin-vue-type-imports:
^0.1.3
- vite-svg-loader:
^3.2.0
Vite setup
import { fileURLToPath, URL } from "url";
import { defineConfig } from "vite";
import path from "path";
import vue from "@vitejs/plugin-vue";
import ViteSvgLoader from "vite-svg-loader";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), ViteSvgLoader()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
build: {
lib: {
entry: path.resolve(__dirname, "src/main.ts"),
name: "DesignSystem",
fileName: (format) => `index.${format}.js`,
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "Vue",
},
},
},
},
});
Storybook main.js
const ViteSvgLoader = require('vite-svg-loader')
module.exports = {
...
"framework": "@storybook/vue3",
"core": {
"builder": "storybook-builder-vite"
},
async viteFinal(config, { configType }) {
config.plugins.push(ViteSvgLoader());
return config;
},
}
Any ideas?
Found the issue!!
The import
statement doesn't support backticks because linking, where modules are loaded, is a pre-runtime process.
From what I'm guessing, since template literals signal an intent for interpolation which would be executed at runtime, only vanilla string literals are allowed.
The solution is just to go with the conventional way to concatenate strings.
async () => import("./assets/" + props.name + ".svg?component")
// instead of
() => import(`./assets/${props.name}.svg?component`)
I will reopen this issue because it's actually still there!
Maybe this is due to the build.lib
options enabled in Vite! It seems that the SVG files are not exposed along with the built files.
That is it! After running some test this is the problem.
When using the lib mode along with defineAsyncComponent
, the SVG is not exported.
Just by removing the defineAsyncComponent, it will work as intended.
Need to find a way to make that happen.
@bissolli I had a similar problem. Based on your work I was able to get it done:
I had to use vite-plugin-glob here.
It might work without the plugin if you don't need to ?component
suffix (vites glob import doesn't support that).
<!-- Icon.vue -->
<template>
<component :is="iconComponent" />
</template>
<script setup lang="ts">
import { computed, defineAsyncComponent, type Component } from "vue";
function importIcons() {
function lowerCaseFirstLetter(str: string) {
return str.charAt(0).toLowerCase() + str.slice(1);
}
function getFileNameFromPath(path: string) {
const file = path.split("/").pop() ?? "";
if (!file) return file;
return file.split(".").shift() ?? "";
}
const iconImports = import.meta.importGlob<Component>(
"@/assets/icons/*.svg",
{
query: { component: true },
export: "default",
}
);
const icons: Record<string, () => Promise<Component>> = {};
for (const path in iconImports) {
const iconName = lowerCaseFirstLetter(getFileNameFromPath(path));
icons[iconName] = iconImports[path];
}
return icons;
}
const props = defineProps({
name: {
type: String,
default: "info",
},
});
const icons = importIcons();
const iconComponent = computed(() =>
icons[props.name] ? defineAsyncComponent(icons[props.name]) : null
);
</script>
// .storybook/main.ts
const { loadConfigFromFile, mergeConfig } = require("vite");
const path = require("path");
const GlobPlugin = require("vite-plugin-glob");
const svgLoader = require("vite-svg-loader");
module.exports = {
stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
framework: "@storybook/vue3",
core: {
builder: "@storybook/builder-vite",
},
async viteFinal(config, { configType }) {
// https://github.com/cafebazaar/emroozjs/blob/master/.storybook/main.js
const { config: userConfig } = await loadConfigFromFile(
path.resolve(__dirname, "../vite.config.ts")
);
return mergeConfig(config, {
...userConfig,
// see: https://github.com/storybookjs/builder-vite/issues/298#issuecomment-1087405464
resolve: {
...config.resolve,
...userConfig.resolve,
alias: [
{
find: "@storybook/core/client",
replacement: "@storybook/core-client",
},
{
find: "@",
replacement: path.resolve(__dirname, "../src"),
},
/* https://v3.vuejs.org/guide/installation.html#for-server-side-rendering */
{
find: "vue",
replacement: path.resolve(
__dirname,
"../node_modules/vue/dist/vue.esm-bundler.js"
),
},
],
},
// manually specify plugins to avoid conflict
plugins: [
// ...config.plugins
svgLoader({
defaultImport: "url",
}),
// https://github.com/antfu/vite-plugin-glob
GlobPlugin({
// enable to let this plugin interpret `import.meta.glob`
// takeover: true,
}),
],
});
},
};
// vite.config.ts
import { fileURLToPath, URL } from "url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import GlobPlugin from "vite-plugin-glob";
import svgLoader from "vite-svg-loader";
const pathSrc = fileURLToPath(new URL("./src", import.meta.url));
// https://vitejs.dev/config/
export default defineConfig({
build: {
// sourcemap: true,
lib: {
entry: `${pathSrc}/lib.ts`,
name: "my-ui",
formats: ["es", "umd"],
fileName: (format) => `my-ui.${format}.js`,
},
rollupOptions: {
output: {
inlineDynamicImports: true,
},
},
// rollupOptions: {
// // make sure to externalize deps that shouldn't be bundled
// // into your library
// external: ["vue"],
// output: {
// // Provide global variables to use in the UMD build
// // for externalized deps
// globals: {
// vue: "Vue",
// },
// },
// },
},
plugins: [
vue(),
svgLoader({
defaultImport: "url",
}),
// https://github.com/antfu/vite-plugin-glob
GlobPlugin({
// enable to let this plugin interpret `import.meta.glob`
// takeover: true,
}),
],
resolve: {
alias: {
"@": pathSrc,
},
},
css: {
devSourcemap: true,
preprocessorOptions: {
scss: {
additionalData: `@use "${pathSrc}/styles/commonImports" as *;`,
},
},
},
});
@dpschen thanks for your solution!
Maybe worth mentioning that I have problems updating to the latest version of the vite-plugin-glob. The approach above should work with v.0.2.9.
Not sure why 🤷
Edit:
To be clear: fixing the mentioned breaking change of vite-plugin-glob (rename of the export option property to import) didn’t fix make the new version work for me.
Maybe worth mentioning that I have problems updating to the latest version of the vite-plugin-glob. The approach above should work with v.0.2.9.
Not sure why shrug
Edit: To be clear: fixing the mentioned breaking change of vite-plugin-glob (rename of the export option property to import) didn’t fix make the new version work for me.
You also have to add the restoreQueryExtension
option to the plugin:
GlobPlugin({ restoreQueryExtension: true }),
Since vite-plugin-glob was merged back into Vite, anybody got a working solution for this? Can't seem to figure out how to dynamically load SVG icons.
It works when running in dev mode, but vite build
won't load any icons with the following error in the browser console:
Script from “images/icons/megaphone.svg” was blocked because of a disallowed MIME type (“image/svg+xml”).
It's processing them when building, but I guess due to the hashes being added, it can't find the generated modules.