Skip to content

Commit

Permalink
Suppport Deno 2 (#964)
Browse files Browse the repository at this point in the history
  • Loading branch information
ije authored Dec 29, 2024
1 parent 0a754ce commit f5c8f83
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 90 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,9 +328,7 @@ specify the types for the imported module.

## Supporting Node.js/Bun

Node.js and Bun don't currently support http modules.

We highly recommend [Reejs](https://ree.js.org/) as the runtime with esm.sh that works both in Node.js and Bun.
esm.sh is not supported by Node.js/Bun currently.

## Global CDN

Expand Down
2 changes: 1 addition & 1 deletion scripts/deploy-ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ echo " IdentityFile ~/.ssh/id_ed25519" >> ~/.ssh/config
echo " IdentitiesOnly yes" >> ~/.ssh/config

echo "--- building..."
go build -ldflags="-s -w -X 'github.com/esm-dev/esm.sh/server.Version=${VERSION}'" -o esmd $(dirname $0)/../main.go
go build -ldflags="-s -w -X 'github.com/esm-dev/esm.sh/server.VERSION=${VERSION}'" -o esmd $(dirname $0)/../main.go
if [ "$?" != "0" ]; then
exit 1
fi
Expand Down
43 changes: 27 additions & 16 deletions server/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/esm-dev/esm.sh/server/npm_replacements"
"github.com/esm-dev/esm.sh/server/storage"
esbuild "github.com/evanw/esbuild/pkg/api"
"github.com/ije/gox/crypto/rand"
"github.com/ije/gox/set"
"github.com/ije/gox/utils"
)
Expand Down Expand Up @@ -173,7 +174,16 @@ func (ctx *BuildContext) Build() (meta *BuildMeta, err error) {
func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, includes [][2]string, err error) {
entry := ctx.resolveEntry(ctx.esmPath)
if entry.isEmpty() {
err = fmt.Errorf("could not resolve build entry")
if analyzeMode {
// ignore the empty entry in analyze mode
return
}
// the installation maybe not completed, move it to trash and delete it in the background
tmpDir := path.Join(os.TempDir(), "esm-trash-"+rand.Hex.String(32))
if os.Rename(ctx.wd, tmpDir) == nil {
go os.RemoveAll(tmpDir)
}
err = errors.New("could not resolve build entry")
return
}

Expand Down Expand Up @@ -933,10 +943,14 @@ func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, include
delete(define, "process")
}
}
if ctx.isDenoTarget() {
// deno 2 has removed the `window` global object, let's replace it with `globalThis`
define["window"] = "globalThis"
}
for k, v := range define {
define["global."+k] = v
}
define["global"] = "__global$"
define["global"] = "globalThis"
}
conditions := ctx.args.conditions
if ctx.dev {
Expand Down Expand Up @@ -1197,10 +1211,6 @@ REBUILD:
header.WriteString(`var __rResolve$ = p => p;`)
header.WriteByte('\n')
}
if ids.Has("__global$") {
header.WriteString(`var __global$ = globalThis || (typeof window !== "undefined" ? window : self);`)
header.WriteByte('\n')
}
}

