|
+
enableRequestRewrite
|
diff --git a/packages/docs/src/routes/docs/(qwik)/core/rendering/index.mdx b/packages/docs/src/routes/docs/(qwik)/core/rendering/index.mdx
index 061f2ac73f9..df632cf002c 100644
--- a/packages/docs/src/routes/docs/(qwik)/core/rendering/index.mdx
+++ b/packages/docs/src/routes/docs/(qwik)/core/rendering/index.mdx
@@ -100,9 +100,9 @@ export const Child = component$((props: { name: string }) => {
### Rendering a list of items
-Qwik supports rendering lists with either `items.map()` or the [`Each`](../each/index.mdx) component.
+Qwik supports rendering lists with either `items.map()` or the experimental [`Each`](/docs/labs/each/) component.
-For keyed collections, prefer [`Each`](../each/index.mdx). If you render with `items.map()`, every item in the list must have a unique `key` property on the first child returned by the mapping function. The `key` must be a string or number and must be unique within the list.
+For keyed collections, prefer [`Each`](/docs/labs/each/) once you have enabled it with `experimental: ['each']`. If you render with `items.map()`, every item in the list must have a unique `key` property on the first child returned by the mapping function. The `key` must be a string or number and must be unique within the list.
```tsx {6} /data.map/ /key/#a
import { component$ } from '@qwik.dev/core';
diff --git a/packages/docs/src/routes/docs/(qwik)/core/each/index.mdx b/packages/docs/src/routes/docs/labs/each/index.mdx
similarity index 64%
rename from packages/docs/src/routes/docs/(qwik)/core/each/index.mdx
rename to packages/docs/src/routes/docs/labs/each/index.mdx
index 55289e4e23b..e5d3cd1a558 100644
--- a/packages/docs/src/routes/docs/(qwik)/core/each/index.mdx
+++ b/packages/docs/src/routes/docs/labs/each/index.mdx
@@ -1,15 +1,42 @@
---
-title: Each | Components
+title: "\U0001F9EA Each | Experimental"
keywords: 'lists, keyed rendering, control flow, loops'
---
-import CodeSandbox from '../../../../../components/code-sandbox/index.tsx';
+import CodeSandbox from '../../../../components/code-sandbox/index.tsx';
+import { Note } from '~/components/note/note';
# `Each`
+**Stage:** `implementation`
+
+
+ `Each` is experimental and currently a technical preview. Its API and behavior may still change
+ as we gather feedback from real-world usage.
+
+
`Each` is a built-in Qwik component for rendering keyed lists.
-It is most useful when list items have a stable identity and you want Qwik to preserve and move existing rows instead of re-rendering the whole list when the order changes.
+It is most useful when list items have a stable identity and you want Qwik to preserve and move
+existing rows instead of re-rendering the whole list when the order changes.
+
+To use it, you must add `experimental: ['each']` to your `qwikVite` plugin options:
+
+```ts
+// vite.config.ts
+import { defineConfig } from 'vite';
+import { qwikVite } from '@qwik.dev/core/optimizer';
+
+export default defineConfig(() => {
+ return {
+ plugins: [
+ qwikVite({
+ experimental: ['each'],
+ }),
+ ],
+ };
+});
+```
```tsx /Each/ /key$/ /item$/
@@ -54,7 +81,8 @@ export default component$(() => {
- `key$`: returns a stable unique key for each item
- `item$`: returns the JSX for one item
-`item$` must return a single JSX node. If you need multiple siblings, wrap them in a container element or a `Fragment`.
+`item$` must return a single JSX node. If you need multiple siblings, wrap them in a container
+element or a `Fragment`.
```tsx /Fragment/ /Each/
import { Each, Fragment, component$ } from '@qwik.dev/core';
@@ -80,7 +108,8 @@ export default component$(() => {
If your list items have stable keys, prefer `Each`.
-It is the specialized keyed-list primitive in Qwik and is designed to preserve and move existing rows efficiently.
+It is the specialized keyed-list primitive in Qwik and is designed to preserve and move existing
+rows efficiently.
Use `items.map()` for simple list rendering or when you do not have a stable key:
@@ -92,6 +121,10 @@ Use `items.map()` for simple list rendering or when you do not have a stable key
```
+Qwik may also be able to optimize some keyed `.map()` patterns into `Each` under the hood in the
+future when it can prove the transformation is safe. For now, use `Each` explicitly when you want
+its keyed-row preservation behavior.
+
Prefer `Each` when:
- items have stable ids
@@ -108,13 +141,19 @@ Prefer `map()` when:
`Each` uses the value from `key$` as the identity of the row.
-When keys stay the same, Qwik can move and reuse the existing rows instead of recreating them. This is why `Each` is a good fit for drag-and-drop lists, sortable tables, and other UIs where items move around often.
+When keys stay the same, Qwik can move and reuse the existing rows instead of recreating them. This
+is why `Each` is a good fit for drag-and-drop lists, sortable tables, and other UIs where items
+move around often.
-There is an important tradeoff: if you replace an item object but keep the same key, `Each` preserves the existing row. That means the row template is not re-run just because a new object with the same key was passed in.
+There is an important tradeoff: if you replace an item object but keep the same key, `Each`
+preserves the existing row. That means the row template is not re-run just because a new object
+with the same key was passed in.
So `Each` is usually the right choice for keyed collections. It has a different update strategy.
-If you need the row output to update from replaced item objects, prefer `map()`. If you need row-local updates with `Each`, model that row state with Qwik reactivity instead of relying on replacing the whole item object.
+If you need the row output to update from replaced item objects, prefer `map()`. If you need
+row-local updates with `Each`, model that row state with Qwik reactivity instead of relying on
+replacing the whole item object.
## Keys
@@ -127,5 +166,5 @@ Using unstable keys defeats the main benefit of `Each` and can produce confusing
## Related
-- For general list rendering, see [Rendering](../rendering/index.mdx)
+- For general list rendering, see [Rendering](/docs/core/rendering/)
- For the generated API entry, see [API Reference](/api/qwik/#each)
diff --git a/packages/docs/src/routes/docs/menu.md b/packages/docs/src/routes/docs/menu.md
index 24ab77432b4..c5df915ab6d 100644
--- a/packages/docs/src/routes/docs/menu.md
+++ b/packages/docs/src/routes/docs/menu.md
@@ -16,7 +16,6 @@
- [Tasks & Lifecycle]()
- [Context]()
- [Slots]()
-- [Each]()
- [Rendering]()
- [Styling]()
- [API Reference](/api/qwik/)
@@ -159,6 +158,7 @@
## Experimental 🧪
- [Overview](/docs/labs/index.mdx)
+- [Each](/docs/labs/each/index.mdx)
- [Insights](/docs/labs/insights/index.mdx)
- [Typed Routes](/docs/labs/typed-routes/index.mdx)
- [Devtools](/docs/labs/devtools/index.mdx)
diff --git a/packages/docs/vite.config.ts b/packages/docs/vite.config.ts
index 88207c97920..206d85d6b61 100644
--- a/packages/docs/vite.config.ts
+++ b/packages/docs/vite.config.ts
@@ -253,7 +253,7 @@ export default defineConfig(({ mode }) => {
}),
qwikVite({
debug: false,
- experimental: ['insights'],
+ experimental: ['each', 'preventNavigate', 'insights'],
}),
partytownVite({
dest: resolve('dist', '~partytown'),
diff --git a/packages/qwik-vite/src/plugins/plugin.ts b/packages/qwik-vite/src/plugins/plugin.ts
index 612bd4d584e..1f0f6d36083 100644
--- a/packages/qwik-vite/src/plugins/plugin.ts
+++ b/packages/qwik-vite/src/plugins/plugin.ts
@@ -63,6 +63,8 @@ const CLIENT_STRIP_CTX_NAME = [
* @public
*/
export enum ExperimentalFeatures {
+ /** Enable the Each keyed-list primitive */
+ each = 'each',
/** Enable the usePreventNavigate hook */
preventNavigate = 'preventNavigate',
/** Enable the Valibot form validation */
@@ -774,6 +776,28 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) {
const dir = parsedPathId.dir;
const base = parsedPathId.base;
const ext = parsedPathId.ext.toLowerCase();
+
+ const mode =
+ opts.target === 'lib'
+ ? 'lib'
+ : opts.buildMode === 'development'
+ ? devServer?.hot && opts.devTools.hmr
+ ? 'hmr'
+ : 'dev'
+ : 'prod';
+
+ let didChange = false;
+ if (mode !== 'lib') {
+ // this messes a bit with the source map, but it's ok for if statements
+ code = code.replaceAll(/__EXPERIMENTAL__\.(\w+)/g, (_, feature) => {
+ didChange = true;
+ if (opts.experimental?.[feature as ExperimentalFeatures]) {
+ return 'true';
+ }
+ return 'false';
+ });
+ }
+
if (ext in TRANSFORM_EXTS || TRANSFORM_REGEX.test(pathId)) {
/** Strip client|server code from qwik server|client, but not in lib/test */
const strip = opts.target === 'client' || opts.target === 'ssr';
@@ -782,25 +806,6 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) {
`Transforming ${id} (for: ${isServer ? 'server' : 'client'}${strip ? ', strip' : ''})`
);
- const mode =
- opts.target === 'lib'
- ? 'lib'
- : opts.buildMode === 'development'
- ? devServer?.hot && opts.devTools.hmr
- ? 'hmr'
- : 'dev'
- : 'prod';
-
- if (mode !== 'lib') {
- // this messes a bit with the source map, but it's ok for if statements
- code = code.replaceAll(/__EXPERIMENTAL__\.(\w+)/g, (_, feature) => {
- if (opts.experimental?.[feature as ExperimentalFeatures]) {
- return 'true';
- }
- return 'false';
- });
- }
-
let filePath = base;
if (opts.srcDir) {
filePath = path.relative(opts.srcDir, pathId);
@@ -890,7 +895,7 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) {
debug(`transform(${count})`, 'Not transforming', id);
- return null;
+ return didChange ? { code } : null;
};
type OutputAnalyzer = {
diff --git a/packages/qwik-vite/src/qwik.optimizer.api.md b/packages/qwik-vite/src/qwik.optimizer.api.md
index f7dae706875..78f8dc1c872 100644
--- a/packages/qwik-vite/src/qwik.optimizer.api.md
+++ b/packages/qwik-vite/src/qwik.optimizer.api.md
@@ -19,6 +19,7 @@ export type BundleGraphAdder = (manifest: QwikManifest) => Record {
/** @internal */
export const eachCmp = (props: EachProps) => {
+ if (!__EXPERIMENTAL__.each) {
+ throw new Error(
+ 'Each is experimental and must be enabled with `experimental: ["each"]` in the `qwikVite` plugin.'
+ );
+ }
useTaskQrl(/*#__PURE__*/ inlinedQrl(eachCmpTask, '_eaT', [props]));
return SkipRender;
};
-/** @public */
+/** @public @experimental */
export const Each = /*#__PURE__*/ componentQrl>(
/*#__PURE__*/ inlinedQrl(eachCmp, '_eaC')
) as EachComponent;
diff --git a/vitest.config.ts b/vitest.config.ts
index 1da2a78c1ba..efcee8c98d2 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -8,6 +8,7 @@ export default defineConfig({
debug: !true,
srcDir: `./packages/qwik/src`,
devTools: { hmr: false },
+ experimental: ['each'],
}),
tsconfigPaths({ ignoreConfigErrors: true }),
],
|