如何让esbuild允许多个插件处理匹配相同过滤规则的文件
嘿,我懂你的需求——你想让多个esbuild插件都能处理符合同一过滤规则的文件,虽然esbuild的原生设计确实没把这个作为核心场景,但咱们有靠谱的 workaround 可以实现这个需求。
先说说核心问题:esbuild的onLoad钩子默认是“抢占式”的——如果某个插件在onLoad里返回了contents字段,后续针对该文件的onLoad钩子就不会再执行了。所以要让多个插件都能插手处理,咱们可以从两个方向入手:
方案一:用onTransform钩子替代(推荐)
onTransform钩子的特性就是按注册顺序依次执行所有匹配的插件,不管前一个插件有没有修改内容。它会在所有onLoad完成之后触发,刚好适合多个插件对同一文件做链式处理。
举个例子,假设你两个插件都要处理.txt文件:
package main import ( "strings" "github.com/evanw/esbuild/pkg/api" ) // 第一个插件:给文本加前缀 func PluginOne() api.Plugin { return api.Plugin{ Name: "plugin-one", Setup: func(build api.PluginBuild) { build.OnTransform(api.OnTransformOptions{ Filter: "\\.txt$", }, func(args api.OnTransformArgs) (api.OnTransformResult, error) { modified := "[PluginOne] " + args.Input return api.OnTransformResult{ Contents: &modified, }, nil }) }, } } // 第二个插件:给文本加后缀 func PluginTwo() api.Plugin { return api.Plugin{ Name: "plugin-two", Setup: func(build api.PluginBuild) { build.OnTransform(api.OnTransformOptions{ Filter: "\\.txt$", }, func(args api.OnTransformArgs) (api.OnTransformResult, error) { modified := *args.Contents + " [PluginTwo]" return api.OnTransformResult{ Contents: &modified, }, nil }) }, } } func main() { result := api.Build(api.BuildOptions{ EntryPoints: []string{"test.txt"}, Outfile: "out.txt", Plugins: []api.Plugin{PluginOne(), PluginTwo()}, }) if len(result.Errors) > 0 { panic(result.Errors) } }
这样处理后,test.txt的内容会先被PluginOne加上前缀,再被PluginTwo加上后缀,最终输出的out.txt会包含两个插件的处理结果。
方案二:修改onLoad的处理逻辑(适合必须用onLoad的场景)
如果因为某些原因必须用onLoad,那咱们可以让前面的插件不直接返回最终内容,而是把处理后的内容存在build的PluginData里,或者修改文件的namespace让后续插件能继续匹配。
比如:
import "os" func PluginOne() api.Plugin { return api.Plugin{ Name: "plugin-one", Setup: func(build api.PluginBuild) { build.OnLoad(api.OnLoadOptions{ Filter: "\\.txt$", }, func(args api.OnLoadArgs) (api.OnLoadResult, error) { // 读取原始内容 content, err := os.ReadFile(args.Path) if err != nil { return api.OnLoadResult{}, err } // 处理内容 modified := "[PluginOne] " + string(content) // 把处理后的内容存在PluginData里,传给下一个插件 return api.OnLoadResult{ PluginData: modified, // 设置自定义命名空间,让下一个插件能精准匹配 Namespace: "processed-by-plugin-one", }, nil }) }, } } func PluginTwo() api.Plugin { return api.Plugin{ Name: "plugin-two", Setup: func(build api.PluginBuild) { // 匹配被PluginOne处理过的文件(指定命名空间) build.OnLoad(api.OnLoadOptions{ Filter: "\\.txt$", Namespace: "processed-by-plugin-one", }, func(args api.OnLoadArgs) (api.OnLoadResult, error) { // 从PluginData拿到上一个插件处理后的内容 prevContent := args.PluginData.(string) // 继续处理 finalContent := prevContent + " [PluginTwo]" return api.OnLoadResult{ Contents: &finalContent, }, nil }) }, } }
这种方式需要手动管理文件的处理状态,相对麻烦一些,所以优先推荐用onTransform的方案。
最后总结一下:如果是对文件内容做链式修改,onTransform是最省心的选择;如果涉及到文件加载阶段的特殊处理(比如自定义读取逻辑),就用onLoad配合Namespace和PluginData来传递状态,让多个插件依次处理同一个文件。
备注:内容来源于stack exchange,提问作者Mohammed Al Ameen

