Skip to content

Commit fce0712

Browse files
authored
CSS files should resolve through package.json exports (#1219)
1 parent 5cc3937 commit fce0712

3 files changed

Lines changed: 68 additions & 0 deletions

File tree

server/build_resolver.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,27 @@ func (ctx *BuildContext) resolveEntry(esm EsmPath) (entry BuildEntry) {
7676
case ".json", ".jsx", ".svelte", ".vue":
7777
entry.update(subPath, true)
7878
return
79+
case ".css":
80+
// check if CSS file is defined in exports
81+
if pkgJson.Exports.Len() > 0 {
82+
if v, ok := pkgJson.Exports.Get("./" + subPath); ok {
83+
if s, ok := v.(string); ok {
84+
entry.update(s, true)
85+
return
86+
} else if obj, ok := v.(npm.JSONObject); ok {
87+
// handle conditional exports like {"types": "...", "default": "..."}
88+
if defaultPath, ok := obj.Get("default"); ok {
89+
if s, ok := defaultPath.(string); ok {
90+
entry.update(s, true)
91+
return
92+
}
93+
}
94+
}
95+
}
96+
}
97+
// if not found in exports, use the subPath as-is
98+
entry.update(subPath, true)
99+
return
79100
default:
80101
// continue
81102
}

server/router.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,24 @@ func esmRouter(db Database, esmStorage storage.Storage, logger *log.Logger) rex.
11531153
}
11541154
if err != nil {
11551155
if os.IsNotExist(err) {
1156+
// try to resolve the file through package.json exports
1157+
b := &BuildContext{
1158+
npmrc: npmrc,
1159+
esmPath: esm,
1160+
}
1161+
err = b.install()
1162+
if err != nil {
1163+
return rex.Status(500, err.Error())
1164+
}
1165+
entry := b.resolveEntry(esm)
1166+
if entry.main != "" && entry.main != "./"+esm.SubPath {
1167+
// redirect to the resolved path
1168+
query := ""
1169+
if rawQuery != "" {
1170+
query = "?" + rawQuery
1171+
}
1172+
return redirect(ctx, fmt.Sprintf("%s/%s%s%s", origin, esm.Name(), utils.NormalizePathname(entry.main), query), true)
1173+
}
11561174
ctx.SetHeader("Cache-Control", ccImmutable)
11571175
return rex.Status(404, "File Not Found")
11581176
}

test/issue-1217/test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { assertEquals, assertStringIncludes } from "jsr:@std/assert";
2+
3+
Deno.test("issue #1217 - CSS files should resolve through package.json exports", async () => {
4+
// Test case 1: yet-another-react-lightbox
5+
// The package.json has: "./styles.css": {"default": "./dist/styles.css"}
6+
// Should redirect from /styles.css to /dist/styles.css
7+
const res1 = await fetch(
8+
"http://localhost:8080/*yet-another-react-lightbox@3.21.7/styles.css",
9+
{ redirect: "follow" },
10+
);
11+
const css1 = await res1.text();
12+
assertEquals(res1.ok, true, "yet-another-react-lightbox styles.css should be found");
13+
assertEquals(res1.status, 200, "Should return 200 OK");
14+
assertStringIncludes(res1.url, "/dist/styles.css", "Should resolve to /dist/styles.css");
15+
assertStringIncludes(css1, "yarl__", "Should contain lightbox CSS");
16+
17+
// Test case 2: react-tweet
18+
// The package.json has: "./theme.css": "./dist/twitter-theme/theme.css"
19+
// Should redirect from /theme.css to /dist/twitter-theme/theme.css
20+
const res2 = await fetch(
21+
"http://localhost:8080/*react-tweet@3.2.2/theme.css",
22+
{ redirect: "follow" },
23+
);
24+
const css2 = await res2.text();
25+
assertEquals(res2.ok, true, "react-tweet theme.css should be found");
26+
assertEquals(res2.status, 200, "Should return 200 OK");
27+
assertStringIncludes(res2.url, "/dist/twitter-theme/theme.css", "Should resolve to /dist/twitter-theme/theme.css");
28+
assertStringIncludes(css2, "tweet", "Should contain tweet CSS");
29+
});

0 commit comments

Comments
 (0)