-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
[🐞] When try to load component dinamically via import I get error #2643
Comments
|
It's my understanding that if you build Qwik components correctly, there's no reason to use dynamic import(). The in-browser runtime loads everything on-demand already. |
@mrclay If the dynamicity is not about the on-demand loading but the programable loading by name as here, then I think it is still relevant |
Same issue here. I want to load components (images as with How am I able to load those based on this key dynamically? This is a fundamental requirement for larger projects. @EggDice @oceangravity did one of you guys had any success with this? |
I wonder if you could use a dynamic import inside https://qwik.builder.io/api/qwik/#useresource or https://qwik.builder.io/docs/components/tasks/#usetask |
This error happens even with no dynamic content. As long as you've got a back quote in the
|
I don't understand. If it is in the database, then I am going to close this issue because this is either not needed or is working as intended. If you want to lazy load and you don't fall into categories 1, 2, or 3 above, please create a new issue. |
@mhevery hey I mean the path or name of a component is read dynamically for the current user or page and I need to load it dynamically. So like mentioned by @oceangravity: const Component = await import(`~/components/${nameFromDatabase}`) Is this possible with Qwik? I find that issue a lot, like if everyone is just building static sites but in big apps you need to load components dynamically based on information related to the user or a product. |
Yes, the above is possible with caveats.
Here is one way to do in Qwik: https://stackblitz.com/edit/qwik-starter-j55lca (but depending on your requirements it may be done differently) import { Component, component$, useSignal, useTask$ } from '@builder.io/qwik';
import { server$ } from '@builder.io/qwik-city';
export const getComponentFromDB = server$((name: string) => {
return {
cmpA: CompA,
cmpB: CompB,
}[name];
});
export const CompA = component$(() => <span>A</span>);
export const CompB = component$(() => <span>B</span>);
export default component$(() => {
const Comp = useSignal<Component<any>>();
const name = useSignal('cmpA');
useTask$(async ({ track }) => {
const compName = track(() => name.value);
Comp.value = await getComponentFromDB(compName);
});
return (
<div>
<input type="text" bind:value={name} />
<div>dynamic component: {Comp.value && <Comp.value />}</div>
</div>
);
}); |
Ahh, that's interesting, thank you Miško. But when you have a lot of components, would there be a possibility to get all components under a specific directory? That's how Vite solves it as far as I remember, so when Vite (or was it webpack?) detects a variable in an import statement, it does that hash map for you at build/dev time. Would be the most elegant solution I think, or is there something that would speak against such an approach? |
@appinteractive it is possible to make a vite plugin that creates such a registry object from a directory of Qwik components. |
In order to support resumability, the code has to go through an optimizer and each function needs to get a hash both on server and client. So there is more to it than just "loading" So in general all lazy loaded code needs to be available to the optimizer at the time of compilation. |
That's out of question, just wondered if the imports could be generated from items like SVGs, images or components inside a specific directory by pointing to a But that is possible is already perfectly fine, just a bit cumbersome maybe to work with in case of updates, aka "Developer Experience" or "DRY" but it's more flexible I guess. Thanks for clarifying though 🙏 |
Hi all E.g:
I already have it working when running dev mode but the problem comes when running the preview as it tries to load the TSX files and not a built js module. Is there any way to make it work, or what I'm trying to do is not possible with Qwik? https://stackblitz.com/edit/github-zlgpzh-2safe5?file=src%2Froutes%2Fdynamic%2F[slug]%2Findex.tsx,src%2Futils%2Fload-component.ts Thanks |
@victorlmneves Make your dynamic component into a switch that imports each component separately |
Something like import.meta.glob might be what you're looking for. It works both for importing components and their Example : const components = import.meta.glob("/src/registry/new-york/examples/*", {
import: "default",
eager: true,
});
const componentsCodes = import.meta.glob("/src/registry/new-york/examples/*", {
as: "raw",
eager: true,
});
type ComponentPreviewProps = QwikIntrinsicElements["div"] & {
name: string;
align?: "center" | "start" | "end";
code?: string;
language?: "tsx" | "html" | "css";
};
export const ComponentPreview = component$<ComponentPreviewProps>(
({ name, align = "center", language = "tsx", ...props }) => {
const config = useConfig();
const highlighterSignal = useSignal<string>();
const componentPath = `/src/registry/${config.value.style}/examples/${name}.tsx`;
const Component = components[componentPath] as Component<any>;
useTask$(async () => {
const highlighter = await setHighlighter();
const code = componentsCodes[componentPath];
highlighterSignal.value = highlighter.codeToHtml(code, {
lang: language,
});
});
return (
<div>
...
<Component />
<div dangerouslySetInnerHTML={highlighterSignal.value} />
</div>
)
}
) Benefits:
Drawbacks:
This doesn't seem to affect performance once the dev server is up and running. I haven't had the ability/time to test this in production yet (because of a qwik-ui bug). For my use case the drawbacks outweigh the benefits. +15 seconds or more every time I run pnpm dev is not worth it for me. I think I'll be better off by importing the components where they're needed and passing them through with a Slot. I think you should be able to do the same even with your user config coming from the database. |
@wmertens not sure if I got it. Can you detail? |
Sorry for the oversight, it's actually possible to import.meta.glob without Rectified example: type ComponentPreviewProps = QwikIntrinsicElements["div"] & {
name: string;
align?: "center" | "start" | "end";
language?: "tsx" | "html" | "css";
};
export const ComponentPreview = component$<ComponentPreviewProps>(
({ name, align = "center", language = "tsx", ...props }) => {
const config = useConfig();
const highlighterSignal = useSignal<string>();
const componentPath = `/src/registry/${config.value.style}/examples/${name}.tsx`;
const Component = useSignal<Component<any>>();
const ComponentRaw = useSignal<string>();
useTask$(async () => {
const highlighter = await setHighlighter();
Component.value = (await components[componentPath]()) as Component<any>;
ComponentRaw.value = (await componentsRaw[componentPath]()) as string;
highlighterSignal.value = highlighter.codeToHtml(
ComponentRaw.value || "",
{
lang: language,
}
);
});
return (
<div>
...
{Component.value && <Component.value />}
<div dangerouslySetInnerHTML={highlighterSignal.value} />
</div>
);
}
); This doesn't seem to add much to dev server starting time and it does allow me to improve my mdx editing DX quite a lot. So in my .mdx files, instead of doing import CardWithFormPreview from "~/registry/new-york/examples/card-with-form";
import CardWithFormCode from "~/registry/new-york/examples/card-with-form?raw";
<ComponentPreview code={CardWithFormCode}>
<CardWithFormPreview q:slot="preview" />
</ComponentPreview> where I have to add weird conditional logic with Slots if I want to pass different components (in my case I also have /registry/default, so I would have to find a way to display the right components based on user config). I can simply do <ComponentPreview name="card-with-form" /> And let my component handle everything for me 👌 . @mhevery what do you think of import.meta.glob as an alternative to dynamic import? If it's not an issue for the optimizer I think it should be presented in the docs as it can significantly improve the DX for some use cases (especially in .mdx files where there's no typescript auto-complete). This would require a bit more testing (especially in prod), but I can work on a docs PR if you like the idea. |
Let's say I am using Qwik's Responsive Images1. I have a number of images I want to optimize, and instead of manually importing each of them: import Image1 from "./image1.jpg?jsx"
import Image2 from "./image2.jpg?jsx"
import Image3 from "./image3.jpg?jsx"
// etc. I would like to do it in a bit more concise way with const images = imagePaths.map(path => import(path).then(module => module.default)) I can't think of any alternative ways of doing this currently. Footnotes |
Hi! As @dhnm mentioned, we need the tool to dynamically load the images. I have data structures containing image file names and would really like to use the Qwik way to import those images, instead of manually specifying each. I've made it work, but I still have a bug. Please advise how to handle the error:
Steps to reproduce (maybe there are other ways if you know the nature of the problem):
Images are loaded fine if you start with the page with images. Here is my code. import {
$,
type Component,
component$,
useSignal,
useTask$,
} from '@builder.io/qwik';
import { server$ } from '@builder.io/qwik-city';
/*
This based on
https://qwik.dev/docs/cookbook/glob-import/
https://github.com/QwikDev/qwik/issues/2643
*/
const metaGlobComponents: Record<string, any> = import.meta.glob(
'/src/media/**/**.*',
{
import: 'default',
query: 'jsx',
eager: true,
}
);
export const GetLazyImageComponent = server$((path: string) => {
// possible paths:
// 'file.png'
// /images/technologies/svg/react_clean.svg
const brokenDownPath = path.split('/').filter(pathPart => !!pathPart);
let fullPath = `/src/media/images/technologies/active/${path}`;
if (brokenDownPath[0]==='images') {
fullPath = `/src/media/${brokenDownPath.join('/')}`
}
if (!metaGlobComponents[fullPath]) {
return;
}
const val = metaGlobComponents[fullPath]();
const MetaComponent = $(()=>val);
return MetaComponent;
});
export default component$((props: any) => {
const MetaComponent = useSignal<Component<any>>();
useTask$(async () => {
const qrlAsyncFunction = await GetLazyImageComponent(props.path)
if (!qrlAsyncFunction) {
return;
}
// eslint-disable-next-line qwik/valid-lexical-scope
MetaComponent.value = (await qrlAsyncFunction) as unknown as Component<any>;
});
return <>{MetaComponent.value && <MetaComponent.value />}</>;
}); Thank you! |
Hi @tgskiv 👋 I assume it stops working for you in 1.8.0? |
Hi @maiieul thanks for the response and sorry for not specifying it in the comment. |
@tgskiv can you try with eager false? |
@vmertens I'm currently proceeding the same task. With eager false it just doesn't load images, with eager true everything seems ok. BUT I have a usecase where I show the same images on 2 pages, 1 carousel, other is tiles. Carousel is static and images load normally, but tiles are dynamically paginated and if I switch to this kind of import and try to navigate to the second page I receive "[vite] Internal server error: Dynamic import() inside Qrl($) scope is not a string, relative paths might break" |
Which component is affected?
Qwik Rollup / Vite plugin
Describe the bug
Hi 😊
Currently, I can import any component by this way, the normal way:
I tried it with success:
But, if I wanna import some component dynamically (async) like:
It fails 😪 with error:
In Vite, you can pass it with
/* @vite-ignore */
comment, but I tried it too with no success.Is there some way to achieve successfully this?
Reproduction
https://stackblitz.com/edit/qwik-starter-qnzpvu?file=src%2Fcomponents%2Fcomponent-b.tsx,src%2Fcomponents%2Fcomponent-c.tsx,src%2Froutes%2Flayout.tsx
Steps to reproduce
npm install && npm start
System Info
Additional Information
No response
The text was updated successfully, but these errors were encountered: