Skip to content

Commit

Permalink
fix #4010, fix #4012: import.meta regression
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Dec 20, 2024
1 parent de9598f commit 947f99f
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 13 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

* Fix regression with `--define` and `import.meta` ([#4010](https://github.com/evanw/esbuild/issues/4010), [#4012](https://github.com/evanw/esbuild/issues/4012))

The previous change in version 0.24.1 to use a more expression-like parser for `define` values to allow quoted property names introduced a regression that removed the ability to use `--define:import.meta=...`. Even though `import` is normally a keyword that can't be used as an identifier, ES modules special-case the `import.meta` expression to behave like an identifier anyway. This change fixes the regression.

## 0.24.1

* Allow `es2024` as a target in `tsconfig.json` ([#4004](https://github.com/evanw/esbuild/issues/4004))
Expand Down
17 changes: 15 additions & 2 deletions internal/js_parser/global_name_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,22 @@ func ParseGlobalName(log logger.Log, source logger.Source) (result []string, ok

lexer := js_lexer.NewLexerGlobalName(log, source)

// Start off with an identifier
// Start off with an identifier or a keyword that results in an object
result = append(result, lexer.Identifier.String)
lexer.Expect(js_lexer.TIdentifier)
switch lexer.Token {
case js_lexer.TThis:
lexer.Next()

case js_lexer.TImport:
// Handle "import.meta"
lexer.Next()
lexer.Expect(js_lexer.TDot)
result = append(result, lexer.Identifier.String)
lexer.ExpectContextualKeyword("meta")

default:
lexer.Expect(js_lexer.TIdentifier)
}

// Follow with dot or index expressions
for lexer.Token != js_lexer.TEndOfFile {
Expand Down
30 changes: 23 additions & 7 deletions internal/linker/linker.go
Original file line number Diff line number Diff line change
Expand Up @@ -5973,7 +5973,7 @@ func (c *linkerContext) generateChunkJS(chunkIndex int, chunkWaitGroup *sync.Wai
func (c *linkerContext) generateGlobalNamePrefix() string {
var text string
globalName := c.options.GlobalName
prefix := globalName[0]
prefix, globalName := globalName[0], globalName[1:]
space := " "
join := ";\n"

Expand All @@ -5982,17 +5982,26 @@ func (c *linkerContext) generateGlobalNamePrefix() string {
join = ";"
}

// Assume the "this" and "import.meta" objects always exist
isExistingObject := prefix == "this"
if prefix == "import" && len(globalName) > 0 && globalName[0] == "meta" {
prefix, globalName = "import.meta", globalName[1:]
isExistingObject = true
}

// Use "||=" to make the code more compact when it's supported
if len(globalName) > 1 && !c.options.UnsupportedJSFeatures.Has(compat.LogicalAssignment) {
if js_printer.CanEscapeIdentifier(prefix, c.options.UnsupportedJSFeatures, c.options.ASCIIOnly) {
if len(globalName) > 0 && !c.options.UnsupportedJSFeatures.Has(compat.LogicalAssignment) {
if isExistingObject {
// Keep the prefix as it is
} else if js_printer.CanEscapeIdentifier(prefix, c.options.UnsupportedJSFeatures, c.options.ASCIIOnly) {
if c.options.ASCIIOnly {
prefix = string(js_printer.QuoteIdentifier(nil, prefix, c.options.UnsupportedJSFeatures))
}
text = fmt.Sprintf("var %s%s", prefix, join)
} else {
prefix = fmt.Sprintf("this[%s]", helpers.QuoteForJSON(prefix, c.options.ASCIIOnly))
}
for _, name := range globalName[1:] {
for _, name := range globalName {
var dotOrIndex string
if js_printer.CanEscapeIdentifier(name, c.options.UnsupportedJSFeatures, c.options.ASCIIOnly) {
if c.options.ASCIIOnly {
Expand All @@ -6002,12 +6011,19 @@ func (c *linkerContext) generateGlobalNamePrefix() string {
} else {
dotOrIndex = fmt.Sprintf("[%s]", helpers.QuoteForJSON(name, c.options.ASCIIOnly))
}
prefix = fmt.Sprintf("(%s%s||=%s{})%s", prefix, space, space, dotOrIndex)
if isExistingObject {
prefix = fmt.Sprintf("%s%s", prefix, dotOrIndex)
isExistingObject = false
} else {
prefix = fmt.Sprintf("(%s%s||=%s{})%s", prefix, space, space, dotOrIndex)
}
}
return fmt.Sprintf("%s%s%s=%s", text, prefix, space, space)
}

if js_printer.CanEscapeIdentifier(prefix, c.options.UnsupportedJSFeatures, c.options.ASCIIOnly) {
if isExistingObject {
text = fmt.Sprintf("%s%s=%s", prefix, space, space)
} else if js_printer.CanEscapeIdentifier(prefix, c.options.UnsupportedJSFeatures, c.options.ASCIIOnly) {
if c.options.ASCIIOnly {
prefix = string(js_printer.QuoteIdentifier(nil, prefix, c.options.UnsupportedJSFeatures))
}
Expand All @@ -6017,7 +6033,7 @@ func (c *linkerContext) generateGlobalNamePrefix() string {
text = fmt.Sprintf("%s%s=%s", prefix, space, space)
}

for _, name := range globalName[1:] {
for _, name := range globalName {
oldPrefix := prefix
if js_printer.CanEscapeIdentifier(name, c.options.UnsupportedJSFeatures, c.options.ASCIIOnly) {
if c.options.ASCIIOnly {
Expand Down
30 changes: 26 additions & 4 deletions scripts/js-api-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -6009,6 +6009,22 @@ class Foo {
π["π 𐀀"]["𐀀"]["𐀀 π"] = `)
},

async iifeGlobalNameThis({ esbuild }) {
const { code } = await esbuild.transform(`export default 123`, { format: 'iife', globalName: 'this.foo.bar' })
const globals = {}
vm.createContext(globals)
vm.runInContext(code, globals)
assert.strictEqual(globals.foo.bar.default, 123)
assert.strictEqual(code.slice(0, code.indexOf('(() => {\n')), `(this.foo ||= {}).bar = `)
},

async iifeGlobalNameImportMeta({ esbuild }) {
const { code } = await esbuild.transform(`export default 123`, { format: 'iife', globalName: 'import.meta.foo.bar' })
const { default: import_meta } = await import('data:text/javascript,' + code + '\nexport default import.meta')
assert.strictEqual(import_meta.foo.bar.default, 123)
assert.strictEqual(code.slice(0, code.indexOf('(() => {\n')), `(import.meta.foo ||= {}).bar = `)
},

async jsx({ esbuild }) {
const { code } = await esbuild.transform(`console.log(<div/>)`, { loader: 'jsx' })
assert.strictEqual(code, `console.log(/* @__PURE__ */ React.createElement("div", null));\n`)
Expand Down Expand Up @@ -6123,13 +6139,19 @@ class Foo {
},

async defineThis({ esbuild }) {
const { code } = await esbuild.transform(`console.log(a, b); export {}`, { define: { a: 'this', b: 'this.foo' }, format: 'esm' })
assert.strictEqual(code, `console.log(void 0, (void 0).foo);\n`)
const { code: code1 } = await esbuild.transform(`console.log(a, b); export {}`, { define: { a: 'this', b: 'this.foo' }, format: 'esm' })
assert.strictEqual(code1, `console.log(void 0, (void 0).foo);\n`)

const { code: code2 } = await esbuild.transform(`console.log(this, this.x); export {}`, { define: { this: 'a', 'this.x': 'b' }, format: 'esm' })
assert.strictEqual(code2, `console.log(a, b);\n`)
},

async defineImportMetaESM({ esbuild }) {
const { code } = await esbuild.transform(`console.log(a, b); export {}`, { define: { a: 'import.meta', b: 'import.meta.foo' }, format: 'esm' })
assert.strictEqual(code, `console.log(import.meta, import.meta.foo);\n`)
const { code: code1 } = await esbuild.transform(`console.log(a, b); export {}`, { define: { a: 'import.meta', b: 'import.meta.foo' }, format: 'esm' })
assert.strictEqual(code1, `console.log(import.meta, import.meta.foo);\n`)

const { code: code2 } = await esbuild.transform(`console.log(import.meta, import.meta.x); export {}`, { define: { 'import.meta': 'a', 'import.meta.x': 'b' }, format: 'esm' })
assert.strictEqual(code2, `console.log(a, b);\n`)
},

async defineImportMetaIIFE({ esbuild }) {
Expand Down

0 comments on commit 947f99f

Please sign in to comment.