Skip to content

Commit

Permalink
Support browser field (close #450)
Browse files Browse the repository at this point in the history
  • Loading branch information
ije committed Dec 27, 2022
1 parent 29e6b79 commit 612b9df
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 20 deletions.
58 changes: 45 additions & 13 deletions server/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ func (task *BuildTask) build(tracing *stringSet) (esm *ESM, err error) {
externalDeps := newStringSet()
extraExternal := newStringSet()
esmResolverPlugin := api.Plugin{
Name: "esm.sh-resolver",
Name: "esm-resolver",
Setup: func(build api.PluginBuild) {
build.OnResolve(
api.OnResolveOptions{Filter: ".*"},
Expand All @@ -268,6 +268,29 @@ func (task *BuildTask) build(tracing *stringSet) (esm *ESM, err error) {
// resolve nodejs builtin modules like `node:path`
specifier = strings.TrimPrefix(specifier, "node:")

// use `browser` field
if len(npm.Browser) > 0 && task.Target != "deno" && task.Target != "node" {
spec := specifier
if strings.HasPrefix(specifier, "./") || strings.HasPrefix(specifier, "../") || specifier == ".." {
fullpath := path.Join(path.Dir(args.Importer), specifier)
// in macOS, the dir `/private/var/` is equal to `/var/`
if strings.HasPrefix(fullpath, "/private/var/") {
fullpath = strings.TrimPrefix(fullpath, "/private")
}
spec = "." + strings.TrimPrefix(fullpath, path.Join(task.wd, "node_modules", npm.Name))
}
if name, ok := npm.Browser[spec]; ok {
if name == "" {
// browser exclude
return api.OnResolveResult{Namespace: "browser-exclude"}, nil
} else if strings.HasPrefix(name, "./") {
specifier = path.Join(task.wd, "node_modules", npm.Name, name)
} else {
specifier = name
}
}
}

// use `?alias` query
if len(task.alias) > 0 {
if name, ok := task.alias[specifier]; ok {
Expand Down Expand Up @@ -317,14 +340,14 @@ func (task *BuildTask) build(tracing *stringSet) (esm *ESM, err error) {
// splits modules based on the `exports` defines in package.json,
// see https://nodejs.org/api/packages.html
if strings.HasPrefix(specifier, "./") || strings.HasPrefix(specifier, "../") || specifier == ".." {
resolvedPath := path.Join(path.Dir(args.Importer), specifier)
fullpath := path.Join(path.Dir(args.Importer), specifier)
// in macOS, the dir `/private/var/` is equal to `/var/`
if strings.HasPrefix(resolvedPath, "/private/var/") {
resolvedPath = strings.TrimPrefix(resolvedPath, "/private")
if strings.HasPrefix(fullpath, "/private/var/") {
fullpath = strings.TrimPrefix(fullpath, "/private")
}
specInPkg := "." + strings.TrimPrefix(resolvedPath, path.Join(task.wd, "node_modules", npm.Name))
spec := "." + strings.TrimPrefix(fullpath, path.Join(task.wd, "node_modules", npm.Name))
// bundle {pkgName}/{pkgName}.js
if specInPkg == fmt.Sprintf("./%s.js", task.Pkg.Name) {
if spec == fmt.Sprintf("./%s.js", task.Pkg.Name) {
return api.OnResolveResult{}, nil
}
v, ok := npm.DefinedExports.(map[string]interface{})
Expand All @@ -335,16 +358,16 @@ func (task *BuildTask) build(tracing *stringSet) (esm *ESM, err error) {
for _, value := range m {
s, ok := value.(string)
if ok && s != "" {
match := specInPkg == s || specInPkg+".js" == s || specInPkg+".mjs" == s
match := spec == s || spec+".js" == s || spec+".mjs" == s
if !match {
if a := strings.Split(s, "*"); len(a) == 2 {
prefix := a[0]
suffix := a[1]
if (strings.HasPrefix(specInPkg, prefix)) &&
(strings.HasSuffix(specInPkg, suffix) ||
strings.HasSuffix(specInPkg+".js", suffix) ||
strings.HasSuffix(specInPkg+".mjs", suffix)) {
matchName := strings.TrimPrefix(strings.TrimSuffix(specInPkg, suffix), prefix)
if (strings.HasPrefix(spec, prefix)) &&
(strings.HasSuffix(spec, suffix) ||
strings.HasSuffix(spec+".js", suffix) ||
strings.HasSuffix(spec+".mjs", suffix)) {
matchName := strings.TrimPrefix(strings.TrimSuffix(spec, suffix), prefix)
export = strings.Replace(export, "*", matchName, -1)
match = true
}
Expand Down Expand Up @@ -400,6 +423,15 @@ func (task *BuildTask) build(tracing *stringSet) (esm *ESM, err error) {
},
)

// for browser exclude
build.OnLoad(
api.OnLoadOptions{Filter: ".*", Namespace: "browser-exclude"},
func(args api.OnLoadArgs) (ret api.OnLoadResult, err error) {
contents := "export default undefined;"
return api.OnLoadResult{Contents: &contents, Loader: api.LoaderJS}, nil
},
)

// workaround for prisma build
if npm.Name == "prisma" {
build.OnLoad(
Expand Down Expand Up @@ -429,7 +461,7 @@ func (task *BuildTask) build(tracing *stringSet) (esm *ESM, err error) {
},
}
esmBundlerPlugin := api.Plugin{
Name: "esm.sh-bundler",
Name: "esm-bundler",
Setup: func(build api.PluginBuild) {
build.OnResolve(
api.OnResolveOptions{Filter: ".*"},
Expand Down
9 changes: 5 additions & 4 deletions server/esm.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,11 +329,12 @@ func fixNpmPackage(wd string, np *NpmPackage, target string, isDev bool) *NpmPac
}
}

if np.Browser != "" && fileExists(path.Join(nmDir, np.Name, np.Browser)) {
isEsm, _, _ := parseJS(path.Join(nmDir, np.Name, np.Browser))
browserMain := np.Browser["."]
if browserMain != "" && fileExists(path.Join(nmDir, np.Name, browserMain)) {
isEsm, _, _ := parseJS(path.Join(nmDir, np.Name, browserMain))
if isEsm {
log.Infof("%s@%s: use `browser` field as module: %s", np.Name, np.Version, np.Browser)
np.Module = np.Browser
log.Infof("%s@%s: use `browser` field as module: %s", np.Name, np.Version, browserMain)
np.Module = browserMain
}
}

Expand Down
22 changes: 19 additions & 3 deletions server/nodejs.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,6 @@ type NpmPackageTemp struct {
PeerDependencies map[string]string `json:"peerDependencies,omitempty"`
Imports map[string]interface{} `json:"imports,omitempty"`
DefinedExports interface{} `json:"exports,omitempty"`
// todo: support `browser` field
}

func (a *StringOrMap) MainValue() string {
Expand All @@ -214,17 +213,34 @@ func (a *StringOrMap) MainValue() string {
}

func (a *NpmPackageTemp) ToNpmPackage() *NpmPackage {
browser := map[string]string{}
if a.Browser.Value != "" {
browser["."] = a.Browser.Value
}
if a.Browser.Map != nil {
for k, v := range a.Browser.Map {
s, isStr := v.(string)
if isStr {
browser[k] = s
} else {
b, ok := v.(bool)
if ok && !b {
browser[k] = ""
}
}
}
}
return &NpmPackage{
Name: a.Name,
Version: a.Version,
Type: a.Type,
Main: a.Main,
Browser: a.Browser.MainValue(),
Module: a.Module.MainValue(),
ES2015: a.ES2015.MainValue(),
JsNextMain: a.JsNextMain,
Types: a.Types,
Typings: a.Typings,
Browser: browser,
Dependencies: a.Dependencies,
PeerDependencies: a.PeerDependencies,
Imports: a.Imports,
Expand All @@ -238,12 +254,12 @@ type NpmPackage struct {
Version string
Type string
Main string
Browser string
Module string
ES2015 string
JsNextMain string
Types string
Typings string
Browser map[string]string
Dependencies map[string]string
PeerDependencies map[string]string
Imports map[string]interface{}
Expand Down

0 comments on commit 612b9df

Please sign in to comment.