diff --git a/src/module.ts b/src/module.ts index be7c68d..f464737 100644 --- a/src/module.ts +++ b/src/module.ts @@ -133,19 +133,21 @@ 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;