Skip to content

Commit

Permalink
Support external dependencies of dependencies via ?deps(close #165)
Browse files Browse the repository at this point in the history
  • Loading branch information
ije committed Mar 9, 2024
1 parent 9c59490 commit 6a6f349
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 119 deletions.
48 changes: 24 additions & 24 deletions server/build.go
Expand Up @@ -48,7 +48,7 @@ type BuildTask struct {
imports []string
requires [][2]string
smOffset int
subBuilds *stringSet
subBuilds *StringSet
}

func (task *BuildTask) Build() (esm *ESMBuild, err error) {
Expand Down Expand Up @@ -124,7 +124,7 @@ func (task *BuildTask) build() (err error) {
if strings.HasSuffix(task.Pkg.SubModule, ".json") {
nmDir := path.Join(task.wd, "node_modules")
jsonPath := path.Join(nmDir, task.Pkg.Name, task.Pkg.SubModule)
if fileExists(jsonPath) {
if existsFile(jsonPath) {
json, err := os.ReadFile(jsonPath)
if err != nil {
return err
Expand Down Expand Up @@ -274,7 +274,7 @@ func (task *BuildTask) build() (err error) {
if task.Target == "node" {
define = map[string]string{}
}
browserExclude := map[string]*stringSet{}
browserExclude := map[string]*StringSet{}
implicitExternal := newStringSet()

noBundle := task.NoBundle || (npm.SideEffects != nil && npm.SideEffects.Len() > 0)
Expand Down Expand Up @@ -451,20 +451,20 @@ rebuild:
}

// native node modules do not work via http import
if strings.HasSuffix(fullFilepath, ".node") && fileExists(fullFilepath) {
if strings.HasSuffix(fullFilepath, ".node") && existsFile(fullFilepath) {
return api.OnResolveResult{
Path: fmt.Sprintf("%s/error.js?type=unsupported-node-native-module&name=%s&importer=%s", cfg.CdnBasePath, path.Base(args.Path), task.Pkg),
External: true,
}, nil
}

// bundles json module
if strings.HasSuffix(fullFilepath, ".json") && fileExists(fullFilepath) {
if strings.HasSuffix(fullFilepath, ".json") && existsFile(fullFilepath) {
return api.OnResolveResult{Path: fullFilepath}, nil
}

// embed wasm as WebAssembly.Module
if strings.HasSuffix(fullFilepath, ".wasm") && fileExists(fullFilepath) {
if strings.HasSuffix(fullFilepath, ".wasm") && existsFile(fullFilepath) {
return api.OnResolveResult{Path: fullFilepath, Namespace: "wasm"}, nil
}

Expand Down Expand Up @@ -571,7 +571,6 @@ rebuild:
}
}
}
fmt.Println(match, name, bareName, task.Pkg.SubModule)
if match {
exportPrefix, _ := utils.SplitByLastByte(name, '*')
url := path.Join(npm.Name, exportPrefix+strings.TrimPrefix(bareName, prefix))
Expand Down Expand Up @@ -915,9 +914,9 @@ rebuild:
finalContent.Write(header.Bytes())
finalContent.Write(jsContent)

// check if package is deprecated
if task.Pkg.Deprecated != "" {
fmt.Fprintf(finalContent, `console.warn("[npm] %%cdeprecated%%c %s@%s: %s", "color:red", "");%s`, task.Pkg.Name, task.Pkg.Version, strings.ReplaceAll(task.Pkg.Deprecated, "\"", "\\\""), "\n")
deprecated, ok := isDeprecated(task.Pkg.Name, task.Pkg.Version)
if ok {
fmt.Fprintf(finalContent, `console.warn("[npm] %%cdeprecated%%c %s@%s: %s", "color:red", "");%s`, task.Pkg.Name, task.Pkg.Version, strings.ReplaceAll(deprecated, "\"", "\\\""), "\n")
}

// add sourcemap Url
Expand Down Expand Up @@ -1081,6 +1080,16 @@ func (task *BuildTask) resolveExternal(specifier string, kind api.ResolveKind) (
version = "latest"
}
}
// use version defined in `?deps` query if it exists
for _, dep := range task.Args.deps {
if pkgName == dep.Name {
version = dep.Version
}
}
// force the version of 'react' (as dependency) equals to 'react-dom'
if task.Pkg.Name == "react-dom" && pkgName == "react" {
version = task.Pkg.Version
}
if !regexpFullVersion.MatchString(version) {
p, _, err := getPackageInfo(task.resovleDir, pkgName, version)
if err == nil {
Expand All @@ -1094,22 +1103,13 @@ func (task *BuildTask) resolveExternal(specifier string, kind api.ResolveKind) (
SubModule: toModuleBareName(subpath, true),
}
args := BuildArgs{
alias: cloneMap(task.Args.alias),
conditions: newStringSet(task.Args.conditions.Values()...),
external: newStringSet(task.Args.external.Values()...),
alias: task.Args.alias,
conditions: task.Args.conditions,
deps: task.Args.deps,
external: newStringSet(),
exports: newStringSet(),
}
// force the dependency version of `react` equals to react-dom
if task.Pkg.Name == "react-dom" && specifier == "react" {
pkg.Version = task.Pkg.Version
}
// use version defined in `?deps` query
for _, dep := range task.Args.deps {
if specifier == dep.Name || strings.HasPrefix(specifier, dep.Name+"/") {
pkg.Version = dep.Version
break
}
}
fixBuildArgs(&args, pkg)
resolvedPath = task.getImportPath(pkg, encodeBuildArgsPrefix(args, pkg, false))
}

Expand Down
82 changes: 63 additions & 19 deletions server/build_args.go
Expand Up @@ -11,9 +11,9 @@ import (
type BuildArgs struct {
alias map[string]string
deps PkgSlice
conditions *stringSet
external *stringSet
exports *stringSet
conditions *StringSet
external *StringSet
exports *StringSet
denoStdVersion string
ignoreAnnotations bool
ignoreRequire bool
Expand Down Expand Up @@ -83,20 +83,7 @@ func decodeBuildArgsPrefix(raw string) (args BuildArgs, err error) {

func encodeBuildArgsPrefix(args BuildArgs, pkg Pkg, isDts bool) string {
lines := []string{}
pkgDeps := newStringSet("_")
if len(args.alias)+len(args.deps)+args.external.Len() > 0 && cfg != nil {
info, _, err := getPackageInfo("", pkg.Name, pkg.Version)
if err == nil {
pkgDeps.Reset()
for name := range info.Dependencies {
pkgDeps.Add(name)
}
for name := range info.PeerDependencies {
pkgDeps.Add(name)
}
}
}
if len(args.alias) > 0 && pkgDeps.Len() > 0 {
if len(args.alias) > 0 {
var ss sort.StringSlice
for from, to := range args.alias {
if from != pkg.Name {
Expand All @@ -108,7 +95,7 @@ func encodeBuildArgsPrefix(args BuildArgs, pkg Pkg, isDts bool) string {
lines = append(lines, fmt.Sprintf("a/%s", strings.Join(ss, ",")))
}
}
if len(args.deps) > 0 && pkgDeps.Len() > 0 {
if len(args.deps) > 0 {
var ss sort.StringSlice
for _, p := range args.deps {
if p.Name != pkg.Name {
Expand All @@ -120,7 +107,7 @@ func encodeBuildArgsPrefix(args BuildArgs, pkg Pkg, isDts bool) string {
lines = append(lines, fmt.Sprintf("d/%s", strings.Join(ss, ",")))
}
}
if args.external.Len() > 0 && pkgDeps.Len() > 0 {
if args.external.Len() > 0 {
var ss sort.StringSlice
for _, name := range args.external.Values() {
if name != pkg.Name {
Expand Down Expand Up @@ -173,3 +160,60 @@ func encodeBuildArgsPrefix(args BuildArgs, pkg Pkg, isDts bool) string {
}
return ""
}

func fixBuildArgs(args *BuildArgs, pkg Pkg) {
if len(args.alias) > 0 || len(args.deps) > 0 {
depTree := newStringSet(walkDeps(newStringSet(), pkg)...)
if len(args.alias) > 0 {
alias := map[string]string{}
for from, to := range args.alias {
if depTree.Has(from) {
alias[from] = to
}
}
for _, to := range alias {
pkgName, _, _ := splitPkgPath(to)
depTree.Add(pkgName)
}
args.alias = alias
}
if len(args.deps) > 0 {
var deps PkgSlice
for _, p := range args.deps {
if depTree.Has(p.Name) {
deps = append(deps, p)
}
}
args.deps = deps
}
}
}

func walkDeps(marker *StringSet, pkg Pkg) (deps []string) {
if marker.Has(pkg.Name) {
return nil
}
marker.Add(pkg.Name)
p, _, err := getPackageInfo("", pkg.Name, pkg.Version)
if err != nil {
return nil
}
pkgDeps := map[string]string{}
for name, version := range p.Dependencies {
pkgDeps[name] = version
}
for name, version := range p.PeerDependencies {
pkgDeps[name] = version
}
ch := make(chan []string, len(pkgDeps))
for name, version := range pkgDeps {
deps = append(deps, name)
go func(c chan []string, marker *StringSet, name, version string) {
c <- walkDeps(marker, Pkg{Name: name, Version: version})
}(ch, marker, name, version)
}
for range pkgDeps {
deps = append(deps, <-ch...)
}
return
}
50 changes: 25 additions & 25 deletions server/build_helpers.go
Expand Up @@ -179,9 +179,9 @@ func (task *BuildTask) analyze(forceCjsOnly bool) (esm *ESMBuild, npm NpmPackage
if strings.HasSuffix(pkg.SubModule, "~.d.ts") {
submodule := strings.TrimSuffix(pkg.SubModule, "~.d.ts")
subDir := path.Join(wd, "node_modules", npm.Name, submodule)
if fileExists(path.Join(subDir, "index.d.ts")) {
if existsFile(path.Join(subDir, "index.d.ts")) {
npm.Types = path.Join(submodule, "index.d.ts")
} else if fileExists(path.Join(subDir + ".d.ts")) {
} else if existsFile(path.Join(subDir + ".d.ts")) {
npm.Types = submodule + ".d.ts"
}
} else {
Expand All @@ -190,7 +190,7 @@ func (task *BuildTask) analyze(forceCjsOnly bool) (esm *ESMBuild, npm NpmPackage
} else {
subDir := path.Join(wd, "node_modules", npm.Name, pkg.SubModule)
packageFile := path.Join(subDir, "package.json")
if fileExists(packageFile) {
if existsFile(packageFile) {
var p NpmPackageInfo
err = utils.ParseJSONFile(packageFile, &p)
if err != nil {
Expand All @@ -216,23 +216,23 @@ func (task *BuildTask) analyze(forceCjsOnly bool) (esm *ESMBuild, npm NpmPackage
npm.Types = path.Join(pkg.SubModule, p.Types)
} else if p.Typings != "" {
npm.Types = path.Join(pkg.SubModule, p.Typings)
} else if fileExists(path.Join(subDir, "index.d.ts")) {
} else if existsFile(path.Join(subDir, "index.d.ts")) {
npm.Types = path.Join(pkg.SubModule, "index.d.ts")
} else if fileExists(path.Join(subDir + ".d.ts")) {
} else if existsFile(path.Join(subDir + ".d.ts")) {
npm.Types = pkg.SubModule + ".d.ts"
}
} else {
fp := path.Join(wd, "node_modules", npm.Name, pkg.SubModule+".mjs")
if npm.Type == "module" || npm.Module != "" || fileExists(fp) {
if npm.Type == "module" || npm.Module != "" || existsFile(fp) {
// follow main module type
npm.Module = pkg.SubModule
} else {
npm.Main = pkg.SubModule
}
npm.Types = ""
if fileExists(path.Join(subDir, "index.d.ts")) {
if existsFile(path.Join(subDir, "index.d.ts")) {
npm.Types = path.Join(pkg.SubModule, "index.d.ts")
} else if fileExists(path.Join(subDir + ".d.ts")) {
} else if existsFile(path.Join(subDir + ".d.ts")) {
npm.Types = pkg.SubModule + ".d.ts"
}
// reslove sub-module using `exports` conditions if exists
Expand Down Expand Up @@ -461,21 +461,21 @@ func (task *BuildTask) normalizeNpmPackage(p NpmPackageInfo) NpmPackageInfo {

nmDir := path.Join(task.wd, "node_modules")
if p.Module == "" {
if p.JsNextMain != "" && fileExists(path.Join(nmDir, p.Name, p.JsNextMain)) {
if p.JsNextMain != "" && existsFile(path.Join(nmDir, p.Name, p.JsNextMain)) {
p.Module = p.JsNextMain
} else if p.ES2015 != "" && fileExists(path.Join(nmDir, p.Name, p.ES2015)) {
} else if p.ES2015 != "" && existsFile(path.Join(nmDir, p.Name, p.ES2015)) {
p.Module = p.ES2015
} else if p.Main != "" && (p.Type == "module" || strings.HasSuffix(p.Main, ".mjs")) {
p.Module = p.Main
}
}

if p.Main == "" && p.Module == "" {
if fileExists(path.Join(nmDir, p.Name, "index.mjs")) {
if existsFile(path.Join(nmDir, p.Name, "index.mjs")) {
p.Module = "./index.mjs"
} else if fileExists(path.Join(nmDir, p.Name, "index.js")) {
} else if existsFile(path.Join(nmDir, p.Name, "index.js")) {
p.Main = "./index.js"
} else if fileExists(path.Join(nmDir, p.Name, "index.cjs")) {
} else if existsFile(path.Join(nmDir, p.Name, "index.cjs")) {
p.Main = "./index.cjs"
}
}
Expand All @@ -502,7 +502,7 @@ func (task *BuildTask) normalizeNpmPackage(p NpmPackageInfo) NpmPackageInfo {
}
}
if browserModule == "" && browserMain == "" {
if m := p.Browser["."]; m != "" && fileExists(path.Join(nmDir, p.Name, m)) {
if m := p.Browser["."]; m != "" && existsFile(path.Join(nmDir, p.Name, m)) {
isEsm, _, _ := validateJS(path.Join(nmDir, p.Name, m))
if isEsm {
browserModule = m
Expand All @@ -525,12 +525,12 @@ func (task *BuildTask) normalizeNpmPackage(p NpmPackageInfo) NpmPackageInfo {
} else {
name, _ := utils.SplitByLastByte(p.Main, '.')
maybeTypesPath := name + ".d.ts"
if fileExists(path.Join(nmDir, p.Name, maybeTypesPath)) {
if existsFile(path.Join(nmDir, p.Name, maybeTypesPath)) {
p.Types = maybeTypesPath
} else {
dir, _ := utils.SplitByLastByte(p.Main, '/')
maybeTypesPath := dir + "/index.d.ts"
if fileExists(path.Join(nmDir, p.Name, maybeTypesPath)) {
if existsFile(path.Join(nmDir, p.Name, maybeTypesPath)) {
p.Types = maybeTypesPath
}
}
Expand All @@ -544,12 +544,12 @@ func (task *BuildTask) normalizeNpmPackage(p NpmPackageInfo) NpmPackageInfo {
} else {
name, _ := utils.SplitByLastByte(p.Module, '.')
maybeTypesPath := name + ".d.ts"
if fileExists(path.Join(nmDir, p.Name, maybeTypesPath)) {
if existsFile(path.Join(nmDir, p.Name, maybeTypesPath)) {
p.Types = maybeTypesPath
} else {
dir, _ := utils.SplitByLastByte(p.Module, '/')
maybeTypesPath := dir + "/index.d.ts"
if fileExists(path.Join(nmDir, p.Name, maybeTypesPath)) {
if existsFile(path.Join(nmDir, p.Name, maybeTypesPath)) {
p.Types = maybeTypesPath
}
}
Expand Down Expand Up @@ -652,33 +652,33 @@ func queryESMBuild(id string) (*ESMBuild, bool) {
func esmLexer(wd string, packageName string, moduleSpecifier string) (resolvedName string, namedExports []string, err error) {
pkgDir := path.Join(wd, "node_modules", packageName)
resolvedName = moduleSpecifier
if !fileExists(path.Join(pkgDir, resolvedName)) {
if !existsFile(path.Join(pkgDir, resolvedName)) {
for _, ext := range esExts {
name := moduleSpecifier + ext
if fileExists(path.Join(pkgDir, name)) {
if existsFile(path.Join(pkgDir, name)) {
resolvedName = name
break
}
}
}
if !fileExists(path.Join(pkgDir, resolvedName)) {
if !existsFile(path.Join(pkgDir, resolvedName)) {
if endsWith(resolvedName, esExts...) {
name, ext := utils.SplitByLastByte(resolvedName, '.')
fixedName := name + "/index." + ext
if fileExists(path.Join(pkgDir, fixedName)) {
if existsFile(path.Join(pkgDir, fixedName)) {
resolvedName = fixedName
}
} else if dirExists(path.Join(pkgDir, moduleSpecifier)) {
} else if existsDir(path.Join(pkgDir, moduleSpecifier)) {
for _, ext := range esExts {
name := path.Join(moduleSpecifier, "index"+ext)
if fileExists(path.Join(pkgDir, name)) {
if existsFile(path.Join(pkgDir, name)) {
resolvedName = name
break
}
}
}
}
if !fileExists(path.Join(pkgDir, resolvedName)) {
if !existsFile(path.Join(pkgDir, resolvedName)) {
for _, ext := range esExts {
if strings.HasSuffix(resolvedName, "index/index"+ext) {
resolvedName = strings.TrimSuffix(resolvedName, "/index"+ext) + ext
Expand Down
2 changes: 1 addition & 1 deletion server/build_rewriter.go
Expand Up @@ -20,7 +20,7 @@ func (task *BuildTask) rewriteJS(js []byte) (ret []byte, dropSourceMap bool) {

case "tailwindcss":
preflightCSSFile := path.Join(task.wd, "node_modules", "tailwindcss/src/css/preflight.css")
if fileExists(preflightCSSFile) {
if existsFile(preflightCSSFile) {
data, err := os.ReadFile(preflightCSSFile)
if err == nil {
str, _ := json.Marshal(string(data))
Expand Down

0 comments on commit 6a6f349

Please sign in to comment.