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:
|
jobs:
|
||||||
unit-test:
|
unit-test:
|
||||||
uses: sxzz/workflows/.github/workflows/unit-test.yml@v1
|
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/)
|
- [Rspack](https://www.rspack.dev/)
|
||||||
- [Rolldown](https://rolldown.rs/)
|
- [Rolldown](https://rolldown.rs/)
|
||||||
- [Farm](https://www.farmfe.org/)
|
- [Farm](https://www.farmfe.org/)
|
||||||
|
- [Bun](https://bun.com/)
|
||||||
- And every framework built on top of them.
|
- And every framework built on top of them.
|
||||||
|
|
||||||
## Documentations
|
## Documentations
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
export const title = 'Unplugin'
|
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 url = 'https://unplugin.unjs.io/'
|
||||||
export const ogImage = `${url}/og.png`
|
export const ogImage = `${url}/og.png`
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
Unplugin
|
Unplugin
|
||||||
</h1>
|
</h1>
|
||||||
<p align="center">
|
<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>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
|||||||
@ -18,6 +18,7 @@ lastUpdated: false
|
|||||||
- [Rspack](https://www.rspack.dev/)
|
- [Rspack](https://www.rspack.dev/)
|
||||||
- [Rolldown](https://rolldown.rs/)
|
- [Rolldown](https://rolldown.rs/)
|
||||||
- [Farm](https://www.farmfe.org/)
|
- [Farm](https://www.farmfe.org/)
|
||||||
|
- [Bun](https://bun.com/)
|
||||||
|
|
||||||
## Trying It Online
|
## 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]
|
```js [Vue-CLI]
|
||||||
// vue.config.js
|
// vue.config.js
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -193,18 +209,18 @@ export default defineConfig({
|
|||||||
|
|
||||||
## Supported Hooks
|
## Supported Hooks
|
||||||
|
|
||||||
| Hook | Rollup | Vite | webpack | esbuild | Rspack | Farm | Rolldown |
|
| 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> | ✅ | ✅ | ✅ |
|
| [`enforce`](https://vite.dev/guide/api-plugin.html#plugin-ordering) | ❌ <sup>1</sup> | ✅ | ✅ | ❌ <sup>1</sup> | ✅ | ✅ | ✅ | ❌ |
|
||||||
| [`buildStart`](https://rollupjs.org/plugin-development/#buildstart) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| [`buildStart`](https://rollupjs.org/plugin-development/#buildstart) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`resolveId`](https://rollupjs.org/plugin-development/#resolveid) | ✅ | ✅ | ✅ | ✅ | ✅ <sup>5</sup> | ✅ | ✅ |
|
| [`resolveId`](https://rollupjs.org/plugin-development/#resolveid) | ✅ | ✅ | ✅ | ✅ | ✅ <sup>5</sup> | ✅ | ✅ | ✅ |
|
||||||
| ~~`loadInclude`~~<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| ~~`loadInclude`~~<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`load`](https://rollupjs.org/plugin-development/#load) | ✅ | ✅ | ✅ | ✅ <sup>3</sup> | ✅ | ✅ | ✅ |
|
| [`load`](https://rollupjs.org/plugin-development/#load) | ✅ | ✅ | ✅ | ✅ <sup>3</sup> | ✅ | ✅ | ✅ | ✅ |
|
||||||
| ~~`transformInclude`~~<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| ~~`transformInclude`~~<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`transform`](https://rollupjs.org/plugin-development/#transform) | ✅ | ✅ | ✅ | ✅ <sup>3</sup> | ✅ | ✅ | ✅ |
|
| [`transform`](https://rollupjs.org/plugin-development/#transform) | ✅ | ✅ | ✅ | ✅ <sup>3</sup> | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`watchChange`](https://rollupjs.org/plugin-development/#watchchange) | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
|
| [`watchChange`](https://rollupjs.org/plugin-development/#watchchange) | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ |
|
||||||
| [`buildEnd`](https://rollupjs.org/plugin-development/#buildend) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| [`buildEnd`](https://rollupjs.org/plugin-development/#buildend) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ <sup>6</sup> |
|
||||||
| [`writeBundle`](https://rollupjs.org/plugin-development/#writebundle)<sup>4</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| [`writeBundle`](https://rollupjs.org/plugin-development/#writebundle)<sup>4</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ <sup>6</sup> |
|
||||||
|
|
||||||
::: details Notice
|
::: 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.
|
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.
|
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.
|
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 rspackPlugin = unplugin.rspack
|
||||||
export const esbuildPlugin = unplugin.esbuild
|
export const esbuildPlugin = unplugin.esbuild
|
||||||
export const farmPlugin = unplugin.farm
|
export const farmPlugin = unplugin.farm
|
||||||
|
export const bunPlugin = unplugin.bun
|
||||||
```
|
```
|
||||||
|
|
||||||
### Filters
|
### Filters
|
||||||
@ -289,14 +307,14 @@ More details can be found in the [Rolldown's documentation](https://rolldown.rs/
|
|||||||
|
|
||||||
## Supported Context
|
## Supported Context
|
||||||
|
|
||||||
| Context | Rollup | Vite | webpack | esbuild | Rspack | Farm | Rolldown |
|
| Context | Rollup | Vite | webpack | esbuild | Rspack | Farm | Rolldown | Bun |
|
||||||
| ------------------------------------------------------------------------------------- | :----: | :--: | :-----: | :-----: | :----: | :--: | :------: |
|
| ------------------------------------------------------------------------------------- | :----: | :--: | :-----: | :-----: | :----: | :--: | :------: | :-: |
|
||||||
| [`this.parse`](https://rollupjs.org/plugin-development/#this-parse) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| [`this.parse`](https://rollupjs.org/plugin-development/#this-parse) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`this.addWatchFile`](https://rollupjs.org/plugin-development/#this-addwatchfile) | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
|
| [`this.addWatchFile`](https://rollupjs.org/plugin-development/#this-addwatchfile) | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`this.emitFile`](https://rollupjs.org/plugin-development/#this-emitfile)<sup>1</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| [`this.emitFile`](https://rollupjs.org/plugin-development/#this-emitfile)<sup>1</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`this.getWatchFiles`](https://rollupjs.org/plugin-development/#this-getwatchfiles) | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
|
| [`this.getWatchFiles`](https://rollupjs.org/plugin-development/#this-getwatchfiles) | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`this.warn`](https://rollupjs.org/plugin-development/#this-warn) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| [`this.warn`](https://rollupjs.org/plugin-development/#this-warn) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [`this.error`](https://rollupjs.org/plugin-development/#this-error) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| [`this.error`](https://rollupjs.org/plugin-development/#this-error) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
|
||||||
::: info Notice
|
::: info Notice
|
||||||
|
|
||||||
@ -309,9 +327,9 @@ More details can be found in the [Rolldown's documentation](https://rolldown.rs/
|
|||||||
|
|
||||||
### Bundler Supported
|
### Bundler Supported
|
||||||
|
|
||||||
| Rollup | Vite | webpack | Rspack | esbuild | Farm | Rolldown |
|
| Rollup | Vite | webpack | Rspack | esbuild | Farm | Rolldown | Bun |
|
||||||
| :--------------------: | :--: | :-----: | :----: | :-----: | :--: | :------: |
|
| :--------------------: | :--: | :-----: | :----: | :-----: | :--: | :------: | :-: |
|
||||||
| ✅ `>=3.1`<sup>1</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| ✅ `>=3.1`<sup>1</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
|
||||||
::: details Notice
|
::: details Notice
|
||||||
|
|
||||||
@ -409,6 +427,9 @@ export const unpluginFactory: UnpluginFactory<Options | undefined> = (
|
|||||||
farm: {
|
farm: {
|
||||||
// Farm plugin
|
// Farm plugin
|
||||||
},
|
},
|
||||||
|
bun: {
|
||||||
|
// Bun plugin
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,6 +445,7 @@ Each of the function takes the same generic factory argument as `createUnplugin`
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
import {
|
import {
|
||||||
|
createBunPlugin,
|
||||||
createEsbuildPlugin,
|
createEsbuildPlugin,
|
||||||
createFarmPlugin,
|
createFarmPlugin,
|
||||||
createRolldownPlugin,
|
createRolldownPlugin,
|
||||||
@ -440,4 +462,5 @@ const esbuildPlugin = createEsbuildPlugin(/* factory */)
|
|||||||
const webpackPlugin = createWebpackPlugin(/* factory */)
|
const webpackPlugin = createWebpackPlugin(/* factory */)
|
||||||
const rspackPlugin = createRspackPlugin(/* factory */)
|
const rspackPlugin = createRspackPlugin(/* factory */)
|
||||||
const farmPlugin = createFarmPlugin(/* factory */)
|
const farmPlugin = createFarmPlugin(/* factory */)
|
||||||
|
const bunPlugin = createBunPlugin(/* factory */)
|
||||||
```
|
```
|
||||||
|
|||||||
@ -5,7 +5,7 @@ sidebar: false
|
|||||||
hero:
|
hero:
|
||||||
name: Unplugin
|
name: Unplugin
|
||||||
text: The Unified<br>Plugin System
|
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:
|
image:
|
||||||
light: /logo_light.svg
|
light: /logo_light.svg
|
||||||
dark: /logo_dark.svg
|
dark: /logo_dark.svg
|
||||||
@ -64,6 +64,12 @@ features:
|
|||||||
icon:
|
icon:
|
||||||
src: /features/rolldown.svg
|
src: /features/rolldown.svg
|
||||||
|
|
||||||
|
- title: Bun
|
||||||
|
details: All-in-one JavaScript runtime & toolkit
|
||||||
|
link: https://bun.com/
|
||||||
|
icon:
|
||||||
|
src: /features/bun.svg
|
||||||
|
|
||||||
- title: More
|
- title: More
|
||||||
details: More supported bundlers...
|
details: More supported bundlers...
|
||||||
link: /guide/#supported-hooks
|
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:",
|
"@types/picomatch": "catalog:",
|
||||||
"ansis": "catalog:",
|
"ansis": "catalog:",
|
||||||
"bumpp": "catalog:",
|
"bumpp": "catalog:",
|
||||||
|
"bun-types-no-globals": "catalog:",
|
||||||
"esbuild": "catalog:",
|
"esbuild": "catalog:",
|
||||||
"eslint": "catalog:",
|
"eslint": "catalog:",
|
||||||
"eslint-plugin-format": "catalog:",
|
"eslint-plugin-format": "catalog:",
|
||||||
|
|||||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@ -24,6 +24,9 @@ catalogs:
|
|||||||
bumpp:
|
bumpp:
|
||||||
specifier: ^10.3.1
|
specifier: ^10.3.1
|
||||||
version: 10.3.1
|
version: 10.3.1
|
||||||
|
bun-types-no-globals:
|
||||||
|
specifier: ^1.2.22
|
||||||
|
version: 1.3.1
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.39.0
|
specifier: ^9.39.0
|
||||||
version: 9.39.0
|
version: 9.39.0
|
||||||
@ -196,6 +199,9 @@ importers:
|
|||||||
bumpp:
|
bumpp:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 10.3.1
|
version: 10.3.1
|
||||||
|
bun-types-no-globals:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 1.3.1
|
||||||
esbuild:
|
esbuild:
|
||||||
specifier: ^0.25.12
|
specifier: ^0.25.12
|
||||||
version: 0.25.12
|
version: 0.25.12
|
||||||
@ -2107,6 +2113,9 @@ packages:
|
|||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
bun-types-no-globals@1.3.1:
|
||||||
|
resolution: {integrity: sha512-nVhf54PRc8MzKvMK0IXy6TlPGy8Qtk/BY/kk7IUuAxDROYWGRhD+9WF1H0VvzIeB3/AMnuV3az9K7h/4GfSWCw==}
|
||||||
|
|
||||||
bundle-name@3.0.0:
|
bundle-name@3.0.0:
|
||||||
resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==}
|
resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@ -5319,14 +5328,14 @@ snapshots:
|
|||||||
'@babel/template@7.27.2':
|
'@babel/template@7.27.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.27.1
|
'@babel/code-frame': 7.27.1
|
||||||
'@babel/parser': 7.27.7
|
'@babel/parser': 7.28.5
|
||||||
'@babel/types': 7.28.5
|
'@babel/types': 7.28.5
|
||||||
|
|
||||||
'@babel/traverse@7.27.7':
|
'@babel/traverse@7.27.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.27.1
|
'@babel/code-frame': 7.27.1
|
||||||
'@babel/generator': 7.28.5
|
'@babel/generator': 7.28.5
|
||||||
'@babel/parser': 7.27.7
|
'@babel/parser': 7.28.5
|
||||||
'@babel/template': 7.27.2
|
'@babel/template': 7.27.2
|
||||||
'@babel/types': 7.28.5
|
'@babel/types': 7.28.5
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
@ -7188,6 +7197,8 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- magicast
|
- magicast
|
||||||
|
|
||||||
|
bun-types-no-globals@1.3.1: {}
|
||||||
|
|
||||||
bundle-name@3.0.0:
|
bundle-name@3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
run-applescript: 5.0.0
|
run-applescript: 5.0.0
|
||||||
|
|||||||
@ -11,6 +11,7 @@ catalog:
|
|||||||
'@types/picomatch': ^4.0.2
|
'@types/picomatch': ^4.0.2
|
||||||
ansis: ^4.2.0
|
ansis: ^4.2.0
|
||||||
bumpp: ^10.3.1
|
bumpp: ^10.3.1
|
||||||
|
bun-types-no-globals: ^1.2.22
|
||||||
esbuild: ^0.25.12
|
esbuild: ^0.25.12
|
||||||
eslint: ^9.39.0
|
eslint: ^9.39.0
|
||||||
eslint-plugin-format: ^1.0.2
|
eslint-plugin-format: ^1.0.2
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { join, resolve } from 'node:path'
|
|||||||
import process from 'node:process'
|
import process from 'node:process'
|
||||||
import c from 'ansis'
|
import c from 'ansis'
|
||||||
|
|
||||||
|
const isBun = !!process.versions.bun
|
||||||
|
|
||||||
const dir = resolve(import.meta.dirname, '../test/fixtures')
|
const dir = resolve(import.meta.dirname, '../test/fixtures')
|
||||||
let fixtures = await readdir(dir)
|
let fixtures = await readdir(dir)
|
||||||
|
|
||||||
@ -16,6 +18,13 @@ for (const name of fixtures) {
|
|||||||
if (existsSync(join(path, 'dist')))
|
if (existsSync(join(path, 'dist')))
|
||||||
await rm(join(path, 'dist')).catch(() => {})
|
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')
|
console.log(c.yellow.inverse.bold`\n Vite `, name, '\n')
|
||||||
execSync('npx vite --version', { cwd: path, stdio: 'inherit' })
|
execSync('npx vite --version', { cwd: path, stdio: 'inherit' })
|
||||||
execSync('npx vite build', { 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 type { UnpluginFactory, UnpluginInstance } from './types'
|
||||||
|
import { getBunPlugin } from './bun'
|
||||||
import { getEsbuildPlugin } from './esbuild'
|
import { getEsbuildPlugin } from './esbuild'
|
||||||
import { getFarmPlugin } from './farm'
|
import { getFarmPlugin } from './farm'
|
||||||
import { getRolldownPlugin } from './rolldown'
|
import { getRolldownPlugin } from './rolldown'
|
||||||
@ -36,6 +37,9 @@ export function createUnplugin<UserOptions, Nested extends boolean = boolean>(
|
|||||||
get unloader() {
|
get unloader() {
|
||||||
return getUnloaderPlugin(factory)
|
return getUnloaderPlugin(factory)
|
||||||
},
|
},
|
||||||
|
get bun() {
|
||||||
|
return getBunPlugin(factory)
|
||||||
|
},
|
||||||
get raw() {
|
get raw() {
|
||||||
return factory
|
return factory
|
||||||
},
|
},
|
||||||
@ -89,3 +93,9 @@ export function createUnloaderPlugin<UserOptions, Nested extends boolean = boole
|
|||||||
): UnpluginInstance<UserOptions>['unloader'] {
|
): UnpluginInstance<UserOptions>['unloader'] {
|
||||||
return getUnloaderPlugin(factory)
|
return getUnloaderPlugin(factory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createBunPlugin<UserOptions, Nested extends boolean = boolean>(
|
||||||
|
factory: UnpluginFactory<UserOptions, Nested>,
|
||||||
|
): UnpluginInstance<UserOptions>['bun'] {
|
||||||
|
return getBunPlugin(factory)
|
||||||
|
}
|
||||||
|
|||||||
10
src/globals.d.ts
vendored
10
src/globals.d.ts
vendored
@ -1,8 +1,14 @@
|
|||||||
/**
|
import * as BunModule from 'bun'
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
export import Bun = BunModule
|
||||||
|
|
||||||
|
/**
|
||||||
* Flag that is replaced with a boolean during build time.
|
* Flag that is replaced with a boolean during build time.
|
||||||
* __DEV__ is false in the final library output, and it is
|
* __DEV__ is false in the final library output, and it is
|
||||||
* true when the library is ad-hoc transpiled, ie. during tests.
|
* true when the library is ad-hoc transpiled, ie. during tests.
|
||||||
*
|
*
|
||||||
* See "tsdown.config.ts" and "vitest.config.ts" for more info.
|
* See "tsdown.config.ts" and "vitest.config.ts" for more info.
|
||||||
*/
|
*/
|
||||||
declare const __DEV__: boolean
|
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 { 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 { 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 { BuildOptions, Plugin as EsbuildPlugin, Loader, PluginBuild } from 'esbuild'
|
||||||
import type { Plugin as RolldownPlugin } from 'rolldown'
|
import type { Plugin as RolldownPlugin } from 'rolldown'
|
||||||
import type { AstNode, EmittedAsset, PluginContextMeta as RollupContextMeta, Plugin as RollupPlugin, SourceMapInput } from 'rollup'
|
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'
|
import type VirtualModulesPlugin from 'webpack-virtual-modules'
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
|
BunPlugin,
|
||||||
EsbuildPlugin,
|
EsbuildPlugin,
|
||||||
RolldownPlugin,
|
RolldownPlugin,
|
||||||
RollupPlugin,
|
RollupPlugin,
|
||||||
@ -52,12 +55,13 @@ export type NativeBuildContext
|
|||||||
| { framework: 'esbuild', build: PluginBuild }
|
| { framework: 'esbuild', build: PluginBuild }
|
||||||
| { framework: 'rspack', compiler: RspackCompiler, compilation: RspackCompilation, loaderContext?: RspackLoaderContext | undefined }
|
| { framework: 'rspack', compiler: RspackCompiler, compilation: RspackCompilation, loaderContext?: RspackLoaderContext | undefined }
|
||||||
| { framework: 'farm', context: FarmCompilationContext }
|
| { framework: 'farm', context: FarmCompilationContext }
|
||||||
|
| { framework: 'bun', build: BunPluginBuilder }
|
||||||
|
|
||||||
export interface UnpluginBuildContext {
|
export interface UnpluginBuildContext {
|
||||||
addWatchFile: (id: string) => void
|
addWatchFile: (id: string) => void
|
||||||
emitFile: (emittedFile: EmittedAsset) => void
|
emitFile: (emittedFile: EmittedAsset) => void
|
||||||
getWatchFiles: () => string[]
|
getWatchFiles: () => string[]
|
||||||
parse: (input: string, options?: any) => AstNode
|
parse: (input: string, options?: Partial<AcornOptions>) => AstNode
|
||||||
getNativeBuildContext?: (() => NativeBuildContext) | undefined
|
getNativeBuildContext?: (() => NativeBuildContext) | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +146,7 @@ export interface UnpluginOptions {
|
|||||||
config?: ((options: BuildOptions) => void) | undefined
|
config?: ((options: BuildOptions) => void) | undefined
|
||||||
} | undefined
|
} | undefined
|
||||||
farm?: Partial<FarmPlugin> | undefined
|
farm?: Partial<FarmPlugin> | undefined
|
||||||
|
bun?: Partial<BunPlugin> | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResolvedUnpluginOptions extends UnpluginOptions {
|
export interface ResolvedUnpluginOptions extends UnpluginOptions {
|
||||||
@ -168,6 +173,7 @@ export interface UnpluginInstance<UserOptions, Nested extends boolean = boolean>
|
|||||||
esbuild: UnpluginFactoryOutput<UserOptions, EsbuildPlugin>
|
esbuild: UnpluginFactoryOutput<UserOptions, EsbuildPlugin>
|
||||||
unloader: UnpluginFactoryOutput<UserOptions, Nested extends true ? Array<UnloaderPlugin> : UnloaderPlugin>
|
unloader: UnpluginFactoryOutput<UserOptions, Nested extends true ? Array<UnloaderPlugin> : UnloaderPlugin>
|
||||||
farm: UnpluginFactoryOutput<UserOptions, FarmPlugin>
|
farm: UnpluginFactoryOutput<UserOptions, FarmPlugin>
|
||||||
|
bun: UnpluginFactoryOutput<UserOptions, BunPlugin>
|
||||||
raw: UnpluginFactory<UserOptions, Nested>
|
raw: UnpluginFactory<UserOptions, Nested>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,6 +186,10 @@ export type UnpluginContextMeta = Partial<RollupContextMeta> & ({
|
|||||||
framework: 'esbuild'
|
framework: 'esbuild'
|
||||||
/** Set the host plugin name of esbuild when returning multiple plugins */
|
/** Set the host plugin name of esbuild when returning multiple plugins */
|
||||||
esbuildHostName?: string | undefined
|
esbuildHostName?: string | undefined
|
||||||
|
} | {
|
||||||
|
framework: 'bun'
|
||||||
|
/** Set the host plugin name of bun when returning multiple plugins */
|
||||||
|
bunHostName?: string | undefined
|
||||||
} | {
|
} | {
|
||||||
framework: 'rspack'
|
framework: 'rspack'
|
||||||
rspack: { compiler: RspackCompiler }
|
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 fs from 'node:fs/promises'
|
||||||
import { resolve } from 'node:path'
|
import { resolve } from 'node:path'
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { onlyBun } from '../../../utils'
|
||||||
|
|
||||||
const r = (...args: string[]) => resolve(__dirname, '../dist', ...args)
|
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]')
|
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 fs from 'node:fs/promises'
|
||||||
import { resolve } from 'node:path'
|
import { resolve } from 'node:path'
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { onlyBun } from '../../../utils'
|
||||||
|
|
||||||
const r = (...args: string[]) => resolve(__dirname, '../dist', ...args)
|
const r = (...args: string[]) => resolve(__dirname, '../dist', ...args)
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ describe('transform build', () => {
|
|||||||
|
|
||||||
expect(content).toContain('NON-TARGET: __UNPLUGIN__')
|
expect(content).toContain('NON-TARGET: __UNPLUGIN__')
|
||||||
expect(content).toContain('TARGET: [Injected Post Rollup]')
|
expect(content).toContain('TARGET: [Injected Post Rollup]')
|
||||||
|
// Query imports are external in Rollup
|
||||||
})
|
})
|
||||||
|
|
||||||
it('webpack', async () => {
|
it('webpack', async () => {
|
||||||
@ -51,4 +53,12 @@ describe('transform build', () => {
|
|||||||
expect(content).toContain('TARGET: [Injected Post Farm]')
|
expect(content).toContain('TARGET: [Injected Post Farm]')
|
||||||
expect(content).toContain('QUERY: [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',
|
name: 'transform-fixture-pre',
|
||||||
resolveId(id) {
|
resolveId(id) {
|
||||||
// Rollup doesn't know how to import module with query string so we ignore the module
|
// 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') {
|
if (id.includes('?query-param=query-value') && (meta.framework === 'rollup' || meta.framework === 'bun')) {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
external: true,
|
external: true,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import fs from 'node:fs/promises'
|
import fs from 'node:fs/promises'
|
||||||
import { resolve } from 'node:path'
|
import { resolve } from 'node:path'
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { onlyBun } from '../../../utils'
|
||||||
|
|
||||||
const r = (...args: string[]) => resolve(__dirname, '../dist', ...args)
|
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:ONE')
|
||||||
expect(content).toContain('VIRTUAL:TWO')
|
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 * as path from 'node:path'
|
||||||
import { createUnplugin } from 'unplugin'
|
import { createUnplugin } from 'unplugin'
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { onlyBun } from '../../utils'
|
||||||
import { build, toArray } from '../utils'
|
import { build, toArray } from '../utils'
|
||||||
|
|
||||||
function createUnpluginWithHooks(
|
function createUnpluginWithHooks(
|
||||||
@ -169,4 +170,19 @@ describe('filter', () => {
|
|||||||
|
|
||||||
check(resolveIdHandler, loadHandler, transformHandler)
|
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 * as path from 'node:path'
|
||||||
import { createUnplugin } from 'unplugin'
|
import { createUnplugin } from 'unplugin'
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { onlyBun } from '../../utils'
|
||||||
import { build, toArray } from '../utils'
|
import { build, toArray } from '../utils'
|
||||||
|
|
||||||
const entryFilePath = path.resolve(__dirname, './test-src/entry.js')
|
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
|
// We extract this check because all bundlers should behave the same
|
||||||
function checkHookCalls(
|
function checkHookCalls(
|
||||||
name: 'webpack' | 'rollup' | 'vite' | 'rspack' | 'esbuild',
|
name: 'webpack' | 'rollup' | 'vite' | 'rspack' | 'esbuild' | 'bun',
|
||||||
resolveIdCallback: Mock,
|
resolveIdCallback: Mock,
|
||||||
transformIncludeCallback: Mock,
|
transformIncludeCallback: Mock,
|
||||||
transformCallback: Mock,
|
transformCallback: Mock,
|
||||||
@ -213,4 +214,27 @@ describe('id parameter should be consistent across hooks and plugins', () => {
|
|||||||
|
|
||||||
checkHookCalls('esbuild', mockResolveIdHook, mockTransformIncludeHook, mockTransformHook, mockLoadHook)
|
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 * as path from 'node:path'
|
||||||
import { createUnplugin } from 'unplugin'
|
import { createUnplugin } from 'unplugin'
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { onlyBun } from '../../utils'
|
||||||
import { build, toArray } from '../utils'
|
import { build, toArray } from '../utils'
|
||||||
|
|
||||||
const entryFilePath = path.resolve(__dirname, './test-src/entry.js')
|
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()
|
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 * as path from 'node:path'
|
||||||
import { createUnplugin } from 'unplugin'
|
import { createUnplugin } from 'unplugin'
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { onlyBun } from '../../utils'
|
||||||
import { build, toArray } from '../utils'
|
import { build, toArray } from '../utils'
|
||||||
|
|
||||||
function createUnpluginWithCallback(resolveIdCallback: UnpluginOptions['resolveId']) {
|
function createUnpluginWithCallback(resolveIdCallback: UnpluginOptions['resolveId']) {
|
||||||
@ -138,4 +139,17 @@ describe('resolveId hook', () => {
|
|||||||
|
|
||||||
checkResolveIdHook(mockResolveIdHook)
|
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 esbuildBuild: typeof esbuild.build = esbuild.build
|
||||||
export const webpackBuild: typeof webpack.webpack = webpack.webpack || (webpack as any).default || webpack
|
export const webpackBuild: typeof webpack.webpack = webpack.webpack || (webpack as any).default || webpack
|
||||||
export const rspackBuild: typeof rspack.rspack = rspack.rspack
|
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
|
export const webpackVersion: string = ((webpack as any).default || webpack).version
|
||||||
|
|
||||||
@ -23,6 +28,7 @@ export const build: {
|
|||||||
rolldown: typeof rolldownBuild
|
rolldown: typeof rolldownBuild
|
||||||
vite: typeof viteBuild
|
vite: typeof viteBuild
|
||||||
esbuild: typeof esbuildBuild
|
esbuild: typeof esbuildBuild
|
||||||
|
bun: typeof bunBuild
|
||||||
} = {
|
} = {
|
||||||
webpack: webpackBuild,
|
webpack: webpackBuild,
|
||||||
rspack: rspackBuild,
|
rspack: rspackBuild,
|
||||||
@ -39,4 +45,5 @@ export const build: {
|
|||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
esbuild: esbuildBuild,
|
esbuild: esbuildBuild,
|
||||||
|
bun: bunBuild,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import * as fs from 'node:fs'
|
|||||||
import * as path from 'node:path'
|
import * as path from 'node:path'
|
||||||
import { createUnplugin } from 'unplugin'
|
import { createUnplugin } from 'unplugin'
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { onlyBun } from '../../utils'
|
||||||
import { build, toArray } from '../utils'
|
import { build, toArray } from '../utils'
|
||||||
|
|
||||||
function createUnpluginWithCallbacks(resolveIdCallback: UnpluginOptions['resolveId'], loadCallback: UnpluginOptions['load']) {
|
function createUnpluginWithCallbacks(resolveIdCallback: UnpluginOptions['resolveId'], loadCallback: UnpluginOptions['load']) {
|
||||||
@ -158,4 +159,18 @@ describe('virtual ids', () => {
|
|||||||
checkResolveIdHook(mockResolveIdHook)
|
checkResolveIdHook(mockResolveIdHook)
|
||||||
checkLoadHook(mockLoadHook)
|
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 * as path from 'node:path'
|
||||||
import { createUnplugin } from 'unplugin'
|
import { createUnplugin } from 'unplugin'
|
||||||
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'
|
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { onlyBun } from '../../utils'
|
||||||
import { build, toArray, webpackVersion } from '../utils'
|
import { build, toArray, webpackVersion } from '../utils'
|
||||||
|
|
||||||
function createUnpluginWithCallback(writeBundleCallback: UnpluginOptions['writeBundle']) {
|
function createUnpluginWithCallback(writeBundleCallback: UnpluginOptions['writeBundle']) {
|
||||||
@ -166,4 +167,20 @@ describe('writeBundle hook', () => {
|
|||||||
|
|
||||||
checkWriteBundleHook(mockResolveIdHook)
|
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",
|
"moduleDetection": "force",
|
||||||
"module": "preserve",
|
"module": "preserve",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"paths": {
|
|
||||||
"unplugin": [
|
|
||||||
"./src/index.ts"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"types": [
|
"types": [
|
||||||
"node"
|
"node",
|
||||||
|
"bun-types-no-globals"
|
||||||
],
|
],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user