// apply cjs requires
Expand Down Expand Up @@ -1607,17 +1617,18 @@ func (ctx *BuildContext) analyzeSplitting() (err error) {
pkgJson: ctx.pkgJson,
}
_, includes, err := b.buildModule(true)
if err == nil {
for _, include := range includes {
module, importer := include[0], include[1]
ref, ok := refs[module]
if !ok {
ref = Ref{entries: set.New[string](), importers: set.New[string]()}
refs[module] = ref
}
ref.importers.Add(importer)
ref.entries.Add(exportName)
if err != nil {
return fmt.Errorf("failed to analyze %s: %v", esmPath.Specifier(), err)
}
for _, include := range includes {
module, importer := include[0], include[1]
ref, ok := refs[module]
if !ok {
ref = Ref{entries: set.New[string](), importers: set.New[string]()}
refs[module] = ref
}
ref.importers.Add(importer)
ref.entries.Add(exportName)
}
}
shared := set.New[string]()
Expand Down
4 changes: 3 additions & 1 deletion server/build_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,9 @@ func (ctx *BuildContext) resolveConditionExportEntry(conditions JSONObject, pref
condition, ok := conditions.Get(conditionName)
if ok {
if s, ok := condition.(string); ok {
entry.types = s
if entry.types == "" || endsWith(s, ".d.ts", ".d.mts", ".d") {
entry.types = s
}
} else if obj, ok := condition.(JSONObject); ok {
entry = ctx.resolveConditionExportEntry(obj, "types")
}
Expand Down
30 changes: 20 additions & 10 deletions server/build_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,26 @@ func (ctx *BuildContext) rewriteJS(in []byte) (out []byte, dropSourceMap bool) {
return in, false
}

func (ctx *BuildContext) rewriteDTS(dts string, in []byte) []byte {
// fix preact/compat types
if ctx.esmPath.PkgName == "preact" && dts == "./compat/src/index.d.ts" {
if !bytes.Contains(in, []byte("export type PropsWithChildren")) {
return bytes.ReplaceAll(
in,
[]byte("export import ComponentProps = preact.ComponentProps;"),
[]byte("export import ComponentProps = preact.ComponentProps;\n\n// added by esm.sh\nexport type PropsWithChildren<P = unknown> = P & { children?: preact.ComponentChildren };"),
)
func (ctx *BuildContext) rewriteDTS(filename string, dts []byte) []byte {
switch ctx.esmPath.PkgName {
case "preact":
// fix preact/compat types
if filename == "./compat/src/index.d.ts" {
if !bytes.Contains(dts, []byte("export type PropsWithChildren")) {
return bytes.ReplaceAll(
dts,
[]byte("export import ComponentProps = preact.ComponentProps;"),
[]byte("export import ComponentProps = preact.ComponentProps;\n\n// added by esm.sh\nexport type PropsWithChildren<P = unknown> = P & { children?: preact.ComponentChildren };"),
)
}
}
case "@rollup/plugin-commonjs":
// see https://github.com/denoland/deno/issues/27492
return bytes.ReplaceAll(
dts,
[]byte("[package: string]: ReadonlyArray<string>"),
[]byte("[name : string]: ReadonlyArray<string>"),
)
}
return in
return dts
}
1 change: 0 additions & 1 deletion server/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const (
MB = 1 << 20
maxAssetFileSize = 50 * MB
maxPackageTarballSize = 256 * MB
nodeTypesVersion = "22.9.0"
)

// asset extensions
Expand Down
49 changes: 27 additions & 22 deletions server/dts_lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ func parseDts(r io.Reader, w *bytes.Buffer, resolve func(specifier string, kind
if err != nil {
return
}
fmt.Fprintf(w, `/// <reference %s="%s" />`, format, res)
if len(res) > 0 {
fmt.Fprintf(w, `/// <reference %s="%s" />`, format, res)
} else {
fmt.Fprintf(w, `// ignored <reference %s="%s" />`, format, path)
}
} else {
w.Write(line)
}
Expand All @@ -94,17 +98,17 @@ func parseDts(r io.Reader, w *bytes.Buffer, resolve func(specifier string, kind
w.Write(line)
} else {
var i int
exprScanner := bufio.NewScanner(bytes.NewReader(line))
exprScanner.Split(splitExpr)
for exprScanner.Scan() {
stmtScanner := bufio.NewScanner(bytes.NewReader(line))
stmtScanner.Split(splitJSStmt)
for stmtScanner.Scan() {
if i > 0 {
w.WriteByte(';')
}
expr, trimedLeftSpaces := trimSpace(exprScanner.Bytes())
stmt, trimedLeftSpaces := trimSpace(stmtScanner.Bytes())
w.Write(trimedLeftSpaces)
if len(expr) > 0 {
if regexpImportCallExpr.Match(expr) {
expr = regexpImportCallExpr.ReplaceAllFunc(expr, func(importCallExpr []byte) []byte {
if len(stmt) > 0 {
if regexpImportCallExpr.Match(stmt) {
stmt = regexpImportCallExpr.ReplaceAllFunc(stmt, func(importCallExpr []byte) []byte {
q := bytesSingleQoute
a := bytes.Split(importCallExpr, q)
if len(a) != 3 {
Expand All @@ -130,15 +134,15 @@ func parseDts(r io.Reader, w *bytes.Buffer, resolve func(specifier string, kind
})
}

if !importOrExportDeclFound && (bytes.HasPrefix(expr, []byte("import")) && regexpImportDecl.Match(expr)) || (bytes.HasPrefix(expr, []byte("export")) && regexpExportDecl.Match(expr)) {
if !importOrExportDeclFound && (bytes.HasPrefix(stmt, []byte("import")) && regexpImportDecl.Match(stmt)) || (bytes.HasPrefix(stmt, []byte("export")) && regexpExportDecl.Match(stmt)) {
importOrExportDeclFound = true
}
if bytes.HasPrefix(expr, []byte("declare")) && regexpDeclareModuleStmt.Match(expr) {
if bytes.HasPrefix(stmt, []byte("declare")) && regexpDeclareModuleStmt.Match(stmt) {
q := bytesSingleQoute
a := bytes.Split(expr, q)
a := bytes.Split(stmt, q)
if len(a) != 3 {
q = bytesDoubleQoute
a = bytes.Split(expr, q)
a = bytes.Split(stmt, q)
}
if len(a) == 3 {
w.Write(a[0])
Expand All @@ -152,15 +156,15 @@ func parseDts(r io.Reader, w *bytes.Buffer, resolve func(specifier string, kind
w.Write(q)
w.Write(a[2])
} else {
w.Write(expr)
w.Write(stmt)
}
} else if importOrExportDeclFound {
if regexpFromExpr.Match(expr) || (bytes.HasPrefix(expr, []byte("import")) && regexpImportPathDecl.Match(expr)) {
if regexpFromExpr.Match(stmt) || (bytes.HasPrefix(stmt, []byte("import")) && regexpImportPathDecl.Match(stmt)) {
q := bytesSingleQoute
a := bytes.Split(expr, q)
a := bytes.Split(stmt, q)
if len(a) != 3 {
q = bytesDoubleQoute
a = bytes.Split(expr, q)
a = bytes.Split(stmt, q)
}
if len(a) == 3 {
w.Write(a[0])
Expand All @@ -174,19 +178,19 @@ func parseDts(r io.Reader, w *bytes.Buffer, resolve func(specifier string, kind
w.Write(q)
w.Write(a[2])
} else {
w.Write(expr)
w.Write(stmt)
}
importOrExportDeclFound = false
} else {
w.Write(expr)
w.Write(stmt)
}
} else {
w.Write(expr)
w.Write(stmt)
}
}
i++
}
err = exprScanner.Err()
err = stmtScanner.Err()
if err != nil {
return
}
Expand All @@ -197,8 +201,8 @@ func parseDts(r io.Reader, w *bytes.Buffer, resolve func(specifier string, kind
return
}

// A split function for a Scanner
func splitExpr(data []byte, atEOF bool) (advance int, token []byte, err error) {
// A split function for bufio.Scanner to split javascript statement
func splitJSStmt(data []byte, atEOF bool) (advance int, token []byte, err error) {
var commentScope bool
var stringScope byte
for i := 0; i < len(data); i++ {
Expand Down Expand Up @@ -244,6 +248,7 @@ func splitExpr(data []byte, atEOF bool) (advance int, token []byte, err error) {
return 0, data, bufio.ErrFinalToken
}

// trimSpace trims leading and trailing spaces, tabs, newlines and carriage returns
func trimSpace(line []byte) ([]byte, []byte) {
s := 0
l := len(line)
Expand Down
39 changes: 15 additions & 24 deletions server/dts_transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package server
import (
"bytes"
"fmt"
"io"
"os"
"path"
"strings"
Expand Down Expand Up @@ -48,7 +47,7 @@ func transformDTS(ctx *BuildContext, dts string, buildArgsPrefix string, marker
}

dtsFilePath := path.Join(ctx.wd, "node_modules", ctx.esmPath.PkgName, dts)
dtsWD := path.Dir(dtsFilePath)
dtsWd := path.Dir(dtsFilePath)
dtsFile, err := os.Open(dtsFilePath)
if err != nil {
// if the dts file does not exist, print a warning but continue to build
Expand All @@ -65,13 +64,14 @@ func transformDTS(ctx *BuildContext, dts string, buildArgsPrefix string, marker

buffer, recycle := NewBuffer()
defer recycle()

internalDts := set.New[string]()
withNodeBuiltinModule := false
hasReferenceTypesNode := false

err = parseDts(dtsFile, buffer, func(specifier string, kind TsImportKind, position int) (string, error) {
if ctx.esmPath.PkgName == "@types/node" {
return specifier, nil
if strings.HasPrefix(specifier, "node:") || nodeBuiltinModules[specifier] || isRelPathSpecifier(specifier) {
return specifier, nil
}
}

// normalize specifier
Expand All @@ -82,7 +82,7 @@ func transformDTS(ctx *BuildContext, dts string, buildArgsPrefix string, marker
if !endsWith(specifier, ".d.ts", ".d.mts") {
var p PackageJSONRaw
var hasTypes bool
if utils.ParseJSONFile(path.Join(dtsWD, specifier, "package.json"), &p) == nil {
if utils.ParseJSONFile(path.Join(dtsWd, specifier, "package.json"), &p) == nil {
dir := path.Join("/", path.Dir(dts))
if types := p.Types.MainString(); types != "" {
specifier, _ = relPath(dir, "/"+path.Join(dir, specifier, types))
Expand All @@ -93,19 +93,19 @@ func transformDTS(ctx *BuildContext, dts string, buildArgsPrefix string, marker
}
}
if !hasTypes {
if existsFile(path.Join(dtsWD, specifier+".d.mts")) {
if existsFile(path.Join(dtsWd, specifier+".d.mts")) {
specifier = specifier + ".d.mts"
} else if existsFile(path.Join(dtsWD, specifier+".d.ts")) {
} else if existsFile(path.Join(dtsWd, specifier+".d.ts")) {
specifier = specifier + ".d.ts"
} else if existsFile(path.Join(dtsWD, specifier, "index.d.mts")) {
} else if existsFile(path.Join(dtsWd, specifier, "index.d.mts")) {
specifier = strings.TrimSuffix(specifier, "/") + "/index.d.mts"
} else if existsFile(path.Join(dtsWD, specifier, "index.d.ts")) {
} else if existsFile(path.Join(dtsWd, specifier, "index.d.ts")) {
specifier = strings.TrimSuffix(specifier, "/") + "/index.d.ts"
} else if endsWith(specifier, ".js", ".mjs", ".cjs") {
specifier = stripModuleExt(specifier)
if existsFile(path.Join(dtsWD, specifier+".d.mts")) {
if existsFile(path.Join(dtsWd, specifier+".d.mts")) {
specifier = specifier + ".d.mts"
} else if existsFile(path.Join(dtsWD, specifier+".d.ts")) {
} else if existsFile(path.Join(dtsWd, specifier+".d.ts")) {
specifier = specifier + ".d.ts"
}
}
Expand All @@ -120,13 +120,12 @@ func transformDTS(ctx *BuildContext, dts string, buildArgsPrefix string, marker
return specifier, nil
}

if (kind == TsReferencePath || kind == TsReferenceTypes) && specifier == "node" {
hasReferenceTypesNode = true
return fmt.Sprintf("{ESM_CDN_ORIGIN}/@types/node@%s/index.d.ts", nodeTypesVersion), nil
if kind == TsReferenceTypes && specifier == "node" {
// return empty string to ignore the reference types 'node'
return "", nil
}

if specifier == "node" || isNodeBuiltInModule(specifier) {
withNodeBuiltinModule = true
return specifier, nil
}

Expand Down Expand Up @@ -240,14 +239,6 @@ func transformDTS(ctx *BuildContext, dts string, buildArgsPrefix string, marker
}
dtsFile.Close()

if withNodeBuiltinModule && !hasReferenceTypesNode {
buf, recycle := NewBuffer()
defer recycle()
fmt.Fprintf(buf, "/// <reference path=\"{ESM_CDN_ORIGIN}/@types/node@%s/index.d.ts\" />\n", nodeTypesVersion)
io.Copy(buf, buffer)
buffer = buf
}

err = buildStorage.Put(savePath, bytes.NewReader(ctx.rewriteDTS(dts, buffer.Bytes())))
if err != nil {
return
Expand Down
12 changes: 1 addition & 11 deletions server/npm.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,17 +331,7 @@ func (rc *NpmRC) StoreDir() string {
}

func (rc *NpmRC) getPackageInfo(name string, semver string) (info *PackageJSON, err error) {
// use fixed version for `@types/node`
if name == "@types/node" {
info = &PackageJSON{
Name: "@types/node",
Version: nodeTypesVersion,
Types: "index.d.ts",
}
return
}

// strip leading `=` or `v`
// strip leading `=` or `v` from semver
if (strings.HasPrefix(semver, "=") || strings.HasPrefix(semver, "v")) && regexpVersionStrict.MatchString(semver[1:]) {
semver = semver[1:]
}
Expand Down
Loading

0 comments on commit f5c8f83

Please sign in to comment.