Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: filters #494

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 23 additions & 17 deletions docs/guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,9 @@ export default defineConfig({
| [`enforce`](https://vite.dev/guide/api-plugin.html#plugin-ordering) | ❌ <sup>1</sup> | ✅ | ✅ | ❌ <sup>1</sup> | ✅ | ✅ | ✅ |
| [`buildStart`](https://rollupjs.org/plugin-development/#buildstart) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| [`resolveId`](https://rollupjs.org/plugin-development/#resolveid) | ✅ | ✅ | ✅ | ✅ | ✅ <sup>5</sup> | ✅ | ✅ |
| `loadInclude`<sup>2</sup> | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| ~~`loadInclude`~~<sup>2</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> | ✅ | ✅ | ✅ |
| [`watchChange`](https://rollupjs.org/plugin-development/#watchchange) | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
| [`buildEnd`](https://rollupjs.org/plugin-development/#buildend) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Expand All @@ -209,11 +209,14 @@ export default defineConfig({
::: details Notice

1. Rollup and esbuild do not support using `enforce` to control the order of plugins. Users need to maintain the order manually.
2. webpack's id filter is outside of loader logic; an additional hook is needed for better perf on webpack. In Rollup and Vite, this hook has been polyfilled to match the behaviors. See for the following usage examples.
2. Webpack's id filter is outside of loader logic; an additional hook is needed for better performance on Webpack and Rolldown.
However, it is now deprecated. Please use `transform/load/resolveId.filter` instead.
In Rollup, this hook has been polyfilled to match the behaviors. See the following usage examples for reference.
3. Although esbuild can handle both JavaScript and CSS and many other file formats, you can only return JavaScript in `load` and `transform` results.
4. Currently, `writeBundle` is only serves as a hook for the timing. It doesn't pass any arguments.
5. Rspack supports `resolveId` with a minimum required version of v1.0.0-alpha.1.
:::

:::

### Usage

Expand All @@ -227,14 +230,14 @@ export interface Options {

export const unpluginFactory: UnpluginFactory<Options | undefined> = options => ({
name: 'unplugin-starter',
// webpack's id filter is outside of loader logic,
// an additional hook is needed for better perf on webpack
transformInclude(id) {
return id.endsWith('main.ts')
},
// just like rollup transform
transform(code) {
return code.replace(/<template>/, '<template><div>Injected</div>')
transform: {
// an additional hook is needed for better perf on webpack and rolldown
filter: {
id: /main\.ts$/
},
handler(code) {
return code.replace(/<template>/, '<template><div>Injected</div>')
},
},
// more hooks coming
})
Expand Down Expand Up @@ -334,11 +337,14 @@ export const unpluginFactory: UnpluginFactory<Options | undefined> = (
console.log(meta.framework) // vite rollup webpack esbuild rspack...
return {
name: 'unplugin-starter',
transform(code) {
return code.replace(/<template>/, '<template><div>Injected</div>')
},
transformInclude(id) {
return id.endsWith('main.ts')
transform: {
// an additional hook is needed for better perf on webpack and rolldown
filter: {
id: /main\.ts$/
},
handler(code) {
return code.replace(/<template>/, '<template><div>Injected</div>')
},
},
vite: {
// Vite plugin
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
},
"dependencies": {
"acorn": "^8.14.1",
"picomatch": "^4.0.2",
"webpack-virtual-modules": "^0.6.2"
},
"devDependencies": {
Expand All @@ -55,6 +56,7 @@
"@rspack/core": "^1.3.4",
"@types/fs-extra": "^11.0.4",
"@types/node": "^22.14.0",
"@types/picomatch": "^3.0.2",
"ansis": "^3.17.0",
"bumpp": "^10.1.0",
"esbuild": "^0.25.2",
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 36 additions & 24 deletions src/esbuild/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
} from '../types'
import fs from 'node:fs'
import path from 'node:path'
import { normalizeObjectHook } from '../utils/filter'
import { toArray } from '../utils/general'
import {
combineSourcemaps,
Expand Down Expand Up @@ -167,20 +168,24 @@ function buildSetup() {

if (plugin.resolveId) {
onResolve({ filter: onResolveFilter }, async (args) => {
if (initialOptions.external?.includes(args.path)) {
const id = args.path
if (initialOptions.external?.includes(id)) {
// 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
return
}

const { errors, warnings, mixedContext }
= createPluginContext(context)
const { handler, filter } = normalizeObjectHook('resolveId', plugin.resolveId!)
if (!filter(id))
return

const { errors, warnings, mixedContext } = createPluginContext(context)

const isEntry = args.kind === 'entry-point'
const result = await plugin.resolveId!.call(
const result = await handler.call(
mixedContext,
args.path,
id,
// We explicitly have this if statement here for consistency with
// the integration of other bundlers.
// Here, `args.importer` is just an empty string on entry files
Expand Down Expand Up @@ -212,26 +217,26 @@ function buildSetup() {

if (plugin.load) {
onLoad({ filter: onLoadFilter }, async (args) => {
const { handler, filter } = normalizeObjectHook('load', plugin.load!)
const id = args.path + (args.suffix || '') // compat for #427

const { errors, warnings, mixedContext }
= createPluginContext(context)
if (plugin.loadInclude && !plugin.loadInclude(id))
return
if (!filter(id))
return

// 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)
const { errors, warnings, mixedContext } = createPluginContext(context)

let code: string | undefined, map: SourceMap | null | undefined
let code: string | undefined
let map: SourceMap | null | undefined

if (plugin.load && (!plugin.loadInclude || plugin.loadInclude(id))) {
const result = await plugin.load.call(mixedContext, id)
if (typeof result === 'string') {
code = result
}
else if (typeof result === 'object' && result !== null) {
code = result.code
map = result.map as any
}
const result = await handler.call(mixedContext, id)
if (typeof result === 'string') {
code = result
}
else if (typeof result === 'object' && result !== null) {
code = result.code
map = result.map as any
}

if (code === undefined)
Expand All @@ -240,6 +245,10 @@ function buildSetup() {
if (map)
code = processCodeWithSourceMap(map, code)

// 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)

return {
contents: code,
errors,
Expand All @@ -253,17 +262,20 @@ function buildSetup() {

if (plugin.transform) {
onTransform({ filter: onLoadFilter }, async (args) => {
const id = args.path + (args.suffix || '')
const { handler, filter } = normalizeObjectHook('transform', plugin.transform!)

const id = args.path + (args.suffix || '')
if (plugin.transformInclude && !plugin.transformInclude(id))
return
let code = await args.getContents()
if (!filter(id, code))
return

const { mixedContext, errors, warnings } = createPluginContext(context)
const resolveDir = path.dirname(args.path)

let code = await args.getContents()
let map: SourceMap | null | undefined
const result = await plugin.transform!.call(mixedContext, code, id)
const result = await handler.call(mixedContext, code, id)
if (typeof result === 'string') {
code = result
}
Expand Down
37 changes: 20 additions & 17 deletions src/farm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type { JsPluginExtended, WatchChangeEvents } from './utils'

import path from 'node:path'

import { normalizeObjectHook } from '../utils/filter'
import { toArray } from '../utils/general'
import { createFarmContext, unpluginContext } from './context'
import {
Expand Down Expand Up @@ -92,15 +93,21 @@ export function toFarmPlugin(plugin: UnpluginOptions, options?: Record<string, a
const resolvedIdPath = path.resolve(
params.importer ?? '',
)
const id = decodeStr(params.source)

const { handler, filter } = normalizeObjectHook('resolveId', _resolveId)
if (!filter(id))
return null

let isEntry = false
if (isObject(params.kind) && 'entry' in params.kind) {
const kindWithEntry = params.kind as { entry: string }
isEntry = kindWithEntry.entry === 'index'
}
const farmContext = createFarmContext(context!, resolvedIdPath)
const resolveIdResult = await _resolveId.call(
const resolveIdResult = await handler.call(
Object.assign(unpluginContext(context), farmContext),
decodeStr(params.source),
id,
resolvedIdPath ?? null,
{ isEntry },
)
Expand Down Expand Up @@ -141,20 +148,17 @@ export function toFarmPlugin(plugin: UnpluginOptions, options?: Record<string, a
context,
): Promise<PluginLoadHookResult | null> {
const resolvedPath = decodeStr(params.resolvedPath)

const id = appendQuery(resolvedPath, params.query)

const loader = formatTransformModuleType(id)

const shouldLoadInclude
= plugin.loadInclude?.(id)

if (!shouldLoadInclude)
if (plugin.loadInclude && !plugin.loadInclude?.(id))
return null
const { handler, filter } = normalizeObjectHook('load', _load)
if (!filter(id))
return null

const farmContext = createFarmContext(context!, id)

const content: TransformResult = await _load.call(
const content: TransformResult = await handler.call(
Object.assign(unpluginContext(context!), farmContext),
id,
)
Expand All @@ -178,19 +182,18 @@ export function toFarmPlugin(plugin: UnpluginOptions, options?: Record<string, a
context: CompilationContext,
) {
const resolvedPath = decodeStr(params.resolvedPath)

const id = appendQuery(resolvedPath, params.query)

const loader = formatTransformModuleType(id)

const shouldTransformInclude
= plugin.transformInclude?.(id)
const farmContext = createFarmContext(context, id)
if (plugin.transformInclude && !plugin.transformInclude(id))
return null

if (!shouldTransformInclude)
const { handler, filter } = normalizeObjectHook('transform', _transform)
if (!filter(id, params.content))
return null

const resource: TransformResult = await _transform.call(
const farmContext = createFarmContext(context, id)
const resource: TransformResult = await handler.call(
Object.assign(unpluginContext(context), farmContext),
params.content,
id,
Expand Down
Loading