Skip to content

Commit

Permalink
internal: factor out key format for define map
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Dec 19, 2024
1 parent 364e5b9 commit 68573ab
Showing 1 changed file with 52 additions and 35 deletions.
87 changes: 52 additions & 35 deletions pkg/api/api_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,18 @@ func validateJSXExpr(log logger.Log, text string, name string) config.DefineExpr
return config.DefineExpr{}
}

// This returns an arbitrary but unique key for each unique array of strings
func mapKeyForDefine(parts []string) string {
var sb strings.Builder
var n [4]byte
for _, part := range parts {
binary.LittleEndian.PutUint32(n[:], uint32(len(part)))
sb.Write(n[:])
sb.WriteString(part)
}
return sb.String()
}

func validateDefines(
log logger.Log,
defines map[string]string,
Expand All @@ -533,13 +545,26 @@ func validateDefines(
minify bool,
drop Drop,
) (*config.ProcessedDefines, []config.InjectedDefine) {
// Sort injected defines for determinism, since the imports will be injected
// into every file in the order that we return them from this function
sortedKeys := make([]string, 0, len(defines))
for key := range defines {
sortedKeys = append(sortedKeys, key)
}
sort.Strings(sortedKeys)

rawDefines := make(map[string]config.DefineData)
var valueToInject map[string]config.InjectedDefine
var definesToInject []string
nodeEnvParts := []string{"process", "env", "NODE_ENV"}
nodeEnvMapKey := mapKeyForDefine(nodeEnvParts)
var injectedDefines []config.InjectedDefine

for _, key := range sortedKeys {
value := defines[key]
keyParts := strings.Split(key, ".")
mapKey := mapKeyForDefine(keyParts)

for key, value := range defines {
// The key must be a dot-separated identifier list
for _, part := range strings.Split(key, ".") {
for _, part := range keyParts {
if !js_ast.IsIdentifier(part) {
if part == key {
log.AddError(nil, logger.Range{}, fmt.Sprintf("The define key %q must be a valid identifier", key))
Expand All @@ -555,10 +580,10 @@ func validateDefines(

// Define simple expressions
if defineExpr.Constant != nil || len(defineExpr.Parts) > 0 {
rawDefines[key] = config.DefineData{DefineExpr: &defineExpr}
rawDefines[mapKey] = config.DefineData{KeyParts: keyParts, DefineExpr: &defineExpr}

// Try to be helpful for common mistakes
if len(defineExpr.Parts) == 1 && key == "process.env.NODE_ENV" {
if len(defineExpr.Parts) == 1 && mapKey == nodeEnvMapKey {
data := logger.MsgData{
Text: fmt.Sprintf("%q is defined as an identifier instead of a string (surround %q with quotes to get a string)", key, value),
}
Expand Down Expand Up @@ -606,83 +631,75 @@ func validateDefines(

// Inject complex expressions
if injectExpr != nil {
definesToInject = append(definesToInject, key)
if valueToInject == nil {
valueToInject = make(map[string]config.InjectedDefine)
}
valueToInject[key] = config.InjectedDefine{
index := ast.MakeIndex32(uint32(len(injectedDefines)))
injectedDefines = append(injectedDefines, config.InjectedDefine{
Source: logger.Source{Contents: value},
Data: injectExpr,
Name: key,
}
})
rawDefines[mapKey] = config.DefineData{KeyParts: keyParts, DefineExpr: &config.DefineExpr{InjectedDefineIndex: index}}
continue
}

// Anything else is unsupported
log.AddError(nil, logger.Range{}, fmt.Sprintf("Invalid define value (must be an entity name or valid JSON syntax): %s", value))
}

// Sort injected defines for determinism, since the imports will be injected
// into every file in the order that we return them from this function
var injectedDefines []config.InjectedDefine
if len(definesToInject) > 0 {
injectedDefines = make([]config.InjectedDefine, len(definesToInject))
sort.Strings(definesToInject)
for i, key := range definesToInject {
injectedDefines[i] = valueToInject[key]
rawDefines[key] = config.DefineData{DefineExpr: &config.DefineExpr{InjectedDefineIndex: ast.MakeIndex32(uint32(i))}}
}
}

// If we're bundling for the browser, add a special-cased define for
// "process.env.NODE_ENV" that is "development" when not minifying and
// "production" when minifying. This is a convention from the React world
// that must be handled to avoid all React code crashing instantly. This
// is only done if it's not already defined so that you can override it if
// necessary.
if isBuildAPI && platform == config.PlatformBrowser {
if _, process := rawDefines["process"]; !process {
if _, processEnv := rawDefines["process.env"]; !processEnv {
if _, processEnvNodeEnv := rawDefines["process.env.NODE_ENV"]; !processEnvNodeEnv {
if _, process := rawDefines[mapKeyForDefine([]string{"process"})]; !process {
if _, processEnv := rawDefines[mapKeyForDefine([]string{"process.env"})]; !processEnv {
if _, processEnvNodeEnv := rawDefines[nodeEnvMapKey]; !processEnvNodeEnv {
var value []uint16
if minify {
value = helpers.StringToUTF16("production")
} else {
value = helpers.StringToUTF16("development")
}
rawDefines["process.env.NODE_ENV"] = config.DefineData{DefineExpr: &config.DefineExpr{Constant: &js_ast.EString{Value: value}}}
rawDefines[nodeEnvMapKey] = config.DefineData{KeyParts: nodeEnvParts, DefineExpr: &config.DefineExpr{Constant: &js_ast.EString{Value: value}}}
}
}
}
}

// If we're dropping all console API calls, replace each one with undefined
if (drop & DropConsole) != 0 {
define := rawDefines["console"]
consoleParts := []string{"console"}
consoleMapKey := mapKeyForDefine(consoleParts)
define := rawDefines[consoleMapKey]
define.KeyParts = consoleParts
define.Flags |= config.MethodCallsMustBeReplacedWithUndefined
rawDefines["console"] = define
rawDefines[consoleMapKey] = define
}

for _, key := range pureFns {
keyParts := strings.Split(key, ".")
mapKey := mapKeyForDefine(keyParts)

// The key must be a dot-separated identifier list
for _, part := range strings.Split(key, ".") {
for _, part := range keyParts {
if !js_ast.IsIdentifier(part) {
log.AddError(nil, logger.Range{}, fmt.Sprintf("Invalid pure function: %q", key))
continue
}
}

// Merge with any previously-specified defines
define := rawDefines[key]
define := rawDefines[mapKey]
define.KeyParts = keyParts
define.Flags |= config.CallCanBeUnwrappedIfUnused
rawDefines[key] = define
rawDefines[mapKey] = define
}

// Processing defines is expensive. Process them once here so the same object
// can be shared between all parsers we create using these arguments.
definesArray := make([]config.DefineData, 0, len(rawDefines))
for key, define := range rawDefines {
define.KeyParts = strings.Split(key, ".")
for _, define := range rawDefines {
definesArray = append(definesArray, define)
}
processed := config.ProcessDefines(definesArray)
Expand Down

0 comments on commit 68573ab

Please sign in to comment.