Skip to content

Commit

Permalink
fix #1868: remove "import.meta" warning sometimes
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Dec 20, 2021
1 parent 8244508 commit 8187e73
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 35 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@

This release also fixes a bug with this transformation where minifying the unused expression `` `foo ${bar}` `` into `"" + bar` changed the meaning of the expression. Template string interpolation always calls `toString` while string addition may call `valueOf` instead. This unused expression is now minified to `` `${bar}` ``, which is slightly longer but which avoids the behavior change.

* Avoid warning about `import.meta` if it's replaced ([#1868](https://github.com/evanw/esbuild/issues/1868))

It's possible to replace the `import.meta` expression using the `--define:` feature. Previously doing that still warned that the `import.meta` syntax was not supported when targeting ES5. With this release, there will no longer be a warning in this case.

## 0.14.5

* Fix an issue with the publishing script
Expand Down
37 changes: 37 additions & 0 deletions internal/bundler/bundler_default_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4240,6 +4240,43 @@ 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}
},
},
})
default_suite.expectBundled(t, bundled{
files: map[string]string{
"/replaced.js": `
console.log(import.meta.x)
`,
"/kept.js": `
console.log(import.meta.y)
`,
"/dead-code.js": `
var x = () => console.log(import.meta.z)
`,
},
entryPaths: []string{
"/replaced.js",
"/kept.js",
"/dead-code.js",
},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputDir: "/out",
Defines: &defines,
UnsupportedJSFeatures: compat.ImportMeta,
},
expectedScanLog: `dead-code.js: WARNING: "import.meta" is not available in the configured target environment and will be empty
kept.js: WARNING: "import.meta" is not available in the configured target environment and will be empty
`,
})
}

