90 lines
2.9 KiB
Go
90 lines
2.9 KiB
Go
package webbundler
|
|
|
|
// defaultExportShimPlugin bridges two ESM-strictness mismatches that
|
|
// existed in the previous custom bundler:
|
|
//
|
|
// 1. `export default function Name` also emits `export { Name }` so
|
|
// callers using `import { Name } from "..."` resolve correctly.
|
|
// 2. If the source declares `export function/class/const Name` (or
|
|
// `Name`) where Name matches the file's basename, synthesize
|
|
// `export { Name as default }` so callers using `import Name from
|
|
// "..."` resolve correctly. The old bundler made every IIFE
|
|
// return value available as both the named symbol AND the default
|
|
// import; standard ESM does not, so esbuild needs the shim until
|
|
// callers migrate to named imports throughout.
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
esbuild "github.com/evanw/esbuild/pkg/api"
|
|
)
|
|
|
|
func defaultExportShimPlugin() esbuild.Plugin {
|
|
return esbuild.Plugin{
|
|
Name: "default-export-shim",
|
|
Setup: func(b esbuild.PluginBuild) {
|
|
b.OnLoad(esbuild.OnLoadOptions{Filter: `\.(js|ts)$`}, func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
|
|
raw, err := os.ReadFile(args.Path)
|
|
if err != nil {
|
|
return esbuild.OnLoadResult{}, err
|
|
}
|
|
out := addNamedExportForDefault(string(raw))
|
|
out = addDefaultExportForFilenameMatch(out, args.Path)
|
|
loader := esbuild.LoaderJS
|
|
if strings.HasSuffix(args.Path, ".ts") {
|
|
loader = esbuild.LoaderTS
|
|
}
|
|
return esbuild.OnLoadResult{Contents: &out, Loader: loader}, nil
|
|
})
|
|
},
|
|
}
|
|
}
|
|
|
|
var reFilenameMatchExport = regexp.MustCompile(`(?m)^export\s+(?:async\s+)?(?:function|class|const|let|var)\s+(\w+)`)
|
|
var reExistingDefault = regexp.MustCompile(`(?m)^export\s+default\b`)
|
|
|
|
// addDefaultExportForFilenameMatch synthesizes `export { Name as
|
|
// default }` if the source declares a top-level export whose name
|
|
// matches the file's basename and the file does not already have a
|
|
// default export. This preserves the old bundler's behavior where
|
|
// `import Foo from "./Foo.js"` resolved against `export function
|
|
// Foo(...)`.
|
|
func addDefaultExportForFilenameMatch(src, path string) string {
|
|
if reExistingDefault.MatchString(src) {
|
|
return src
|
|
}
|
|
base := filepath.Base(path)
|
|
base = strings.TrimSuffix(base, filepath.Ext(base))
|
|
if base == "" {
|
|
return src
|
|
}
|
|
for _, m := range reFilenameMatchExport.FindAllStringSubmatch(src, -1) {
|
|
if m[1] == base {
|
|
return src + "\nexport { " + base + " as default };\n"
|
|
}
|
|
}
|
|
return src
|
|
}
|
|
|
|
var reExportDefaultDecl = regexp.MustCompile(`(?m)^export\s+default\s+(?:async\s+)?(?:function|class)\s+(\w+)`)
|
|
|
|
func addNamedExportForDefault(src string) string {
|
|
matches := reExportDefaultDecl.FindAllStringSubmatch(src, -1)
|
|
if len(matches) == 0 {
|
|
return src
|
|
}
|
|
names := make([]string, 0, len(matches))
|
|
seen := map[string]bool{}
|
|
for _, m := range matches {
|
|
if seen[m[1]] {
|
|
continue
|
|
}
|
|
seen[m[1]] = true
|
|
names = append(names, m[1])
|
|
}
|
|
return src + "\nexport { " + strings.Join(names, ", ") + " };\n"
|
|
}
|