diff --git a/README.md b/README.md
index ac915fdc..83d534be 100644
--- a/README.md
+++ b/README.md
@@ -75,7 +75,42 @@ export const webpackPlugin = unplugin.webpack
export const esbuildPlugin = unplugin.esbuild
```
-### Plugin Installation
+## Nested Plugins
+
+Since `v0.10.0`, unplugin supports constructing multiple nested plugins to behave like a single one. For example:
+
+###### Supported
+
+| Rollup | Vite | Webpack 4 | Webpack 5 | esbuild |
+| :----: | :--: | :-------: | :-------: | :-----: |
+| ✅ `>=3.1` | ✅ | ✅ | ✅ | ⚠️5 |
+
+5. Since esbuild does not have a built-in transform phase, the `transform` hook of nested plugin will not work on esbuild yet. Other hooks like `load` or `resolveId` work fine. We will try to find a way to support it in the future.
+
+###### Usage
+
+```ts
+import { createUnplugin } from 'unplugin'
+
+export const unplugin = createUnplugin((options: UserOptions) => {
+ return [
+ {
+ name: 'plugin-a',
+ transform (code) {
+ // ...
+ }
+ },
+ {
+ name: 'plugin-b',
+ resolveId (id) {
+ // ...
+ }
+ }
+ ]
+})
+```
+
+## Plugin Installation
###### Vite
diff --git a/package.json b/package.json
index f04353ef..cf700170 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
"@nuxtjs/eslint-config-typescript": "^11.0.0",
"@types/express": "^4.17.14",
"@types/fs-extra": "^9.0.13",
- "@types/node": "^18.7.16",
+ "@types/node": "^18.11.0",
"@types/webpack-sources": "^3.2.0",
"bumpp": "^8.2.1",
"conventional-changelog-cli": "^2.2.2",
@@ -53,7 +53,7 @@
"jiti": "^1.16.0",
"magic-string": "^0.26.7",
"picocolors": "^1.0.0",
- "rollup": "^2.79.1",
+ "rollup": "^3.2.2",
"tsup": "^6.3.0",
"typescript": "^4.8.4",
"vite": "^3.1.8",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5ca97c42..48383c17 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -6,7 +6,7 @@ specifiers:
'@nuxtjs/eslint-config-typescript': ^11.0.0
'@types/express': ^4.17.14
'@types/fs-extra': ^9.0.13
- '@types/node': ^18.7.16
+ '@types/node': ^18.11.0
'@types/webpack-sources': ^3.2.0
acorn: ^8.8.0
bumpp: ^8.2.1
@@ -20,7 +20,7 @@ specifiers:
jiti: ^1.16.0
magic-string: ^0.26.7
picocolors: ^1.0.0
- rollup: ^2.79.1
+ rollup: ^3.2.2
tsup: ^6.3.0
typescript: ^4.8.4
vite: ^3.1.8
@@ -42,7 +42,7 @@ devDependencies:
'@nuxtjs/eslint-config-typescript': 11.0.0_z4bbprzjrhnsfa24uvmcbu7f5q
'@types/express': 4.17.14
'@types/fs-extra': 9.0.13
- '@types/node': 18.7.16
+ '@types/node': 18.11.0
'@types/webpack-sources': 3.2.0
bumpp: 8.2.1
conventional-changelog-cli: 2.2.2
@@ -54,7 +54,7 @@ devDependencies:
jiti: 1.16.0
magic-string: 0.26.7
picocolors: 1.0.0
- rollup: 2.79.1
+ rollup: 3.2.2
tsup: 6.3.0_typescript@4.8.4
typescript: 4.8.4
vite: 3.1.8
@@ -281,7 +281,7 @@ packages:
resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
dependencies:
'@types/connect': 3.4.35
- '@types/node': 18.7.16
+ '@types/node': 18.11.0
dev: true
/@types/chai-subset/1.3.3:
@@ -297,7 +297,7 @@ packages:
/@types/connect/3.4.35:
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
dependencies:
- '@types/node': 18.7.16
+ '@types/node': 18.11.0
dev: true
/@types/eslint-scope/3.7.3:
@@ -321,7 +321,7 @@ packages:
/@types/express-serve-static-core/4.17.27:
resolution: {integrity: sha512-e/sVallzUTPdyOTiqi8O8pMdBBphscvI6E4JYaKlja4Lm+zh7UFSSdW5VMkRbhDtmrONqOUHOXRguPsDckzxNA==}
dependencies:
- '@types/node': 18.7.16
+ '@types/node': 18.11.0
'@types/qs': 6.9.7
'@types/range-parser': 1.2.4
dev: true
@@ -338,7 +338,7 @@ packages:
/@types/fs-extra/9.0.13:
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
dependencies:
- '@types/node': 18.7.16
+ '@types/node': 18.11.0
dev: true
/@types/json-schema/7.0.9:
@@ -357,8 +357,8 @@ packages:
resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
dev: true
- /@types/node/18.7.16:
- resolution: {integrity: sha512-EQHhixfu+mkqHMZl1R2Ovuvn47PUw18azMJOTwSZr9/fhzHNGXAJ0ma0dayRVchprpCj0Kc1K1xKoWaATWF1qg==}
+ /@types/node/18.11.0:
+ resolution: {integrity: sha512-IOXCvVRToe7e0ny7HpT/X9Rb2RYtElG1a+VshjwT00HxrM2dWBApHQoqsI6WiY7Q03vdf2bCrIGzVrkF/5t10w==}
dev: true
/@types/normalize-package-data/2.4.1:
@@ -377,7 +377,7 @@ packages:
resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==}
dependencies:
'@types/mime': 1.3.2
- '@types/node': 18.7.16
+ '@types/node': 18.11.0
dev: true
/@types/source-list-map/0.1.2:
@@ -387,7 +387,7 @@ packages:
/@types/webpack-sources/3.2.0:
resolution: {integrity: sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==}
dependencies:
- '@types/node': 18.7.16
+ '@types/node': 18.11.0
'@types/source-list-map': 0.1.2
source-map: 0.7.3
dev: true
@@ -2552,7 +2552,7 @@ packages:
resolution: {integrity: sha512-f2s8kEdy15cv9r7q4KkzGXvlY0JTcmCbMHZBfSQDwW77REr45IDWwd0lksDFeVHH2jJ5pqb90T77XscrjeGzzg==}
engines: {node: '>= 10.13.0'}
dependencies:
- '@types/node': 18.7.16
+ '@types/node': 18.11.0
merge-stream: 2.0.0
supports-color: 8.1.1
dev: true
@@ -3344,6 +3344,14 @@ packages:
fsevents: 2.3.2
dev: true
+ /rollup/3.2.2:
+ resolution: {integrity: sha512-tw8NITEB/A8aa8F+mmIJ7fQ7Abej0R9ugR1ZzsCqb7P8HWVIVdneN69BMTDjhk0qbUsewDSJSDTcVuCTogs8JA==}
+ engines: {node: '>=14.18.0', npm: '>=8.0.0'}
+ hasBin: true
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
/run-parallel/1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
dependencies:
@@ -3983,7 +3991,7 @@ packages:
dependencies:
'@types/chai': 4.3.3
'@types/chai-subset': 1.3.3
- '@types/node': 18.7.16
+ '@types/node': 18.11.0
chai: 4.3.6
debug: 4.3.4
local-pkg: 0.4.2
diff --git a/scripts/buildFixtures.ts b/scripts/buildFixtures.ts
index 8c48b613..57553f85 100644
--- a/scripts/buildFixtures.ts
+++ b/scripts/buildFixtures.ts
@@ -22,7 +22,7 @@ async function run () {
execSync('npx vite build', { cwd: path, stdio: 'inherit' })
console.log(c.red(c.inverse(c.bold('\n Rollup '))), name, '\n')
execSync('npx rollup --version', { cwd: path, stdio: 'inherit' })
- execSync('npx rollup -c', { cwd: path, stdio: 'inherit' })
+ execSync('npx rollup --bundleConfigAsCjs -c', { cwd: path, stdio: 'inherit' })
console.log(c.blue(c.inverse(c.bold('\n Webpack '))), name, '\n')
execSync('npx webpack --version', { cwd: path, stdio: 'inherit' })
execSync('npx webpack', { cwd: path, stdio: 'inherit' })
diff --git a/src/esbuild/index.ts b/src/esbuild/index.ts
index 7a71098f..a509052c 100644
--- a/src/esbuild/index.ts
+++ b/src/esbuild/index.ts
@@ -5,198 +5,208 @@ import type { PartialMessage } from 'esbuild'
import type { SourceMap } from 'rollup'
import { Parser } from 'acorn'
import { RawSourceMap } from '@ampproject/remapping'
-import type { UnpluginBuildContext, UnpluginContext, UnpluginContextMeta, UnpluginFactory, UnpluginInstance } from '../types'
-import { combineSourcemaps, fixSourceMap, guessLoader } from './utils'
+import type { EsbuildPlugin, UnpluginBuildContext, UnpluginContext, UnpluginContextMeta, UnpluginFactory, UnpluginInstance, UnpluginOptions } from '../types'
+import { toArray, combineSourcemaps, fixSourceMap, guessLoader } from './utils'
const watchListRecord: Record = {}
const watchList: Set = new Set()
+let i = 0
+
export function getEsbuildPlugin (
factory: UnpluginFactory
): UnpluginInstance['esbuild'] {
- return (userOptions?: UserOptions) => {
+ return (userOptions?: UserOptions): EsbuildPlugin => {
const meta: UnpluginContextMeta = {
framework: 'esbuild'
}
- const plugin = factory(userOptions!, meta)
-
- return {
- name: plugin.name,
- setup:
- plugin.esbuild?.setup ??
- function setup ({ onStart, onEnd, onResolve, onLoad, initialOptions, esbuild: { build } }) {
- const onResolveFilter = plugin.esbuild?.onResolveFilter ?? /.*/
- const onLoadFilter = plugin.esbuild?.onLoadFilter ?? /.*/
-
- const context:UnpluginBuildContext = {
- parse (code: string, opts: any = {}) {
- return Parser.parse(code, {
- sourceType: 'module',
- ecmaVersion: 'latest',
- locations: true,
- ...opts
- })
- },
- addWatchFile (id) {
- watchList.add(path.resolve(id))
- },
- emitFile (emittedFile) {
- const outFileName = emittedFile.fileName || emittedFile.name
- if (initialOptions.outdir && emittedFile.source && outFileName) {
- fs.writeFileSync(path.resolve(initialOptions.outdir, outFileName), emittedFile.source)
- }
- },
- getWatchFiles () {
- return Array.from(watchList)
+ const plugins = toArray(factory(userOptions!, meta))
+
+ const setup = (plugin: UnpluginOptions): EsbuildPlugin['setup'] =>
+ plugin.esbuild?.setup ??
+ (({ onStart, onEnd, onResolve, onLoad, initialOptions, esbuild: { build } }) => {
+ const onResolveFilter = plugin.esbuild?.onResolveFilter ?? /.*/
+ const onLoadFilter = plugin.esbuild?.onLoadFilter ?? /.*/
+
+ const context: UnpluginBuildContext = {
+ parse (code: string, opts: any = {}) {
+ return Parser.parse(code, {
+ sourceType: 'module',
+ ecmaVersion: 'latest',
+ locations: true,
+ ...opts
+ })
+ },
+ addWatchFile (id) {
+ watchList.add(path.resolve(id))
+ },
+ emitFile (emittedFile) {
+ const outFileName = emittedFile.fileName || emittedFile.name
+ if (initialOptions.outdir && emittedFile.source && outFileName) {
+ fs.writeFileSync(path.resolve(initialOptions.outdir, outFileName), emittedFile.source)
}
+ },
+ getWatchFiles () {
+ return Array.from(watchList)
}
+ }
- // Ensure output directory exists for this.emitFile
- if (initialOptions.outdir && !fs.existsSync(initialOptions.outdir)) {
- fs.mkdirSync(initialOptions.outdir, { recursive: true })
- }
+ // Ensure output directory exists for this.emitFile
+ if (initialOptions.outdir && !fs.existsSync(initialOptions.outdir)) {
+ fs.mkdirSync(initialOptions.outdir, { recursive: true })
+ }
- if (plugin.buildStart) {
- onStart(() => plugin.buildStart!.call(context))
- }
+ if (plugin.buildStart) {
+ onStart(() => plugin.buildStart!.call(context))
+ }
- if (plugin.buildEnd || initialOptions.watch) {
- const rebuild = () => build({
- ...initialOptions,
- watch: false
- })
+ if (plugin.buildEnd || initialOptions.watch) {
+ const rebuild = () => build({
+ ...initialOptions,
+ watch: false
+ })
+
+ onEnd(async () => {
+ await plugin.buildEnd!.call(context)
+ if (initialOptions.watch) {
+ Object.keys(watchListRecord).forEach((id) => {
+ if (!watchList.has(id)) {
+ watchListRecord[id].close()
+ delete watchListRecord[id]
+ }
+ })
+ watchList.forEach((id) => {
+ if (!Object.keys(watchListRecord).includes(id)) {
+ watchListRecord[id] = chokidar.watch(id)
+ watchListRecord[id].on('change', async () => {
+ await plugin.watchChange?.call(context, id, { event: 'update' })
+ rebuild()
+ })
+ watchListRecord[id].on('unlink', async () => {
+ await plugin.watchChange?.call(context, id, { event: 'delete' })
+ rebuild()
+ })
+ }
+ })
+ }
+ })
+ }
- onEnd(async () => {
- await plugin.buildEnd!.call(context)
- if (initialOptions.watch) {
- Object.keys(watchListRecord).forEach((id) => {
- if (!watchList.has(id)) {
- watchListRecord[id].close()
- delete watchListRecord[id]
- }
- })
- watchList.forEach((id) => {
- if (!Object.keys(watchListRecord).includes(id)) {
- watchListRecord[id] = chokidar.watch(id)
- watchListRecord[id].on('change', async () => {
- await plugin.watchChange?.call(context, id, { event: 'update' })
- rebuild()
- })
- watchListRecord[id].on('unlink', async () => {
- await plugin.watchChange?.call(context, id, { event: 'delete' })
- rebuild()
- })
- }
- })
- }
- })
- }
+ if (plugin.resolveId) {
+ onResolve({ filter: onResolveFilter }, async (args) => {
+ if (initialOptions.external?.includes(args.path)) {
+ // We don't want to call the `resolveId` hook for external modules, since rollup doesn't do
+ // that and we want to have consistent behaviour across bundlers
+ return undefined
+ }
- if (plugin.resolveId) {
- onResolve({ filter: onResolveFilter }, async (args) => {
- if (initialOptions.external?.includes(args.path)) {
- // We don't want to call the `resolveId` hook for external modules, since rollup doesn't do
- // that and we want to have consistent behaviour across bundlers
- return undefined
- }
+ const isEntry = args.kind === 'entry-point'
+ const result = await plugin.resolveId!(
+ args.path,
+ // We explicitly have this if statement here for consistency with the integration of other bundelers.
+ // Here, `args.importer` is just an empty string on entry files whereas the euqivalent on other bundlers is `undefined.`
+ isEntry ? undefined : args.importer,
+ { isEntry }
+ )
+ if (typeof result === 'string') {
+ return { path: result, namespace: plugin.name }
+ } else if (typeof result === 'object' && result !== null) {
+ return { path: result.id, external: result.external, namespace: plugin.name }
+ }
+ })
+ }
- const isEntry = args.kind === 'entry-point'
- const result = await plugin.resolveId!(
- args.path,
- // We explicitly have this if statement here for consistency with the integration of other bundelers.
- // Here, `args.importer` is just an empty string on entry files whereas the euqivalent on other bundlers is `undefined.`
- isEntry ? undefined : args.importer,
- { isEntry }
- )
+ if (plugin.load || plugin.transform) {
+ onLoad({ filter: onLoadFilter }, async (args) => {
+ const id = args.path + args.suffix
+
+ const errors: PartialMessage[] = []
+ const warnings: PartialMessage[] = []
+ const pluginContext: UnpluginContext = {
+ error (message) { errors.push({ text: String(message) }) },
+ warn (message) { warnings.push({ text: String(message) }) }
+ }
+ // because we use `namespace` to simulate virtual modules,
+ // it is required to forward `resolveDir` for esbuild to find dependencies.
+ const resolveDir = path.dirname(args.path)
+
+ let code: string | undefined, map: SourceMap | null | undefined
+
+ if (plugin.load && (!plugin.loadInclude || plugin.loadInclude(id))) {
+ const result = await plugin.load.call(Object.assign(context, pluginContext), id)
if (typeof result === 'string') {
- return { path: result, namespace: plugin.name }
+ code = result
} else if (typeof result === 'object' && result !== null) {
- return { path: result.id, external: result.external, namespace: plugin.name }
+ code = result.code
+ map = result.map as any
}
- })
- }
-
- if (plugin.load || plugin.transform) {
- onLoad({ filter: onLoadFilter }, async (args) => {
- const id = args.path + args.suffix
+ }
- const errors: PartialMessage[] = []
- const warnings: PartialMessage[] = []
- const pluginContext: UnpluginContext = {
- error (message) { errors.push({ text: String(message) }) },
- warn (message) { warnings.push({ text: String(message) }) }
+ if (!plugin.transform) {
+ if (code === undefined) {
+ return null
}
- // because we use `namespace` to simulate virtual modules,
- // it is required to forward `resolveDir` for esbuild to find dependencies.
- const resolveDir = path.dirname(args.path)
-
- let code: string | undefined, map: SourceMap | null | undefined
-
- if (plugin.load && (!plugin.loadInclude || plugin.loadInclude(id))) {
- const result = await plugin.load.call(Object.assign(context, pluginContext), id)
- if (typeof result === 'string') {
- code = result
- } else if (typeof result === 'object' && result !== null) {
- code = result.code
- map = result.map
+ if (map) {
+ // fix missing sourcesContent, esbuild depends on it
+ if (!map.sourcesContent || map.sourcesContent.length === 0) {
+ map.sourcesContent = [code]
}
+ map = fixSourceMap(map as RawSourceMap)
+ code += `\n//# sourceMappingURL=${map.toUrl()}`
}
+ return { contents: code, errors, warnings, loader: guessLoader(args.path), resolveDir }
+ }
- if (!plugin.transform) {
- if (code === undefined) {
- return null
- }
- if (map) {
- // fix missing sourcesContent, esbuild depends on it
- if (!map.sourcesContent || map.sourcesContent.length === 0) {
- map.sourcesContent = [code]
- }
- map = fixSourceMap(map as RawSourceMap)
- code += `\n//# sourceMappingURL=${map.toUrl()}`
- }
- return { contents: code, errors, warnings, loader: guessLoader(args.path), resolveDir }
+ if (!plugin.transformInclude || plugin.transformInclude(id)) {
+ if (!code) {
+ // caution: 'utf8' assumes the input file is not in binary.
+ // if you want your plugin handle binary files, make sure to
+ // `plugin.load()` them first.
+ code = await fs.promises.readFile(args.path, 'utf8')
}
- if (!plugin.transformInclude || plugin.transformInclude(id)) {
- if (!code) {
- // caution: 'utf8' assumes the input file is not in binary.
- // if you want your plugin handle binary files, make sure to
- // `plugin.load()` them first.
- code = await fs.promises.readFile(args.path, 'utf8')
- }
-
- const result = await plugin.transform.call(Object.assign(context, pluginContext), code, id)
- if (typeof result === 'string') {
- code = result
- } else if (typeof result === 'object' && result !== null) {
- code = result.code
- // if we already got sourcemap from `load()`,
- // combine the two sourcemaps
- if (map && result.map) {
- map = combineSourcemaps(args.path, [
- result.map as RawSourceMap,
- map as RawSourceMap
- ]) as SourceMap
- } else {
- // otherwise, we always keep the last one, even if it's empty
- map = result.map
- }
+ const result = await plugin.transform.call(Object.assign(context, pluginContext), code, id)
+ if (typeof result === 'string') {
+ code = result
+ } else if (typeof result === 'object' && result !== null) {
+ code = result.code
+ // if we already got sourcemap from `load()`,
+ // combine the two sourcemaps
+ if (map && result.map) {
+ map = combineSourcemaps(args.path, [
+ result.map as RawSourceMap,
+ map as RawSourceMap
+ ]) as SourceMap
+ } else {
+ // otherwise, we always keep the last one, even if it's empty
+ map = result.map as any
}
}
+ }
- if (code) {
- if (map) {
- if (!map.sourcesContent || map.sourcesContent.length === 0) {
- map.sourcesContent = [code]
- }
- map = fixSourceMap(map as RawSourceMap)
- code += `\n//# sourceMappingURL=${map.toUrl()}`
+ if (code) {
+ if (map) {
+ if (!map.sourcesContent || map.sourcesContent.length === 0) {
+ map.sourcesContent = [code]
}
- return { contents: code, errors, warnings, loader: guessLoader(args.path), resolveDir }
+ map = fixSourceMap(map as RawSourceMap)
+ code += `\n//# sourceMappingURL=${map.toUrl()}`
}
- })
- }
+ return { contents: code, errors, warnings, loader: guessLoader(args.path), resolveDir }
+ }
+ })
}
- }
+ })
+
+ const setupMultiplePlugins = ():EsbuildPlugin['setup'] =>
+ (build) => {
+ for (const plugin of plugins) {
+ setup(plugin)(build)
+ }
+ }
+
+ return plugins.length === 1
+ ? { name: plugins[0].name, setup: setup(plugins[0]) }
+ : { name: meta.esbuildHostName ?? `unplugin-host-${i++}`, setup: setupMultiplePlugins() }
}
}
diff --git a/src/esbuild/utils.ts b/src/esbuild/utils.ts
index 6e6315f3..c146218b 100644
--- a/src/esbuild/utils.ts
+++ b/src/esbuild/utils.ts
@@ -7,6 +7,8 @@ import type {
import type { Loader } from 'esbuild'
import type { SourceMap } from 'rollup'
+export * from '../utils'
+
const ExtToLoader: Record = {
'.js': 'js',
'.mjs': 'js',
diff --git a/src/rollup/index.ts b/src/rollup/index.ts
index d3539d41..feb9db1c 100644
--- a/src/rollup/index.ts
+++ b/src/rollup/index.ts
@@ -1,4 +1,5 @@
import { UnpluginInstance, UnpluginFactory, UnpluginOptions, RollupPlugin, UnpluginContextMeta } from '../types'
+import { toArray } from '../utils'
export function getRollupPlugin (
factory: UnpluginFactory
@@ -7,8 +8,9 @@ export function getRollupPlugin (
const meta: UnpluginContextMeta = {
framework: 'rollup'
}
- const rawPlugin = factory(userOptions!, meta)
- return toRollupPlugin(rawPlugin)
+ const rawPlugins = toArray(factory(userOptions!, meta))
+ const plugins = rawPlugins.map(plugin => toRollupPlugin(plugin))
+ return plugins.length === 1 ? plugins[0] : plugins
}
}
diff --git a/src/types.ts b/src/types.ts
index 12d7ef36..b4f261a0 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -3,6 +3,7 @@ import type { Compiler as WebpackCompiler, WebpackPluginInstance } from 'webpack
import type { Plugin as VitePlugin } from 'vite'
import type { Plugin as EsbuildPlugin } from 'esbuild'
import type VirtualModulesPlugin from 'webpack-virtual-modules'
+import type { Arrayable } from './utils'
export {
EsbuildPlugin,
@@ -65,25 +66,32 @@ export interface ResolvedUnpluginOptions extends UnpluginOptions {
__virtualModulePrefix: string
}
-export type UnpluginFactory = (options: UserOptions, meta: UnpluginContextMeta) => UnpluginOptions
+export type UnpluginFactory = (options: UserOptions, meta: UnpluginContextMeta) =>
+ Arrayable
export type UnpluginFactoryOutput = undefined extends UserOptions
? (options?: UserOptions) => Return
: (options: UserOptions) => Return
export interface UnpluginInstance {
- rollup: UnpluginFactoryOutput
+ rollup: UnpluginFactoryOutput>
+ vite: UnpluginFactoryOutput>
webpack: UnpluginFactoryOutput
- vite: UnpluginFactoryOutput
esbuild: UnpluginFactoryOutput
raw: UnpluginFactory
}
-export interface UnpluginContextMeta extends Partial {
- framework: 'rollup' | 'vite' | 'webpack' | 'esbuild'
- webpack?: {
+export type UnpluginContextMeta = Partial & ({
+ framework: 'rollup' | 'vite'
+} | {
+ framework: 'webpack'
+ webpack: {
compiler: WebpackCompiler
}
-}
+} | {
+ framework: 'esbuild'
+ /** Set the host plugin name of esbuild when returning multiple plugins */
+ esbuildHostName?: string,
+})
export interface UnpluginContext {
error(message: any): void
diff --git a/src/utils.ts b/src/utils.ts
index 85809f19..7292343e 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -17,3 +17,19 @@ export function normalizeAbsolutePath (path: string) {
return path
}
}
+
+/**
+ * Null or whatever
+ */
+export type Nullable = T | null | undefined
+
+/**
+ * Array, or not yet
+ */
+export type Arrayable = T | Array
+
+export function toArray (array?: Nullable>): Array {
+ array = array || []
+ if (Array.isArray(array)) { return array }
+ return [array]
+}
diff --git a/src/vite/index.ts b/src/vite/index.ts
index 21b3bb98..a1f44112 100644
--- a/src/vite/index.ts
+++ b/src/vite/index.ts
@@ -1,5 +1,6 @@
import { toRollupPlugin } from '../rollup'
import { UnpluginInstance, UnpluginFactory, VitePlugin, UnpluginContextMeta } from '../types'
+import { toArray } from '../utils'
export function getVitePlugin (
factory: UnpluginFactory
@@ -8,13 +9,16 @@ export function getVitePlugin (
const meta: UnpluginContextMeta = {
framework: 'vite'
}
- const rawPlugin = factory(userOptions!, meta)
+ const rawPlugins = toArray(factory(userOptions!, meta))
- const plugin = toRollupPlugin(rawPlugin, false) as VitePlugin
+ const plugins = rawPlugins.map((rawPlugin) => {
+ const plugin = toRollupPlugin(rawPlugin, false) as VitePlugin
+ if (rawPlugin.vite) {
+ Object.assign(plugin, rawPlugin.vite)
+ }
+ return plugin
+ })
- if (rawPlugin.vite) {
- Object.assign(plugin, rawPlugin.vite)
- }
- return plugin
+ return plugins.length === 1 ? plugins[0] : plugins
}
}
diff --git a/src/webpack/index.ts b/src/webpack/index.ts
index 94c6c9aa..e5fa123d 100644
--- a/src/webpack/index.ts
+++ b/src/webpack/index.ts
@@ -4,7 +4,7 @@ import { resolve, dirname } from 'path'
import VirtualModulesPlugin from 'webpack-virtual-modules'
import type { ResolvePluginInstance, RuleSetUseItem } from 'webpack'
import type { UnpluginContextMeta, UnpluginInstance, UnpluginFactory, WebpackCompiler, ResolvedUnpluginOptions } from '../types'
-import { normalizeAbsolutePath } from '../utils'
+import { normalizeAbsolutePath, toArray } from '../utils'
import { createContext } from './context'
const _dirname = typeof __dirname !== 'undefined' ? __dirname : dirname(fileURLToPath(import.meta.url))
@@ -29,6 +29,9 @@ export function getWebpackPlugin (
return (userOptions?: UserOptions) => {
return {
apply (compiler: WebpackCompiler) {
+ const injected = compiler.$unpluginContext || {}
+ compiler.$unpluginContext = injected
+
const meta: UnpluginContextMeta = {
framework: 'webpack',
webpack: {
@@ -36,185 +39,185 @@ export function getWebpackPlugin (
}
}
- const rawPlugin = factory(userOptions!, meta)
- const plugin = Object.assign(
- rawPlugin,
- {
- __unpluginMeta: meta,
- __virtualModulePrefix: VIRTUAL_MODULE_PREFIX
- }
- ) as ResolvedUnpluginOptions
+ const rawPlugins = toArray(factory(userOptions!, meta))
+ for (const rawPlugin of rawPlugins) {
+ const plugin = Object.assign(
+ rawPlugin,
+ {
+ __unpluginMeta: meta,
+ __virtualModulePrefix: VIRTUAL_MODULE_PREFIX
+ }
+ ) as ResolvedUnpluginOptions
- // inject context object to share with loaders
- const injected = compiler.$unpluginContext || {}
- compiler.$unpluginContext = injected
- injected[plugin.name] = plugin
+ // inject context object to share with loaders
+ injected[plugin.name] = plugin
- compiler.hooks.thisCompilation.tap(plugin.name, (compilation) => {
- compilation.hooks.childCompiler.tap(plugin.name, (childCompiler) => {
- childCompiler.$unpluginContext = injected
+ compiler.hooks.thisCompilation.tap(plugin.name, (compilation) => {
+ compilation.hooks.childCompiler.tap(plugin.name, (childCompiler) => {
+ childCompiler.$unpluginContext = injected
+ })
})
- })
-
- const externalModules = new Set()
-
- // transform hook
- if (plugin.transform) {
- const useLoader: RuleSetUseItem[] = [{
- loader: `${TRANSFORM_LOADER}?unpluginName=${encodeURIComponent(plugin.name)}`
- }]
- const useNone: RuleSetUseItem[] = []
- compiler.options.module.rules.push({
- enforce: plugin.enforce,
- use: (data: { resource: string | null, resourceQuery: string }) => {
- if (data.resource == null) {
+
+ const externalModules = new Set()
+
+ // transform hook
+ if (plugin.transform) {
+ const useLoader: RuleSetUseItem[] = [{
+ loader: `${TRANSFORM_LOADER}?unpluginName=${encodeURIComponent(plugin.name)}`
+ }]
+ const useNone: RuleSetUseItem[] = []
+ compiler.options.module.rules.unshift({
+ enforce: plugin.enforce,
+ use: (data: { resource: string | null, resourceQuery: string }) => {
+ if (data.resource == null) {
+ return useNone
+ }
+ const id = normalizeAbsolutePath(data.resource + (data.resourceQuery || ''))
+ if (!plugin.transformInclude || plugin.transformInclude(id)) {
+ return useLoader
+ }
return useNone
}
- const id = normalizeAbsolutePath(data.resource + (data.resourceQuery || ''))
- if (!plugin.transformInclude || plugin.transformInclude(id)) {
- return useLoader
- }
- return useNone
+ })
+ }
+
+ // resolveId hook
+ if (plugin.resolveId) {
+ let vfs = compiler.options.plugins.find(i => i instanceof VirtualModulesPlugin) as VirtualModulesPlugin
+ if (!vfs) {
+ vfs = new VirtualModulesPlugin()
+ compiler.options.plugins.push(vfs)
}
- })
- }
+ plugin.__vfsModules = new Set()
+ plugin.__vfs = vfs
+
+ const resolverPlugin: ResolvePluginInstance = {
+ apply (resolver) {
+ const target = resolver.ensureHook('resolve')
+
+ resolver
+ .getHook('resolve')
+ .tapAsync(plugin.name, async (request, resolveContext, callback) => {
+ if (!request.request) {
+ return callback()
+ }
- // resolveId hook
- if (plugin.resolveId) {
- let vfs = compiler.options.plugins.find(i => i instanceof VirtualModulesPlugin) as VirtualModulesPlugin
- if (!vfs) {
- vfs = new VirtualModulesPlugin()
- compiler.options.plugins.push(vfs)
- }
- plugin.__vfsModules = new Set()
- plugin.__vfs = vfs
-
- const resolverPlugin: ResolvePluginInstance = {
- apply (resolver) {
- const target = resolver.ensureHook('resolve')
-
- resolver
- .getHook('resolve')
- .tapAsync(plugin.name, async (request, resolveContext, callback) => {
- if (!request.request) {
- return callback()
- }
-
- // filter out invalid requests
- if (normalizeAbsolutePath(request.request).startsWith(plugin.__virtualModulePrefix)) {
- return callback()
- }
-
- const id = normalizeAbsolutePath(request.request)
-
- const requestContext = (request as unknown as { context: { issuer: string } }).context
- const importer = requestContext.issuer !== '' ? requestContext.issuer : undefined
- const isEntry = requestContext.issuer === ''
-
- // call hook
- const resolveIdResult = await plugin.resolveId!(id, importer, { isEntry })
-
- if (resolveIdResult == null) {
- return callback()
- }
-
- let resolved = typeof resolveIdResult === 'string' ? resolveIdResult : resolveIdResult.id
-
- const isExternal = typeof resolveIdResult === 'string' ? false : resolveIdResult.external === true
- if (isExternal) {
- externalModules.add(resolved)
- }
-
- // If the resolved module does not exist,
- // we treat it as a virtual module
- if (!fs.existsSync(resolved)) {
- resolved = normalizeAbsolutePath(
- plugin.__virtualModulePrefix +
- encodeURIComponent(resolved) // URI encode id so webpack doesn't think it's part of the path
- )
-
- // webpack virtual module should pass in the correct path
- // https://github.com/unjs/unplugin/pull/155
- if (!plugin.__vfsModules!.has(resolved)) {
- plugin.__vfs!.writeModule(resolved, '')
- plugin.__vfsModules!.add(resolved)
+ // filter out invalid requests
+ if (normalizeAbsolutePath(request.request).startsWith(plugin.__virtualModulePrefix)) {
+ return callback()
}
- }
- // construct the new request
- const newRequest = {
- ...request,
- request: resolved
- }
+ const id = normalizeAbsolutePath(request.request)
- // redirect the resolver
- resolver.doResolve(target, newRequest, null, resolveContext, callback)
- })
- }
- }
+ const requestContext = (request as unknown as { context: { issuer: string } }).context
+ const importer = requestContext.issuer !== '' ? requestContext.issuer : undefined
+ const isEntry = requestContext.issuer === ''
- compiler.options.resolve.plugins = compiler.options.resolve.plugins || []
- compiler.options.resolve.plugins.push(resolverPlugin)
- }
+ // call hook
+ const resolveIdResult = await plugin.resolveId!(id, importer, { isEntry })
- // load hook
- if (plugin.load) {
- compiler.options.module.rules.push({
- include (id) {
- if (id.startsWith(plugin.__virtualModulePrefix)) {
- id = decodeURIComponent(id.slice(plugin.__virtualModulePrefix.length))
- }
+ if (resolveIdResult == null) {
+ return callback()
+ }
- // load include filter
- if (plugin.loadInclude && !plugin.loadInclude(id)) {
- return false
- }
+ let resolved = typeof resolveIdResult === 'string' ? resolveIdResult : resolveIdResult.id
- // Don't run load hook for external modules
- return !externalModules.has(id)
- },
- enforce: plugin.enforce,
- use: [{
- loader: LOAD_LOADER,
- options: {
- unpluginName: plugin.name
- }
- }]
- })
- }
+ const isExternal = typeof resolveIdResult === 'string' ? false : resolveIdResult.external === true
+ if (isExternal) {
+ externalModules.add(resolved)
+ }
- if (plugin.webpack) {
- plugin.webpack(compiler)
- }
+ // If the resolved module does not exist,
+ // we treat it as a virtual module
+ if (!fs.existsSync(resolved)) {
+ resolved = normalizeAbsolutePath(
+ plugin.__virtualModulePrefix +
+ encodeURIComponent(resolved) // URI encode id so webpack doesn't think it's part of the path
+ )
+
+ // webpack virtual module should pass in the correct path
+ // https://github.com/unjs/unplugin/pull/155
+ if (!plugin.__vfsModules!.has(resolved)) {
+ plugin.__vfs!.writeModule(resolved, '')
+ plugin.__vfsModules!.add(resolved)
+ }
+ }
- if (plugin.watchChange || plugin.buildStart) {
- compiler.hooks.make.tapPromise(plugin.name, async (compilation) => {
- const context = createContext(compilation)
- if (plugin.watchChange && (compiler.modifiedFiles || compiler.removedFiles)) {
- const promises:Promise[] = []
- if (compiler.modifiedFiles) {
- compiler.modifiedFiles.forEach(file =>
- promises.push(Promise.resolve(plugin.watchChange!.call(context, file, { event: 'update' })))
- )
- }
- if (compiler.removedFiles) {
- compiler.removedFiles.forEach(file =>
- promises.push(Promise.resolve(plugin.watchChange!.call(context, file, { event: 'delete' })))
- )
+ // construct the new request
+ const newRequest = {
+ ...request,
+ request: resolved
+ }
+
+ // redirect the resolver
+ resolver.doResolve(target, newRequest, null, resolveContext, callback)
+ })
}
- await Promise.all(promises)
}
- if (plugin.buildStart) {
- return await plugin.buildStart.call(context)
- }
- })
- }
+ compiler.options.resolve.plugins = compiler.options.resolve.plugins || []
+ compiler.options.resolve.plugins.push(resolverPlugin)
+ }
- if (plugin.buildEnd) {
- compiler.hooks.emit.tapPromise(plugin.name, async (compilation) => {
- await plugin.buildEnd!.call(createContext(compilation))
- })
+ // load hook
+ if (plugin.load) {
+ compiler.options.module.rules.unshift({
+ include (id) {
+ if (id.startsWith(plugin.__virtualModulePrefix)) {
+ id = decodeURIComponent(id.slice(plugin.__virtualModulePrefix.length))
+ }
+
+ // load include filter
+ if (plugin.loadInclude && !plugin.loadInclude(id)) {
+ return false
+ }
+
+ // Don't run load hook for external modules
+ return !externalModules.has(id)
+ },
+ enforce: plugin.enforce,
+ use: [{
+ loader: LOAD_LOADER,
+ options: {
+ unpluginName: plugin.name
+ }
+ }]
+ })
+ }
+
+ if (plugin.webpack) {
+ plugin.webpack(compiler)
+ }
+
+ if (plugin.watchChange || plugin.buildStart) {
+ compiler.hooks.make.tapPromise(plugin.name, async (compilation) => {
+ const context = createContext(compilation)
+ if (plugin.watchChange && (compiler.modifiedFiles || compiler.removedFiles)) {
+ const promises:Promise[] = []
+ if (compiler.modifiedFiles) {
+ compiler.modifiedFiles.forEach(file =>
+ promises.push(Promise.resolve(plugin.watchChange!.call(context, file, { event: 'update' })))
+ )
+ }
+ if (compiler.removedFiles) {
+ compiler.removedFiles.forEach(file =>
+ promises.push(Promise.resolve(plugin.watchChange!.call(context, file, { event: 'delete' })))
+ )
+ }
+ await Promise.all(promises)
+ }
+
+ if (plugin.buildStart) {
+ return await plugin.buildStart.call(context)
+ }
+ })
+ }
+
+ if (plugin.buildEnd) {
+ compiler.hooks.emit.tapPromise(plugin.name, async (compilation) => {
+ await plugin.buildEnd!.call(createContext(compilation))
+ })
+ }
}
}
}
diff --git a/test/fixtures/transform/__test__/build.test.ts b/test/fixtures/transform/__test__/build.test.ts
index 5b56f45c..ac2cb0ad 100644
--- a/test/fixtures/transform/__test__/build.test.ts
+++ b/test/fixtures/transform/__test__/build.test.ts
@@ -9,30 +9,31 @@ describe('transform build', () => {
const content = await fs.readFile(r('vite/main.js.mjs'), 'utf-8')
expect(content).toContain('NON-TARGET: __UNPLUGIN__')
- expect(content).toContain('TARGET: [Injected Vite]')
- expect(content).toContain('QUERY: [Injected Vite]')
+ expect(content).toContain('TARGET: [Injected Post Vite]')
+ expect(content).toContain('QUERY: [Injected Post Vite]')
})
it('rollup', async () => {
const content = await fs.readFile(r('rollup/main.js'), 'utf-8')
expect(content).toContain('NON-TARGET: __UNPLUGIN__')
- expect(content).toContain('TARGET: [Injected Rollup]')
+ expect(content).toContain('TARGET: [Injected Post Rollup]')
})
it('webpack', async () => {
const content = await fs.readFile(r('webpack/main.js'), 'utf-8')
expect(content).toContain('NON-TARGET: __UNPLUGIN__')
- expect(content).toContain('TARGET: [Injected Webpack]')
- expect(content).toContain('QUERY: [Injected Webpack]')
+ expect(content).toContain('TARGET: [Injected Post Webpack]')
+ expect(content).toContain('QUERY: [Injected Post Webpack]')
})
- it('esbuild', async () => {
+ // TODO: esbuild not yet support nested transform
+ it.fails('esbuild', async () => {
const content = await fs.readFile(r('esbuild/main.js'), 'utf-8')
expect(content).toContain('NON-TARGET: __UNPLUGIN__')
- expect(content).toContain('TARGET: [Injected Esbuild]')
- expect(content).toContain('QUERY: [Injected Esbuild]')
+ expect(content).toContain('TARGET: [Injected Post Esbuild]')
+ expect(content).toContain('QUERY: [Injected Post Esbuild]')
})
})
diff --git a/test/fixtures/transform/unplugin.js b/test/fixtures/transform/unplugin.js
index 21e5862a..809d4abf 100644
--- a/test/fixtures/transform/unplugin.js
+++ b/test/fixtures/transform/unplugin.js
@@ -1,43 +1,70 @@
-const { createUnplugin } = require('unplugin')
const MagicString = require('magic-string')
+const { createUnplugin } = require('unplugin')
module.exports = createUnplugin((options, meta) => {
- return {
- name: 'transform-fixture',
- 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') {
- return {
- id,
- external: true
+ return [
+ {
+ 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') {
+ return {
+ id,
+ external: true
+ }
+ }
+ },
+ transformInclude (id) {
+ return id.match(/[/\\]target\.js$/) || id.includes('?query-param=query-value')
+ },
+ transform (code, id) {
+ const s = new MagicString(code)
+ const index = code.indexOf('__UNPLUGIN__')
+ if (index === -1) {
+ return null
}
- }
- },
- transformInclude (id) {
- return id.match(/[/\\]target\.js$/) || id.includes('?query-param=query-value')
- },
- transform (code, id) {
- const s = new MagicString(code)
- const index = code.indexOf('__UNPLUGIN__')
- if (index === -1) {
- return null
- }
- const injectedCode = `[Injected ${options.msg}]`
+ const injectedCode = `[Injected ${options.msg}]`
- if (id.includes(injectedCode)) {
- throw new Error('File was already transformed')
+ if (id.includes(injectedCode)) {
+ throw new Error('File was already transformed')
+ }
+
+ s.overwrite(index, index + '__UNPLUGIN__'.length, injectedCode)
+
+ return {
+ code: s.toString(),
+ map: s.generateMap({
+ source: id,
+ includeContent: true
+ })
+ }
}
+ },
+ {
+ name: 'transform-fixture-post',
+ transformInclude (id) {
+ return id.match(/[/\\]target\.js$/) || id.includes('?query-param=query-value')
+ },
+ transform (code, id) {
+ if (!code.includes('Injected')) {
+ return null
+ }
- s.overwrite(index, index + '__UNPLUGIN__'.length, injectedCode)
+ const s = new MagicString(code)
+ s.replace(
+ 'Injected',
+ 'Injected Post'
+ )
- return {
- code: s.toString(),
- map: s.generateMap({
- source: id,
- includeContent: true
- })
+ return {
+ code: s.toString(),
+ map: s.generateMap({
+ source: id,
+ includeContent: true
+ })
+ }
}
}
- }
+ ]
})
diff --git a/test/unit-tests/id-consistency/id-consistency.test.ts b/test/unit-tests/id-consistency/id-consistency.test.ts
index 41195d2a..74ca0760 100644
--- a/test/unit-tests/id-consistency/id-consistency.test.ts
+++ b/test/unit-tests/id-consistency/id-consistency.test.ts
@@ -1,7 +1,7 @@
import * as path from 'path'
import { it, describe, expect, vi, afterEach, Mock } from 'vitest'
-import { build } from '../utils'
-import { createUnplugin, UnpluginOptions } from 'unplugin'
+import { build, toArray } from '../utils'
+import { createUnplugin, UnpluginOptions, VitePlugin } from 'unplugin'
const entryFilePath = path.resolve(__dirname, './test-src/entry.js')
const externals = ['path']
@@ -73,10 +73,12 @@ describe('id parameter should be consistent accross hooks and plugins', () => {
mockTransformHook,
mockLoadHook
).vite
+ // we need to define `enforce` here for the plugin to be run
+ const plugins = toArray(plugin()).map((plugin): VitePlugin => ({ ...plugin, enforce: 'pre' }))
await build.vite({
clearScreen: false,
- plugins: [{ ...plugin(), enforce: 'pre' }], // we need to define `enforce` here for the plugin to be run
+ plugins: [plugins],
build: {
lib: {
entry: entryFilePath,
diff --git a/test/unit-tests/resolve-id-external/resolve-id-external.test.ts b/test/unit-tests/resolve-id-external/resolve-id-external.test.ts
index 9bc11edd..d179ca92 100644
--- a/test/unit-tests/resolve-id-external/resolve-id-external.test.ts
+++ b/test/unit-tests/resolve-id-external/resolve-id-external.test.ts
@@ -1,7 +1,7 @@
import * as path from 'path'
import { it, describe, expect, vi, afterEach } from 'vitest'
-import { build } from '../utils'
-import { createUnplugin } from 'unplugin'
+import { build, toArray } from '../utils'
+import { VitePlugin, createUnplugin } from 'unplugin'
const entryFilePath = path.resolve(__dirname, './test-src/entry.js')
const externals = ['path']
@@ -45,9 +45,11 @@ describe('load hook should not be called when resolveId hook returned `external:
it('vite', async () => {
const plugin = createMockedUnplugin().vite
+ // we need to define `enforce` here for the plugin to be run
+ const plugins = toArray(plugin()).map((plugin): VitePlugin => ({ ...plugin, enforce: 'pre' }))
await build.vite({
clearScreen: false,
- plugins: [{ ...plugin(), enforce: 'pre' }], // we need to define `enforce` here for the plugin to be run
+ plugins: [plugins],
build: {
lib: {
entry: entryFilePath,
diff --git a/test/unit-tests/resolve-id/resolve-id.test.ts b/test/unit-tests/resolve-id/resolve-id.test.ts
index 9c57030f..3831cb8e 100644
--- a/test/unit-tests/resolve-id/resolve-id.test.ts
+++ b/test/unit-tests/resolve-id/resolve-id.test.ts
@@ -1,7 +1,7 @@
import * as path from 'path'
import { it, describe, expect, vi, afterEach, Mock } from 'vitest'
-import { build } from '../utils'
-import { createUnplugin, UnpluginOptions } from 'unplugin'
+import { build, toArray } from '../utils'
+import { createUnplugin, UnpluginOptions, VitePlugin } from 'unplugin'
function createUnpluginWithCallback (resolveIdCallback: UnpluginOptions['resolveId']) {
return createUnplugin(() => ({
@@ -47,10 +47,12 @@ describe('resolveId hook', () => {
it('vite', async () => {
const mockResolveIdHook = vi.fn(() => undefined)
const plugin = createUnpluginWithCallback(mockResolveIdHook).vite
+ // we need to define `enforce` here for the plugin to be run
+ const plugins = toArray(plugin()).map((plugin): VitePlugin => ({ ...plugin, enforce: 'pre' }))
await build.vite({
clearScreen: false,
- plugins: [{ ...plugin(), enforce: 'pre' }], // we need to define `enforce` here for the plugin to be run
+ plugins: [plugins],
build: {
lib: {
entry: path.resolve(__dirname, 'test-src/entry.js'),
diff --git a/test/unit-tests/utils.ts b/test/unit-tests/utils.ts
index c8522f30..74647c60 100644
--- a/test/unit-tests/utils.ts
+++ b/test/unit-tests/utils.ts
@@ -3,6 +3,8 @@ import * as rollup from 'rollup'
import * as webpack from 'webpack'
import * as esbuild from 'esbuild'
+export * from '../../src/utils'
+
export const viteBuild = vite.build
export const rollupBuild = rollup.rollup
export const esbuildBuild = esbuild.build