From 05e4e1d59351d8e5c93a16ae84bebea6ff773f57 Mon Sep 17 00:00:00 2001 From: Je Xia Date: Sun, 12 Jan 2025 13:46:54 +0800 Subject: [PATCH] Refactor (#996) --- README.md | 12 +-- go.mod | 10 +-- go.sum | 20 ++--- server/build.go | 3 - server/build_resolver.go | 1 - server/legacy_router.go | 68 ++++++++-------- server/router.go | 159 ++++++++++++++++++------------------- server/server.go | 8 +- test/legacy-routes/test.ts | 8 +- 9 files changed, 138 insertions(+), 151 deletions(-) diff --git a/README.md b/README.md index 6c8406019..f19f36e9b 100644 --- a/README.md +++ b/README.md @@ -131,10 +131,10 @@ Or you can override the bundling strategy by adding the `esm.sh` field to your ` } ``` -You can also use the `?bundle=all` query to bundle the module along with all its external dependencies (excluding those in `peerDependencies`) into a single JavaScript file. +You can also add the `?standalone` flag to bundle the module along with all its external dependencies (excluding those in `peerDependencies`) into a single JavaScript file. ```js -import { Button } from "https://esm.sh/antd?bundle=all"; +import { Button } from "https://esm.sh/antd?standalone"; ``` ### Tree Shaking @@ -150,23 +150,23 @@ import { __await, __rest } from "https://esm.sh/tslib?exports=__await,__rest"; / By using this feature, you can take advantage of tree shaking with esbuild and achieve a smaller bundle size. **Note, this feature doesn't work with CommonJS modules.** -### Development Mode +### Development Build ```js import React from "https://esm.sh/react?dev"; ``` -With the `?dev` option, esm.sh builds a module with `process.env.NODE_ENV` set to `"development"` or based on the +With the `?dev` query, esm.sh builds a module with `process.env.NODE_ENV` set to `"development"` or based on the condition `development` in the `exports` field. This is useful for libraries that have different behavior in development and production. For example, React uses a different warning message in development mode. ### ESBuild Options By default, esm.sh checks the `User-Agent` header to determine the build target. You can also specify the `target` by -adding `?target`, available targets are: **es2015** - **es2022**, **esnext**, **deno**, **denonext**, and **node**. +adding `?target`, available targets are: **es2015** - **es2024**, **esnext**, **deno**, **denonext**, and **node**. ```js -import React from "https://esm.sh/react?target=esnext"; +import React from "https://esm.sh/react?target=es2022"; ``` Other supported options of esbuild: diff --git a/go.mod b/go.mod index b00587b1d..244c7eb01 100644 --- a/go.mod +++ b/go.mod @@ -8,19 +8,19 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/ije/esbuild-internal v0.24.2 github.com/ije/gox v0.9.7 - github.com/ije/rex v1.14.5 + github.com/ije/rex v1.14.6 github.com/mssola/useragent v1.0.0 github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark-meta v1.1.0 - golang.org/x/net v0.33.0 - golang.org/x/term v0.27.0 + golang.org/x/net v0.34.0 + golang.org/x/term v0.28.0 ) require ( github.com/andybalholm/brotli v1.1.1 // indirect github.com/rs/cors v1.11.1 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 6117a71bc..e94ea2d9e 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/ije/esbuild-internal v0.24.2 h1:i1sjIu6suFZ1Arc4w0gWNE8OLLiAhEQ3nhU98 github.com/ije/esbuild-internal v0.24.2/go.mod h1:s7HvKZ4ZGifyzvgWpSwnJOQTr6b+bsgfNBZ8HAEwwSM= github.com/ije/gox v0.9.7 h1:KfEiWOvk/ZqhJnTxJCWMB8mkBLi3HYhGJrrxQyua204= github.com/ije/gox v0.9.7/go.mod h1:3GTaK8WXf6oxRbrViLqKNLTNcMR871Dz0zoujFNmG48= -github.com/ije/rex v1.14.5 h1:Db6lqRJOMxqeiSs4cixa8AkIqkldhMg0mO+qdHmrOUE= -github.com/ije/rex v1.14.5/go.mod h1:+ZeiG36LreDX5XrIl4i4feyvjr5kzMcHCO/a6LIoS48= +github.com/ije/rex v1.14.6 h1:L+qhRh996+kOQFcRdu3yjPr0MZlZjCdhFVwqYriwToY= +github.com/ije/rex v1.14.6/go.mod h1:Gvl2st1enT+SilH1q2enM8o37evm0IN/cx9F+B0z7jU= github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5o= github.com/mssola/useragent v1.0.0/go.mod h1:hz9Cqz4RXusgg1EdI4Al0INR62kP7aPSRNHnpU+b85Y= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= @@ -22,15 +22,15 @@ github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/server/build.go b/server/build.go index a374447a3..7d3bf1161 100644 --- a/server/build.go +++ b/server/build.go @@ -35,7 +35,6 @@ type BuildContext struct { bundleMode BundleMode externalAll bool target string - pinedTarget bool dev bool wd string pkgJson *PackageJSON @@ -315,7 +314,6 @@ func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, include args: ctx.args, externalAll: ctx.externalAll, target: ctx.target, - pinedTarget: ctx.pinedTarget, dev: ctx.dev, } err = b.install() @@ -1271,7 +1269,6 @@ REBUILD: args: ctx.args, externalAll: ctx.externalAll, target: ctx.target, - pinedTarget: ctx.pinedTarget, dev: ctx.dev, } err = b.install() diff --git a/server/build_resolver.go b/server/build_resolver.go index 311f5fe75..ca6d2ad0e 100644 --- a/server/build_resolver.go +++ b/server/build_resolver.go @@ -1239,7 +1239,6 @@ func (ctx *BuildContext) analyzeSplitting() (err error) { args: ctx.args, externalAll: ctx.externalAll, target: ctx.target, - pinedTarget: ctx.pinedTarget, dev: ctx.dev, wd: ctx.wd, pkgJson: ctx.pkgJson, diff --git a/server/legacy_router.go b/server/legacy_router.go index 38228fc73..dcae23900 100644 --- a/server/legacy_router.go +++ b/server/legacy_router.go @@ -24,15 +24,15 @@ func esmLegacyRouter(ctx *rex.Context) any { method := ctx.R.Method pathname := ctx.R.URL.Path -Start: +START: // build API (deprecated) if pathname == "/build" { if method == "POST" { return rex.Status(403, "The `/build` API has been deprecated.") } if method == "GET" { - ctx.Header.Set("Content-Type", ctJavaScript) - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("Content-Type", ctJavaScript) + ctx.SetHeader("Cache-Control", ccImmutable) return ` const deprecated = new Error("[esm.sh] The build API has been deprecated.") export function build(_) { throw deprecated } @@ -46,7 +46,7 @@ Start: // `/react-dom@18.3.1&pin=v135` if strings.Contains(pathname, "&pin=") { - return legacyESM(ctx, pathname) + return legacyESM(ctx, pathname, false) } // `/react-dom@18.3.1?pin=v135` @@ -58,14 +58,14 @@ Start: if bv <= 0 || bv > 135 { return rex.Status(400, "Invalid `pin` query") } - return legacyESM(ctx, pathname) + return legacyESM(ctx, pathname, false) } } // `/stable/react@18.3.1?dev` // `/stable/react@18.3.1/es2022/react.mjs` if strings.HasPrefix(pathname, "/stable/") { - return legacyESM(ctx, pathname[7:]) + return legacyESM(ctx, pathname[7:], true) } // `/v135/react-dom@18.3.1?dev` @@ -78,15 +78,15 @@ Start: return rex.Status(400, "Invalid Module Path") } if path == "" && strings.HasPrefix(ctx.UserAgent(), "Deno/") { - ctx.Header.Set("Content-Type", ctJavaScript) - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("Content-Type", ctJavaScript) + ctx.SetHeader("Cache-Control", ccImmutable) return `throw new Error("[esm.sh] The deno CLI has been deprecated, please use our vscode extension instead: https://marketplace.visualstudio.com/items?itemName=ije.esm-vscode")` } if path == "build" { pathname = "/build" - goto Start + goto START } - return legacyESM(ctx, "/"+path) + return legacyESM(ctx, "/"+path, true) } } @@ -98,8 +98,8 @@ Start: return ctx.Next() } -func legacyESM(ctx *rex.Context, pathname string) any { - pkgName, pkgVersion, isBuildDist, err := splitLegacyESMPath(pathname) +func legacyESM(ctx *rex.Context, modulePath string, hasBuildVersionPrefix bool) any { + pkgName, pkgVersion, hasTargetSegment, err := splitLegacyESMPath(modulePath) if err != nil { return rex.Status(400, err.Error()) } @@ -119,26 +119,26 @@ func legacyESM(ctx *rex.Context, pathname string) any { return redirect(ctx, getOrigin(ctx)+strings.Replace(ctx.R.URL.Path, "@"+pkgVersion, "@"+pkgInfo.Version, 1)+query, false) } savePath := "legacy/" + normalizeSavePath("", ctx.R.URL.Path[1:]) - if isBuildDist || endsWith(pathname, ".d.ts", ".d.mts") { + if (hasBuildVersionPrefix && hasTargetSegment) || endsWith(modulePath, ".d.ts", ".d.mts") { f, _, e := buildStorage.Get(savePath) if e != nil && e != storage.ErrNotFound { return rex.Status(500, "Storage Error: "+e.Error()) } if e == nil { - switch path.Ext(pathname) { + switch path.Ext(modulePath) { case ".js", ".mjs": - ctx.Header.Set("Content-Type", ctJavaScript) + ctx.SetHeader("Content-Type", ctJavaScript) case ".ts", ".mts": - ctx.Header.Set("Content-Type", ctTypeScript) + ctx.SetHeader("Content-Type", ctTypeScript) case ".map": - ctx.Header.Set("Content-Type", ctJSON) + ctx.SetHeader("Content-Type", ctJSON) case ".css": - ctx.Header.Set("Content-Type", ctCSS) + ctx.SetHeader("Content-Type", ctCSS) default: f.Close() return rex.Status(404, "Module Not Found") } - ctx.Header.Set("Control-Cache", ccImmutable) + ctx.SetHeader("Control-Cache", ccImmutable) return f // auto closed } } else { @@ -161,14 +161,14 @@ func legacyESM(ctx *rex.Context, pathname string) any { defer f.Close() var ret []string if json.NewDecoder(f).Decode(&ret) == nil && len(ret) >= 2 { - ctx.Header.Set("Content-Type", ctJavaScript) - ctx.Header.Set("Control-Cache", ccImmutable) - ctx.Header.Set("X-ESM-Id", ret[0]) + ctx.SetHeader("Content-Type", ctJavaScript) + ctx.SetHeader("Control-Cache", ccImmutable) + ctx.SetHeader("X-ESM-Id", ret[0]) if varyUA { appendVaryHeader(ctx.W.Header(), "User-Agent") } if len(ret) == 3 { - ctx.Header.Set("X-TypeScript-Types", getOrigin(ctx)+ret[1]) + ctx.SetHeader("X-TypeScript-Types", getOrigin(ctx)+ret[1]) return ret[2] } return ret[1] @@ -193,19 +193,19 @@ func legacyESM(ctx *rex.Context, pathname string) any { if err != nil { return rex.Status(500, "Failed to fetch data from the legacy esm.sh server") } - ctx.Header.Set("Cache-Control", "public, max-age=600") + ctx.SetHeader("Cache-Control", "public, max-age=600") return rex.Status(res.StatusCode, data) } - if isBuildDist || endsWith(pathname, ".d.ts", ".d.mts") { + if (hasBuildVersionPrefix && hasTargetSegment) || endsWith(modulePath, ".d.ts", ".d.mts") { buf, recycle := NewBuffer() defer recycle() err := buildStorage.Put(savePath, io.TeeReader(res.Body, buf)) if err != nil { return rex.Status(500, "Storage Error") } - ctx.Header.Set("Content-Type", res.Header.Get("Content-Type")) - ctx.Header.Set("Control-Cache", ccImmutable) + ctx.SetHeader("Content-Type", res.Header.Get("Content-Type")) + ctx.SetHeader("Control-Cache", ccImmutable) return buf.Bytes() } else { code, err := io.ReadAll(res.Body) @@ -214,7 +214,7 @@ func legacyESM(ctx *rex.Context, pathname string) any { } esmId := res.Header.Get("X-Esm-Id") if esmId == "" { - ctx.Header.Set("Cache-Control", "public, max-age=600") + ctx.SetHeader("Cache-Control", "public, max-age=600") return rex.Status(502, "Unexpected response from the legacy esm.sh server") } dts := res.Header.Get("X-TypeScript-Types") @@ -235,20 +235,20 @@ func legacyESM(ctx *rex.Context, pathname string) any { if err != nil { return rex.Status(500, "Storage Error") } - ctx.Header.Set("Content-Type", res.Header.Get("Content-Type")) - ctx.Header.Set("Control-Cache", ccImmutable) - ctx.Header.Set("X-ESM-Id", esmId) + ctx.SetHeader("Content-Type", res.Header.Get("Content-Type")) + ctx.SetHeader("Control-Cache", ccImmutable) + ctx.SetHeader("X-ESM-Id", esmId) if query != "" && !ctx.R.URL.Query().Has("target") { appendVaryHeader(ctx.W.Header(), "User-Agent") } if dts != "" { - ctx.Header.Set("X-TypeScript-Types", getOrigin(ctx)+dts) + ctx.SetHeader("X-TypeScript-Types", getOrigin(ctx)+dts) } return code } } -func splitLegacyESMPath(pathname string) (pkgName string, version string, isBuildDist bool, err error) { +func splitLegacyESMPath(pathname string) (pkgName string, version string, hasTargetSegment bool, err error) { if strings.HasPrefix(pathname, "/gh/") { if !strings.ContainsRune(pathname[4:], '/') { err = errors.New("invalid path") @@ -258,7 +258,7 @@ func splitLegacyESMPath(pathname string) (pkgName string, version string, isBuil pathname = "/@" + pathname[4:] } - pkgName, maybeVersion, _, isBuildDist := splitEsmPath(pathname) + pkgName, maybeVersion, _, hasTargetSegment := splitEsmPath(pathname) if !validatePackageName(pkgName) { err = fmt.Errorf("invalid package name '%s'", pkgName) return diff --git a/server/router.go b/server/router.go index a5f72d128..e5e1b5e02 100644 --- a/server/router.go +++ b/server/router.go @@ -145,7 +145,7 @@ func esmRouter() rex.Handle { go buildStorage.Put(savePath+".map", strings.NewReader(output.Map)) } go buildStorage.Put(savePath, strings.NewReader(output.Code)) - ctx.Header.Set("Cache-Control", ccMustRevalidate) + ctx.SetHeader("Cache-Control", ccMustRevalidate) return output case "/purge": @@ -216,8 +216,8 @@ func esmRouter() rex.Handle { if err != nil { return err } - ctx.Header.Set("Content-Type", "image/x-icon") - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("Content-Type", "image/x-icon") + ctx.SetHeader("Cache-Control", ccImmutable) return favicon case "/robots.txt": @@ -225,7 +225,7 @@ func esmRouter() rex.Handle { case "/": if strings.HasPrefix(ctx.UserAgent(), "Deno/") { - ctx.Header.Set("Content-Type", ctJavaScript) + ctx.SetHeader("Content-Type", ctJavaScript) return `throw new Error("[esm.sh] The deno CLI has been deprecated, please use our vscode extension instead: https://marketplace.visualstudio.com/items?itemName=ije.esm-vscode")` } if ctx.R.Header.Get("If-None-Match") == globalETag { @@ -276,9 +276,9 @@ func esmRouter() rex.Handle { if err != nil { return rex.Status(500, err.Error()) } - ctx.Header.Set("Content-Type", ctHTML) - ctx.Header.Set("Cache-Control", ccMustRevalidate) - ctx.Header.Set("Etag", globalETag) + ctx.SetHeader("Content-Type", ctHTML) + ctx.SetHeader("Cache-Control", ccMustRevalidate) + ctx.SetHeader("Etag", globalETag) return indexHTML case "/status.json": @@ -313,7 +313,7 @@ func esmRouter() rex.Handle { disk = "error" } - ctx.Header.Set("Cache-Control", ccMustRevalidate) + ctx.SetHeader("Cache-Control", ccMustRevalidate) return map[string]any{ "buildQueue": q[:i], "version": VERSION, @@ -408,15 +408,15 @@ func esmRouter() rex.Handle { return rex.Status(500, err.Error()) } if DEBUG { - ctx.Header.Set("Cache-Control", ccMustRevalidate) + ctx.SetHeader("Cache-Control", ccMustRevalidate) } else { - ctx.Header.Set("Cache-Control", ccOneDay) + ctx.SetHeader("Cache-Control", ccOneDay) } - ctx.Header.Set("Etag", globalETag) + ctx.SetHeader("Etag", globalETag) if targetFromUA { appendVaryHeader(ctx.W.Header(), "User-Agent") } - ctx.Header.Set("Content-Type", ctJavaScript) + ctx.SetHeader("Content-Type", ctJavaScript) return js } @@ -432,12 +432,12 @@ func esmRouter() rex.Handle { return rex.Status(500, err.Error()) } if strings.HasSuffix(pathname, ".map") { - ctx.Header.Set("Content-Type", ctJSON) + ctx.SetHeader("Content-Type", ctJSON) } else { - ctx.Header.Set("Content-Type", ctJavaScript) + ctx.SetHeader("Content-Type", ctJavaScript) } - ctx.Header.Set("Last-Modified", fi.ModTime().UTC().Format(http.TimeFormat)) - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("Last-Modified", fi.ModTime().UTC().Format(http.TimeFormat)) + ctx.SetHeader("Cache-Control", ccImmutable) return f // auto closed } @@ -455,16 +455,16 @@ func esmRouter() rex.Handle { code = []byte("export default {}") } if strings.HasPrefix(name, "chunk-") { - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("Cache-Control", ccImmutable) } else { ifNoneMatch := ctx.R.Header.Get("If-None-Match") if ifNoneMatch == globalETag && !DEBUG { return rex.Status(http.StatusNotModified, nil) } - ctx.Header.Set("Cache-Control", ccOneDay) - ctx.Header.Set("Etag", globalETag) + ctx.SetHeader("Cache-Control", ccOneDay) + ctx.SetHeader("Etag", globalETag) } - ctx.Header.Set("Content-Type", ctJavaScript) + ctx.SetHeader("Content-Type", ctJavaScript) return code } @@ -475,18 +475,18 @@ func esmRouter() rex.Handle { return rex.Status(404, "not found") } if !DEBUG { - ctx.Header.Set("Cache-Control", ccMustRevalidate) + ctx.SetHeader("Cache-Control", ccMustRevalidate) } else { etag := fmt.Sprintf(`W/"%d%d"`, startTime.Unix(), len(data)) if ifNoneMatch := ctx.R.Header.Get("If-None-Match"); ifNoneMatch == etag { return rex.Status(http.StatusNotModified, nil) } - ctx.Header.Set("Etag", etag) - ctx.Header.Set("Cache-Control", ccOneDay) + ctx.SetHeader("Etag", etag) + ctx.SetHeader("Cache-Control", ccOneDay) } contentType := common.ContentType(pathname) if contentType != "" { - ctx.Header.Set("Content-Type", contentType) + ctx.SetHeader("Content-Type", contentType) } return data } @@ -581,8 +581,8 @@ func esmRouter() rex.Handle { return rex.Status(500, err.Error()) } if err == nil { - ctx.Header.Set("Cache-Control", ccImmutable) - ctx.Header.Set("Content-Type", ctCSS) + ctx.SetHeader("Cache-Control", ccImmutable) + ctx.SetHeader("Content-Type", ctCSS) return r // auto closed } res, err := fetchClient.Fetch(ctxUrl, nil) @@ -700,8 +700,8 @@ func esmRouter() rex.Handle { } minifiedCSS := ret.OutputFiles[0].Contents go buildStorage.Put(savePath, bytes.NewReader(minifiedCSS)) - ctx.Header.Set("Cache-Control", ccImmutable) - ctx.Header.Set("Content-Type", ctCSS) + ctx.SetHeader("Cache-Control", ccImmutable) + ctx.SetHeader("Content-Type", ctCSS) return minifiedCSS } else { im := query.Get("im") @@ -817,11 +817,11 @@ func esmRouter() rex.Handle { } body = strings.NewReader(fmt.Sprintf("var style = document.createElement('style');\nstyle.textContent = %s;\ndocument.head.appendChild(style);\nexport default null;", utils.MustEncodeJSON(string(css)))) } - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("Cache-Control", ccImmutable) if extname == ".css" { - ctx.Header.Set("Content-Type", ctCSS) + ctx.SetHeader("Content-Type", ctCSS) } else { - ctx.Header.Set("Content-Type", ctJavaScript) + ctx.SetHeader("Content-Type", ctJavaScript) } return body // auto closed } @@ -996,7 +996,7 @@ func esmRouter() rex.Handle { if rawQuery != "" { query = "?" + rawQuery } - ctx.Header.Set("Cache-Control", fmt.Sprintf("public, max-age=%d", config.NpmQueryCacheTTL)) + ctx.SetHeader("Cache-Control", fmt.Sprintf("public, max-age=%d", config.NpmQueryCacheTTL)) return redirect(ctx, fmt.Sprintf("%s/%s%s%s", origin, pkgName, subPath, query), false) } if pathKind != EsmEntry { @@ -1027,7 +1027,7 @@ func esmRouter() rex.Handle { if rawQuery != "" { query = "?" + rawQuery } - ctx.Header.Set("Cache-Control", fmt.Sprintf("public, max-age=%d", config.NpmQueryCacheTTL)) + ctx.SetHeader("Cache-Control", fmt.Sprintf("public, max-age=%d", config.NpmQueryCacheTTL)) return redirect(ctx, fmt.Sprintf("%s%s/%s@%s%s%s", origin, registryPrefix, pkgName, pkgVersion, subPath, query), false) } } else { @@ -1037,8 +1037,8 @@ func esmRouter() rex.Handle { wasmUrl := origin + pathname fmt.Fprintf(buf, "/* esm.sh - wasm module */\n") fmt.Fprintf(buf, "const data = await fetch(%s).then(r => r.arrayBuffer());\nexport default new WebAssembly.Module(data);", strings.TrimSpace(string(utils.MustEncodeJSON(wasmUrl)))) - ctx.Header.Set("Content-Type", ctJavaScript) - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("Content-Type", ctJavaScript) + ctx.SetHeader("Cache-Control", ccImmutable) return buf } @@ -1170,29 +1170,29 @@ func esmRouter() rex.Handle { } } if endsWith(esm.SubPath, ".js", ".mjs", ".cjs") { - ctx.Header.Set("Content-Type", ctJavaScript) + ctx.SetHeader("Content-Type", ctJavaScript) } else if endsWith(esm.SubPath, ".ts", ".mts", ".cts", ".tsx") { - ctx.Header.Set("Content-Type", ctTypeScript) + ctx.SetHeader("Content-Type", ctTypeScript) } else if strings.HasSuffix(esm.SubPath, ".jsx") { - ctx.Header.Set("Content-Type", "text/jsx; charset=utf-8") + ctx.SetHeader("Content-Type", "text/jsx; charset=utf-8") } else { contentType := common.ContentType(esm.SubPath) if contentType != "" { - ctx.Header.Set("Content-Type", contentType) + ctx.SetHeader("Content-Type", contentType) } } if cacheHit { - ctx.Header.Set("X-Raw-File-Cache-Status", "HIT") + ctx.SetHeader("X-Raw-File-Cache-Status", "HIT") } - ctx.Header.Set("Etag", etag) - ctx.Header.Set("Last-Modified", stat.ModTime().UTC().Format(http.TimeFormat)) - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("Etag", etag) + ctx.SetHeader("Last-Modified", stat.ModTime().UTC().Format(http.TimeFormat)) + ctx.SetHeader("Cache-Control", ccImmutable) if strings.HasSuffix(esm.SubPath, ".json") && query.Has("module") { jsonData, err := io.ReadAll(content) if err != nil { return rex.Status(500, err.Error()) } - ctx.Header.Set("Content-Type", ctJavaScript) + ctx.SetHeader("Content-Type", ctJavaScript) return concatBytes([]byte("export default "), jsonData) } return content // auto closed @@ -1216,16 +1216,16 @@ func esmRouter() rex.Handle { } } if err == nil { - ctx.Header.Set("Last-Modified", stat.ModTime().UTC().Format(http.TimeFormat)) - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("Last-Modified", stat.ModTime().UTC().Format(http.TimeFormat)) + ctx.SetHeader("Cache-Control", ccImmutable) if pathKind == EsmDts { - ctx.Header.Set("Content-Type", ctTypeScript) + ctx.SetHeader("Content-Type", ctTypeScript) } else if pathKind == EsmSourceMap { - ctx.Header.Set("Content-Type", ctJSON) + ctx.SetHeader("Content-Type", ctJSON) } else if strings.HasSuffix(pathname, ".css") { - ctx.Header.Set("Content-Type", ctCSS) + ctx.SetHeader("Content-Type", ctCSS) } else { - ctx.Header.Set("Content-Type", ctJavaScript) + ctx.SetHeader("Content-Type", ctJavaScript) // check `?exports` query jsIndentSet := set.New[string]() if query.Has("exports") { @@ -1471,7 +1471,7 @@ func esmRouter() rex.Handle { return rex.Status(500, "Failed to build types: "+output.err.Error()) } case <-time.After(time.Duration(config.BuildWaitTime) * time.Second): - ctx.Header.Set("Cache-Control", ccMustRevalidate) + ctx.SetHeader("Cache-Control", ccMustRevalidate) return rex.Status(http.StatusRequestTimeout, "timeout, the types is waiting to be built, please try refreshing the page.") } content, _, err = readDts() @@ -1487,8 +1487,8 @@ func esmRouter() rex.Handle { if err != nil { return rex.Status(500, err.Error()) } - ctx.Header.Set("Content-Type", ctTypeScript) - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("Content-Type", ctTypeScript) + ctx.SetHeader("Cache-Control", ccImmutable) return bytes.ReplaceAll(buffer, []byte("{ESM_CDN_ORIGIN}"), []byte(origin)) } @@ -1568,7 +1568,6 @@ func esmRouter() rex.Handle { bundleMode: bundleMode, externalAll: externalAll, target: target, - pinedTarget: !targetFromUA, dev: isDev, } ret, ok, err := buildCtx.Exists() @@ -1581,10 +1580,8 @@ func esmRouter() rex.Handle { case output := <-ch: if output.err != nil { msg := output.err.Error() - if strings.Contains(msg, "no such file or directory") || - strings.Contains(msg, "is not exported from package") || - strings.Contains(msg, "could not resolve build entry") { - ctx.Header.Set("Cache-Control", ccImmutable) + if strings.Contains(msg, "no such file or directory") || strings.Contains(msg, "is not exported from package") || strings.Contains(msg, "could not resolve build entry") { + ctx.SetHeader("Cache-Control", ccImmutable) return rex.Status(404, "module not found") } if strings.HasSuffix(msg, " not found") { @@ -1594,7 +1591,7 @@ func esmRouter() rex.Handle { } ret = output.meta case <-time.After(time.Duration(config.BuildWaitTime) * time.Second): - ctx.Header.Set("Cache-Control", ccMustRevalidate) + ctx.SetHeader("Cache-Control", ccMustRevalidate) return rex.Status(http.StatusRequestTimeout, "timeout, the module is waiting to be built, please try refreshing the page.") } } @@ -1602,9 +1599,9 @@ func esmRouter() rex.Handle { // redirect to `*.d.ts` file if ret.TypesOnly { dtsUrl := origin + ret.Dts - ctx.Header.Set("X-TypeScript-Types", dtsUrl) - ctx.Header.Set("Content-Type", ctJavaScript) - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("X-TypeScript-Types", dtsUrl) + ctx.SetHeader("Content-Type", ctJavaScript) + ctx.SetHeader("Cache-Control", ccImmutable) if ctx.R.Method == http.MethodHead { return []byte{} } @@ -1643,8 +1640,8 @@ func esmRouter() rex.Handle { if ret.ExportDefault { fmt.Fprintf(buf, "export { default } from \"%s\";\n", buildCtx.Path()) } - ctx.Header.Set("Content-Type", ctJavaScript) - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("Content-Type", ctJavaScript) + ctx.SetHeader("Cache-Control", ccImmutable) return buf.Bytes() } savePath := buildCtx.getSavepath() @@ -1659,14 +1656,14 @@ func esmRouter() rex.Handle { } return rex.Status(500, err.Error()) } - ctx.Header.Set("Last-Modified", fi.ModTime().UTC().Format(http.TimeFormat)) - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("Last-Modified", fi.ModTime().UTC().Format(http.TimeFormat)) + ctx.SetHeader("Cache-Control", ccImmutable) if endsWith(savePath, ".css") { - ctx.Header.Set("Content-Type", ctCSS) + ctx.SetHeader("Content-Type", ctCSS) } else if endsWith(savePath, ".map") { - ctx.Header.Set("Content-Type", ctJSON) + ctx.SetHeader("Content-Type", ctJSON) } else { - ctx.Header.Set("Content-Type", ctJavaScript) + ctx.SetHeader("Content-Type", ctJavaScript) if isWorker { defer f.Close() moduleUrl := origin + buildCtx.Path() @@ -1731,7 +1728,7 @@ func esmRouter() rex.Handle { if !ret.CJS && len(exports) > 0 { esm += "?exports=" + strings.Join(exports, ",") } - ctx.Header.Set("X-ESM-Path", esm) + ctx.SetHeader("X-ESM-Path", esm) fmt.Fprintf(buf, "export * from \"%s\";\n", esm) if ret.ExportDefault && (len(exports) == 0 || stringInSlice(exports, "default")) { fmt.Fprintf(buf, "export { default } from \"%s\";\n", esm) @@ -1741,10 +1738,10 @@ func esmRouter() rex.Handle { fmt.Fprintf(buf, "export const { %s } = _;\n", strings.Join(exports, ", ")) } if !noDts && ret.Dts != "" { - ctx.Header.Set("X-TypeScript-Types", origin+ret.Dts) - ctx.Header.Set("Access-Control-Expose-Headers", "X-ESM-Path, X-TypeScript-Types") + ctx.SetHeader("X-TypeScript-Types", origin+ret.Dts) + ctx.SetHeader("Access-Control-Expose-Headers", "X-ESM-Path, X-TypeScript-Types") } else { - ctx.Header.Set("Access-Control-Expose-Headers", "X-ESM-Path") + ctx.SetHeader("Access-Control-Expose-Headers", "X-ESM-Path") } } @@ -1752,11 +1749,11 @@ func esmRouter() rex.Handle { appendVaryHeader(ctx.W.Header(), "User-Agent") } if isExactVersion { - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("Cache-Control", ccImmutable) } else { - ctx.Header.Set("Cache-Control", fmt.Sprintf("public, max-age=%d", config.NpmQueryCacheTTL)) + ctx.SetHeader("Cache-Control", fmt.Sprintf("public, max-age=%d", config.NpmQueryCacheTTL)) } - ctx.Header.Set("Content-Type", ctJavaScript) + ctx.SetHeader("Content-Type", ctJavaScript) if ctx.R.Method == http.MethodHead { return rex.NoContent() } @@ -1784,11 +1781,11 @@ func redirect(ctx *rex.Context, url string, isMovedPermanently bool) any { code := http.StatusFound if isMovedPermanently { code = http.StatusMovedPermanently - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("Cache-Control", ccImmutable) } else { - ctx.Header.Set("Cache-Control", fmt.Sprintf("public, max-age=%d", config.NpmQueryCacheTTL)) + ctx.SetHeader("Cache-Control", fmt.Sprintf("public, max-age=%d", config.NpmQueryCacheTTL)) } - ctx.Header.Set("Location", url) + ctx.SetHeader("Location", url) return rex.Status(code, nil) } @@ -1800,7 +1797,7 @@ func errorJS(ctx *rex.Context, message string) any { buf.Write(utils.MustEncodeJSON(message)) buf.WriteString(");\n") buf.WriteString("export default null;\n") - ctx.Header.Set("Content-Type", ctJavaScript) - ctx.Header.Set("Cache-Control", ccImmutable) + ctx.SetHeader("Content-Type", ctJavaScript) + ctx.SetHeader("Cache-Control", ccImmutable) return buf.Bytes() } diff --git a/server/server.go b/server/server.go index 7ab41bd18..b578a7fa7 100644 --- a/server/server.go +++ b/server/server.go @@ -210,7 +210,7 @@ func customLandingPage(options *LandingPageOptions) rex.Handle { if ctx.R.Header.Get("If-None-Match") == etag { return rex.Status(http.StatusNotModified, nil) } - ctx.Header.Set("Etag", etag) + ctx.SetHeader("Etag", etag) } else { lastModified := res.Header.Get("Last-Modified") if lastModified != "" { @@ -222,14 +222,14 @@ func customLandingPage(options *LandingPageOptions) rex.Handle { return rex.Status(http.StatusNotModified, nil) } } - ctx.Header.Set("Last-Modified", lastModified) + ctx.SetHeader("Last-Modified", lastModified) } } cacheCache := res.Header.Get("Cache-Control") if cacheCache == "" { - ctx.Header.Set("Cache-Control", ccMustRevalidate) + ctx.SetHeader("Cache-Control", ccMustRevalidate) } - ctx.Header.Set("Content-Type", res.Header.Get("Content-Type")) + ctx.SetHeader("Content-Type", res.Header.Get("Content-Type")) return res.Body // auto closed } return ctx.Next() diff --git a/test/legacy-routes/test.ts b/test/legacy-routes/test.ts index 37b12c30f..307183312 100644 --- a/test/legacy-routes/test.ts +++ b/test/legacy-routes/test.ts @@ -164,17 +164,11 @@ Deno.test("legacy routes", async () => { assertStringIncludes(await res.text(), "ManyKeysWeakMap"); } { + // invalid build version const res = await fetch("http://localhost:8080/v136/react-dom@18.3.1/es2022/client.js", { headers: { "User-Agent": "i'm a browser" }, }); res.body?.cancel(); assertEquals(res.status, 400); } - { - const res = await fetch("http://localhost:8080/react-dom@18.3.1/es2022/client.js", { - headers: { "User-Agent": "i'm a browser" }, - }); - res.body?.cancel(); - assertEquals(res.status, 404); - } });