Skip to content

Commit

Permalink
Refactor (esm-dev#996)
Browse files Browse the repository at this point in the history
  • Loading branch information
ije authored Jan 12, 2025
1 parent a699705 commit 05e4e1d
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 151 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
20 changes: 10 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand All @@ -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=
Expand Down
3 changes: 0 additions & 3 deletions server/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ type BuildContext struct {
bundleMode BundleMode
externalAll bool
target string
pinedTarget bool
dev bool
wd string
pkgJson *PackageJSON
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -1271,7 +1269,6 @@ REBUILD:
args: ctx.args,
externalAll: ctx.externalAll,
target: ctx.target,
pinedTarget: ctx.pinedTarget,
dev: ctx.dev,
}
err = b.install()
Expand Down
1 change: 0 additions & 1 deletion server/build_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
68 changes: 34 additions & 34 deletions server/legacy_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -46,7 +46,7 @@ Start:

// `/[email protected]&pin=v135`
if strings.Contains(pathname, "&pin=") {
return legacyESM(ctx, pathname)
return legacyESM(ctx, pathname, false)
}

// `/[email protected]?pin=v135`
Expand All @@ -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/[email protected]?dev`
// `/stable/[email protected]/es2022/react.mjs`
if strings.HasPrefix(pathname, "/stable/") {
return legacyESM(ctx, pathname[7:])
return legacyESM(ctx, pathname[7:], true)
}

// `/v135/[email protected]?dev`
Expand All @@ -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)
}
}

Expand All @@ -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())
}
Expand All @@ -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 {
Expand All @@ -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]
Expand All @@ -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)
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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
Expand Down
Loading

0 comments on commit 05e4e1d

Please sign in to comment.