Skip to content

Request: qwik.dev/astro provides an integration for Astro 6/7 that uses Qwik v1.x #291

Description

@ianrose

Overview

Can the integration be updated to work with newer versions of Astro using Qwik v1.x? Below captures the outcome of upgrading to Astro 6 and the qwik.dev/astro v1 integration, which then pulls in Qwik v2-beta. In practice that beta dependency introduces several breaking changes and ships dev-mode output rather than production code.

Result of app at Astro 6, qwik.dev/astro 1, Qwik v2-beta

Qwik v2-beta removes preloader option

  • Configuring the preloader is critical for the app's Lighthouse score
  • Lighthouse score takes a significant hit

Qwik v2-beta breaks Qwik component integration tests

  • Qwik testing library dependency has not been updated for Qwik v2-beta. Have to install Qwik v1 as a dependency for testing. App now has 2 versions of Qwik.

Qwik v2-beta package distribution is not production code. The output is from dev mode. See "Qwik v2-beta Dev-mode output" below. Considerable configuration to Vite is required.

Qwik v2-beta Dev-mode output

Error: Code(Q30): Qwik version 2.0.0-beta.35-dev already imported while importing 2.0.0-beta.35-dev.
This can lead to issues due to duplicated shared structures.
Verify that the Qwik libraries you're using are in "resolve.noExternal[]" and in "optimizeDeps.exclude".

Stack trace points to:

node_modules/dist-dev/tsc-out/packages/qwik/src/core/index.js

Not a Duplicate npm Package

Running npm ls @qwik.dev/core confirms only one copy is installed.

npm has already deduplicated the package. Adding overrides in package.json cannot help because there is nothing to collapse.

The Real Cause: Conditional Exports Resolved Differently by Vite vs Node

The node_modules/dist-dev/tsc-out/... paths in the Q30 stack trace are not real files and not a Vite alias. They are source map annotations embedded in core.mjs.map that point back to the Qwik monorepo's internal dev-build layout. vite-node displays source-mapped paths in stack traces, which made the path appear meaningful, it isn't.

The actual trigger is @qwik.dev/core's conditional exports:

// node_modules/@qwik.dev/core/package.json (abridged)
"exports": {
  ".": {
    "import": {
      "development": "./dist/core.mjs",
      "production": "./dist/core.prod.mjs",
      "default":     "./dist/core.prod.mjs"
    }
  }
}

In a Vitest run, two resolution paths are active simultaneously:

Loader Export condition used File loaded
vite-node (inlined modules) "development" (Vite adds this) dist/core.mjs
Node native (external modules) "default" dist/core.prod.mjs

Both core.mjs and core.prod.mjs contain independent copies of Qwik's module-level singleton. When both files are executed in the same process — one through Vite, one through Node — the singleton is initialized twice, and Q30 fires.

The isDev export in optimizer.mjs (line 974) is a code generator template, not a baked-in constant:

// This function generates the virtual @qwik.dev/core/build module at runtime:
const S = t.buildMode === "development";
return `export const isDev = ${JSON.stringify(S)};`;

It has no bearing on which file is loaded from the conditional exports.

Why package.json Cannot Fix This

package.json overrides operate at the npm installation level. They can force a specific package version to be resolved but cannot influence how Vite and Node resolve conditional exports differently at runtime.

Installing "the exact commit referenced in the source maps" (5421ed4) also cannot help: 2.0.0-beta.30-dev+5421ed4 is already the installed version. The dist-dev/tsc-out/... path in the stack trace is the Qwik monorepo's source map path embedded in core.mjs.map is metadata, not a file that needs to exist on the current machine.

Fix Applied

The fix forces all @qwik.dev/core imports through vite-node rather than Node's native loader. This ensures only one export condition ("development" to core.mjs) is ever used, eliminating the second singleton initialization.

vitest.config.qwik.ts inside the test: block:

server: {
  deps: {
    inline: ['@qwik.dev/core'],  // force vite-node to resolve core.mjs always
  },
},

Supporting guards that prevent other split-load vectors:

resolve: {
  dedupe: ['@qwik.dev/core'],   // ensure a single resolution across all plugins
},
ssr: {
  noExternal: ['@qwik.dev/core'],  // belt-and-suspenders for SSR context
},
optimizeDeps: {
  exclude: ['@qwik.dev/core'],     // keep out of pre-bundle cache
},

astro.config.ts (Vite section) — prevents the build from splitting the bundle:

resolve: {
  dedupe: ['@qwik.dev/core'],
},

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions