diff --git a/package.json b/package.json index 6632753..0cb50a6 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "node-fetch-native": "^1.4.0", "pnpm": "^8.7.0", "rimraf": "^5.0.1", + "rollup": "3.28.1", "simple-git-hooks": "^2.9.0", "supertest": "^6.3.3", "typescript": "^5.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90e6bf8..ed23865 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: rimraf: specifier: ^5.0.1 version: 5.0.1 + rollup: + specifier: 3.28.1 + version: 3.28.1 simple-git-hooks: specifier: ^2.9.0 version: 2.9.0 diff --git a/src/helper.ts b/src/helper.ts index 5147523..3831849 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -17,13 +17,18 @@ export type Compression = 'gzip' | 'deflate' | 'br' export type StreamCompression = 'gzip' | 'deflate' /** - * `send` was removed and `toResponse` was added in h3 v2. They are accessed - * dynamically so this package keeps working with both h3 v1 and v2. + * `send` (h3 v1) and `toResponse` (h3 v2) each only exist in a single major + * version. They are read through a runtime key so that bundlers (Nuxt / Nitro / + * Rollup re-bundling `dist/index.mjs`) don't turn the namespace member access + * into a static `import { toResponse } from 'h3'`, which would fail on the other + * version (see issue: Nuxt build error `"toResponse" is not exported by h3`). */ -const send = (h3 as { send?: (event: H3Event, data?: unknown) => unknown }).send -const toResponse = (h3 as { - toResponse?: (val: unknown, event: H3Event) => Response | Promise -}).toResponse +function h3Export(name: string): T | undefined { + return (h3 as Record)[name] as T | undefined +} + +const send = h3Export<(event: H3Event, data?: unknown) => unknown>('send') +const toResponse = h3Export<(val: unknown, event: H3Event) => Response | Promise>('toResponse') /** * Returns the best compression accepted by the client via the diff --git a/test/dist-bundling.test.ts b/test/dist-bundling.test.ts new file mode 100644 index 0000000..6f739d7 --- /dev/null +++ b/test/dist-bundling.test.ts @@ -0,0 +1,67 @@ +import { execSync } from 'node:child_process' +import { existsSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import { dirname, resolve } from 'node:path' +import { beforeAll, describe, expect, it } from 'vitest' + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const distEntry = resolve(root, 'dist/index.mjs') + +/** + * A stub for `h3` that mimics how a single h3 major looks to a bundler: it + * exports the symbols that exist in *both* versions, but never both `send` + * (v1 only) and `toResponse` (v2 only). If the package forces a static named + * import of a version-specific symbol, Rollup re-bundling `dist/index.mjs` + * (as Nuxt / Nitro does) emits a `MISSING_EXPORT` warning — exactly the + * `"toResponse" is not exported by h3` build error reported downstream. + */ +const h3Stub = ` +export const getRequestHeader = () => undefined +export const setResponseHeader = () => undefined +` + +function stubPlugin() { + return { + name: 'h3-stub', + resolveId(id: string) { + if (id === 'h3') + return '\0h3-stub' + if (id.startsWith('node:')) + return { id, external: true } + return null + }, + load(id: string) { + if (id === '\0h3-stub') + return h3Stub + return null + }, + } +} + +describe('downstream bundling (regression for #14 follow-up)', () => { + beforeAll(() => { + if (!existsSync(distEntry)) + execSync('pnpm build', { cwd: root, stdio: 'inherit' }) + }, 120_000) + + it('bundles dist/index.mjs without missing h3 exports (v1 + v2 safe)', async () => { + const { rollup } = await import('rollup') + + const warnings: string[] = [] + const bundle = await rollup({ + input: distEntry, + plugins: [stubPlugin() as any], + onwarn(warning) { + warnings.push(typeof warning === 'string' ? warning : warning.message) + }, + }) + await bundle.generate({ format: 'es' }) + await bundle.close() + + const missingExport = warnings.filter( + w => /is not exported by/.test(w) || /toResponse|"send"/.test(w), + ) + + expect(missingExport).toEqual([]) + }) +})