deno.land / x / esm@v135_2 / server / build_helpers.go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799package server
import ( "crypto/sha1" "encoding/hex" "encoding/json" "errors" "fmt" "io" "os" "path" "strings"
"github.com/esm-dev/esm.sh/server/storage" "github.com/evanw/esbuild/pkg/api" "github.com/ije/gox/utils")
func (task *BuildTask) ID() string { if task.id != "" { return task.id }
pkg := task.Pkg name := strings.TrimSuffix(path.Base(pkg.Name), ".js") extname := ".mjs"
if pkg.FromEsmsh { name = "mod" } if pkg.SubModule != "" { name = pkg.SubModule extname = ".js" } if task.Target == "raw" { extname = "" } if task.Dev { name += ".development" } if task.BundleDeps { name += ".bundle" } else if task.NoBundle { name += ".bundless" }
task.id = fmt.Sprintf( "%s%s/%s@%s/%s%s/%s%s", task.getBuildVersion(task.Pkg), task.ghPrefix(), pkg.Name, pkg.Version, encodeBuildArgsPrefix(task.Args, task.Pkg, task.Target == "types"), task.Target, name, extname, ) if task.Target == "types" { task.id = strings.TrimSuffix(task.id, extname) } return task.id}
func (task *BuildTask) ghPrefix() string { if task.Pkg.FromGithub { return "/gh" } return ""}
func (task *BuildTask) getImportPath(pkg Pkg, buildArgsPrefix string) string { name := strings.TrimSuffix(path.Base(pkg.Name), ".js") extname := ".mjs" if pkg.SubModule != "" { name = pkg.SubModule extname = ".js" } if pkg.FromEsmsh { name = "mod" } // workaround for es5-ext weird "/#/" path if pkg.Name == "es5-ext" { name = strings.ReplaceAll(name, "/#/", "/$$/") } if task.Dev { name += ".development" } return fmt.Sprintf( "%s/%s/%s@%s/%s%s/%s%s", cfg.CdnBasePath, task.getBuildVersion(pkg), pkg.Name, pkg.Version, buildArgsPrefix, task.Target, name, extname, )}
func (task *BuildTask) getBuildVersion(pkg Pkg) string { if stableBuild[pkg.Name] { return "stable" } return fmt.Sprintf("v%d", task.BuildVersion)}
func (task *BuildTask) getSavepath() string { id := task.ID() if stableBuild[task.Pkg.Name] { id = path.Join(fmt.Sprintf("v%d", STABLE_VERSION), strings.TrimPrefix(id, "stable/")) } return normalizeSavePath(path.Join("builds", id))}
func normalizeSavePath(pathname string) string { segs := strings.Split(pathname, "/") for i, seg := range segs { if strings.HasPrefix(seg, "X-") && len(seg) > 42 { h := sha1.New() h.Write([]byte(seg)) segs[i] = "X-" + hex.EncodeToString(h.Sum(nil)) } } return strings.Join(segs, "/")}
func (task *BuildTask) getPackageInfo(name string) (pkg Pkg, p NpmPackageInfo, fromPackageJSON bool, err error) { pkgName, _, subpath := splitPkgPath(name) var version string if pkg, ok := task.Args.deps.Get(pkgName); ok { version = pkg.Version } else if v, ok := task.npm.Dependencies[pkgName]; ok { version = v } else if v, ok = task.npm.PeerDependencies[pkgName]; ok { version = v } else { version = "latest" } p, fromPackageJSON, err = getPackageInfo(task.installDir, pkgName, version) if err == nil { pkg = Pkg{ Name: p.Name, Version: p.Version, SubPath: subpath, SubModule: toModuleBareName(subpath, true), } } return}
func (task *BuildTask) isServerTarget() bool { return task.Target == "deno" || task.Target == "denonext" || task.Target == "node"}
func (task *BuildTask) isDenoTarget() bool { return task.Target == "deno" || task.Target == "denonext"}
func (task *BuildTask) analyze(forceCjsOnly bool) (esm *ESMBuild, npm NpmPackageInfo, reexport string, err error) { wd := task.wd pkg := task.Pkg
var p NpmPackageInfo err = utils.ParseJSONFile(path.Join(wd, "node_modules", pkg.Name, "package.json"), &p) if err != nil { return } npm = task.fixNpmPackage(p)
// Check if the supplied path name is actually a main export. // See: https://github.com/esm-dev/esm.sh/issues/578 if pkg.SubPath == path.Clean(npm.Main) || pkg.SubPath == path.Clean(npm.Module) { task.Pkg.SubModule = "" npm = task.fixNpmPackage(p) }
esm = &ESMBuild{}
defer func() { esm.FromCJS = npm.Module == "" && npm.Main != "" esm.TypesOnly = isTypesOnlyPackage(npm) }()
if pkg.SubModule != "" { if endsWith(pkg.SubModule, ".d.ts", ".d.mts") { 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")) { npm.Types = path.Join(submodule, "index.d.ts") } else if fileExists(path.Join(subDir + ".d.ts")) { npm.Types = submodule + ".d.ts" } } else { npm.Types = pkg.SubModule } } else { subDir := path.Join(wd, "node_modules", npm.Name, pkg.SubModule) packageFile := path.Join(subDir, "package.json") if fileExists(packageFile) { var p NpmPackageInfo err = utils.ParseJSONFile(packageFile, &p) if err != nil { return } if p.Version == "" { // use parent package version if submodule package.json doesn't have version p.Version = npm.Version } np := task.fixNpmPackage(p) if np.Module != "" { npm.Module = path.Join(pkg.SubModule, np.Module) } else { npm.Module = "" } if p.Main != "" { npm.Main = path.Join(pkg.SubModule, p.Main) } else { npm.Main = path.Join(pkg.SubModule, "index.js") } npm.Types = "" if p.Types != "" { 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")) { npm.Types = path.Join(pkg.SubModule, "index.d.ts") } else if fileExists(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) { // follow main module type npm.Module = pkg.SubModule } else { npm.Main = pkg.SubModule } npm.Types = "" if fileExists(path.Join(subDir, "index.d.ts")) { npm.Types = path.Join(pkg.SubModule, "index.d.ts") } else if fileExists(path.Join(subDir + ".d.ts")) { npm.Types = pkg.SubModule + ".d.ts" } // reslove sub-module using `exports` conditions if exists if npm.PkgExports != nil { if om, ok := npm.PkgExports.(*orderedMap); ok { for e := om.l.Front(); e != nil; e = e.Next() { name, exports := om.Entry(e) if name == "./"+pkg.SubModule || name == "./"+pkg.SubModule+".js" || name == "./"+pkg.SubModule+".mjs" { /** exports: { "./lib/core": { "require": "./lib/core.js", "import": "./esm/core.js" }, "./lib/core.js": { "require": "./lib/core.js", "import": "./esm/core.js" } } */ task.resolveConditions(&npm, exports, npm.Type) break } else if strings.HasSuffix(name, "*") && strings.HasPrefix("./"+pkg.SubModule, strings.TrimSuffix(name, "*")) { /** exports: { "./lib/languages/*": { "require": "./lib/languages/*.js", "import": "./esm/languages/*.js" }, } */ suffix := strings.TrimPrefix("./"+pkg.SubModule, strings.TrimSuffix(name, "*")) hitExports := false if om, ok := exports.(*orderedMap); ok { newExports := newOrderedMap() for e := om.l.Front(); e != nil; e = e.Next() { key, value := om.Entry(e) if s, ok := value.(string); ok && s != name { newExports.Set(key, strings.Replace(s, "*", suffix, -1)) hitExports = true } /** exports: { "./*": { "types": "./*.d.ts", "import": { "types": "./esm/*.d.mts", "default": "./esm/*.mjs" }, "default": "./*.js" } } */ if s, ok := value.(map[string]interface{}); ok { subNewDefinies := newOrderedMap() for subKey, subValue := range s { if s1, ok := subValue.(string); ok && s1 != name { subNewDefinies.Set(subKey, strings.Replace(s1, "*", suffix, -1)) hitExports = true } } newExports.Set(key, subNewDefinies) } } exports = newExports } else if s, ok := exports.(string); ok { exports = strings.Replace(s, "*", suffix, -1) hitExports = true } if hitExports { task.resolveConditions(&npm, exports, npm.Type) break } } } } } } } }
if task.Target == "types" || isTypesOnlyPackage(npm) { return }
nodeEnv := "production" if task.Dev { nodeEnv = "development" }
if npm.Module != "" && !forceCjsOnly { modulePath, namedExports, erro := esmLexer(wd, npm.Name, npm.Module) if erro == nil { npm.Module = modulePath esm.NamedExports = namedExports esm.HasExportDefault = includes(namedExports, "default") return } if erro.Error() != "not a module" { err = fmt.Errorf("esmLexer: %s", erro) return }
npm.Main = npm.Module npm.Module = ""
var ret cjsExportsResult ret, err = cjsLexer(wd, path.Join(wd, "node_modules", pkg.Name, modulePath), nodeEnv) if err == nil && ret.Error != "" { err = fmt.Errorf("cjsLexer: %s", ret.Error) } if err != nil { return } reexport = ret.Reexport esm.HasExportDefault = ret.ExportDefault esm.NamedExports = ret.Exports log.Warnf("fake ES module '%s' of '%s'", npm.Main, npm.Name) return }
if npm.Main != "" { // install peer dependencies when using `requireMode` if includes(requireModeAllowList, pkg.Name) && len(npm.PeerDependencies) > 0 { pkgs := make([]string, len(npm.PeerDependencies)) i := 0 for n, v := range npm.PeerDependencies { pkgs[i] = n + "@" + v i++ } err = pnpmInstall(wd, pkgs...) if err != nil { return } } var ret cjsExportsResult ret, err = cjsLexer(wd, pkg.ImportPath(), nodeEnv) if err == nil && ret.Error != "" { err = fmt.Errorf("cjsLexer: %s", ret.Error) } if err != nil { return } reexport = ret.Reexport esm.HasExportDefault = ret.ExportDefault esm.NamedExports = ret.Exports } return}
func (task *BuildTask) fixNpmPackage(p NpmPackageInfo) NpmPackageInfo { if task.Pkg.FromGithub { p.Name = task.Pkg.Name p.Version = task.Pkg.Version } else { p.Version = strings.TrimPrefix(p.Version, "v") }
if p.Types == "" && p.Typings != "" { p.Types = p.Typings }
if len(p.TypesVersions) > 0 { var usedCondition string for c, e := range p.TypesVersions { if c == "*" && strings.HasPrefix(c, ">") || strings.HasPrefix(c, ">=") { if usedCondition == "" || c == "*" || c > usedCondition { if om, ok := e.(*orderedMap); ok { d, ok := om.m["*"] if !ok { d, ok = om.m["."] } if ok { if a, ok := d.([]interface{}); ok && len(a) > 0 { if t, ok := a[0].(string); ok { usedCondition = c if strings.HasSuffix(t, "*") { f := p.Types if f == "" { f = "index.d.ts" } t = path.Join(t[:len(t)-1], f) } p.Types = t } } } } } } } }
if exports := p.PkgExports; exports != nil { if om, ok := exports.(*orderedMap); ok { v, ok := om.m["."] if ok { /* exports: { ".": { "require": "./cjs/index.js", "import": "./esm/index.js" } } exports: { ".": "./esm/index.js" } */ task.resolveConditions(&p, v, p.Type) } else { /* exports: { "require": "./cjs/index.js", "import": "./esm/index.js" } */ task.resolveConditions(&p, om, p.Type) } } else if s, ok := exports.(string); ok { /* exports: "./esm/index.js" */ task.resolveConditions(&p, s, p.Type) } }
nmDir := path.Join(task.wd, "node_modules") if p.Module == "" { if p.JsNextMain != "" && fileExists(path.Join(nmDir, p.Name, p.JsNextMain)) { p.Module = p.JsNextMain } else if p.ES2015 != "" && fileExists(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")) { p.Module = "./index.mjs" } else if fileExists(path.Join(nmDir, p.Name, "index.js")) { p.Main = "./index.js" } else if fileExists(path.Join(nmDir, p.Name, "index.cjs")) { p.Main = "./index.cjs" } }
if p.Module != "" && !strings.HasPrefix(p.Module, "./") && !strings.HasPrefix(p.Module, "../") { p.Module = "." + utils.CleanPath(p.Module) } if p.Main != "" && !strings.HasPrefix(p.Main, "./") && !strings.HasPrefix(p.Module, "../") { p.Main = "." + utils.CleanPath(p.Main) }
if !task.isServerTarget() { var browserModule string var browserMain string if p.Module != "" { m, ok := p.Browser[p.Module] if ok { browserModule = m } } else if p.Main != "" { m, ok := p.Browser[p.Main] if ok { browserMain = m } } if browserModule == "" && browserMain == "" { if m := p.Browser["."]; m != "" && fileExists(path.Join(nmDir, p.Name, m)) { isEsm, _, _ := validateJS(path.Join(nmDir, p.Name, m)) if isEsm { browserModule = m } else { browserMain = m } } } if browserModule != "" { p.Module = browserModule } else if browserMain != "" { p.Main = browserMain } }
if p.Types == "" && p.Main != "" { if strings.HasSuffix(p.Main, ".d.ts") { p.Types = p.Main p.Main = "" } else { name, _ := utils.SplitByLastByte(p.Main, '.') maybeTypesPath := name + ".d.ts" if fileExists(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)) { p.Types = maybeTypesPath } } } }
if p.Types == "" && p.Module != "" { if strings.HasSuffix(p.Module, ".d.ts") { p.Types = p.Module p.Module = "" } else { name, _ := utils.SplitByLastByte(p.Module, '.') maybeTypesPath := name + ".d.ts" if fileExists(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)) { p.Types = maybeTypesPath } } } }
return p}
// see https://nodejs.org/api/packages.htmlfunc (task *BuildTask) resolveConditions(p *NpmPackageInfo, exports interface{}, pType string) { s, ok := exports.(string) if ok { if pType == "module" { p.Module = s } else { p.Main = s } return }
om, ok := exports.(*orderedMap) if !ok { return }
for e := om.l.Front(); e != nil; e = e.Next() { key := e.Value.(string) value := om.m[key] s, ok := value.(string) if ok && s != "" { switch key { case "types": p.Types = s case "typings": p.Typings = s } } }
targetConditions := []string{"browser"} conditions := []string{"module", "import", "es2015"} _, hasRequireCondition := om.m["require"] _, hasNodeCondition := om.m["node"] if pType == "module" || hasRequireCondition || hasNodeCondition { conditions = append(conditions, "default") } switch task.Target { case "deno", "denonext": targetConditions = []string{"deno", "worker"} conditions = append(conditions, "browser") // priority use `node` condition for solid.js (< 1.5.6) in deno if (p.Name == "solid-js" || strings.HasPrefix(p.Name, "solid-js/")) && semverLessThan(p.Version, "1.5.6") { targetConditions = []string{"node"} } case "node": targetConditions = []string{"node"} } if task.Dev { targetConditions = append(targetConditions, "development") } if task.Args.conditions.Len() > 0 { targetConditions = append(task.Args.conditions.Values(), targetConditions...) } for _, condition := range append(targetConditions, conditions...) { v, ok := om.m[condition] if ok { task.resolveConditions(p, v, "module") return } } for _, condition := range append(targetConditions, "require", "node", "default") { v, ok := om.m[condition] if ok { task.resolveConditions(p, v, "commonjs") break } }}
func queryESMBuild(id string) (*ESMBuild, bool) { value, err := db.Get(id) if err == nil && value != nil { var esm ESMBuild err = json.Unmarshal(value, &esm) if err == nil { if strings.HasPrefix(id, "stable/") { id = fmt.Sprintf("v%d/", STABLE_VERSION) + strings.TrimPrefix(id, "stable/") } if !esm.TypesOnly { _, err = fs.Stat(path.Join("builds", id)) } if err == nil || os.IsExist(err) { return &esm, true } } // delete the invalid db entry db.Delete(id) } return nil, false}
var jsExts = []string{".mjs", ".js", ".jsx", ".mts", ".ts", ".tsx"}
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)) { for _, ext := range jsExts { name := moduleSpecifier + ext if fileExists(path.Join(pkgDir, name)) { resolvedName = name break } } } if !fileExists(path.Join(pkgDir, resolvedName)) { if endsWith(resolvedName, jsExts...) { name, ext := utils.SplitByLastByte(resolvedName, '.') fixedName := name + "/index." + ext if fileExists(path.Join(pkgDir, fixedName)) { resolvedName = fixedName } } else if dirExists(path.Join(pkgDir, moduleSpecifier)) { for _, ext := range jsExts { name := path.Join(moduleSpecifier, "index"+ext) if fileExists(path.Join(pkgDir, name)) { resolvedName = name break } } } } if !fileExists(path.Join(pkgDir, resolvedName)) { for _, ext := range jsExts { if strings.HasSuffix(resolvedName, "index/index"+ext) { resolvedName = strings.TrimSuffix(resolvedName, "/index"+ext) + ext break } } }
isESM, _namedExports, err := validateJS(path.Join(pkgDir, resolvedName)) if err != nil { return }
if !isESM { err = errors.New("not a module") return }
namedExports = _namedExports return}
func copyRawBuildFile(id string, name string, dir string) (err error) { var r io.ReadCloser var f *os.File r, err = fs.OpenFile(path.Join("publish", strings.TrimPrefix(id, "~"), name)) if err != nil { if err == storage.ErrNotFound { return nil } return fmt.Errorf("open file failed: %s", name) } defer r.Close() ensureDir(dir) f, err = os.OpenFile(path.Join(dir, name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return err } defer f.Close() _, err = io.Copy(f, r) return}
func bundleNodePolyfill(name string, globalName string, namedExport string, target api.Target) ([]byte, error) { ret := api.Build(api.BuildOptions{ Stdin: &api.StdinOptions{ Contents: fmt.Sprintf(`import * as e from "node_%s.js";globalThis.%s=e.%s`, name, globalName, namedExport), Loader: api.LoaderJS, }, Write: false, Bundle: true, Target: target, Format: api.FormatIIFE, Platform: api.PlatformBrowser, MinifyWhitespace: true, MinifyIdentifiers: true, MinifySyntax: true, Plugins: []api.Plugin{{ Name: "esm", Setup: func(build api.PluginBuild) { build.OnResolve( api.OnResolveOptions{Filter: ".*"}, func(args api.OnResolveArgs) (api.OnResolveResult, error) { return api.OnResolveResult{Path: path.Join("server/embed/polyfills/", args.Path), Namespace: "embed"}, nil }, ) build.OnLoad( api.OnLoadOptions{Filter: ".*", Namespace: "embed"}, func(args api.OnLoadArgs) (api.OnLoadResult, error) { data, err := embedFS.ReadFile(args.Path) if err != nil { return api.OnLoadResult{}, err } contents := string(data) return api.OnLoadResult{ Contents: &contents, Loader: api.LoaderJS, }, nil }, ) }}}, }) if ret.Errors != nil && len(ret.Errors) > 0 { return nil, errors.New(ret.Errors[0].Text) } return ret.OutputFiles[0].Contents, nil}
func minify(code string, target api.Target, loader api.Loader) ([]byte, error) { ret := api.Transform(code, api.TransformOptions{ Target: target, Format: api.FormatESModule, Platform: api.PlatformBrowser, MinifyWhitespace: true, MinifyIdentifiers: true, MinifySyntax: true, LegalComments: api.LegalCommentsInline, Loader: loader, }) if ret.Errors != nil && len(ret.Errors) > 0 { return nil, errors.New(ret.Errors[0].Text) } return ret.Code, nil}
Version Info