func TestDefineThis(t *testing.T) {
defines := config.ProcessDefines(map[string]config.DefineData{
"this": {
Expand Down
13 changes: 13 additions & 0 deletions internal/bundler/snapshots/snapshots_default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,19 @@ TestDefineImportMeta
// entry.js
console.log(1, 2, 3, 2 .baz, 1 .bar);

================================================================================
TestDefineImportMetaES5
---------- /out/replaced.js ----------
// replaced.js
console.log(1);

---------- /out/kept.js ----------
// kept.js
var import_meta = {};
console.log(import_meta.y);

---------- /out/dead-code.js ----------

================================================================================
TestDefineThis
---------- /out.js ----------
Expand Down
5 changes: 3 additions & 2 deletions internal/js_ast/js_ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,9 @@ type ENewTarget struct {
Range logger.Range
}

type EImportMeta struct{}
type EImportMeta struct {
RangeLen int32
}

// These help reduce unnecessary memory allocations
var BMissingShared = &BMissing{}
Expand All @@ -469,7 +471,6 @@ var ESuperShared = &ESuper{}
var ENullShared = &ENull{}
var EUndefinedShared = &EUndefined{}
var EThisShared = &EThis{}
var EImportMetaShared = &EImportMeta{}

type ENew struct {
Target Expr
Expand Down
71 changes: 38 additions & 33 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ type parser struct {
allowPrivateIdentifiers bool
hasTopLevelReturn bool
latestReturnHadSemicolon bool
hasImportMeta bool
hasESModuleSyntax bool
warnedThisIsUndefined bool
topLevelAwaitKeyword logger.Range
Expand Down Expand Up @@ -3619,14 +3618,9 @@ func (p *parser) parseImportExpr(loc logger.Loc, level js_ast.L) js_ast.Expr {
p.es6ImportKeyword = js_lexer.RangeOfIdentifier(p.source, loc)
p.lexer.Next()
if p.lexer.IsContextualKeyword("meta") {
r := p.lexer.Range()
rangeLen := p.lexer.Range().End() - loc.Start
p.lexer.Next()
p.hasImportMeta = true
if p.options.unsupportedJSFeatures.Has(compat.ImportMeta) {
r = logger.Range{Loc: loc, Len: r.End() - loc.Start}
p.markSyntaxFeature(compat.ImportMeta, r)
}
return js_ast.Expr{Loc: loc, Data: js_ast.EImportMetaShared}
return js_ast.Expr{Loc: loc, Data: &js_ast.EImportMeta{RangeLen: rangeLen}}
} else {
p.lexer.ExpectedString("\"meta\"")
}
Expand Down Expand Up @@ -11164,7 +11158,21 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
}
}

if p.importMetaRef != js_ast.InvalidRef {
// Warn about "import.meta" if it's not replaced by a define
if p.options.unsupportedJSFeatures.Has(compat.ImportMeta) {
r := logger.Range{Loc: expr.Loc, Len: e.RangeLen}
p.markSyntaxFeature(compat.ImportMeta, r)
}

// Convert "import.meta" to a variable if it's not supported in the output format
if p.options.unsupportedJSFeatures.Has(compat.ImportMeta) ||
(p.options.mode != config.ModePassThrough && !p.options.outputFormat.KeepES6ImportExportSyntax()) {
// Generate the variable if it doesn't exist yet
if p.importMetaRef == js_ast.InvalidRef {
p.importMetaRef = p.newSymbol(js_ast.SymbolOther, "import_meta")
p.moduleScope.Generated = append(p.moduleScope.Generated, p.importMetaRef)
}

// Replace "import.meta" with a reference to the symbol
p.recordUsage(p.importMetaRef)
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EIdentifier{Ref: p.importMetaRef}}, exprOut{}
Expand Down Expand Up @@ -14457,6 +14465,7 @@ func newParser(log logger.Log, source logger.Source, lexer js_lexer.Lexer, optio
runtimeImports: make(map[string]js_ast.Ref),
promiseRef: js_ast.InvalidRef,
afterArrowBodyLoc: logger.Loc{Start: -1},
importMetaRef: js_ast.InvalidRef,

// For lowering private methods
weakMapRef: js_ast.InvalidRef,
Expand Down Expand Up @@ -14583,21 +14592,6 @@ func Parse(log logger.Log, source logger.Source, options Options) (result js_ast
break
}

// Insert a variable for "import.meta" at the top of the file if it was used.
// We don't need to worry about "use strict" directives because this only
// happens when bundling, in which case we are flatting the module scopes of
// all modules together anyway so such directives are meaningless.
if p.importMetaRef != js_ast.InvalidRef {
importMetaStmt := js_ast.Stmt{Data: &js_ast.SLocal{
Kind: p.selectLocalKind(js_ast.LocalConst),
Decls: []js_ast.Decl{{
Binding: js_ast.Binding{Data: &js_ast.BIdentifier{Ref: p.importMetaRef}},
ValueOrNil: js_ast.Expr{Data: &js_ast.EObject{}},
}},
}}
stmts = append(append(make([]js_ast.Stmt, 0, len(stmts)+1), importMetaStmt), stmts...)
}

// Add an empty part for the namespace export that we can fill in later
nsExportPart := js_ast.Part{
SymbolUses: make(map[js_ast.Ref]js_ast.SymbolUse),
Expand Down Expand Up @@ -14687,6 +14681,26 @@ func Parse(log logger.Log, source logger.Source, options Options) (result js_ast
}
}

// Insert a variable for "import.meta" at the top of the file if it was used.
// We don't need to worry about "use strict" directives because this only
// happens when bundling, in which case we are flatting the module scopes of
// all modules together anyway so such directives are meaningless.
if p.importMetaRef != js_ast.InvalidRef {
importMetaStmt := js_ast.Stmt{Data: &js_ast.SLocal{
Kind: p.selectLocalKind(js_ast.LocalConst),
Decls: []js_ast.Decl{{
Binding: js_ast.Binding{Data: &js_ast.BIdentifier{Ref: p.importMetaRef}},
ValueOrNil: js_ast.Expr{Data: &js_ast.EObject{}},
}},
}}
before = append(before, js_ast.Part{
Stmts: []js_ast.Stmt{importMetaStmt},
SymbolUses: make(map[js_ast.Ref]js_ast.SymbolUse),
DeclaredSymbols: []js_ast.DeclaredSymbol{{Ref: p.importMetaRef, IsTopLevel: true}},
CanBeRemovedIfUnused: true,
})
}

// Pop the module scope to apply the "ContainsDirectEval" rules
p.popScope()

Expand Down Expand Up @@ -14829,15 +14843,6 @@ func (p *parser) prepareForVisitPass() {
p.moduleRef = p.newSymbol(js_ast.SymbolHoisted, "module")
}

// Convert "import.meta" to a variable if it's not supported in the output format
if p.hasImportMeta && (p.options.unsupportedJSFeatures.Has(compat.ImportMeta) ||
(p.options.mode != config.ModePassThrough && !p.options.outputFormat.KeepES6ImportExportSyntax())) {
p.importMetaRef = p.newSymbol(js_ast.SymbolOther, "import_meta")
p.moduleScope.Generated = append(p.moduleScope.Generated, p.importMetaRef)
} else {
p.importMetaRef = js_ast.InvalidRef
}

// Handle "@jsx" and "@jsxFrag" pragmas now that lexing is done
if p.options.jsx.Parse {
if expr, ok := ParseJSXExpr(p.lexer.JSXFactoryPragmaComment.Text, JSXFactory); !ok {
Expand Down

0 comments on commit 8187e73

Please sign in to comment.