Skip to content

Commit

Permalink
Using BoltDB to persist build meta (#1014)
Browse files Browse the repository at this point in the history
  • Loading branch information
ije authored Jan 14, 2025
1 parent 1ad9072 commit 6e9ee25
Show file tree
Hide file tree
Showing 14 changed files with 557 additions and 320 deletions.
62 changes: 62 additions & 0 deletions HOSTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,65 @@ FROM ghcr.io/esm-dev/esm.sh:latest
ADD --chown=esm:esm ./config.json /etc/esmd/config.json
CMD ["esmd", "--config", "/etc/esmd/config.json"]
```

## Deploy with CloudFlare CDN

To deploy the server with CloudFlare CDN, you need to create following cache rules in the CloudFlare dashboard, and each rule should be set to **"Eligible for cache"**:

### 1. Cache `.d.ts` Files

```ruby
(ends_with(http.request.uri.path, ".d.ts")) or
(ends_with(http.request.uri.path, ".d.mts")) or
(ends_with(http.request.uri.path, ".d.cts"))
```

### 2. Cache Package Asset Files

```ruby
(http.request.uri.path.extension in {"node" "wasm" "less" "sass" "scss" "stylus" "styl" "json" "jsonc" "csv" "xml" "plist" "tmLanguage" "tmTheme" "yml" "yaml" "txt" "glsl" "frag" "vert" "md" "mdx" "markdown" "html" "htm" "svg" "png" "jpg" "jpeg" "webp" "gif" "ico" "eot" "ttf" "otf" "woff" "woff2" "m4a" "mp3" "m3a" "ogg" "oga" "wav" "weba" "gz" "tgz" "css" "map"})
```

### 3. Cache `?target=*`

```ruby
(http.request.uri.query contains "target=es2015") or
(http.request.uri.query contains "target=es2016") or
(http.request.uri.query contains "target=es2017") or
(http.request.uri.query contains "target=es2018") or
(http.request.uri.query contains "target=es2019") or
(http.request.uri.query contains "target=es2020") or
(http.request.uri.query contains "target=es2021") or
(http.request.uri.query contains "target=es2022") or
(http.request.uri.query contains "target=es2023")or
(http.request.uri.query contains "target=es2024") or
(http.request.uri.query contains "target=esnext") or
(http.request.uri.query contains "target=denonext") or
(http.request.uri.query contains "target=deno") or
(http.request.uri.query contains "target=node")
```

### 4. Cache "/(target)/"

```ruby
(http.request.uri.path contains "/es2015/" and http.request.uri.path.extension in {"mjs" "map" "css"}) or
(http.request.uri.path contains "/es2016/" and http.request.uri.path.extension in {"mjs" "map" "css"}) or
(http.request.uri.path contains "/es2017/" and http.request.uri.path.extension in {"mjs" "map" "css"}) or
(http.request.uri.path contains "/es2018/" and http.request.uri.path.extension in {"mjs" "map" "css"}) or
(http.request.uri.path contains "/es2019/" and http.request.uri.path.extension in {"mjs" "map" "css"}) or
(http.request.uri.path contains "/es2020/" and http.request.uri.path.extension in {"mjs" "map" "css"}) or
(http.request.uri.path contains "/es2021/" and http.request.uri.path.extension in {"mjs" "map" "css"}) or
(http.request.uri.path contains "/es2022/" and http.request.uri.path.extension in {"mjs" "map" "css"}) or
(http.request.uri.path contains "/es2023/" and http.request.uri.path.extension in {"mjs" "map" "css"}) or
(http.request.uri.path contains "/es2024/" and http.request.uri.path.extension in {"mjs" "map" "css"}) or
(http.request.uri.path contains "/esnext/" and http.request.uri.path.extension in {"mjs" "map" "css"}) or
(http.request.uri.path contains "/denonext/" and http.request.uri.path.extension in {"mjs" "map" "css"}) or
(http.request.uri.path contains "/deno/" and http.request.uri.path.extension in {"mjs" "map" "css"}) or
(http.request.uri.path contains "/node/" and http.request.uri.path.extension in {"mjs" "map" "css"})
```

### 5. Bypass Cache for Deno/Bun/Node

```ruby
(not starts_with(http.user_agent, "Deno/") and not starts_with(http.user_agent, "Bun/") and not starts_with(http.user_agent, "Node/") and not starts_with(http.user_agent, "Node.js/") and http.user_agent ne "undici")
```
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ dev/cli:

dev: config.json
@rm -rf .esmd/storage
@rm -rf .esmd/esm.db
@go run -tags debug main.go --config=config.json

.PHONY: test
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/yuin/goldmark-meta v1.1.0
golang.org/x/net v0.34.0
golang.org/x/term v0.28.0
go.etcd.io/bbolt v1.3.11
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ 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=
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
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=
Expand Down
90 changes: 44 additions & 46 deletions server/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ const (
)

type BuildContext struct {
esm EsmPath
npmrc *NpmRC
db DB
storage storage.Storage
esm EsmPath
args BuildArgs
bundleMode BundleMode
externalAll bool
Expand All @@ -47,20 +49,6 @@ type BuildContext struct {
smOffset int
}

type BuildMeta struct {
CJS bool `json:"cjs,omitempty"`
HasCSS bool `json:"hasCSS,omitempty"`
TypesOnly bool `json:"typesOnly,omitempty"`
ExportDefault bool `json:"exportDefault,omitempty"`
Imports []string `json:"imports,omitempty"`
Dts string `json:"dts,omitempty"`
}

type Ref struct {
entries *set.Set[string]
importers *set.Set[string]
}

var (
regexpESMInternalIdent = regexp.MustCompile(`__[a-zA-Z]+\$`)
regexpVarDecl = regexp.MustCompile(`var ([\w$]+)\s*=\s*[\w$]+$`)
Expand Down Expand Up @@ -102,28 +90,29 @@ func (ctx *BuildContext) Path() string {
}

func (ctx *BuildContext) Exists() (meta *BuildMeta, ok bool, err error) {
key := ctx.getSavepath() + ".meta"
key := ctx.npmrc.zoneId + ":" + ctx.Path()
meta, err = withLRUCache(key, func() (*BuildMeta, error) {
r, _, err := buildStorage.Get(key)
metadata, err := ctx.db.Get(key)
if err != nil {
log.Errorf("db.get(%s): %v", key, err)
return nil, err
}
defer r.Close()

var meta BuildMeta
if json.NewDecoder(r).Decode(&meta) == nil {
return &meta, nil
if metadata == nil {
return nil, storage.ErrNotFound
}

// delete the invalid meta file in the storage
buildStorage.Delete(key)
return nil, storage.ErrNotFound
meta, err := decodeBuildMeta(metadata)
if err != nil {
// delete the invalid metadata
ctx.db.Delete(key)
return nil, storage.ErrNotFound
}
return meta, nil
})
if err != nil {
if err == storage.ErrNotFound {
err = nil
}
return nil, false, err
return
}
ok = true
return
Expand Down Expand Up @@ -167,16 +156,12 @@ func (ctx *BuildContext) Build() (meta *BuildMeta, err error) {
return
}

// save the build result into db
key := ctx.getSavepath() + ".meta"
buf, recycle := NewBuffer()
defer recycle()
err = json.NewEncoder(buf).Encode(meta)
// save the build result to the storage
key := ctx.npmrc.zoneId + ":" + ctx.Path()
err = ctx.db.Put(key, encodeBuildMeta(meta))
if err != nil {
return
}
if e := buildStorage.Put(key, buf); e != nil {
log.Errorf("db: %v", e)
log.Errorf("db.put(%s): %v", key, err)
err = errors.New("db: " + err.Error())
}
return
}
Expand Down Expand Up @@ -278,13 +263,13 @@ func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, include
defer recycle()
buffer.WriteString("export default ")
buffer.Write(jsonData)
err = buildStorage.Put(ctx.getSavepath(), buffer)
err = ctx.storage.Put(ctx.getSavepath(), buffer)
if err != nil {
log.Errorf("storage.put(%s): %v", ctx.getSavepath(), err)
err = errors.New("storage: " + err.Error())
return
}
meta = &BuildMeta{
ExportDefault: true,
}
meta = &BuildMeta{ExportDefault: true}
return
}
}
Expand All @@ -309,8 +294,10 @@ func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, include
return
}
b := &BuildContext{
esm: dep,
npmrc: ctx.npmrc,
db: ctx.db,
storage: ctx.storage,
esm: dep,
args: ctx.args,
externalAll: ctx.externalAll,
target: ctx.target,
Expand All @@ -331,8 +318,10 @@ func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, include
if meta.ExportDefault {
fmt.Fprintf(buf, `export { default } from "%s";`, importUrl)
}
err = buildStorage.Put(ctx.getSavepath(), buf)
err = ctx.storage.Put(ctx.getSavepath(), buf)
if err != nil {
log.Errorf("storage.put(%s): %v", ctx.getSavepath(), err)
err = errors.New("storage: " + err.Error())
return
}
meta.Dts, err = ctx.resloveDTS(entry)
Expand Down Expand Up @@ -1264,8 +1253,10 @@ REBUILD:
isEsModule[i] = true
} else {
b := &BuildContext{
esm: dep,
npmrc: ctx.npmrc,
db: ctx.db,
storage: ctx.storage,
esm: dep,
args: ctx.args,
externalAll: ctx.externalAll,
target: ctx.target,
Expand Down Expand Up @@ -1333,8 +1324,10 @@ REBUILD:
finalJS.WriteString(".map")
}

err = buildStorage.Put(ctx.getSavepath(), finalJS)
err = ctx.storage.Put(ctx.getSavepath(), finalJS)
if err != nil {
log.Errorf("storage.put(%s): %v", ctx.getSavepath(), err)
err = errors.New("storage: " + err.Error())
return
}
}
Expand All @@ -1343,8 +1336,11 @@ REBUILD:
for _, file := range res.OutputFiles {
if strings.HasSuffix(file.Path, ".css") {
savePath := ctx.getSavepath()
err = buildStorage.Put(strings.TrimSuffix(savePath, path.Ext(savePath))+".css", bytes.NewReader(file.Contents))
savePath = strings.TrimSuffix(savePath, path.Ext(savePath)) + ".css"
err = ctx.storage.Put(savePath, bytes.NewReader(file.Contents))
if err != nil {
log.Errorf("storage.put(%s): %v", savePath, err)
err = errors.New("storage: " + err.Error())
return
}
meta.HasCSS = true
Expand All @@ -1362,8 +1358,10 @@ REBUILD:
buf, recycle := NewBuffer()
defer recycle()
if json.NewEncoder(buf).Encode(sourceMap) == nil {
err = buildStorage.Put(ctx.getSavepath()+".map", buf)
err = ctx.storage.Put(ctx.getSavepath()+".map", buf)
if err != nil {
log.Errorf("storage.put(%s): %v", ctx.getSavepath()+".map", err)
err = errors.New("storage: " + err.Error())
return
}
}
Expand Down
Loading

0 comments on commit 6e9ee25

Please sign in to comment.