From 52973b6d28c1f2dc5cb18ed1813a9db9b4f60cd3 Mon Sep 17 00:00:00 2001 From: nicokampf Date: Wed, 8 Jul 2026 10:48:53 +0200 Subject: [PATCH 1/2] fix: resolve CJS/ESM named-export interop for js-sha256 & gql-query-builder under Nuxt 4 / Vite 7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes the client-only build.transpile pushes for js-sha256 and gql-query-builder (the root cause: under Vite 7 / Nuxt 4 they were served raw, breaking named imports and the client bundle → no hydration). Both are now read through dedicated interop helpers (namespace import + default fallback) and pre-bundled via optimizeDeps. - add runtime/helpers/js-sha256-interop.ts and gql-query-builder-interop.ts (kept out of the auto-imported runtime dirs so their generic exports don't leak into the global Nuxt auto-import namespace) - point the 5 runtime import sites at the interop helpers - keep the two deps decoupled so the password-hashing path never pulls gql-query-builder - gate gql-query-builder pre-bundling behind !disableGraphql Co-Authored-By: Claude Opus 4.8 (1M context) --- src/module.ts | 19 ++++++++------ src/runtime/composables/gql-async-query.ts | 2 +- src/runtime/composables/gql-mutation.ts | 2 +- src/runtime/composables/gql-query.ts | 2 +- src/runtime/composables/gql-subscription.ts | 2 +- src/runtime/functions/graphql-meta.ts | 2 +- .../helpers/gql-query-builder-interop.ts | 26 +++++++++++++++++++ src/runtime/helpers/js-sha256-interop.ts | 20 ++++++++++++++ 8 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 src/runtime/helpers/gql-query-builder-interop.ts create mode 100644 src/runtime/helpers/js-sha256-interop.ts diff --git a/src/module.ts b/src/module.ts index be7c68d..eb93280 100644 --- a/src/module.ts +++ b/src/module.ts @@ -133,19 +133,22 @@ export default defineNuxtModule({ }, 5000); } - if (!options.disableGraphql) { - // TODO: Remove when package fixed with valid ESM exports - nuxt.options.build.transpile.push(({ isServer }) => !isServer && 'gql-query-builder'); - } - - nuxt.options.build.transpile.push(({ isServer }) => !isServer && 'js-sha256'); - // TODO: Remove when package fixed with valid ESM exports + // gql-query-builder (CJS) and js-sha256 (UMD) expose named exports that Vite 7 / + // Nuxt 4 cannot statically detect from the raw package output. They must NOT be + // added to build.transpile (which would serve them raw, breaking the named imports + // and the client bundle → no hydration). Instead they are pre-bundled via + // optimizeDeps so esbuild produces a proper interop wrapper, and the runtime reads + // the members through the runtime/helpers/*-interop.ts modules. extendViteConfig((config) => { config.optimizeDeps = config.optimizeDeps || {}; config.optimizeDeps.include = config.optimizeDeps.include || []; config.optimizeDeps.exclude = config.optimizeDeps.exclude || []; - config.optimizeDeps.include.push('gql-query-builder'); + // js-sha256 backs password hashing and is used regardless of GraphQL. config.optimizeDeps.include.push('js-sha256'); + // gql-query-builder is only reached through the GraphQL composables. + if (!options.disableGraphql) { + config.optimizeDeps.include.push('gql-query-builder'); + } }); nuxt.hook('nitro:config', (nitro) => { diff --git a/src/runtime/composables/gql-async-query.ts b/src/runtime/composables/gql-async-query.ts index e83d080..d75a0f9 100644 --- a/src/runtime/composables/gql-async-query.ts +++ b/src/runtime/composables/gql-async-query.ts @@ -1,10 +1,10 @@ -import { query } from 'gql-query-builder'; import gql from 'graphql-tag'; import { type AsyncData, callWithNuxt, useAsyncData, useNuxtApp } from 'nuxt/app'; import type { IGraphQLOptions } from '../interfaces/graphql-options.interface'; import { hashPasswords } from '../functions/graphql-meta'; +import { query } from '../helpers/gql-query-builder-interop'; import { useAuthState } from '../states/auth'; import { useAuth } from './use-auth'; import { useHelper } from './use-helper'; diff --git a/src/runtime/composables/gql-mutation.ts b/src/runtime/composables/gql-mutation.ts index 7be31c4..d9fd47c 100644 --- a/src/runtime/composables/gql-mutation.ts +++ b/src/runtime/composables/gql-mutation.ts @@ -1,4 +1,3 @@ -import { mutation } from 'gql-query-builder'; import gql from 'graphql-tag'; import { callWithNuxt, useNuxtApp } from 'nuxt/app'; @@ -6,6 +5,7 @@ import type { GraphqlError } from '../interfaces/graphql-error.interface'; import type { IGraphQLOptions } from '../interfaces/graphql-options.interface'; import { hashPasswords } from '../functions/graphql-meta'; +import { mutation } from '../helpers/gql-query-builder-interop'; import { useAuthState } from '../states/auth'; import { useAuth } from './use-auth'; import { useRequestOptions } from './use-request-options'; diff --git a/src/runtime/composables/gql-query.ts b/src/runtime/composables/gql-query.ts index 2951dbd..0a61cab 100644 --- a/src/runtime/composables/gql-query.ts +++ b/src/runtime/composables/gql-query.ts @@ -1,4 +1,3 @@ -import { query } from 'gql-query-builder'; import gql from 'graphql-tag'; import { callWithNuxt, useNuxtApp } from 'nuxt/app'; @@ -6,6 +5,7 @@ import type { GraphqlError } from '../interfaces/graphql-error.interface'; import type { IGraphQLOptions } from '../interfaces/graphql-options.interface'; import { hashPasswords } from '../functions/graphql-meta'; +import { query } from '../helpers/gql-query-builder-interop'; import { useAuthState } from '../states/auth'; import { useAuth } from './use-auth'; import { useRequestOptions } from './use-request-options'; diff --git a/src/runtime/composables/gql-subscription.ts b/src/runtime/composables/gql-subscription.ts index 9ddbd95..f3f5840 100644 --- a/src/runtime/composables/gql-subscription.ts +++ b/src/runtime/composables/gql-subscription.ts @@ -1,6 +1,5 @@ import type { Client } from 'graphql-ws'; -import { subscription } from 'gql-query-builder'; import gql from 'graphql-tag'; import { useNuxtApp } from 'nuxt/app'; import { ref } from 'vue'; @@ -9,6 +8,7 @@ import type { IGraphQLOptions } from '../interfaces/graphql-options.interface'; import type { ReturnTypeOfSubscription } from '../interfaces/return-type-of-subscription.interface'; import { hashPasswords } from '../functions/graphql-meta'; +import { subscription } from '../helpers/gql-query-builder-interop'; export async function gqlSubscription(method: string, options: IGraphQLOptions = {}): Promise> { const { _meta, _wsClient } = useNuxtApp(); diff --git a/src/runtime/functions/graphql-meta.ts b/src/runtime/functions/graphql-meta.ts index f8a50cb..6898cc3 100644 --- a/src/runtime/functions/graphql-meta.ts +++ b/src/runtime/functions/graphql-meta.ts @@ -1,10 +1,10 @@ import { buildClientSchema, getIntrospectionQuery } from 'graphql'; -import { sha256 } from 'js-sha256'; import { ofetch } from 'ofetch'; import type { GraphQLMeta } from '../../generate'; import { useGraphQLMeta } from '../composables/use-graphql-meta'; +import { sha256 } from '../helpers/js-sha256-interop'; /** * Hash a string with SHA-256 diff --git a/src/runtime/helpers/gql-query-builder-interop.ts b/src/runtime/helpers/gql-query-builder-interop.ts new file mode 100644 index 0000000..7f23955 --- /dev/null +++ b/src/runtime/helpers/gql-query-builder-interop.ts @@ -0,0 +1,26 @@ +// Interop layer for gql-query-builder (CJS with __esModule, no ESM entry point). +// +// `gql-query-builder` exposes `query`/`mutation`/`subscription` as named exports +// that Vite 7 / Nuxt 4 cannot statically detect from the raw CJS output, so a +// direct `import { query } from 'gql-query-builder'` breaks the client bundle +// (no hydration). Importing the whole namespace and resolving the members at +// runtime works for both CJS shapes: +// - __esModule (exports.query = ...) → member sits directly on the namespace +// - module.exports = {...} → member sits under `.default` +// The package must stay out of `build.transpile` and be pre-bundled via +// optimizeDeps (dev) / handled by the commonjs plugin (build) for the interop +// wrapper to be produced (see module.ts). +// +// This file deliberately lives outside the auto-imported runtime dirs so its +// generic `query`/`mutation`/`subscription` exports are NOT registered as global +// Nuxt auto-imports. It is kept separate from the js-sha256 interop so the +// disableGraphql / password-hashing path never pulls gql-query-builder into the +// module graph. + +import * as gqlQueryBuilder from 'gql-query-builder'; + +const mod = (gqlQueryBuilder as any).query ? (gqlQueryBuilder as any) : ((gqlQueryBuilder as any).default ?? gqlQueryBuilder); + +export const query: typeof import('gql-query-builder').query = mod.query; +export const mutation: typeof import('gql-query-builder').mutation = mod.mutation; +export const subscription: typeof import('gql-query-builder').subscription = mod.subscription; diff --git a/src/runtime/helpers/js-sha256-interop.ts b/src/runtime/helpers/js-sha256-interop.ts new file mode 100644 index 0000000..1884d6a --- /dev/null +++ b/src/runtime/helpers/js-sha256-interop.ts @@ -0,0 +1,20 @@ +// Interop layer for js-sha256 (UMD/CJS, no ESM entry point). +// +// `js-sha256` exposes its members as named exports that Vite 7 / Nuxt 4 cannot +// statically detect from the raw UMD output, so a direct `import { sha256 } from +// 'js-sha256'` breaks the client bundle (no hydration). Importing the whole +// namespace and resolving the member at runtime works for both CJS shapes: +// - __esModule (exports.sha256 = ...) → member sits directly on the namespace +// - module.exports = {...} → member sits under `.default` +// The package must stay out of `build.transpile` and be pre-bundled via +// optimizeDeps (dev) / handled by the commonjs plugin (build) for the interop +// wrapper to be produced (see module.ts). +// +// This file deliberately lives outside the auto-imported runtime dirs so its +// generic `sha256` export is NOT registered as a global Nuxt auto-import. + +import * as jsSha256 from 'js-sha256'; + +const mod = (jsSha256 as any).sha256 ? (jsSha256 as any) : ((jsSha256 as any).default ?? jsSha256); + +export const sha256: typeof import('js-sha256').sha256 = mod.sha256; From 1cb3bb29020730797ad9d3548684175f03f5380b Mon Sep 17 00:00:00 2001 From: nicokampf Date: Wed, 8 Jul 2026 11:13:45 +0200 Subject: [PATCH 2/2] refactor: drop dead optimizeDeps.exclude init in interop vite config The exclude array was initialized but never pushed to. Removed as part of the CJS/ESM interop cleanup (flagged by review). No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/module.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/module.ts b/src/module.ts index eb93280..f464737 100644 --- a/src/module.ts +++ b/src/module.ts @@ -142,7 +142,6 @@ export default defineNuxtModule({ extendViteConfig((config) => { config.optimizeDeps = config.optimizeDeps || {}; config.optimizeDeps.include = config.optimizeDeps.include || []; - config.optimizeDeps.exclude = config.optimizeDeps.exclude || []; // js-sha256 backs password hashing and is used regardless of GraphQL. config.optimizeDeps.include.push('js-sha256'); // gql-query-builder is only reached through the GraphQL composables.