diff --git a/internal/resolver/package_json.go b/internal/resolver/package_json.go index 26aec46176a..4ff6bbda61b 100644 --- a/internal/resolver/package_json.go +++ b/internal/resolver/package_json.go @@ -152,12 +152,16 @@ func (r resolverQuery) parsePackageJSON(path string) *packageJSON { packageJSON.absMainFields = make(map[string]string) } if absPath := toAbsPath(r.fs.Join(path, main), jsonSource.RangeOfString(mainJSON.Loc)); absPath != nil { - // HACK: Cypress related, this is to mitigate the hacky version dance going on inside + // HACK: module resolution related, this is to mitigate the hacky version dance going on inside // https://github.com/bevry/safefs/blob/11c7818dc3b3968e080003e0e960ae13e487dd1a/es6guardian.js // It prevents to statically determine which module will actually be loaded and thus breaks things. - // Remove once that dep has been upgraded or esbuild has a mechanism to make this work. if strings.HasSuffix(*absPath, "node_modules/safefs/es6guardian.js") { packageJSON.absMainFields[field] = strings.Replace(*absPath, "es6guardian.js", "es5/lib/safefs.js", 1) + } else if strings.Contains(*absPath, "node_modules/istextorbinary/index.cjs") { + // This one breaks require overwrites as it works via a custom loader like the above. + // In order to do so it also has to query a `package.json` file which is an I/O operation we prefer + // to avoid. + packageJSON.absMainFields[field] = strings.Replace(*absPath, "istextorbinary/index.cjs", "istextorbinary/edition-esnext/index.js", 1) } else { packageJSON.absMainFields[field] = *absPath } diff --git a/internal/snap_api/snap_api_test.go b/internal/snap_api/snap_api_test.go index bb0aea35be3..395d78f16fb 100644 --- a/internal/snap_api/snap_api_test.go +++ b/internal/snap_api/snap_api_test.go @@ -28,7 +28,7 @@ func TestEntryRequiringLocalModule(t *testing.T) { __commonJS["./entry.js"] = function(exports, module2, __filename, __dirname, require) { let oneTwoThree; function __get_oneTwoThree__() { - return oneTwoThree = oneTwoThree || (require("./foo.js").oneTwoThree) + return oneTwoThree = oneTwoThree || (require("./foo", "./foo.js", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)).oneTwoThree) } module2.exports = function() { get_console().log((__get_oneTwoThree__())); @@ -66,7 +66,7 @@ __commonJS["./foo.js"] = function(exports, module2, __filename, __dirname, requi __commonJS["./entry.js"] = function(exports, module2, __filename, __dirname, require) { let import_foo; function __get_import_foo__() { - return import_foo = import_foo || (__toModule(require("./foo.js"))) + return import_foo = import_foo || (__toModule(require("./foo", "./foo.js", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)))) } module2.exports = function() { get_console().log((__get_import_foo__()).oneTwoThree); @@ -94,7 +94,7 @@ module.exports = function () { deprecate() } __commonJS["./entry.js"] = function(exports, module2, __filename, __dirname, require) { let deprecate; function __get_deprecate__() { - return deprecate = deprecate || (require("./depd.js")("http-errors")) + return deprecate = deprecate || (require("./depd", "./depd.js", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname))("http-errors")) } module2.exports = function() { (__get_deprecate__())(); @@ -122,7 +122,7 @@ __commonJS["./body-parser.js"] = function(exports, module2, __filename, __dirnam };`, ProjectBaseDir + "/entry.js": ` __commonJS["./entry.js"] = function(exports, module2, __filename, __dirname, require) { - require("./body-parser.js"); + require("./body-parser", "./body-parser.js", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)); };`, }, }, @@ -170,7 +170,7 @@ require('non-existent') files: map[string]string{ ProjectBaseDir + `/entry.js`: ` __commonJS["./entry.js"] = function(exports, module2, __filename, __dirname, require) { - require("non-existent"); + require("non-existent", "non-existent", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)); };`, }, }) @@ -221,19 +221,19 @@ exports.bar = require('./bar') files: map[string]string{ `dev/foo.js`: ` __commonJS["./foo.js"] = function(exports, module, __filename, __dirname, require) { - var fs = require("fs"); + var fs = require("fs", "fs", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)); };`, `dev/bar.js`: ` __commonJS["./bar.js"] = function(exports, module2, __filename, __dirname, require) { let path; function __get_path__() { - return path = path || (require("path")) + return path = path || (require("path", "path", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname))) } };`, `dev/entry.js`: ` __commonJS["./entry.js"] = function(exports, module2, __filename, __dirname, require) { - Object.defineProperty(exports, "foo", { get: () => require("./foo.js") }); - Object.defineProperty(exports, "bar", { get: () => require("./bar.js") }); + Object.defineProperty(exports, "foo", { get: () => require("./foo", "./foo.js", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)) }); + Object.defineProperty(exports, "bar", { get: () => require("./bar", "./bar.js", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)) }); };`, }, }, @@ -260,12 +260,12 @@ exports.fsevents = require('` + ProjectBaseDir + `/node_modules/fsevents/fsevent files: map[string]string{ `dev/node_modules/fsevents/fsevents.js`: ` __commonJS["./node_modules/fsevents/fsevents.js"] = function(exports, module, __filename, __dirname, require) { - var Native = require("./node_modules/fsevents/fsevents.node"); + var Native = require("./node_modules/fsevents/fsevents.node", "./node_modules/fsevents/fsevents.node", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)); var events = Native.constants; };`, `dev/entry.js`: ` __commonJS["./entry.js"] = function(exports, module, __filename, __dirname, require) { - exports.fsevents = require("./node_modules/fsevents/fsevents.js"); + exports.fsevents = require("/dev/node_modules/fsevents/fsevents.js", "./node_modules/fsevents/fsevents.js", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)); };`, }, }, @@ -296,7 +296,7 @@ __commonJS["./node_modules/fsevents/fsevents.js"] = function(exports, module2, _ };`, `dev/entry.js`: ` __commonJS["./entry.js"] = function(exports, module2, __filename, __dirname, require) { - exports.fsevents = require("./node_modules/fsevents/fsevents.js"); + exports.fsevents = require("/dev/node_modules/fsevents/fsevents.js", "./node_modules/fsevents/fsevents.js", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)); };`, }, }, @@ -328,7 +328,7 @@ __commonJS["./node_modules/file-url.js"] = function(exports, module2, __filename };`, `dev/entry.js`: ` __commonJS["./entry.js"] = function(exports, module2, __filename, __dirname, require) { - exports.fileUrl = require("./node_modules/file-url.js"); + exports.fileUrl = require("/dev/node_modules/file-url.js", "./node_modules/file-url.js", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)); };`, }, }, @@ -366,8 +366,8 @@ __commonJS["./reassigns-console.js"] = function(exports, module2, __filename, __ `dev/entry.js`: ` __commonJS["./entry.js"] = function(exports, module2, __filename, __dirname, require) { module2.exports = function() { - require("./fine.js"); - require("./reassigns-console.js"); + require("./fine", "./fine.js", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)); + require("./reassigns-console", "./reassigns-console.js", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)); }; };`, }, @@ -390,6 +390,39 @@ process.emitWarning = override ) } +func TestRequireResolveRewrite(t *testing.T) { + snapApiSuite.expectBuild(t, built{ + files: map[string]string{ + ProjectBaseDir + "/fixtures/sync-deps.js": `module.exports = 1`, + ProjectBaseDir + "/foo.js": `module.exports = 1`, + ProjectBaseDir + "/entry.js": ` +const fooPath = require.resolve('./foo') +require.resolve('./foo') +delete require.cache[require.resolve('./fixtures/sync-deps.js')] +function toBeResolved(prefix) { + return prefix + 'foo' +} +require.resolve(toBeResolved('./')) +`, + }, + entryPoints: []string{ProjectBaseDir + "/entry.js"}, + }, + buildResult{ + files: map[string]string{ + `dev/entry.js`: ` +__commonJS["./entry.js"] = function(exports, module2, __filename, __dirname, require) { + var fooPath = require.resolve("./foo", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)); + require.resolve("./foo", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)); + delete require.cache[require.resolve("./fixtures/sync-deps.js", (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname))]; + function toBeResolved(prefix) { + return prefix + "foo"; + } + require.resolve(toBeResolved("./"), (typeof __filename2 !== 'undefined' ? __filename2 : __filename), (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)); +};`, + }, + }, + ) +} func TestDebug(t *testing.T) { snapApiSuite.debugBuild(t, built{ files: map[string]string{ diff --git a/internal/snap_printer/snap_handle_ecall.go b/internal/snap_printer/snap_handle_ecall.go new file mode 100644 index 00000000000..a14f635c6fa --- /dev/null +++ b/internal/snap_printer/snap_handle_ecall.go @@ -0,0 +1,45 @@ +package snap_printer + +import "github.com/evanw/esbuild/internal/js_ast" + +// require.resolve +func (p *printer) handleRequireResolve(ecall *js_ast.ECall) (handled bool) { + switch tgt := ecall.Target.Data.(type) { + case *js_ast.EDot: + if tgt.Name != "resolve" { + return false + } + // Ensure it is a `require.resolve` + switch reqTgt := tgt.Target.Data.(type) { + case *js_ast.EIdentifier: + if p.renamer.IsRequire(reqTgt.Ref) { + // Cannot rewrite non-custom require.resolve + if len(ecall.Args) > 1 { + return false + } + p._printRequireResolve(&ecall.Args[0]) + return true + } + } + } + return false +} + +// NOTE: currently not used as when bundling the ERequireResolve case is hit instead +func (p *printer) handleECall(ecall *js_ast.ECall) (handled bool) { + return p.handleRequireResolve(ecall) +} + +// ----------------- +// Printers +// ----------------- + +func (p *printer) _printRequireResolve(request *js_ast.Expr) { + p.print("require.resolve(") + p.printExpr(*request, js_ast.LComma, 0) + // NOTE: more info about __dirname2/__dirname inside + // internal/snap_renamer/snap_renamer.go (functionWrapperForAbsPath) + p.print(", (typeof __filename2 !== 'undefined' ? __filename2 : __filename)") + p.print(", (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)") + p.print(")") +} diff --git a/internal/snap_printer/snap_printer.go b/internal/snap_printer/snap_printer.go index 2b0329b09d7..d513415a99c 100644 --- a/internal/snap_printer/snap_printer.go +++ b/internal/snap_printer/snap_printer.go @@ -950,7 +950,11 @@ func (p *printer) printRequireOrImportExpr(importRecordIndex uint32, leadingInte p.printSpaceBeforeIdentifier() p.print("require(") p.addSourceMapping(record.Range.Loc) + p.printQuotedUTF8(record.Path.Text, true) + p.print(", ") p.printQuotedUTF8(p.resolveRequireName(record), true /* allowBacktick */) + p.print(", (typeof __filename2 !== 'undefined' ? __filename2 : __filename)") + p.print(", (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)") p.print(")") return } @@ -1201,32 +1205,35 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags int) { } } - // We don't ever want to accidentally generate a direct eval expression here - p.callTarget = e.Target.Data - if !e.IsDirectEval && p.isUnboundEvalIdentifier(e.Target) { - if p.options.RemoveWhitespace { - p.print("(0,") + if handled := p.handleECall(e); !handled { + // We don't ever want to accidentally generate a direct eval expression here + p.callTarget = e.Target.Data + if !e.IsDirectEval && p.isUnboundEvalIdentifier(e.Target) { + if p.options.RemoveWhitespace { + p.print("(0,") + } else { + p.print("(0, ") + } + p.printExpr(e.Target, js_ast.LPostfix, 0) + p.print(")") } else { - p.print("(0, ") + p.printExpr(e.Target, js_ast.LPostfix, targetFlags) } - p.printExpr(e.Target, js_ast.LPostfix, 0) - p.print(")") - } else { - p.printExpr(e.Target, js_ast.LPostfix, targetFlags) - } - if e.OptionalChain == js_ast.OptionalChainStart { - p.print("?.") - } - p.print("(") - for i, arg := range e.Args { - if i != 0 { - p.print(",") - p.printSpace() + if e.OptionalChain == js_ast.OptionalChainStart { + p.print("?.") } - p.printExpr(arg, js_ast.LComma, 0) - } - p.print(")") + p.print("(") + for i, arg := range e.Args { + if i != 0 { + p.print(",") + p.printSpace() + } + p.printExpr(arg, js_ast.LComma, 0) + } + p.print(")") + } // end handleECall + if wrap { p.print(")") } @@ -1245,6 +1252,8 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags int) { p.printSpaceBeforeIdentifier() p.print("require.resolve(") p.printQuotedUTF8(p.importRecords[e.ImportRecordIndex].Path.Text, true /* allowBacktick */) + p.print(", (typeof __filename2 !== 'undefined' ? __filename2 : __filename)") + p.print(", (typeof __dirname2 !== 'undefined' ? __dirname2 : __dirname)") p.print(")") if wrap { p.print(")") @@ -2828,7 +2837,7 @@ func Print( ) PrintResult { var p *printer - var isRenaming bool = false + var isRenaming = false switch snapRenamer := r.(type) { case *snap_renamer.SnapRenamer: isRenaming = snapRenamer.IsEnabled diff --git a/internal/snap_printer/snap_printer_test.go b/internal/snap_printer/snap_printer_test.go index b0b97547132..21bc6efd8fe 100644 --- a/internal/snap_printer/snap_printer_test.go +++ b/internal/snap_printer/snap_printer_test.go @@ -1310,7 +1310,8 @@ module.exports = exports = require("./lib/_stream_readable.js"); func TestDebug(t *testing.T) { debugPrinted(t, ` -const { v4: uuidv4 } = require('uuid') -var x = uuidv4() +function runTests() { + delete require.cache[require.resolve("./fixtures/sync-deps.js")]; +} `, ReplaceAll) } diff --git a/internal/snap_renamer/snap_renamer.go b/internal/snap_renamer/snap_renamer.go index 954bb02bfb8..99c086671fb 100644 --- a/internal/snap_renamer/snap_renamer.go +++ b/internal/snap_renamer/snap_renamer.go @@ -232,31 +232,31 @@ func (r *SnapRenamer) IsUnwrappable(ref js_ast.Ref) bool { if symbol.Kind == js_ast.SymbolUnbound { return true } - return r.isExportSymbol(symbol) + return r.isSymbolNamed(symbol, "exports") } -func (r *SnapRenamer) isExportSymbol(symbol *js_ast.Symbol) bool { +func (r *SnapRenamer) isSymbolNamed(symbol *js_ast.Symbol, name string) bool { matchesKind := symbol.Kind == js_ast.SymbolHoisted || symbol.Kind == js_ast.SymbolUnbound - return matchesKind && symbol.OriginalName == "exports" + return matchesKind && symbol.OriginalName == name } func (r *SnapRenamer) IsExport(ref js_ast.Ref) bool { ref = r.resolveRefFromSymbols(ref) symbol := r.symbols.Get(ref) - return r.isExportSymbol(symbol) + return r.isSymbolNamed(symbol, "exports") } -func (r *SnapRenamer) isModuleSymbol(symbol *js_ast.Symbol) bool { - matchesKind := symbol.Kind == js_ast.SymbolHoisted || - symbol.Kind == js_ast.SymbolUnbound - return matchesKind && symbol.OriginalName == "module" +func (r *SnapRenamer) IsModule(ref js_ast.Ref) bool { + ref = r.resolveRefFromSymbols(ref) + symbol := r.symbols.Get(ref) + return r.isSymbolNamed(symbol, "module") } -func (r *SnapRenamer) IsModule(ref js_ast.Ref) bool { +func (r *SnapRenamer) IsRequire(ref js_ast.Ref) bool { ref = r.resolveRefFromSymbols(ref) symbol := r.symbols.Get(ref) - return r.isModuleSymbol(symbol) + return r.isSymbolNamed(symbol, "require") } // NOTE: esbuild renames __dirname/__filename to __dirname2/__filename2 in some cases and