Skip to content

Commit 25c6862

Browse files
committed
close #2001, fix #2002: "browser" map edge case
1 parent ce9b989 commit 25c6862

File tree

5 files changed

+129
-2
lines changed

5 files changed

+129
-2
lines changed

CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,41 @@
22

33
## Unreleased
44

5+
* Handle an additional `browser` map edge case ([#2001](https://github.com/evanw/esbuild/pull/2001), [#2002](https://github.com/evanw/esbuild/issues/2002))
6+
7+
There is a community convention around the `browser` field in `package.json` that allows remapping import paths within a package when the package is bundled for use within a browser. There isn't a rigorous definition of how it's supposed to work and every bundler implements it differently. The approach esbuild uses is to try to be "maximally compatible" in that if at least one bundler exhibits a particular behavior regarding the `browser` map that allows a mapping to work, then esbuild also attempts to make that work.
8+
9+
I have a collection of test cases for this going here: https://github.com/evanw/package-json-browser-tests. However, I was missing test coverage for the edge case where a package path import in a subdirectory of the package could potentially match a remapping. The "maximally compatible" approach means replicating bugs in Browserify's implementation of the feature where package paths are mistaken for relative paths and are still remapped. Here's a specific example of an edge case that's now handled:
10+
11+
```js
12+
// entry.js
13+
require('pkg/sub')
14+
```
15+
16+
```json
17+
// node_modules/pkg/package.json:
18+
{
19+
"browser": {
20+
"./sub": "./sub/foo.js",
21+
"./sub/sub": "./sub/bar.js"
22+
}
23+
}
24+
```
25+
26+
```js
27+
// node_modules/pkg/sub/foo.js:
28+
require('sub')
29+
```
30+
31+
```js
32+
// node_modules/pkg/sub/bar.js:
33+
console.log('works')
34+
```
35+
36+
The import path `sub` in `require('sub')` is mistaken for a relative path by Browserify due to a bug in Browserify, so Browserify treats it as if it were `./sub` instead. This is a Browserify-specific behavior and currently doesn't happen in any other bundler (except for esbuild, which attempts to replicate Browserify's bug).
37+
38+
Previously esbuild was incorrectly resolving `./sub` relative to the top-level package directory instead of to the subdirectory in this case, which meant `./sub` was incorrectly matching `"./sub": "./sub/foo.js"` instead of `"./sub/sub": "./sub/bar.js"`. This has been fixed so esbuild can now emulate Browserify's bug correctly in this edge case.
39+
540
* Support for esbuild with Linux on RISC-V 64bit ([#1624](https://github.com/evanw/esbuild/pull/1624))
641

742
With this release, esbuild now has a published binary executable for the RISC-V 64bit architecture in the [`platform-linux-riscv64`](https://www.npmjs.com/package/platform-linux-riscv64) npm package. This change was contributed by [@piggynl](https://github.com/piggynl).

internal/bundler/bundler_packagejson_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,49 @@ func TestPackageJsonBrowserIndexNoExt(t *testing.T) {
805805
})
806806
}
807807

808+
// See https://github.com/evanw/esbuild/issues/2002
809+
func TestPackageJsonBrowserIssue2002A(t *testing.T) {
810+
packagejson_suite.expectBundled(t, bundled{
811+
files: map[string]string{
812+
"/Users/user/project/src/entry.js": `require('pkg/sub')`,
813+
"/Users/user/project/src/node_modules/pkg/package.json": `{
814+
"browser": {
815+
"./sub": "./sub/foo.js"
816+
}
817+
}`,
818+
"/Users/user/project/src/node_modules/pkg/sub/foo.js": `require('sub')`,
819+
"/Users/user/project/src/node_modules/sub/package.json": `{ "main": "./bar" }`,
820+
"/Users/user/project/src/node_modules/sub/bar.js": `works()`,
821+
},
822+
entryPaths: []string{"/Users/user/project/src/entry.js"},
823+
options: config.Options{
824+
Mode: config.ModeBundle,
825+
AbsOutputFile: "/Users/user/project/out.js",
826+
},
827+
})
828+
}
829+
830+
func TestPackageJsonBrowserIssue2002B(t *testing.T) {
831+
packagejson_suite.expectBundled(t, bundled{
832+
files: map[string]string{
833+
"/Users/user/project/src/entry.js": `require('pkg/sub')`,
834+
"/Users/user/project/src/node_modules/pkg/package.json": `{
835+
"browser": {
836+
"./sub": "./sub/foo.js",
837+
"./sub/sub": "./sub/bar.js"
838+
}
839+
}`,
840+
"/Users/user/project/src/node_modules/pkg/sub/foo.js": `require('sub')`,
841+
"/Users/user/project/src/node_modules/pkg/sub/bar.js": `works()`,
842+
},
843+
entryPaths: []string{"/Users/user/project/src/entry.js"},
844+
options: config.Options{
845+
Mode: config.ModeBundle,
846+
AbsOutputFile: "/Users/user/project/out.js",
847+
},
848+
})
849+
}
850+
808851
func TestPackageJsonDualPackageHazardImportOnly(t *testing.T) {
809852
packagejson_suite.expectBundled(t, bundled{
810853
files: map[string]string{

internal/bundler/snapshots/snapshots_packagejson.txt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,46 @@ console.log(node);
3131
console.log(browser2);
3232
console.log(browser2);
3333

34+
================================================================================
35+
TestPackageJsonBrowserIssue2002A
36+
---------- /Users/user/project/out.js ----------
37+
// Users/user/project/src/node_modules/sub/bar.js
38+
var require_bar = __commonJS({
39+
"Users/user/project/src/node_modules/sub/bar.js"() {
40+
works();
41+
}
42+
});
43+
44+
// Users/user/project/src/node_modules/pkg/sub/foo.js
45+
var require_foo = __commonJS({
46+
"Users/user/project/src/node_modules/pkg/sub/foo.js"() {
47+
require_bar();
48+
}
49+
});
50+
51+
// Users/user/project/src/entry.js
52+
require_foo();
53+
54+
================================================================================
55+
TestPackageJsonBrowserIssue2002B
56+
---------- /Users/user/project/out.js ----------
57+
// Users/user/project/src/node_modules/pkg/sub/bar.js
58+
var require_bar = __commonJS({
59+
"Users/user/project/src/node_modules/pkg/sub/bar.js"() {
60+
works();
61+
}
62+
});
63+
64+
// Users/user/project/src/node_modules/pkg/sub/foo.js
65+
var require_foo = __commonJS({
66+
"Users/user/project/src/node_modules/pkg/sub/foo.js"() {
67+
require_bar();
68+
}
69+
});
70+
71+
// Users/user/project/src/entry.js
72+
require_foo();
73+
3474
================================================================================
3575
TestPackageJsonBrowserMapAvoidMissing
3676
---------- /Users/user/project/out.js ----------

internal/resolver/package_json.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,16 @@ func (r resolverQuery) checkBrowserMap(resolveDirInfo *dirInfo, inputPath string
197197
}
198198
}
199199
if isInSamePackage {
200-
checkPath("./" + inputPath)
200+
relativePathPrefix := "./"
201+
202+
// Use the relative path from the file containing the import path to the
203+
// enclosing package.json file. This includes any subdirectories within the
204+
// package if there are any.
205+
if relPath, ok := r.fs.Rel(resolveDirInfo.enclosingBrowserScope.absPath, resolveDirInfo.absPath); ok && relPath != "." {
206+
relativePathPrefix += strings.ReplaceAll(relPath, "\\", "/") + "/"
207+
}
208+
209+
checkPath(relativePathPrefix + inputPath)
201210
}
202211
}
203212
}

npm/esbuild/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
"esbuild-linux-64": "0.14.20",
2525
"esbuild-linux-arm": "0.14.20",
2626
"esbuild-linux-arm64": "0.14.20",
27-
"esbuild-linux-riscv64": "0.14.20",
2827
"esbuild-linux-mips64le": "0.14.20",
2928
"esbuild-linux-ppc64le": "0.14.20",
29+
"esbuild-linux-riscv64": "0.14.20",
3030
"esbuild-linux-s390x": "0.14.20",
3131
"esbuild-netbsd-64": "0.14.20",
3232
"esbuild-openbsd-64": "0.14.20",

0 commit comments

Comments
 (0)