Closed oceangravity closed 1 year ago
Start a new pull request in StackBlitz Codeflow.
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 .jsx
) dynamically as the data for loading is represented as a string in the database.
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 import()
function, vite throw the error:
[vite] Internal server error: Dynamic import() inside Qrl($) scope is not a string, relative paths might break
Plugin: vite-plugin-qwik
File: <...>/icon.tsx:24:21
24 | const res = await import(`./icons/material/zoom_in.txt?raw`);
| ^
25 | return res.default;
26 | });
./
or ../
will not work (and can't work))icons/material/zoom_in.txt?raw
can not work because they are vite tricks and only exist during build/dev time, not once the application is in production.Same issue here. I want to load components (images as with .jsx ) dynamically, as the data for loading is represented as a string in the database.
I don't understand. If it is in the database, then import()
will not help you, and you need to use some other RPC mechanism.
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. But manually maintaining the registry probably takes less time than writing and maintaining the plugin
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.
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 path + var
without the need of creating an index file containing all assets by hand.
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 I'm trying to implement let's say something similar. The project where I'm currently working uses a server-driven UI architecture but for a "regular website" and not for a mobile app using Vue 3. This means that we don't build pages, only components, and then the page is built based on JSON returned from the server.
E.g:
{
"name": "New Dashboard",
"appearance": "Scene",
"data": {
"id": "unique-page-id-123",
"layout": "MasterLayout",
"title": "Dasboard",
"description": "",
"keywords": "",
"components": [
{
"name": "counter",
"appearance": "counter",
"data": {
"id": "unique-stage-1"
}
},
{
"name": "work",
"appearance": "work",
"data": {
"id": "1",
"type": null,
"client": "Ficaat",
"project": "Layout and Responsive HTML catalogue made to run on tablets (2013)",
"description": "Layout and Responsive HTML catalogue made to run on tablets (2013)",
"slug": "ficaat-tablet",
"image": "ficaat_app.jpg"
}
}
]
}
}
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 To see it working in dev mode, just click on the hamburger menu and then click on "Dynamic Scene"
Thanks
@victorlmneves Make your dynamic component into a switch that imports each component separately
@appinteractive
But when you have a lot of components, would there be a possibility to get all components under a specific directory?
Something like import.meta.glob might be what you're looking for. It works both for importing components and their ?raw
value.
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:
?raw
value for the dev server to show the page. I expect this to increase as the number of components increases. -> -1 for the DX.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.
@victorlmneves Make your dynamic component into a switch that imports each component separately
@wmertens not sure if I got it. Can you detail? Thanks
@appinteractive
Sorry for the oversight, it's actually possible to import.meta.glob without eager:true
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 :ok_hand: .
@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 Images^1. 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 import()
, eg.:
const images = imagePaths.map(path => import(path).then(module => module.default))
I can't think of any alternative ways of doing this currently.
Hi! The last example suggested by @maiieul in this issue seems not to be working anymore, just as the example in the documentation https://qwik.dev/docs/cookbook/glob-import/ does not work as well.
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:
(index):366 QWIK ERROR Code(30): QRLs can not be resolved because it does not have
an attached container. This means that the QRL does not know where it belongs inside the DOM,
so it cant dynamically import() from a relative path.
/src/components/slides/tools/getlazyimagecomponent_server_metacomponent_yoh0mayxono.js?_qrl_parent=tools-icon.tsx GetLazyImageComponent_server_MetaComponent_Yoh0mAyxono Error:
Code(30): QRLs can not be resolved because it does not have an attached container. This means that the QRL does not know where it belongs inside the DOM, so it cant dynamically import() from a relative path.
at createAndLogError (http://localhost:5174/node_modules/@builder.io/qwik/dist/core.mjs?v=faddbb96:159:54)
at logErrorAndStop (http://localhost:5174/node_modules/@builder.io/qwik/dist/core.mjs?v=faddbb96:108:17)
at qError (http://localhost:5174/node_modules/@builder.io/qwik/dist/core.mjs?v=faddbb96:315:12)
at Object.importSymbol (http://localhost:5174/node_modules/@builder.io/qwik/dist/core.mjs?v=faddbb96:333:23)
at resolve (http://localhost:5174/node_modules/@builder.io/qwik/dist/core.mjs?v=faddbb96:8667:44)
at resolveLazy (http://localhost:5174/node_modules/@builder.io/qwik/dist/core.mjs?v=faddbb96:8674:49)
at http://localhost:5174/node_modules/@builder.io/qwik/dist/core.mjs?v=faddbb96:8678:39
at qrl (http://localhost:5174/node_modules/@builder.io/qwik/dist/core.mjs?v=faddbb96:8606:30)
at invokeApply (http://localhost:5174/node_modules/@builder.io/qwik/dist/core.mjs?v=faddbb96:4536:26)
at invoke (http://localhost:5174/node_modules/@builder.io/qwik/dist/core.mjs?v=faddbb96:4528:24)
Images are loaded fine if you start with the page with images.
Here is my code. The error appears somewhere outside the component.
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. It was 1.7.2, now 1.8.0 but it still can be reproduced.
@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