mirror of
https://github.com/unjs/unplugin.git
synced 2025-12-08 20:26:33 +00:00
* 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>
296 lines
10 KiB
TypeScript
296 lines
10 KiB
TypeScript
import type { PluginBuilder } from 'bun'
|
|
import { Buffer } from 'node:buffer'
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import {
|
|
createBuildContext,
|
|
createPluginContext,
|
|
} from '../../../src/bun/utils'
|
|
|
|
vi.mock('node:fs')
|
|
vi.mock('node:path')
|
|
|
|
describe('bun utils', () => {
|
|
afterEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe('createBuildContext', () => {
|
|
it('should create build context with all required methods', () => {
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const context = createBuildContext(mockBuild as PluginBuilder)
|
|
|
|
expect(context.addWatchFile).toBeInstanceOf(Function)
|
|
expect(context.getWatchFiles).toBeInstanceOf(Function)
|
|
expect(context.emitFile).toBeInstanceOf(Function)
|
|
expect(context.parse).toBeInstanceOf(Function)
|
|
expect(context.getNativeBuildContext).toBeInstanceOf(Function)
|
|
})
|
|
|
|
it('should handle addWatchFile and getWatchFiles', () => {
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const context = createBuildContext(mockBuild as PluginBuilder)
|
|
|
|
expect(context.getWatchFiles()).toEqual([])
|
|
|
|
context.addWatchFile('file1.js')
|
|
context.addWatchFile('file2.js')
|
|
|
|
expect(context.getWatchFiles()).toEqual(['file1.js', 'file2.js'])
|
|
})
|
|
|
|
it('should emit file with fileName', () => {
|
|
const mockExistsSync = vi.mocked(fs.existsSync)
|
|
const mockWriteFileSync = vi.mocked(fs.writeFileSync)
|
|
const mockResolve = vi.mocked(path.resolve)
|
|
const mockDirname = vi.mocked(path.dirname)
|
|
|
|
mockExistsSync.mockReturnValue(true)
|
|
mockResolve.mockReturnValue('/path/to/outdir/output.js')
|
|
mockDirname.mockReturnValue('/path/to/outdir')
|
|
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const context = createBuildContext(mockBuild as PluginBuilder)
|
|
|
|
context.emitFile({
|
|
type: 'asset',
|
|
fileName: 'output.js',
|
|
source: 'console.log("hello")',
|
|
} as any)
|
|
|
|
expect(mockResolve).toHaveBeenCalledWith('/path/to/outdir', 'output.js')
|
|
expect(mockDirname).toHaveBeenCalledWith('/path/to/outdir/output.js')
|
|
expect(mockWriteFileSync).toHaveBeenCalledWith(
|
|
'/path/to/outdir/output.js',
|
|
'console.log("hello")',
|
|
)
|
|
})
|
|
|
|
it('should emit file with name when fileName is not provided', () => {
|
|
const mockExistsSync = vi.mocked(fs.existsSync)
|
|
const mockWriteFileSync = vi.mocked(fs.writeFileSync)
|
|
const mockResolve = vi.mocked(path.resolve)
|
|
const mockDirname = vi.mocked(path.dirname)
|
|
|
|
mockExistsSync.mockReturnValue(true)
|
|
mockResolve.mockReturnValue('/path/to/outdir/output.js')
|
|
mockDirname.mockReturnValue('/path/to/outdir')
|
|
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const context = createBuildContext(mockBuild as PluginBuilder)
|
|
|
|
context.emitFile({
|
|
type: 'asset',
|
|
name: 'output.js',
|
|
source: 'console.log("hello")',
|
|
} as any)
|
|
|
|
expect(mockResolve).toHaveBeenCalledWith('/path/to/outdir', 'output.js')
|
|
expect(mockWriteFileSync).toHaveBeenCalledWith(
|
|
'/path/to/outdir/output.js',
|
|
'console.log("hello")',
|
|
)
|
|
})
|
|
|
|
it('should create directory if it does not exist when emitting file', () => {
|
|
const mockExistsSync = vi.mocked(fs.existsSync)
|
|
const mockMkdirSync = vi.mocked(fs.mkdirSync)
|
|
const mockWriteFileSync = vi.mocked(fs.writeFileSync)
|
|
const mockResolve = vi.mocked(path.resolve)
|
|
const mockDirname = vi.mocked(path.dirname)
|
|
|
|
mockExistsSync.mockReturnValue(false)
|
|
mockResolve.mockReturnValue('/path/to/outdir/nested/output.js')
|
|
mockDirname.mockReturnValue('/path/to/outdir/nested')
|
|
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const context = createBuildContext(mockBuild as PluginBuilder)
|
|
|
|
context.emitFile({
|
|
type: 'asset',
|
|
fileName: 'nested/output.js',
|
|
source: 'console.log("hello")',
|
|
} as any)
|
|
|
|
expect(mockMkdirSync).toHaveBeenCalledWith('/path/to/outdir/nested', { recursive: true })
|
|
expect(mockWriteFileSync).toHaveBeenCalledWith(
|
|
'/path/to/outdir/nested/output.js',
|
|
'console.log("hello")',
|
|
)
|
|
})
|
|
|
|
it('should handle Buffer source when emitting file', () => {
|
|
const mockExistsSync = vi.mocked(fs.existsSync)
|
|
const mockWriteFileSync = vi.mocked(fs.writeFileSync)
|
|
const mockResolve = vi.mocked(path.resolve)
|
|
const mockDirname = vi.mocked(path.dirname)
|
|
|
|
mockExistsSync.mockReturnValue(true)
|
|
mockResolve.mockReturnValue('/path/to/outdir/output.bin')
|
|
mockDirname.mockReturnValue('/path/to/outdir')
|
|
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const context = createBuildContext(mockBuild as PluginBuilder)
|
|
const bufferSource = Buffer.from('binary data')
|
|
|
|
context.emitFile({
|
|
type: 'asset',
|
|
fileName: 'output.bin',
|
|
source: bufferSource,
|
|
} as any)
|
|
|
|
expect(mockWriteFileSync).toHaveBeenCalledWith(
|
|
'/path/to/outdir/output.bin',
|
|
bufferSource,
|
|
)
|
|
})
|
|
|
|
it('should not emit file when source is missing', () => {
|
|
const mockWriteFileSync = vi.mocked(fs.writeFileSync)
|
|
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const context = createBuildContext(mockBuild as PluginBuilder)
|
|
|
|
context.emitFile({
|
|
type: 'asset',
|
|
fileName: 'output.js',
|
|
} as any)
|
|
|
|
expect(mockWriteFileSync).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should not emit file when both fileName and name are missing', () => {
|
|
const mockWriteFileSync = vi.mocked(fs.writeFileSync)
|
|
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const context = createBuildContext(mockBuild as PluginBuilder)
|
|
|
|
context.emitFile({
|
|
type: 'asset',
|
|
source: 'console.log("hello")',
|
|
} as any)
|
|
|
|
expect(mockWriteFileSync).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should not emit file when outdir is not configured', () => {
|
|
const mockWriteFileSync = vi.mocked(fs.writeFileSync)
|
|
|
|
const mockBuild = { config: {} }
|
|
const context = createBuildContext(mockBuild as PluginBuilder)
|
|
|
|
context.emitFile({
|
|
type: 'asset',
|
|
fileName: 'output.js',
|
|
source: 'console.log("hello")',
|
|
} as any)
|
|
|
|
expect(mockWriteFileSync).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should parse code with acorn', () => {
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const context = createBuildContext(mockBuild as PluginBuilder)
|
|
|
|
const ast = context.parse('const x = 1')
|
|
expect(ast).toBeDefined()
|
|
expect(ast.type).toBe('Program')
|
|
expect((ast as any).body).toHaveLength(1)
|
|
expect((ast as any).body[0].type).toBe('VariableDeclaration')
|
|
})
|
|
|
|
it('should parse code with custom options', () => {
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const context = createBuildContext(mockBuild as PluginBuilder)
|
|
|
|
const ast = context.parse('const x = 1', {
|
|
sourceType: 'script',
|
|
ecmaVersion: 2015,
|
|
})
|
|
expect(ast).toBeDefined()
|
|
expect(ast.type).toBe('Program')
|
|
})
|
|
|
|
it('should return native build context', () => {
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const context = createBuildContext(mockBuild as PluginBuilder)
|
|
|
|
const nativeContext = context.getNativeBuildContext!()
|
|
expect(nativeContext).toEqual({
|
|
framework: 'bun',
|
|
build: mockBuild,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('createPluginContext', () => {
|
|
it('should create plugin context with error and warn methods', () => {
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const buildContext = createBuildContext(mockBuild as PluginBuilder)
|
|
const pluginContext = createPluginContext(buildContext)
|
|
|
|
expect(pluginContext.errors).toEqual([])
|
|
expect(pluginContext.warnings).toEqual([])
|
|
expect(pluginContext.mixedContext).toBeDefined()
|
|
expect(pluginContext.mixedContext.error).toBeInstanceOf(Function)
|
|
expect(pluginContext.mixedContext.warn).toBeInstanceOf(Function)
|
|
})
|
|
|
|
it('should collect errors when error is called', () => {
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const buildContext = createBuildContext(mockBuild as PluginBuilder)
|
|
const pluginContext = createPluginContext(buildContext)
|
|
|
|
pluginContext.mixedContext.error('Error message')
|
|
expect(pluginContext.errors).toHaveLength(1)
|
|
expect(pluginContext.errors[0]).toBe('Error message')
|
|
|
|
pluginContext.mixedContext.error('Another error')
|
|
expect(pluginContext.errors).toHaveLength(2)
|
|
expect(pluginContext.errors[1]).toBe('Another error')
|
|
})
|
|
|
|
it('should collect warnings when warn is called', () => {
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const buildContext = createBuildContext(mockBuild as PluginBuilder)
|
|
const pluginContext = createPluginContext(buildContext)
|
|
|
|
pluginContext.mixedContext.warn('Warning message')
|
|
expect(pluginContext.warnings).toHaveLength(1)
|
|
expect(pluginContext.warnings[0]).toBe('Warning message')
|
|
|
|
pluginContext.mixedContext.warn('Another warning')
|
|
expect(pluginContext.warnings).toHaveLength(2)
|
|
expect(pluginContext.warnings[1]).toBe('Another warning')
|
|
})
|
|
|
|
it('should include build context methods in mixed context', () => {
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const buildContext = createBuildContext(mockBuild as PluginBuilder)
|
|
const pluginContext = createPluginContext(buildContext)
|
|
|
|
expect(pluginContext.mixedContext.addWatchFile).toBeInstanceOf(Function)
|
|
expect(pluginContext.mixedContext.getWatchFiles).toBeInstanceOf(Function)
|
|
expect(pluginContext.mixedContext.emitFile).toBeInstanceOf(Function)
|
|
expect(pluginContext.mixedContext.parse).toBeInstanceOf(Function)
|
|
})
|
|
|
|
it('should handle complex error objects', () => {
|
|
const mockBuild = { config: { outdir: '/path/to/outdir' } }
|
|
const buildContext = createBuildContext(mockBuild as PluginBuilder)
|
|
const pluginContext = createPluginContext(buildContext)
|
|
|
|
const errorObj = {
|
|
message: 'Complex error',
|
|
code: 'ERR_001',
|
|
stack: 'Error stack trace',
|
|
}
|
|
|
|
pluginContext.mixedContext.error(errorObj)
|
|
expect(pluginContext.errors).toHaveLength(1)
|
|
expect(pluginContext.errors[0]).toEqual(errorObj)
|
|
})
|
|
})
|
|
})
|