diff --git a/.changeset/qrl-captures-singleton.md b/.changeset/qrl-captures-singleton.md new file mode 100644 index 00000000000..14ca3ddaef3 --- /dev/null +++ b/.changeset/qrl-captures-singleton.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +BREAKING (only when using internal v2 beta API): The `_captures` variable is now a singleton object called `_capturesObj` with a single property `_` that contains the captures string. Normally this should not impact you. diff --git a/.changeset/shared-singletons-global.md b/.changeset/shared-singletons-global.md new file mode 100644 index 00000000000..41090ace6ed --- /dev/null +++ b/.changeset/shared-singletons-global.md @@ -0,0 +1,7 @@ +--- +'@qwik.dev/core': patch +--- + +FIX: Move singletons to `globalThis.__qwik__`. Same-version coexistence is fine and gets to share the singleton state. On the server, only allow one Qwik version per process. + +This is a necessary step to allow Qwik third party libraries to stay external on the server. diff --git a/e2e/adapters-e2e/package.json b/e2e/adapters-e2e/package.json index a385fdb259c..1010e4cb981 100644 --- a/e2e/adapters-e2e/package.json +++ b/e2e/adapters-e2e/package.json @@ -17,11 +17,11 @@ "build.server.deno": "vite build -c adapters/deno/vite.config.ts", "build.server.express": "vite build -c adapters/express/vite.config.ts", "build.types": "tsc --incremental --noEmit", + "bun": "pnpm build.runtime.bun && pnpm serve.bun", + "deno": "pnpm build.runtime.deno && pnpm serve.deno", "deploy": "vercel deploy", "dev": "vite --mode ssr", "dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force", - "bun": "pnpm build.runtime.bun && pnpm serve.bun", - "deno": "pnpm build.runtime.deno && pnpm serve.deno", "express": "pnpm build.runtime.express && pnpm serve.express", "fmt": "prettier --write .", "fmt.check": "prettier --check .", diff --git a/package.json b/package.json index 3622142cbe0..d26b3e5e111 100644 --- a/package.json +++ b/package.json @@ -234,9 +234,9 @@ "lint.prettier": "prettier --cache --check .", "lint.rust": "make lint", "lint.syncpack": "syncpack list-mismatches", + "pack.core": "node --require ./scripts/runBefore.ts scripts/pack-local-qwik-core.ts", "preinstall": "npx only-allow pnpm", "prepare": "simple-git-hooks", - "pack.core": "node --require ./scripts/runBefore.ts scripts/pack-local-qwik-core.ts", "prettier.fix": "prettier --cache --write .", "qwik-push-build-repos": "node --require ./scripts/runBefore.ts ./scripts/qwik-push-build-repos.ts", "release": "changeset publish", @@ -264,9 +264,9 @@ "test.e2e.firefox": "playwright test e2e/qwik-e2e/tests --browser=firefox --config e2e/qwik-e2e/playwright.config.ts", "test.e2e.integrations.bun.chromium": "playwright test e2e/adapters-e2e/tests --project=chromium --config e2e/adapters-e2e/playwright.bun.config.ts", "test.e2e.integrations.bun.webkit": "playwright test e2e/adapters-e2e/tests --project=webkit --config e2e/adapters-e2e/playwright.bun.config.ts", + "test.e2e.integrations.chromium": "playwright test e2e/adapters-e2e/tests --project=chromium --config e2e/adapters-e2e/playwright.config.ts", "test.e2e.integrations.deno.chromium": "playwright test e2e/adapters-e2e/tests --project=chromium --config e2e/adapters-e2e/playwright.deno.config.ts", "test.e2e.integrations.deno.webkit": "playwright test e2e/adapters-e2e/tests --project=webkit --config e2e/adapters-e2e/playwright.deno.config.ts", - "test.e2e.integrations.chromium": "playwright test e2e/adapters-e2e/tests --project=chromium --config e2e/adapters-e2e/playwright.config.ts", "test.e2e.integrations.webkit": "playwright test e2e/adapters-e2e/tests --project=webkit --config e2e/adapters-e2e/playwright.config.ts", "test.e2e.qwik-react.chromium": "playwright test e2e/qwik-react-e2e/tests --project=chromium --config e2e/qwik-react-e2e/playwright.config.ts", "test.e2e.qwik-react.webkit": "playwright test e2e/qwik-react-e2e/tests --project=webkit --config e2e/qwik-react-e2e/playwright.config.ts", diff --git a/packages/docs/package.json b/packages/docs/package.json index 3b6175fca40..1ff97f4b4dd 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -41,8 +41,8 @@ "gray-matter": "4.0.3", "leaflet": "1.9.4", "magic-string": "0.30.21", - "playwright": "1.57.0", "pagefind": "1.4.0", + "playwright": "1.57.0", "prettier": "3.7.4", "prism-themes": "1.9.0", "prismjs": "1.30.0", diff --git a/packages/docs/src/routes/api/qwik/api.json b/packages/docs/src/routes/api/qwik/api.json index 99abf3ef791..3f8dbc7710e 100644 --- a/packages/docs/src/routes/api/qwik/api.json +++ b/packages/docs/src/routes/api/qwik/api.json @@ -2786,7 +2786,7 @@ } ], "kind": "Function", - "content": "Reruns the `taskFn` when the observed inputs change.\n\nUse `useTask` to observe changes on a set of inputs, and then re-execute the `taskFn` when those inputs change.\n\nThe `taskFn` only executes if the observed inputs change. To observe the inputs, use the `obs` function to wrap property reads. This creates subscriptions that will trigger the `taskFn` to rerun.\n\n\n```typescript\nuseTask$: (fn: TaskFn, opts?: TaskOptions) => void\n```\n\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nfn\n\n\n\n\n[TaskFn](#taskfn)\n\n\n\n\n\n
\n\nopts\n\n\n\n\n[TaskOptions](#taskoptions)\n\n\n\n\n_(Optional)_\n\n\n
\n\n**Returns:**\n\nvoid", + "content": "Reruns the `taskFn` when the observed inputs change.\n\nUse `useTask` to observe changes on a set of inputs, and then re-execute the `taskFn` when those inputs change.\n\nThe `taskFn` only executes if the observed inputs change. To observe the inputs, use the `obs` function to wrap property reads. This creates subscriptions that will trigger the `taskFn` to rerun.\n\nCleanup callbacks registered with `cleanup()` or returned from the task may be async. When a task reruns, Qwik waits for the previous cleanup to finish before starting the next invocation.\n\nDuring SSR, the cleanup function is called immediately after SSR completes. Therefore, it is not called on the client side after resuming, but only the second time the task runs on the client.\n\n\n```typescript\nuseTask$: (fn: TaskFn, opts?: TaskOptions) => void\n```\n\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nfn\n\n\n\n\n[TaskFn](#taskfn)\n\n\n\n\n\n
\n\nopts\n\n\n\n\n[TaskOptions](#taskoptions)\n\n\n\n\n_(Optional)_\n\n\n
\n\n**Returns:**\n\nvoid", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-task-dollar.ts", "mdFile": "core.usetask_.md" }, @@ -2800,7 +2800,7 @@ } ], "kind": "Function", - "content": "```tsx\nconst Timer = component$(() => {\n const store = useStore({\n count: 0,\n });\n\n useVisibleTask$(() => {\n // Only runs in the client\n const timer = setInterval(() => {\n store.count++;\n }, 500);\n return () => {\n clearInterval(timer);\n };\n });\n\n return
{store.count}
;\n});\n```\n\n\n```typescript\nuseVisibleTask$: (fn: TaskFn, opts?: OnVisibleTaskOptions) => void\n```\n\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nfn\n\n\n\n\n[TaskFn](#taskfn)\n\n\n\n\n\n
\n\nopts\n\n\n\n\n[OnVisibleTaskOptions](#onvisibletaskoptions)\n\n\n\n\n_(Optional)_\n\n\n
\n\n**Returns:**\n\nvoid", + "content": "```tsx\nconst Timer = component$(() => {\n const store = useStore({\n count: 0,\n });\n\n useVisibleTask$(() => {\n // Only runs in the client\n const timer = setInterval(() => {\n store.count++;\n }, 500);\n return () => {\n clearInterval(timer);\n };\n });\n\n return
{store.count}
;\n});\n```\nVisible Tasks are a variant of Tasks that only run in the browser, and are registered but not executed during SSR. They are useful for running code that should only execute in the browser, such as code that interacts with the DOM or browser APIs.\n\nCleanup callbacks registered with `cleanup()` or returned from the task may be async. When a visible task reruns, Qwik waits for the previous cleanup to finish before starting the next invocation.\n\n\n```typescript\nuseVisibleTask$: (fn: TaskFn, opts?: OnVisibleTaskOptions) => void\n```\n\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nfn\n\n\n\n\n[TaskFn](#taskfn)\n\n\n\n\n\n
\n\nopts\n\n\n\n\n[OnVisibleTaskOptions](#onvisibletaskoptions)\n\n\n\n\n_(Optional)_\n\n\n
\n\n**Returns:**\n\nvoid", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-visible-task-dollar.ts", "mdFile": "core.usevisibletask_.md" }, diff --git a/packages/docs/src/routes/api/qwik/index.mdx b/packages/docs/src/routes/api/qwik/index.mdx index e60d9ff0c6e..fd921e91de3 100644 --- a/packages/docs/src/routes/api/qwik/index.mdx +++ b/packages/docs/src/routes/api/qwik/index.mdx @@ -10458,6 +10458,10 @@ Use `useTask` to observe changes on a set of inputs, and then re-execute the `ta The `taskFn` only executes if the observed inputs change. To observe the inputs, use the `obs` function to wrap property reads. This creates subscriptions that will trigger the `taskFn` to rerun. +Cleanup callbacks registered with `cleanup()` or returned from the task may be async. When a task reruns, Qwik waits for the previous cleanup to finish before starting the next invocation. + +During SSR, the cleanup function is called immediately after SSR completes. Therefore, it is not called on the client side after resuming, but only the second time the task runs on the client. + ```typescript useTask$: (fn: TaskFn, opts?: TaskOptions) => void ``` @@ -10529,6 +10533,10 @@ const Timer = component$(() => { }); ``` +Visible Tasks are a variant of Tasks that only run in the browser, and are registered but not executed during SSR. They are useful for running code that should only execute in the browser, such as code that interacts with the DOM or browser APIs. + +Cleanup callbacks registered with `cleanup()` or returned from the task may be async. When a visible task reruns, Qwik waits for the previous cleanup to finish before starting the next invocation. + ```typescript useVisibleTask$: (fn: TaskFn, opts?: OnVisibleTaskOptions) => void ``` diff --git a/packages/optimizer/core/README.md b/packages/optimizer/core/README.md index b71f5110123..8608cf1c91e 100644 --- a/packages/optimizer/core/README.md +++ b/packages/optimizer/core/README.md @@ -20,9 +20,9 @@ useTask$(() => { useTaskQrl(qrl(() => import('./myFile_useTask_abc123'), 's_abc123', [state])); // Output (segment module: myFile_useTask_abc123.js) -import { _captures } from '@qwik.dev/core'; +import { _capturesObj } from '@qwik.dev/core'; export const s_abc123 = () => { - const state = _captures[0]; + const state = _capturesObj._[0]; console.log(state.count); }; ``` @@ -32,7 +32,7 @@ export const s_abc123 = () => { A segment is an extracted closure with metadata. Each segment becomes a separate ES module file (in non-inline strategies). The segment module contains: 1. Imports for all externally-referenced identifiers -2. A `_captures` import if the closure captures lexical variables +2. A `_capturesObj` import if the closure captures lexical variables 3. The closure body as a named export ### Captures (Scoped Identifiers) @@ -41,7 +41,7 @@ When a `$`-closure references variables from its enclosing lexical scope (not im 1. Identifies captured variables by walking the closure body and checking each identifier against the lexical scope stack 2. Passes them as an array argument to `qrl()`: `qrl(import, "name", [var1, var2])` -3. In the segment module, rewrites the function to read captures from `_captures`: `const var1 = _captures[0]` +3. In the segment module, rewrites the function to read captures from `_capturesObj`: `const var1 = _capturesObj._[0]` **Exception — event handlers on native elements:** For `$`-props on native elements (e.g., `onClick$` on `