Skip to content

Commit

Permalink
Improve build resolver for fixing some edge cases (#988)
Browse files Browse the repository at this point in the history
  • Loading branch information
ije authored Jan 7, 2025
1 parent 106f53c commit 38c7c21
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 75 deletions.
113 changes: 59 additions & 54 deletions server/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type BuildContext struct {
wd string
pkgJson *PackageJSON
path string
rawPath string
status string
splitting *set.ReadOnlySet[string]
esmImports [][2]string
Expand Down Expand Up @@ -98,57 +99,7 @@ func (ctx *BuildContext) Path() string {
return ctx.path
}

asteriskPrefix := ""
if ctx.externalAll {
asteriskPrefix = "*"
}

esm := ctx.esm
if ctx.target == "types" {
if strings.HasSuffix(esm.SubPath, ".d.ts") {
ctx.path = fmt.Sprintf(
"/%s%s/%s%s",
asteriskPrefix,
esm.Name(),
ctx.getBuildArgsPrefix(true),
esm.SubPath,
)
} else {
ctx.path = "/" + esm.Specifier()
}
return ctx.path
}

name := strings.TrimSuffix(path.Base(esm.PkgName), ".js")
if esm.SubModuleName != "" {
if esm.SubModuleName == name {
// if the sub-module name is same as the package name
name = "__" + esm.SubModuleName
} else {
name = esm.SubModuleName
}
// workaround for es5-ext "../#/.." path
if esm.PkgName == "es5-ext" {
name = strings.ReplaceAll(name, "/#/", "/%23/")
}
}

if ctx.dev {
name += ".development"
}
if ctx.bundleMode == BundleAll {
name += ".bundle"
} else if ctx.bundleMode == BundleFalse {
name += ".nobundle"
}
ctx.path = fmt.Sprintf(
"/%s%s/%s%s/%s.mjs",
asteriskPrefix,
esm.Name(),
ctx.getBuildArgsPrefix(ctx.target == "types"),
ctx.target,
name,
)
ctx.buildPath()
return ctx.path
}

Expand Down Expand Up @@ -232,6 +183,60 @@ func (ctx *BuildContext) Build() (meta *BuildMeta, err error) {
return
}

func (ctx *BuildContext) buildPath() {
asteriskPrefix := ""
if ctx.externalAll {
asteriskPrefix = "*"
}

esm := ctx.esm
if ctx.target == "types" {
if strings.HasSuffix(esm.SubPath, ".d.ts") {
ctx.path = fmt.Sprintf(
"/%s%s/%s%s",
asteriskPrefix,
esm.Name(),
ctx.getBuildArgsPrefix(true),
esm.SubPath,
)
} else {
ctx.path = "/" + esm.Specifier()
}
return
}

name := strings.TrimSuffix(path.Base(esm.PkgName), ".js")
if esm.SubModuleName != "" {
if esm.SubModuleName == name {
// if the sub-module name is same as the package name
name = "__" + esm.SubModuleName
} else {
name = esm.SubModuleName
}
// workaround for es5-ext "../#/.." path
if esm.PkgName == "es5-ext" {
name = strings.ReplaceAll(name, "/#/", "/%23/")
}
}

if ctx.dev {
name += ".development"
}
if ctx.bundleMode == BundleAll {
name += ".bundle"
} else if ctx.bundleMode == BundleFalse {
name += ".nobundle"
}
ctx.path = fmt.Sprintf(
"/%s%s/%s%s/%s.mjs",
asteriskPrefix,
esm.Name(),
ctx.getBuildArgsPrefix(ctx.target == "types"),
ctx.target,
name,
)
}

func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, includes [][2]string, err error) {
entry := ctx.resolveEntry(ctx.esm)
if entry.isEmpty() {
Expand Down Expand Up @@ -1443,10 +1448,9 @@ func (ctx *BuildContext) buildTypes() (ret *BuildMeta, err error) {

func (ctx *BuildContext) install() (err error) {
if ctx.wd == "" || ctx.pkgJson == nil {
var p *PackageJSON
p, err = ctx.npmrc.installPackage(ctx.esm.Package())
p, err := ctx.npmrc.installPackage(ctx.esm.Package())
if err != nil {
return
return err
}

if ctx.esm.GhPrefix || ctx.esm.PrPrefix {
Expand Down Expand Up @@ -1491,6 +1495,7 @@ func (ctx *BuildContext) install() (err error) {
if isMainModule {
ctx.esm.SubModuleName = ""
ctx.esm.SubPath = ""
ctx.rawPath = ctx.path
ctx.path = ""
}
}
Expand Down
4 changes: 4 additions & 0 deletions server/build_queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ func (q *BuildQueue) run(task *BuildTask) {
q.lock.Lock()
q.queue.Remove(task.el)
delete(q.tasks, task.ctx.Path())
if task.ctx.rawPath != "" {
// the `Build` function may have changed the path
delete(q.tasks, task.ctx.rawPath)
}
q.chann += 1
q.lock.Unlock()

Expand Down
35 changes: 28 additions & 7 deletions server/build_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,43 @@ func (ctx *BuildContext) resolveEntry(esm EsmPath) (entry BuildEntry) {
// reslove sub-module using `exports` conditions if exists
// see https://nodejs.org/api/packages.html#package-entry-points
if pkgJson.Exports.Len() > 0 {
exportEntry := BuildEntry{}
for _, name := range pkgJson.Exports.keys {
conditions, ok := pkgJson.Exports.values[name]
if ok {
if name == "./"+subModuleName || stripEntryModuleExt(name) == "./"+subModuleName {
var exportEntry BuildEntry
conditions, ok := pkgJson.Exports.Get("./" + subModuleName)
if ok {
if s, ok := conditions.(string); ok {
/**
exports: {
"./lib/foo": "./lib/foo.js"
}
*/
exportEntry.update(s, pkgJson.Type == "module")
} else if obj, ok := conditions.(JSONObject); ok {
/**
exports: {
"./lib/foo": {
"require": "./lib/foo.js",
"import": "./esm/foo.js",
"types": "./types/foo.d.ts"
}
}
*/
exportEntry = ctx.resolveConditionExportEntry(obj, pkgJson.Type)
}
} else {
for _, name := range pkgJson.Exports.keys {
conditions := pkgJson.Exports.values[name]
if stripEntryModuleExt(name) == "./"+subModuleName {
if s, ok := conditions.(string); ok {
/**
exports: {
"./lib/foo": "./lib/foo.js"
"./lib/foo.js": "./lib/foo.js"
}
*/
exportEntry.update(s, pkgJson.Type == "module")
} else if obj, ok := conditions.(JSONObject); ok {
/**
exports: {
"./lib/foo": {
"./lib/foo.js": {
"require": "./lib/foo.js",
"import": "./esm/foo.js",
"types": "./types/foo.d.ts"
Expand Down
42 changes: 28 additions & 14 deletions server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,25 +186,27 @@ func esmRouter() rex.Handle {
}

// strip trailing slash
if last := len(pathname) - 1; pathname != "/" && pathname[last] == '/' {
pathname = pathname[:last]
if pl := len(pathname); pl > 1 && pathname[pl-1] == '/' {
pathname = pathname[:pl-1]
}

// strip loc suffix
// e.g. https://esm.sh/react/es2022/react.mjs:2:3
i := len(pathname) - 1
j := 0
for {
if i < 0 || pathname[i] == '/' {
break
{
i := len(pathname) - 1
j := 0
for {
if i < 0 || pathname[i] == '/' {
break
}
if pathname[i] == ':' {
j = i
}
i--
}
if pathname[i] == ':' {
j = i
if j > 0 {
pathname = pathname[:j]
}
i--
}
if j > 0 {
pathname = pathname[:j]
}

// static routes
Expand Down Expand Up @@ -1612,7 +1614,7 @@ func esmRouter() rex.Handle {
if !ret.HasCSS {
return rex.Status(404, "Package CSS not found")
}
url := fmt.Sprintf("%s%s.css", origin, strings.TrimSuffix(buildCtx.Path(), ".mjs"))
url := origin + strings.TrimSuffix(buildCtx.Path(), ".mjs") + ".css"
return redirect(ctx, url, isExactVersion)
}

Expand All @@ -1631,6 +1633,18 @@ func esmRouter() rex.Handle {

// if the path is `ESMBuild`, return the built js/css content
if pathKind == EsmBuild {
// if the build
if esm.SubPath != buildCtx.esm.SubPath {
buf, recycle := NewBuffer()
defer recycle()
fmt.Fprintf(buf, "export * from \"%s\";\n", buildCtx.Path())
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)
return buf.Bytes()
}
savePath := buildCtx.getSavepath()
if strings.HasSuffix(esm.SubPath, ".css") {
path, _ := utils.SplitByLastByte(savePath, '.')
Expand Down
8 changes: 8 additions & 0 deletions test/fix-url/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,11 @@ Deno.test("dts-transformer: support `.d` extension", async () => {
const dts = await res.text();
assertStringIncludes(dts, "'./config.d.ts'");
});

Deno.test("fix rewritten build path", async () => {
for (let i = 0; i < 2; i++) {
const res = await fetch("http://localhost:8080/@ucanto/[email protected]/denonext/src/lib.mjs");
const js = await res.text();
assertEquals(js.trim(), `export * from "/@ucanto/[email protected]/denonext/core.mjs";`);
}
});
8 changes: 8 additions & 0 deletions test/issue-787/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { assertExists } from "jsr:@std/assert";

import * as capabilities from "http://localhost:8080/@web3-storage/capabilities@^18.0.0/index?target=denonext";

Deno.test("issue #787", () => {
assertExists(capabilities.add);
assertExists(capabilities.add.can);
});

0 comments on commit 38c7c21

Please sign in to comment.