Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 11 additions & 6 deletions src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Response>
}).toResponse
function h3Export<T>(name: string): T | undefined {
return (h3 as Record<string, unknown>)[name] as T | undefined
}

const send = h3Export<(event: H3Event, data?: unknown) => unknown>('send')
const toResponse = h3Export<(val: unknown, event: H3Event) => Response | Promise<Response>>('toResponse')

/**
* Returns the best compression accepted by the client via the
Expand Down
67 changes: 67 additions & 0 deletions test/dist-bundling.test.ts
Original file line number Diff line number Diff line change
@@ -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([])
})
})
Loading