mirror of
https://github.com/unjs/unplugin.git
synced 2025-12-08 20:26:33 +00:00
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>
This commit is contained in:
parent
ffc1e55446
commit
f674d582bc
33
.github/workflows/ci.yml
vendored
33
.github/workflows/ci.yml
vendored
@ -15,3 +15,36 @@ permissions: {}
|
||||
jobs:
|
||||
unit-test:
|
||||
uses: sxzz/workflows/.github/workflows/unit-test.yml@v1
|
||||
with:
|
||||
typecheck: pnpm run build && pnpm run typecheck
|
||||
|
||||
unit-test-bun:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: pnpm
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Test with bun
|
||||
run: bun run scripts/buildFixtures.ts && bun -b vitest --allowOnly
|
||||
|
||||
@ -15,6 +15,7 @@ Currently supports:
|
||||
- [Rspack](https://www.rspack.dev/)
|
||||
- [Rolldown](https://rolldown.rs/)
|
||||
- [Farm](https://www.farmfe.org/)
|
||||
- [Bun](https://bun.com/)
|
||||
- And every framework built on top of them.
|
||||
|
||||
## Documentations
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export const title = 'Unplugin'
|
||||
export const description = 'Unified plugin system. Support Vite, Rollup, webpack, esbuild, and every frameworks on top of them.'
|
||||
export const description = 'Unified plugin system. Support Vite, Rollup, webpack, esbuild, Bun, and every frameworks on top of them.'
|
||||
export const url = 'https://unplugin.unjs.io/'
|
||||
export const ogImage = `${url}/og.png`
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
Unplugin
|
||||
</h1>
|
||||
<p align="center">
|
||||
Unified plugin system, Support Vite, Rollup, webpack, esbuild, and more
|
||||
Unified plugin system, Support Vite, Rollup, webpack, esbuild, Bun, and more
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
||||
@ -18,6 +18,7 @@ lastUpdated: false
|
||||
- [Rspack](https://www.rspack.dev/)
|
||||
- [Rolldown](https://rolldown.rs/)
|
||||
- [Farm](https://www.farmfe.org/)
|
||||
- [Bun](https://bun.com/)
|
||||
|
||||
## Trying It Online
|
||||
|
||||
@ -153,6 +154,21 @@ export default defineConfig({
|
||||
})
|
||||
```
|
||||
|
||||
```ts [Bun]
|
||||
// bun.config.ts
|
||||
import Starter from 'unplugin-starter/bun'
|
||||
|
||||
await Bun.build({
|
||||
entrypoints: ['./src/index.ts'],
|
||||
outdir: './dist',
|
||||
plugins: [
|
||||
Starter({
|
||||
/* options */
|
||||
}),
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
```js [Vue-CLI]
|
||||
// vue.config.js
|
||||
module.exports = {
|
||||
@ -193,18 +209,18 @@ export default defineConfig({
|
||||
|
||||
## Supported Hooks
|
||||
|
||||
| Hook | Rollup | Vite | webpack | esbuild | Rspack | Farm | Rolldown |
|
||||
| --------------------------------------------------------------------------------- | :-------------: | :--: | :-----: | :-------------: | :-------------: | :--: | :------: |
|
||||
| [`enforce`](https://vite.dev/guide/api-plugin.html#plugin-ordering) | ❌ <sup>1</sup> | ✅ | ✅ | ❌ <sup>1</sup> | ✅ | ✅ | ✅ |
|
||||
| [`buildStart`](https://rollupjs.org/plugin-development/#buildstart) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`resolveId`](https://rollupjs.org/plugin-development/#resolveid) | ✅ | ✅ | ✅ | ✅ | ✅ <sup>5</sup> | ✅ | ✅ |
|
||||
| ~~`loadInclude`~~<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`load`](https://rollupjs.org/plugin-development/#load) | ✅ | ✅ | ✅ | ✅ <sup>3</sup> | ✅ | ✅ | ✅ |
|
||||
| ~~`transformInclude`~~<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`transform`](https://rollupjs.org/plugin-development/#transform) | ✅ | ✅ | ✅ | ✅ <sup>3</sup> | ✅ | ✅ | ✅ |
|
||||
| [`watchChange`](https://rollupjs.org/plugin-development/#watchchange) | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
|
||||
| [`buildEnd`](https://rollupjs.org/plugin-development/#buildend) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`writeBundle`](https://rollupjs.org/plugin-development/#writebundle)<sup>4</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Hook | Rollup | Vite | webpack | esbuild | Rspack | Farm | Rolldown | Bun |
|
||||
| --------------------------------------------------------------------------------- | :-------------: | :--: | :-----: | :-------------: | :-------------: | :--: | :------: | :-------------: |
|
||||
| [`enforce`](https://vite.dev/guide/api-plugin.html#plugin-ordering) | ❌ <sup>1</sup> | ✅ | ✅ | ❌ <sup>1</sup> | ✅ | ✅ | ✅ | ❌ |
|
||||
| [`buildStart`](https://rollupjs.org/plugin-development/#buildstart) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`resolveId`](https://rollupjs.org/plugin-development/#resolveid) | ✅ | ✅ | ✅ | ✅ | ✅ <sup>5</sup> | ✅ | ✅ | ✅ |
|
||||
| ~~`loadInclude`~~<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`load`](https://rollupjs.org/plugin-development/#load) | ✅ | ✅ | ✅ | ✅ <sup>3</sup> | ✅ | ✅ | ✅ | ✅ |
|
||||
| ~~`transformInclude`~~<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`transform`](https://rollupjs.org/plugin-development/#transform) | ✅ | ✅ | ✅ | ✅ <sup>3</sup> | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`watchChange`](https://rollupjs.org/plugin-development/#watchchange) | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ |
|
||||
| [`buildEnd`](https://rollupjs.org/plugin-development/#buildend) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ <sup>6</sup> |
|
||||
| [`writeBundle`](https://rollupjs.org/plugin-development/#writebundle)<sup>4</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ <sup>6</sup> |
|
||||
|
||||
::: details Notice
|
||||
|
||||
@ -215,6 +231,7 @@ export default defineConfig({
|
||||
3. Although esbuild can handle both JavaScript and CSS and many other file formats, you can only return JavaScript in `load` and `transform` results.
|
||||
4. Currently, `writeBundle` is only serves as a hook for the timing. It doesn't pass any arguments.
|
||||
5. Rspack supports `resolveId` with a minimum required version of v1.0.0-alpha.1.
|
||||
6. Bun's plugin API doesn't have an `onEnd` hook yet, so `buildEnd` and `writeBundle` are not supported.
|
||||
|
||||
:::
|
||||
|
||||
@ -253,6 +270,7 @@ export const webpackPlugin = unplugin.webpack
|
||||
export const rspackPlugin = unplugin.rspack
|
||||
export const esbuildPlugin = unplugin.esbuild
|
||||
export const farmPlugin = unplugin.farm
|
||||
export const bunPlugin = unplugin.bun
|
||||
```
|
||||
|
||||
### Filters
|
||||
@ -289,14 +307,14 @@ More details can be found in the [Rolldown's documentation](https://rolldown.rs/
|
||||
|
||||
## Supported Context
|
||||
|
||||
| Context | Rollup | Vite | webpack | esbuild | Rspack | Farm | Rolldown |
|
||||
| ------------------------------------------------------------------------------------- | :----: | :--: | :-----: | :-----: | :----: | :--: | :------: |
|
||||
| [`this.parse`](https://rollupjs.org/plugin-development/#this-parse) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`this.addWatchFile`](https://rollupjs.org/plugin-development/#this-addwatchfile) | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
|
||||
| [`this.emitFile`](https://rollupjs.org/plugin-development/#this-emitfile)<sup>1</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`this.getWatchFiles`](https://rollupjs.org/plugin-development/#this-getwatchfiles) | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
|
||||
| [`this.warn`](https://rollupjs.org/plugin-development/#this-warn) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`this.error`](https://rollupjs.org/plugin-development/#this-error) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Context | Rollup | Vite | webpack | esbuild | Rspack | Farm | Rolldown | Bun |
|
||||
| ------------------------------------------------------------------------------------- | :----: | :--: | :-----: | :-----: | :----: | :--: | :------: | :-: |
|
||||
| [`this.parse`](https://rollupjs.org/plugin-development/#this-parse) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`this.addWatchFile`](https://rollupjs.org/plugin-development/#this-addwatchfile) | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`this.emitFile`](https://rollupjs.org/plugin-development/#this-emitfile)<sup>1</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`this.getWatchFiles`](https://rollupjs.org/plugin-development/#this-getwatchfiles) | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`this.warn`](https://rollupjs.org/plugin-development/#this-warn) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`this.error`](https://rollupjs.org/plugin-development/#this-error) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
::: info Notice
|
||||
|
||||
@ -309,9 +327,9 @@ More details can be found in the [Rolldown's documentation](https://rolldown.rs/
|
||||
|
||||
### Bundler Supported
|
||||
|
||||
| Rollup | Vite | webpack | Rspack | esbuild | Farm | Rolldown |
|
||||
| :--------------------: | :--: | :-----: | :----: | :-----: | :--: | :------: |
|
||||
| ✅ `>=3.1`<sup>1</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Rollup | Vite | webpack | Rspack | esbuild | Farm | Rolldown | Bun |
|
||||
| :--------------------: | :--: | :-----: | :----: | :-----: | :--: | :------: | :-: |
|
||||
| ✅ `>=3.1`<sup>1</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
::: details Notice
|
||||
|
||||
@ -409,6 +427,9 @@ export const unpluginFactory: UnpluginFactory<Options | undefined> = (
|
||||
farm: {
|
||||
// Farm plugin
|
||||
},
|
||||
bun: {
|
||||
// Bun plugin
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -424,6 +445,7 @@ Each of the function takes the same generic factory argument as `createUnplugin`
|
||||
|
||||
```ts
|
||||
import {
|
||||
createBunPlugin,
|
||||
createEsbuildPlugin,
|
||||
createFarmPlugin,
|
||||
createRolldownPlugin,
|
||||
@ -440,4 +462,5 @@ const esbuildPlugin = createEsbuildPlugin(/* factory */)
|
||||
const webpackPlugin = createWebpackPlugin(/* factory */)
|
||||
const rspackPlugin = createRspackPlugin(/* factory */)
|
||||
const farmPlugin = createFarmPlugin(/* factory */)
|
||||
const bunPlugin = createBunPlugin(/* factory */)
|
||||
```
|
||||
|
||||
@ -5,7 +5,7 @@ sidebar: false
|
||||
hero:
|
||||
name: Unplugin
|
||||
text: The Unified<br>Plugin System
|
||||
tagline: Supports Vite, Rollup, webpack, esbuild, and every framework built on top of them.
|
||||
tagline: Supports Vite, Rollup, webpack, esbuild, Bun, and every framework built on top of them.
|
||||
image:
|
||||
light: /logo_light.svg
|
||||
dark: /logo_dark.svg
|
||||
@ -64,6 +64,12 @@ features:
|
||||
icon:
|
||||
src: /features/rolldown.svg
|
||||
|
||||
- title: Bun
|
||||
details: All-in-one JavaScript runtime & toolkit
|
||||
link: https://bun.com/
|
||||
icon:
|
||||
src: /features/bun.svg
|
||||
|
||||
- title: More
|
||||
details: More supported bundlers...
|
||||
link: /guide/#supported-hooks
|
||||
|
||||
1
docs/public/features/bun.svg
Normal file
1
docs/public/features/bun.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="Bun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 70"><title>Bun Logo</title><path id="Shadow" d="M71.09,20.74c-.16-.17-.33-.34-.5-.5s-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5A26.46,26.46,0,0,1,75.5,35.7c0,16.57-16.82,30.05-37.5,30.05-11.58,0-21.94-4.23-28.83-10.86l.5.5.5.5.5.5.5.5.5.5.5.5.5.5C19.55,65.3,30.14,69.75,42,69.75c20.68,0,37.5-13.48,37.5-30C79.5,32.69,76.46,26,71.09,20.74Z"/><g id="Body"><path id="Background" d="M73,35.7c0,15.21-15.67,27.54-35,27.54S3,50.91,3,35.7C3,26.27,9,17.94,18.22,13S33.18,3,38,3s8.94,4.13,19.78,10C67,17.94,73,26.27,73,35.7Z" style="fill:#fbf0df"/><path id="Bottom_Shadow" data-name="Bottom Shadow" d="M73,35.7a21.67,21.67,0,0,0-.8-5.78c-2.73,33.3-43.35,34.9-59.32,24.94A40,40,0,0,0,38,63.24C57.3,63.24,73,50.89,73,35.7Z" style="fill:#f6dece"/><path id="Light_Shine" data-name="Light Shine" d="M24.53,11.17C29,8.49,34.94,3.46,40.78,3.45A9.29,9.29,0,0,0,38,3c-2.42,0-5,1.25-8.25,3.13-1.13.66-2.3,1.39-3.54,2.15-2.33,1.44-5,3.07-8,4.7C8.69,18.13,3,26.62,3,35.7c0,.4,0,.8,0,1.19C9.06,15.48,20.07,13.85,24.53,11.17Z" style="fill:#fffefc"/><path id="Top" d="M35.12,5.53A16.41,16.41,0,0,1,29.49,18c-.28.25-.06.73.3.59,3.37-1.31,7.92-5.23,6-13.14C35.71,5,35.12,5.12,35.12,5.53Zm2.27,0A16.24,16.24,0,0,1,39,19c-.12.35.31.65.55.36C41.74,16.56,43.65,11,37.93,5,37.64,4.74,37.19,5.14,37.39,5.49Zm2.76-.17A16.42,16.42,0,0,1,47,17.12a.33.33,0,0,0,.65.11c.92-3.49.4-9.44-7.17-12.53C40.08,4.54,39.82,5.08,40.15,5.32ZM21.69,15.76a16.94,16.94,0,0,0,10.47-9c.18-.36.75-.22.66.18-1.73,8-7.52,9.67-11.12,9.45C21.32,16.4,21.33,15.87,21.69,15.76Z" style="fill:#ccbea7;fill-rule:evenodd"/><path id="Outline" d="M38,65.75C17.32,65.75.5,52.27.5,35.7c0-10,6.18-19.33,16.53-24.92,3-1.6,5.57-3.21,7.86-4.62,1.26-.78,2.45-1.51,3.6-2.19C32,1.89,35,.5,38,.5s5.62,1.2,8.9,3.14c1,.57,2,1.19,3.07,1.87,2.49,1.54,5.3,3.28,9,5.27C69.32,16.37,75.5,25.69,75.5,35.7,75.5,52.27,58.68,65.75,38,65.75ZM38,3c-2.42,0-5,1.25-8.25,3.13-1.13.66-2.3,1.39-3.54,2.15-2.33,1.44-5,3.07-8,4.7C8.69,18.13,3,26.62,3,35.7,3,50.89,18.7,63.25,38,63.25S73,50.89,73,35.7C73,26.62,67.31,18.13,57.78,13,54,11,51.05,9.12,48.66,7.64c-1.09-.67-2.09-1.29-3-1.84C42.63,4,40.42,3,38,3Z"/></g><g id="Mouth"><g id="Background-2" data-name="Background"><path d="M45.05,43a8.93,8.93,0,0,1-2.92,4.71,6.81,6.81,0,0,1-4,1.88A6.84,6.84,0,0,1,34,47.71,8.93,8.93,0,0,1,31.12,43a.72.72,0,0,1,.8-.81H44.26A.72.72,0,0,1,45.05,43Z" style="fill:#b71422"/></g><g id="Tongue"><path id="Background-3" data-name="Background" d="M34,47.79a6.91,6.91,0,0,0,4.12,1.9,6.91,6.91,0,0,0,4.11-1.9,10.63,10.63,0,0,0,1-1.07,6.83,6.83,0,0,0-4.9-2.31,6.15,6.15,0,0,0-5,2.78C33.56,47.4,33.76,47.6,34,47.79Z" style="fill:#ff6164"/><path id="Outline-2" data-name="Outline" d="M34.16,47a5.36,5.36,0,0,1,4.19-2.08,6,6,0,0,1,4,1.69c.23-.25.45-.51.66-.77a7,7,0,0,0-4.71-1.93,6.36,6.36,0,0,0-4.89,2.36A9.53,9.53,0,0,0,34.16,47Z"/></g><path id="Outline-3" data-name="Outline" d="M38.09,50.19a7.42,7.42,0,0,1-4.45-2,9.52,9.52,0,0,1-3.11-5.05,1.2,1.2,0,0,1,.26-1,1.41,1.41,0,0,1,1.13-.51H44.26a1.44,1.44,0,0,1,1.13.51,1.19,1.19,0,0,1,.25,1h0a9.52,9.52,0,0,1-3.11,5.05A7.42,7.42,0,0,1,38.09,50.19Zm-6.17-7.4c-.16,0-.2.07-.21.09a8.29,8.29,0,0,0,2.73,4.37A6.23,6.23,0,0,0,38.09,49a6.28,6.28,0,0,0,3.65-1.73,8.3,8.3,0,0,0,2.72-4.37.21.21,0,0,0-.2-.09Z"/></g><g id="Face"><ellipse id="Right_Blush" data-name="Right Blush" cx="53.22" cy="40.18" rx="5.85" ry="3.44" style="fill:#febbd0"/><ellipse id="Left_Bluch" data-name="Left Bluch" cx="22.95" cy="40.18" rx="5.85" ry="3.44" style="fill:#febbd0"/><path id="Eyes" d="M25.7,38.8a5.51,5.51,0,1,0-5.5-5.51A5.51,5.51,0,0,0,25.7,38.8Zm24.77,0A5.51,5.51,0,1,0,45,33.29,5.5,5.5,0,0,0,50.47,38.8Z" style="fill-rule:evenodd"/><path id="Iris" d="M24,33.64a2.07,2.07,0,1,0-2.06-2.07A2.07,2.07,0,0,0,24,33.64Zm24.77,0a2.07,2.07,0,1,0-2.06-2.07A2.07,2.07,0,0,0,48.75,33.64Z" style="fill:#fff;fill-rule:evenodd"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@ -59,6 +59,7 @@
|
||||
"@types/picomatch": "catalog:",
|
||||
"ansis": "catalog:",
|
||||
"bumpp": "catalog:",
|
||||
"bun-types-no-globals": "catalog:",
|
||||
"esbuild": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"eslint-plugin-format": "catalog:",
|
||||
|
||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@ -24,6 +24,9 @@ catalogs:
|
||||
bumpp:
|
||||
specifier: ^10.3.1
|
||||
version: 10.3.1
|
||||
bun-types-no-globals:
|
||||
specifier: ^1.2.22
|
||||
version: 1.3.1
|
||||
eslint:
|
||||
specifier: ^9.39.0
|
||||
version: 9.39.0
|
||||
@ -196,6 +199,9 @@ importers:
|
||||
bumpp:
|
||||
specifier: 'catalog:'
|
||||
version: 10.3.1
|
||||
bun-types-no-globals:
|
||||
specifier: 'catalog:'
|
||||
version: 1.3.1
|
||||
esbuild:
|
||||
specifier: ^0.25.12
|
||||
version: 0.25.12
|
||||
@ -2107,6 +2113,9 @@ packages:
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
bun-types-no-globals@1.3.1:
|
||||
resolution: {integrity: sha512-nVhf54PRc8MzKvMK0IXy6TlPGy8Qtk/BY/kk7IUuAxDROYWGRhD+9WF1H0VvzIeB3/AMnuV3az9K7h/4GfSWCw==}
|
||||
|
||||
bundle-name@3.0.0:
|
||||
resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==}
|
||||
engines: {node: '>=12'}
|
||||
@ -5319,14 +5328,14 @@ snapshots:
|
||||
'@babel/template@7.27.2':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
'@babel/parser': 7.27.7
|
||||
'@babel/parser': 7.28.5
|
||||
'@babel/types': 7.28.5
|
||||
|
||||
'@babel/traverse@7.27.7':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
'@babel/generator': 7.28.5
|
||||
'@babel/parser': 7.27.7
|
||||
'@babel/parser': 7.28.5
|
||||
'@babel/template': 7.27.2
|
||||
'@babel/types': 7.28.5
|
||||
debug: 4.4.3
|
||||
@ -7188,6 +7197,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- magicast
|
||||
|
||||
bun-types-no-globals@1.3.1: {}
|
||||
|
||||
bundle-name@3.0.0:
|
||||
dependencies:
|
||||
run-applescript: 5.0.0
|
||||
|
||||
@ -11,6 +11,7 @@ catalog:
|
||||
'@types/picomatch': ^4.0.2
|
||||
ansis: ^4.2.0
|
||||
bumpp: ^10.3.1
|
||||
bun-types-no-globals: ^1.2.22
|
||||
esbuild: ^0.25.12
|
||||
eslint: ^9.39.0
|
||||
eslint-plugin-format: ^1.0.2
|
||||
|
||||
@ -5,6 +5,8 @@ import { join, resolve } from 'node:path'
|
||||
import process from 'node:process'
|
||||
import c from 'ansis'
|
||||
|
||||
const isBun = !!process.versions.bun
|
||||
|
||||
const dir = resolve(import.meta.dirname, '../test/fixtures')
|
||||
let fixtures = await readdir(dir)
|
||||
|
||||
@ -16,6 +18,13 @@ for (const name of fixtures) {
|
||||
if (existsSync(join(path, 'dist')))
|
||||
await rm(join(path, 'dist')).catch(() => {})
|
||||
|
||||
if (isBun) {
|
||||
console.log(c.magentaBright.inverse.bold`\n Bun `, name, '\n')
|
||||
execSync('bun --version', { cwd: path, stdio: 'inherit' })
|
||||
execSync('bun bun.config.js', { cwd: path, stdio: 'inherit' })
|
||||
continue // skip other builders in bun environment
|
||||
}
|
||||
|
||||
console.log(c.yellow.inverse.bold`\n Vite `, name, '\n')
|
||||
execSync('npx vite --version', { cwd: path, stdio: 'inherit' })
|
||||
execSync('npx vite build', { cwd: path, stdio: 'inherit' })
|
||||
|
||||
238
src/bun/index.ts
Normal file
238
src/bun/index.ts
Normal file
@ -0,0 +1,238 @@
|
||||
import type { BunPlugin, Loader } from 'bun'
|
||||
import type { TransformResult, UnpluginContextMeta, UnpluginFactory, UnpluginInstance } from '../types'
|
||||
import { isAbsolute } from 'node:path'
|
||||
import { normalizeObjectHook } from '../utils/filter'
|
||||
import { toArray } from '../utils/general'
|
||||
import { createBuildContext, createPluginContext, guessLoader } from './utils'
|
||||
|
||||
export function getBunPlugin<UserOptions = Record<string, never>>(
|
||||
factory: UnpluginFactory<UserOptions>,
|
||||
): UnpluginInstance<UserOptions>['bun'] {
|
||||
return (userOptions?: UserOptions): BunPlugin => {
|
||||
if (typeof Bun === 'undefined') {
|
||||
throw new ReferenceError('Bun is not supported in this environment')
|
||||
}
|
||||
|
||||
if (!Bun.semver.satisfies(Bun.version, '>=1.2.22')) {
|
||||
throw new Error('Bun 1.2.22 or higher is required, please upgrade Bun')
|
||||
}
|
||||
|
||||
const meta: UnpluginContextMeta = {
|
||||
framework: 'bun',
|
||||
}
|
||||
|
||||
const plugins = toArray(factory(userOptions!, meta))
|
||||
|
||||
return {
|
||||
name: (plugins.length === 1 ? plugins[0].name : meta.bunHostName)
|
||||
?? `unplugin-host:${plugins.map(p => p.name).join(':')}`,
|
||||
|
||||
async setup(build) {
|
||||
const context = createBuildContext(build)
|
||||
|
||||
if (plugins.some(plugin => plugin.buildStart)) {
|
||||
build.onStart(async () => {
|
||||
for (const plugin of plugins) {
|
||||
if (plugin.buildStart) {
|
||||
await plugin.buildStart.call(context)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const resolveIdHooks = plugins
|
||||
.filter(plugin => plugin.resolveId)
|
||||
.map(plugin => ({
|
||||
plugin,
|
||||
...normalizeObjectHook('resolveId', plugin.resolveId!),
|
||||
}))
|
||||
|
||||
const loadHooks = plugins
|
||||
.filter(plugin => plugin.load)
|
||||
.map(plugin => ({
|
||||
plugin,
|
||||
...normalizeObjectHook('load', plugin.load!),
|
||||
}))
|
||||
|
||||
const transformHooks = plugins
|
||||
.filter(plugin => plugin.transform || plugin.transformInclude)
|
||||
.map(plugin => ({
|
||||
plugin,
|
||||
...normalizeObjectHook('transform', plugin.transform!),
|
||||
}))
|
||||
|
||||
const virtualModulePlugins = new Set<string>()
|
||||
for (const plugin of plugins) {
|
||||
if (plugin.resolveId && plugin.load) {
|
||||
virtualModulePlugins.add(plugin.name)
|
||||
}
|
||||
}
|
||||
|
||||
if (resolveIdHooks.length) {
|
||||
build.onResolve({ filter: /.*/ }, async (args) => {
|
||||
if (build.config?.external?.includes(args.path)) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const { plugin, handler, filter } of resolveIdHooks) {
|
||||
if (!filter(args.path))
|
||||
continue
|
||||
|
||||
const { mixedContext, errors, warnings } = createPluginContext(context)
|
||||
const isEntry = args.kind === 'entry-point-run' || args.kind === 'entry-point-build'
|
||||
|
||||
const result = await handler.call(
|
||||
mixedContext,
|
||||
args.path,
|
||||
isEntry ? undefined : args.importer,
|
||||
{ isEntry },
|
||||
)
|
||||
|
||||
for (const warning of warnings) {
|
||||
console.warn('[unplugin]', typeof warning === 'string' ? warning : warning.message)
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
const errorMessage = errors.map(e => typeof e === 'string' ? e : e.message).join('\n')
|
||||
throw new Error(`[unplugin] ${plugin.name}: ${errorMessage}`)
|
||||
}
|
||||
|
||||
if (typeof result === 'string') {
|
||||
if (!isAbsolute(result)) {
|
||||
return {
|
||||
path: result,
|
||||
namespace: plugin.name,
|
||||
}
|
||||
}
|
||||
return { path: result }
|
||||
}
|
||||
else if (typeof result === 'object' && result !== null) {
|
||||
if (!isAbsolute(result.id)) {
|
||||
return {
|
||||
path: result.id,
|
||||
external: result.external,
|
||||
namespace: plugin.name,
|
||||
}
|
||||
}
|
||||
return {
|
||||
path: result.id,
|
||||
external: result.external,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function processLoadTransform(
|
||||
id: string,
|
||||
namespace: string,
|
||||
loader?: Loader,
|
||||
): Promise<{ contents: string, loader: Loader } | undefined> {
|
||||
let code: string | undefined
|
||||
let hasResult = false
|
||||
|
||||
const namespaceLoadHooks = namespace === 'file'
|
||||
? loadHooks
|
||||
: loadHooks.filter(h => h.plugin.name === namespace)
|
||||
|
||||
for (const { plugin, handler, filter } of namespaceLoadHooks) {
|
||||
if (plugin.loadInclude && !plugin.loadInclude(id))
|
||||
continue
|
||||
if (!filter(id))
|
||||
continue
|
||||
|
||||
const { mixedContext, errors, warnings } = createPluginContext(context)
|
||||
const result = await handler.call(mixedContext, id)
|
||||
|
||||
for (const warning of warnings) {
|
||||
console.warn('[unplugin]', typeof warning === 'string' ? warning : warning.message)
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
const errorMessage = errors.map(e => typeof e === 'string' ? e : e.message).join('\n')
|
||||
throw new Error(`[unplugin] ${plugin.name}: ${errorMessage}`)
|
||||
}
|
||||
|
||||
if (typeof result === 'string') {
|
||||
code = result
|
||||
hasResult = true
|
||||
break
|
||||
}
|
||||
else if (typeof result === 'object' && result !== null) {
|
||||
code = result.code
|
||||
hasResult = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasResult && namespace === 'file' && transformHooks.length > 0) {
|
||||
code = await Bun.file(id).text()
|
||||
}
|
||||
|
||||
if (code !== undefined) {
|
||||
const namespaceTransformHooks = namespace === 'file'
|
||||
? transformHooks
|
||||
: transformHooks.filter(h => h.plugin.name === namespace)
|
||||
|
||||
for (const { plugin, handler, filter } of namespaceTransformHooks) {
|
||||
if (plugin.transformInclude && !plugin.transformInclude(id))
|
||||
continue
|
||||
if (!filter(id, code))
|
||||
continue
|
||||
|
||||
const { mixedContext, errors, warnings } = createPluginContext(context)
|
||||
const result: TransformResult = await handler.call(mixedContext, code, id)
|
||||
|
||||
for (const warning of warnings) {
|
||||
console.warn('[unplugin]', typeof warning === 'string' ? warning : warning.message)
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
const errorMessage = errors.map(e => typeof e === 'string' ? e : e.message).join('\n')
|
||||
throw new Error(`[unplugin] ${plugin.name}: ${errorMessage}`)
|
||||
}
|
||||
|
||||
if (typeof result === 'string') {
|
||||
code = result
|
||||
hasResult = true
|
||||
}
|
||||
else if (typeof result === 'object' && result !== null) {
|
||||
code = result.code
|
||||
hasResult = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasResult && code !== undefined) {
|
||||
return {
|
||||
contents: code,
|
||||
loader: loader ?? guessLoader(id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (loadHooks.length || transformHooks.length) {
|
||||
build.onLoad({ filter: /.*/, namespace: 'file' }, async (args) => {
|
||||
return processLoadTransform(args.path, 'file', args.loader)
|
||||
})
|
||||
}
|
||||
|
||||
for (const pluginName of virtualModulePlugins) {
|
||||
build.onLoad({ filter: /.*/, namespace: pluginName }, async (args) => {
|
||||
return processLoadTransform(args.path, pluginName, args.loader)
|
||||
})
|
||||
}
|
||||
|
||||
if (plugins.some(plugin => plugin.buildEnd || plugin.writeBundle)) {
|
||||
build.onEnd(async () => {
|
||||
for (const plugin of plugins) {
|
||||
if (plugin.buildEnd) {
|
||||
await plugin.buildEnd.call(context)
|
||||
}
|
||||
if (plugin.writeBundle) {
|
||||
await plugin.writeBundle()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/bun/utils.ts
Normal file
89
src/bun/utils.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import type { Loader, PluginBuilder } from 'bun'
|
||||
import type { UnpluginBuildContext, UnpluginContext, UnpluginMessage } from '../types'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import * as acorn from 'acorn'
|
||||
|
||||
const ExtToLoader: Record<string, Loader> = {
|
||||
'.js': 'js',
|
||||
'.mjs': 'js',
|
||||
'.cjs': 'js',
|
||||
'.jsx': 'jsx',
|
||||
'.ts': 'ts',
|
||||
'.cts': 'ts',
|
||||
'.mts': 'ts',
|
||||
'.tsx': 'tsx',
|
||||
'.css': 'css',
|
||||
'.less': 'css',
|
||||
'.stylus': 'css',
|
||||
'.scss': 'css',
|
||||
'.sass': 'css',
|
||||
'.json': 'json',
|
||||
'.txt': 'text',
|
||||
}
|
||||
|
||||
export function guessLoader(id: string): Loader {
|
||||
return ExtToLoader[path.extname(id).toLowerCase()] || 'js'
|
||||
}
|
||||
|
||||
export function createBuildContext(build: PluginBuilder): UnpluginBuildContext {
|
||||
const watchFiles: string[] = []
|
||||
|
||||
return {
|
||||
addWatchFile(file) {
|
||||
watchFiles.push(file)
|
||||
},
|
||||
getWatchFiles() {
|
||||
return watchFiles
|
||||
},
|
||||
emitFile(emittedFile) {
|
||||
const outFileName = emittedFile.fileName || emittedFile.name
|
||||
const outdir = build?.config?.outdir
|
||||
if (outdir && emittedFile.source && outFileName) {
|
||||
const outPath = path.resolve(outdir, outFileName)
|
||||
const outDir = path.dirname(outPath)
|
||||
if (!fs.existsSync(outDir))
|
||||
fs.mkdirSync(outDir, { recursive: true })
|
||||
fs.writeFileSync(outPath, emittedFile.source)
|
||||
}
|
||||
},
|
||||
parse(code, opts = {}) {
|
||||
return acorn.parse(code, {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 'latest',
|
||||
locations: true,
|
||||
...opts,
|
||||
})
|
||||
},
|
||||
getNativeBuildContext() {
|
||||
return { framework: 'bun', build }
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function createPluginContext(
|
||||
buildContext: UnpluginBuildContext,
|
||||
): {
|
||||
errors: Array<string | UnpluginMessage>
|
||||
warnings: Array<string | UnpluginMessage>
|
||||
mixedContext: UnpluginBuildContext & UnpluginContext
|
||||
} {
|
||||
const errors: Array<string | UnpluginMessage> = []
|
||||
const warnings: Array<string | UnpluginMessage> = []
|
||||
|
||||
const mixedContext: UnpluginBuildContext & UnpluginContext = {
|
||||
...buildContext,
|
||||
error(error) {
|
||||
errors.push(error)
|
||||
},
|
||||
warn(warning) {
|
||||
warnings.push(warning)
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
errors,
|
||||
warnings,
|
||||
mixedContext,
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import type { UnpluginFactory, UnpluginInstance } from './types'
|
||||
import { getBunPlugin } from './bun'
|
||||
import { getEsbuildPlugin } from './esbuild'
|
||||
import { getFarmPlugin } from './farm'
|
||||
import { getRolldownPlugin } from './rolldown'
|
||||
@ -36,6 +37,9 @@ export function createUnplugin<UserOptions, Nested extends boolean = boolean>(
|
||||
get unloader() {
|
||||
return getUnloaderPlugin(factory)
|
||||
},
|
||||
get bun() {
|
||||
return getBunPlugin(factory)
|
||||
},
|
||||
get raw() {
|
||||
return factory
|
||||
},
|
||||
@ -89,3 +93,9 @@ export function createUnloaderPlugin<UserOptions, Nested extends boolean = boole
|
||||
): UnpluginInstance<UserOptions>['unloader'] {
|
||||
return getUnloaderPlugin(factory)
|
||||
}
|
||||
|
||||
export function createBunPlugin<UserOptions, Nested extends boolean = boolean>(
|
||||
factory: UnpluginFactory<UserOptions, Nested>,
|
||||
): UnpluginInstance<UserOptions>['bun'] {
|
||||
return getBunPlugin(factory)
|
||||
}
|
||||
|
||||
22
src/globals.d.ts
vendored
22
src/globals.d.ts
vendored
@ -1,8 +1,14 @@
|
||||
/**
|
||||
* Flag that is replaced with a boolean during build time.
|
||||
* __DEV__ is false in the final library output, and it is
|
||||
* true when the library is ad-hoc transpiled, ie. during tests.
|
||||
*
|
||||
* See "tsdown.config.ts" and "vitest.config.ts" for more info.
|
||||
*/
|
||||
declare const __DEV__: boolean
|
||||
import * as BunModule from 'bun'
|
||||
|
||||
declare global {
|
||||
export import Bun = BunModule
|
||||
|
||||
/**
|
||||
* Flag that is replaced with a boolean during build time.
|
||||
* __DEV__ is false in the final library output, and it is
|
||||
* true when the library is ad-hoc transpiled, ie. during tests.
|
||||
*
|
||||
* See "tsdown.config.ts" and "vitest.config.ts" for more info.
|
||||
*/
|
||||
declare const __DEV__: boolean
|
||||
}
|
||||
|
||||
12
src/types.ts
12
src/types.ts
@ -1,5 +1,7 @@
|
||||
import type { CompilationContext as FarmCompilationContext, JsPlugin as FarmPlugin } from '@farmfe/core'
|
||||
import type { Compilation as RspackCompilation, Compiler as RspackCompiler, LoaderContext as RspackLoaderContext, RspackPluginInstance } from '@rspack/core'
|
||||
import type { Options as AcornOptions } from 'acorn'
|
||||
import type { BunPlugin, PluginBuilder as BunPluginBuilder } from 'bun'
|
||||
import type { BuildOptions, Plugin as EsbuildPlugin, Loader, PluginBuild } from 'esbuild'
|
||||
import type { Plugin as RolldownPlugin } from 'rolldown'
|
||||
import type { AstNode, EmittedAsset, PluginContextMeta as RollupContextMeta, Plugin as RollupPlugin, SourceMapInput } from 'rollup'
|
||||
@ -9,6 +11,7 @@ import type { Compilation as WebpackCompilation, Compiler as WebpackCompiler, Lo
|
||||
import type VirtualModulesPlugin from 'webpack-virtual-modules'
|
||||
|
||||
export type {
|
||||
BunPlugin,
|
||||
EsbuildPlugin,
|
||||
RolldownPlugin,
|
||||
RollupPlugin,
|
||||
@ -52,12 +55,13 @@ export type NativeBuildContext
|
||||
| { framework: 'esbuild', build: PluginBuild }
|
||||
| { framework: 'rspack', compiler: RspackCompiler, compilation: RspackCompilation, loaderContext?: RspackLoaderContext | undefined }
|
||||
| { framework: 'farm', context: FarmCompilationContext }
|
||||
| { framework: 'bun', build: BunPluginBuilder }
|
||||
|
||||
export interface UnpluginBuildContext {
|
||||
addWatchFile: (id: string) => void
|
||||
emitFile: (emittedFile: EmittedAsset) => void
|
||||
getWatchFiles: () => string[]
|
||||
parse: (input: string, options?: any) => AstNode
|
||||
parse: (input: string, options?: Partial<AcornOptions>) => AstNode
|
||||
getNativeBuildContext?: (() => NativeBuildContext) | undefined
|
||||
}
|
||||
|
||||
@ -142,6 +146,7 @@ export interface UnpluginOptions {
|
||||
config?: ((options: BuildOptions) => void) | undefined
|
||||
} | undefined
|
||||
farm?: Partial<FarmPlugin> | undefined
|
||||
bun?: Partial<BunPlugin> | undefined
|
||||
}
|
||||
|
||||
export interface ResolvedUnpluginOptions extends UnpluginOptions {
|
||||
@ -168,6 +173,7 @@ export interface UnpluginInstance<UserOptions, Nested extends boolean = boolean>
|
||||
esbuild: UnpluginFactoryOutput<UserOptions, EsbuildPlugin>
|
||||
unloader: UnpluginFactoryOutput<UserOptions, Nested extends true ? Array<UnloaderPlugin> : UnloaderPlugin>
|
||||
farm: UnpluginFactoryOutput<UserOptions, FarmPlugin>
|
||||
bun: UnpluginFactoryOutput<UserOptions, BunPlugin>
|
||||
raw: UnpluginFactory<UserOptions, Nested>
|
||||
}
|
||||
|
||||
@ -180,6 +186,10 @@ export type UnpluginContextMeta = Partial<RollupContextMeta> & ({
|
||||
framework: 'esbuild'
|
||||
/** Set the host plugin name of esbuild when returning multiple plugins */
|
||||
esbuildHostName?: string | undefined
|
||||
} | {
|
||||
framework: 'bun'
|
||||
/** Set the host plugin name of bun when returning multiple plugins */
|
||||
bunHostName?: string | undefined
|
||||
} | {
|
||||
framework: 'rspack'
|
||||
rspack: { compiler: RspackCompiler }
|
||||
|
||||
7
test/fixtures/load/__test__/build.test.ts
vendored
7
test/fixtures/load/__test__/build.test.ts
vendored
@ -1,6 +1,7 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { onlyBun } from '../../../utils'
|
||||
|
||||
const r = (...args: string[]) => resolve(__dirname, '../dist', ...args)
|
||||
|
||||
@ -38,4 +39,10 @@ describe('load-called-before-transform', () => {
|
||||
|
||||
expect(content).toContain('it is a msg -> through the load hook -> transform-[Injected Farm]')
|
||||
})
|
||||
|
||||
onlyBun('bun', async () => {
|
||||
const content = await fs.readFile(r('bun/main.js'), 'utf-8')
|
||||
|
||||
expect(content).toContain('it is a msg -> through the load hook -> transform-[Injected Bun]')
|
||||
})
|
||||
})
|
||||
|
||||
8
test/fixtures/load/bun.config.js
vendored
Normal file
8
test/fixtures/load/bun.config.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
const Bun = require('bun')
|
||||
const { bun } = require('./unplugin')
|
||||
|
||||
await Bun.build({
|
||||
entrypoints: ['./src/main.js'],
|
||||
outdir: './dist/bun',
|
||||
plugins: [bun({ msg: 'Bun' })],
|
||||
})
|
||||
10
test/fixtures/transform/__test__/build.test.ts
vendored
10
test/fixtures/transform/__test__/build.test.ts
vendored
@ -1,6 +1,7 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { onlyBun } from '../../../utils'
|
||||
|
||||
const r = (...args: string[]) => resolve(__dirname, '../dist', ...args)
|
||||
|
||||
@ -18,6 +19,7 @@ describe('transform build', () => {
|
||||
|
||||
expect(content).toContain('NON-TARGET: __UNPLUGIN__')
|
||||
expect(content).toContain('TARGET: [Injected Post Rollup]')
|
||||
// Query imports are external in Rollup
|
||||
})
|
||||
|
||||
it('webpack', async () => {
|
||||
@ -51,4 +53,12 @@ describe('transform build', () => {
|
||||
expect(content).toContain('TARGET: [Injected Post Farm]')
|
||||
expect(content).toContain('QUERY: [Injected Post Farm]')
|
||||
})
|
||||
|
||||
onlyBun('bun', async () => {
|
||||
const content = await fs.readFile(r('bun/main.js'), 'utf-8')
|
||||
|
||||
expect(content).toContain('NON-TARGET: __UNPLUGIN__')
|
||||
expect(content).toContain('TARGET: [Injected Post Bun]')
|
||||
// Like Rollup, imports with query params are marked as external in Bun
|
||||
})
|
||||
})
|
||||
|
||||
8
test/fixtures/transform/bun.config.js
vendored
Normal file
8
test/fixtures/transform/bun.config.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
const Bun = require('bun')
|
||||
const { bun } = require('./unplugin')
|
||||
|
||||
await Bun.build({
|
||||
entrypoints: ['./src/main.js'],
|
||||
outdir: './dist/bun',
|
||||
plugins: [bun({ msg: 'Bun' })],
|
||||
})
|
||||
4
test/fixtures/transform/unplugin.js
vendored
4
test/fixtures/transform/unplugin.js
vendored
@ -6,8 +6,8 @@ module.exports = createUnplugin((options, meta) => {
|
||||
{
|
||||
name: 'transform-fixture-pre',
|
||||
resolveId(id) {
|
||||
// Rollup doesn't know how to import module with query string so we ignore the module
|
||||
if (id.includes('?query-param=query-value') && meta.framework === 'rollup') {
|
||||
// Rollup and Bun don't know how to import module with query string so we ignore the module
|
||||
if (id.includes('?query-param=query-value') && (meta.framework === 'rollup' || meta.framework === 'bun')) {
|
||||
return {
|
||||
id,
|
||||
external: true,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { onlyBun } from '../../../utils'
|
||||
|
||||
const r = (...args: string[]) => resolve(__dirname, '../dist', ...args)
|
||||
|
||||
@ -46,4 +47,11 @@ describe('virtual-module build', () => {
|
||||
expect(content).toContain('VIRTUAL:ONE')
|
||||
expect(content).toContain('VIRTUAL:TWO')
|
||||
})
|
||||
|
||||
onlyBun('bun', async () => {
|
||||
const content = await fs.readFile(r('bun/main.js'), 'utf-8')
|
||||
|
||||
expect(content).toContain('VIRTUAL:ONE')
|
||||
expect(content).toContain('VIRTUAL:TWO')
|
||||
})
|
||||
})
|
||||
|
||||
8
test/fixtures/virtual-module/bun.config.js
vendored
Normal file
8
test/fixtures/virtual-module/bun.config.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
const Bun = require('bun')
|
||||
const { bun } = require('./unplugin')
|
||||
|
||||
await Bun.build({
|
||||
entrypoints: ['./src/main.js'],
|
||||
outdir: './dist/bun',
|
||||
plugins: [bun()],
|
||||
})
|
||||
51
test/unit-tests/bun/index.test.ts
Normal file
51
test/unit-tests/bun/index.test.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
describe.skipIf(typeof Bun === 'undefined')('bun plugin', () => {
|
||||
it('should export bun plugin', () => {
|
||||
const unplugin = createUnplugin(() => ({
|
||||
name: 'test-plugin',
|
||||
}))
|
||||
|
||||
expect(unplugin.bun).toBeDefined()
|
||||
expect(typeof unplugin.bun).toBe('function')
|
||||
})
|
||||
|
||||
it('should create bun plugin with correct name', () => {
|
||||
const unplugin = createUnplugin(() => ({
|
||||
name: 'test-plugin',
|
||||
}))
|
||||
|
||||
const bunPlugin = unplugin.bun()
|
||||
expect(bunPlugin.name).toBe('test-plugin')
|
||||
expect(bunPlugin.setup).toBeDefined()
|
||||
expect(typeof bunPlugin.setup).toBe('function')
|
||||
})
|
||||
|
||||
it('should handle options correctly', () => {
|
||||
interface Options {
|
||||
value: string
|
||||
}
|
||||
|
||||
const unplugin = createUnplugin<Options>(options => ({
|
||||
name: 'test-plugin',
|
||||
buildStart() {
|
||||
expect(options.value).toBe('test')
|
||||
},
|
||||
}))
|
||||
|
||||
const bunPlugin = unplugin.bun({ value: 'test' })
|
||||
expect(bunPlugin).toBeDefined()
|
||||
})
|
||||
|
||||
it('should support multiple plugins with host name', () => {
|
||||
const unplugin = createUnplugin(() => [
|
||||
{ name: 'plugin-1' },
|
||||
{ name: 'plugin-2' },
|
||||
])
|
||||
|
||||
const bunPlugin = unplugin.bun()
|
||||
expect(bunPlugin.name).toBe('unplugin-host:plugin-1:plugin-2')
|
||||
expect(bunPlugin.setup).toBeDefined()
|
||||
})
|
||||
})
|
||||
134
test/unit-tests/bun/nested.test.ts
Normal file
134
test/unit-tests/bun/nested.test.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
describe.skipIf(typeof Bun === 'undefined')('bun nested plugin support', () => {
|
||||
it('should call buildStart for all nested plugins', async () => {
|
||||
const buildStart1 = vi.fn()
|
||||
const buildStart2 = vi.fn()
|
||||
|
||||
const unplugin = createUnplugin(() => [
|
||||
{
|
||||
name: 'plugin-1',
|
||||
buildStart: buildStart1,
|
||||
},
|
||||
{
|
||||
name: 'plugin-2',
|
||||
buildStart: buildStart2,
|
||||
},
|
||||
])
|
||||
|
||||
const bunPlugin = unplugin.bun()
|
||||
const mockBuild: Bun.PluginBuilder = {
|
||||
onResolve: vi.fn(),
|
||||
onLoad: vi.fn(),
|
||||
onStart: vi.fn(callback => callback()),
|
||||
config: { outdir: './dist' } as Bun.BuildConfig & { plugins: Bun.BunPlugin[] },
|
||||
} as Partial<Bun.PluginBuilder> as Bun.PluginBuilder
|
||||
|
||||
await bunPlugin.setup(mockBuild)
|
||||
|
||||
expect(buildStart1).toHaveBeenCalledTimes(1)
|
||||
expect(buildStart2).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should handle resolveId from multiple plugins', async () => {
|
||||
const resolveId1 = vi.fn().mockResolvedValue('resolved-1')
|
||||
const resolveId2 = vi.fn().mockResolvedValue('resolved-2')
|
||||
|
||||
const unplugin = createUnplugin(() => [
|
||||
{
|
||||
name: 'plugin-1',
|
||||
resolveId: resolveId1,
|
||||
},
|
||||
{
|
||||
name: 'plugin-2',
|
||||
resolveId: resolveId2,
|
||||
},
|
||||
])
|
||||
|
||||
const bunPlugin = unplugin.bun()
|
||||
const onResolveCallback = vi.fn()
|
||||
const mockBuild = {
|
||||
onResolve: vi.fn((options, callback) => {
|
||||
onResolveCallback.mockImplementation(callback)
|
||||
}),
|
||||
onLoad: vi.fn(),
|
||||
onStart: vi.fn(),
|
||||
config: { outdir: './dist' },
|
||||
} as never as Bun.PluginBuilder
|
||||
|
||||
await bunPlugin.setup(mockBuild)
|
||||
|
||||
expect(mockBuild.onResolve).toHaveBeenCalledWith(
|
||||
{ filter: /.*/ },
|
||||
expect.any(Function),
|
||||
)
|
||||
|
||||
const result = await onResolveCallback({
|
||||
path: 'test.js',
|
||||
importer: 'index.js',
|
||||
kind: 'import-statement',
|
||||
})
|
||||
|
||||
expect(result).toEqual({ path: 'resolved-1', namespace: 'plugin-1' })
|
||||
expect(resolveId1).toHaveBeenCalledWith(
|
||||
'test.js',
|
||||
'index.js',
|
||||
{ isEntry: false },
|
||||
)
|
||||
|
||||
expect(resolveId2).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle transform from multiple plugins', async () => {
|
||||
const transform1 = vi.fn((code: string) => `${code}\n// transformed by plugin-1`)
|
||||
const transform2 = vi.fn((code: string) => `${code}\n// transformed by plugin-2`)
|
||||
|
||||
const unplugin = createUnplugin(() => [
|
||||
{
|
||||
name: 'plugin-1',
|
||||
transform: transform1,
|
||||
},
|
||||
{
|
||||
name: 'plugin-2',
|
||||
transform: transform2,
|
||||
},
|
||||
])
|
||||
|
||||
const bunPlugin = unplugin.bun()
|
||||
let onLoadCallback: Bun.OnLoadCallback
|
||||
const mockBuild = {
|
||||
onResolve: vi.fn(),
|
||||
onLoad: vi.fn((options, callback) => {
|
||||
if (!onLoadCallback) {
|
||||
onLoadCallback = callback
|
||||
}
|
||||
}),
|
||||
onStart: vi.fn(),
|
||||
config: { outdir: './dist' },
|
||||
} as never as Bun.PluginBuilder
|
||||
|
||||
const originalFile = Bun.file
|
||||
|
||||
Bun.file = vi.fn().mockReturnValue({
|
||||
text: vi.fn().mockResolvedValue('original code'),
|
||||
})
|
||||
|
||||
await bunPlugin.setup(mockBuild)
|
||||
|
||||
const result = await onLoadCallback!({
|
||||
path: 'test.js',
|
||||
loader: 'js',
|
||||
} as Bun.OnLoadArgs)
|
||||
|
||||
expect(result).toEqual({
|
||||
contents: 'original code\n// transformed by plugin-1\n// transformed by plugin-2',
|
||||
loader: 'js',
|
||||
})
|
||||
|
||||
expect(transform1).toHaveBeenCalledWith('original code', 'test.js')
|
||||
expect(transform2).toHaveBeenCalledWith('original code\n// transformed by plugin-1', 'test.js')
|
||||
|
||||
Bun.file = originalFile
|
||||
})
|
||||
})
|
||||
295
test/unit-tests/bun/utils.test.ts
Normal file
295
test/unit-tests/bun/utils.test.ts
Normal file
@ -0,0 +1,295 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
1
test/unit-tests/filter/.gitignore
vendored
Normal file
1
test/unit-tests/filter/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
test-out
|
||||
@ -3,6 +3,7 @@ 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 createUnpluginWithHooks(
|
||||
@ -169,4 +170,19 @@ describe('filter', () => {
|
||||
|
||||
check(resolveIdHandler, loadHandler, transformHandler)
|
||||
})
|
||||
|
||||
onlyBun('bun', async () => {
|
||||
const { hook: resolveId, handler: resolveIdHandler } = createIdHook()
|
||||
const { hook: load, handler: loadHandler } = createIdHook()
|
||||
const { hook: transform, handler: transformHandler } = createTransformHook()
|
||||
const plugin = createUnpluginWithHooks(resolveId, load, transform).bun
|
||||
|
||||
await build.bun({
|
||||
entrypoints: [path.resolve(__dirname, 'test-src/entry.js')],
|
||||
plugins: [plugin()],
|
||||
outdir: path.resolve(__dirname, 'test-out/bun'),
|
||||
})
|
||||
|
||||
check(resolveIdHandler, loadHandler, transformHandler)
|
||||
})
|
||||
})
|
||||
|
||||
1
test/unit-tests/id-consistency/.gitignore
vendored
Normal file
1
test/unit-tests/id-consistency/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
test-out
|
||||
@ -3,6 +3,7 @@ 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'
|
||||
|
||||
const entryFilePath = path.resolve(__dirname, './test-src/entry.js')
|
||||
@ -25,7 +26,7 @@ function createUnpluginWithCallback(
|
||||
|
||||
// We extract this check because all bundlers should behave the same
|
||||
function checkHookCalls(
|
||||
name: 'webpack' | 'rollup' | 'vite' | 'rspack' | 'esbuild',
|
||||
name: 'webpack' | 'rollup' | 'vite' | 'rspack' | 'esbuild' | 'bun',
|
||||
resolveIdCallback: Mock,
|
||||
transformIncludeCallback: Mock,
|
||||
transformCallback: Mock,
|
||||
@ -213,4 +214,27 @@ describe('id parameter should be consistent across hooks and plugins', () => {
|
||||
|
||||
checkHookCalls('esbuild', mockResolveIdHook, mockTransformIncludeHook, mockTransformHook, mockLoadHook)
|
||||
})
|
||||
|
||||
onlyBun('bun', async () => {
|
||||
const mockResolveIdHook = vi.fn(() => undefined)
|
||||
const mockTransformIncludeHook = vi.fn(() => true)
|
||||
const mockTransformHook = vi.fn(() => undefined)
|
||||
const mockLoadHook = vi.fn(() => undefined)
|
||||
|
||||
const plugin = createUnpluginWithCallback(
|
||||
mockResolveIdHook,
|
||||
mockTransformIncludeHook,
|
||||
mockTransformHook,
|
||||
mockLoadHook,
|
||||
).bun
|
||||
|
||||
await build.bun({
|
||||
entrypoints: [entryFilePath],
|
||||
plugins: [plugin()],
|
||||
external: externals,
|
||||
outdir: path.resolve(__dirname, 'test-out/bun'),
|
||||
})
|
||||
|
||||
checkHookCalls('bun', mockResolveIdHook, mockTransformIncludeHook, mockTransformHook, mockLoadHook)
|
||||
})
|
||||
})
|
||||
|
||||
1
test/unit-tests/resolve-id-external/.gitignore
vendored
Normal file
1
test/unit-tests/resolve-id-external/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
test-out
|
||||
@ -2,6 +2,7 @@ import type { VitePlugin } from 'unplugin'
|
||||
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'
|
||||
|
||||
const entryFilePath = path.resolve(__dirname, './test-src/entry.js')
|
||||
@ -141,4 +142,17 @@ describe('load hook should not be called when resolveId hook returned `external:
|
||||
|
||||
checkHookCalls()
|
||||
})
|
||||
|
||||
onlyBun('bun', async () => {
|
||||
const plugin = createMockedUnplugin().bun
|
||||
|
||||
await build.bun({
|
||||
entrypoints: [entryFilePath],
|
||||
plugins: [plugin()],
|
||||
external: externals,
|
||||
outdir: path.resolve(__dirname, 'test-out/bun'),
|
||||
})
|
||||
|
||||
checkHookCalls()
|
||||
})
|
||||
})
|
||||
|
||||
1
test/unit-tests/resolve-id/.gitignore
vendored
Normal file
1
test/unit-tests/resolve-id/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
test-out
|
||||
@ -3,6 +3,7 @@ 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']) {
|
||||
@ -138,4 +139,17 @@ describe('resolveId hook', () => {
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -13,6 +13,11 @@ export const rolldownBuild: typeof rolldown.build = rolldown.build
|
||||
export const esbuildBuild: typeof esbuild.build = esbuild.build
|
||||
export const webpackBuild: typeof webpack.webpack = webpack.webpack || (webpack as any).default || webpack
|
||||
export const rspackBuild: typeof rspack.rspack = rspack.rspack
|
||||
export const bunBuild: typeof Bun.build = typeof Bun !== 'undefined'
|
||||
? Bun.build
|
||||
: () => {
|
||||
throw new ReferenceError('Bun.build does not exist in this environment. Please run your app with the Bun runtime.')
|
||||
}
|
||||
|
||||
export const webpackVersion: string = ((webpack as any).default || webpack).version
|
||||
|
||||
@ -23,6 +28,7 @@ export const build: {
|
||||
rolldown: typeof rolldownBuild
|
||||
vite: typeof viteBuild
|
||||
esbuild: typeof esbuildBuild
|
||||
bun: typeof bunBuild
|
||||
} = {
|
||||
webpack: webpackBuild,
|
||||
rspack: rspackBuild,
|
||||
@ -39,4 +45,5 @@ export const build: {
|
||||
}))
|
||||
},
|
||||
esbuild: esbuildBuild,
|
||||
bun: bunBuild,
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import * as fs from 'node:fs'
|
||||
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 createUnpluginWithCallbacks(resolveIdCallback: UnpluginOptions['resolveId'], loadCallback: UnpluginOptions['load']) {
|
||||
@ -158,4 +159,18 @@ describe('virtual ids', () => {
|
||||
checkResolveIdHook(mockResolveIdHook)
|
||||
checkLoadHook(mockLoadHook)
|
||||
})
|
||||
|
||||
onlyBun('bun', async () => {
|
||||
const mockResolveIdHook = createResolveIdHook()
|
||||
const mockLoadHook = createLoadHook()
|
||||
const plugin = createUnpluginWithCallbacks(mockResolveIdHook, mockLoadHook).bun
|
||||
|
||||
await build.bun({
|
||||
entrypoints: [path.resolve(__dirname, 'test-src/entry.js')],
|
||||
plugins: [plugin()],
|
||||
})
|
||||
|
||||
checkResolveIdHook(mockResolveIdHook)
|
||||
checkLoadHook(mockLoadHook)
|
||||
})
|
||||
})
|
||||
|
||||
@ -5,6 +5,7 @@ import * as fs from 'node:fs'
|
||||
import * as path from 'node:path'
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'
|
||||
import { onlyBun } from '../../utils'
|
||||
import { build, toArray, webpackVersion } from '../utils'
|
||||
|
||||
function createUnpluginWithCallback(writeBundleCallback: UnpluginOptions['writeBundle']) {
|
||||
@ -166,4 +167,20 @@ describe('writeBundle hook', () => {
|
||||
|
||||
checkWriteBundleHook(mockResolveIdHook)
|
||||
})
|
||||
|
||||
onlyBun('bun', async () => {
|
||||
expect.assertions(3)
|
||||
const mockResolveIdHook = vi.fn(generateMockWriteBundleHook(path.resolve(__dirname, 'test-out/bun')))
|
||||
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'),
|
||||
naming: 'output.[ext]',
|
||||
sourcemap: 'external',
|
||||
})
|
||||
|
||||
checkWriteBundleHook(mockResolveIdHook)
|
||||
})
|
||||
})
|
||||
|
||||
3
test/utils.ts
Normal file
3
test/utils.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { it } from 'vitest'
|
||||
|
||||
export const onlyBun: typeof it.only | typeof it.skip = typeof Bun !== 'undefined' ? it.only : it.skip
|
||||
@ -5,14 +5,10 @@
|
||||
"moduleDetection": "force",
|
||||
"module": "preserve",
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"unplugin": [
|
||||
"./src/index.ts"
|
||||
]
|
||||
},
|
||||
"resolveJsonModule": true,
|
||||
"types": [
|
||||
"node"
|
||||
"node",
|
||||
"bun-types-no-globals"
|
||||
],
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user