Skip to content

Commit

Permalink
fix #2292: allow entity names as define values
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jun 15, 2022
1 parent 9860ee3 commit 889bab1
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 227 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -40,6 +40,10 @@

This fix was contributed by [@nkeynes](https://github.com/nkeynes).

* Allow entity names as define values ([#2292](https://github.com/evanw/esbuild/issues/2292))

The "define" feature allows you to replace certain expressions with certain other expressions at compile time. For example, you might want to replace the global identifier `IS_PRODUCTION` with the boolean value `true` when building for production. Previously the only expressions you could substitute in were either identifier expressions or anything that is valid JSON syntax. This limitation exists because supporting more complex expressions is more complex (for example, substituting in a `require()` call could potentially pull in additional files, which would need to be handled). With this release, you can now also now define something as a member expression chain of the form `foo.abc.xyz`.

* Implement package self-references ([#2312](https://github.com/evanw/esbuild/issues/2312))

This release implements a rarely-used feature in node where a package can import itself by name instead of using relative imports. You can read more about this feature here: https://nodejs.org/api/packages.html#self-referencing-a-package-using-its-name. For example, assuming the `package.json` in a given package looks like this:
Expand Down
4 changes: 2 additions & 2 deletions internal/bundler/bundler.go
Expand Up @@ -1189,10 +1189,10 @@ func (s *scanner) maybeParseFile(

// Allow certain properties to be overridden
if len(resolveResult.JSXFactory) > 0 {
optionsClone.JSX.Factory = config.JSXExpr{Parts: resolveResult.JSXFactory}
optionsClone.JSX.Factory = config.DefineExpr{Parts: resolveResult.JSXFactory}
}
if len(resolveResult.JSXFragment) > 0 {
optionsClone.JSX.Fragment = config.JSXExpr{Parts: resolveResult.JSXFragment}
optionsClone.JSX.Fragment = config.DefineExpr{Parts: resolveResult.JSXFragment}
}
if resolveResult.UseDefineForClassFieldsTS != config.Unspecified {
optionsClone.UseDefineForClassFields = resolveResult.UseDefineForClassFieldsTS
Expand Down
96 changes: 48 additions & 48 deletions internal/bundler/bundler_default_test.go
Expand Up @@ -449,8 +449,8 @@ func TestJSXImportsCommonJS(t *testing.T) {
options: config.Options{
Mode: config.ModeBundle,
JSX: config.JSXOptions{
Factory: config.JSXExpr{Parts: []string{"elem"}},
Fragment: config.JSXExpr{Parts: []string{"frag"}},
Factory: config.DefineExpr{Parts: []string{"elem"}},
Fragment: config.DefineExpr{Parts: []string{"frag"}},
},
AbsOutputFile: "/out.js",
},
Expand All @@ -473,8 +473,8 @@ func TestJSXImportsES6(t *testing.T) {
options: config.Options{
Mode: config.ModeBundle,
JSX: config.JSXOptions{
Factory: config.JSXExpr{Parts: []string{"elem"}},
Fragment: config.JSXExpr{Parts: []string{"frag"}},
Factory: config.DefineExpr{Parts: []string{"elem"}},
Fragment: config.DefineExpr{Parts: []string{"frag"}},
},
AbsOutputFile: "/out.js",
},
Expand Down Expand Up @@ -529,7 +529,7 @@ func TestJSXConstantFragments(t *testing.T) {
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
JSX: config.JSXOptions{
Fragment: config.JSXExpr{
Fragment: config.DefineExpr{
Constant: &js_ast.EString{Value: helpers.StringToUTF16("]")},
},
},
Expand Down Expand Up @@ -2090,7 +2090,7 @@ func TestImportReExportES6Issue149(t *testing.T) {
options: config.Options{
Mode: config.ModeBundle,
JSX: config.JSXOptions{
Factory: config.JSXExpr{Parts: []string{"h"}},
Factory: config.DefineExpr{Parts: []string{"h"}},
},
AbsOutputFile: "/out.js",
ExternalSettings: config.ExternalSettings{
Expand Down Expand Up @@ -3979,18 +3979,18 @@ func TestInjectDuplicate(t *testing.T) {
func TestInject(t *testing.T) {
defines := config.ProcessDefines(map[string]config.DefineData{
"chain.prop": {
DefineFunc: func(args config.DefineArgs) js_ast.E {
return &js_ast.EIdentifier{Ref: args.FindSymbol(args.Loc, "replace")}
DefineExpr: &config.DefineExpr{
Parts: []string{"replace"},
},
},
"obj.defined": {
DefineFunc: func(args config.DefineArgs) js_ast.E {
return &js_ast.EString{Value: helpers.StringToUTF16("defined")}
DefineExpr: &config.DefineExpr{
Constant: &js_ast.EString{Value: helpers.StringToUTF16("defined")},
},
},
"injectedAndDefined": {
DefineFunc: func(args config.DefineArgs) js_ast.E {
return &js_ast.EString{Value: helpers.StringToUTF16("should be used")}
DefineExpr: &config.DefineExpr{
Constant: &js_ast.EString{Value: helpers.StringToUTF16("should be used")},
},
},
})
Expand Down Expand Up @@ -4059,18 +4059,18 @@ func TestInject(t *testing.T) {
func TestInjectNoBundle(t *testing.T) {
defines := config.ProcessDefines(map[string]config.DefineData{
"chain.prop": {
DefineFunc: func(args config.DefineArgs) js_ast.E {
return &js_ast.EIdentifier{Ref: args.FindSymbol(args.Loc, "replace")}
DefineExpr: &config.DefineExpr{
Parts: []string{"replace"},
},
},
"obj.defined": {
DefineFunc: func(args config.DefineArgs) js_ast.E {
return &js_ast.EString{Value: helpers.StringToUTF16("defined")}
DefineExpr: &config.DefineExpr{
Constant: &js_ast.EString{Value: helpers.StringToUTF16("defined")},
},
},
"injectedAndDefined": {
DefineFunc: func(args config.DefineArgs) js_ast.E {
return &js_ast.EString{Value: helpers.StringToUTF16("should be used")}
DefineExpr: &config.DefineExpr{
Constant: &js_ast.EString{Value: helpers.StringToUTF16("should be used")},
},
},
})
Expand Down Expand Up @@ -4134,8 +4134,8 @@ func TestInjectNoBundle(t *testing.T) {
func TestInjectJSX(t *testing.T) {
defines := config.ProcessDefines(map[string]config.DefineData{
"React.createElement": {
DefineFunc: func(args config.DefineArgs) js_ast.E {
return &js_ast.EIdentifier{Ref: args.FindSymbol(args.Loc, "el")}
DefineExpr: &config.DefineExpr{
Parts: []string{"el"},
},
},
})
Expand Down Expand Up @@ -4310,18 +4310,18 @@ func TestAvoidTDZNoBundle(t *testing.T) {
func TestDefineImportMeta(t *testing.T) {
defines := config.ProcessDefines(map[string]config.DefineData{
"import.meta": {
DefineFunc: func(args config.DefineArgs) js_ast.E {
return &js_ast.ENumber{Value: 1}
DefineExpr: &config.DefineExpr{
Constant: &js_ast.ENumber{Value: 1},
},
},
"import.meta.foo": {
DefineFunc: func(args config.DefineArgs) js_ast.E {
return &js_ast.ENumber{Value: 2}
DefineExpr: &config.DefineExpr{
Constant: &js_ast.ENumber{Value: 2},
},
},
"import.meta.foo.bar": {
DefineFunc: func(args config.DefineArgs) js_ast.E {
return &js_ast.ENumber{Value: 3}
DefineExpr: &config.DefineExpr{
Constant: &js_ast.ENumber{Value: 3},
},
},
})
Expand Down Expand Up @@ -4354,8 +4354,8 @@ func TestDefineImportMeta(t *testing.T) {
func TestDefineImportMetaES5(t *testing.T) {
defines := config.ProcessDefines(map[string]config.DefineData{
"import.meta.x": {
DefineFunc: func(args config.DefineArgs) js_ast.E {
return &js_ast.ENumber{Value: 1}
DefineExpr: &config.DefineExpr{
Constant: &js_ast.ENumber{Value: 1},
},
},
})
Expand Down Expand Up @@ -4391,18 +4391,18 @@ kept.js: WARNING: "import.meta" is not available in the configured target enviro
func TestDefineThis(t *testing.T) {
defines := config.ProcessDefines(map[string]config.DefineData{
"this": {
DefineFunc: func(args config.DefineArgs) js_ast.E {
return &js_ast.ENumber{Value: 1}
DefineExpr: &config.DefineExpr{
Constant: &js_ast.ENumber{Value: 1},
},
},
"this.foo": {
DefineFunc: func(args config.DefineArgs) js_ast.E {
return &js_ast.ENumber{Value: 2}
DefineExpr: &config.DefineExpr{
Constant: &js_ast.ENumber{Value: 2},
},
},
"this.foo.bar": {
DefineFunc: func(args config.DefineArgs) js_ast.E {
return &js_ast.ENumber{Value: 3}
DefineExpr: &config.DefineExpr{
Constant: &js_ast.ENumber{Value: 3},
},
},
})
Expand Down Expand Up @@ -4791,8 +4791,8 @@ func TestJSXThisValueCommonJS(t *testing.T) {
options: config.Options{
Mode: config.ModeBundle,
JSX: config.JSXOptions{
Factory: config.JSXExpr{Parts: []string{"this"}},
Fragment: config.JSXExpr{Parts: []string{"this"}},
Factory: config.DefineExpr{Parts: []string{"this"}},
Fragment: config.DefineExpr{Parts: []string{"this"}},
},
AbsOutputDir: "/out",
},
Expand Down Expand Up @@ -4833,8 +4833,8 @@ func TestJSXThisValueESM(t *testing.T) {
options: config.Options{
Mode: config.ModeBundle,
JSX: config.JSXOptions{
Factory: config.JSXExpr{Parts: []string{"this"}},
Fragment: config.JSXExpr{Parts: []string{"this"}},
Factory: config.DefineExpr{Parts: []string{"this"}},
Fragment: config.DefineExpr{Parts: []string{"this"}},
},
AbsOutputDir: "/out",
},
Expand Down Expand Up @@ -4878,8 +4878,8 @@ func TestJSXThisPropertyCommonJS(t *testing.T) {
options: config.Options{
Mode: config.ModeBundle,
JSX: config.JSXOptions{
Factory: config.JSXExpr{Parts: []string{"this", "factory"}},
Fragment: config.JSXExpr{Parts: []string{"this", "fragment"}},
Factory: config.DefineExpr{Parts: []string{"this", "factory"}},
Fragment: config.DefineExpr{Parts: []string{"this", "fragment"}},
},
AbsOutputDir: "/out",
},
Expand Down Expand Up @@ -4920,8 +4920,8 @@ func TestJSXThisPropertyESM(t *testing.T) {
options: config.Options{
Mode: config.ModeBundle,
JSX: config.JSXOptions{
Factory: config.JSXExpr{Parts: []string{"this", "factory"}},
Fragment: config.JSXExpr{Parts: []string{"this", "fragment"}},
Factory: config.DefineExpr{Parts: []string{"this", "factory"}},
Fragment: config.DefineExpr{Parts: []string{"this", "fragment"}},
},
AbsOutputDir: "/out",
},
Expand Down Expand Up @@ -4968,8 +4968,8 @@ func TestJSXImportMetaValue(t *testing.T) {
Mode: config.ModeBundle,
UnsupportedJSFeatures: compat.ImportMeta,
JSX: config.JSXOptions{
Factory: config.JSXExpr{Parts: []string{"import", "meta"}},
Fragment: config.JSXExpr{Parts: []string{"import", "meta"}},
Factory: config.DefineExpr{Parts: []string{"import", "meta"}},
Fragment: config.DefineExpr{Parts: []string{"import", "meta"}},
},
AbsOutputDir: "/out",
},
Expand Down Expand Up @@ -5018,8 +5018,8 @@ func TestJSXImportMetaProperty(t *testing.T) {
Mode: config.ModeBundle,
UnsupportedJSFeatures: compat.ImportMeta,
JSX: config.JSXOptions{
Factory: config.JSXExpr{Parts: []string{"import", "meta", "factory"}},
Fragment: config.JSXExpr{Parts: []string{"import", "meta", "fragment"}},
Factory: config.DefineExpr{Parts: []string{"import", "meta", "factory"}},
Fragment: config.DefineExpr{Parts: []string{"import", "meta", "fragment"}},
},
AbsOutputDir: "/out",
},
Expand Down Expand Up @@ -5978,8 +5978,8 @@ func TestManglePropsJSXTransform(t *testing.T) {
AbsOutputFile: "/out.js",
MangleProps: regexp.MustCompile("_$"),
JSX: config.JSXOptions{
Factory: config.JSXExpr{Parts: []string{"Foo", "createElement_"}},
Fragment: config.JSXExpr{Parts: []string{"Foo", "Fragment_"}},
Factory: config.DefineExpr{Parts: []string{"Foo", "createElement_"}},
Fragment: config.DefineExpr{Parts: []string{"Foo", "Fragment_"}},
},
},
})
Expand Down
5 changes: 2 additions & 3 deletions internal/bundler/bundler_ts_test.go
Expand Up @@ -5,7 +5,6 @@ import (

"github.com/evanw/esbuild/internal/compat"
"github.com/evanw/esbuild/internal/config"
"github.com/evanw/esbuild/internal/js_ast"
)

var ts_suite = suite{
Expand Down Expand Up @@ -1719,8 +1718,8 @@ func TestTSEnumDefine(t *testing.T) {
Defines: &config.ProcessedDefines{
IdentifierDefines: map[string]config.DefineData{
"d": {
DefineFunc: func(args config.DefineArgs) js_ast.E {
return &js_ast.EIdentifier{Ref: args.FindSymbol(args.Loc, "b")}
DefineExpr: &config.DefineExpr{
Parts: []string{"b"},
},
},
},
Expand Down
9 changes: 2 additions & 7 deletions internal/config/config.go
Expand Up @@ -13,17 +13,12 @@ import (
)

type JSXOptions struct {
Factory JSXExpr
Fragment JSXExpr
Factory DefineExpr
Fragment DefineExpr
Parse bool
Preserve bool
}

type JSXExpr struct {
Constant js_ast.E
Parts []string
}

type TSOptions struct {
Parse bool
NoAmbiguousLessThan bool
Expand Down
32 changes: 21 additions & 11 deletions internal/config/globals.go
Expand Up @@ -5,9 +5,9 @@ import (
"strings"
"sync"

"github.com/evanw/esbuild/internal/ast"
"github.com/evanw/esbuild/internal/helpers"
"github.com/evanw/esbuild/internal/js_ast"
"github.com/evanw/esbuild/internal/logger"
)

var processedGlobalsMutex sync.Mutex
Expand Down Expand Up @@ -842,16 +842,26 @@ var knownGlobals = [][]string{
{"window"},
}

type DefineArgs struct {
FindSymbol func(logger.Loc, string) js_ast.Ref
SymbolForDefine func(int) js_ast.Ref
Loc logger.Loc
// We currently only support compile-time replacement with certain expressions:
//
// - Primitive literals
// - Identifiers
// - "Entity names" which are identifiers followed by property accesses
//
// We don't support arbitrary expressions because arbitrary expressions may
// require the full AST. For example, there could be "import()" or "require()"
// expressions that need an import record. We also need to re-generate some
// nodes such as identifiers within the injected context so that they can
// bind to symbols in that context. Other expressions such as "this" may
// also be contextual.
type DefineExpr struct {
Constant js_ast.E
Parts []string
InjectedDefineIndex ast.Index32
}

type DefineFunc func(DefineArgs) js_ast.E

type DefineData struct {
DefineFunc DefineFunc
DefineExpr *DefineExpr

// True if accessing this value is known to not have any side effects. For
// example, a bare reference to "Object.create" can be removed because it
Expand Down Expand Up @@ -928,13 +938,13 @@ func ProcessDefines(userDefines map[string]DefineData) ProcessedDefines {

// Swap in certain literal values because those can be constant folded
result.IdentifierDefines["undefined"] = DefineData{
DefineFunc: func(DefineArgs) js_ast.E { return js_ast.EUndefinedShared },
DefineExpr: &DefineExpr{Constant: js_ast.EUndefinedShared},
}
result.IdentifierDefines["NaN"] = DefineData{
DefineFunc: func(DefineArgs) js_ast.E { return &js_ast.ENumber{Value: math.NaN()} },
DefineExpr: &DefineExpr{Constant: &js_ast.ENumber{Value: math.NaN()}},
}
result.IdentifierDefines["Infinity"] = DefineData{
DefineFunc: func(DefineArgs) js_ast.E { return &js_ast.ENumber{Value: math.Inf(1)} },
DefineExpr: &DefineExpr{Constant: &js_ast.ENumber{Value: math.Inf(1)}},
}

// Then copy the user-specified defines in afterwards, which will overwrite
Expand Down

0 comments on commit 889bab1

Please sign in to comment.