Skip to content
Open
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
38 changes: 38 additions & 0 deletions cmd/esbuild/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,7 @@ func (service *serviceType) handleBuildRequest(id uint32, request map[string]int
options.AbsWorkingDir = request["absWorkingDir"].(string)
options.NodePaths = decodeStringArray(request["nodePaths"].([]interface{}))
options.MangleCache, _ = request["mangleCache"].(map[string]interface{})
options.MangleNamespaceCaches = decodeNamespaceCaches(request)

for _, entry := range entries {
entry := entry.([]interface{})
Expand Down Expand Up @@ -665,6 +666,9 @@ func (service *serviceType) handleBuildRequest(id uint32, request map[string]int
if options.MangleCache != nil {
response["mangleCache"] = result.MangleCache
}
if options.MangleNamespaceCaches != nil {
encodeNamespaceCaches(result.MangleNamespaceCaches, response)
}
if writeToStdout && len(result.OutputFiles) == 1 {
response["writeToStdout"] = result.OutputFiles[0].Contents
}
Expand Down Expand Up @@ -1161,6 +1165,7 @@ func (service *serviceType) handleTransformRequest(id uint32, request map[string
return encodeErrorPacket(id, err)
}
options.MangleCache, _ = request["mangleCache"].(map[string]interface{})
options.MangleNamespaceCaches = decodeNamespaceCaches(request)

transformInput := input
if inputFS {
Expand Down Expand Up @@ -1218,6 +1223,9 @@ func (service *serviceType) handleTransformRequest(id uint32, request map[string
if result.MangleCache != nil {
response["mangleCache"] = result.MangleCache
}
if result.MangleNamespaceCaches != nil {
encodeNamespaceCaches(result.MangleNamespaceCaches, response)
}

return encodePacket(packet{
id: id,
Expand Down Expand Up @@ -1428,3 +1436,33 @@ func decodeMessageToPrivate(obj map[string]interface{}) logger.Msg {
}
return msg
}

// Decodes namespace caches from a protocol request. The protocol uses
// map[string]interface{} but the Go API uses map[string]map[string]interface{}.
func decodeNamespaceCaches(request map[string]interface{}) map[string]map[string]interface{} {
nsCaches, ok := request["mangleNamespaceCaches"].(map[string]interface{})
if !ok {
return nil
}
converted := make(map[string]map[string]interface{}, len(nsCaches))
for nsKey, nsValue := range nsCaches {
if nsMap, ok := nsValue.(map[string]interface{}); ok {
converted[nsKey] = nsMap
}
}
return converted
}

// Encodes namespace caches into a protocol response. Converts
// map[string]map[string]interface{} to map[string]interface{}
// because the protocol encoder only handles the latter type.
func encodeNamespaceCaches(nsCaches map[string]map[string]interface{}, response map[string]interface{}) {
if nsCaches == nil {
return
}
converted := make(map[string]interface{}, len(nsCaches))
for k, v := range nsCaches {
converted[k] = v
}
response["mangleNamespaceCaches"] = converted
}
8 changes: 5 additions & 3 deletions internal/bundler/bundler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3009,7 +3009,7 @@ type Linker func(
dataForSourceMaps func() []DataForSourceMap,
) []graph.OutputFile

func (b *Bundle) Compile(log logger.Log, timer *helpers.Timer, mangleCache map[string]interface{}, link Linker) ([]graph.OutputFile, string) {
func (b *Bundle) Compile(log logger.Log, timer *helpers.Timer, mangleCache map[string]interface{}, mangleNamespaceCaches map[string]map[string]interface{}, link Linker) ([]graph.OutputFile, string) {
timer.Begin("Compile phase")
defer timer.End("Compile phase")

Expand All @@ -3023,9 +3023,10 @@ func (b *Bundle) Compile(log logger.Log, timer *helpers.Timer, mangleCache map[s
cssUsedLocalNames := make(map[string]bool)
options.ExclusiveMangleCacheUpdate = func(cb func(
mangleCache map[string]interface{},
mangleNamespaceCaches map[string]map[string]interface{},
cssUsedLocalNames map[string]bool,
)) {
cb(mangleCache, cssUsedLocalNames)
cb(mangleCache, mangleNamespaceCaches, cssUsedLocalNames)
}

files := make([]graph.InputFile, len(b.files))
Expand Down Expand Up @@ -3061,12 +3062,13 @@ func (b *Bundle) Compile(log logger.Log, timer *helpers.Timer, mangleCache map[s
optionsClone := options
optionsClone.ExclusiveMangleCacheUpdate = func(cb func(
mangleCache map[string]interface{},
mangleNamespaceCaches map[string]map[string]interface{},
cssUsedLocalNames map[string]bool,
)) {
// Serialize all accesses to the mangle cache in entry point order for determinism
serializer.Enter(i)
defer serializer.Leave(i)
cb(mangleCache, cssUsedLocalNames)
cb(mangleCache, mangleNamespaceCaches, cssUsedLocalNames)
}

resultGroups[i] = link(&optionsClone, forked, log, b.fs, b.res, files, entryPoints,
Expand Down
117 changes: 117 additions & 0 deletions internal/bundler_tests/bundler_default_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7433,6 +7433,123 @@ func TestManglePropsAvoidCollisions(t *testing.T) {
})
}

func TestManglePropNamespaces(t *testing.T) {
default_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
export let typeA = {
TypeA_foo_: 1,
TypeA_bar_: 2,
}
export let typeB = {
TypeB_foo_: 3,
TypeB_bar_: 4,
}
`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModePassThrough,
AbsOutputFile: "/out.js",
MangleProps: regexp.MustCompile("_$"),
ManglePropNamespaces: regexp.MustCompile(`^[A-Z][^_]*_`),
},
})
}

func TestManglePropNamespacesAvoidGlobalCollision(t *testing.T) {
default_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
export let global = {
global_prop_: 1,
}
export let typeA = {
TypeA_foo_: 2,
}
export let typeB = {
TypeB_foo_: 3,
}
`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModePassThrough,
AbsOutputFile: "/out.js",
MangleProps: regexp.MustCompile("_$"),
ManglePropNamespaces: regexp.MustCompile(`^[A-Z][^_]*_`),
},
})
}

func TestManglePropNamespacesNoReuse(t *testing.T) {
// Properties within the same namespace must not collide
default_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
export let typeA = {
TypeA_x_: 1,
TypeA_y_: 2,
TypeA_z_: 3,
}
`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModePassThrough,
AbsOutputFile: "/out.js",
MangleProps: regexp.MustCompile("_$"),
ManglePropNamespaces: regexp.MustCompile(`^[A-Z][^_]*_`),
},
})
}

func TestManglePropNamespacesFullMatch(t *testing.T) {
// When the namespace regex consumes the entire property name, treat it as un-namespaced
default_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
export let obj = {
TypeA_: 1,
TypeA_foo_: 2,
TypeB_foo_: 3,
}
`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModePassThrough,
AbsOutputFile: "/out.js",
MangleProps: regexp.MustCompile("_$"),
ManglePropNamespaces: regexp.MustCompile(`^[A-Z][^_]*_`),
},
})
}

func TestManglePropNamespacesSuffix(t *testing.T) {
default_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
export let typeA = {
foo_TypeA_: 1,
bar_TypeA_: 2,
}
export let typeB = {
foo_TypeB_: 3,
bar_TypeB_: 4,
}
`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModePassThrough,
AbsOutputFile: "/out.js",
MangleProps: regexp.MustCompile("_$"),
ManglePropNamespaces: regexp.MustCompile(`_[A-Z][^_]*_$`),
},
})
}

func TestManglePropsTypeScriptFeatures(t *testing.T) {
default_suite.expectBundled(t, bundled{
files: map[string]string{
Expand Down
2 changes: 1 addition & 1 deletion internal/bundler_tests/bundler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func (s *suite) __expectBundledImpl(t *testing.T, args bundled, fsKind fs.MockKi
}

log = logger.NewDeferLog(logKind, nil)
results, metafileJSON := bundle.Compile(log, nil, nil, linker.Link)
results, metafileJSON := bundle.Compile(log, nil, nil, nil, linker.Link)
msgs = log.Done()
assertLog(t, msgs, args.expectedCompileLog)

Expand Down
55 changes: 55 additions & 0 deletions internal/bundler_tests/snapshots/snapshots_default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3735,6 +3735,61 @@ x._doNotMangleThis, x?._doNotMangleThis, x[y ? "_doNotMangleThis" : z], x?.[y ?
var { _doNotMangleThis: x } = y;
"_doNotMangleThis" in x, (y ? "_doNotMangleThis" : z) in x, (y ? z : "_doNotMangleThis") in x;

================================================================================
TestManglePropNamespaces
---------- /out.js ----------
export let typeA = {
a: 1,
b: 2
};
export let typeB = {
a: 3,
b: 4
};

================================================================================
TestManglePropNamespacesAvoidGlobalCollision
---------- /out.js ----------
export let global = {
a: 1
};
export let typeA = {
b: 2
};
export let typeB = {
b: 3
};

================================================================================
TestManglePropNamespacesFullMatch
---------- /out.js ----------
export let obj = {
a: 1,
b: 2,
b: 3
};

================================================================================
TestManglePropNamespacesNoReuse
---------- /out.js ----------
export let typeA = {
a: 1,
b: 2,
c: 3
};

================================================================================
TestManglePropNamespacesSuffix
---------- /out.js ----------
export let typeA = {
a: 1,
b: 2
};
export let typeB = {
a: 3,
b: 4
};

================================================================================
TestMangleProps
---------- /out/entry1.js ----------
Expand Down
14 changes: 8 additions & 6 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,12 +398,13 @@ func (flag *CancelFlag) DidCancel() bool {
}

type Options struct {
ModuleTypeData js_ast.ModuleTypeData
Defines *ProcessedDefines
TSAlwaysStrict *TSAlwaysStrict
MangleProps *regexp.Regexp
ReserveProps *regexp.Regexp
CancelFlag *CancelFlag
ModuleTypeData js_ast.ModuleTypeData
Defines *ProcessedDefines
TSAlwaysStrict *TSAlwaysStrict
MangleProps *regexp.Regexp
ReserveProps *regexp.Regexp
ManglePropNamespaces *regexp.Regexp
CancelFlag *CancelFlag

// When mangling property names, call this function with a callback and do
// the property name mangling inside the callback. The callback takes an
Expand All @@ -420,6 +421,7 @@ type Options struct {
// has finished.
ExclusiveMangleCacheUpdate func(cb func(
mangleCache map[string]interface{},
mangleNamespaceCaches map[string]map[string]interface{},
cssUsedLocalNames map[string]bool,
))

Expand Down
Loading