unplugin/test/unit-tests/resolve-id/resolve-id.test.ts
Alistair Smith f674d582bc
feat: support bun plugin (#539)
* loose start for bun plugin

* implement bun

* support emitFile()

* fix Bun cases in integration test

* add bun to other test files

* remove bun-types-no-globals from github

* restore bun-types-no-globals from npm @^1.2

* bun does not yet support .onEnd, so for now we shouldn't fake it with brittle workarounds

* add Bun in the documentation

* some missing bun references in docs

* support multiple plugins

* use Bun namespace instead of importing module that won't necessarily exist

* Bun is a cute pink color!

* fix the transform hook

* fix for virtual modules

* tidy up

* setup bun in ci

* revert unplugin require path

* ignore bun in test-out folders

* update tests

* support onEnd

* remove

* implement guessLoader(), bun also now supports onEnd()

* don't eat errors/warnings

* we dont need to outdir for bun in this test

* bun writebundle test

* Update to bun@1.2.22 (supports onEnd and onResolve)

* use onStart()

* define onStart() in mocks

* onStart

* ci: run vitest in Bun so we can run bun's tests

* Bun error message if building outside of Bun

* skip bun specific tests when not running in bun

* refactor

* allow only

* ci: fix typecheck

---------

Co-authored-by: Kevin Deng <sxzz@sxzz.moe>
2025-11-03 18:30:00 +08:00

156 lines
4.5 KiB
TypeScript

import type { UnpluginBuildContext, UnpluginContext, UnpluginOptions, VitePlugin } from 'unplugin'
import type { Mock } from 'vitest'
import * as path from 'node:path'
import { createUnplugin } from 'unplugin'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { onlyBun } from '../../utils'
import { build, toArray } from '../utils'
function createUnpluginWithCallback(resolveIdCallback: UnpluginOptions['resolveId']) {
return createUnplugin(() => ({
name: 'test-plugin',
resolveId: resolveIdCallback,
}))
}
// We extract this check because all bundlers should behave the same
const propsToTest: (keyof (UnpluginContext & UnpluginBuildContext))[] = ['addWatchFile', 'emitFile', 'getWatchFiles', 'parse', 'error', 'warn']
function createResolveIdHook(): Mock {
const mockResolveIdHook = vi.fn(function (this: UnpluginContext & UnpluginBuildContext) {
for (const prop of propsToTest) {
expect(this).toHaveProperty(prop)
expect(this[prop]).toBeInstanceOf(Function)
}
})
return mockResolveIdHook
}
function checkResolveIdHook(resolveIdCallback: Mock): void {
expect.assertions(4 * (1 + propsToTest.length * 2))
expect(resolveIdCallback).toHaveBeenCalledWith(
expect.stringMatching(/(?:\/|\\)entry\.js$/),
undefined,
expect.objectContaining({ isEntry: true }),
)
expect(resolveIdCallback).toHaveBeenCalledWith(
'./proxy-export',
expect.stringMatching(/(?:\/|\\)entry\.js$/),
expect.objectContaining({ isEntry: false }),
)
expect(resolveIdCallback).toHaveBeenCalledWith(
'./default-export',
expect.stringMatching(/(?:\/|\\)proxy-export\.js$/),
expect.objectContaining({ isEntry: false }),
)
expect(resolveIdCallback).toHaveBeenCalledWith(
'./named-export',
expect.stringMatching(/(?:\/|\\)proxy-export\.js$/),
expect.objectContaining({ isEntry: false }),
)
}
describe('resolveId hook', () => {
afterEach(() => {
vi.restoreAllMocks()
})
it('vite', async () => {
const mockResolveIdHook = createResolveIdHook()
const plugin = createUnpluginWithCallback(mockResolveIdHook).vite
// we need to define `enforce` here for the plugin to be run
const plugins = toArray(plugin()).map((plugin): VitePlugin => ({ ...plugin, enforce: 'pre' }))
await build.vite({
clearScreen: false,
plugins: [plugins],
build: {
lib: {
entry: path.resolve(__dirname, 'test-src/entry.js'),
name: 'TestLib',
},
write: false, // don't output anything
},
})
checkResolveIdHook(mockResolveIdHook)
})
it('rollup', async () => {
const mockResolveIdHook = createResolveIdHook()
const plugin = createUnpluginWithCallback(mockResolveIdHook).rollup
await build.rollup({
input: path.resolve(__dirname, 'test-src/entry.js'),
plugins: [plugin()],
})
checkResolveIdHook(mockResolveIdHook)
})
it('webpack', async () => {
const mockResolveIdHook = createResolveIdHook()
const plugin = createUnpluginWithCallback(mockResolveIdHook).webpack
await new Promise((resolve) => {
build.webpack(
{
entry: path.resolve(__dirname, 'test-src/entry.js'),
plugins: [plugin()],
},
resolve,
)
})
checkResolveIdHook(mockResolveIdHook)
})
it('rspack', async () => {
const mockResolveIdHook = createResolveIdHook()
const plugin = createUnpluginWithCallback(mockResolveIdHook).rspack
await new Promise((resolve) => {
build.rspack(
{
entry: path.resolve(__dirname, 'test-src/entry.js'),
plugins: [plugin()],
},
resolve,
)
})
checkResolveIdHook(mockResolveIdHook)
})
it('esbuild', async () => {
const mockResolveIdHook = createResolveIdHook()
const plugin = createUnpluginWithCallback(mockResolveIdHook).esbuild
await build.esbuild({
entryPoints: [path.resolve(__dirname, 'test-src/entry.js')],
plugins: [plugin()],
bundle: true, // actually traverse imports
write: false, // don't pollute console
})
checkResolveIdHook(mockResolveIdHook)
})
onlyBun('bun', async () => {
const mockResolveIdHook = createResolveIdHook()
const plugin = createUnpluginWithCallback(mockResolveIdHook).bun
await build.bun({
entrypoints: [path.resolve(__dirname, 'test-src/entry.js')],
plugins: [plugin()],
outdir: path.resolve(__dirname, 'test-out/bun'), // Bun requires outdir
})
checkResolveIdHook(mockResolveIdHook)
})
})