mirror of
https://github.com/zhigang1992/esbuild.git
synced 2026-04-30 10:22:49 +08:00
add a plugin api (#111)
This commit is contained in:
@@ -102,7 +102,8 @@ const (
|
||||
type Loader uint8
|
||||
|
||||
const (
|
||||
LoaderJS Loader = iota
|
||||
LoaderNone Loader = iota
|
||||
LoaderJS
|
||||
LoaderJSX
|
||||
LoaderTS
|
||||
LoaderTSX
|
||||
@@ -113,6 +114,7 @@ const (
|
||||
LoaderFile
|
||||
LoaderBinary
|
||||
LoaderCSS
|
||||
LoaderDefault
|
||||
)
|
||||
|
||||
type Platform uint8
|
||||
@@ -148,11 +150,12 @@ type Engine struct {
|
||||
}
|
||||
|
||||
type Location struct {
|
||||
File string
|
||||
Line int // 1-based
|
||||
Column int // 0-based, in bytes
|
||||
Length int // in bytes
|
||||
LineText string
|
||||
File string
|
||||
Namespace string
|
||||
Line int // 1-based
|
||||
Column int // 0-based, in bytes
|
||||
Length int // in bytes
|
||||
LineText string
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
@@ -230,6 +233,7 @@ type BuildOptions struct {
|
||||
EntryPoints []string
|
||||
Stdin *StdinOptions
|
||||
Write bool
|
||||
Plugins []Plugin
|
||||
}
|
||||
|
||||
type StdinOptions struct {
|
||||
@@ -297,3 +301,60 @@ type TransformResult struct {
|
||||
func Transform(input string, options TransformOptions) TransformResult {
|
||||
return transformImpl(input, options)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Plugin API
|
||||
|
||||
type Plugin struct {
|
||||
Name string
|
||||
Setup func(PluginBuild)
|
||||
}
|
||||
|
||||
type PluginBuild interface {
|
||||
OnResolve(options OnResolveOptions, callback func(OnResolveArgs) (OnResolveResult, error))
|
||||
OnLoad(options OnLoadOptions, callback func(OnLoadArgs) (OnLoadResult, error))
|
||||
}
|
||||
|
||||
type OnResolveOptions struct {
|
||||
Filter string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type OnResolveArgs struct {
|
||||
Path string
|
||||
Importer string
|
||||
Namespace string
|
||||
ResolveDir string
|
||||
}
|
||||
|
||||
type OnResolveResult struct {
|
||||
PluginName string
|
||||
|
||||
Errors []Message
|
||||
Warnings []Message
|
||||
|
||||
Path string
|
||||
External bool
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type OnLoadOptions struct {
|
||||
Filter string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type OnLoadArgs struct {
|
||||
Path string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type OnLoadResult struct {
|
||||
PluginName string
|
||||
|
||||
Errors []Message
|
||||
Warnings []Message
|
||||
|
||||
Contents *string
|
||||
ResolveDir string
|
||||
Loader Loader
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -103,6 +104,8 @@ func validateASCIIOnly(value Charset) bool {
|
||||
|
||||
func validateLoader(value Loader) config.Loader {
|
||||
switch value {
|
||||
case LoaderNone:
|
||||
return config.LoaderNone
|
||||
case LoaderJS:
|
||||
return config.LoaderJS
|
||||
case LoaderJSX:
|
||||
@@ -125,6 +128,8 @@ func validateLoader(value Loader) config.Loader {
|
||||
return config.LoaderBinary
|
||||
case LoaderCSS:
|
||||
return config.LoaderCSS
|
||||
case LoaderDefault:
|
||||
return config.LoaderDefault
|
||||
default:
|
||||
panic("Invalid loader")
|
||||
}
|
||||
@@ -386,20 +391,21 @@ func validateOutputExtensions(log logger.Log, outExtensions map[string]string) m
|
||||
return result
|
||||
}
|
||||
|
||||
func messagesOfKind(kind logger.MsgKind, msgs []logger.Msg) []Message {
|
||||
func convertMessagesToPublic(kind logger.MsgKind, msgs []logger.Msg) []Message {
|
||||
var filtered []Message
|
||||
for _, msg := range msgs {
|
||||
if msg.Kind == kind {
|
||||
var location *Location
|
||||
loc := msg.Location
|
||||
|
||||
if msg.Location != nil {
|
||||
loc := msg.Location
|
||||
if loc != nil {
|
||||
location = &Location{
|
||||
File: loc.File,
|
||||
Line: loc.Line,
|
||||
Column: loc.Column,
|
||||
Length: loc.Length,
|
||||
LineText: loc.LineText,
|
||||
File: loc.File,
|
||||
Namespace: loc.Namespace,
|
||||
Line: loc.Line,
|
||||
Column: loc.Column,
|
||||
Length: loc.Length,
|
||||
LineText: loc.LineText,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,6 +418,35 @@ func messagesOfKind(kind logger.MsgKind, msgs []logger.Msg) []Message {
|
||||
return filtered
|
||||
}
|
||||
|
||||
func convertMessagesToInternal(msgs []logger.Msg, kind logger.MsgKind, messages []Message) []logger.Msg {
|
||||
for _, message := range messages {
|
||||
var location *logger.MsgLocation
|
||||
loc := message.Location
|
||||
|
||||
if loc != nil {
|
||||
namespace := loc.Namespace
|
||||
if namespace == "" {
|
||||
namespace = "file"
|
||||
}
|
||||
location = &logger.MsgLocation{
|
||||
File: loc.File,
|
||||
Namespace: namespace,
|
||||
Line: loc.Line,
|
||||
Column: loc.Column,
|
||||
Length: loc.Length,
|
||||
LineText: loc.LineText,
|
||||
}
|
||||
}
|
||||
|
||||
msgs = append(msgs, logger.Msg{
|
||||
Kind: kind,
|
||||
Text: message.Text,
|
||||
Location: location,
|
||||
})
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Build API
|
||||
|
||||
@@ -538,6 +573,8 @@ func buildImpl(buildOpts BuildOptions) BuildResult {
|
||||
log.AddError(nil, logger.Loc{}, "Splitting currently only works with the \"esm\" format")
|
||||
}
|
||||
|
||||
loadPlugins(&options, realFS, log, buildOpts.Plugins)
|
||||
|
||||
var outputFiles []OutputFile
|
||||
|
||||
// Stop now if there were errors
|
||||
@@ -608,8 +645,8 @@ func buildImpl(buildOpts BuildOptions) BuildResult {
|
||||
|
||||
msgs := log.Done()
|
||||
return BuildResult{
|
||||
Errors: messagesOfKind(logger.Error, msgs),
|
||||
Warnings: messagesOfKind(logger.Warning, msgs),
|
||||
Errors: convertMessagesToPublic(logger.Error, msgs),
|
||||
Warnings: convertMessagesToPublic(logger.Warning, msgs),
|
||||
OutputFiles: outputFiles,
|
||||
}
|
||||
}
|
||||
@@ -656,6 +693,14 @@ func transformImpl(input string, transformOpts TransformOptions) TransformResult
|
||||
}
|
||||
}
|
||||
|
||||
// Apply default values
|
||||
if transformOpts.Sourcefile == "" {
|
||||
transformOpts.Sourcefile = "<stdin>"
|
||||
}
|
||||
if transformOpts.Loader == LoaderNone {
|
||||
transformOpts.Loader = LoaderJS
|
||||
}
|
||||
|
||||
// Convert and validate the transformOpts
|
||||
jsFeatures, cssFeatures := validateFeatures(log, transformOpts.Target, transformOpts.Engines)
|
||||
options := config.Options{
|
||||
@@ -728,9 +773,145 @@ func transformImpl(input string, transformOpts TransformOptions) TransformResult
|
||||
|
||||
msgs := log.Done()
|
||||
return TransformResult{
|
||||
Errors: messagesOfKind(logger.Error, msgs),
|
||||
Warnings: messagesOfKind(logger.Warning, msgs),
|
||||
Errors: convertMessagesToPublic(logger.Error, msgs),
|
||||
Warnings: convertMessagesToPublic(logger.Warning, msgs),
|
||||
Code: code,
|
||||
Map: sourceMap,
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Plugin API
|
||||
|
||||
type pluginImpl struct {
|
||||
log logger.Log
|
||||
fs fs.FS
|
||||
plugin config.Plugin
|
||||
}
|
||||
|
||||
func (impl *pluginImpl) OnResolve(options OnResolveOptions, callback func(OnResolveArgs) (OnResolveResult, error)) {
|
||||
filter, err := config.CompileFilterForPlugin(impl.plugin.Name, "OnResolve", options.Filter)
|
||||
if filter == nil {
|
||||
impl.log.AddError(nil, logger.Loc{}, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
impl.plugin.OnResolve = append(impl.plugin.OnResolve, config.OnResolve{
|
||||
Name: impl.plugin.Name,
|
||||
Filter: filter,
|
||||
Namespace: options.Namespace,
|
||||
Callback: func(args config.OnResolveArgs) (result config.OnResolveResult) {
|
||||
response, err := callback(OnResolveArgs{
|
||||
Path: args.Path,
|
||||
Importer: args.Importer.Text,
|
||||
Namespace: args.Importer.Namespace,
|
||||
ResolveDir: args.ResolveDir,
|
||||
})
|
||||
result.PluginName = response.PluginName
|
||||
|
||||
if err != nil {
|
||||
result.ThrownError = err
|
||||
return
|
||||
}
|
||||
|
||||
if response.Namespace == "" {
|
||||
response.Namespace = "file"
|
||||
}
|
||||
result.Path = logger.Path{Text: response.Path, Namespace: response.Namespace}
|
||||
result.External = response.External
|
||||
|
||||
// Convert log messages
|
||||
if len(response.Errors)+len(response.Warnings) > 0 {
|
||||
msgs := make(sortableMsgs, 0, len(response.Errors)+len(response.Warnings))
|
||||
msgs = convertMessagesToInternal(msgs, logger.Error, response.Errors)
|
||||
msgs = convertMessagesToInternal(msgs, logger.Warning, response.Warnings)
|
||||
sort.Sort(msgs)
|
||||
result.Msgs = msgs
|
||||
}
|
||||
return
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (impl *pluginImpl) OnLoad(options OnLoadOptions, callback func(OnLoadArgs) (OnLoadResult, error)) {
|
||||
filter, err := config.CompileFilterForPlugin(impl.plugin.Name, "OnLoad", options.Filter)
|
||||
if filter == nil {
|
||||
impl.log.AddError(nil, logger.Loc{}, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
impl.plugin.OnLoad = append(impl.plugin.OnLoad, config.OnLoad{
|
||||
Filter: filter,
|
||||
Namespace: options.Namespace,
|
||||
Callback: func(args config.OnLoadArgs) (result config.OnLoadResult) {
|
||||
response, err := callback(OnLoadArgs{
|
||||
Path: args.Path.Text,
|
||||
Namespace: args.Path.Namespace,
|
||||
})
|
||||
result.PluginName = response.PluginName
|
||||
|
||||
if err != nil {
|
||||
result.ThrownError = err
|
||||
return
|
||||
}
|
||||
|
||||
result.Contents = response.Contents
|
||||
result.Loader = validateLoader(response.Loader)
|
||||
if absPath := validatePath(impl.log, impl.fs, response.ResolveDir); absPath != "" {
|
||||
result.AbsResolveDir = absPath
|
||||
}
|
||||
|
||||
// Convert log messages
|
||||
if len(response.Errors)+len(response.Warnings) > 0 {
|
||||
msgs := make(sortableMsgs, 0, len(response.Errors)+len(response.Warnings))
|
||||
msgs = convertMessagesToInternal(msgs, logger.Error, response.Errors)
|
||||
msgs = convertMessagesToInternal(msgs, logger.Warning, response.Warnings)
|
||||
sort.Sort(msgs)
|
||||
result.Msgs = msgs
|
||||
}
|
||||
return
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// This type is just so we can use Go's native sort function
|
||||
type sortableMsgs []logger.Msg
|
||||
|
||||
func (a sortableMsgs) Len() int { return len(a) }
|
||||
func (a sortableMsgs) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
func (a sortableMsgs) Less(i int, j int) bool {
|
||||
ai := a[i].Location
|
||||
aj := a[j].Location
|
||||
if ai == nil || aj == nil {
|
||||
return ai == nil && aj != nil
|
||||
}
|
||||
if ai.File != aj.File {
|
||||
return ai.File < aj.File
|
||||
}
|
||||
if ai.Line != aj.Line {
|
||||
return ai.Line < aj.Line
|
||||
}
|
||||
if ai.Column != aj.Column {
|
||||
return ai.Column < aj.Column
|
||||
}
|
||||
return a[i].Text < a[j].Text
|
||||
}
|
||||
|
||||
func loadPlugins(options *config.Options, fs fs.FS, log logger.Log, plugins []Plugin) {
|
||||
for i, item := range plugins {
|
||||
if item.Name == "" {
|
||||
log.AddError(nil, logger.Loc{}, fmt.Sprintf("Plugin at index %d is missing a name", i))
|
||||
continue
|
||||
}
|
||||
|
||||
impl := &pluginImpl{
|
||||
fs: fs,
|
||||
log: log,
|
||||
plugin: config.Plugin{Name: item.Name},
|
||||
}
|
||||
|
||||
item.Setup(impl)
|
||||
options.Plugins = append(options.Plugins, impl.plugin)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/evanw/esbuild/internal/helpers"
|
||||
"github.com/evanw/esbuild/internal/logger"
|
||||
"github.com/evanw/esbuild/pkg/api"
|
||||
)
|
||||
@@ -188,7 +189,7 @@ func parseOptionsImpl(osArgs []string, buildOpts *api.BuildOptions, transformOpt
|
||||
return fmt.Errorf("Missing \"=\": %q", value)
|
||||
}
|
||||
ext, text := value[:equals], value[equals+1:]
|
||||
loader, err := parseLoader(text)
|
||||
loader, err := helpers.ParseLoader(text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -196,7 +197,7 @@ func parseOptionsImpl(osArgs []string, buildOpts *api.BuildOptions, transformOpt
|
||||
|
||||
case strings.HasPrefix(arg, "--loader="):
|
||||
value := arg[len("--loader="):]
|
||||
loader, err := parseLoader(value)
|
||||
loader, err := helpers.ParseLoader(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -419,36 +420,6 @@ outer:
|
||||
return
|
||||
}
|
||||
|
||||
func parseLoader(text string) (api.Loader, error) {
|
||||
switch text {
|
||||
case "js":
|
||||
return api.LoaderJS, nil
|
||||
case "jsx":
|
||||
return api.LoaderJSX, nil
|
||||
case "ts":
|
||||
return api.LoaderTS, nil
|
||||
case "tsx":
|
||||
return api.LoaderTSX, nil
|
||||
case "css":
|
||||
return api.LoaderCSS, nil
|
||||
case "json":
|
||||
return api.LoaderJSON, nil
|
||||
case "text":
|
||||
return api.LoaderText, nil
|
||||
case "base64":
|
||||
return api.LoaderBase64, nil
|
||||
case "dataurl":
|
||||
return api.LoaderDataURL, nil
|
||||
case "file":
|
||||
return api.LoaderFile, nil
|
||||
case "binary":
|
||||
return api.LoaderBinary, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("Invalid loader: %q (valid: "+
|
||||
"js, jsx, ts, tsx, css, json, text, base64, dataurl, file, binary)", text)
|
||||
}
|
||||
}
|
||||
|
||||
// This returns either BuildOptions, TransformOptions, or an error
|
||||
func parseOptionsForRun(osArgs []string) (*api.BuildOptions, *api.TransformOptions, error) {
|
||||
// If there's an entry point or we're bundling, then we're building
|
||||
|
||||
Reference in New Issue
Block a user