Skip to content

Commit 6402f11

Browse files
committed
basic support for parsing import attributes
1 parent 7ece556 commit 6402f11

12 files changed

Lines changed: 298 additions & 123 deletions

File tree

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,24 @@
131131
this error and leave the unresolved path in the bundle.
132132
```
133133

134+
* Parse and print the `with` keyword in `import` statements
135+
136+
JavaScript was going to have a feature called "import assertions" that adds an `assert` keyword to `import` statements. It looked like this:
137+
138+
```js
139+
import stuff from './stuff.json' assert { type: 'json' }
140+
```
141+
142+
The feature provided a way to assert that the imported file is of a certain type (but was not allowed to affect how the import is interpreted, even though that's how everyone expected it to behave). The feature was fully specified and then actually implemented and shipped in Chrome before the people behind the feature realized that they should allow it to affect how the import is interpreted after all. So import assertions are no longer going to be added to the language.
143+
144+
Instead, the [current proposal](https://github.com/tc39/proposal-import-attributes) is to add a feature called "import attributes" instead that adds a `with` keyword to import statements. It looks like this:
145+
146+
```js
147+
import stuff from './stuff.json' with { type: 'json' }
148+
```
149+
150+
This feature provides a way to affect how the import is interpreted. With this release, esbuild now has preliminary support for parsing and printing this new `with` keyword. The `with` keyword is not yet interpreted by esbuild, however, so bundling code with it will generate a build error. All this release does is allow you to use esbuild to process code containing it (such as removing types from TypeScript code). Note that this syntax is not yet a part of JavaScript and may be removed or altered in the future if the specification changes (which it already has once, as described above). If that happens, esbuild reserves the right to remove or alter its support for this syntax too.
151+
134152
## 0.19.2
135153

136154
* Update how CSS nesting is parsed again

compat-table/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export const jsFeatures = {
5757
Generator: true,
5858
Hashbang: true,
5959
ImportAssertions: true,
60+
ImportAttributes: true,
6061
ImportMeta: true,
6162
InlineScript: true,
6263
LogicalAssignment: true,

internal/ast/ast.go

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,10 @@ func (flags ImportRecordFlags) Has(flag ImportRecordFlags) bool {
149149
}
150150

151151
type ImportRecord struct {
152-
Assertions *ImportAssertions
153-
GlobPattern *GlobPattern
154-
Path logger.Path
155-
Range logger.Range
152+
AssertOrWith *ImportAssertOrWith
153+
GlobPattern *GlobPattern
154+
Path logger.Path
155+
Range logger.Range
156156

157157
// If the "HandlesImportErrors" flag is present, then this is the location
158158
// of the error handler. This is used for error reporting.
@@ -170,24 +170,39 @@ type ImportRecord struct {
170170
Kind ImportKind
171171
}
172172

173-
type ImportAssertions struct {
174-
Entries []AssertEntry
175-
AssertLoc logger.Loc
173+
type AssertOrWithKeyword uint8
174+
175+
const (
176+
AssertKeyword AssertOrWithKeyword = iota
177+
WithKeyword
178+
)
179+
180+
func (kw AssertOrWithKeyword) String() string {
181+
if kw == AssertKeyword {
182+
return "assert"
183+
}
184+
return "with"
185+
}
186+
187+
type ImportAssertOrWith struct {
188+
Entries []AssertOrWithEntry
189+
KeywordLoc logger.Loc
176190
InnerOpenBraceLoc logger.Loc
177191
InnerCloseBraceLoc logger.Loc
178192
OuterOpenBraceLoc logger.Loc
179193
OuterCloseBraceLoc logger.Loc
194+
Keyword AssertOrWithKeyword
180195
}
181196

182-
type AssertEntry struct {
197+
type AssertOrWithEntry struct {
183198
Key []uint16 // An identifier or a string
184199
Value []uint16 // Always a string
185200
KeyLoc logger.Loc
186201
ValueLoc logger.Loc
187202
PreferQuotedKey bool
188203
}
189204

190-
func FindAssertion(assertions []AssertEntry, name string) *AssertEntry {
205+
func FindAssertOrWithEntry(assertions []AssertOrWithEntry, name string) *AssertOrWithEntry {
191206
for _, assertion := range assertions {
192207
if helpers.UTF16EqualsString(assertion.Key, name) {
193208
return &assertion

internal/bundler/bundler.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,13 @@ func parseFile(args parseArgs) {
380380
continue
381381
}
382382

383+
// TODO: Implement bundling with import attributes
384+
if record.AssertOrWith != nil && record.AssertOrWith.Keyword == ast.WithKeyword {
385+
args.log.AddError(&tracker, js_lexer.RangeOfIdentifier(result.file.inputFile.Source, record.AssertOrWith.KeywordLoc),
386+
"Bundling with import attributes is not currently supported")
387+
continue
388+
}
389+
383390
// Special-case glob pattern imports
384391
if record.GlobPattern != nil {
385392
prettyPath := helpers.GlobPatternToString(record.GlobPattern.Parts)
@@ -1962,7 +1969,7 @@ func (s *scanner) scanAllDependencies() {
19621969
sourceIndex := s.allocateGlobSourceIndex(result.file.inputFile.Source.Index, uint32(importRecordIndex))
19631970
record.SourceIndex = ast.MakeIndex32(sourceIndex)
19641971
s.results[sourceIndex] = s.generateResultForGlobResolve(sourceIndex, globResults.absPath,
1965-
&result.file.inputFile.Source, record.Range, record.GlobPattern.Kind, globResults, record.Assertions)
1972+
&result.file.inputFile.Source, record.Range, record.GlobPattern.Kind, globResults, record.AssertOrWith)
19661973
}
19671974
continue
19681975
}
@@ -2010,7 +2017,7 @@ func (s *scanner) generateResultForGlobResolve(
20102017
importRange logger.Range,
20112018
kind ast.ImportKind,
20122019
result globResolveResult,
2013-
assertions *ast.ImportAssertions,
2020+
assertions *ast.ImportAssertOrWith,
20142021
) parseResult {
20152022
keys := make([]string, 0, len(result.resolveResults))
20162023
for key := range result.resolveResults {
@@ -2057,10 +2064,10 @@ func (s *scanner) generateResultForGlobResolve(
20572064

20582065
resolveResults = append(resolveResults, &resolveResult)
20592066
importRecords = append(importRecords, ast.ImportRecord{
2060-
Path: path,
2061-
SourceIndex: sourceIndex,
2062-
Assertions: assertions,
2063-
Kind: kind,
2067+
Path: path,
2068+
SourceIndex: sourceIndex,
2069+
AssertOrWith: assertions,
2070+
Kind: kind,
20642071
})
20652072

20662073
switch kind {
@@ -2203,7 +2210,7 @@ func (s *scanner) processScannedFiles(entryPointMeta []graph.EntryPoint) []scann
22032210
s.log.AddErrorWithNotes(&tracker, record.Range,
22042211
fmt.Sprintf("The file %q was loaded with the %q loader", otherFile.inputFile.Source.PrettyPath, config.LoaderToString[otherFile.inputFile.Loader]),
22052212
[]logger.MsgData{
2206-
tracker.MsgData(js_lexer.RangeOfImportAssertion(result.file.inputFile.Source, *ast.FindAssertion(record.Assertions.Entries, "type")),
2213+
tracker.MsgData(js_lexer.RangeOfImportAssertOrWith(result.file.inputFile.Source, *ast.FindAssertOrWithEntry(record.AssertOrWith.Entries, "type")),
22072214
"This import assertion requires the loader to be \"json\" instead:"),
22082215
{Text: "You need to either reconfigure esbuild to ensure that the loader for this file is \"json\" or you need to remove this import assertion."}})
22092216
}

internal/bundler_tests/bundler_loader_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,3 +1537,23 @@ func TestLoaderCopyWithInjectedFileBundle(t *testing.T) {
15371537
},
15381538
})
15391539
}
1540+
1541+
func TestLoaderBundleWithImportAttributes(t *testing.T) {
1542+
loader_suite.expectBundled(t, bundled{
1543+
files: map[string]string{
1544+
"/entry.js": `
1545+
import x from "./import.js"
1546+
import y from "./import.js" with { type: 'json' }
1547+
console.log(x === y)
1548+
`,
1549+
"/import.js": `{}`,
1550+
},
1551+
entryPaths: []string{"/entry.js"},
1552+
options: config.Options{
1553+
Mode: config.ModeBundle,
1554+
AbsOutputFile: "/out.js",
1555+
},
1556+
expectedScanLog: `entry.js: ERROR: Bundling with import attributes is not currently supported
1557+
`,
1558+
})
1559+
}

internal/compat/js_table.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ const (
9090
Generator
9191
Hashbang
9292
ImportAssertions
93+
ImportAttributes
9394
ImportMeta
9495
InlineScript
9596
LogicalAssignment
@@ -149,6 +150,7 @@ var StringToJSFeature = map[string]JSFeature{
149150
"generator": Generator,
150151
"hashbang": Hashbang,
151152
"import-assertions": ImportAssertions,
153+
"import-attributes": ImportAttributes,
152154
"import-meta": ImportMeta,
153155
"inline-script": InlineScript,
154156
"logical-assignment": LogicalAssignment,
@@ -504,6 +506,7 @@ var jsTable = map[JSFeature]map[Engine][]versionRange{
504506
Edge: {{start: v{91, 0, 0}}},
505507
Node: {{start: v{16, 14, 0}}},
506508
},
509+
ImportAttributes: {},
507510
ImportMeta: {
508511
Chrome: {{start: v{64, 0, 0}}},
509512
Deno: {{start: v{1, 0, 0}}},

internal/graph/graph.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ func CloneLinkerGraph(
195195
// For example, "import('./foo.json', { assert: { type: 'json' } })"
196196
// will likely be converted into an import of a JavaScript file and
197197
// leaving the import assertion there will prevent it from working.
198-
record.Assertions = nil
198+
record.AssertOrWith = nil
199199
}
200200
}
201201
}

internal/js_lexer/js_lexer.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -673,9 +673,9 @@ func RangeOfIdentifier(source logger.Source, loc logger.Loc) logger.Range {
673673
return source.RangeOfString(loc)
674674
}
675675

676-
func RangeOfImportAssertion(source logger.Source, assertion ast.AssertEntry) logger.Range {
677-
loc := RangeOfIdentifier(source, assertion.KeyLoc).Loc
678-
return logger.Range{Loc: loc, Len: source.RangeOfString(assertion.ValueLoc).End() - loc.Start}
676+
func RangeOfImportAssertOrWith(source logger.Source, assertOrWith ast.AssertOrWithEntry) logger.Range {
677+
loc := RangeOfIdentifier(source, assertOrWith.KeyLoc).Loc
678+
return logger.Range{Loc: loc, Len: source.RangeOfString(assertOrWith.ValueLoc).End() - loc.Start}
679679
}
680680

681681
func (lexer *Lexer) ExpectJSXElementChild(token T) {

0 commit comments

Comments
 (0)