Skip to content

Commit bb7a0a1

Browse files
committed
fix #2239: more closely replicate a browserify bug
1 parent 7e2d75c commit bb7a0a1

File tree

4 files changed

+113
-22
lines changed

4 files changed

+113
-22
lines changed

CHANGELOG.md

+35
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,41 @@
3737

3838
Note that this behavior is TypeScript-specific. Babel doesn't support the `...` token at all (it gives the error "Spread children are not supported in React").
3939

40+
* Slightly adjust esbuild's handling of the `browser` field in `package.json` ([#2239](https://github.com/evanw/esbuild/issues/2239))
41+
42+
This release changes esbuild's interpretation of `browser` path remapping to fix a regression that was introduced in esbuild version 0.14.21. Browserify has a bug where it incorrectly matches package paths to relative paths in the `browser` field, and esbuild replicates this bug for compatibility with Browserify. I have a set of tests that I use to verify that esbuild's replication of this Browserify is accurate here: https://github.com/evanw/package-json-browser-tests. However, I was missing a test case and esbuild's behavior diverges from Browserify in this case. This release now handles this edge case as well:
43+
44+
* `entry.js`:
45+
46+
```js
47+
require('pkg/sub')
48+
```
49+
50+
* `node_modules/pkg/package.json`:
51+
52+
```json
53+
{
54+
"browser": {
55+
"./sub": "./sub/foo.js",
56+
"./sub/sub.js": "./sub/foo.js"
57+
}
58+
}
59+
```
60+
61+
* `node_modules/pkg/sub/foo.js`:
62+
63+
```js
64+
require('sub')
65+
```
66+
67+
* `node_modules/sub/index.js`:
68+
69+
```js
70+
console.log('works')
71+
```
72+
73+
The import path `sub` in `require('sub')` was previously matching the remapping `"./sub/sub.js": "./sub/foo.js"` but with this release it should now no longer match that remapping. Now `require('sub')` will only match the remapping `"./sub/sub": "./sub/foo.js"` (without the trailing `.js`). Browserify apparently only matches without the `.js` suffix here.
74+
4075
## 0.14.38
4176

4277
* Further fixes to TypeScript 4.7 instantiation expression parsing ([#2201](https://github.com/evanw/esbuild/issues/2201))

internal/bundler/bundler_packagejson_test.go

+22
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,28 @@ func TestPackageJsonBrowserIssue2002B(t *testing.T) {
848848
})
849849
}
850850

