Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

thlorenz/feat/require resolve #2

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions internal/resolver/package_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
63 changes: 48 additions & 15 deletions internal/snap_api/snap_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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__()));
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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__())();
Expand Down Expand Up @@ -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));
};`,
},
},
Expand Down Expand Up @@ -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));
};`,
},
})
Expand Down Expand Up @@ -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)) });
};`,
},
},
Expand All @@ -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));
};`,
},
},
Expand Down Expand Up @@ -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));
};`,
},
},
Expand Down Expand Up @@ -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));
};`,
},
},
Expand Down Expand Up @@ -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));
};
};`,
},
Expand All @@ -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{
Expand Down
45 changes: 45 additions & 0 deletions internal/snap_printer/snap_handle_ecall.go
Original file line number Diff line number Diff line change
@@ -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(")")
}
55 changes: 32 additions & 23 deletions internal/snap_printer/snap_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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(")")
}
Expand All @@ -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(")")
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions internal/snap_printer/snap_printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
20 changes: 10 additions & 10 deletions internal/snap_renamer/snap_renamer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down