851+
// See https://github.com/evanw/esbuild/issues/2239
852+
func TestPackageJsonBrowserIssue2002C(t *testing.T) {
853+
packagejson_suite.expectBundled(t, bundled{
854+
files: map[string]string{
855+
"/Users/user/project/src/entry.js": `require('pkg/sub')`,
856+
"/Users/user/project/src/node_modules/pkg/package.json": `{
857+
"browser": {
858+
"./sub": "./sub/foo.js",
859+
"./sub/sub.js": "./sub/bar.js"
860+
}
861+
}`,
862+
"/Users/user/project/src/node_modules/pkg/sub/foo.js": `require('sub')`,
863+
"/Users/user/project/src/node_modules/sub/index.js": `works()`,
864+
},
865+
entryPaths: []string{"/Users/user/project/src/entry.js"},
866+
options: config.Options{
867+
Mode: config.ModeBundle,
868+
AbsOutputFile: "/Users/user/project/out.js",
869+
},
870+
})
871+
}
872+
851873
func TestPackageJsonDualPackageHazardImportOnly(t *testing.T) {
852874
packagejson_suite.expectBundled(t, bundled{
853875
files: map[string]string{

internal/bundler/snapshots/snapshots_packagejson.txt

+20
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,26 @@ var require_foo = __commonJS({
7171
// Users/user/project/src/entry.js
7272
require_foo();
7373

74+
================================================================================
75+
TestPackageJsonBrowserIssue2002C
76+
---------- /Users/user/project/out.js ----------
77+
// Users/user/project/src/node_modules/sub/index.js
78+
var require_sub = __commonJS({
79+
"Users/user/project/src/node_modules/sub/index.js"() {
80+
works();
81+
}
82+
});
83+
84+
// Users/user/project/src/node_modules/pkg/sub/foo.js
85+
var require_foo = __commonJS({
86+
"Users/user/project/src/node_modules/pkg/sub/foo.js"() {
87+
require_sub();
88+
}
89+
});
90+
91+
// Users/user/project/src/entry.js
92+
require_foo();
93+
7494
================================================================================
7595
TestPackageJsonBrowserMapAvoidMissing
7696
---------- /Users/user/project/out.js ----------

internal/resolver/package_json.go

+36-22
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,14 @@ func (r resolverQuery) checkBrowserMap(resolveDirInfo *dirInfo, inputPath string
9999
packageJSON := resolveDirInfo.enclosingBrowserScope.packageJSON
100100
browserMap := packageJSON.browserMap
101101

102-
checkPath := func(pathToCheck string) bool {
102+
type implicitExtensions uint8
103+
104+
const (
105+
includeImplicitExtensions implicitExtensions = iota
106+
skipImplicitExtensions
107+
)
108+
109+
checkPath := func(pathToCheck string, implicitExtensions implicitExtensions) bool {
103110
if r.debugLogs != nil {
104111
r.debugLogs.addNote(fmt.Sprintf("Checking for %q in the \"browser\" map in %q",
105112
pathToCheck, packageJSON.source.KeyPath.Text))
@@ -116,15 +123,17 @@ func (r resolverQuery) checkBrowserMap(resolveDirInfo *dirInfo, inputPath string
116123
}
117124

118125
// If that failed, try adding implicit extensions
119-
for _, ext := range r.options.ExtensionOrder {
120-
extPath := pathToCheck + ext
121-
if r.debugLogs != nil {
122-
r.debugLogs.addNote(fmt.Sprintf(" Checking for %q", extPath))
123-
}
124-
remapped, ok = browserMap[extPath]
125-
if ok {
126-
inputPath = extPath
127-
return true
126+
if implicitExtensions == includeImplicitExtensions {
127+
for _, ext := range r.options.ExtensionOrder {
128+
extPath := pathToCheck + ext
129+
if r.debugLogs != nil {
130+
r.debugLogs.addNote(fmt.Sprintf(" Checking for %q", extPath))
131+
}
132+
remapped, ok = browserMap[extPath]
133+
if ok {
134+
inputPath = extPath
135+
return true
136+
}
128137
}
129138
}
130139

@@ -145,15 +154,17 @@ func (r resolverQuery) checkBrowserMap(resolveDirInfo *dirInfo, inputPath string
145154
}
146155

147156
// If that failed, try adding implicit extensions
148-
for _, ext := range r.options.ExtensionOrder {
149-
extPath := indexPath + ext
150-
if r.debugLogs != nil {
151-
r.debugLogs.addNote(fmt.Sprintf(" Checking for %q", extPath))
152-
}
153-
remapped, ok = browserMap[extPath]
154-
if ok {
155-
inputPath = extPath
156-
return true
157+
if implicitExtensions == includeImplicitExtensions {
158+
for _, ext := range r.options.ExtensionOrder {
159+
extPath := indexPath + ext
160+
if r.debugLogs != nil {
161+
r.debugLogs.addNote(fmt.Sprintf(" Checking for %q", extPath))
162+
}
163+
remapped, ok = browserMap[extPath]
164+
if ok {
165+
inputPath = extPath
166+
return true
167+
}
157168
}
158169
}
159170

@@ -175,11 +186,11 @@ func (r resolverQuery) checkBrowserMap(resolveDirInfo *dirInfo, inputPath string
175186
}
176187

177188
// First try the import path as a package path
178-
if !checkPath(inputPath) && IsPackagePath(inputPath) {
189+
if !checkPath(inputPath, includeImplicitExtensions) && IsPackagePath(inputPath) {
179190
// If a package path didn't work, try the import path as a relative path
180191
switch kind {
181192
case absolutePathKind:
182-
checkPath("./" + inputPath)
193+
checkPath("./"+inputPath, includeImplicitExtensions)
183194

184195
case packagePathKind:
185196
// Browserify allows a browser map entry of "./pkg" to override a package
@@ -206,7 +217,10 @@ func (r resolverQuery) checkBrowserMap(resolveDirInfo *dirInfo, inputPath string
206217
relativePathPrefix += strings.ReplaceAll(relPath, "\\", "/") + "/"
207218
}
208219

209-
checkPath(relativePathPrefix + inputPath)
220+
// Browserify lets "require('pkg')" match "./pkg" but not "./pkg.js".
221+
// So don't add implicit extensions specifically in this place so we
222+
// match Browserify's behavior.
223+
checkPath(relativePathPrefix+inputPath, skipImplicitExtensions)
210224
}
211225
}
212226
}

0 commit comments

Comments
 (0)