github.com/evanw/esbuild@v0.21.4/internal/linker/linker.go (about) 1 package linker 2 3 // This package implements the second phase of the bundling operation that 4 // generates the output files when given a module graph. It has been split off 5 // into separate package to allow two linkers to cleanly exist in the same code 6 // base. This will be useful when rewriting the linker because the new one can 7 // be off by default to minimize disruption, but can still be enabled by anyone 8 // to assist in giving feedback on the rewrite. 9 10 import ( 11 "bytes" 12 "encoding/base64" 13 "encoding/binary" 14 "fmt" 15 "hash" 16 "path" 17 "sort" 18 "strconv" 19 "strings" 20 "sync" 21 22 "github.com/evanw/esbuild/internal/ast" 23 "github.com/evanw/esbuild/internal/bundler" 24 "github.com/evanw/esbuild/internal/compat" 25 "github.com/evanw/esbuild/internal/config" 26 "github.com/evanw/esbuild/internal/css_ast" 27 "github.com/evanw/esbuild/internal/css_lexer" 28 "github.com/evanw/esbuild/internal/css_parser" 29 "github.com/evanw/esbuild/internal/css_printer" 30 "github.com/evanw/esbuild/internal/fs" 31 "github.com/evanw/esbuild/internal/graph" 32 "github.com/evanw/esbuild/internal/helpers" 33 "github.com/evanw/esbuild/internal/js_ast" 34 "github.com/evanw/esbuild/internal/js_lexer" 35 "github.com/evanw/esbuild/internal/js_printer" 36 "github.com/evanw/esbuild/internal/logger" 37 "github.com/evanw/esbuild/internal/renamer" 38 "github.com/evanw/esbuild/internal/resolver" 39 "github.com/evanw/esbuild/internal/runtime" 40 "github.com/evanw/esbuild/internal/sourcemap" 41 "github.com/evanw/esbuild/internal/xxhash" 42 ) 43 44 type linkerContext struct { 45 options *config.Options 46 timer *helpers.Timer 47 log logger.Log 48 fs fs.FS 49 res *resolver.Resolver 50 graph graph.LinkerGraph 51 chunks []chunkInfo 52 53 // This helps avoid an infinite loop when matching imports to exports 54 cycleDetector []importTracker 55 56 // This represents the parallel computation of source map related data. 57 // Calling this will block until the computation is done. The resulting value 58 // is shared between threads and must be treated as immutable. 59 dataForSourceMaps func() []bundler.DataForSourceMap 60 61 // This is passed to us from the bundling phase 62 uniqueKeyPrefix string 63 uniqueKeyPrefixBytes []byte // This is just "uniqueKeyPrefix" in byte form 64 65 // Property mangling results go here 66 mangledProps map[ast.Ref]string 67 68 // We may need to refer to the CommonJS "module" symbol for exports 69 unboundModuleRef ast.Ref 70 71 // We may need to refer to the "__esm" and/or "__commonJS" runtime symbols 72 cjsRuntimeRef ast.Ref 73 esmRuntimeRef ast.Ref 74 } 75 76 type partRange struct { 77 sourceIndex uint32 78 partIndexBegin uint32 79 partIndexEnd uint32 80 } 81 82 type chunkInfo struct { 83 // This is a random string and is used to represent the output path of this 84 // chunk before the final output path has been computed. 85 uniqueKey string 86 87 filesWithPartsInChunk map[uint32]bool 88 entryBits helpers.BitSet 89 90 // For code splitting 91 crossChunkImports []chunkImport 92 93 // This is the representation-specific information 94 chunkRepr chunkRepr 95 96 // This is the final path of this chunk relative to the output directory, but 97 // without the substitution of the final hash (since it hasn't been computed). 98 finalTemplate []config.PathTemplate 99 100 // This is the final path of this chunk relative to the output directory. It 101 // is the substitution of the final hash into "finalTemplate". 102 finalRelPath string 103 104 // If non-empty, this chunk needs to generate an external legal comments file. 105 externalLegalComments []byte 106 107 // This contains the hash for just this chunk without including information 108 // from the hashes of other chunks. Later on in the linking process, the 109 // final hash for this chunk will be constructed by merging the isolated 110 // hashes of all transitive dependencies of this chunk. This is separated 111 // into two phases like this to handle cycles in the chunk import graph. 112 waitForIsolatedHash func() []byte 113 114 // Other fields relating to the output file for this chunk 115 jsonMetadataChunkCallback func(finalOutputSize int) helpers.Joiner 116 outputSourceMap sourcemap.SourceMapPieces 117 118 // When this chunk is initially generated in isolation, the output pieces 119 // will contain slices of the output with the unique keys of other chunks 120 // omitted. 121 intermediateOutput intermediateOutput 122 123 // This information is only useful if "isEntryPoint" is true 124 entryPointBit uint // An index into "c.graph.EntryPoints" 125 sourceIndex uint32 // An index into "c.sources" 126 isEntryPoint bool 127 128 isExecutable bool 129 } 130 131 type chunkImport struct { 132 chunkIndex uint32 133 importKind ast.ImportKind 134 } 135 136 type outputPieceIndexKind uint8 137 138 const ( 139 outputPieceNone outputPieceIndexKind = iota 140 outputPieceAssetIndex 141 outputPieceChunkIndex 142 ) 143 144 // This is a chunk of source code followed by a reference to another chunk. For 145 // example, the file "@import 'CHUNK0001'; body { color: black; }" would be 146 // represented by two pieces, one with the data "@import '" and another with the 147 // data "'; body { color: black; }". The first would have the chunk index 1 and 148 // the second would have an invalid chunk index. 149 type outputPiece struct { 150 data []byte 151 152 // Note: The "kind" may be "outputPieceNone" in which case there is one piece 153 // with data and no chunk index. For example, the chunk may not contain any 154 // imports. 155 index uint32 156 kind outputPieceIndexKind 157 } 158 159 type intermediateOutput struct { 160 // If the chunk has references to other chunks, then "pieces" contains the 161 // contents of the chunk and "joiner" should not be used. Another joiner 162 // will have to be constructed later when merging the pieces together. 163 pieces []outputPiece 164 165 // If the chunk doesn't have any references to other chunks, then "pieces" is 166 // nil and "joiner" contains the contents of the chunk. This is more efficient 167 // because it avoids doing a join operation twice. 168 joiner helpers.Joiner 169 } 170 171 type chunkRepr interface{ isChunk() } 172 173 func (*chunkReprJS) isChunk() {} 174 func (*chunkReprCSS) isChunk() {} 175 176 type chunkReprJS struct { 177 filesInChunkInOrder []uint32 178 partsInChunkInOrder []partRange 179 180 // For code splitting 181 exportsToOtherChunks map[ast.Ref]string 182 importsFromOtherChunks map[uint32]crossChunkImportItemArray 183 crossChunkPrefixStmts []js_ast.Stmt 184 crossChunkSuffixStmts []js_ast.Stmt 185 186 cssChunkIndex uint32 187 hasCSSChunk bool 188 } 189 190 type chunkReprCSS struct { 191 importsInChunkInOrder []cssImportOrder 192 } 193 194 type externalImportCSS struct { 195 path logger.Path 196 conditions []css_ast.ImportConditions 197 conditionImportRecords []ast.ImportRecord 198 } 199 200 // Returns a log where "log.HasErrors()" only returns true if any errors have 201 // been logged since this call. This is useful when there have already been 202 // errors logged by other linkers that share the same log. 203 func wrappedLog(log logger.Log) logger.Log { 204 var mutex sync.Mutex 205 var hasErrors bool 206 addMsg := log.AddMsg 207 208 log.AddMsg = func(msg logger.Msg) { 209 if msg.Kind == logger.Error { 210 mutex.Lock() 211 defer mutex.Unlock() 212 hasErrors = true 213 } 214 addMsg(msg) 215 } 216 217 log.HasErrors = func() bool { 218 mutex.Lock() 219 defer mutex.Unlock() 220 return hasErrors 221 } 222 223 return log 224 } 225 226 func Link( 227 options *config.Options, 228 timer *helpers.Timer, 229 log logger.Log, 230 fs fs.FS, 231 res *resolver.Resolver, 232 inputFiles []graph.InputFile, 233 entryPoints []graph.EntryPoint, 234 uniqueKeyPrefix string, 235 reachableFiles []uint32, 236 dataForSourceMaps func() []bundler.DataForSourceMap, 237 ) []graph.OutputFile { 238 timer.Begin("Link") 239 defer timer.End("Link") 240 241 log = wrappedLog(log) 242 243 timer.Begin("Clone linker graph") 244 c := linkerContext{ 245 options: options, 246 timer: timer, 247 log: log, 248 fs: fs, 249 res: res, 250 dataForSourceMaps: dataForSourceMaps, 251 uniqueKeyPrefix: uniqueKeyPrefix, 252 uniqueKeyPrefixBytes: []byte(uniqueKeyPrefix), 253 graph: graph.CloneLinkerGraph( 254 inputFiles, 255 reachableFiles, 256 entryPoints, 257 options.CodeSplitting, 258 ), 259 } 260 timer.End("Clone linker graph") 261 262 // Use a smaller version of these functions if we don't need profiler names 263 runtimeRepr := c.graph.Files[runtime.SourceIndex].InputFile.Repr.(*graph.JSRepr) 264 if c.options.ProfilerNames { 265 c.cjsRuntimeRef = runtimeRepr.AST.NamedExports["__commonJS"].Ref 266 c.esmRuntimeRef = runtimeRepr.AST.NamedExports["__esm"].Ref 267 } else { 268 c.cjsRuntimeRef = runtimeRepr.AST.NamedExports["__commonJSMin"].Ref 269 c.esmRuntimeRef = runtimeRepr.AST.NamedExports["__esmMin"].Ref 270 } 271 272 var additionalFiles []graph.OutputFile 273 for _, entryPoint := range entryPoints { 274 file := &c.graph.Files[entryPoint.SourceIndex].InputFile 275 switch repr := file.Repr.(type) { 276 case *graph.JSRepr: 277 // Loaders default to CommonJS when they are the entry point and the output 278 // format is not ESM-compatible since that avoids generating the ESM-to-CJS 279 // machinery. 280 if repr.AST.HasLazyExport && (c.options.Mode == config.ModePassThrough || 281 (c.options.Mode == config.ModeConvertFormat && !c.options.OutputFormat.KeepESMImportExportSyntax())) { 282 repr.AST.ExportsKind = js_ast.ExportsCommonJS 283 } 284 285 // Entry points with ES6 exports must generate an exports object when 286 // targeting non-ES6 formats. Note that the IIFE format only needs this 287 // when the global name is present, since that's the only way the exports 288 // can actually be observed externally. 289 if repr.AST.ExportKeyword.Len > 0 && (options.OutputFormat == config.FormatCommonJS || 290 (options.OutputFormat == config.FormatIIFE && len(options.GlobalName) > 0)) { 291 repr.AST.UsesExportsRef = true 292 repr.Meta.ForceIncludeExportsForEntryPoint = true 293 } 294 295 case *graph.CopyRepr: 296 // If an entry point uses the copy loader, then copy the file manually 297 // here. Other uses of the copy loader will automatically be included 298 // along with the corresponding bundled chunk but that doesn't happen 299 // for entry points. 300 additionalFiles = append(additionalFiles, file.AdditionalFiles...) 301 } 302 } 303 304 // Allocate a new unbound symbol called "module" in case we need it later 305 if c.options.OutputFormat == config.FormatCommonJS { 306 c.unboundModuleRef = c.graph.GenerateNewSymbol(runtime.SourceIndex, ast.SymbolUnbound, "module") 307 } else { 308 c.unboundModuleRef = ast.InvalidRef 309 } 310 311 c.scanImportsAndExports() 312 313 // Stop now if there were errors 314 if c.log.HasErrors() { 315 c.options.ExclusiveMangleCacheUpdate(func(map[string]interface{}, map[string]bool) { 316 // Always do this so that we don't cause other entry points when there are errors 317 }) 318 return []graph.OutputFile{} 319 } 320 321 c.treeShakingAndCodeSplitting() 322 323 if c.options.Mode == config.ModePassThrough { 324 for _, entryPoint := range c.graph.EntryPoints() { 325 c.preventExportsFromBeingRenamed(entryPoint.SourceIndex) 326 } 327 } 328 329 c.computeChunks() 330 c.computeCrossChunkDependencies() 331 332 // Merge mangled properties before chunks are generated since the names must 333 // be consistent across all chunks, or the generated code will break 334 c.timer.Begin("Waiting for mangle cache") 335 c.options.ExclusiveMangleCacheUpdate(func( 336 mangleCache map[string]interface{}, 337 cssUsedLocalNames map[string]bool, 338 ) { 339 c.timer.End("Waiting for mangle cache") 340 c.mangleProps(mangleCache) 341 c.mangleLocalCSS(cssUsedLocalNames) 342 }) 343 344 // Make sure calls to "ast.FollowSymbols()" in parallel goroutines after this 345 // won't hit concurrent map mutation hazards 346 ast.FollowAllSymbols(c.graph.Symbols) 347 348 return c.generateChunksInParallel(additionalFiles) 349 } 350 351 func (c *linkerContext) mangleProps(mangleCache map[string]interface{}) { 352 c.timer.Begin("Mangle props") 353 defer c.timer.End("Mangle props") 354 355 mangledProps := make(map[ast.Ref]string) 356 c.mangledProps = mangledProps 357 358 // Reserve all JS keywords 359 reservedProps := make(map[string]bool) 360 for keyword := range js_lexer.Keywords { 361 reservedProps[keyword] = true 362 } 363 364 // Reserve all target properties in the cache 365 for original, remapped := range mangleCache { 366 if remapped == false { 367 reservedProps[original] = true 368 } else { 369 reservedProps[remapped.(string)] = true 370 } 371 } 372 373 // Merge all mangled property symbols together 374 freq := ast.CharFreq{} 375 mergedProps := make(map[string]ast.Ref) 376 for _, sourceIndex := range c.graph.ReachableFiles { 377 // Don't mangle anything in the runtime code 378 if sourceIndex == runtime.SourceIndex { 379 continue 380 } 381 382 // For each file 383 if repr, ok := c.graph.Files[sourceIndex].InputFile.Repr.(*graph.JSRepr); ok { 384 // Reserve all non-mangled properties 385 for prop := range repr.AST.ReservedProps { 386 reservedProps[prop] = true 387 } 388 389 // Merge each mangled property with other ones of the same name 390 for name, ref := range repr.AST.MangledProps { 391 if existing, ok := mergedProps[name]; ok { 392 ast.MergeSymbols(c.graph.Symbols, ref, existing) 393 } else { 394 mergedProps[name] = ref 395 } 396 } 397 398 // Include this file's frequency histogram, which affects the mangled names 399 if repr.AST.CharFreq != nil { 400 freq.Include(repr.AST.CharFreq) 401 } 402 } 403 } 404 405 // Sort by use count (note: does not currently account for live vs. dead code) 406 sorted := make(renamer.StableSymbolCountArray, 0, len(mergedProps)) 407 stableSourceIndices := c.graph.StableSourceIndices 408 for _, ref := range mergedProps { 409 sorted = append(sorted, renamer.StableSymbolCount{ 410 StableSourceIndex: stableSourceIndices[ref.SourceIndex], 411 Ref: ref, 412 Count: c.graph.Symbols.Get(ref).UseCountEstimate, 413 }) 414 } 415 sort.Sort(sorted) 416 417 // Assign names in order of use count 418 minifier := ast.DefaultNameMinifierJS.ShuffleByCharFreq(freq) 419 nextName := 0 420 for _, symbolCount := range sorted { 421 symbol := c.graph.Symbols.Get(symbolCount.Ref) 422 423 // Don't change existing mappings 424 if existing, ok := mangleCache[symbol.OriginalName]; ok { 425 if existing != false { 426 mangledProps[symbolCount.Ref] = existing.(string) 427 } 428 continue 429 } 430 431 // Generate a new name 432 name := minifier.NumberToMinifiedName(nextName) 433 nextName++ 434 435 // Avoid reserved properties 436 for reservedProps[name] { 437 name = minifier.NumberToMinifiedName(nextName) 438 nextName++ 439 } 440 441 // Track the new mapping 442 if mangleCache != nil { 443 mangleCache[symbol.OriginalName] = name 444 } 445 mangledProps[symbolCount.Ref] = name 446 } 447 } 448 449 func (c *linkerContext) mangleLocalCSS(usedLocalNames map[string]bool) { 450 c.timer.Begin("Mangle local CSS") 451 defer c.timer.End("Mangle local CSS") 452 453 mangledProps := c.mangledProps 454 globalNames := make(map[string]bool) 455 localNames := make(map[ast.Ref]struct{}) 456 457 // Collect all local and global CSS names 458 freq := ast.CharFreq{} 459 for _, sourceIndex := range c.graph.ReachableFiles { 460 if repr, ok := c.graph.Files[sourceIndex].InputFile.Repr.(*graph.CSSRepr); ok { 461 for innerIndex, symbol := range c.graph.Symbols.SymbolsForSource[sourceIndex] { 462 if symbol.Kind == ast.SymbolGlobalCSS { 463 globalNames[symbol.OriginalName] = true 464 } else { 465 ref := ast.Ref{SourceIndex: sourceIndex, InnerIndex: uint32(innerIndex)} 466 ref = ast.FollowSymbols(c.graph.Symbols, ref) 467 localNames[ref] = struct{}{} 468 } 469 } 470 471 // Include this file's frequency histogram, which affects the mangled names 472 if repr.AST.CharFreq != nil { 473 freq.Include(repr.AST.CharFreq) 474 } 475 } 476 } 477 478 // Sort by use count (note: does not currently account for live vs. dead code) 479 sorted := make(renamer.StableSymbolCountArray, 0, len(localNames)) 480 stableSourceIndices := c.graph.StableSourceIndices 481 for ref := range localNames { 482 sorted = append(sorted, renamer.StableSymbolCount{ 483 StableSourceIndex: stableSourceIndices[ref.SourceIndex], 484 Ref: ref, 485 Count: c.graph.Symbols.Get(ref).UseCountEstimate, 486 }) 487 } 488 sort.Sort(sorted) 489 490 // Rename all local names to avoid collisions 491 if c.options.MinifyIdentifiers { 492 minifier := ast.DefaultNameMinifierCSS.ShuffleByCharFreq(freq) 493 nextName := 0 494 495 for _, symbolCount := range sorted { 496 name := minifier.NumberToMinifiedName(nextName) 497 for globalNames[name] || usedLocalNames[name] { 498 nextName++ 499 name = minifier.NumberToMinifiedName(nextName) 500 } 501 502 // Turn this local name into a global one 503 mangledProps[symbolCount.Ref] = name 504 usedLocalNames[name] = true 505 } 506 } else { 507 nameCounts := make(map[string]uint32) 508 509 for _, symbolCount := range sorted { 510 symbol := c.graph.Symbols.Get(symbolCount.Ref) 511 name := fmt.Sprintf("%s_%s", c.graph.Files[symbolCount.Ref.SourceIndex].InputFile.Source.IdentifierName, symbol.OriginalName) 512 513 // If the name is already in use, generate a new name by appending a number 514 if globalNames[name] || usedLocalNames[name] { 515 // To avoid O(n^2) behavior, the number must start off being the number 516 // that we used last time there was a collision with this name. Otherwise 517 // if there are many collisions with the same name, each name collision 518 // would have to increment the counter past all previous name collisions 519 // which is a O(n^2) time algorithm. 520 tries, ok := nameCounts[name] 521 if !ok { 522 tries = 1 523 } 524 prefix := name 525 526 // Keep incrementing the number until the name is unused 527 for { 528 tries++ 529 name = prefix + strconv.Itoa(int(tries)) 530 531 // Make sure this new name is unused 532 if !globalNames[name] && !usedLocalNames[name] { 533 // Store the count so we can start here next time instead of starting 534 // from 1. This means we avoid O(n^2) behavior. 535 nameCounts[prefix] = tries 536 break 537 } 538 } 539 } 540 541 // Turn this local name into a global one 542 mangledProps[symbolCount.Ref] = name 543 usedLocalNames[name] = true 544 } 545 } 546 } 547 548 // Currently the automatic chunk generation algorithm should by construction 549 // never generate chunks that import each other since files are allocated to 550 // chunks based on which entry points they are reachable from. 551 // 552 // This will change in the future when we allow manual chunk labels. But before 553 // we allow manual chunk labels, we'll need to rework module initialization to 554 // allow code splitting chunks to be lazily-initialized. 555 // 556 // Since that work hasn't been finished yet, cycles in the chunk import graph 557 // can cause initialization bugs. So let's forbid these cycles for now to guard 558 // against code splitting bugs that could cause us to generate buggy chunks. 559 func (c *linkerContext) enforceNoCyclicChunkImports() { 560 var validate func(int, map[int]int) bool 561 562 // DFS memoization with 3-colors, more space efficient 563 // 0: white (unvisited), 1: gray (visiting), 2: black (visited) 564 colors := make(map[int]int) 565 validate = func(chunkIndex int, colors map[int]int) bool { 566 if colors[chunkIndex] == 1 { 567 c.log.AddError(nil, logger.Range{}, "Internal error: generated chunks contain a circular import") 568 return true 569 } 570 571 if colors[chunkIndex] == 2 { 572 return false 573 } 574 575 colors[chunkIndex] = 1 576 577 for _, chunkImport := range c.chunks[chunkIndex].crossChunkImports { 578 // Ignore cycles caused by dynamic "import()" expressions. These are fine 579 // because they don't necessarily cause initialization order issues and 580 // they don't indicate a bug in our chunk generation algorithm. They arise 581 // normally in real code (e.g. two files that import each other). 582 if chunkImport.importKind != ast.ImportDynamic { 583 584 // Recursively validate otherChunkIndex 585 if validate(int(chunkImport.chunkIndex), colors) { 586 return true 587 } 588 } 589 } 590 591 colors[chunkIndex] = 2 592 return false 593 } 594 595 for i := range c.chunks { 596 if validate(i, colors) { 597 break 598 } 599 } 600 } 601 602 func (c *linkerContext) generateChunksInParallel(additionalFiles []graph.OutputFile) []graph.OutputFile { 603 c.timer.Begin("Generate chunks") 604 defer c.timer.End("Generate chunks") 605 606 // Generate each chunk on a separate goroutine. When a chunk needs to 607 // reference the path of another chunk, it will use a temporary path called 608 // the "uniqueKey" since the final path hasn't been computed yet (and is 609 // in general uncomputable at this point because paths have hashes that 610 // include information about chunk dependencies, and chunk dependencies 611 // can be cyclic due to dynamic imports). 612 generateWaitGroup := sync.WaitGroup{} 613 generateWaitGroup.Add(len(c.chunks)) 614 for chunkIndex := range c.chunks { 615 switch c.chunks[chunkIndex].chunkRepr.(type) { 616 case *chunkReprJS: 617 go c.generateChunkJS(chunkIndex, &generateWaitGroup) 618 case *chunkReprCSS: 619 go c.generateChunkCSS(chunkIndex, &generateWaitGroup) 620 } 621 } 622 c.enforceNoCyclicChunkImports() 623 generateWaitGroup.Wait() 624 625 // Compute the final hashes of each chunk, then use those to create the final 626 // paths of each chunk. This can technically be done in parallel but it 627 // probably doesn't matter so much because we're not hashing that much data. 628 visited := make([]uint32, len(c.chunks)) 629 var finalBytes []byte 630 for chunkIndex := range c.chunks { 631 chunk := &c.chunks[chunkIndex] 632 var hashSubstitution *string 633 634 // Only wait for the hash if necessary 635 if config.HasPlaceholder(chunk.finalTemplate, config.HashPlaceholder) { 636 // Compute the final hash using the isolated hashes of the dependencies 637 hash := xxhash.New() 638 c.appendIsolatedHashesForImportedChunks(hash, uint32(chunkIndex), visited, ^uint32(chunkIndex)) 639 finalBytes = hash.Sum(finalBytes[:0]) 640 finalString := bundler.HashForFileName(finalBytes) 641 hashSubstitution = &finalString 642 } 643 644 // Render the last remaining placeholder in the template 645 chunk.finalRelPath = config.TemplateToString(config.SubstituteTemplate(chunk.finalTemplate, config.PathPlaceholders{ 646 Hash: hashSubstitution, 647 })) 648 } 649 650 // Generate the final output files by joining file pieces together and 651 // substituting the temporary paths for the final paths. This substitution 652 // can be done in parallel for each chunk. 653 c.timer.Begin("Generate final output files") 654 var resultsWaitGroup sync.WaitGroup 655 results := make([][]graph.OutputFile, len(c.chunks)) 656 resultsWaitGroup.Add(len(c.chunks)) 657 for chunkIndex, chunk := range c.chunks { 658 go func(chunkIndex int, chunk chunkInfo) { 659 var outputFiles []graph.OutputFile 660 661 // Each file may optionally contain additional files to be copied to the 662 // output directory. This is used by the "file" and "copy" loaders. 663 var commentPrefix string 664 var commentSuffix string 665 switch chunkRepr := chunk.chunkRepr.(type) { 666 case *chunkReprJS: 667 for _, sourceIndex := range chunkRepr.filesInChunkInOrder { 668 outputFiles = append(outputFiles, c.graph.Files[sourceIndex].InputFile.AdditionalFiles...) 669 } 670 commentPrefix = "//" 671 672 case *chunkReprCSS: 673 for _, entry := range chunkRepr.importsInChunkInOrder { 674 if entry.kind == cssImportSourceIndex { 675 outputFiles = append(outputFiles, c.graph.Files[entry.sourceIndex].InputFile.AdditionalFiles...) 676 } 677 } 678 commentPrefix = "/*" 679 commentSuffix = " */" 680 } 681 682 // Path substitution for the chunk itself 683 finalRelDir := c.fs.Dir(chunk.finalRelPath) 684 outputContentsJoiner, outputSourceMapShifts := c.substituteFinalPaths(chunk.intermediateOutput, 685 func(finalRelPathForImport string) string { 686 return c.pathBetweenChunks(finalRelDir, finalRelPathForImport) 687 }) 688 689 // Generate the optional legal comments file for this chunk 690 if chunk.externalLegalComments != nil { 691 finalRelPathForLegalComments := chunk.finalRelPath + ".LEGAL.txt" 692 693 // Link the file to the legal comments 694 if c.options.LegalComments == config.LegalCommentsLinkedWithComment { 695 importPath := c.pathBetweenChunks(finalRelDir, finalRelPathForLegalComments) 696 importPath = strings.TrimPrefix(importPath, "./") 697 outputContentsJoiner.EnsureNewlineAtEnd() 698 outputContentsJoiner.AddString("/*! For license information please see ") 699 outputContentsJoiner.AddString(importPath) 700 outputContentsJoiner.AddString(" */\n") 701 } 702 703 // Write the external legal comments file 704 outputFiles = append(outputFiles, graph.OutputFile{ 705 AbsPath: c.fs.Join(c.options.AbsOutputDir, finalRelPathForLegalComments), 706 Contents: chunk.externalLegalComments, 707 JSONMetadataChunk: fmt.Sprintf( 708 "{\n \"imports\": [],\n \"exports\": [],\n \"inputs\": {},\n \"bytes\": %d\n }", len(chunk.externalLegalComments)), 709 }) 710 } 711 712 // Generate the optional source map for this chunk 713 if c.options.SourceMap != config.SourceMapNone && chunk.outputSourceMap.HasContent() { 714 outputSourceMap := chunk.outputSourceMap.Finalize(outputSourceMapShifts) 715 finalRelPathForSourceMap := chunk.finalRelPath + ".map" 716 717 // Potentially write a trailing source map comment 718 switch c.options.SourceMap { 719 case config.SourceMapLinkedWithComment: 720 importPath := c.pathBetweenChunks(finalRelDir, finalRelPathForSourceMap) 721 importPath = strings.TrimPrefix(importPath, "./") 722 outputContentsJoiner.EnsureNewlineAtEnd() 723 outputContentsJoiner.AddString(commentPrefix) 724 outputContentsJoiner.AddString("# sourceMappingURL=") 725 outputContentsJoiner.AddString(importPath) 726 outputContentsJoiner.AddString(commentSuffix) 727 outputContentsJoiner.AddString("\n") 728 729 case config.SourceMapInline, config.SourceMapInlineAndExternal: 730 outputContentsJoiner.EnsureNewlineAtEnd() 731 outputContentsJoiner.AddString(commentPrefix) 732 outputContentsJoiner.AddString("# sourceMappingURL=data:application/json;base64,") 733 outputContentsJoiner.AddString(base64.StdEncoding.EncodeToString(outputSourceMap)) 734 outputContentsJoiner.AddString(commentSuffix) 735 outputContentsJoiner.AddString("\n") 736 } 737 738 // Potentially write the external source map file 739 switch c.options.SourceMap { 740 case config.SourceMapLinkedWithComment, config.SourceMapInlineAndExternal, config.SourceMapExternalWithoutComment: 741 outputFiles = append(outputFiles, graph.OutputFile{ 742 AbsPath: c.fs.Join(c.options.AbsOutputDir, finalRelPathForSourceMap), 743 Contents: outputSourceMap, 744 JSONMetadataChunk: fmt.Sprintf( 745 "{\n \"imports\": [],\n \"exports\": [],\n \"inputs\": {},\n \"bytes\": %d\n }", len(outputSourceMap)), 746 }) 747 } 748 } 749 750 // Finalize the output contents 751 outputContents := outputContentsJoiner.Done() 752 753 // Path substitution for the JSON metadata 754 var jsonMetadataChunk string 755 if c.options.NeedsMetafile { 756 jsonMetadataChunkPieces := c.breakJoinerIntoPieces(chunk.jsonMetadataChunkCallback(len(outputContents))) 757 jsonMetadataChunkBytes, _ := c.substituteFinalPaths(jsonMetadataChunkPieces, func(finalRelPathForImport string) string { 758 return resolver.PrettyPath(c.fs, logger.Path{Text: c.fs.Join(c.options.AbsOutputDir, finalRelPathForImport), Namespace: "file"}) 759 }) 760 jsonMetadataChunk = string(jsonMetadataChunkBytes.Done()) 761 } 762 763 // Generate the output file for this chunk 764 outputFiles = append(outputFiles, graph.OutputFile{ 765 AbsPath: c.fs.Join(c.options.AbsOutputDir, chunk.finalRelPath), 766 Contents: outputContents, 767 JSONMetadataChunk: jsonMetadataChunk, 768 IsExecutable: chunk.isExecutable, 769 }) 770 771 results[chunkIndex] = outputFiles 772 resultsWaitGroup.Done() 773 }(chunkIndex, chunk) 774 } 775 resultsWaitGroup.Wait() 776 c.timer.End("Generate final output files") 777 778 // Merge the output files from the different goroutines together in order 779 outputFilesLen := len(additionalFiles) 780 for _, result := range results { 781 outputFilesLen += len(result) 782 } 783 outputFiles := make([]graph.OutputFile, 0, outputFilesLen) 784 outputFiles = append(outputFiles, additionalFiles...) 785 for _, result := range results { 786 outputFiles = append(outputFiles, result...) 787 } 788 return outputFiles 789 } 790 791 // Given a set of output pieces (i.e. a buffer already divided into the spans 792 // between import paths), substitute the final import paths in and then join 793 // everything into a single byte buffer. 794 func (c *linkerContext) substituteFinalPaths( 795 intermediateOutput intermediateOutput, 796 modifyPath func(string) string, 797 ) (j helpers.Joiner, shifts []sourcemap.SourceMapShift) { 798 // Optimization: If there can be no substitutions, just reuse the initial 799 // joiner that was used when generating the intermediate chunk output 800 // instead of creating another one and copying the whole file into it. 801 if intermediateOutput.pieces == nil { 802 return intermediateOutput.joiner, []sourcemap.SourceMapShift{{}} 803 } 804 805 var shift sourcemap.SourceMapShift 806 shifts = make([]sourcemap.SourceMapShift, 0, len(intermediateOutput.pieces)) 807 shifts = append(shifts, shift) 808 809 for _, piece := range intermediateOutput.pieces { 810 var dataOffset sourcemap.LineColumnOffset 811 j.AddBytes(piece.data) 812 dataOffset.AdvanceBytes(piece.data) 813 shift.Before.Add(dataOffset) 814 shift.After.Add(dataOffset) 815 816 switch piece.kind { 817 case outputPieceAssetIndex: 818 file := c.graph.Files[piece.index] 819 if len(file.InputFile.AdditionalFiles) != 1 { 820 panic("Internal error") 821 } 822 relPath, _ := c.fs.Rel(c.options.AbsOutputDir, file.InputFile.AdditionalFiles[0].AbsPath) 823 824 // Make sure to always use forward slashes, even on Windows 825 relPath = strings.ReplaceAll(relPath, "\\", "/") 826 827 importPath := modifyPath(relPath) 828 j.AddString(importPath) 829 shift.Before.AdvanceString(file.InputFile.UniqueKeyForAdditionalFile) 830 shift.After.AdvanceString(importPath) 831 shifts = append(shifts, shift) 832 833 case outputPieceChunkIndex: 834 chunk := c.chunks[piece.index] 835 importPath := modifyPath(chunk.finalRelPath) 836 j.AddString(importPath) 837 shift.Before.AdvanceString(chunk.uniqueKey) 838 shift.After.AdvanceString(importPath) 839 shifts = append(shifts, shift) 840 } 841 } 842 843 return 844 } 845 846 func (c *linkerContext) accurateFinalByteCount(output intermediateOutput, chunkFinalRelDir string) int { 847 count := 0 848 849 // Note: The paths generated here must match "substituteFinalPaths" above 850 for _, piece := range output.pieces { 851 count += len(piece.data) 852 853 switch piece.kind { 854 case outputPieceAssetIndex: 855 file := c.graph.Files[piece.index] 856 if len(file.InputFile.AdditionalFiles) != 1 { 857 panic("Internal error") 858 } 859 relPath, _ := c.fs.Rel(c.options.AbsOutputDir, file.InputFile.AdditionalFiles[0].AbsPath) 860 861 // Make sure to always use forward slashes, even on Windows 862 relPath = strings.ReplaceAll(relPath, "\\", "/") 863 864 importPath := c.pathBetweenChunks(chunkFinalRelDir, relPath) 865 count += len(importPath) 866 867 case outputPieceChunkIndex: 868 chunk := c.chunks[piece.index] 869 importPath := c.pathBetweenChunks(chunkFinalRelDir, chunk.finalRelPath) 870 count += len(importPath) 871 } 872 } 873 874 return count 875 } 876 877 func (c *linkerContext) pathBetweenChunks(fromRelDir string, toRelPath string) string { 878 // Join with the public path if it has been configured 879 if c.options.PublicPath != "" { 880 return joinWithPublicPath(c.options.PublicPath, toRelPath) 881 } 882 883 // Otherwise, return a relative path 884 relPath, ok := c.fs.Rel(fromRelDir, toRelPath) 885 if !ok { 886 c.log.AddError(nil, logger.Range{}, 887 fmt.Sprintf("Cannot traverse from directory %q to chunk %q", fromRelDir, toRelPath)) 888 return "" 889 } 890 891 // Make sure to always use forward slashes, even on Windows 892 relPath = strings.ReplaceAll(relPath, "\\", "/") 893 894 // Make sure the relative path doesn't start with a name, since that could 895 // be interpreted as a package path instead of a relative path 896 if !strings.HasPrefix(relPath, "./") && !strings.HasPrefix(relPath, "../") { 897 relPath = "./" + relPath 898 } 899 900 return relPath 901 } 902 903 func (c *linkerContext) computeCrossChunkDependencies() { 904 c.timer.Begin("Compute cross-chunk dependencies") 905 defer c.timer.End("Compute cross-chunk dependencies") 906 907 if !c.options.CodeSplitting { 908 // No need to compute cross-chunk dependencies if there can't be any 909 return 910 } 911 912 type chunkMeta struct { 913 imports map[ast.Ref]bool 914 exports map[ast.Ref]bool 915 dynamicImports map[int]bool 916 } 917 918 chunkMetas := make([]chunkMeta, len(c.chunks)) 919 920 // For each chunk, see what symbols it uses from other chunks. Do this in 921 // parallel because it's the most expensive part of this function. 922 waitGroup := sync.WaitGroup{} 923 waitGroup.Add(len(c.chunks)) 924 for chunkIndex, chunk := range c.chunks { 925 go func(chunkIndex int, chunk chunkInfo) { 926 chunkMeta := &chunkMetas[chunkIndex] 927 imports := make(map[ast.Ref]bool) 928 chunkMeta.imports = imports 929 chunkMeta.exports = make(map[ast.Ref]bool) 930 931 // Go over each file in this chunk 932 for sourceIndex := range chunk.filesWithPartsInChunk { 933 // Go over each part in this file that's marked for inclusion in this chunk 934 switch repr := c.graph.Files[sourceIndex].InputFile.Repr.(type) { 935 case *graph.JSRepr: 936 for partIndex, partMeta := range repr.AST.Parts { 937 if !partMeta.IsLive { 938 continue 939 } 940 part := &repr.AST.Parts[partIndex] 941 942 // Rewrite external dynamic imports to point to the chunk for that entry point 943 for _, importRecordIndex := range part.ImportRecordIndices { 944 record := &repr.AST.ImportRecords[importRecordIndex] 945 if record.SourceIndex.IsValid() && c.isExternalDynamicImport(record, sourceIndex) { 946 otherChunkIndex := c.graph.Files[record.SourceIndex.GetIndex()].EntryPointChunkIndex 947 record.Path.Text = c.chunks[otherChunkIndex].uniqueKey 948 record.SourceIndex = ast.Index32{} 949 record.Flags |= ast.ShouldNotBeExternalInMetafile | ast.ContainsUniqueKey 950 951 // Track this cross-chunk dynamic import so we make sure to 952 // include its hash when we're calculating the hashes of all 953 // dependencies of this chunk. 954 if int(otherChunkIndex) != chunkIndex { 955 if chunkMeta.dynamicImports == nil { 956 chunkMeta.dynamicImports = make(map[int]bool) 957 } 958 chunkMeta.dynamicImports[int(otherChunkIndex)] = true 959 } 960 } 961 } 962 963 // Remember what chunk each top-level symbol is declared in. Symbols 964 // with multiple declarations such as repeated "var" statements with 965 // the same name should already be marked as all being in a single 966 // chunk. In that case this will overwrite the same value below which 967 // is fine. 968 for _, declared := range part.DeclaredSymbols { 969 if declared.IsTopLevel { 970 c.graph.Symbols.Get(declared.Ref).ChunkIndex = ast.MakeIndex32(uint32(chunkIndex)) 971 } 972 } 973 974 // Record each symbol used in this part. This will later be matched up 975 // with our map of which chunk a given symbol is declared in to 976 // determine if the symbol needs to be imported from another chunk. 977 for ref := range part.SymbolUses { 978 symbol := c.graph.Symbols.Get(ref) 979 980 // Ignore unbound symbols, which don't have declarations 981 if symbol.Kind == ast.SymbolUnbound { 982 continue 983 } 984 985 // Ignore symbols that are going to be replaced by undefined 986 if symbol.ImportItemStatus == ast.ImportItemMissing { 987 continue 988 } 989 990 // If this is imported from another file, follow the import 991 // reference and reference the symbol in that file instead 992 if importData, ok := repr.Meta.ImportsToBind[ref]; ok { 993 ref = importData.Ref 994 symbol = c.graph.Symbols.Get(ref) 995 } else if repr.Meta.Wrap == graph.WrapCJS && ref != repr.AST.WrapperRef { 996 // The only internal symbol that wrapped CommonJS files export 997 // is the wrapper itself. 998 continue 999 } 1000 1001 // If this is an ES6 import from a CommonJS file, it will become a 1002 // property access off the namespace symbol instead of a bare 1003 // identifier. In that case we want to pull in the namespace symbol 1004 // instead. The namespace symbol stores the result of "require()". 1005 if symbol.NamespaceAlias != nil { 1006 ref = symbol.NamespaceAlias.NamespaceRef 1007 } 1008 1009 // We must record this relationship even for symbols that are not 1010 // imports. Due to code splitting, the definition of a symbol may 1011 // be moved to a separate chunk than the use of a symbol even if 1012 // the definition and use of that symbol are originally from the 1013 // same source file. 1014 imports[ref] = true 1015 } 1016 } 1017 } 1018 } 1019 1020 // Include the exports if this is an entry point chunk 1021 if chunk.isEntryPoint { 1022 if repr, ok := c.graph.Files[chunk.sourceIndex].InputFile.Repr.(*graph.JSRepr); ok { 1023 if repr.Meta.Wrap != graph.WrapCJS { 1024 for _, alias := range repr.Meta.SortedAndFilteredExportAliases { 1025 export := repr.Meta.ResolvedExports[alias] 1026 targetRef := export.Ref 1027 1028 // If this is an import, then target what the import points to 1029 if importData, ok := c.graph.Files[export.SourceIndex].InputFile.Repr.(*graph.JSRepr).Meta.ImportsToBind[targetRef]; ok { 1030 targetRef = importData.Ref 1031 } 1032 1033 // If this is an ES6 import from a CommonJS file, it will become a 1034 // property access off the namespace symbol instead of a bare 1035 // identifier. In that case we want to pull in the namespace symbol 1036 // instead. The namespace symbol stores the result of "require()". 1037 if symbol := c.graph.Symbols.Get(targetRef); symbol.NamespaceAlias != nil { 1038 targetRef = symbol.NamespaceAlias.NamespaceRef 1039 } 1040 1041 imports[targetRef] = true 1042 } 1043 } 1044 1045 // Ensure "exports" is included if the current output format needs it 1046 if repr.Meta.ForceIncludeExportsForEntryPoint { 1047 imports[repr.AST.ExportsRef] = true 1048 } 1049 1050 // Include the wrapper if present 1051 if repr.Meta.Wrap != graph.WrapNone { 1052 imports[repr.AST.WrapperRef] = true 1053 } 1054 } 1055 } 1056 1057 waitGroup.Done() 1058 }(chunkIndex, chunk) 1059 } 1060 waitGroup.Wait() 1061 1062 // Mark imported symbols as exported in the chunk from which they are declared 1063 for chunkIndex := range c.chunks { 1064 chunk := &c.chunks[chunkIndex] 1065 chunkRepr, ok := chunk.chunkRepr.(*chunkReprJS) 1066 if !ok { 1067 continue 1068 } 1069 chunkMeta := chunkMetas[chunkIndex] 1070 1071 // Find all uses in this chunk of symbols from other chunks 1072 chunkRepr.importsFromOtherChunks = make(map[uint32]crossChunkImportItemArray) 1073 for importRef := range chunkMeta.imports { 1074 // Ignore uses that aren't top-level symbols 1075 if otherChunkIndex := c.graph.Symbols.Get(importRef).ChunkIndex; otherChunkIndex.IsValid() { 1076 if otherChunkIndex := otherChunkIndex.GetIndex(); otherChunkIndex != uint32(chunkIndex) { 1077 chunkRepr.importsFromOtherChunks[otherChunkIndex] = 1078 append(chunkRepr.importsFromOtherChunks[otherChunkIndex], crossChunkImportItem{ref: importRef}) 1079 chunkMetas[otherChunkIndex].exports[importRef] = true 1080 } 1081 } 1082 } 1083 1084 // If this is an entry point, make sure we import all chunks belonging to 1085 // this entry point, even if there are no imports. We need to make sure 1086 // these chunks are evaluated for their side effects too. 1087 if chunk.isEntryPoint { 1088 for otherChunkIndex, otherChunk := range c.chunks { 1089 if _, ok := otherChunk.chunkRepr.(*chunkReprJS); ok && chunkIndex != otherChunkIndex && otherChunk.entryBits.HasBit(chunk.entryPointBit) { 1090 imports := chunkRepr.importsFromOtherChunks[uint32(otherChunkIndex)] 1091 chunkRepr.importsFromOtherChunks[uint32(otherChunkIndex)] = imports 1092 } 1093 } 1094 } 1095 1096 // Make sure we also track dynamic cross-chunk imports. These need to be 1097 // tracked so we count them as dependencies of this chunk for the purpose 1098 // of hash calculation. 1099 if chunkMeta.dynamicImports != nil { 1100 sortedDynamicImports := make([]int, 0, len(chunkMeta.dynamicImports)) 1101 for chunkIndex := range chunkMeta.dynamicImports { 1102 sortedDynamicImports = append(sortedDynamicImports, chunkIndex) 1103 } 1104 sort.Ints(sortedDynamicImports) 1105 for _, chunkIndex := range sortedDynamicImports { 1106 chunk.crossChunkImports = append(chunk.crossChunkImports, chunkImport{ 1107 importKind: ast.ImportDynamic, 1108 chunkIndex: uint32(chunkIndex), 1109 }) 1110 } 1111 } 1112 } 1113 1114 // Generate cross-chunk exports. These must be computed before cross-chunk 1115 // imports because of export alias renaming, which must consider all export 1116 // aliases simultaneously to avoid collisions. 1117 for chunkIndex := range c.chunks { 1118 chunk := &c.chunks[chunkIndex] 1119 chunkRepr, ok := chunk.chunkRepr.(*chunkReprJS) 1120 if !ok { 1121 continue 1122 } 1123 1124 chunkRepr.exportsToOtherChunks = make(map[ast.Ref]string) 1125 switch c.options.OutputFormat { 1126 case config.FormatESModule: 1127 r := renamer.ExportRenamer{} 1128 var items []js_ast.ClauseItem 1129 for _, export := range c.sortedCrossChunkExportItems(chunkMetas[chunkIndex].exports) { 1130 var alias string 1131 if c.options.MinifyIdentifiers { 1132 alias = r.NextMinifiedName() 1133 } else { 1134 alias = r.NextRenamedName(c.graph.Symbols.Get(export.Ref).OriginalName) 1135 } 1136 items = append(items, js_ast.ClauseItem{Name: ast.LocRef{Ref: export.Ref}, Alias: alias}) 1137 chunkRepr.exportsToOtherChunks[export.Ref] = alias 1138 } 1139 if len(items) > 0 { 1140 chunkRepr.crossChunkSuffixStmts = []js_ast.Stmt{{Data: &js_ast.SExportClause{ 1141 Items: items, 1142 }}} 1143 } 1144 1145 default: 1146 panic("Internal error") 1147 } 1148 } 1149 1150 // Generate cross-chunk imports. These must be computed after cross-chunk 1151 // exports because the export aliases must already be finalized so they can 1152 // be embedded in the generated import statements. 1153 for chunkIndex := range c.chunks { 1154 chunk := &c.chunks[chunkIndex] 1155 chunkRepr, ok := chunk.chunkRepr.(*chunkReprJS) 1156 if !ok { 1157 continue 1158 } 1159 1160 var crossChunkPrefixStmts []js_ast.Stmt 1161 1162 for _, crossChunkImport := range c.sortedCrossChunkImports(chunkRepr.importsFromOtherChunks) { 1163 switch c.options.OutputFormat { 1164 case config.FormatESModule: 1165 var items []js_ast.ClauseItem 1166 for _, item := range crossChunkImport.sortedImportItems { 1167 items = append(items, js_ast.ClauseItem{Name: ast.LocRef{Ref: item.ref}, Alias: item.exportAlias}) 1168 } 1169 importRecordIndex := uint32(len(chunk.crossChunkImports)) 1170 chunk.crossChunkImports = append(chunk.crossChunkImports, chunkImport{ 1171 importKind: ast.ImportStmt, 1172 chunkIndex: crossChunkImport.chunkIndex, 1173 }) 1174 if len(items) > 0 { 1175 // "import {a, b} from './chunk.js'" 1176 crossChunkPrefixStmts = append(crossChunkPrefixStmts, js_ast.Stmt{Data: &js_ast.SImport{ 1177 Items: &items, 1178 ImportRecordIndex: importRecordIndex, 1179 }}) 1180 } else { 1181 // "import './chunk.js'" 1182 crossChunkPrefixStmts = append(crossChunkPrefixStmts, js_ast.Stmt{Data: &js_ast.SImport{ 1183 ImportRecordIndex: importRecordIndex, 1184 }}) 1185 } 1186 1187 default: 1188 panic("Internal error") 1189 } 1190 } 1191 1192 chunkRepr.crossChunkPrefixStmts = crossChunkPrefixStmts 1193 } 1194 } 1195 1196 type crossChunkImport struct { 1197 sortedImportItems crossChunkImportItemArray 1198 chunkIndex uint32 1199 } 1200 1201 // This type is just so we can use Go's native sort function 1202 type crossChunkImportArray []crossChunkImport 1203 1204 func (a crossChunkImportArray) Len() int { return len(a) } 1205 func (a crossChunkImportArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] } 1206 1207 func (a crossChunkImportArray) Less(i int, j int) bool { 1208 return a[i].chunkIndex < a[j].chunkIndex 1209 } 1210 1211 // Sort cross-chunk imports by chunk name for determinism 1212 func (c *linkerContext) sortedCrossChunkImports(importsFromOtherChunks map[uint32]crossChunkImportItemArray) crossChunkImportArray { 1213 result := make(crossChunkImportArray, 0, len(importsFromOtherChunks)) 1214 1215 for otherChunkIndex, importItems := range importsFromOtherChunks { 1216 // Sort imports from a single chunk by alias for determinism 1217 otherChunk := &c.chunks[otherChunkIndex] 1218 exportsToOtherChunks := otherChunk.chunkRepr.(*chunkReprJS).exportsToOtherChunks 1219 for i, item := range importItems { 1220 importItems[i].exportAlias = exportsToOtherChunks[item.ref] 1221 } 1222 sort.Sort(importItems) 1223 result = append(result, crossChunkImport{ 1224 chunkIndex: otherChunkIndex, 1225 sortedImportItems: importItems, 1226 }) 1227 } 1228 1229 sort.Sort(result) 1230 return result 1231 } 1232 1233 type crossChunkImportItem struct { 1234 exportAlias string 1235 ref ast.Ref 1236 } 1237 1238 // This type is just so we can use Go's native sort function 1239 type crossChunkImportItemArray []crossChunkImportItem 1240 1241 func (a crossChunkImportItemArray) Len() int { return len(a) } 1242 func (a crossChunkImportItemArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] } 1243 1244 func (a crossChunkImportItemArray) Less(i int, j int) bool { 1245 return a[i].exportAlias < a[j].exportAlias 1246 } 1247 1248 // The sort order here is arbitrary but needs to be consistent between builds. 1249 // The InnerIndex should be stable because the parser for a single file is 1250 // single-threaded and deterministically assigns out InnerIndex values 1251 // sequentially. But the SourceIndex should be unstable because the main thread 1252 // assigns out source index values sequentially to newly-discovered dependencies 1253 // in a multi-threaded producer/consumer relationship. So instead we use the 1254 // index of the source in the DFS order over all entry points for stability. 1255 type stableRef struct { 1256 StableSourceIndex uint32 1257 Ref ast.Ref 1258 } 1259 1260 // This type is just so we can use Go's native sort function 1261 type stableRefArray []stableRef 1262 1263 func (a stableRefArray) Len() int { return len(a) } 1264 func (a stableRefArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] } 1265 func (a stableRefArray) Less(i int, j int) bool { 1266 ai, aj := a[i], a[j] 1267 return ai.StableSourceIndex < aj.StableSourceIndex || 1268 (ai.StableSourceIndex == aj.StableSourceIndex && ai.Ref.InnerIndex < aj.Ref.InnerIndex) 1269 } 1270 1271 // Sort cross-chunk exports by chunk name for determinism 1272 func (c *linkerContext) sortedCrossChunkExportItems(exportRefs map[ast.Ref]bool) stableRefArray { 1273 result := make(stableRefArray, 0, len(exportRefs)) 1274 for ref := range exportRefs { 1275 result = append(result, stableRef{ 1276 StableSourceIndex: c.graph.StableSourceIndices[ref.SourceIndex], 1277 Ref: ref, 1278 }) 1279 } 1280 sort.Sort(result) 1281 return result 1282 } 1283 1284 func (c *linkerContext) scanImportsAndExports() { 1285 c.timer.Begin("Scan imports and exports") 1286 defer c.timer.End("Scan imports and exports") 1287 1288 // Step 1: Figure out what modules must be CommonJS 1289 c.timer.Begin("Step 1") 1290 for _, sourceIndex := range c.graph.ReachableFiles { 1291 file := &c.graph.Files[sourceIndex] 1292 additionalFiles := file.InputFile.AdditionalFiles 1293 1294 switch repr := file.InputFile.Repr.(type) { 1295 case *graph.CSSRepr: 1296 // Inline URLs for non-CSS files into the CSS file 1297 for importRecordIndex := range repr.AST.ImportRecords { 1298 if record := &repr.AST.ImportRecords[importRecordIndex]; record.SourceIndex.IsValid() { 1299 otherFile := &c.graph.Files[record.SourceIndex.GetIndex()] 1300 if otherRepr, ok := otherFile.InputFile.Repr.(*graph.JSRepr); ok { 1301 record.Path.Text = otherRepr.AST.URLForCSS 1302 record.Path.Namespace = "" 1303 record.SourceIndex = ast.Index32{} 1304 if otherFile.InputFile.Loader == config.LoaderEmpty { 1305 record.Flags |= ast.WasLoadedWithEmptyLoader 1306 } else { 1307 record.Flags |= ast.ShouldNotBeExternalInMetafile 1308 } 1309 if strings.Contains(otherRepr.AST.URLForCSS, c.uniqueKeyPrefix) { 1310 record.Flags |= ast.ContainsUniqueKey 1311 } 1312 1313 // Copy the additional files to the output directory 1314 additionalFiles = append(additionalFiles, otherFile.InputFile.AdditionalFiles...) 1315 } 1316 } else if record.CopySourceIndex.IsValid() { 1317 otherFile := &c.graph.Files[record.CopySourceIndex.GetIndex()] 1318 if otherRepr, ok := otherFile.InputFile.Repr.(*graph.CopyRepr); ok { 1319 record.Path.Text = otherRepr.URLForCode 1320 record.Path.Namespace = "" 1321 record.CopySourceIndex = ast.Index32{} 1322 record.Flags |= ast.ShouldNotBeExternalInMetafile | ast.ContainsUniqueKey 1323 1324 // Copy the additional files to the output directory 1325 additionalFiles = append(additionalFiles, otherFile.InputFile.AdditionalFiles...) 1326 } 1327 } 1328 } 1329 1330 // Validate cross-file "composes: ... from" named imports 1331 for _, composes := range repr.AST.Composes { 1332 for _, name := range composes.ImportedNames { 1333 if record := repr.AST.ImportRecords[name.ImportRecordIndex]; record.SourceIndex.IsValid() { 1334 otherFile := &c.graph.Files[record.SourceIndex.GetIndex()] 1335 if otherRepr, ok := otherFile.InputFile.Repr.(*graph.CSSRepr); ok { 1336 if _, ok := otherRepr.AST.LocalScope[name.Alias]; !ok { 1337 if global, ok := otherRepr.AST.GlobalScope[name.Alias]; ok { 1338 var hint string 1339 if otherFile.InputFile.Loader == config.LoaderCSS { 1340 hint = fmt.Sprintf("Use the \"local-css\" loader for %q to enable local names.", otherFile.InputFile.Source.PrettyPath) 1341 } else { 1342 hint = fmt.Sprintf("Use the \":local\" selector to change %q into a local name.", name.Alias) 1343 } 1344 c.log.AddErrorWithNotes(file.LineColumnTracker(), 1345 css_lexer.RangeOfIdentifier(file.InputFile.Source, name.AliasLoc), 1346 fmt.Sprintf("Cannot use global name %q with \"composes\"", name.Alias), 1347 []logger.MsgData{ 1348 otherFile.LineColumnTracker().MsgData( 1349 css_lexer.RangeOfIdentifier(otherFile.InputFile.Source, global.Loc), 1350 fmt.Sprintf("The global name %q is defined here:", name.Alias), 1351 ), 1352 {Text: hint}, 1353 }) 1354 } else { 1355 c.log.AddError(file.LineColumnTracker(), 1356 css_lexer.RangeOfIdentifier(file.InputFile.Source, name.AliasLoc), 1357 fmt.Sprintf("The name %q never appears in %q", 1358 name.Alias, otherFile.InputFile.Source.PrettyPath)) 1359 } 1360 } 1361 } 1362 } 1363 } 1364 } 1365 1366 c.validateComposesFromProperties(file, repr) 1367 1368 case *graph.JSRepr: 1369 for importRecordIndex := range repr.AST.ImportRecords { 1370 record := &repr.AST.ImportRecords[importRecordIndex] 1371 if !record.SourceIndex.IsValid() { 1372 if record.CopySourceIndex.IsValid() { 1373 otherFile := &c.graph.Files[record.CopySourceIndex.GetIndex()] 1374 if otherRepr, ok := otherFile.InputFile.Repr.(*graph.CopyRepr); ok { 1375 record.Path.Text = otherRepr.URLForCode 1376 record.Path.Namespace = "" 1377 record.CopySourceIndex = ast.Index32{} 1378 record.Flags |= ast.ShouldNotBeExternalInMetafile | ast.ContainsUniqueKey 1379 1380 // Copy the additional files to the output directory 1381 additionalFiles = append(additionalFiles, otherFile.InputFile.AdditionalFiles...) 1382 } 1383 } 1384 continue 1385 } 1386 1387 otherFile := &c.graph.Files[record.SourceIndex.GetIndex()] 1388 otherRepr := otherFile.InputFile.Repr.(*graph.JSRepr) 1389 1390 switch record.Kind { 1391 case ast.ImportStmt: 1392 // Importing using ES6 syntax from a file without any ES6 syntax 1393 // causes that module to be considered CommonJS-style, even if it 1394 // doesn't have any CommonJS exports. 1395 // 1396 // That means the ES6 imports will become undefined instead of 1397 // causing errors. This is for compatibility with older CommonJS- 1398 // style bundlers. 1399 // 1400 // We emit a warning in this case but try to avoid turning the module 1401 // into a CommonJS module if possible. This is possible with named 1402 // imports (the module stays an ECMAScript module but the imports are 1403 // rewritten with undefined) but is not possible with star or default 1404 // imports: 1405 // 1406 // import * as ns from './empty-file' 1407 // import defVal from './empty-file' 1408 // console.log(ns, defVal) 1409 // 1410 // In that case the module *is* considered a CommonJS module because 1411 // the namespace object must be created. 1412 if (record.Flags.Has(ast.ContainsImportStar) || record.Flags.Has(ast.ContainsDefaultAlias)) && 1413 otherRepr.AST.ExportsKind == js_ast.ExportsNone && !otherRepr.AST.HasLazyExport { 1414 otherRepr.Meta.Wrap = graph.WrapCJS 1415 otherRepr.AST.ExportsKind = js_ast.ExportsCommonJS 1416 } 1417 1418 case ast.ImportRequire: 1419 // Files that are imported with require() must be wrapped so that 1420 // they can be lazily-evaluated 1421 if otherRepr.AST.ExportsKind == js_ast.ExportsESM { 1422 otherRepr.Meta.Wrap = graph.WrapESM 1423 } else { 1424 otherRepr.Meta.Wrap = graph.WrapCJS 1425 otherRepr.AST.ExportsKind = js_ast.ExportsCommonJS 1426 } 1427 1428 case ast.ImportDynamic: 1429 if !c.options.CodeSplitting { 1430 // If we're not splitting, then import() is just a require() that 1431 // returns a promise, so the imported file must also be wrapped 1432 if otherRepr.AST.ExportsKind == js_ast.ExportsESM { 1433 otherRepr.Meta.Wrap = graph.WrapESM 1434 } else { 1435 otherRepr.Meta.Wrap = graph.WrapCJS 1436 otherRepr.AST.ExportsKind = js_ast.ExportsCommonJS 1437 } 1438 } 1439 } 1440 } 1441 1442 // If the output format doesn't have an implicit CommonJS wrapper, any file 1443 // that uses CommonJS features will need to be wrapped, even though the 1444 // resulting wrapper won't be invoked by other files. An exception is made 1445 // for entry point files in CommonJS format (or when in pass-through mode). 1446 if repr.AST.ExportsKind == js_ast.ExportsCommonJS && (!file.IsEntryPoint() || 1447 c.options.OutputFormat == config.FormatIIFE || c.options.OutputFormat == config.FormatESModule) { 1448 repr.Meta.Wrap = graph.WrapCJS 1449 } 1450 } 1451 1452 file.InputFile.AdditionalFiles = additionalFiles 1453 } 1454 c.timer.End("Step 1") 1455 1456 // Step 2: Propagate dynamic export status for export star statements that 1457 // are re-exports from a module whose exports are not statically analyzable. 1458 // In this case the export star must be evaluated at run time instead of at 1459 // bundle time. 1460 c.timer.Begin("Step 2") 1461 for _, sourceIndex := range c.graph.ReachableFiles { 1462 repr, ok := c.graph.Files[sourceIndex].InputFile.Repr.(*graph.JSRepr) 1463 if !ok { 1464 continue 1465 } 1466 1467 if repr.Meta.Wrap != graph.WrapNone { 1468 c.recursivelyWrapDependencies(sourceIndex) 1469 } 1470 1471 if len(repr.AST.ExportStarImportRecords) > 0 { 1472 visited := make(map[uint32]bool) 1473 c.hasDynamicExportsDueToExportStar(sourceIndex, visited) 1474 } 1475 1476 // Even if the output file is CommonJS-like, we may still need to wrap 1477 // CommonJS-style files. Any file that imports a CommonJS-style file will 1478 // cause that file to need to be wrapped. This is because the import 1479 // method, whatever it is, will need to invoke the wrapper. Note that 1480 // this can include entry points (e.g. an entry point that imports a file 1481 // that imports that entry point). 1482 for _, record := range repr.AST.ImportRecords { 1483 if record.SourceIndex.IsValid() { 1484 otherRepr := c.graph.Files[record.SourceIndex.GetIndex()].InputFile.Repr.(*graph.JSRepr) 1485 if otherRepr.AST.ExportsKind == js_ast.ExportsCommonJS { 1486 c.recursivelyWrapDependencies(record.SourceIndex.GetIndex()) 1487 } 1488 } 1489 } 1490 } 1491 c.timer.End("Step 2") 1492 1493 // Step 3: Resolve "export * from" statements. This must be done after we 1494 // discover all modules that can have dynamic exports because export stars 1495 // are ignored for those modules. 1496 c.timer.Begin("Step 3") 1497 exportStarStack := make([]uint32, 0, 32) 1498 for _, sourceIndex := range c.graph.ReachableFiles { 1499 repr, ok := c.graph.Files[sourceIndex].InputFile.Repr.(*graph.JSRepr) 1500 if !ok { 1501 continue 1502 } 1503 1504 // Expression-style loaders defer code generation until linking. Code 1505 // generation is done here because at this point we know that the 1506 // "ExportsKind" field has its final value and will not be changed. 1507 if repr.AST.HasLazyExport { 1508 c.generateCodeForLazyExport(sourceIndex) 1509 } 1510 1511 // Propagate exports for export star statements 1512 if len(repr.AST.ExportStarImportRecords) > 0 { 1513 c.addExportsForExportStar(repr.Meta.ResolvedExports, sourceIndex, exportStarStack) 1514 } 1515 1516 // Also add a special export so import stars can bind to it. This must be 1517 // done in this step because it must come after CommonJS module discovery 1518 // but before matching imports with exports. 1519 repr.Meta.ResolvedExportStar = &graph.ExportData{ 1520 Ref: repr.AST.ExportsRef, 1521 SourceIndex: sourceIndex, 1522 } 1523 } 1524 c.timer.End("Step 3") 1525 1526 // Step 4: Match imports with exports. This must be done after we process all 1527 // export stars because imports can bind to export star re-exports. 1528 c.timer.Begin("Step 4") 1529 for _, sourceIndex := range c.graph.ReachableFiles { 1530 file := &c.graph.Files[sourceIndex] 1531 repr, ok := file.InputFile.Repr.(*graph.JSRepr) 1532 if !ok { 1533 continue 1534 } 1535 1536 if len(repr.AST.NamedImports) > 0 { 1537 c.matchImportsWithExportsForFile(uint32(sourceIndex)) 1538 } 1539 1540 // If we're exporting as CommonJS and this file was originally CommonJS, 1541 // then we'll be using the actual CommonJS "exports" and/or "module" 1542 // symbols. In that case make sure to mark them as such so they don't 1543 // get minified. 1544 if file.IsEntryPoint() && repr.AST.ExportsKind == js_ast.ExportsCommonJS && repr.Meta.Wrap == graph.WrapNone && 1545 (c.options.OutputFormat == config.FormatPreserve || c.options.OutputFormat == config.FormatCommonJS) { 1546 exportsRef := ast.FollowSymbols(c.graph.Symbols, repr.AST.ExportsRef) 1547 moduleRef := ast.FollowSymbols(c.graph.Symbols, repr.AST.ModuleRef) 1548 c.graph.Symbols.Get(exportsRef).Kind = ast.SymbolUnbound 1549 c.graph.Symbols.Get(moduleRef).Kind = ast.SymbolUnbound 1550 } else if repr.Meta.ForceIncludeExportsForEntryPoint || repr.AST.ExportsKind != js_ast.ExportsCommonJS { 1551 repr.Meta.NeedsExportsVariable = true 1552 } 1553 1554 // Create the wrapper part for wrapped files. This is needed by a later step. 1555 c.createWrapperForFile(uint32(sourceIndex)) 1556 } 1557 c.timer.End("Step 4") 1558 1559 // Step 5: Create namespace exports for every file. This is always necessary 1560 // for CommonJS files, and is also necessary for other files if they are 1561 // imported using an import star statement. 1562 c.timer.Begin("Step 5") 1563 waitGroup := sync.WaitGroup{} 1564 for _, sourceIndex := range c.graph.ReachableFiles { 1565 repr, ok := c.graph.Files[sourceIndex].InputFile.Repr.(*graph.JSRepr) 1566 if !ok { 1567 continue 1568 } 1569 1570 // This is the slowest step and is also parallelizable, so do this in parallel. 1571 waitGroup.Add(1) 1572 go func(sourceIndex uint32, repr *graph.JSRepr) { 1573 // Now that all exports have been resolved, sort and filter them to create 1574 // something we can iterate over later. 1575 aliases := make([]string, 0, len(repr.Meta.ResolvedExports)) 1576 nextAlias: 1577 for alias, export := range repr.Meta.ResolvedExports { 1578 otherFile := &c.graph.Files[export.SourceIndex].InputFile 1579 otherRepr := otherFile.Repr.(*graph.JSRepr) 1580 1581 // Re-exporting multiple symbols with the same name causes an ambiguous 1582 // export. These names cannot be used and should not end up in generated code. 1583 if len(export.PotentiallyAmbiguousExportStarRefs) > 0 { 1584 mainRef := export.Ref 1585 mainLoc := export.NameLoc 1586 if imported, ok := otherRepr.Meta.ImportsToBind[export.Ref]; ok { 1587 mainRef = imported.Ref 1588 mainLoc = imported.NameLoc 1589 } 1590 1591 for _, ambiguousExport := range export.PotentiallyAmbiguousExportStarRefs { 1592 ambiguousFile := &c.graph.Files[ambiguousExport.SourceIndex].InputFile 1593 ambiguousRepr := ambiguousFile.Repr.(*graph.JSRepr) 1594 ambiguousRef := ambiguousExport.Ref 1595 ambiguousLoc := ambiguousExport.NameLoc 1596 if imported, ok := ambiguousRepr.Meta.ImportsToBind[ambiguousExport.Ref]; ok { 1597 ambiguousRef = imported.Ref 1598 ambiguousLoc = imported.NameLoc 1599 } 1600 1601 if mainRef != ambiguousRef { 1602 file := &c.graph.Files[sourceIndex].InputFile 1603 otherTracker := logger.MakeLineColumnTracker(&otherFile.Source) 1604 ambiguousTracker := logger.MakeLineColumnTracker(&ambiguousFile.Source) 1605 c.log.AddIDWithNotes(logger.MsgID_Bundler_AmbiguousReexport, logger.Debug, nil, logger.Range{}, 1606 fmt.Sprintf("Re-export of %q in %q is ambiguous and has been removed", alias, file.Source.PrettyPath), 1607 []logger.MsgData{ 1608 otherTracker.MsgData(js_lexer.RangeOfIdentifier(otherFile.Source, mainLoc), 1609 fmt.Sprintf("One definition of %q comes from %q here:", alias, otherFile.Source.PrettyPath)), 1610 ambiguousTracker.MsgData(js_lexer.RangeOfIdentifier(ambiguousFile.Source, ambiguousLoc), 1611 fmt.Sprintf("Another definition of %q comes from %q here:", alias, ambiguousFile.Source.PrettyPath)), 1612 }, 1613 ) 1614 continue nextAlias 1615 } 1616 } 1617 } 1618 1619 // Ignore re-exported imports in TypeScript files that failed to be 1620 // resolved. These are probably just type-only imports so the best thing to 1621 // do is to silently omit them from the export list. 1622 if otherRepr.Meta.IsProbablyTypeScriptType[export.Ref] { 1623 continue 1624 } 1625 1626 if c.options.OutputFormat == config.FormatESModule && c.options.UnsupportedJSFeatures.Has(compat.ArbitraryModuleNamespaceNames) && c.graph.Files[sourceIndex].IsEntryPoint() { 1627 c.maybeForbidArbitraryModuleNamespaceIdentifier("export", export.SourceIndex, export.NameLoc, alias) 1628 } 1629 1630 aliases = append(aliases, alias) 1631 } 1632 sort.Strings(aliases) 1633 repr.Meta.SortedAndFilteredExportAliases = aliases 1634 1635 // Export creation uses "sortedAndFilteredExportAliases" so this must 1636 // come second after we fill in that array 1637 c.createExportsForFile(uint32(sourceIndex)) 1638 1639 // Each part tracks the other parts it depends on within this file 1640 localDependencies := make(map[uint32]uint32) 1641 parts := repr.AST.Parts 1642 namedImports := repr.AST.NamedImports 1643 graph := c.graph 1644 for partIndex := range parts { 1645 part := &parts[partIndex] 1646 1647 // Now that all files have been parsed, determine which property 1648 // accesses off of imported symbols are inlined enum values and 1649 // which ones aren't 1650 for ref, properties := range part.ImportSymbolPropertyUses { 1651 use := part.SymbolUses[ref] 1652 1653 // Rare path: this import is a TypeScript enum 1654 if importData, ok := repr.Meta.ImportsToBind[ref]; ok { 1655 if symbol := graph.Symbols.Get(importData.Ref); symbol.Kind == ast.SymbolTSEnum { 1656 if enum, ok := graph.TSEnums[importData.Ref]; ok { 1657 foundNonInlinedEnum := false 1658 for name, propertyUse := range properties { 1659 if _, ok := enum[name]; !ok { 1660 foundNonInlinedEnum = true 1661 use.CountEstimate += propertyUse.CountEstimate 1662 } 1663 } 1664 if foundNonInlinedEnum { 1665 part.SymbolUses[ref] = use 1666 } 1667 } 1668 continue 1669 } 1670 } 1671 1672 // Common path: this import isn't a TypeScript enum 1673 for _, propertyUse := range properties { 1674 use.CountEstimate += propertyUse.CountEstimate 1675 } 1676 part.SymbolUses[ref] = use 1677 } 1678 1679 // Also determine which function calls will be inlined (and so should 1680 // not count as uses), and which ones will not be (and so should count 1681 // as uses) 1682 for ref, callUse := range part.SymbolCallUses { 1683 use := part.SymbolUses[ref] 1684 1685 // Find the symbol that was called 1686 symbol := graph.Symbols.Get(ref) 1687 if symbol.Kind == ast.SymbolImport { 1688 if importData, ok := repr.Meta.ImportsToBind[ref]; ok { 1689 symbol = graph.Symbols.Get(importData.Ref) 1690 } 1691 } 1692 flags := symbol.Flags 1693 1694 // Rare path: this is a function that will be inlined 1695 if (flags & (ast.IsEmptyFunction | ast.CouldPotentiallyBeMutated)) == ast.IsEmptyFunction { 1696 // Every call will be inlined 1697 continue 1698 } else if (flags & (ast.IsIdentityFunction | ast.CouldPotentiallyBeMutated)) == ast.IsIdentityFunction { 1699 // Every single-argument call will be inlined as long as it's not a spread 1700 callUse.CallCountEstimate -= callUse.SingleArgNonSpreadCallCountEstimate 1701 if callUse.CallCountEstimate == 0 { 1702 continue 1703 } 1704 } 1705 1706 // Common path: this isn't a function that will be inlined 1707 use.CountEstimate += callUse.CallCountEstimate 1708 part.SymbolUses[ref] = use 1709 } 1710 1711 // Now that we know this, we can determine cross-part dependencies 1712 for ref := range part.SymbolUses { 1713 1714 // Rare path: this import is an inlined const value 1715 if graph.ConstValues != nil { 1716 if importData, ok := repr.Meta.ImportsToBind[ref]; ok { 1717 if _, isConstValue := graph.ConstValues[importData.Ref]; isConstValue { 1718 delete(part.SymbolUses, importData.Ref) 1719 continue 1720 } 1721 } 1722 } 1723 1724 for _, otherPartIndex := range repr.TopLevelSymbolToParts(ref) { 1725 if oldPartIndex, ok := localDependencies[otherPartIndex]; !ok || oldPartIndex != uint32(partIndex) { 1726 localDependencies[otherPartIndex] = uint32(partIndex) 1727 part.Dependencies = append(part.Dependencies, js_ast.Dependency{ 1728 SourceIndex: sourceIndex, 1729 PartIndex: otherPartIndex, 1730 }) 1731 } 1732 } 1733 1734 // Also map from imports to parts that use them 1735 if namedImport, ok := namedImports[ref]; ok { 1736 namedImport.LocalPartsWithUses = append(namedImport.LocalPartsWithUses, uint32(partIndex)) 1737 namedImports[ref] = namedImport 1738 } 1739 } 1740 } 1741 1742 waitGroup.Done() 1743 }(sourceIndex, repr) 1744 } 1745 waitGroup.Wait() 1746 c.timer.End("Step 5") 1747 1748 // Step 6: Bind imports to exports. This adds non-local dependencies on the 1749 // parts that declare the export to all parts that use the import. Also 1750 // generate wrapper parts for wrapped files. 1751 c.timer.Begin("Step 6") 1752 for _, sourceIndex := range c.graph.ReachableFiles { 1753 file := &c.graph.Files[sourceIndex] 1754 repr, ok := file.InputFile.Repr.(*graph.JSRepr) 1755 if !ok { 1756 continue 1757 } 1758 1759 // Pre-generate symbols for re-exports CommonJS symbols in case they 1760 // are necessary later. This is done now because the symbols map cannot be 1761 // mutated later due to parallelism. 1762 if file.IsEntryPoint() && c.options.OutputFormat == config.FormatESModule { 1763 copies := make([]ast.Ref, len(repr.Meta.SortedAndFilteredExportAliases)) 1764 for i, alias := range repr.Meta.SortedAndFilteredExportAliases { 1765 copies[i] = c.graph.GenerateNewSymbol(sourceIndex, ast.SymbolOther, "export_"+alias) 1766 } 1767 repr.Meta.CJSExportCopies = copies 1768 } 1769 1770 // Use "init_*" for ESM wrappers instead of "require_*" 1771 if repr.Meta.Wrap == graph.WrapESM { 1772 c.graph.Symbols.Get(repr.AST.WrapperRef).OriginalName = "init_" + file.InputFile.Source.IdentifierName 1773 } 1774 1775 // If this isn't CommonJS, then rename the unused "exports" and "module" 1776 // variables to avoid them causing the identically-named variables in 1777 // actual CommonJS files from being renamed. This is purely about 1778 // aesthetics and is not about correctness. This is done here because by 1779 // this point, we know the CommonJS status will not change further. 1780 if repr.Meta.Wrap != graph.WrapCJS && repr.AST.ExportsKind != js_ast.ExportsCommonJS { 1781 name := file.InputFile.Source.IdentifierName 1782 c.graph.Symbols.Get(repr.AST.ExportsRef).OriginalName = name + "_exports" 1783 c.graph.Symbols.Get(repr.AST.ModuleRef).OriginalName = name + "_module" 1784 } 1785 1786 // Include the "__export" symbol from the runtime if it was used in the 1787 // previous step. The previous step can't do this because it's running in 1788 // parallel and can't safely mutate the "importsToBind" map of another file. 1789 if repr.Meta.NeedsExportSymbolFromRuntime { 1790 runtimeRepr := c.graph.Files[runtime.SourceIndex].InputFile.Repr.(*graph.JSRepr) 1791 exportRef := runtimeRepr.AST.ModuleScope.Members["__export"].Ref 1792 c.graph.GenerateSymbolImportAndUse(sourceIndex, js_ast.NSExportPartIndex, exportRef, 1, runtime.SourceIndex) 1793 } 1794 1795 for importRef, importData := range repr.Meta.ImportsToBind { 1796 resolvedRepr := c.graph.Files[importData.SourceIndex].InputFile.Repr.(*graph.JSRepr) 1797 partsDeclaringSymbol := resolvedRepr.TopLevelSymbolToParts(importData.Ref) 1798 1799 for _, partIndex := range repr.AST.NamedImports[importRef].LocalPartsWithUses { 1800 part := &repr.AST.Parts[partIndex] 1801 1802 // Depend on the file containing the imported symbol 1803 for _, resolvedPartIndex := range partsDeclaringSymbol { 1804 part.Dependencies = append(part.Dependencies, js_ast.Dependency{ 1805 SourceIndex: importData.SourceIndex, 1806 PartIndex: resolvedPartIndex, 1807 }) 1808 } 1809 1810 // Also depend on any files that re-exported this symbol in between the 1811 // file containing the import and the file containing the imported symbol 1812 part.Dependencies = append(part.Dependencies, importData.ReExports...) 1813 } 1814 1815 // Merge these symbols so they will share the same name 1816 ast.MergeSymbols(c.graph.Symbols, importRef, importData.Ref) 1817 } 1818 1819 // If this is an entry point, depend on all exports so they are included 1820 if file.IsEntryPoint() { 1821 var dependencies []js_ast.Dependency 1822 1823 for _, alias := range repr.Meta.SortedAndFilteredExportAliases { 1824 export := repr.Meta.ResolvedExports[alias] 1825 targetSourceIndex := export.SourceIndex 1826 targetRef := export.Ref 1827 1828 // If this is an import, then target what the import points to 1829 targetRepr := c.graph.Files[targetSourceIndex].InputFile.Repr.(*graph.JSRepr) 1830 if importData, ok := targetRepr.Meta.ImportsToBind[targetRef]; ok { 1831 targetSourceIndex = importData.SourceIndex 1832 targetRef = importData.Ref 1833 targetRepr = c.graph.Files[targetSourceIndex].InputFile.Repr.(*graph.JSRepr) 1834 dependencies = append(dependencies, importData.ReExports...) 1835 } 1836 1837 // Pull in all declarations of this symbol 1838 for _, partIndex := range targetRepr.TopLevelSymbolToParts(targetRef) { 1839 dependencies = append(dependencies, js_ast.Dependency{ 1840 SourceIndex: targetSourceIndex, 1841 PartIndex: partIndex, 1842 }) 1843 } 1844 } 1845 1846 // Ensure "exports" is included if the current output format needs it 1847 if repr.Meta.ForceIncludeExportsForEntryPoint { 1848 dependencies = append(dependencies, js_ast.Dependency{ 1849 SourceIndex: sourceIndex, 1850 PartIndex: js_ast.NSExportPartIndex, 1851 }) 1852 } 1853 1854 // Include the wrapper if present 1855 if repr.Meta.Wrap != graph.WrapNone { 1856 dependencies = append(dependencies, js_ast.Dependency{ 1857 SourceIndex: sourceIndex, 1858 PartIndex: repr.Meta.WrapperPartIndex.GetIndex(), 1859 }) 1860 } 1861 1862 // Represent these constraints with a dummy part 1863 entryPointPartIndex := c.graph.AddPartToFile(sourceIndex, js_ast.Part{ 1864 Dependencies: dependencies, 1865 CanBeRemovedIfUnused: false, 1866 }) 1867 repr.Meta.EntryPointPartIndex = ast.MakeIndex32(entryPointPartIndex) 1868 1869 // Pull in the "__toCommonJS" symbol if we need it due to being an entry point 1870 if repr.Meta.ForceIncludeExportsForEntryPoint { 1871 c.graph.GenerateRuntimeSymbolImportAndUse(sourceIndex, entryPointPartIndex, "__toCommonJS", 1) 1872 } 1873 } 1874 1875 // Encode import-specific constraints in the dependency graph 1876 for partIndex, part := range repr.AST.Parts { 1877 toESMUses := uint32(0) 1878 toCommonJSUses := uint32(0) 1879 runtimeRequireUses := uint32(0) 1880 1881 // Imports of wrapped files must depend on the wrapper 1882 for _, importRecordIndex := range part.ImportRecordIndices { 1883 record := &repr.AST.ImportRecords[importRecordIndex] 1884 1885 // Don't follow external imports (this includes import() expressions) 1886 if !record.SourceIndex.IsValid() || c.isExternalDynamicImport(record, sourceIndex) { 1887 // This is an external import. Check if it will be a "require()" call. 1888 if record.Kind == ast.ImportRequire || !c.options.OutputFormat.KeepESMImportExportSyntax() || 1889 (record.Kind == ast.ImportDynamic && c.options.UnsupportedJSFeatures.Has(compat.DynamicImport)) { 1890 // We should use "__require" instead of "require" if we're not 1891 // generating a CommonJS output file, since it won't exist otherwise 1892 if config.ShouldCallRuntimeRequire(c.options.Mode, c.options.OutputFormat) { 1893 record.Flags |= ast.CallRuntimeRequire 1894 runtimeRequireUses++ 1895 } 1896 1897 // If this wasn't originally a "require()" call, then we may need 1898 // to wrap this in a call to the "__toESM" wrapper to convert from 1899 // CommonJS semantics to ESM semantics. 1900 // 1901 // Unfortunately this adds some additional code since the conversion 1902 // is somewhat complex. As an optimization, we can avoid this if the 1903 // following things are true: 1904 // 1905 // - The import is an ES module statement (e.g. not an "import()" expression) 1906 // - The ES module namespace object must not be captured 1907 // - The "default" and "__esModule" exports must not be accessed 1908 // 1909 if record.Kind != ast.ImportRequire && 1910 (record.Kind != ast.ImportStmt || 1911 record.Flags.Has(ast.ContainsImportStar) || 1912 record.Flags.Has(ast.ContainsDefaultAlias) || 1913 record.Flags.Has(ast.ContainsESModuleAlias)) { 1914 record.Flags |= ast.WrapWithToESM 1915 toESMUses++ 1916 } 1917 } 1918 continue 1919 } 1920 1921 otherSourceIndex := record.SourceIndex.GetIndex() 1922 otherRepr := c.graph.Files[otherSourceIndex].InputFile.Repr.(*graph.JSRepr) 1923 1924 if otherRepr.Meta.Wrap != graph.WrapNone { 1925 // Depend on the automatically-generated require wrapper symbol 1926 wrapperRef := otherRepr.AST.WrapperRef 1927 c.graph.GenerateSymbolImportAndUse(sourceIndex, uint32(partIndex), wrapperRef, 1, otherSourceIndex) 1928 1929 // This is an ES6 import of a CommonJS module, so it needs the 1930 // "__toESM" wrapper as long as it's not a bare "require()" 1931 if record.Kind != ast.ImportRequire && otherRepr.AST.ExportsKind == js_ast.ExportsCommonJS { 1932 record.Flags |= ast.WrapWithToESM 1933 toESMUses++ 1934 } 1935 1936 // If this is an ESM wrapper, also depend on the exports object 1937 // since the final code will contain an inline reference to it. 1938 // This must be done for "require()" and "import()" expressions 1939 // but does not need to be done for "import" statements since 1940 // those just cause us to reference the exports directly. 1941 if otherRepr.Meta.Wrap == graph.WrapESM && record.Kind != ast.ImportStmt { 1942 c.graph.GenerateSymbolImportAndUse(sourceIndex, uint32(partIndex), otherRepr.AST.ExportsRef, 1, otherSourceIndex) 1943 1944 // If this is a "require()" call, then we should add the 1945 // "__esModule" marker to behave as if the module was converted 1946 // from ESM to CommonJS. This is done via a wrapper instead of 1947 // by modifying the exports object itself because the same ES 1948 // module may be simultaneously imported and required, and the 1949 // importing code should not see "__esModule" while the requiring 1950 // code should see "__esModule". This is an extremely complex 1951 // and subtle set of bundler interop issues. See for example 1952 // https://github.com/evanw/esbuild/issues/1591. 1953 if record.Kind == ast.ImportRequire { 1954 record.Flags |= ast.WrapWithToCJS 1955 toCommonJSUses++ 1956 } 1957 } 1958 } else if record.Kind == ast.ImportStmt && otherRepr.AST.ExportsKind == js_ast.ExportsESMWithDynamicFallback { 1959 // This is an import of a module that has a dynamic export fallback 1960 // object. In that case we need to depend on that object in case 1961 // something ends up needing to use it later. This could potentially 1962 // be omitted in some cases with more advanced analysis if this 1963 // dynamic export fallback object doesn't end up being needed. 1964 c.graph.GenerateSymbolImportAndUse(sourceIndex, uint32(partIndex), otherRepr.AST.ExportsRef, 1, otherSourceIndex) 1965 } 1966 } 1967 1968 // If there's an ES6 import of a non-ES6 module, then we're going to need the 1969 // "__toESM" symbol from the runtime to wrap the result of "require()" 1970 c.graph.GenerateRuntimeSymbolImportAndUse(sourceIndex, uint32(partIndex), "__toESM", toESMUses) 1971 1972 // If there's a CommonJS require of an ES6 module, then we're going to need the 1973 // "__toCommonJS" symbol from the runtime to wrap the exports object 1974 c.graph.GenerateRuntimeSymbolImportAndUse(sourceIndex, uint32(partIndex), "__toCommonJS", toCommonJSUses) 1975 1976 // If there are unbundled calls to "require()" and we're not generating 1977 // code for node, then substitute a "__require" wrapper for "require". 1978 c.graph.GenerateRuntimeSymbolImportAndUse(sourceIndex, uint32(partIndex), "__require", runtimeRequireUses) 1979 1980 // If there's an ES6 export star statement of a non-ES6 module, then we're 1981 // going to need the "__reExport" symbol from the runtime 1982 reExportUses := uint32(0) 1983 for _, importRecordIndex := range repr.AST.ExportStarImportRecords { 1984 record := &repr.AST.ImportRecords[importRecordIndex] 1985 1986 // Is this export star evaluated at run time? 1987 happensAtRunTime := !record.SourceIndex.IsValid() && (!file.IsEntryPoint() || !c.options.OutputFormat.KeepESMImportExportSyntax()) 1988 if record.SourceIndex.IsValid() { 1989 otherSourceIndex := record.SourceIndex.GetIndex() 1990 otherRepr := c.graph.Files[otherSourceIndex].InputFile.Repr.(*graph.JSRepr) 1991 if otherSourceIndex != sourceIndex && otherRepr.AST.ExportsKind.IsDynamic() { 1992 happensAtRunTime = true 1993 } 1994 if otherRepr.AST.ExportsKind == js_ast.ExportsESMWithDynamicFallback { 1995 // This looks like "__reExport(exports_a, exports_b)". Make sure to 1996 // pull in the "exports_b" symbol into this export star. This matters 1997 // in code splitting situations where the "export_b" symbol might live 1998 // in a different chunk than this export star. 1999 c.graph.GenerateSymbolImportAndUse(sourceIndex, uint32(partIndex), otherRepr.AST.ExportsRef, 1, otherSourceIndex) 2000 } 2001 } 2002 if happensAtRunTime { 2003 // Depend on this file's "exports" object for the first argument to "__reExport" 2004 c.graph.GenerateSymbolImportAndUse(sourceIndex, uint32(partIndex), repr.AST.ExportsRef, 1, sourceIndex) 2005 record.Flags |= ast.CallsRunTimeReExportFn 2006 repr.AST.UsesExportsRef = true 2007 reExportUses++ 2008 } 2009 } 2010 c.graph.GenerateRuntimeSymbolImportAndUse(sourceIndex, uint32(partIndex), "__reExport", reExportUses) 2011 } 2012 } 2013 c.timer.End("Step 6") 2014 } 2015 2016 func (c *linkerContext) validateComposesFromProperties(rootFile *graph.LinkerFile, rootRepr *graph.CSSRepr) { 2017 for _, local := range rootRepr.AST.LocalSymbols { 2018 type propertyInFile struct { 2019 file *graph.LinkerFile 2020 loc logger.Loc 2021 } 2022 2023 visited := make(map[ast.Ref]bool) 2024 properties := make(map[string]propertyInFile) 2025 var visit func(*graph.LinkerFile, *graph.CSSRepr, ast.Ref) 2026 2027 visit = func(file *graph.LinkerFile, repr *graph.CSSRepr, ref ast.Ref) { 2028 if visited[ref] { 2029 return 2030 } 2031 visited[ref] = true 2032 2033 composes, ok := repr.AST.Composes[ref] 2034 if !ok { 2035 return 2036 } 2037 2038 for _, name := range composes.ImportedNames { 2039 if record := repr.AST.ImportRecords[name.ImportRecordIndex]; record.SourceIndex.IsValid() { 2040 otherFile := &c.graph.Files[record.SourceIndex.GetIndex()] 2041 if otherRepr, ok := otherFile.InputFile.Repr.(*graph.CSSRepr); ok { 2042 if otherName, ok := otherRepr.AST.LocalScope[name.Alias]; ok { 2043 visit(otherFile, otherRepr, otherName.Ref) 2044 } 2045 } 2046 } 2047 } 2048 2049 for _, name := range composes.Names { 2050 visit(file, repr, name.Ref) 2051 } 2052 2053 // Warn about cross-file composition with the same CSS properties 2054 for keyText, keyLoc := range composes.Properties { 2055 property, ok := properties[keyText] 2056 if !ok { 2057 properties[keyText] = propertyInFile{file, keyLoc} 2058 continue 2059 } 2060 if property.file == file || property.file == nil { 2061 continue 2062 } 2063 2064 localOriginalName := c.graph.Symbols.Get(local.Ref).OriginalName 2065 c.log.AddMsgID(logger.MsgID_CSS_UndefinedComposesFrom, logger.Msg{ 2066 Kind: logger.Warning, 2067 Data: rootFile.LineColumnTracker().MsgData( 2068 css_lexer.RangeOfIdentifier(rootFile.InputFile.Source, local.Loc), 2069 fmt.Sprintf("The value of %q in the %q class is undefined", keyText, localOriginalName), 2070 ), 2071 Notes: []logger.MsgData{ 2072 property.file.LineColumnTracker().MsgData( 2073 css_lexer.RangeOfIdentifier(property.file.InputFile.Source, property.loc), 2074 fmt.Sprintf("The first definition of %q is here:", keyText), 2075 ), 2076 file.LineColumnTracker().MsgData( 2077 css_lexer.RangeOfIdentifier(file.InputFile.Source, keyLoc), 2078 fmt.Sprintf("The second definition of %q is here:", keyText), 2079 ), 2080 {Text: fmt.Sprintf("The specification of \"composes\" does not define an order when class declarations from separate files are composed together. "+ 2081 "The value of the %q property for %q may change unpredictably as the code is edited. "+ 2082 "Make sure that all definitions of %q for %q are in a single file.", keyText, localOriginalName, keyText, localOriginalName)}, 2083 }, 2084 }) 2085 2086 // Don't warn more than once 2087 property.file = nil 2088 properties[keyText] = property 2089 } 2090 } 2091 2092 visit(rootFile, rootRepr, local.Ref) 2093 } 2094 } 2095 2096 func (c *linkerContext) generateCodeForLazyExport(sourceIndex uint32) { 2097 file := &c.graph.Files[sourceIndex] 2098 repr := file.InputFile.Repr.(*graph.JSRepr) 2099 2100 // Grab the lazy expression 2101 if len(repr.AST.Parts) < 1 { 2102 panic("Internal error") 2103 } 2104 part := &repr.AST.Parts[len(repr.AST.Parts)-1] 2105 if len(part.Stmts) != 1 { 2106 panic("Internal error") 2107 } 2108 lazyValue := part.Stmts[0].Data.(*js_ast.SLazyExport).Value 2109 2110 // If this JavaScript file is a stub from a CSS file, populate the exports of 2111 // this JavaScript stub with the local names from that CSS file. This is done 2112 // now instead of earlier because we need the whole bundle to be present. 2113 if repr.CSSSourceIndex.IsValid() { 2114 cssSourceIndex := repr.CSSSourceIndex.GetIndex() 2115 if css, ok := c.graph.Files[cssSourceIndex].InputFile.Repr.(*graph.CSSRepr); ok { 2116 exports := js_ast.EObject{} 2117 2118 for _, local := range css.AST.LocalSymbols { 2119 value := js_ast.Expr{Loc: local.Loc, Data: &js_ast.ENameOfSymbol{Ref: local.Ref}} 2120 visited := map[ast.Ref]bool{local.Ref: true} 2121 var parts []js_ast.TemplatePart 2122 var visitName func(*graph.CSSRepr, ast.Ref) 2123 var visitComposes func(*graph.CSSRepr, ast.Ref) 2124 2125 visitName = func(repr *graph.CSSRepr, ref ast.Ref) { 2126 if !visited[ref] { 2127 visited[ref] = true 2128 visitComposes(repr, ref) 2129 parts = append(parts, js_ast.TemplatePart{ 2130 Value: js_ast.Expr{Data: &js_ast.ENameOfSymbol{Ref: ref}}, 2131 TailCooked: []uint16{' '}, 2132 }) 2133 } 2134 } 2135 2136 visitComposes = func(repr *graph.CSSRepr, ref ast.Ref) { 2137 if composes, ok := repr.AST.Composes[ref]; ok { 2138 for _, name := range composes.ImportedNames { 2139 if record := repr.AST.ImportRecords[name.ImportRecordIndex]; record.SourceIndex.IsValid() { 2140 otherFile := &c.graph.Files[record.SourceIndex.GetIndex()] 2141 if otherRepr, ok := otherFile.InputFile.Repr.(*graph.CSSRepr); ok { 2142 if otherName, ok := otherRepr.AST.LocalScope[name.Alias]; ok { 2143 visitName(otherRepr, otherName.Ref) 2144 } 2145 } 2146 } 2147 } 2148 2149 for _, name := range composes.Names { 2150 visitName(repr, name.Ref) 2151 } 2152 } 2153 } 2154 2155 visitComposes(css, local.Ref) 2156 2157 if len(parts) > 0 { 2158 value.Data = &js_ast.ETemplate{Parts: append(parts, js_ast.TemplatePart{Value: value})} 2159 } 2160 2161 exports.Properties = append(exports.Properties, js_ast.Property{ 2162 Key: js_ast.Expr{Loc: local.Loc, Data: &js_ast.EString{Value: helpers.StringToUTF16(c.graph.Symbols.Get(local.Ref).OriginalName)}}, 2163 ValueOrNil: value, 2164 }) 2165 } 2166 2167 lazyValue.Data = &exports 2168 } 2169 } 2170 2171 // Use "module.exports = value" for CommonJS-style modules 2172 if repr.AST.ExportsKind == js_ast.ExportsCommonJS { 2173 part.Stmts = []js_ast.Stmt{js_ast.AssignStmt( 2174 js_ast.Expr{Loc: lazyValue.Loc, Data: &js_ast.EDot{ 2175 Target: js_ast.Expr{Loc: lazyValue.Loc, Data: &js_ast.EIdentifier{Ref: repr.AST.ModuleRef}}, 2176 Name: "exports", 2177 NameLoc: lazyValue.Loc, 2178 }}, 2179 lazyValue, 2180 )} 2181 c.graph.GenerateSymbolImportAndUse(sourceIndex, 0, repr.AST.ModuleRef, 1, sourceIndex) 2182 return 2183 } 2184 2185 // Otherwise, generate ES6 export statements. These are added as additional 2186 // parts so they can be tree shaken individually. 2187 part.Stmts = nil 2188 2189 // Generate a new symbol and link the export into the graph for tree shaking 2190 generateExport := func(loc logger.Loc, name string, alias string) (ast.Ref, uint32) { 2191 ref := c.graph.GenerateNewSymbol(sourceIndex, ast.SymbolOther, name) 2192 partIndex := c.graph.AddPartToFile(sourceIndex, js_ast.Part{ 2193 DeclaredSymbols: []js_ast.DeclaredSymbol{{Ref: ref, IsTopLevel: true}}, 2194 CanBeRemovedIfUnused: true, 2195 }) 2196 c.graph.GenerateSymbolImportAndUse(sourceIndex, partIndex, repr.AST.ModuleRef, 1, sourceIndex) 2197 repr.Meta.TopLevelSymbolToPartsOverlay[ref] = []uint32{partIndex} 2198 repr.Meta.ResolvedExports[alias] = graph.ExportData{ 2199 Ref: ref, 2200 NameLoc: loc, 2201 SourceIndex: sourceIndex, 2202 } 2203 return ref, partIndex 2204 } 2205 2206 // Unwrap JSON objects into separate top-level variables. This improves tree- 2207 // shaking by letting you only import part of a JSON file. 2208 // 2209 // But don't do this for files loaded via "with { type: 'json' }" as that 2210 // behavior is specified to not export anything except for the "default" 2211 // export: https://github.com/tc39/proposal-json-modules 2212 if object, ok := lazyValue.Data.(*js_ast.EObject); ok && file.InputFile.Loader != config.LoaderWithTypeJSON { 2213 for _, property := range object.Properties { 2214 if str, ok := property.Key.Data.(*js_ast.EString); ok && 2215 (!file.IsEntryPoint() || js_ast.IsIdentifierUTF16(str.Value) || 2216 !c.options.UnsupportedJSFeatures.Has(compat.ArbitraryModuleNamespaceNames)) { 2217 if name := helpers.UTF16ToString(str.Value); name != "default" { 2218 ref, partIndex := generateExport(property.Key.Loc, name, name) 2219 2220 // This initializes the generated variable with a copy of the property 2221 // value, which is INCORRECT for values that are objects/arrays because 2222 // they will have separate object identity. This is fixed up later in 2223 // "generateCodeForFileInChunkJS" by changing the object literal to 2224 // reference this generated variable instead. 2225 // 2226 // Changing the object literal is deferred until that point instead of 2227 // doing it now because we only want to do this for top-level variables 2228 // that actually end up being used, and we don't know which ones will 2229 // end up actually being used at this point (since import binding hasn't 2230 // happened yet). So we need to wait until after tree shaking happens. 2231 repr.AST.Parts[partIndex].Stmts = []js_ast.Stmt{{Loc: property.Key.Loc, Data: &js_ast.SLocal{ 2232 IsExport: true, 2233 Decls: []js_ast.Decl{{ 2234 Binding: js_ast.Binding{Loc: property.Key.Loc, Data: &js_ast.BIdentifier{Ref: ref}}, 2235 ValueOrNil: property.ValueOrNil, 2236 }}, 2237 }}} 2238 } 2239 } 2240 } 2241 } 2242 2243 // Generate the default export 2244 ref, partIndex := generateExport(lazyValue.Loc, file.InputFile.Source.IdentifierName+"_default", "default") 2245 repr.AST.Parts[partIndex].Stmts = []js_ast.Stmt{{Loc: lazyValue.Loc, Data: &js_ast.SExportDefault{ 2246 DefaultName: ast.LocRef{Loc: lazyValue.Loc, Ref: ref}, 2247 Value: js_ast.Stmt{Loc: lazyValue.Loc, Data: &js_ast.SExpr{Value: lazyValue}}, 2248 }}} 2249 } 2250 2251 func (c *linkerContext) createExportsForFile(sourceIndex uint32) { 2252 //////////////////////////////////////////////////////////////////////////////// 2253 // WARNING: This method is run in parallel over all files. Do not mutate data 2254 // for other files within this method or you will create a data race. 2255 //////////////////////////////////////////////////////////////////////////////// 2256 2257 file := &c.graph.Files[sourceIndex] 2258 repr := file.InputFile.Repr.(*graph.JSRepr) 2259 2260 // Generate a getter per export 2261 properties := []js_ast.Property{} 2262 nsExportDependencies := []js_ast.Dependency{} 2263 nsExportSymbolUses := make(map[ast.Ref]js_ast.SymbolUse) 2264 for _, alias := range repr.Meta.SortedAndFilteredExportAliases { 2265 export := repr.Meta.ResolvedExports[alias] 2266 2267 // If this is an export of an import, reference the symbol that the import 2268 // was eventually resolved to. We need to do this because imports have 2269 // already been resolved by this point, so we can't generate a new import 2270 // and have that be resolved later. 2271 if importData, ok := c.graph.Files[export.SourceIndex].InputFile.Repr.(*graph.JSRepr).Meta.ImportsToBind[export.Ref]; ok { 2272 export.Ref = importData.Ref 2273 export.SourceIndex = importData.SourceIndex 2274 nsExportDependencies = append(nsExportDependencies, importData.ReExports...) 2275 } 2276 2277 // Exports of imports need EImportIdentifier in case they need to be re- 2278 // written to a property access later on 2279 var value js_ast.Expr 2280 if c.graph.Symbols.Get(export.Ref).NamespaceAlias != nil { 2281 value = js_ast.Expr{Data: &js_ast.EImportIdentifier{Ref: export.Ref}} 2282 } else { 2283 value = js_ast.Expr{Data: &js_ast.EIdentifier{Ref: export.Ref}} 2284 } 2285 2286 // Add a getter property 2287 var getter js_ast.Expr 2288 body := js_ast.FnBody{Block: js_ast.SBlock{Stmts: []js_ast.Stmt{{Loc: value.Loc, Data: &js_ast.SReturn{ValueOrNil: value}}}}} 2289 if c.options.UnsupportedJSFeatures.Has(compat.Arrow) { 2290 getter = js_ast.Expr{Data: &js_ast.EFunction{Fn: js_ast.Fn{Body: body}}} 2291 } else { 2292 getter = js_ast.Expr{Data: &js_ast.EArrow{PreferExpr: true, Body: body}} 2293 } 2294 properties = append(properties, js_ast.Property{ 2295 Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16(alias)}}, 2296 ValueOrNil: getter, 2297 }) 2298 nsExportSymbolUses[export.Ref] = js_ast.SymbolUse{CountEstimate: 1} 2299 2300 // Make sure the part that declares the export is included 2301 for _, partIndex := range c.graph.Files[export.SourceIndex].InputFile.Repr.(*graph.JSRepr).TopLevelSymbolToParts(export.Ref) { 2302 // Use a non-local dependency since this is likely from a different 2303 // file if it came in through an export star 2304 nsExportDependencies = append(nsExportDependencies, js_ast.Dependency{ 2305 SourceIndex: export.SourceIndex, 2306 PartIndex: partIndex, 2307 }) 2308 } 2309 } 2310 2311 declaredSymbols := []js_ast.DeclaredSymbol{} 2312 var nsExportStmts []js_ast.Stmt 2313 2314 // Prefix this part with "var exports = {}" if this isn't a CommonJS entry point 2315 if repr.Meta.NeedsExportsVariable { 2316 nsExportStmts = append(nsExportStmts, js_ast.Stmt{Data: &js_ast.SLocal{Decls: []js_ast.Decl{{ 2317 Binding: js_ast.Binding{Data: &js_ast.BIdentifier{Ref: repr.AST.ExportsRef}}, 2318 ValueOrNil: js_ast.Expr{Data: &js_ast.EObject{}}, 2319 }}}}) 2320 declaredSymbols = append(declaredSymbols, js_ast.DeclaredSymbol{ 2321 Ref: repr.AST.ExportsRef, 2322 IsTopLevel: true, 2323 }) 2324 } 2325 2326 // "__export(exports, { foo: () => foo })" 2327 exportRef := ast.InvalidRef 2328 if len(properties) > 0 { 2329 runtimeRepr := c.graph.Files[runtime.SourceIndex].InputFile.Repr.(*graph.JSRepr) 2330 exportRef = runtimeRepr.AST.ModuleScope.Members["__export"].Ref 2331 nsExportStmts = append(nsExportStmts, js_ast.Stmt{Data: &js_ast.SExpr{Value: js_ast.Expr{Data: &js_ast.ECall{ 2332 Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: exportRef}}, 2333 Args: []js_ast.Expr{ 2334 {Data: &js_ast.EIdentifier{Ref: repr.AST.ExportsRef}}, 2335 {Data: &js_ast.EObject{ 2336 Properties: properties, 2337 }}, 2338 }, 2339 }}}}) 2340 2341 // Make sure this file depends on the "__export" symbol 2342 for _, partIndex := range runtimeRepr.TopLevelSymbolToParts(exportRef) { 2343 nsExportDependencies = append(nsExportDependencies, js_ast.Dependency{ 2344 SourceIndex: runtime.SourceIndex, 2345 PartIndex: partIndex, 2346 }) 2347 } 2348 2349 // Make sure the CommonJS closure, if there is one, includes "exports" 2350 repr.AST.UsesExportsRef = true 2351 } 2352 2353 // Decorate "module.exports" with the "__esModule" flag to indicate that 2354 // we used to be an ES module. This is done by wrapping the exports object 2355 // instead of by mutating the exports object because other modules in the 2356 // bundle (including the entry point module) may do "import * as" to get 2357 // access to the exports object and should NOT see the "__esModule" flag. 2358 if repr.Meta.ForceIncludeExportsForEntryPoint && 2359 c.options.OutputFormat == config.FormatCommonJS { 2360 2361 runtimeRepr := c.graph.Files[runtime.SourceIndex].InputFile.Repr.(*graph.JSRepr) 2362 toCommonJSRef := runtimeRepr.AST.NamedExports["__toCommonJS"].Ref 2363 2364 // "module.exports = __toCommonJS(exports);" 2365 nsExportStmts = append(nsExportStmts, js_ast.AssignStmt( 2366 js_ast.Expr{Data: &js_ast.EDot{ 2367 Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: c.unboundModuleRef}}, 2368 Name: "exports", 2369 }}, 2370 2371 js_ast.Expr{Data: &js_ast.ECall{ 2372 Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: toCommonJSRef}}, 2373 Args: []js_ast.Expr{{Data: &js_ast.EIdentifier{Ref: repr.AST.ExportsRef}}}, 2374 }}, 2375 )) 2376 } 2377 2378 // No need to generate a part if it'll be empty 2379 if len(nsExportStmts) > 0 { 2380 // Initialize the part that was allocated for us earlier. The information 2381 // here will be used after this during tree shaking. 2382 repr.AST.Parts[js_ast.NSExportPartIndex] = js_ast.Part{ 2383 Stmts: nsExportStmts, 2384 SymbolUses: nsExportSymbolUses, 2385 Dependencies: nsExportDependencies, 2386 DeclaredSymbols: declaredSymbols, 2387 2388 // This can be removed if nothing uses it 2389 CanBeRemovedIfUnused: true, 2390 2391 // Make sure this is trimmed if unused even if tree shaking is disabled 2392 ForceTreeShaking: true, 2393 } 2394 2395 // Pull in the "__export" symbol if it was used 2396 if exportRef != ast.InvalidRef { 2397 repr.Meta.NeedsExportSymbolFromRuntime = true 2398 } 2399 } 2400 } 2401 2402 func (c *linkerContext) createWrapperForFile(sourceIndex uint32) { 2403 repr := c.graph.Files[sourceIndex].InputFile.Repr.(*graph.JSRepr) 2404 2405 switch repr.Meta.Wrap { 2406 // If this is a CommonJS file, we're going to need to generate a wrapper 2407 // for the CommonJS closure. That will end up looking something like this: 2408 // 2409 // var require_foo = __commonJS((exports, module) => { 2410 // ... 2411 // }); 2412 // 2413 // However, that generation is special-cased for various reasons and is 2414 // done later on. Still, we're going to need to ensure that this file 2415 // both depends on the "__commonJS" symbol and declares the "require_foo" 2416 // symbol. Instead of special-casing this during the reachablity analysis 2417 // below, we just append a dummy part to the end of the file with these 2418 // dependencies and let the general-purpose reachablity analysis take care 2419 // of it. 2420 case graph.WrapCJS: 2421 runtimeRepr := c.graph.Files[runtime.SourceIndex].InputFile.Repr.(*graph.JSRepr) 2422 commonJSParts := runtimeRepr.TopLevelSymbolToParts(c.cjsRuntimeRef) 2423 2424 // Generate the dummy part 2425 dependencies := make([]js_ast.Dependency, len(commonJSParts)) 2426 for i, partIndex := range commonJSParts { 2427 dependencies[i] = js_ast.Dependency{ 2428 SourceIndex: runtime.SourceIndex, 2429 PartIndex: partIndex, 2430 } 2431 } 2432 partIndex := c.graph.AddPartToFile(sourceIndex, js_ast.Part{ 2433 SymbolUses: map[ast.Ref]js_ast.SymbolUse{ 2434 repr.AST.WrapperRef: {CountEstimate: 1}, 2435 }, 2436 DeclaredSymbols: []js_ast.DeclaredSymbol{ 2437 {Ref: repr.AST.ExportsRef, IsTopLevel: true}, 2438 {Ref: repr.AST.ModuleRef, IsTopLevel: true}, 2439 {Ref: repr.AST.WrapperRef, IsTopLevel: true}, 2440 }, 2441 Dependencies: dependencies, 2442 }) 2443 repr.Meta.WrapperPartIndex = ast.MakeIndex32(partIndex) 2444 c.graph.GenerateSymbolImportAndUse(sourceIndex, partIndex, c.cjsRuntimeRef, 1, runtime.SourceIndex) 2445 2446 // If this is a lazily-initialized ESM file, we're going to need to 2447 // generate a wrapper for the ESM closure. That will end up looking 2448 // something like this: 2449 // 2450 // var init_foo = __esm(() => { 2451 // ... 2452 // }); 2453 // 2454 // This depends on the "__esm" symbol and declares the "init_foo" symbol 2455 // for similar reasons to the CommonJS closure above. 2456 case graph.WrapESM: 2457 runtimeRepr := c.graph.Files[runtime.SourceIndex].InputFile.Repr.(*graph.JSRepr) 2458 esmParts := runtimeRepr.TopLevelSymbolToParts(c.esmRuntimeRef) 2459 2460 // Generate the dummy part 2461 dependencies := make([]js_ast.Dependency, len(esmParts)) 2462 for i, partIndex := range esmParts { 2463 dependencies[i] = js_ast.Dependency{ 2464 SourceIndex: runtime.SourceIndex, 2465 PartIndex: partIndex, 2466 } 2467 } 2468 partIndex := c.graph.AddPartToFile(sourceIndex, js_ast.Part{ 2469 SymbolUses: map[ast.Ref]js_ast.SymbolUse{ 2470 repr.AST.WrapperRef: {CountEstimate: 1}, 2471 }, 2472 DeclaredSymbols: []js_ast.DeclaredSymbol{ 2473 {Ref: repr.AST.WrapperRef, IsTopLevel: true}, 2474 }, 2475 Dependencies: dependencies, 2476 }) 2477 repr.Meta.WrapperPartIndex = ast.MakeIndex32(partIndex) 2478 c.graph.GenerateSymbolImportAndUse(sourceIndex, partIndex, c.esmRuntimeRef, 1, runtime.SourceIndex) 2479 } 2480 } 2481 2482 func (c *linkerContext) matchImportsWithExportsForFile(sourceIndex uint32) { 2483 file := &c.graph.Files[sourceIndex] 2484 repr := file.InputFile.Repr.(*graph.JSRepr) 2485 2486 // Sort imports for determinism. Otherwise our unit tests will randomly 2487 // fail sometimes when error messages are reordered. 2488 sortedImportRefs := make([]int, 0, len(repr.AST.NamedImports)) 2489 for ref := range repr.AST.NamedImports { 2490 sortedImportRefs = append(sortedImportRefs, int(ref.InnerIndex)) 2491 } 2492 sort.Ints(sortedImportRefs) 2493 2494 // Pair imports with their matching exports 2495 for _, innerIndex := range sortedImportRefs { 2496 // Re-use memory for the cycle detector 2497 c.cycleDetector = c.cycleDetector[:0] 2498 2499 importRef := ast.Ref{SourceIndex: sourceIndex, InnerIndex: uint32(innerIndex)} 2500 result, reExports := c.matchImportWithExport(importTracker{sourceIndex: sourceIndex, importRef: importRef}, nil) 2501 switch result.kind { 2502 case matchImportIgnore: 2503 2504 case matchImportNormal: 2505 repr.Meta.ImportsToBind[importRef] = graph.ImportData{ 2506 ReExports: reExports, 2507 SourceIndex: result.sourceIndex, 2508 Ref: result.ref, 2509 } 2510 2511 case matchImportNamespace: 2512 c.graph.Symbols.Get(importRef).NamespaceAlias = &ast.NamespaceAlias{ 2513 NamespaceRef: result.namespaceRef, 2514 Alias: result.alias, 2515 } 2516 2517 case matchImportNormalAndNamespace: 2518 repr.Meta.ImportsToBind[importRef] = graph.ImportData{ 2519 ReExports: reExports, 2520 SourceIndex: result.sourceIndex, 2521 Ref: result.ref, 2522 } 2523 2524 c.graph.Symbols.Get(importRef).NamespaceAlias = &ast.NamespaceAlias{ 2525 NamespaceRef: result.namespaceRef, 2526 Alias: result.alias, 2527 } 2528 2529 case matchImportCycle: 2530 namedImport := repr.AST.NamedImports[importRef] 2531 c.log.AddError(file.LineColumnTracker(), js_lexer.RangeOfIdentifier(file.InputFile.Source, namedImport.AliasLoc), 2532 fmt.Sprintf("Detected cycle while resolving import %q", namedImport.Alias)) 2533 2534 case matchImportProbablyTypeScriptType: 2535 repr.Meta.IsProbablyTypeScriptType[importRef] = true 2536 2537 case matchImportAmbiguous: 2538 namedImport := repr.AST.NamedImports[importRef] 2539 r := js_lexer.RangeOfIdentifier(file.InputFile.Source, namedImport.AliasLoc) 2540 var notes []logger.MsgData 2541 2542 // Provide the locations of both ambiguous exports if possible 2543 if result.nameLoc.Start != 0 && result.otherNameLoc.Start != 0 { 2544 a := c.graph.Files[result.sourceIndex] 2545 b := c.graph.Files[result.otherSourceIndex] 2546 ra := js_lexer.RangeOfIdentifier(a.InputFile.Source, result.nameLoc) 2547 rb := js_lexer.RangeOfIdentifier(b.InputFile.Source, result.otherNameLoc) 2548 notes = []logger.MsgData{ 2549 a.LineColumnTracker().MsgData(ra, "One matching export is here:"), 2550 b.LineColumnTracker().MsgData(rb, "Another matching export is here:"), 2551 } 2552 } 2553 2554 symbol := c.graph.Symbols.Get(importRef) 2555 if symbol.ImportItemStatus == ast.ImportItemGenerated { 2556 // This is a warning instead of an error because although it appears 2557 // to be a named import, it's actually an automatically-generated 2558 // named import that was originally a property access on an import 2559 // star namespace object. Normally this property access would just 2560 // resolve to undefined at run-time instead of failing at binding- 2561 // time, so we emit a warning and rewrite the value to the literal 2562 // "undefined" instead of emitting an error. 2563 symbol.ImportItemStatus = ast.ImportItemMissing 2564 msg := fmt.Sprintf("Import %q will always be undefined because there are multiple matching exports", namedImport.Alias) 2565 c.log.AddIDWithNotes(logger.MsgID_Bundler_ImportIsUndefined, logger.Warning, file.LineColumnTracker(), r, msg, notes) 2566 } else { 2567 msg := fmt.Sprintf("Ambiguous import %q has multiple matching exports", namedImport.Alias) 2568 c.log.AddErrorWithNotes(file.LineColumnTracker(), r, msg, notes) 2569 } 2570 } 2571 } 2572 } 2573 2574 type matchImportKind uint8 2575 2576 const ( 2577 // The import is either external or undefined 2578 matchImportIgnore matchImportKind = iota 2579 2580 // "sourceIndex" and "ref" are in use 2581 matchImportNormal 2582 2583 // "namespaceRef" and "alias" are in use 2584 matchImportNamespace 2585 2586 // Both "matchImportNormal" and "matchImportNamespace" 2587 matchImportNormalAndNamespace 2588 2589 // The import could not be evaluated due to a cycle 2590 matchImportCycle 2591 2592 // The import is missing but came from a TypeScript file 2593 matchImportProbablyTypeScriptType 2594 2595 // The import resolved to multiple symbols via "export * from" 2596 matchImportAmbiguous 2597 ) 2598 2599 type matchImportResult struct { 2600 alias string 2601 kind matchImportKind 2602 namespaceRef ast.Ref 2603 sourceIndex uint32 2604 nameLoc logger.Loc // Optional, goes with sourceIndex, ignore if zero 2605 otherSourceIndex uint32 2606 otherNameLoc logger.Loc // Optional, goes with otherSourceIndex, ignore if zero 2607 ref ast.Ref 2608 } 2609 2610 func (c *linkerContext) matchImportWithExport( 2611 tracker importTracker, reExportsIn []js_ast.Dependency, 2612 ) (result matchImportResult, reExports []js_ast.Dependency) { 2613 var ambiguousResults []matchImportResult 2614 reExports = reExportsIn 2615 2616 loop: 2617 for { 2618 // Make sure we avoid infinite loops trying to resolve cycles: 2619 // 2620 // // foo.js 2621 // export {a as b} from './foo.js' 2622 // export {b as c} from './foo.js' 2623 // export {c as a} from './foo.js' 2624 // 2625 // This uses a O(n^2) array scan instead of a O(n) map because the vast 2626 // majority of cases have one or two elements and Go arrays are cheap to 2627 // reuse without allocating. 2628 for _, previousTracker := range c.cycleDetector { 2629 if tracker == previousTracker { 2630 result = matchImportResult{kind: matchImportCycle} 2631 break loop 2632 } 2633 } 2634 c.cycleDetector = append(c.cycleDetector, tracker) 2635 2636 // Resolve the import by one step 2637 nextTracker, status, potentiallyAmbiguousExportStarRefs := c.advanceImportTracker(tracker) 2638 switch status { 2639 case importCommonJS, importCommonJSWithoutExports, importExternal, importDisabled: 2640 if status == importExternal && c.options.OutputFormat.KeepESMImportExportSyntax() { 2641 // Imports from external modules should not be converted to CommonJS 2642 // if the output format preserves the original ES6 import statements 2643 break 2644 } 2645 2646 // If it's a CommonJS or external file, rewrite the import to a 2647 // property access. Don't do this if the namespace reference is invalid 2648 // though. This is the case for star imports, where the import is the 2649 // namespace. 2650 trackerFile := &c.graph.Files[tracker.sourceIndex] 2651 namedImport := trackerFile.InputFile.Repr.(*graph.JSRepr).AST.NamedImports[tracker.importRef] 2652 if namedImport.NamespaceRef != ast.InvalidRef { 2653 if result.kind == matchImportNormal { 2654 result.kind = matchImportNormalAndNamespace 2655 result.namespaceRef = namedImport.NamespaceRef 2656 result.alias = namedImport.Alias 2657 } else { 2658 result = matchImportResult{ 2659 kind: matchImportNamespace, 2660 namespaceRef: namedImport.NamespaceRef, 2661 alias: namedImport.Alias, 2662 } 2663 } 2664 } 2665 2666 // Warn about importing from a file that is known to not have any exports 2667 if status == importCommonJSWithoutExports { 2668 symbol := c.graph.Symbols.Get(tracker.importRef) 2669 symbol.ImportItemStatus = ast.ImportItemMissing 2670 kind := logger.Warning 2671 if helpers.IsInsideNodeModules(trackerFile.InputFile.Source.KeyPath.Text) { 2672 kind = logger.Debug 2673 } 2674 c.log.AddID(logger.MsgID_Bundler_ImportIsUndefined, kind, 2675 trackerFile.LineColumnTracker(), 2676 js_lexer.RangeOfIdentifier(trackerFile.InputFile.Source, namedImport.AliasLoc), 2677 fmt.Sprintf("Import %q will always be undefined because the file %q has no exports", 2678 namedImport.Alias, c.graph.Files[nextTracker.sourceIndex].InputFile.Source.PrettyPath)) 2679 } 2680 2681 case importDynamicFallback: 2682 // If it's a file with dynamic export fallback, rewrite the import to a property access 2683 trackerFile := &c.graph.Files[tracker.sourceIndex] 2684 namedImport := trackerFile.InputFile.Repr.(*graph.JSRepr).AST.NamedImports[tracker.importRef] 2685 if result.kind == matchImportNormal { 2686 result.kind = matchImportNormalAndNamespace 2687 result.namespaceRef = nextTracker.importRef 2688 result.alias = namedImport.Alias 2689 } else { 2690 result = matchImportResult{ 2691 kind: matchImportNamespace, 2692 namespaceRef: nextTracker.importRef, 2693 alias: namedImport.Alias, 2694 } 2695 } 2696 2697 case importNoMatch: 2698 symbol := c.graph.Symbols.Get(tracker.importRef) 2699 trackerFile := &c.graph.Files[tracker.sourceIndex] 2700 namedImport := trackerFile.InputFile.Repr.(*graph.JSRepr).AST.NamedImports[tracker.importRef] 2701 r := js_lexer.RangeOfIdentifier(trackerFile.InputFile.Source, namedImport.AliasLoc) 2702 2703 // Report mismatched imports and exports 2704 if symbol.ImportItemStatus == ast.ImportItemGenerated { 2705 // This is not an error because although it appears to be a named 2706 // import, it's actually an automatically-generated named import 2707 // that was originally a property access on an import star 2708 // namespace object: 2709 // 2710 // import * as ns from 'foo' 2711 // const undefinedValue = ns.notAnExport 2712 // 2713 // If this code wasn't bundled, this property access would just resolve 2714 // to undefined at run-time instead of failing at binding-time, so we 2715 // emit rewrite the value to the literal "undefined" instead of 2716 // emitting an error. 2717 symbol.ImportItemStatus = ast.ImportItemMissing 2718 2719 // Don't emit a log message if this symbol isn't used, since then the 2720 // log message isn't helpful. This can happen with "import" assignment 2721 // statements in TypeScript code since they are ambiguously either a 2722 // type or a value. We consider them to be a type if they aren't used. 2723 // 2724 // import * as ns from 'foo' 2725 // 2726 // // There's no warning here because this is dead code 2727 // if (false) ns.notAnExport 2728 // 2729 // // There's no warning here because this is never used 2730 // import unused = ns.notAnExport 2731 // 2732 if symbol.UseCountEstimate > 0 { 2733 nextFile := &c.graph.Files[nextTracker.sourceIndex].InputFile 2734 msg := logger.Msg{ 2735 Kind: logger.Warning, 2736 Data: trackerFile.LineColumnTracker().MsgData(r, fmt.Sprintf( 2737 "Import %q will always be undefined because there is no matching export in %q", 2738 namedImport.Alias, nextFile.Source.PrettyPath)), 2739 } 2740 if helpers.IsInsideNodeModules(trackerFile.InputFile.Source.KeyPath.Text) { 2741 msg.Kind = logger.Debug 2742 } 2743 c.maybeCorrectObviousTypo(nextFile.Repr.(*graph.JSRepr), namedImport.Alias, &msg) 2744 c.log.AddMsgID(logger.MsgID_Bundler_ImportIsUndefined, msg) 2745 } 2746 } else { 2747 nextFile := &c.graph.Files[nextTracker.sourceIndex].InputFile 2748 msg := logger.Msg{ 2749 Kind: logger.Error, 2750 Data: trackerFile.LineColumnTracker().MsgData(r, fmt.Sprintf( 2751 "No matching export in %q for import %q", 2752 nextFile.Source.PrettyPath, namedImport.Alias)), 2753 } 2754 c.maybeCorrectObviousTypo(nextFile.Repr.(*graph.JSRepr), namedImport.Alias, &msg) 2755 c.log.AddMsg(msg) 2756 } 2757 2758 case importProbablyTypeScriptType: 2759 // Omit this import from any namespace export code we generate for 2760 // import star statements (i.e. "import * as ns from 'path'") 2761 result = matchImportResult{kind: matchImportProbablyTypeScriptType} 2762 2763 case importFound: 2764 // If there are multiple ambiguous results due to use of "export * from" 2765 // statements, trace them all to see if they point to different things. 2766 for _, ambiguousTracker := range potentiallyAmbiguousExportStarRefs { 2767 // If this is a re-export of another import, follow the import 2768 if _, ok := c.graph.Files[ambiguousTracker.SourceIndex].InputFile.Repr.(*graph.JSRepr).AST.NamedImports[ambiguousTracker.Ref]; ok { 2769 // Save and restore the cycle detector to avoid mixing information 2770 oldCycleDetector := c.cycleDetector 2771 ambiguousResult, newReExportFiles := c.matchImportWithExport(importTracker{ 2772 sourceIndex: ambiguousTracker.SourceIndex, 2773 importRef: ambiguousTracker.Ref, 2774 }, reExports) 2775 c.cycleDetector = oldCycleDetector 2776 ambiguousResults = append(ambiguousResults, ambiguousResult) 2777 reExports = newReExportFiles 2778 } else { 2779 ambiguousResults = append(ambiguousResults, matchImportResult{ 2780 kind: matchImportNormal, 2781 sourceIndex: ambiguousTracker.SourceIndex, 2782 ref: ambiguousTracker.Ref, 2783 nameLoc: ambiguousTracker.NameLoc, 2784 }) 2785 } 2786 } 2787 2788 // Defer the actual binding of this import until after we generate 2789 // namespace export code for all files. This has to be done for all 2790 // import-to-export matches, not just the initial import to the final 2791 // export, since all imports and re-exports must be merged together 2792 // for correctness. 2793 result = matchImportResult{ 2794 kind: matchImportNormal, 2795 sourceIndex: nextTracker.sourceIndex, 2796 ref: nextTracker.importRef, 2797 nameLoc: nextTracker.nameLoc, 2798 } 2799 2800 // Depend on the statement(s) that declared this import symbol in the 2801 // original file 2802 for _, resolvedPartIndex := range c.graph.Files[tracker.sourceIndex].InputFile.Repr.(*graph.JSRepr).TopLevelSymbolToParts(tracker.importRef) { 2803 reExports = append(reExports, js_ast.Dependency{ 2804 SourceIndex: tracker.sourceIndex, 2805 PartIndex: resolvedPartIndex, 2806 }) 2807 } 2808 2809 // If this is a re-export of another import, continue for another 2810 // iteration of the loop to resolve that import as well 2811 if _, ok := c.graph.Files[nextTracker.sourceIndex].InputFile.Repr.(*graph.JSRepr).AST.NamedImports[nextTracker.importRef]; ok { 2812 tracker = nextTracker 2813 continue 2814 } 2815 2816 default: 2817 panic("Internal error") 2818 } 2819 2820 // Stop now if we didn't explicitly "continue" above 2821 break 2822 } 2823 2824 // If there is a potential ambiguity, all results must be the same 2825 for _, ambiguousResult := range ambiguousResults { 2826 if ambiguousResult != result { 2827 if result.kind == matchImportNormal && ambiguousResult.kind == matchImportNormal && 2828 result.nameLoc.Start != 0 && ambiguousResult.nameLoc.Start != 0 { 2829 return matchImportResult{ 2830 kind: matchImportAmbiguous, 2831 sourceIndex: result.sourceIndex, 2832 nameLoc: result.nameLoc, 2833 otherSourceIndex: ambiguousResult.sourceIndex, 2834 otherNameLoc: ambiguousResult.nameLoc, 2835 }, nil 2836 } 2837 return matchImportResult{kind: matchImportAmbiguous}, nil 2838 } 2839 } 2840 2841 return 2842 } 2843 2844 func (c *linkerContext) maybeForbidArbitraryModuleNamespaceIdentifier(kind string, sourceIndex uint32, loc logger.Loc, alias string) { 2845 if !js_ast.IsIdentifier(alias) { 2846 file := &c.graph.Files[sourceIndex] 2847 where := config.PrettyPrintTargetEnvironment(c.options.OriginalTargetEnv, c.options.UnsupportedJSFeatureOverridesMask) 2848 c.log.AddError(file.LineColumnTracker(), file.InputFile.Source.RangeOfString(loc), fmt.Sprintf( 2849 "Using the string %q as an %s name is not supported in %s", alias, kind, where)) 2850 } 2851 } 2852 2853 // Attempt to correct an import name with a typo 2854 func (c *linkerContext) maybeCorrectObviousTypo(repr *graph.JSRepr, name string, msg *logger.Msg) { 2855 if repr.Meta.ResolvedExportTypos == nil { 2856 valid := make([]string, 0, len(repr.Meta.ResolvedExports)) 2857 for alias := range repr.Meta.ResolvedExports { 2858 valid = append(valid, alias) 2859 } 2860 sort.Strings(valid) 2861 typos := helpers.MakeTypoDetector(valid) 2862 repr.Meta.ResolvedExportTypos = &typos 2863 } 2864 2865 if corrected, ok := repr.Meta.ResolvedExportTypos.MaybeCorrectTypo(name); ok { 2866 msg.Data.Location.Suggestion = corrected 2867 export := repr.Meta.ResolvedExports[corrected] 2868 importedFile := &c.graph.Files[export.SourceIndex] 2869 text := fmt.Sprintf("Did you mean to import %q instead?", corrected) 2870 var note logger.MsgData 2871 if export.NameLoc.Start == 0 { 2872 // Don't report a source location for definitions without one. This can 2873 // happen with automatically-generated exports from non-JavaScript files. 2874 note.Text = text 2875 } else { 2876 var r logger.Range 2877 if importedFile.InputFile.Loader.IsCSS() { 2878 r = css_lexer.RangeOfIdentifier(importedFile.InputFile.Source, export.NameLoc) 2879 } else { 2880 r = js_lexer.RangeOfIdentifier(importedFile.InputFile.Source, export.NameLoc) 2881 } 2882 note = importedFile.LineColumnTracker().MsgData(r, text) 2883 } 2884 msg.Notes = append(msg.Notes, note) 2885 } 2886 } 2887 2888 func (c *linkerContext) recursivelyWrapDependencies(sourceIndex uint32) { 2889 repr := c.graph.Files[sourceIndex].InputFile.Repr.(*graph.JSRepr) 2890 if repr.Meta.DidWrapDependencies { 2891 return 2892 } 2893 repr.Meta.DidWrapDependencies = true 2894 2895 // Never wrap the runtime file since it always comes first 2896 if sourceIndex == runtime.SourceIndex { 2897 return 2898 } 2899 2900 // This module must be wrapped 2901 if repr.Meta.Wrap == graph.WrapNone { 2902 if repr.AST.ExportsKind == js_ast.ExportsCommonJS { 2903 repr.Meta.Wrap = graph.WrapCJS 2904 } else { 2905 repr.Meta.Wrap = graph.WrapESM 2906 } 2907 } 2908 2909 // All dependencies must also be wrapped 2910 for _, record := range repr.AST.ImportRecords { 2911 if record.SourceIndex.IsValid() { 2912 c.recursivelyWrapDependencies(record.SourceIndex.GetIndex()) 2913 } 2914 } 2915 } 2916 2917 func (c *linkerContext) hasDynamicExportsDueToExportStar(sourceIndex uint32, visited map[uint32]bool) bool { 2918 // Terminate the traversal now if this file already has dynamic exports 2919 repr := c.graph.Files[sourceIndex].InputFile.Repr.(*graph.JSRepr) 2920 if repr.AST.ExportsKind == js_ast.ExportsCommonJS || repr.AST.ExportsKind == js_ast.ExportsESMWithDynamicFallback { 2921 return true 2922 } 2923 2924 // Avoid infinite loops due to cycles in the export star graph 2925 if visited[sourceIndex] { 2926 return false 2927 } 2928 visited[sourceIndex] = true 2929 2930 // Scan over the export star graph 2931 for _, importRecordIndex := range repr.AST.ExportStarImportRecords { 2932 record := &repr.AST.ImportRecords[importRecordIndex] 2933 2934 // This file has dynamic exports if the exported imports are from a file 2935 // that either has dynamic exports directly or transitively by itself 2936 // having an export star from a file with dynamic exports. 2937 if (!record.SourceIndex.IsValid() && (!c.graph.Files[sourceIndex].IsEntryPoint() || !c.options.OutputFormat.KeepESMImportExportSyntax())) || 2938 (record.SourceIndex.IsValid() && record.SourceIndex.GetIndex() != sourceIndex && c.hasDynamicExportsDueToExportStar(record.SourceIndex.GetIndex(), visited)) { 2939 repr.AST.ExportsKind = js_ast.ExportsESMWithDynamicFallback 2940 return true 2941 } 2942 } 2943 2944 return false 2945 } 2946 2947 func (c *linkerContext) addExportsForExportStar( 2948 resolvedExports map[string]graph.ExportData, 2949 sourceIndex uint32, 2950 sourceIndexStack []uint32, 2951 ) { 2952 // Avoid infinite loops due to cycles in the export star graph 2953 for _, prevSourceIndex := range sourceIndexStack { 2954 if prevSourceIndex == sourceIndex { 2955 return 2956 } 2957 } 2958 sourceIndexStack = append(sourceIndexStack, sourceIndex) 2959 repr := c.graph.Files[sourceIndex].InputFile.Repr.(*graph.JSRepr) 2960 2961 for _, importRecordIndex := range repr.AST.ExportStarImportRecords { 2962 record := &repr.AST.ImportRecords[importRecordIndex] 2963 if !record.SourceIndex.IsValid() { 2964 // This will be resolved at run time instead 2965 continue 2966 } 2967 otherSourceIndex := record.SourceIndex.GetIndex() 2968 2969 // Export stars from a CommonJS module don't work because they can't be 2970 // statically discovered. Just silently ignore them in this case. 2971 // 2972 // We could attempt to check whether the imported file still has ES6 2973 // exports even though it still uses CommonJS features. However, when 2974 // doing this we'd also have to rewrite any imports of these export star 2975 // re-exports as property accesses off of a generated require() call. 2976 otherRepr := c.graph.Files[otherSourceIndex].InputFile.Repr.(*graph.JSRepr) 2977 if otherRepr.AST.ExportsKind == js_ast.ExportsCommonJS { 2978 // All exports will be resolved at run time instead 2979 continue 2980 } 2981 2982 // Accumulate this file's exports 2983 nextExport: 2984 for alias, name := range otherRepr.AST.NamedExports { 2985 // ES6 export star statements ignore exports named "default" 2986 if alias == "default" { 2987 continue 2988 } 2989 2990 // This export star is shadowed if any file in the stack has a matching real named export 2991 for _, prevSourceIndex := range sourceIndexStack { 2992 prevRepr := c.graph.Files[prevSourceIndex].InputFile.Repr.(*graph.JSRepr) 2993 if _, ok := prevRepr.AST.NamedExports[alias]; ok { 2994 continue nextExport 2995 } 2996 } 2997 2998 if existing, ok := resolvedExports[alias]; !ok { 2999 // Initialize the re-export 3000 resolvedExports[alias] = graph.ExportData{ 3001 Ref: name.Ref, 3002 SourceIndex: otherSourceIndex, 3003 NameLoc: name.AliasLoc, 3004 } 3005 3006 // Make sure the symbol is marked as imported so that code splitting 3007 // imports it correctly if it ends up being shared with another chunk 3008 repr.Meta.ImportsToBind[name.Ref] = graph.ImportData{ 3009 Ref: name.Ref, 3010 SourceIndex: otherSourceIndex, 3011 } 3012 } else if existing.SourceIndex != otherSourceIndex { 3013 // Two different re-exports colliding makes it potentially ambiguous 3014 existing.PotentiallyAmbiguousExportStarRefs = 3015 append(existing.PotentiallyAmbiguousExportStarRefs, graph.ImportData{ 3016 SourceIndex: otherSourceIndex, 3017 Ref: name.Ref, 3018 NameLoc: name.AliasLoc, 3019 }) 3020 resolvedExports[alias] = existing 3021 } 3022 } 3023 3024 // Search further through this file's export stars 3025 c.addExportsForExportStar(resolvedExports, otherSourceIndex, sourceIndexStack) 3026 } 3027 } 3028 3029 type importTracker struct { 3030 sourceIndex uint32 3031 nameLoc logger.Loc // Optional, goes with sourceIndex, ignore if zero 3032 importRef ast.Ref 3033 } 3034 3035 type importStatus uint8 3036 3037 const ( 3038 // The imported file has no matching export 3039 importNoMatch importStatus = iota 3040 3041 // The imported file has a matching export 3042 importFound 3043 3044 // The imported file is CommonJS and has unknown exports 3045 importCommonJS 3046 3047 // The import is missing but there is a dynamic fallback object 3048 importDynamicFallback 3049 3050 // The import was treated as a CommonJS import but the file is known to have no exports 3051 importCommonJSWithoutExports 3052 3053 // The imported file was disabled by mapping it to false in the "browser" 3054 // field of package.json 3055 importDisabled 3056 3057 // The imported file is external and has unknown exports 3058 importExternal 3059 3060 // This is a missing re-export in a TypeScript file, so it's probably a type 3061 importProbablyTypeScriptType 3062 ) 3063 3064 func (c *linkerContext) advanceImportTracker(tracker importTracker) (importTracker, importStatus, []graph.ImportData) { 3065 file := &c.graph.Files[tracker.sourceIndex] 3066 repr := file.InputFile.Repr.(*graph.JSRepr) 3067 namedImport := repr.AST.NamedImports[tracker.importRef] 3068 3069 // Is this an external file? 3070 record := &repr.AST.ImportRecords[namedImport.ImportRecordIndex] 3071 if !record.SourceIndex.IsValid() { 3072 return importTracker{}, importExternal, nil 3073 } 3074 3075 // Is this a named import of a file without any exports? 3076 otherSourceIndex := record.SourceIndex.GetIndex() 3077 otherRepr := c.graph.Files[otherSourceIndex].InputFile.Repr.(*graph.JSRepr) 3078 if !namedImport.AliasIsStar && !otherRepr.AST.HasLazyExport && 3079 // CommonJS exports 3080 otherRepr.AST.ExportKeyword.Len == 0 && namedImport.Alias != "default" && 3081 // ESM exports 3082 !otherRepr.AST.UsesExportsRef && !otherRepr.AST.UsesModuleRef { 3083 // Just warn about it and replace the import with "undefined" 3084 return importTracker{sourceIndex: otherSourceIndex, importRef: ast.InvalidRef}, importCommonJSWithoutExports, nil 3085 } 3086 3087 // Is this a CommonJS file? 3088 if otherRepr.AST.ExportsKind == js_ast.ExportsCommonJS { 3089 return importTracker{sourceIndex: otherSourceIndex, importRef: ast.InvalidRef}, importCommonJS, nil 3090 } 3091 3092 // Match this import star with an export star from the imported file 3093 if matchingExport := otherRepr.Meta.ResolvedExportStar; namedImport.AliasIsStar && matchingExport != nil { 3094 // Check to see if this is a re-export of another import 3095 return importTracker{ 3096 sourceIndex: matchingExport.SourceIndex, 3097 importRef: matchingExport.Ref, 3098 nameLoc: matchingExport.NameLoc, 3099 }, importFound, matchingExport.PotentiallyAmbiguousExportStarRefs 3100 } 3101 3102 // Match this import up with an export from the imported file 3103 if matchingExport, ok := otherRepr.Meta.ResolvedExports[namedImport.Alias]; ok { 3104 // Check to see if this is a re-export of another import 3105 return importTracker{ 3106 sourceIndex: matchingExport.SourceIndex, 3107 importRef: matchingExport.Ref, 3108 nameLoc: matchingExport.NameLoc, 3109 }, importFound, matchingExport.PotentiallyAmbiguousExportStarRefs 3110 } 3111 3112 // Is this a file with dynamic exports? 3113 if otherRepr.AST.ExportsKind == js_ast.ExportsESMWithDynamicFallback { 3114 return importTracker{sourceIndex: otherSourceIndex, importRef: otherRepr.AST.ExportsRef}, importDynamicFallback, nil 3115 } 3116 3117 // Missing re-exports in TypeScript files are indistinguishable from types 3118 if file.InputFile.Loader.IsTypeScript() && namedImport.IsExported { 3119 return importTracker{}, importProbablyTypeScriptType, nil 3120 } 3121 3122 return importTracker{sourceIndex: otherSourceIndex}, importNoMatch, nil 3123 } 3124 3125 func (c *linkerContext) treeShakingAndCodeSplitting() { 3126 // Tree shaking: Each entry point marks all files reachable from itself 3127 c.timer.Begin("Tree shaking") 3128 for _, entryPoint := range c.graph.EntryPoints() { 3129 c.markFileLiveForTreeShaking(entryPoint.SourceIndex) 3130 } 3131 c.timer.End("Tree shaking") 3132 3133 // Code splitting: Determine which entry points can reach which files. This 3134 // has to happen after tree shaking because there is an implicit dependency 3135 // between live parts within the same file. All liveness has to be computed 3136 // first before determining which entry points can reach which files. 3137 c.timer.Begin("Code splitting") 3138 for i, entryPoint := range c.graph.EntryPoints() { 3139 c.markFileReachableForCodeSplitting(entryPoint.SourceIndex, uint(i), 0) 3140 } 3141 c.timer.End("Code splitting") 3142 } 3143 3144 func (c *linkerContext) markFileReachableForCodeSplitting(sourceIndex uint32, entryPointBit uint, distanceFromEntryPoint uint32) { 3145 file := &c.graph.Files[sourceIndex] 3146 if !file.IsLive { 3147 return 3148 } 3149 traverseAgain := false 3150 3151 // Track the minimum distance to an entry point 3152 if distanceFromEntryPoint < file.DistanceFromEntryPoint { 3153 file.DistanceFromEntryPoint = distanceFromEntryPoint 3154 traverseAgain = true 3155 } 3156 distanceFromEntryPoint++ 3157 3158 // Don't mark this file more than once 3159 if file.EntryBits.HasBit(entryPointBit) && !traverseAgain { 3160 return 3161 } 3162 file.EntryBits.SetBit(entryPointBit) 3163 3164 switch repr := file.InputFile.Repr.(type) { 3165 case *graph.JSRepr: 3166 // If the JavaScript stub for a CSS file is included, also include the CSS file 3167 if repr.CSSSourceIndex.IsValid() { 3168 c.markFileReachableForCodeSplitting(repr.CSSSourceIndex.GetIndex(), entryPointBit, distanceFromEntryPoint) 3169 } 3170 3171 // Traverse into all imported files 3172 for _, record := range repr.AST.ImportRecords { 3173 if record.SourceIndex.IsValid() && !c.isExternalDynamicImport(&record, sourceIndex) { 3174 c.markFileReachableForCodeSplitting(record.SourceIndex.GetIndex(), entryPointBit, distanceFromEntryPoint) 3175 } 3176 } 3177 3178 // Traverse into all dependencies of all parts in this file 3179 for _, part := range repr.AST.Parts { 3180 for _, dependency := range part.Dependencies { 3181 if dependency.SourceIndex != sourceIndex { 3182 c.markFileReachableForCodeSplitting(dependency.SourceIndex, entryPointBit, distanceFromEntryPoint) 3183 } 3184 } 3185 } 3186 3187 case *graph.CSSRepr: 3188 // Traverse into all dependencies 3189 for _, record := range repr.AST.ImportRecords { 3190 if record.SourceIndex.IsValid() { 3191 c.markFileReachableForCodeSplitting(record.SourceIndex.GetIndex(), entryPointBit, distanceFromEntryPoint) 3192 } 3193 } 3194 } 3195 } 3196 3197 func (c *linkerContext) markFileLiveForTreeShaking(sourceIndex uint32) { 3198 file := &c.graph.Files[sourceIndex] 3199 3200 // Don't mark this file more than once 3201 if file.IsLive { 3202 return 3203 } 3204 file.IsLive = true 3205 3206 switch repr := file.InputFile.Repr.(type) { 3207 case *graph.JSRepr: 3208 // If the JavaScript stub for a CSS file is included, also include the CSS file 3209 if repr.CSSSourceIndex.IsValid() { 3210 c.markFileLiveForTreeShaking(repr.CSSSourceIndex.GetIndex()) 3211 } 3212 3213 for partIndex, part := range repr.AST.Parts { 3214 canBeRemovedIfUnused := part.CanBeRemovedIfUnused 3215 3216 // Also include any statement-level imports 3217 for _, importRecordIndex := range part.ImportRecordIndices { 3218 record := &repr.AST.ImportRecords[importRecordIndex] 3219 if record.Kind != ast.ImportStmt { 3220 continue 3221 } 3222 3223 if record.SourceIndex.IsValid() { 3224 otherSourceIndex := record.SourceIndex.GetIndex() 3225 3226 // Don't include this module for its side effects if it can be 3227 // considered to have no side effects 3228 if otherFile := &c.graph.Files[otherSourceIndex]; otherFile.InputFile.SideEffects.Kind != graph.HasSideEffects && !c.options.IgnoreDCEAnnotations { 3229 continue 3230 } 3231 3232 // Otherwise, include this module for its side effects 3233 c.markFileLiveForTreeShaking(otherSourceIndex) 3234 } else if record.Flags.Has(ast.IsExternalWithoutSideEffects) { 3235 // This can be removed if it's unused 3236 continue 3237 } 3238 3239 // If we get here then the import was included for its side effects, so 3240 // we must also keep this part 3241 canBeRemovedIfUnused = false 3242 } 3243 3244 // Include all parts in this file with side effects, or just include 3245 // everything if tree-shaking is disabled. Note that we still want to 3246 // perform tree-shaking on the runtime even if tree-shaking is disabled. 3247 if !canBeRemovedIfUnused || (!part.ForceTreeShaking && !c.options.TreeShaking && file.IsEntryPoint()) { 3248 c.markPartLiveForTreeShaking(sourceIndex, uint32(partIndex)) 3249 } 3250 } 3251 3252 case *graph.CSSRepr: 3253 // Include all "@import" rules 3254 for _, record := range repr.AST.ImportRecords { 3255 if record.SourceIndex.IsValid() { 3256 c.markFileLiveForTreeShaking(record.SourceIndex.GetIndex()) 3257 } 3258 } 3259 } 3260 } 3261 3262 func (c *linkerContext) isExternalDynamicImport(record *ast.ImportRecord, sourceIndex uint32) bool { 3263 return c.options.CodeSplitting && 3264 record.Kind == ast.ImportDynamic && 3265 c.graph.Files[record.SourceIndex.GetIndex()].IsEntryPoint() && 3266 record.SourceIndex.GetIndex() != sourceIndex 3267 } 3268 3269 func (c *linkerContext) markPartLiveForTreeShaking(sourceIndex uint32, partIndex uint32) { 3270 file := &c.graph.Files[sourceIndex] 3271 repr := file.InputFile.Repr.(*graph.JSRepr) 3272 part := &repr.AST.Parts[partIndex] 3273 3274 // Don't mark this part more than once 3275 if part.IsLive { 3276 return 3277 } 3278 part.IsLive = true 3279 3280 // Include the file containing this part 3281 c.markFileLiveForTreeShaking(sourceIndex) 3282 3283 // Also include any dependencies 3284 for _, dep := range part.Dependencies { 3285 c.markPartLiveForTreeShaking(dep.SourceIndex, dep.PartIndex) 3286 } 3287 } 3288 3289 // JavaScript modules are traversed in depth-first postorder. This is the 3290 // order that JavaScript modules were evaluated in before the top-level await 3291 // feature was introduced. 3292 // 3293 // A 3294 // / \ 3295 // B C 3296 // \ / 3297 // D 3298 // 3299 // If A imports B and then C, B imports D, and C imports D, then the JavaScript 3300 // traversal order is D B C A. 3301 // 3302 // This function may deviate from ESM import order for dynamic imports (both 3303 // "require()" and "import()"). This is because the import order is impossible 3304 // to determine since the imports happen at run-time instead of compile-time. 3305 // In this case we just pick an arbitrary but consistent order. 3306 func (c *linkerContext) findImportedCSSFilesInJSOrder(entryPoint uint32) (order []uint32) { 3307 visited := make(map[uint32]bool) 3308 var visit func(uint32) 3309 3310 // Include this file and all files it imports 3311 visit = func(sourceIndex uint32) { 3312 if visited[sourceIndex] { 3313 return 3314 } 3315 visited[sourceIndex] = true 3316 file := &c.graph.Files[sourceIndex] 3317 repr := file.InputFile.Repr.(*graph.JSRepr) 3318 3319 // Iterate over each part in the file in order 3320 for _, part := range repr.AST.Parts { 3321 // Traverse any files imported by this part. Note that CommonJS calls 3322 // to "require()" count as imports too, sort of as if the part has an 3323 // ESM "import" statement in it. This may seem weird because ESM imports 3324 // are a compile-time concept while CommonJS imports are a run-time 3325 // concept. But we don't want to manipulate <style> tags at run-time so 3326 // this is the only way to do it. 3327 for _, importRecordIndex := range part.ImportRecordIndices { 3328 if record := &repr.AST.ImportRecords[importRecordIndex]; record.SourceIndex.IsValid() { 3329 visit(record.SourceIndex.GetIndex()) 3330 } 3331 } 3332 } 3333 3334 // Iterate over the associated CSS imports in postorder 3335 if repr.CSSSourceIndex.IsValid() { 3336 order = append(order, repr.CSSSourceIndex.GetIndex()) 3337 } 3338 } 3339 3340 // Include all files reachable from the entry point 3341 visit(entryPoint) 3342 3343 return 3344 } 3345 3346 type cssImportKind uint8 3347 3348 const ( 3349 cssImportNone cssImportKind = iota 3350 cssImportSourceIndex 3351 cssImportExternalPath 3352 cssImportLayers 3353 ) 3354 3355 type cssImportOrder struct { 3356 conditions []css_ast.ImportConditions 3357 conditionImportRecords []ast.ImportRecord 3358 3359 layers [][]string // kind == cssImportAtLayer 3360 externalPath logger.Path // kind == cssImportExternal 3361 sourceIndex uint32 // kind == cssImportSourceIndex 3362 3363 kind cssImportKind 3364 } 3365 3366 // CSS files are traversed in depth-first postorder just like JavaScript. But 3367 // unlike JavaScript import statements, CSS "@import" rules are evaluated every 3368 // time instead of just the first time. 3369 // 3370 // A 3371 // / \ 3372 // B C 3373 // \ / 3374 // D 3375 // 3376 // If A imports B and then C, B imports D, and C imports D, then the CSS 3377 // traversal order is D B D C A. 3378 // 3379 // However, evaluating a CSS file multiple times is sort of equivalent to 3380 // evaluating it once at the last location. So we basically drop all but the 3381 // last evaluation in the order. 3382 // 3383 // The only exception to this is "@layer". Evaluating a CSS file multiple 3384 // times is sort of equivalent to evaluating it once at the first location 3385 // as far as "@layer" is concerned. So we may in some cases keep both the 3386 // first and last locations and only write out the "@layer" information 3387 // for the first location. 3388 func (c *linkerContext) findImportedFilesInCSSOrder(entryPoints []uint32) (order []cssImportOrder) { 3389 var visit func(uint32, []uint32, []css_ast.ImportConditions, []ast.ImportRecord) 3390 hasExternalImport := false 3391 3392 // Include this file and all files it imports 3393 visit = func( 3394 sourceIndex uint32, 3395 visited []uint32, 3396 wrappingConditions []css_ast.ImportConditions, 3397 wrappingImportRecords []ast.ImportRecord, 3398 ) { 3399 // The CSS specification strangely does not describe what to do when there 3400 // is a cycle. So we are left with reverse-engineering the behavior from a 3401 // real browser. Here's what the WebKit code base has to say about this: 3402 // 3403 // "Check for a cycle in our import chain. If we encounter a stylesheet 3404 // in our parent chain with the same URL, then just bail." 3405 // 3406 // So that's what we do here. See "StyleRuleImport::requestStyleSheet()" in 3407 // WebKit for more information. 3408 for _, visitedSourceIndex := range visited { 3409 if visitedSourceIndex == sourceIndex { 3410 return 3411 } 3412 } 3413 visited = append(visited, sourceIndex) 3414 3415 repr := c.graph.Files[sourceIndex].InputFile.Repr.(*graph.CSSRepr) 3416 topLevelRules := repr.AST.Rules 3417 3418 // Any pre-import layers come first 3419 if len(repr.AST.LayersPreImport) > 0 { 3420 order = append(order, cssImportOrder{ 3421 kind: cssImportLayers, 3422 layers: repr.AST.LayersPreImport, 3423 conditions: wrappingConditions, 3424 conditionImportRecords: wrappingImportRecords, 3425 }) 3426 } 3427 3428 // Iterate over the top-level "@import" rules 3429 for _, rule := range topLevelRules { 3430 if atImport, ok := rule.Data.(*css_ast.RAtImport); ok { 3431 record := &repr.AST.ImportRecords[atImport.ImportRecordIndex] 3432 3433 // Follow internal dependencies 3434 if record.SourceIndex.IsValid() { 3435 nestedConditions := wrappingConditions 3436 nestedImportRecords := wrappingImportRecords 3437 3438 // If this import has conditions, fork our state so that the entire 3439 // imported stylesheet subtree is wrapped in all of the conditions 3440 if atImport.ImportConditions != nil { 3441 // Fork our state 3442 nestedConditions = append([]css_ast.ImportConditions{}, nestedConditions...) 3443 nestedImportRecords = append([]ast.ImportRecord{}, nestedImportRecords...) 3444 3445 // Clone these import conditions and append them to the state 3446 var conditions css_ast.ImportConditions 3447 conditions, nestedImportRecords = atImport.ImportConditions.CloneWithImportRecords(repr.AST.ImportRecords, nestedImportRecords) 3448 nestedConditions = append(nestedConditions, conditions) 3449 } 3450 3451 visit(record.SourceIndex.GetIndex(), visited, nestedConditions, nestedImportRecords) 3452 continue 3453 } 3454 3455 // Record external dependencies 3456 if (record.Flags & ast.WasLoadedWithEmptyLoader) == 0 { 3457 allConditions := wrappingConditions 3458 allImportRecords := wrappingImportRecords 3459 3460 // If this import has conditions, append it to the list of overall 3461 // conditions for this external import. Note that an external import 3462 // may actually have multiple sets of conditions that can't be 3463 // merged. When this happens we need to generate a nested imported 3464 // CSS file using a data URL. 3465 if atImport.ImportConditions != nil { 3466 var conditions css_ast.ImportConditions 3467 allConditions = append([]css_ast.ImportConditions{}, allConditions...) 3468 allImportRecords = append([]ast.ImportRecord{}, allImportRecords...) 3469 conditions, allImportRecords = atImport.ImportConditions.CloneWithImportRecords(repr.AST.ImportRecords, allImportRecords) 3470 allConditions = append(allConditions, conditions) 3471 } 3472 3473 order = append(order, cssImportOrder{ 3474 kind: cssImportExternalPath, 3475 externalPath: record.Path, 3476 conditions: allConditions, 3477 conditionImportRecords: allImportRecords, 3478 }) 3479 hasExternalImport = true 3480 } 3481 } 3482 } 3483 3484 // Iterate over the "composes" directives. Note that the order doesn't 3485 // matter for these because the output order is explicitly undefined 3486 // in the specification. 3487 for _, record := range repr.AST.ImportRecords { 3488 if record.Kind == ast.ImportComposesFrom && record.SourceIndex.IsValid() { 3489 visit(record.SourceIndex.GetIndex(), visited, wrappingConditions, wrappingImportRecords) 3490 } 3491 } 3492 3493 // Accumulate imports in depth-first postorder 3494 order = append(order, cssImportOrder{ 3495 kind: cssImportSourceIndex, 3496 sourceIndex: sourceIndex, 3497 conditions: wrappingConditions, 3498 conditionImportRecords: wrappingImportRecords, 3499 }) 3500 } 3501 3502 // Include all files reachable from any entry point 3503 var visited [16]uint32 // Preallocate some space for the visited set 3504 for _, sourceIndex := range entryPoints { 3505 visit(sourceIndex, visited[:], nil, nil) 3506 } 3507 3508 // Create a temporary array that we can use for filtering 3509 wipOrder := make([]cssImportOrder, 0, len(order)) 3510 3511 // CSS syntax unfortunately only allows "@import" rules at the top of the 3512 // file. This means we must hoist all external "@import" rules to the top of 3513 // the file when bundling, even though doing so will change the order of CSS 3514 // evaluation. 3515 if hasExternalImport { 3516 // Pass 1: Pull out leading "@layer" and external "@import" rules 3517 isAtLayerPrefix := true 3518 for _, entry := range order { 3519 if (entry.kind == cssImportLayers && isAtLayerPrefix) || entry.kind == cssImportExternalPath { 3520 wipOrder = append(wipOrder, entry) 3521 } 3522 if entry.kind != cssImportLayers { 3523 isAtLayerPrefix = false 3524 } 3525 } 3526 3527 // Pass 2: Append everything that we didn't pull out in pass 1 3528 isAtLayerPrefix = true 3529 for _, entry := range order { 3530 if (entry.kind != cssImportLayers || !isAtLayerPrefix) && entry.kind != cssImportExternalPath { 3531 wipOrder = append(wipOrder, entry) 3532 } 3533 if entry.kind != cssImportLayers { 3534 isAtLayerPrefix = false 3535 } 3536 } 3537 3538 order, wipOrder = wipOrder, order[:0] 3539 } 3540 3541 // Next, optimize import order. If there are duplicate copies of an imported 3542 // file, replace all but the last copy with just the layers that are in that 3543 // file. This works because in CSS, the last instance of a declaration 3544 // overrides all previous instances of that declaration. 3545 { 3546 sourceIndexDuplicates := make(map[uint32][]int) 3547 externalPathDuplicates := make(map[logger.Path][]int) 3548 3549 nextBackward: 3550 for i := len(order) - 1; i >= 0; i-- { 3551 entry := order[i] 3552 switch entry.kind { 3553 case cssImportSourceIndex: 3554 duplicates := sourceIndexDuplicates[entry.sourceIndex] 3555 for _, j := range duplicates { 3556 if isConditionalImportRedundant(entry.conditions, order[j].conditions) { 3557 order[i].kind = cssImportLayers 3558 order[i].layers = c.graph.Files[entry.sourceIndex].InputFile.Repr.(*graph.CSSRepr).AST.LayersPostImport 3559 continue nextBackward 3560 } 3561 } 3562 sourceIndexDuplicates[entry.sourceIndex] = append(duplicates, i) 3563 3564 case cssImportExternalPath: 3565 duplicates := externalPathDuplicates[entry.externalPath] 3566 for _, j := range duplicates { 3567 if isConditionalImportRedundant(entry.conditions, order[j].conditions) { 3568 // Don't remove duplicates entirely. The import conditions may 3569 // still introduce layers to the layer order. Represent this as a 3570 // file with an empty layer list. 3571 order[i].kind = cssImportLayers 3572 continue nextBackward 3573 } 3574 } 3575 externalPathDuplicates[entry.externalPath] = append(duplicates, i) 3576 } 3577 } 3578 } 3579 3580 // Then optimize "@layer" rules by removing redundant ones. This loop goes 3581 // forward instead of backward because "@layer" takes effect at the first 3582 // copy instead of the last copy like other things in CSS. 3583 { 3584 type duplicateEntry struct { 3585 layers [][]string 3586 indices []int 3587 } 3588 var layerDuplicates []duplicateEntry 3589 3590 nextForward: 3591 for i := range order { 3592 entry := order[i] 3593 3594 // Simplify the conditions since we know they only wrap "@layer" 3595 if entry.kind == cssImportLayers { 3596 // Truncate the conditions at the first anonymous layer 3597 for i, conditions := range entry.conditions { 3598 // The layer is anonymous if it's a "layer" token without any 3599 // children instead of a "layer(...)" token with children: 3600 // 3601 // /* entry.css */ 3602 // @import "foo.css" layer; 3603 // 3604 // /* foo.css */ 3605 // @layer foo; 3606 // 3607 // We don't need to generate this (as far as I can tell): 3608 // 3609 // @layer { 3610 // @layer foo; 3611 // } 3612 // 3613 if conditions.Layers != nil && len(conditions.Layers) == 1 && conditions.Layers[0].Children == nil { 3614 entry.conditions = entry.conditions[:i] 3615 entry.layers = nil 3616 break 3617 } 3618 } 3619 3620 // If there are no layer names for this file, trim all conditions 3621 // without layers because we know they have no effect. 3622 // 3623 // /* entry.css */ 3624 // @import "foo.css" layer(foo) supports(display: flex); 3625 // 3626 // /* foo.css */ 3627 // @import "empty.css" supports(display: grid); 3628 // 3629 // That would result in this: 3630 // 3631 // @supports (display: flex) { 3632 // @layer foo { 3633 // @supports (display: grid) {} 3634 // } 3635 // } 3636 // 3637 // Here we can trim "supports(display: grid)" to generate this: 3638 // 3639 // @supports (display: flex) { 3640 // @layer foo; 3641 // } 3642 // 3643 if len(entry.layers) == 0 { 3644 for i := len(entry.conditions) - 1; i >= 0; i-- { 3645 if len(entry.conditions[i].Layers) > 0 { 3646 break 3647 } 3648 entry.conditions = entry.conditions[:i] 3649 } 3650 } 3651 3652 // Remove unnecessary entries entirely 3653 if len(entry.conditions) == 0 && len(entry.layers) == 0 { 3654 continue 3655 } 3656 } 3657 3658 // Omit redundant "@layer" rules with the same set of layer names. Note 3659 // that this tests all import order entries (not just layer ones) because 3660 // sometimes non-layer ones can make following layer ones redundant. 3661 layersKey := entry.layers 3662 if entry.kind == cssImportSourceIndex { 3663 layersKey = c.graph.Files[entry.sourceIndex].InputFile.Repr.(*graph.CSSRepr).AST.LayersPostImport 3664 } 3665 index := 0 3666 for index < len(layerDuplicates) { 3667 if helpers.StringArrayArraysEqual(layersKey, layerDuplicates[index].layers) { 3668 break 3669 } 3670 index++ 3671 } 3672 if index == len(layerDuplicates) { 3673 // This is the first time we've seen this combination of layer names. 3674 // Allocate a new set of duplicate indices to track this combination. 3675 layerDuplicates = append(layerDuplicates, duplicateEntry{layers: layersKey}) 3676 } 3677 duplicates := layerDuplicates[index].indices 3678 for j := len(duplicates) - 1; j >= 0; j-- { 3679 if index := duplicates[j]; isConditionalImportRedundant(entry.conditions, wipOrder[index].conditions) { 3680 if entry.kind != cssImportLayers { 3681 // If an empty layer is followed immediately by a full layer and 3682 // everything else is identical, then we don't need to emit the 3683 // empty layer. For example: 3684 // 3685 // @media screen { 3686 // @supports (display: grid) { 3687 // @layer foo; 3688 // } 3689 // } 3690 // @media screen { 3691 // @supports (display: grid) { 3692 // @layer foo { 3693 // div { 3694 // color: red; 3695 // } 3696 // } 3697 // } 3698 // } 3699 // 3700 // This can be improved by dropping the empty layer. But we can 3701 // only do this if there's nothing in between these two rules. 3702 if j == len(duplicates)-1 && index == len(wipOrder)-1 { 3703 if other := wipOrder[index]; other.kind == cssImportLayers && importConditionsAreEqual(entry.conditions, other.conditions) { 3704 // Remove the previous entry and then overwrite it below 3705 duplicates = duplicates[:j] 3706 wipOrder = wipOrder[:index] 3707 break 3708 } 3709 } 3710 3711 // Non-layer entries still need to be present because they have 3712 // other side effects beside inserting things in the layer order 3713 wipOrder = append(wipOrder, entry) 3714 } 3715 3716 // Don't add this to the duplicate list below because it's redundant 3717 continue nextForward 3718 } 3719 } 3720 layerDuplicates[index].indices = append(duplicates, len(wipOrder)) 3721 wipOrder = append(wipOrder, entry) 3722 } 3723 3724 order, wipOrder = wipOrder, order[:0] 3725 } 3726 3727 // Finally, merge adjacent "@layer" rules with identical conditions together. 3728 { 3729 didClone := -1 3730 for _, entry := range order { 3731 if entry.kind == cssImportLayers && len(wipOrder) > 0 { 3732 prevIndex := len(wipOrder) - 1 3733 prev := wipOrder[prevIndex] 3734 if prev.kind == cssImportLayers && importConditionsAreEqual(prev.conditions, entry.conditions) { 3735 if didClone != prevIndex { 3736 didClone = prevIndex 3737 prev.layers = append([][]string{}, prev.layers...) 3738 } 3739 wipOrder[prevIndex].layers = append(prev.layers, entry.layers...) 3740 continue 3741 } 3742 } 3743 wipOrder = append(wipOrder, entry) 3744 } 3745 order = wipOrder 3746 } 3747 3748 return 3749 } 3750 3751 func importConditionsAreEqual(a []css_ast.ImportConditions, b []css_ast.ImportConditions) bool { 3752 if len(a) != len(b) { 3753 return false 3754 } 3755 for i := 0; i < len(a); i++ { 3756 ai := a[i] 3757 bi := b[i] 3758 if !css_ast.TokensEqualIgnoringWhitespace(ai.Layers, bi.Layers) || 3759 !css_ast.TokensEqualIgnoringWhitespace(ai.Supports, bi.Supports) || 3760 !css_ast.TokensEqualIgnoringWhitespace(ai.Media, bi.Media) { 3761 return false 3762 } 3763 } 3764 return true 3765 } 3766 3767 // Given two "@import" rules for the same source index (an earlier one and a 3768 // later one), the earlier one is masked by the later one if the later one's 3769 // condition list is a prefix of the earlier one's condition list. 3770 // 3771 // For example: 3772 // 3773 // // entry.css 3774 // @import "foo.css" supports(display: flex); 3775 // @import "bar.css" supports(display: flex); 3776 // 3777 // // foo.css 3778 // @import "lib.css" screen; 3779 // 3780 // // bar.css 3781 // @import "lib.css"; 3782 // 3783 // When we bundle this code we'll get an import order as follows: 3784 // 3785 // 1. lib.css [supports(display: flex), screen] 3786 // 2. foo.css [supports(display: flex)] 3787 // 3. lib.css [supports(display: flex)] 3788 // 4. bar.css [supports(display: flex)] 3789 // 5. entry.css [] 3790 // 3791 // For "lib.css", the entry with the conditions [supports(display: flex)] should 3792 // make the entry with the conditions [supports(display: flex), screen] redundant. 3793 // 3794 // Note that all of this deliberately ignores the existence of "@layer" because 3795 // that is handled separately. All of this is only for handling unlayered styles. 3796 func isConditionalImportRedundant(earlier []css_ast.ImportConditions, later []css_ast.ImportConditions) bool { 3797 if len(later) > len(earlier) { 3798 return false 3799 } 3800 3801 for i := 0; i < len(later); i++ { 3802 a := earlier[i] 3803 b := later[i] 3804 3805 // Only compare "@supports" and "@media" if "@layers" is equal 3806 if css_ast.TokensEqualIgnoringWhitespace(a.Layers, b.Layers) { 3807 sameSupports := css_ast.TokensEqualIgnoringWhitespace(a.Supports, b.Supports) 3808 sameMedia := css_ast.TokensEqualIgnoringWhitespace(a.Media, b.Media) 3809 3810 // If the import conditions are exactly equal, then only keep 3811 // the later one. The earlier one is redundant. Example: 3812 // 3813 // @import "foo.css" layer(abc) supports(display: flex) screen; 3814 // @import "foo.css" layer(abc) supports(display: flex) screen; 3815 // 3816 // The later one makes the earlier one redundant. 3817 if sameSupports && sameMedia { 3818 continue 3819 } 3820 3821 // If the media conditions are exactly equal and the later one 3822 // doesn't have any supports conditions, then the later one will 3823 // apply in all cases where the earlier one applies. Example: 3824 // 3825 // @import "foo.css" layer(abc) supports(display: flex) screen; 3826 // @import "foo.css" layer(abc) screen; 3827 // 3828 // The later one makes the earlier one redundant. 3829 if sameMedia && len(b.Supports) == 0 { 3830 continue 3831 } 3832 3833 // If the supports conditions are exactly equal and the later one 3834 // doesn't have any media conditions, then the later one will 3835 // apply in all cases where the earlier one applies. Example: 3836 // 3837 // @import "foo.css" layer(abc) supports(display: flex) screen; 3838 // @import "foo.css" layer(abc) supports(display: flex); 3839 // 3840 // The later one makes the earlier one redundant. 3841 if sameSupports && len(b.Media) == 0 { 3842 continue 3843 } 3844 } 3845 3846 return false 3847 } 3848 3849 return true 3850 } 3851 3852 func (c *linkerContext) computeChunks() { 3853 c.timer.Begin("Compute chunks") 3854 defer c.timer.End("Compute chunks") 3855 3856 jsChunks := make(map[string]chunkInfo) 3857 cssChunks := make(map[string]chunkInfo) 3858 3859 // Create chunks for entry points 3860 for i, entryPoint := range c.graph.EntryPoints() { 3861 file := &c.graph.Files[entryPoint.SourceIndex] 3862 3863 // Create a chunk for the entry point here to ensure that the chunk is 3864 // always generated even if the resulting file is empty 3865 entryBits := helpers.NewBitSet(uint(len(c.graph.EntryPoints()))) 3866 entryBits.SetBit(uint(i)) 3867 key := entryBits.String() 3868 chunk := chunkInfo{ 3869 entryBits: entryBits, 3870 isEntryPoint: true, 3871 sourceIndex: entryPoint.SourceIndex, 3872 entryPointBit: uint(i), 3873 filesWithPartsInChunk: make(map[uint32]bool), 3874 } 3875 3876 switch file.InputFile.Repr.(type) { 3877 case *graph.JSRepr: 3878 chunkRepr := &chunkReprJS{} 3879 chunk.chunkRepr = chunkRepr 3880 jsChunks[key] = chunk 3881 3882 // If this JS entry point has an associated CSS entry point, generate it 3883 // now. This is essentially done by generating a virtual CSS file that 3884 // only contains "@import" statements in the order that the files were 3885 // discovered in JS source order, where JS source order is arbitrary but 3886 // consistent for dynamic imports. Then we run the CSS import order 3887 // algorithm to determine the final CSS file order for the chunk. 3888 3889 if cssSourceIndices := c.findImportedCSSFilesInJSOrder(entryPoint.SourceIndex); len(cssSourceIndices) > 0 { 3890 order := c.findImportedFilesInCSSOrder(cssSourceIndices) 3891 cssFilesWithPartsInChunk := make(map[uint32]bool) 3892 for _, entry := range order { 3893 if entry.kind == cssImportSourceIndex { 3894 cssFilesWithPartsInChunk[uint32(entry.sourceIndex)] = true 3895 } 3896 } 3897 cssChunks[key] = chunkInfo{ 3898 entryBits: entryBits, 3899 isEntryPoint: true, 3900 sourceIndex: entryPoint.SourceIndex, 3901 entryPointBit: uint(i), 3902 filesWithPartsInChunk: cssFilesWithPartsInChunk, 3903 chunkRepr: &chunkReprCSS{ 3904 importsInChunkInOrder: order, 3905 }, 3906 } 3907 chunkRepr.hasCSSChunk = true 3908 } 3909 3910 case *graph.CSSRepr: 3911 order := c.findImportedFilesInCSSOrder([]uint32{entryPoint.SourceIndex}) 3912 for _, entry := range order { 3913 if entry.kind == cssImportSourceIndex { 3914 chunk.filesWithPartsInChunk[uint32(entry.sourceIndex)] = true 3915 } 3916 } 3917 chunk.chunkRepr = &chunkReprCSS{ 3918 importsInChunkInOrder: order, 3919 } 3920 cssChunks[key] = chunk 3921 } 3922 } 3923 3924 // Figure out which JS files are in which chunk 3925 for _, sourceIndex := range c.graph.ReachableFiles { 3926 if file := &c.graph.Files[sourceIndex]; file.IsLive { 3927 if _, ok := file.InputFile.Repr.(*graph.JSRepr); ok { 3928 key := file.EntryBits.String() 3929 chunk, ok := jsChunks[key] 3930 if !ok { 3931 chunk.entryBits = file.EntryBits 3932 chunk.filesWithPartsInChunk = make(map[uint32]bool) 3933 chunk.chunkRepr = &chunkReprJS{} 3934 jsChunks[key] = chunk 3935 } 3936 chunk.filesWithPartsInChunk[uint32(sourceIndex)] = true 3937 } 3938 } 3939 } 3940 3941 // Sort the chunks for determinism. This matters because we use chunk indices 3942 // as sorting keys in a few places. 3943 sortedChunks := make([]chunkInfo, 0, len(jsChunks)+len(cssChunks)) 3944 sortedKeys := make([]string, 0, len(jsChunks)+len(cssChunks)) 3945 for key := range jsChunks { 3946 sortedKeys = append(sortedKeys, key) 3947 } 3948 sort.Strings(sortedKeys) 3949 jsChunkIndicesForCSS := make(map[string]uint32) 3950 for _, key := range sortedKeys { 3951 chunk := jsChunks[key] 3952 if chunk.chunkRepr.(*chunkReprJS).hasCSSChunk { 3953 jsChunkIndicesForCSS[key] = uint32(len(sortedChunks)) 3954 } 3955 sortedChunks = append(sortedChunks, chunk) 3956 } 3957 sortedKeys = sortedKeys[:0] 3958 for key := range cssChunks { 3959 sortedKeys = append(sortedKeys, key) 3960 } 3961 sort.Strings(sortedKeys) 3962 for _, key := range sortedKeys { 3963 chunk := cssChunks[key] 3964 if jsChunkIndex, ok := jsChunkIndicesForCSS[key]; ok { 3965 sortedChunks[jsChunkIndex].chunkRepr.(*chunkReprJS).cssChunkIndex = uint32(len(sortedChunks)) 3966 } 3967 sortedChunks = append(sortedChunks, chunk) 3968 } 3969 3970 // Map from the entry point file to its chunk. We will need this later if 3971 // a file contains a dynamic import to this entry point, since we'll need 3972 // to look up the path for this chunk to use with the import. 3973 for chunkIndex, chunk := range sortedChunks { 3974 if chunk.isEntryPoint { 3975 file := &c.graph.Files[chunk.sourceIndex] 3976 3977 // JS entry points that import CSS files generate two chunks, a JS chunk 3978 // and a CSS chunk. Don't link the CSS chunk to the JS file since the CSS 3979 // chunk is secondary (the JS chunk is primary). 3980 if _, ok := chunk.chunkRepr.(*chunkReprCSS); ok { 3981 if _, ok := file.InputFile.Repr.(*graph.JSRepr); ok { 3982 continue 3983 } 3984 } 3985 3986 file.EntryPointChunkIndex = uint32(chunkIndex) 3987 } 3988 } 3989 3990 // Determine the order of JS files (and parts) within the chunk ahead of time 3991 for _, chunk := range sortedChunks { 3992 if chunkRepr, ok := chunk.chunkRepr.(*chunkReprJS); ok { 3993 chunkRepr.filesInChunkInOrder, chunkRepr.partsInChunkInOrder = c.findImportedPartsInJSOrder(&chunk) 3994 } 3995 } 3996 3997 // Assign general information to each chunk 3998 for chunkIndex := range sortedChunks { 3999 chunk := &sortedChunks[chunkIndex] 4000 4001 // Assign a unique key to each chunk. This key encodes the index directly so 4002 // we can easily recover it later without needing to look it up in a map. The 4003 // last 8 numbers of the key are the chunk index. 4004 chunk.uniqueKey = fmt.Sprintf("%sC%08d", c.uniqueKeyPrefix, chunkIndex) 4005 4006 // Determine the standard file extension 4007 var stdExt string 4008 switch chunk.chunkRepr.(type) { 4009 case *chunkReprJS: 4010 stdExt = c.options.OutputExtensionJS 4011 case *chunkReprCSS: 4012 stdExt = c.options.OutputExtensionCSS 4013 } 4014 4015 // Compute the template substitutions 4016 var dir, base, ext string 4017 var template []config.PathTemplate 4018 if chunk.isEntryPoint { 4019 // Only use the entry path template for user-specified entry points 4020 file := &c.graph.Files[chunk.sourceIndex] 4021 if file.IsUserSpecifiedEntryPoint() { 4022 template = c.options.EntryPathTemplate 4023 } else { 4024 template = c.options.ChunkPathTemplate 4025 } 4026 4027 if c.options.AbsOutputFile != "" { 4028 // If the output path was configured explicitly, use it verbatim 4029 dir = "/" 4030 base = c.fs.Base(c.options.AbsOutputFile) 4031 originalExt := c.fs.Ext(base) 4032 base = base[:len(base)-len(originalExt)] 4033 4034 // Use the extension from the explicit output file path. However, don't do 4035 // that if this is a CSS chunk but the entry point file is not CSS. In that 4036 // case use the standard extension. This happens when importing CSS into JS. 4037 if _, ok := file.InputFile.Repr.(*graph.CSSRepr); ok || stdExt != c.options.OutputExtensionCSS { 4038 ext = originalExt 4039 } else { 4040 ext = stdExt 4041 } 4042 } else { 4043 // Otherwise, derive the output path from the input path 4044 dir, base = bundler.PathRelativeToOutbase( 4045 &c.graph.Files[chunk.sourceIndex].InputFile, 4046 c.options, 4047 c.fs, 4048 !file.IsUserSpecifiedEntryPoint(), 4049 c.graph.EntryPoints()[chunk.entryPointBit].OutputPath, 4050 ) 4051 ext = stdExt 4052 } 4053 } else { 4054 dir = "/" 4055 base = "chunk" 4056 ext = stdExt 4057 template = c.options.ChunkPathTemplate 4058 } 4059 4060 // Determine the output path template 4061 templateExt := strings.TrimPrefix(ext, ".") 4062 template = append(append(make([]config.PathTemplate, 0, len(template)+1), template...), config.PathTemplate{Data: ext}) 4063 chunk.finalTemplate = config.SubstituteTemplate(template, config.PathPlaceholders{ 4064 Dir: &dir, 4065 Name: &base, 4066 Ext: &templateExt, 4067 }) 4068 } 4069 4070 c.chunks = sortedChunks 4071 } 4072 4073 type chunkOrder struct { 4074 sourceIndex uint32 4075 distance uint32 4076 tieBreaker uint32 4077 } 4078 4079 // This type is just so we can use Go's native sort function 4080 type chunkOrderArray []chunkOrder 4081 4082 func (a chunkOrderArray) Len() int { return len(a) } 4083 func (a chunkOrderArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] } 4084 4085 func (a chunkOrderArray) Less(i int, j int) bool { 4086 ai := a[i] 4087 aj := a[j] 4088 return ai.distance < aj.distance || (ai.distance == aj.distance && ai.tieBreaker < aj.tieBreaker) 4089 } 4090 4091 func appendOrExtendPartRange(ranges []partRange, sourceIndex uint32, partIndex uint32) []partRange { 4092 if i := len(ranges) - 1; i >= 0 { 4093 if r := &ranges[i]; r.sourceIndex == sourceIndex && r.partIndexEnd == partIndex { 4094 r.partIndexEnd = partIndex + 1 4095 return ranges 4096 } 4097 } 4098 4099 return append(ranges, partRange{ 4100 sourceIndex: sourceIndex, 4101 partIndexBegin: partIndex, 4102 partIndexEnd: partIndex + 1, 4103 }) 4104 } 4105 4106 func (c *linkerContext) shouldIncludePart(repr *graph.JSRepr, part js_ast.Part) bool { 4107 // As an optimization, ignore parts containing a single import statement to 4108 // an internal non-wrapped file. These will be ignored anyway and it's a 4109 // performance hit to spin up a goroutine only to discover this later. 4110 if len(part.Stmts) == 1 { 4111 if s, ok := part.Stmts[0].Data.(*js_ast.SImport); ok { 4112 record := &repr.AST.ImportRecords[s.ImportRecordIndex] 4113 if record.SourceIndex.IsValid() && c.graph.Files[record.SourceIndex.GetIndex()].InputFile.Repr.(*graph.JSRepr).Meta.Wrap == graph.WrapNone { 4114 return false 4115 } 4116 } 4117 } 4118 return true 4119 } 4120 4121 func (c *linkerContext) findImportedPartsInJSOrder(chunk *chunkInfo) (js []uint32, jsParts []partRange) { 4122 sorted := make(chunkOrderArray, 0, len(chunk.filesWithPartsInChunk)) 4123 4124 // Attach information to the files for use with sorting 4125 for sourceIndex := range chunk.filesWithPartsInChunk { 4126 file := &c.graph.Files[sourceIndex] 4127 sorted = append(sorted, chunkOrder{ 4128 sourceIndex: sourceIndex, 4129 distance: file.DistanceFromEntryPoint, 4130 tieBreaker: c.graph.StableSourceIndices[sourceIndex], 4131 }) 4132 } 4133 4134 // Sort so files closest to an entry point come first. If two files are 4135 // equidistant to an entry point, then break the tie by sorting on the 4136 // stable source index derived from the DFS over all entry points. 4137 sort.Sort(sorted) 4138 4139 visited := make(map[uint32]bool) 4140 jsPartsPrefix := []partRange{} 4141 4142 // Traverse the graph using this stable order and linearize the files with 4143 // dependencies before dependents 4144 var visit func(uint32) 4145 visit = func(sourceIndex uint32) { 4146 if visited[sourceIndex] { 4147 return 4148 } 4149 4150 visited[sourceIndex] = true 4151 file := &c.graph.Files[sourceIndex] 4152 4153 if repr, ok := file.InputFile.Repr.(*graph.JSRepr); ok { 4154 isFileInThisChunk := chunk.entryBits.Equals(file.EntryBits) 4155 4156 // Wrapped files can't be split because they are all inside the wrapper 4157 canFileBeSplit := repr.Meta.Wrap == graph.WrapNone 4158 4159 // Make sure the generated call to "__export(exports, ...)" comes first 4160 // before anything else in this file 4161 if canFileBeSplit && isFileInThisChunk && repr.AST.Parts[js_ast.NSExportPartIndex].IsLive { 4162 jsParts = appendOrExtendPartRange(jsParts, sourceIndex, js_ast.NSExportPartIndex) 4163 } 4164 4165 for partIndex, part := range repr.AST.Parts { 4166 isPartInThisChunk := isFileInThisChunk && repr.AST.Parts[partIndex].IsLive 4167 4168 // Also traverse any files imported by this part 4169 for _, importRecordIndex := range part.ImportRecordIndices { 4170 record := &repr.AST.ImportRecords[importRecordIndex] 4171 if record.SourceIndex.IsValid() && (record.Kind == ast.ImportStmt || isPartInThisChunk) { 4172 if c.isExternalDynamicImport(record, sourceIndex) { 4173 // Don't follow import() dependencies 4174 continue 4175 } 4176 visit(record.SourceIndex.GetIndex()) 4177 } 4178 } 4179 4180 // Then include this part after the files it imports 4181 if isPartInThisChunk { 4182 isFileInThisChunk = true 4183 if canFileBeSplit && uint32(partIndex) != js_ast.NSExportPartIndex && c.shouldIncludePart(repr, part) { 4184 if sourceIndex == runtime.SourceIndex { 4185 jsPartsPrefix = appendOrExtendPartRange(jsPartsPrefix, sourceIndex, uint32(partIndex)) 4186 } else { 4187 jsParts = appendOrExtendPartRange(jsParts, sourceIndex, uint32(partIndex)) 4188 } 4189 } 4190 } 4191 } 4192 4193 if isFileInThisChunk { 4194 js = append(js, sourceIndex) 4195 4196 // CommonJS files are all-or-nothing so all parts must be contiguous 4197 if !canFileBeSplit { 4198 jsPartsPrefix = append(jsPartsPrefix, partRange{ 4199 sourceIndex: sourceIndex, 4200 partIndexBegin: 0, 4201 partIndexEnd: uint32(len(repr.AST.Parts)), 4202 }) 4203 } 4204 } 4205 } 4206 } 4207 4208 // Always put the runtime code first before anything else 4209 visit(runtime.SourceIndex) 4210 for _, data := range sorted { 4211 visit(data.sourceIndex) 4212 } 4213 jsParts = append(jsPartsPrefix, jsParts...) 4214 return 4215 } 4216 4217 func (c *linkerContext) shouldRemoveImportExportStmt( 4218 sourceIndex uint32, 4219 stmtList *stmtList, 4220 loc logger.Loc, 4221 namespaceRef ast.Ref, 4222 importRecordIndex uint32, 4223 ) bool { 4224 repr := c.graph.Files[sourceIndex].InputFile.Repr.(*graph.JSRepr) 4225 record := &repr.AST.ImportRecords[importRecordIndex] 4226 4227 // Is this an external import? 4228 if !record.SourceIndex.IsValid() { 4229 // Keep the "import" statement if "import" statements are supported 4230 if c.options.OutputFormat.KeepESMImportExportSyntax() { 4231 return false 4232 } 4233 4234 // Otherwise, replace this statement with a call to "require()" 4235 stmtList.insideWrapperPrefix = append(stmtList.insideWrapperPrefix, js_ast.Stmt{ 4236 Loc: loc, 4237 Data: &js_ast.SLocal{Decls: []js_ast.Decl{{ 4238 Binding: js_ast.Binding{Loc: loc, Data: &js_ast.BIdentifier{Ref: namespaceRef}}, 4239 ValueOrNil: js_ast.Expr{Loc: record.Range.Loc, Data: &js_ast.ERequireString{ 4240 ImportRecordIndex: importRecordIndex, 4241 }}, 4242 }}}, 4243 }) 4244 return true 4245 } 4246 4247 // We don't need a call to "require()" if this is a self-import inside a 4248 // CommonJS-style module, since we can just reference the exports directly. 4249 if repr.AST.ExportsKind == js_ast.ExportsCommonJS && ast.FollowSymbols(c.graph.Symbols, namespaceRef) == repr.AST.ExportsRef { 4250 return true 4251 } 4252 4253 otherFile := &c.graph.Files[record.SourceIndex.GetIndex()] 4254 otherRepr := otherFile.InputFile.Repr.(*graph.JSRepr) 4255 switch otherRepr.Meta.Wrap { 4256 case graph.WrapNone: 4257 // Remove the statement entirely if this module is not wrapped 4258 4259 case graph.WrapCJS: 4260 // Replace the statement with a call to "require()" 4261 stmtList.insideWrapperPrefix = append(stmtList.insideWrapperPrefix, js_ast.Stmt{ 4262 Loc: loc, 4263 Data: &js_ast.SLocal{Decls: []js_ast.Decl{{ 4264 Binding: js_ast.Binding{Loc: loc, Data: &js_ast.BIdentifier{Ref: namespaceRef}}, 4265 ValueOrNil: js_ast.Expr{Loc: record.Range.Loc, Data: &js_ast.ERequireString{ 4266 ImportRecordIndex: importRecordIndex, 4267 }}, 4268 }}}, 4269 }) 4270 4271 case graph.WrapESM: 4272 // Ignore this file if it's not included in the bundle. This can happen for 4273 // wrapped ESM files but not for wrapped CommonJS files because we allow 4274 // tree shaking inside wrapped ESM files. 4275 if !otherFile.IsLive { 4276 break 4277 } 4278 4279 // Replace the statement with a call to "init()" 4280 value := js_ast.Expr{Loc: loc, Data: &js_ast.ECall{Target: js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: otherRepr.AST.WrapperRef}}}} 4281 if otherRepr.Meta.IsAsyncOrHasAsyncDependency { 4282 // This currently evaluates sibling dependencies in serial instead of in 4283 // parallel, which is incorrect. This should be changed to store a promise 4284 // and await all stored promises after all imports but before any code. 4285 value.Data = &js_ast.EAwait{Value: value} 4286 } 4287 stmtList.insideWrapperPrefix = append(stmtList.insideWrapperPrefix, js_ast.Stmt{Loc: loc, Data: &js_ast.SExpr{Value: value}}) 4288 } 4289 4290 return true 4291 } 4292 4293 func (c *linkerContext) convertStmtsForChunk(sourceIndex uint32, stmtList *stmtList, partStmts []js_ast.Stmt) { 4294 file := &c.graph.Files[sourceIndex] 4295 shouldStripExports := c.options.Mode != config.ModePassThrough || !file.IsEntryPoint() 4296 repr := file.InputFile.Repr.(*graph.JSRepr) 4297 shouldExtractESMStmtsForWrap := repr.Meta.Wrap != graph.WrapNone 4298 4299 // If this file is a CommonJS entry point, double-write re-exports to the 4300 // external CommonJS "module.exports" object in addition to our internal ESM 4301 // export namespace object. The difference between these two objects is that 4302 // our internal one must not have the "__esModule" marker while the external 4303 // one must have the "__esModule" marker. This is done because an ES module 4304 // importing itself should not see the "__esModule" marker but a CommonJS module 4305 // importing us should see the "__esModule" marker. 4306 var moduleExportsForReExportOrNil js_ast.Expr 4307 if c.options.OutputFormat == config.FormatCommonJS && file.IsEntryPoint() { 4308 moduleExportsForReExportOrNil = js_ast.Expr{Data: &js_ast.EDot{ 4309 Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: c.unboundModuleRef}}, 4310 Name: "exports", 4311 }} 4312 } 4313 4314 for _, stmt := range partStmts { 4315 switch s := stmt.Data.(type) { 4316 case *js_ast.SImport: 4317 // "import * as ns from 'path'" 4318 // "import {foo} from 'path'" 4319 if c.shouldRemoveImportExportStmt(sourceIndex, stmtList, stmt.Loc, s.NamespaceRef, s.ImportRecordIndex) { 4320 continue 4321 } 4322 4323 if c.options.UnsupportedJSFeatures.Has(compat.ArbitraryModuleNamespaceNames) && s.Items != nil { 4324 for _, item := range *s.Items { 4325 c.maybeForbidArbitraryModuleNamespaceIdentifier("import", sourceIndex, item.AliasLoc, item.Alias) 4326 } 4327 } 4328 4329 // Make sure these don't end up in the wrapper closure 4330 if shouldExtractESMStmtsForWrap { 4331 stmtList.outsideWrapperPrefix = append(stmtList.outsideWrapperPrefix, stmt) 4332 continue 4333 } 4334 4335 case *js_ast.SExportStar: 4336 // "export * as ns from 'path'" 4337 if s.Alias != nil { 4338 if c.shouldRemoveImportExportStmt(sourceIndex, stmtList, stmt.Loc, s.NamespaceRef, s.ImportRecordIndex) { 4339 continue 4340 } 4341 4342 if c.options.UnsupportedJSFeatures.Has(compat.ArbitraryModuleNamespaceNames) { 4343 c.maybeForbidArbitraryModuleNamespaceIdentifier("export", sourceIndex, s.Alias.Loc, s.Alias.OriginalName) 4344 } 4345 4346 if shouldStripExports { 4347 // Turn this statement into "import * as ns from 'path'" 4348 stmt.Data = &js_ast.SImport{ 4349 NamespaceRef: s.NamespaceRef, 4350 StarNameLoc: &s.Alias.Loc, 4351 ImportRecordIndex: s.ImportRecordIndex, 4352 } 4353 } 4354 4355 // Make sure these don't end up in the wrapper closure 4356 if shouldExtractESMStmtsForWrap { 4357 stmtList.outsideWrapperPrefix = append(stmtList.outsideWrapperPrefix, stmt) 4358 continue 4359 } 4360 break 4361 } 4362 4363 // "export * from 'path'" 4364 if !shouldStripExports { 4365 break 4366 } 4367 record := &repr.AST.ImportRecords[s.ImportRecordIndex] 4368 4369 // Is this export star evaluated at run time? 4370 if !record.SourceIndex.IsValid() && c.options.OutputFormat.KeepESMImportExportSyntax() { 4371 if record.Flags.Has(ast.CallsRunTimeReExportFn) { 4372 // Turn this statement into "import * as ns from 'path'" 4373 stmt.Data = &js_ast.SImport{ 4374 NamespaceRef: s.NamespaceRef, 4375 StarNameLoc: &logger.Loc{Start: stmt.Loc.Start}, 4376 ImportRecordIndex: s.ImportRecordIndex, 4377 } 4378 4379 // Prefix this module with "__reExport(exports, ns, module.exports)" 4380 exportStarRef := c.graph.Files[runtime.SourceIndex].InputFile.Repr.(*graph.JSRepr).AST.ModuleScope.Members["__reExport"].Ref 4381 args := []js_ast.Expr{ 4382 {Loc: stmt.Loc, Data: &js_ast.EIdentifier{Ref: repr.AST.ExportsRef}}, 4383 {Loc: stmt.Loc, Data: &js_ast.EIdentifier{Ref: s.NamespaceRef}}, 4384 } 4385 if moduleExportsForReExportOrNil.Data != nil { 4386 args = append(args, moduleExportsForReExportOrNil) 4387 } 4388 stmtList.insideWrapperPrefix = append(stmtList.insideWrapperPrefix, js_ast.Stmt{ 4389 Loc: stmt.Loc, 4390 Data: &js_ast.SExpr{Value: js_ast.Expr{Loc: stmt.Loc, Data: &js_ast.ECall{ 4391 Target: js_ast.Expr{Loc: stmt.Loc, Data: &js_ast.EIdentifier{Ref: exportStarRef}}, 4392 Args: args, 4393 }}}, 4394 }) 4395 4396 // Make sure these don't end up in the wrapper closure 4397 if shouldExtractESMStmtsForWrap { 4398 stmtList.outsideWrapperPrefix = append(stmtList.outsideWrapperPrefix, stmt) 4399 continue 4400 } 4401 } 4402 } else { 4403 if record.SourceIndex.IsValid() { 4404 if otherRepr := c.graph.Files[record.SourceIndex.GetIndex()].InputFile.Repr.(*graph.JSRepr); otherRepr.Meta.Wrap == graph.WrapESM { 4405 stmtList.insideWrapperPrefix = append(stmtList.insideWrapperPrefix, js_ast.Stmt{Loc: stmt.Loc, 4406 Data: &js_ast.SExpr{Value: js_ast.Expr{Loc: stmt.Loc, Data: &js_ast.ECall{ 4407 Target: js_ast.Expr{Loc: stmt.Loc, Data: &js_ast.EIdentifier{Ref: otherRepr.AST.WrapperRef}}}}}}) 4408 } 4409 } 4410 4411 if record.Flags.Has(ast.CallsRunTimeReExportFn) { 4412 var target js_ast.E 4413 if record.SourceIndex.IsValid() { 4414 if otherRepr := c.graph.Files[record.SourceIndex.GetIndex()].InputFile.Repr.(*graph.JSRepr); otherRepr.AST.ExportsKind == js_ast.ExportsESMWithDynamicFallback { 4415 // Prefix this module with "__reExport(exports, otherExports, module.exports)" 4416 target = &js_ast.EIdentifier{Ref: otherRepr.AST.ExportsRef} 4417 } 4418 } 4419 if target == nil { 4420 // Prefix this module with "__reExport(exports, require(path), module.exports)" 4421 target = &js_ast.ERequireString{ 4422 ImportRecordIndex: s.ImportRecordIndex, 4423 } 4424 } 4425 exportStarRef := c.graph.Files[runtime.SourceIndex].InputFile.Repr.(*graph.JSRepr).AST.ModuleScope.Members["__reExport"].Ref 4426 args := []js_ast.Expr{ 4427 {Loc: stmt.Loc, Data: &js_ast.EIdentifier{Ref: repr.AST.ExportsRef}}, 4428 {Loc: record.Range.Loc, Data: target}, 4429 } 4430 if moduleExportsForReExportOrNil.Data != nil { 4431 args = append(args, moduleExportsForReExportOrNil) 4432 } 4433 stmtList.insideWrapperPrefix = append(stmtList.insideWrapperPrefix, js_ast.Stmt{ 4434 Loc: stmt.Loc, 4435 Data: &js_ast.SExpr{Value: js_ast.Expr{Loc: stmt.Loc, Data: &js_ast.ECall{ 4436 Target: js_ast.Expr{Loc: stmt.Loc, Data: &js_ast.EIdentifier{Ref: exportStarRef}}, 4437 Args: args, 4438 }}}, 4439 }) 4440 } 4441 4442 // Remove the export star statement 4443 continue 4444 } 4445 4446 case *js_ast.SExportFrom: 4447 // "export {foo} from 'path'" 4448 if c.shouldRemoveImportExportStmt(sourceIndex, stmtList, stmt.Loc, s.NamespaceRef, s.ImportRecordIndex) { 4449 continue 4450 } 4451 4452 if c.options.UnsupportedJSFeatures.Has(compat.ArbitraryModuleNamespaceNames) { 4453 for _, item := range s.Items { 4454 c.maybeForbidArbitraryModuleNamespaceIdentifier("export", sourceIndex, item.AliasLoc, item.Alias) 4455 if item.AliasLoc != item.Name.Loc { 4456 c.maybeForbidArbitraryModuleNamespaceIdentifier("import", sourceIndex, item.Name.Loc, item.OriginalName) 4457 } 4458 } 4459 } 4460 4461 if shouldStripExports { 4462 // Turn this statement into "import {foo} from 'path'" 4463 for i, item := range s.Items { 4464 s.Items[i].Alias = item.OriginalName 4465 } 4466 stmt.Data = &js_ast.SImport{ 4467 NamespaceRef: s.NamespaceRef, 4468 Items: &s.Items, 4469 ImportRecordIndex: s.ImportRecordIndex, 4470 IsSingleLine: s.IsSingleLine, 4471 } 4472 } 4473 4474 // Make sure these don't end up in the wrapper closure 4475 if shouldExtractESMStmtsForWrap { 4476 stmtList.outsideWrapperPrefix = append(stmtList.outsideWrapperPrefix, stmt) 4477 continue 4478 } 4479 4480 case *js_ast.SExportClause: 4481 if shouldStripExports { 4482 // Remove export statements entirely 4483 continue 4484 } 4485 4486 if c.options.UnsupportedJSFeatures.Has(compat.ArbitraryModuleNamespaceNames) { 4487 for _, item := range s.Items { 4488 c.maybeForbidArbitraryModuleNamespaceIdentifier("export", sourceIndex, item.AliasLoc, item.Alias) 4489 } 4490 } 4491 4492 // Make sure these don't end up in the wrapper closure 4493 if shouldExtractESMStmtsForWrap { 4494 stmtList.outsideWrapperPrefix = append(stmtList.outsideWrapperPrefix, stmt) 4495 continue 4496 } 4497 4498 case *js_ast.SFunction: 4499 // Strip the "export" keyword while bundling 4500 if shouldStripExports && s.IsExport { 4501 // Be careful to not modify the original statement 4502 clone := *s 4503 clone.IsExport = false 4504 stmt.Data = &clone 4505 } 4506 4507 case *js_ast.SClass: 4508 if shouldStripExports && s.IsExport { 4509 // Be careful to not modify the original statement 4510 clone := *s 4511 clone.IsExport = false 4512 stmt.Data = &clone 4513 } 4514 4515 case *js_ast.SLocal: 4516 if shouldStripExports && s.IsExport { 4517 // Be careful to not modify the original statement 4518 clone := *s 4519 clone.IsExport = false 4520 stmt.Data = &clone 4521 } 4522 4523 case *js_ast.SExportDefault: 4524 // If we're bundling, convert "export default" into a normal declaration 4525 if shouldStripExports { 4526 switch s2 := s.Value.Data.(type) { 4527 case *js_ast.SExpr: 4528 // "export default foo;" => "var default = foo;" 4529 stmt = js_ast.Stmt{Loc: stmt.Loc, Data: &js_ast.SLocal{Decls: []js_ast.Decl{ 4530 {Binding: js_ast.Binding{Loc: s.DefaultName.Loc, Data: &js_ast.BIdentifier{Ref: s.DefaultName.Ref}}, ValueOrNil: s2.Value}, 4531 }}} 4532 4533 case *js_ast.SFunction: 4534 // "export default function() {}" => "function default() {}" 4535 // "export default function foo() {}" => "function foo() {}" 4536 4537 // Be careful to not modify the original statement 4538 s2 = &js_ast.SFunction{Fn: s2.Fn} 4539 s2.Fn.Name = &s.DefaultName 4540 4541 stmt = js_ast.Stmt{Loc: s.Value.Loc, Data: s2} 4542 4543 case *js_ast.SClass: 4544 // "export default class {}" => "class default {}" 4545 // "export default class Foo {}" => "class Foo {}" 4546 4547 // Be careful to not modify the original statement 4548 s2 = &js_ast.SClass{Class: s2.Class} 4549 s2.Class.Name = &s.DefaultName 4550 4551 stmt = js_ast.Stmt{Loc: s.Value.Loc, Data: s2} 4552 4553 default: 4554 panic("Internal error") 4555 } 4556 } 4557 } 4558 4559 stmtList.insideWrapperSuffix = append(stmtList.insideWrapperSuffix, stmt) 4560 } 4561 } 4562 4563 // "var a = 1; var b = 2;" => "var a = 1, b = 2;" 4564 func mergeAdjacentLocalStmts(stmts []js_ast.Stmt) []js_ast.Stmt { 4565 if len(stmts) == 0 { 4566 return stmts 4567 } 4568 4569 didMergeWithPreviousLocal := false 4570 end := 1 4571 4572 for _, stmt := range stmts[1:] { 4573 // Try to merge with the previous variable statement 4574 if after, ok := stmt.Data.(*js_ast.SLocal); ok { 4575 if before, ok := stmts[end-1].Data.(*js_ast.SLocal); ok { 4576 // It must be the same kind of variable statement (i.e. let/var/const) 4577 if before.Kind == after.Kind && before.IsExport == after.IsExport { 4578 if didMergeWithPreviousLocal { 4579 // Avoid O(n^2) behavior for repeated variable declarations 4580 before.Decls = append(before.Decls, after.Decls...) 4581 } else { 4582 // Be careful to not modify the original statement 4583 didMergeWithPreviousLocal = true 4584 clone := *before 4585 clone.Decls = make([]js_ast.Decl, 0, len(before.Decls)+len(after.Decls)) 4586 clone.Decls = append(clone.Decls, before.Decls...) 4587 clone.Decls = append(clone.Decls, after.Decls...) 4588 stmts[end-1].Data = &clone 4589 } 4590 continue 4591 } 4592 } 4593 } 4594 4595 // Otherwise, append a normal statement 4596 didMergeWithPreviousLocal = false 4597 stmts[end] = stmt 4598 end++ 4599 } 4600 4601 return stmts[:end] 4602 } 4603 4604 type stmtList struct { 4605 // These statements come first, and can be inside the wrapper 4606 insideWrapperPrefix []js_ast.Stmt 4607 4608 // These statements come last, and can be inside the wrapper 4609 insideWrapperSuffix []js_ast.Stmt 4610 4611 outsideWrapperPrefix []js_ast.Stmt 4612 } 4613 4614 type compileResultJS struct { 4615 js_printer.PrintResult 4616 4617 sourceIndex uint32 4618 4619 // This is the line and column offset since the previous JavaScript string 4620 // or the start of the file if this is the first JavaScript string. 4621 generatedOffset sourcemap.LineColumnOffset 4622 } 4623 4624 func (c *linkerContext) requireOrImportMetaForSource(sourceIndex uint32) (meta js_printer.RequireOrImportMeta) { 4625 repr := c.graph.Files[sourceIndex].InputFile.Repr.(*graph.JSRepr) 4626 meta.WrapperRef = repr.AST.WrapperRef 4627 meta.IsWrapperAsync = repr.Meta.IsAsyncOrHasAsyncDependency 4628 if repr.Meta.Wrap == graph.WrapESM { 4629 meta.ExportsRef = repr.AST.ExportsRef 4630 } else { 4631 meta.ExportsRef = ast.InvalidRef 4632 } 4633 return 4634 } 4635 4636 func (c *linkerContext) generateCodeForFileInChunkJS( 4637 r renamer.Renamer, 4638 waitGroup *sync.WaitGroup, 4639 partRange partRange, 4640 toCommonJSRef ast.Ref, 4641 toESMRef ast.Ref, 4642 runtimeRequireRef ast.Ref, 4643 result *compileResultJS, 4644 dataForSourceMaps []bundler.DataForSourceMap, 4645 ) { 4646 defer c.recoverInternalError(waitGroup, partRange.sourceIndex) 4647 4648 file := &c.graph.Files[partRange.sourceIndex] 4649 repr := file.InputFile.Repr.(*graph.JSRepr) 4650 nsExportPartIndex := js_ast.NSExportPartIndex 4651 needsWrapper := false 4652 stmtList := stmtList{} 4653 4654 // The top-level directive must come first (the non-wrapped case is handled 4655 // by the chunk generation code, although only for the entry point) 4656 if repr.Meta.Wrap != graph.WrapNone && !file.IsEntryPoint() { 4657 for _, directive := range repr.AST.Directives { 4658 stmtList.insideWrapperPrefix = append(stmtList.insideWrapperPrefix, js_ast.Stmt{ 4659 Data: &js_ast.SDirective{Value: helpers.StringToUTF16(directive)}, 4660 }) 4661 } 4662 } 4663 4664 // Make sure the generated call to "__export(exports, ...)" comes first 4665 // before anything else. 4666 if nsExportPartIndex >= partRange.partIndexBegin && nsExportPartIndex < partRange.partIndexEnd && 4667 repr.AST.Parts[nsExportPartIndex].IsLive { 4668 c.convertStmtsForChunk(partRange.sourceIndex, &stmtList, repr.AST.Parts[nsExportPartIndex].Stmts) 4669 4670 // Move everything to the prefix list 4671 if repr.Meta.Wrap == graph.WrapESM { 4672 stmtList.outsideWrapperPrefix = append(stmtList.outsideWrapperPrefix, stmtList.insideWrapperSuffix...) 4673 } else { 4674 stmtList.insideWrapperPrefix = append(stmtList.insideWrapperPrefix, stmtList.insideWrapperSuffix...) 4675 } 4676 stmtList.insideWrapperSuffix = nil 4677 } 4678 4679 var partIndexForLazyDefaultExport ast.Index32 4680 if repr.AST.HasLazyExport { 4681 if defaultExport, ok := repr.Meta.ResolvedExports["default"]; ok { 4682 partIndexForLazyDefaultExport = ast.MakeIndex32(repr.TopLevelSymbolToParts(defaultExport.Ref)[0]) 4683 } 4684 } 4685 4686 // Add all other parts in this chunk 4687 for partIndex := partRange.partIndexBegin; partIndex < partRange.partIndexEnd; partIndex++ { 4688 part := repr.AST.Parts[partIndex] 4689 if !repr.AST.Parts[partIndex].IsLive { 4690 // Skip the part if it's not in this chunk 4691 continue 4692 } 4693 4694 if uint32(partIndex) == nsExportPartIndex { 4695 // Skip the generated call to "__export()" that was extracted above 4696 continue 4697 } 4698 4699 // Mark if we hit the dummy part representing the wrapper 4700 if uint32(partIndex) == repr.Meta.WrapperPartIndex.GetIndex() { 4701 needsWrapper = true 4702 continue 4703 } 4704 4705 stmts := part.Stmts 4706 4707 // If this could be a JSON file that exports a top-level object literal, go 4708 // over the non-default top-level properties that ended up being imported 4709 // and substitute references to them into the main top-level object literal. 4710 // So this JSON file: 4711 // 4712 // { 4713 // "foo": [1, 2, 3], 4714 // "bar": [4, 5, 6], 4715 // } 4716 // 4717 // is initially compiled into this: 4718 // 4719 // export var foo = [1, 2, 3]; 4720 // export var bar = [4, 5, 6]; 4721 // export default { 4722 // foo: [1, 2, 3], 4723 // bar: [4, 5, 6], 4724 // }; 4725 // 4726 // But we turn it into this if both "foo" and "default" are imported: 4727 // 4728 // export var foo = [1, 2, 3]; 4729 // export default { 4730 // foo, 4731 // bar: [4, 5, 6], 4732 // }; 4733 // 4734 if partIndexForLazyDefaultExport.IsValid() && partIndex == partIndexForLazyDefaultExport.GetIndex() { 4735 stmt := stmts[0] 4736 defaultExport := stmt.Data.(*js_ast.SExportDefault) 4737 defaultExpr := defaultExport.Value.Data.(*js_ast.SExpr) 4738 4739 // Be careful: the top-level value in a JSON file is not necessarily an object 4740 if object, ok := defaultExpr.Value.Data.(*js_ast.EObject); ok { 4741 objectClone := *object 4742 objectClone.Properties = append([]js_ast.Property{}, objectClone.Properties...) 4743 4744 // If any top-level properties ended up being imported directly, change 4745 // the property to just reference the corresponding variable instead 4746 for i, property := range object.Properties { 4747 if str, ok := property.Key.Data.(*js_ast.EString); ok { 4748 if name := helpers.UTF16ToString(str.Value); name != "default" { 4749 if export, ok := repr.Meta.ResolvedExports[name]; ok { 4750 if part := repr.AST.Parts[repr.TopLevelSymbolToParts(export.Ref)[0]]; part.IsLive { 4751 ref := part.Stmts[0].Data.(*js_ast.SLocal).Decls[0].Binding.Data.(*js_ast.BIdentifier).Ref 4752 objectClone.Properties[i].ValueOrNil = js_ast.Expr{Loc: property.Key.Loc, Data: &js_ast.EIdentifier{Ref: ref}} 4753 } 4754 } 4755 } 4756 } 4757 } 4758 4759 // Avoid mutating the original AST 4760 defaultExprClone := *defaultExpr 4761 defaultExprClone.Value.Data = &objectClone 4762 defaultExportClone := *defaultExport 4763 defaultExportClone.Value.Data = &defaultExprClone 4764 stmt.Data = &defaultExportClone 4765 stmts = []js_ast.Stmt{stmt} 4766 } 4767 } 4768 4769 c.convertStmtsForChunk(partRange.sourceIndex, &stmtList, stmts) 4770 } 4771 4772 // Hoist all import statements before any normal statements. ES6 imports 4773 // are different than CommonJS imports. All modules imported via ES6 import 4774 // statements are evaluated before the module doing the importing is 4775 // evaluated (well, except for cyclic import scenarios). We need to preserve 4776 // these semantics even when modules imported via ES6 import statements end 4777 // up being CommonJS modules. 4778 stmts := stmtList.insideWrapperSuffix 4779 if len(stmtList.insideWrapperPrefix) > 0 { 4780 stmts = append(stmtList.insideWrapperPrefix, stmts...) 4781 } 4782 if c.options.MinifySyntax { 4783 stmts = mergeAdjacentLocalStmts(stmts) 4784 } 4785 4786 // Optionally wrap all statements in a closure 4787 if needsWrapper { 4788 switch repr.Meta.Wrap { 4789 case graph.WrapCJS: 4790 // Only include the arguments that are actually used 4791 args := []js_ast.Arg{} 4792 if repr.AST.UsesExportsRef || repr.AST.UsesModuleRef { 4793 args = append(args, js_ast.Arg{Binding: js_ast.Binding{Data: &js_ast.BIdentifier{Ref: repr.AST.ExportsRef}}}) 4794 if repr.AST.UsesModuleRef { 4795 args = append(args, js_ast.Arg{Binding: js_ast.Binding{Data: &js_ast.BIdentifier{Ref: repr.AST.ModuleRef}}}) 4796 } 4797 } 4798 4799 var cjsArgs []js_ast.Expr 4800 if c.options.ProfilerNames { 4801 // "__commonJS({ 'file.js'(exports, module) { ... } })" 4802 kind := js_ast.PropertyField 4803 if !c.options.UnsupportedJSFeatures.Has(compat.ObjectExtensions) { 4804 kind = js_ast.PropertyMethod 4805 } 4806 cjsArgs = []js_ast.Expr{{Data: &js_ast.EObject{Properties: []js_ast.Property{{ 4807 Kind: kind, 4808 Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16(file.InputFile.Source.PrettyPath)}}, 4809 ValueOrNil: js_ast.Expr{Data: &js_ast.EFunction{Fn: js_ast.Fn{Args: args, Body: js_ast.FnBody{Block: js_ast.SBlock{Stmts: stmts}}}}}, 4810 }}}}} 4811 } else if c.options.UnsupportedJSFeatures.Has(compat.Arrow) { 4812 // "__commonJS(function (exports, module) { ... })" 4813 cjsArgs = []js_ast.Expr{{Data: &js_ast.EFunction{Fn: js_ast.Fn{Args: args, Body: js_ast.FnBody{Block: js_ast.SBlock{Stmts: stmts}}}}}} 4814 } else { 4815 // "__commonJS((exports, module) => { ... })" 4816 cjsArgs = []js_ast.Expr{{Data: &js_ast.EArrow{Args: args, Body: js_ast.FnBody{Block: js_ast.SBlock{Stmts: stmts}}}}} 4817 } 4818 value := js_ast.Expr{Data: &js_ast.ECall{ 4819 Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: c.cjsRuntimeRef}}, 4820 Args: cjsArgs, 4821 }} 4822 4823 // "var require_foo = __commonJS(...);" 4824 stmts = append(stmtList.outsideWrapperPrefix, js_ast.Stmt{Data: &js_ast.SLocal{ 4825 Decls: []js_ast.Decl{{ 4826 Binding: js_ast.Binding{Data: &js_ast.BIdentifier{Ref: repr.AST.WrapperRef}}, 4827 ValueOrNil: value, 4828 }}, 4829 }}) 4830 4831 case graph.WrapESM: 4832 // The wrapper only needs to be "async" if there is a transitive async 4833 // dependency. For correctness, we must not use "async" if the module 4834 // isn't async because then calling "require()" on that module would 4835 // swallow any exceptions thrown during module initialization. 4836 isAsync := repr.Meta.IsAsyncOrHasAsyncDependency 4837 4838 // Hoist all top-level "var" and "function" declarations out of the closure 4839 var decls []js_ast.Decl 4840 end := 0 4841 for _, stmt := range stmts { 4842 switch s := stmt.Data.(type) { 4843 case *js_ast.SLocal: 4844 // Convert the declarations to assignments 4845 wrapIdentifier := func(loc logger.Loc, ref ast.Ref) js_ast.Expr { 4846 decls = append(decls, js_ast.Decl{Binding: js_ast.Binding{Loc: loc, Data: &js_ast.BIdentifier{Ref: ref}}}) 4847 return js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}} 4848 } 4849 var value js_ast.Expr 4850 for _, decl := range s.Decls { 4851 binding := js_ast.ConvertBindingToExpr(decl.Binding, wrapIdentifier) 4852 if decl.ValueOrNil.Data != nil { 4853 value = js_ast.JoinWithComma(value, js_ast.Assign(binding, decl.ValueOrNil)) 4854 } 4855 } 4856 if value.Data == nil { 4857 continue 4858 } 4859 stmt = js_ast.Stmt{Loc: stmt.Loc, Data: &js_ast.SExpr{Value: value}} 4860 4861 case *js_ast.SFunction: 4862 stmtList.outsideWrapperPrefix = append(stmtList.outsideWrapperPrefix, stmt) 4863 continue 4864 } 4865 4866 stmts[end] = stmt 4867 end++ 4868 } 4869 stmts = stmts[:end] 4870 4871 var esmArgs []js_ast.Expr 4872 if c.options.ProfilerNames { 4873 // "__esm({ 'file.js'() { ... } })" 4874 kind := js_ast.PropertyField 4875 if !c.options.UnsupportedJSFeatures.Has(compat.ObjectExtensions) { 4876 kind = js_ast.PropertyMethod 4877 } 4878 esmArgs = []js_ast.Expr{{Data: &js_ast.EObject{Properties: []js_ast.Property{{ 4879 Kind: kind, 4880 Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16(file.InputFile.Source.PrettyPath)}}, 4881 ValueOrNil: js_ast.Expr{Data: &js_ast.EFunction{Fn: js_ast.Fn{Body: js_ast.FnBody{Block: js_ast.SBlock{Stmts: stmts}}, IsAsync: isAsync}}}, 4882 }}}}} 4883 } else if c.options.UnsupportedJSFeatures.Has(compat.Arrow) { 4884 // "__esm(function () { ... })" 4885 esmArgs = []js_ast.Expr{{Data: &js_ast.EFunction{Fn: js_ast.Fn{Body: js_ast.FnBody{Block: js_ast.SBlock{Stmts: stmts}}, IsAsync: isAsync}}}} 4886 } else { 4887 // "__esm(() => { ... })" 4888 esmArgs = []js_ast.Expr{{Data: &js_ast.EArrow{Body: js_ast.FnBody{Block: js_ast.SBlock{Stmts: stmts}}, IsAsync: isAsync}}} 4889 } 4890 value := js_ast.Expr{Data: &js_ast.ECall{ 4891 Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: c.esmRuntimeRef}}, 4892 Args: esmArgs, 4893 }} 4894 4895 // "var foo, bar;" 4896 if !c.options.MinifySyntax && len(decls) > 0 { 4897 stmtList.outsideWrapperPrefix = append(stmtList.outsideWrapperPrefix, js_ast.Stmt{Data: &js_ast.SLocal{ 4898 Decls: decls, 4899 }}) 4900 decls = nil 4901 } 4902 4903 // "var init_foo = __esm(...);" 4904 stmts = append(stmtList.outsideWrapperPrefix, js_ast.Stmt{Data: &js_ast.SLocal{ 4905 Decls: append(decls, js_ast.Decl{ 4906 Binding: js_ast.Binding{Data: &js_ast.BIdentifier{Ref: repr.AST.WrapperRef}}, 4907 ValueOrNil: value, 4908 }), 4909 }}) 4910 } 4911 } 4912 4913 // Only generate a source map if needed 4914 var addSourceMappings bool 4915 var inputSourceMap *sourcemap.SourceMap 4916 var lineOffsetTables []sourcemap.LineOffsetTable 4917 if file.InputFile.Loader.CanHaveSourceMap() && c.options.SourceMap != config.SourceMapNone { 4918 addSourceMappings = true 4919 inputSourceMap = file.InputFile.InputSourceMap 4920 lineOffsetTables = dataForSourceMaps[partRange.sourceIndex].LineOffsetTables 4921 } 4922 4923 // Indent the file if everything is wrapped in an IIFE 4924 indent := 0 4925 if c.options.OutputFormat == config.FormatIIFE { 4926 indent++ 4927 } 4928 4929 // Convert the AST to JavaScript code 4930 printOptions := js_printer.Options{ 4931 Indent: indent, 4932 OutputFormat: c.options.OutputFormat, 4933 MinifyIdentifiers: c.options.MinifyIdentifiers, 4934 MinifyWhitespace: c.options.MinifyWhitespace, 4935 MinifySyntax: c.options.MinifySyntax, 4936 LineLimit: c.options.LineLimit, 4937 ASCIIOnly: c.options.ASCIIOnly, 4938 ToCommonJSRef: toCommonJSRef, 4939 ToESMRef: toESMRef, 4940 RuntimeRequireRef: runtimeRequireRef, 4941 TSEnums: c.graph.TSEnums, 4942 ConstValues: c.graph.ConstValues, 4943 LegalComments: c.options.LegalComments, 4944 UnsupportedFeatures: c.options.UnsupportedJSFeatures, 4945 SourceMap: c.options.SourceMap, 4946 AddSourceMappings: addSourceMappings, 4947 InputSourceMap: inputSourceMap, 4948 LineOffsetTables: lineOffsetTables, 4949 RequireOrImportMetaForSource: c.requireOrImportMetaForSource, 4950 MangledProps: c.mangledProps, 4951 NeedsMetafile: c.options.NeedsMetafile, 4952 } 4953 tree := repr.AST 4954 tree.Directives = nil // This is handled elsewhere 4955 tree.Parts = []js_ast.Part{{Stmts: stmts}} 4956 *result = compileResultJS{ 4957 PrintResult: js_printer.Print(tree, c.graph.Symbols, r, printOptions), 4958 sourceIndex: partRange.sourceIndex, 4959 } 4960 4961 if file.InputFile.Loader == config.LoaderFile { 4962 result.JSONMetadataImports = append(result.JSONMetadataImports, fmt.Sprintf("\n {\n \"path\": %s,\n \"kind\": \"file-loader\"\n }", 4963 helpers.QuoteForJSON(file.InputFile.UniqueKeyForAdditionalFile, c.options.ASCIIOnly))) 4964 } 4965 4966 waitGroup.Done() 4967 } 4968 4969 func (c *linkerContext) generateEntryPointTailJS( 4970 r renamer.Renamer, 4971 toCommonJSRef ast.Ref, 4972 toESMRef ast.Ref, 4973 sourceIndex uint32, 4974 ) (result compileResultJS) { 4975 file := &c.graph.Files[sourceIndex] 4976 repr := file.InputFile.Repr.(*graph.JSRepr) 4977 var stmts []js_ast.Stmt 4978 4979 switch c.options.OutputFormat { 4980 case config.FormatPreserve: 4981 if repr.Meta.Wrap != graph.WrapNone { 4982 // "require_foo();" 4983 // "init_foo();" 4984 stmts = append(stmts, js_ast.Stmt{Data: &js_ast.SExpr{Value: js_ast.Expr{Data: &js_ast.ECall{ 4985 Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: repr.AST.WrapperRef}}, 4986 }}}}) 4987 } 4988 4989 case config.FormatIIFE: 4990 if repr.Meta.Wrap == graph.WrapCJS { 4991 if len(c.options.GlobalName) > 0 { 4992 // "return require_foo();" 4993 stmts = append(stmts, js_ast.Stmt{Data: &js_ast.SReturn{ValueOrNil: js_ast.Expr{Data: &js_ast.ECall{ 4994 Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: repr.AST.WrapperRef}}, 4995 }}}}) 4996 } else { 4997 // "require_foo();" 4998 stmts = append(stmts, js_ast.Stmt{Data: &js_ast.SExpr{Value: js_ast.Expr{Data: &js_ast.ECall{ 4999 Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: repr.AST.WrapperRef}}, 5000 }}}}) 5001 } 5002 } else { 5003 if repr.Meta.Wrap == graph.WrapESM { 5004 // "init_foo();" 5005 stmts = append(stmts, js_ast.Stmt{Data: &js_ast.SExpr{Value: js_ast.Expr{Data: &js_ast.ECall{ 5006 Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: repr.AST.WrapperRef}}, 5007 }}}}) 5008 } 5009 5010 if repr.Meta.ForceIncludeExportsForEntryPoint { 5011 // "return __toCommonJS(exports);" 5012 stmts = append(stmts, js_ast.Stmt{Data: &js_ast.SReturn{ 5013 ValueOrNil: js_ast.Expr{Data: &js_ast.ECall{ 5014 Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: toCommonJSRef}}, 5015 Args: []js_ast.Expr{{Data: &js_ast.EIdentifier{Ref: repr.AST.ExportsRef}}}, 5016 }}, 5017 }}) 5018 } 5019 } 5020 5021 case config.FormatCommonJS: 5022 if repr.Meta.Wrap == graph.WrapCJS { 5023 // "module.exports = require_foo();" 5024 stmts = append(stmts, js_ast.AssignStmt( 5025 js_ast.Expr{Data: &js_ast.EDot{ 5026 Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: c.unboundModuleRef}}, 5027 Name: "exports", 5028 }}, 5029 js_ast.Expr{Data: &js_ast.ECall{ 5030 Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: repr.AST.WrapperRef}}, 5031 }}, 5032 )) 5033 } else { 5034 if repr.Meta.Wrap == graph.WrapESM { 5035 // "init_foo();" 5036 stmts = append(stmts, js_ast.Stmt{Data: &js_ast.SExpr{Value: js_ast.Expr{Data: &js_ast.ECall{ 5037 Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: repr.AST.WrapperRef}}, 5038 }}}}) 5039 } 5040 } 5041 5042 // If we are generating CommonJS for node, encode the known export names in 5043 // a form that node can understand them. This relies on the specific behavior 5044 // of this parser, which the node project uses to detect named exports in 5045 // CommonJS files: https://github.com/guybedford/cjs-module-lexer. Think of 5046 // this code as an annotation for that parser. 5047 if c.options.Platform == config.PlatformNode { 5048 // Add a comment since otherwise people will surely wonder what this is. 5049 // This annotation means you can do this and have it work: 5050 // 5051 // import { name } from './file-from-esbuild.cjs' 5052 // 5053 // when "file-from-esbuild.cjs" looks like this: 5054 // 5055 // __export(exports, { name: () => name }); 5056 // 0 && (module.exports = {name}); 5057 // 5058 // The maintainer of "cjs-module-lexer" is receptive to adding esbuild- 5059 // friendly patterns to this library. However, this library has already 5060 // shipped in node and using existing patterns instead of defining new 5061 // patterns is maximally compatible. 5062 // 5063 // An alternative to doing this could be to use "Object.defineProperties" 5064 // instead of "__export" but support for that would need to be added to 5065 // "cjs-module-lexer" and then we would need to be ok with not supporting 5066 // older versions of node that don't have that newly-added support. 5067 5068 // "{a, b, if: null}" 5069 var moduleExports []js_ast.Property 5070 for _, export := range repr.Meta.SortedAndFilteredExportAliases { 5071 if export == "default" { 5072 // In node the default export is always "module.exports" regardless of 5073 // what the annotation says. So don't bother generating "default". 5074 continue 5075 } 5076 5077 // "{if: null}" 5078 var valueOrNil js_ast.Expr 5079 if _, ok := js_lexer.Keywords[export]; ok { 5080 // Make sure keywords don't cause a syntax error. This has to map to 5081 // "null" instead of something shorter like "0" because the library 5082 // "cjs-module-lexer" only supports identifiers in this position, and 5083 // it thinks "null" is an identifier. 5084 valueOrNil = js_ast.Expr{Data: js_ast.ENullShared} 5085 } 5086 5087 moduleExports = append(moduleExports, js_ast.Property{ 5088 Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16(export)}}, 5089 ValueOrNil: valueOrNil, 5090 }) 5091 } 5092 5093 // Add annotations for re-exports: "{...require('./foo')}" 5094 for _, importRecordIndex := range repr.AST.ExportStarImportRecords { 5095 if record := &repr.AST.ImportRecords[importRecordIndex]; !record.SourceIndex.IsValid() { 5096 moduleExports = append(moduleExports, js_ast.Property{ 5097 Kind: js_ast.PropertySpread, 5098 ValueOrNil: js_ast.Expr{Data: &js_ast.ERequireString{ImportRecordIndex: importRecordIndex}}, 5099 }) 5100 } 5101 } 5102 5103 if len(moduleExports) > 0 { 5104 // "0 && (module.exports = {a, b, if: null});" 5105 expr := js_ast.Expr{Data: &js_ast.EBinary{ 5106 Op: js_ast.BinOpLogicalAnd, 5107 Left: js_ast.Expr{Data: &js_ast.ENumber{Value: 0}}, 5108 Right: js_ast.Assign( 5109 js_ast.Expr{Data: &js_ast.EDot{ 5110 Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: c.unboundModuleRef}}, 5111 Name: "exports", 5112 }}, 5113 js_ast.Expr{Data: &js_ast.EObject{Properties: moduleExports}}, 5114 ), 5115 }} 5116 5117 if !c.options.MinifyWhitespace { 5118 stmts = append(stmts, 5119 js_ast.Stmt{Data: &js_ast.SComment{Text: `// Annotate the CommonJS export names for ESM import in node:`}}, 5120 ) 5121 } 5122 5123 stmts = append(stmts, js_ast.Stmt{Data: &js_ast.SExpr{Value: expr}}) 5124 } 5125 } 5126 5127 case config.FormatESModule: 5128 if repr.Meta.Wrap == graph.WrapCJS { 5129 // "export default require_foo();" 5130 stmts = append(stmts, js_ast.Stmt{ 5131 Data: &js_ast.SExportDefault{Value: js_ast.Stmt{ 5132 Data: &js_ast.SExpr{Value: js_ast.Expr{ 5133 Data: &js_ast.ECall{Target: js_ast.Expr{ 5134 Data: &js_ast.EIdentifier{Ref: repr.AST.WrapperRef}}}}}}}}) 5135 } else { 5136 if repr.Meta.Wrap == graph.WrapESM { 5137 if repr.Meta.IsAsyncOrHasAsyncDependency { 5138 // "await init_foo();" 5139 stmts = append(stmts, js_ast.Stmt{ 5140 Data: &js_ast.SExpr{Value: js_ast.Expr{ 5141 Data: &js_ast.EAwait{Value: js_ast.Expr{ 5142 Data: &js_ast.ECall{Target: js_ast.Expr{ 5143 Data: &js_ast.EIdentifier{Ref: repr.AST.WrapperRef}}}}}}}}) 5144 } else { 5145 // "init_foo();" 5146 stmts = append(stmts, js_ast.Stmt{ 5147 Data: &js_ast.SExpr{ 5148 Value: js_ast.Expr{Data: &js_ast.ECall{Target: js_ast.Expr{ 5149 Data: &js_ast.EIdentifier{Ref: repr.AST.WrapperRef}}}}}}) 5150 } 5151 } 5152 5153 if len(repr.Meta.SortedAndFilteredExportAliases) > 0 { 5154 // If the output format is ES6 modules and we're an entry point, generate an 5155 // ES6 export statement containing all exports. Except don't do that if this 5156 // entry point is a CommonJS-style module, since that would generate an ES6 5157 // export statement that's not top-level. Instead, we will export the CommonJS 5158 // exports as a default export later on. 5159 var items []js_ast.ClauseItem 5160 5161 for i, alias := range repr.Meta.SortedAndFilteredExportAliases { 5162 export := repr.Meta.ResolvedExports[alias] 5163 5164 // If this is an export of an import, reference the symbol that the import 5165 // was eventually resolved to. We need to do this because imports have 5166 // already been resolved by this point, so we can't generate a new import 5167 // and have that be resolved later. 5168 if importData, ok := c.graph.Files[export.SourceIndex].InputFile.Repr.(*graph.JSRepr).Meta.ImportsToBind[export.Ref]; ok { 5169 export.Ref = importData.Ref 5170 export.SourceIndex = importData.SourceIndex 5171 } 5172 5173 // Exports of imports need EImportIdentifier in case they need to be re- 5174 // written to a property access later on 5175 if c.graph.Symbols.Get(export.Ref).NamespaceAlias != nil { 5176 // Create both a local variable and an export clause for that variable. 5177 // The local variable is initialized with the initial value of the 5178 // export. This isn't fully correct because it's a "dead" binding and 5179 // doesn't update with the "live" value as it changes. But ES6 modules 5180 // don't have any syntax for bare named getter functions so this is the 5181 // best we can do. 5182 // 5183 // These input files: 5184 // 5185 // // entry_point.js 5186 // export {foo} from './cjs-format.js' 5187 // 5188 // // cjs-format.js 5189 // Object.defineProperty(exports, 'foo', { 5190 // enumerable: true, 5191 // get: () => Math.random(), 5192 // }) 5193 // 5194 // Become this output file: 5195 // 5196 // // cjs-format.js 5197 // var require_cjs_format = __commonJS((exports) => { 5198 // Object.defineProperty(exports, "foo", { 5199 // enumerable: true, 5200 // get: () => Math.random() 5201 // }); 5202 // }); 5203 // 5204 // // entry_point.js 5205 // var cjs_format = __toESM(require_cjs_format()); 5206 // var export_foo = cjs_format.foo; 5207 // export { 5208 // export_foo as foo 5209 // }; 5210 // 5211 tempRef := repr.Meta.CJSExportCopies[i] 5212 stmts = append(stmts, js_ast.Stmt{Data: &js_ast.SLocal{ 5213 Decls: []js_ast.Decl{{ 5214 Binding: js_ast.Binding{Data: &js_ast.BIdentifier{Ref: tempRef}}, 5215 ValueOrNil: js_ast.Expr{Data: &js_ast.EImportIdentifier{Ref: export.Ref}}, 5216 }}, 5217 }}) 5218 items = append(items, js_ast.ClauseItem{ 5219 Name: ast.LocRef{Ref: tempRef}, 5220 Alias: alias, 5221 }) 5222 } else { 5223 // Local identifiers can be exported using an export clause. This is done 5224 // this way instead of leaving the "export" keyword on the local declaration 5225 // itself both because it lets the local identifier be minified and because 5226 // it works transparently for re-exports across files. 5227 // 5228 // These input files: 5229 // 5230 // // entry_point.js 5231 // export * from './esm-format.js' 5232 // 5233 // // esm-format.js 5234 // export let foo = 123 5235 // 5236 // Become this output file: 5237 // 5238 // // esm-format.js 5239 // let foo = 123; 5240 // 5241 // // entry_point.js 5242 // export { 5243 // foo 5244 // }; 5245 // 5246 items = append(items, js_ast.ClauseItem{ 5247 Name: ast.LocRef{Ref: export.Ref}, 5248 Alias: alias, 5249 }) 5250 } 5251 } 5252 5253 stmts = append(stmts, js_ast.Stmt{Data: &js_ast.SExportClause{Items: items}}) 5254 } 5255 } 5256 } 5257 5258 if len(stmts) == 0 { 5259 return 5260 } 5261 5262 tree := repr.AST 5263 tree.Directives = nil 5264 tree.Parts = []js_ast.Part{{Stmts: stmts}} 5265 5266 // Indent the file if everything is wrapped in an IIFE 5267 indent := 0 5268 if c.options.OutputFormat == config.FormatIIFE { 5269 indent++ 5270 } 5271 5272 // Convert the AST to JavaScript code 5273 printOptions := js_printer.Options{ 5274 Indent: indent, 5275 OutputFormat: c.options.OutputFormat, 5276 MinifyIdentifiers: c.options.MinifyIdentifiers, 5277 MinifyWhitespace: c.options.MinifyWhitespace, 5278 MinifySyntax: c.options.MinifySyntax, 5279 LineLimit: c.options.LineLimit, 5280 ASCIIOnly: c.options.ASCIIOnly, 5281 ToCommonJSRef: toCommonJSRef, 5282 ToESMRef: toESMRef, 5283 LegalComments: c.options.LegalComments, 5284 UnsupportedFeatures: c.options.UnsupportedJSFeatures, 5285 RequireOrImportMetaForSource: c.requireOrImportMetaForSource, 5286 MangledProps: c.mangledProps, 5287 } 5288 result.PrintResult = js_printer.Print(tree, c.graph.Symbols, r, printOptions) 5289 return 5290 } 5291 5292 func (c *linkerContext) renameSymbolsInChunk(chunk *chunkInfo, filesInOrder []uint32, timer *helpers.Timer) renamer.Renamer { 5293 if c.options.MinifyIdentifiers { 5294 timer.Begin("Minify symbols") 5295 defer timer.End("Minify symbols") 5296 } else { 5297 timer.Begin("Rename symbols") 5298 defer timer.End("Rename symbols") 5299 } 5300 5301 // Determine the reserved names (e.g. can't generate the name "if") 5302 timer.Begin("Compute reserved names") 5303 moduleScopes := make([]*js_ast.Scope, len(filesInOrder)) 5304 for i, sourceIndex := range filesInOrder { 5305 moduleScopes[i] = c.graph.Files[sourceIndex].InputFile.Repr.(*graph.JSRepr).AST.ModuleScope 5306 } 5307 reservedNames := renamer.ComputeReservedNames(moduleScopes, c.graph.Symbols) 5308 5309 // Node contains code that scans CommonJS modules in an attempt to statically 5310 // detect the set of export names that a module will use. However, it doesn't 5311 // do any scope analysis so it can be fooled by local variables with the same 5312 // name as the CommonJS module-scope variables "exports" and "module". Avoid 5313 // using these names in this case even if there is not a risk of a name 5314 // collision because there is still a risk of node incorrectly detecting 5315 // something in a nested scope as an top-level export. Here's a case where 5316 // this happened: https://github.com/evanw/esbuild/issues/3544 5317 if c.options.OutputFormat == config.FormatCommonJS && c.options.Platform == config.PlatformNode { 5318 reservedNames["exports"] = 1 5319 reservedNames["module"] = 1 5320 } 5321 5322 // These are used to implement bundling, and need to be free for use 5323 if c.options.Mode != config.ModePassThrough { 5324 reservedNames["require"] = 1 5325 reservedNames["Promise"] = 1 5326 } 5327 timer.End("Compute reserved names") 5328 5329 // Make sure imports get a chance to be renamed too 5330 var sortedImportsFromOtherChunks stableRefArray 5331 for _, imports := range chunk.chunkRepr.(*chunkReprJS).importsFromOtherChunks { 5332 for _, item := range imports { 5333 sortedImportsFromOtherChunks = append(sortedImportsFromOtherChunks, stableRef{ 5334 StableSourceIndex: c.graph.StableSourceIndices[item.ref.SourceIndex], 5335 Ref: item.ref, 5336 }) 5337 } 5338 } 5339 sort.Sort(sortedImportsFromOtherChunks) 5340 5341 // Minification uses frequency analysis to give shorter names to more frequent symbols 5342 if c.options.MinifyIdentifiers { 5343 // Determine the first top-level slot (i.e. not in a nested scope) 5344 var firstTopLevelSlots ast.SlotCounts 5345 for _, sourceIndex := range filesInOrder { 5346 firstTopLevelSlots.UnionMax(c.graph.Files[sourceIndex].InputFile.Repr.(*graph.JSRepr).AST.NestedScopeSlotCounts) 5347 } 5348 r := renamer.NewMinifyRenamer(c.graph.Symbols, firstTopLevelSlots, reservedNames) 5349 5350 // Accumulate nested symbol usage counts 5351 timer.Begin("Accumulate symbol counts") 5352 timer.Begin("Parallel phase") 5353 allTopLevelSymbols := make([]renamer.StableSymbolCountArray, len(filesInOrder)) 5354 stableSourceIndices := c.graph.StableSourceIndices 5355 freq := ast.CharFreq{} 5356 waitGroup := sync.WaitGroup{} 5357 waitGroup.Add(len(filesInOrder)) 5358 for i, sourceIndex := range filesInOrder { 5359 repr := c.graph.Files[sourceIndex].InputFile.Repr.(*graph.JSRepr) 5360 5361 // Do this outside of the goroutine because it's not atomic 5362 if repr.AST.CharFreq != nil { 5363 freq.Include(repr.AST.CharFreq) 5364 } 5365 5366 go func(topLevelSymbols *renamer.StableSymbolCountArray, repr *graph.JSRepr) { 5367 if repr.AST.UsesExportsRef { 5368 r.AccumulateSymbolCount(topLevelSymbols, repr.AST.ExportsRef, 1, stableSourceIndices) 5369 } 5370 if repr.AST.UsesModuleRef { 5371 r.AccumulateSymbolCount(topLevelSymbols, repr.AST.ModuleRef, 1, stableSourceIndices) 5372 } 5373 5374 for partIndex, part := range repr.AST.Parts { 5375 if !repr.AST.Parts[partIndex].IsLive { 5376 // Skip the part if it's not in this chunk 5377 continue 5378 } 5379 5380 // Accumulate symbol use counts 5381 r.AccumulateSymbolUseCounts(topLevelSymbols, part.SymbolUses, stableSourceIndices) 5382 5383 // Make sure to also count the declaration in addition to the uses 5384 for _, declared := range part.DeclaredSymbols { 5385 r.AccumulateSymbolCount(topLevelSymbols, declared.Ref, 1, stableSourceIndices) 5386 } 5387 } 5388 5389 sort.Sort(topLevelSymbols) 5390 waitGroup.Done() 5391 }(&allTopLevelSymbols[i], repr) 5392 } 5393 waitGroup.Wait() 5394 timer.End("Parallel phase") 5395 5396 // Accumulate top-level symbol usage counts 5397 timer.Begin("Serial phase") 5398 capacity := len(sortedImportsFromOtherChunks) 5399 for _, array := range allTopLevelSymbols { 5400 capacity += len(array) 5401 } 5402 topLevelSymbols := make(renamer.StableSymbolCountArray, 0, capacity) 5403 for _, stable := range sortedImportsFromOtherChunks { 5404 r.AccumulateSymbolCount(&topLevelSymbols, stable.Ref, 1, stableSourceIndices) 5405 } 5406 for _, array := range allTopLevelSymbols { 5407 topLevelSymbols = append(topLevelSymbols, array...) 5408 } 5409 r.AllocateTopLevelSymbolSlots(topLevelSymbols) 5410 timer.End("Serial phase") 5411 timer.End("Accumulate symbol counts") 5412 5413 // Add all of the character frequency histograms for all files in this 5414 // chunk together, then use it to compute the character sequence used to 5415 // generate minified names. This results in slightly better gzip compression 5416 // over assigning minified names in order (i.e. "a b c ..."). Even though 5417 // it's a very small win, we still do it because it's simple to do and very 5418 // cheap to compute. 5419 minifier := ast.DefaultNameMinifierJS.ShuffleByCharFreq(freq) 5420 timer.Begin("Assign names by frequency") 5421 r.AssignNamesByFrequency(&minifier) 5422 timer.End("Assign names by frequency") 5423 return r 5424 } 5425 5426 // When we're not minifying, just append numbers to symbol names to avoid collisions 5427 r := renamer.NewNumberRenamer(c.graph.Symbols, reservedNames) 5428 nestedScopes := make(map[uint32][]*js_ast.Scope) 5429 5430 timer.Begin("Add top-level symbols") 5431 for _, stable := range sortedImportsFromOtherChunks { 5432 r.AddTopLevelSymbol(stable.Ref) 5433 } 5434 for _, sourceIndex := range filesInOrder { 5435 repr := c.graph.Files[sourceIndex].InputFile.Repr.(*graph.JSRepr) 5436 var scopes []*js_ast.Scope 5437 5438 // Modules wrapped in a CommonJS closure look like this: 5439 // 5440 // // foo.js 5441 // var require_foo = __commonJS((exports, module) => { 5442 // exports.foo = 123; 5443 // }); 5444 // 5445 // The symbol "require_foo" is stored in "file.ast.WrapperRef". We want 5446 // to be able to minify everything inside the closure without worrying 5447 // about collisions with other CommonJS modules. Set up the scopes such 5448 // that it appears as if the file was structured this way all along. It's 5449 // not completely accurate (e.g. we don't set the parent of the module 5450 // scope to this new top-level scope) but it's good enough for the 5451 // renaming code. 5452 if repr.Meta.Wrap == graph.WrapCJS { 5453 r.AddTopLevelSymbol(repr.AST.WrapperRef) 5454 5455 // External import statements will be hoisted outside of the CommonJS 5456 // wrapper if the output format supports import statements. We need to 5457 // add those symbols to the top-level scope to avoid causing name 5458 // collisions. This code special-cases only those symbols. 5459 if c.options.OutputFormat.KeepESMImportExportSyntax() { 5460 for _, part := range repr.AST.Parts { 5461 for _, stmt := range part.Stmts { 5462 switch s := stmt.Data.(type) { 5463 case *js_ast.SImport: 5464 if !repr.AST.ImportRecords[s.ImportRecordIndex].SourceIndex.IsValid() { 5465 r.AddTopLevelSymbol(s.NamespaceRef) 5466 if s.DefaultName != nil { 5467 r.AddTopLevelSymbol(s.DefaultName.Ref) 5468 } 5469 if s.Items != nil { 5470 for _, item := range *s.Items { 5471 r.AddTopLevelSymbol(item.Name.Ref) 5472 } 5473 } 5474 } 5475 5476 case *js_ast.SExportStar: 5477 if !repr.AST.ImportRecords[s.ImportRecordIndex].SourceIndex.IsValid() { 5478 r.AddTopLevelSymbol(s.NamespaceRef) 5479 } 5480 5481 case *js_ast.SExportFrom: 5482 if !repr.AST.ImportRecords[s.ImportRecordIndex].SourceIndex.IsValid() { 5483 r.AddTopLevelSymbol(s.NamespaceRef) 5484 for _, item := range s.Items { 5485 r.AddTopLevelSymbol(item.Name.Ref) 5486 } 5487 } 5488 } 5489 } 5490 } 5491 } 5492 5493 nestedScopes[sourceIndex] = []*js_ast.Scope{repr.AST.ModuleScope} 5494 continue 5495 } 5496 5497 // Modules wrapped in an ESM closure look like this: 5498 // 5499 // // foo.js 5500 // var foo, foo_exports = {}; 5501 // __export(foo_exports, { 5502 // foo: () => foo 5503 // }); 5504 // let init_foo = __esm(() => { 5505 // foo = 123; 5506 // }); 5507 // 5508 // The symbol "init_foo" is stored in "file.ast.WrapperRef". We need to 5509 // minify everything inside the closure without introducing a new scope 5510 // since all top-level variables will be hoisted outside of the closure. 5511 if repr.Meta.Wrap == graph.WrapESM { 5512 r.AddTopLevelSymbol(repr.AST.WrapperRef) 5513 } 5514 5515 // Rename each top-level symbol declaration in this chunk 5516 for partIndex, part := range repr.AST.Parts { 5517 if repr.AST.Parts[partIndex].IsLive { 5518 for _, declared := range part.DeclaredSymbols { 5519 if declared.IsTopLevel { 5520 r.AddTopLevelSymbol(declared.Ref) 5521 } 5522 } 5523 scopes = append(scopes, part.Scopes...) 5524 } 5525 } 5526 5527 nestedScopes[sourceIndex] = scopes 5528 } 5529 timer.End("Add top-level symbols") 5530 5531 // Recursively rename symbols in child scopes now that all top-level 5532 // symbols have been renamed. This is done in parallel because the symbols 5533 // inside nested scopes are independent and can't conflict. 5534 timer.Begin("Assign names by scope") 5535 r.AssignNamesByScope(nestedScopes) 5536 timer.End("Assign names by scope") 5537 return r 5538 } 5539 5540 func (c *linkerContext) generateChunkJS(chunkIndex int, chunkWaitGroup *sync.WaitGroup) { 5541 defer c.recoverInternalError(chunkWaitGroup, runtime.SourceIndex) 5542 5543 chunk := &c.chunks[chunkIndex] 5544 5545 timer := c.timer.Fork() 5546 if timer != nil { 5547 timeName := fmt.Sprintf("Generate chunk %q", path.Clean(config.TemplateToString(chunk.finalTemplate))) 5548 timer.Begin(timeName) 5549 defer c.timer.Join(timer) 5550 defer timer.End(timeName) 5551 } 5552 5553 chunkRepr := chunk.chunkRepr.(*chunkReprJS) 5554 compileResults := make([]compileResultJS, 0, len(chunkRepr.partsInChunkInOrder)) 5555 runtimeMembers := c.graph.Files[runtime.SourceIndex].InputFile.Repr.(*graph.JSRepr).AST.ModuleScope.Members 5556 toCommonJSRef := ast.FollowSymbols(c.graph.Symbols, runtimeMembers["__toCommonJS"].Ref) 5557 toESMRef := ast.FollowSymbols(c.graph.Symbols, runtimeMembers["__toESM"].Ref) 5558 runtimeRequireRef := ast.FollowSymbols(c.graph.Symbols, runtimeMembers["__require"].Ref) 5559 r := c.renameSymbolsInChunk(chunk, chunkRepr.filesInChunkInOrder, timer) 5560 dataForSourceMaps := c.dataForSourceMaps() 5561 5562 // Note: This contains placeholders instead of what the placeholders are 5563 // substituted with. That should be fine though because this should only 5564 // ever be used for figuring out how many "../" to add to a relative path 5565 // from a chunk whose final path hasn't been calculated yet to a chunk 5566 // whose final path has already been calculated. That and placeholders are 5567 // never substituted with something containing a "/" so substitution should 5568 // never change the "../" count. 5569 chunkAbsDir := c.fs.Dir(c.fs.Join(c.options.AbsOutputDir, config.TemplateToString(chunk.finalTemplate))) 5570 5571 // Generate JavaScript for each file in parallel 5572 timer.Begin("Print JavaScript files") 5573 waitGroup := sync.WaitGroup{} 5574 for _, partRange := range chunkRepr.partsInChunkInOrder { 5575 // Skip the runtime in test output 5576 if partRange.sourceIndex == runtime.SourceIndex && c.options.OmitRuntimeForTests { 5577 continue 5578 } 5579 5580 // Create a goroutine for this file 5581 compileResults = append(compileResults, compileResultJS{}) 5582 compileResult := &compileResults[len(compileResults)-1] 5583 waitGroup.Add(1) 5584 go c.generateCodeForFileInChunkJS( 5585 r, 5586 &waitGroup, 5587 partRange, 5588 toCommonJSRef, 5589 toESMRef, 5590 runtimeRequireRef, 5591 compileResult, 5592 dataForSourceMaps, 5593 ) 5594 } 5595 5596 // Also generate the cross-chunk binding code 5597 var crossChunkPrefix []byte 5598 var crossChunkSuffix []byte 5599 var jsonMetadataImports []string 5600 { 5601 // Indent the file if everything is wrapped in an IIFE 5602 indent := 0 5603 if c.options.OutputFormat == config.FormatIIFE { 5604 indent++ 5605 } 5606 printOptions := js_printer.Options{ 5607 Indent: indent, 5608 OutputFormat: c.options.OutputFormat, 5609 MinifyIdentifiers: c.options.MinifyIdentifiers, 5610 MinifyWhitespace: c.options.MinifyWhitespace, 5611 MinifySyntax: c.options.MinifySyntax, 5612 LineLimit: c.options.LineLimit, 5613 NeedsMetafile: c.options.NeedsMetafile, 5614 } 5615 crossChunkImportRecords := make([]ast.ImportRecord, len(chunk.crossChunkImports)) 5616 for i, chunkImport := range chunk.crossChunkImports { 5617 crossChunkImportRecords[i] = ast.ImportRecord{ 5618 Kind: chunkImport.importKind, 5619 Path: logger.Path{Text: c.chunks[chunkImport.chunkIndex].uniqueKey}, 5620 Flags: ast.ShouldNotBeExternalInMetafile | ast.ContainsUniqueKey, 5621 } 5622 } 5623 crossChunkResult := js_printer.Print(js_ast.AST{ 5624 ImportRecords: crossChunkImportRecords, 5625 Parts: []js_ast.Part{{Stmts: chunkRepr.crossChunkPrefixStmts}}, 5626 }, c.graph.Symbols, r, printOptions) 5627 crossChunkPrefix = crossChunkResult.JS 5628 jsonMetadataImports = crossChunkResult.JSONMetadataImports 5629 crossChunkSuffix = js_printer.Print(js_ast.AST{ 5630 Parts: []js_ast.Part{{Stmts: chunkRepr.crossChunkSuffixStmts}}, 5631 }, c.graph.Symbols, r, printOptions).JS 5632 } 5633 5634 // Generate the exports for the entry point, if there are any 5635 var entryPointTail compileResultJS 5636 if chunk.isEntryPoint { 5637 entryPointTail = c.generateEntryPointTailJS( 5638 r, 5639 toCommonJSRef, 5640 toESMRef, 5641 chunk.sourceIndex, 5642 ) 5643 } 5644 5645 waitGroup.Wait() 5646 timer.End("Print JavaScript files") 5647 timer.Begin("Join JavaScript files") 5648 5649 j := helpers.Joiner{} 5650 prevOffset := sourcemap.LineColumnOffset{} 5651 5652 // Optionally strip whitespace 5653 indent := "" 5654 space := " " 5655 newline := "\n" 5656 if c.options.MinifyWhitespace { 5657 space = "" 5658 newline = "" 5659 } 5660 newlineBeforeComment := false 5661 isExecutable := false 5662 5663 // Start with the hashbang if there is one. This must be done before the 5664 // banner because it only works if it's literally the first character. 5665 if chunk.isEntryPoint { 5666 if repr := c.graph.Files[chunk.sourceIndex].InputFile.Repr.(*graph.JSRepr); repr.AST.Hashbang != "" { 5667 hashbang := repr.AST.Hashbang + "\n" 5668 prevOffset.AdvanceString(hashbang) 5669 j.AddString(hashbang) 5670 newlineBeforeComment = true 5671 isExecutable = true 5672 } 5673 } 5674 5675 // Then emit the banner after the hashbang. This must come before the 5676 // "use strict" directive below because some people use the banner to 5677 // emit a hashbang, which must be the first thing in the file. 5678 if len(c.options.JSBanner) > 0 { 5679 prevOffset.AdvanceString(c.options.JSBanner) 5680 prevOffset.AdvanceString("\n") 5681 j.AddString(c.options.JSBanner) 5682 j.AddString("\n") 5683 newlineBeforeComment = true 5684 } 5685 5686 // Add the top-level directive if present (but omit "use strict" in ES 5687 // modules because all ES modules are automatically in strict mode) 5688 if chunk.isEntryPoint { 5689 repr := c.graph.Files[chunk.sourceIndex].InputFile.Repr.(*graph.JSRepr) 5690 for _, directive := range repr.AST.Directives { 5691 if directive != "use strict" || c.options.OutputFormat != config.FormatESModule { 5692 quoted := string(helpers.QuoteForJSON(directive, c.options.ASCIIOnly)) + ";" + newline 5693 prevOffset.AdvanceString(quoted) 5694 j.AddString(quoted) 5695 newlineBeforeComment = true 5696 } 5697 } 5698 } 5699 5700 // Optionally wrap with an IIFE 5701 if c.options.OutputFormat == config.FormatIIFE { 5702 var text string 5703 indent = " " 5704 if len(c.options.GlobalName) > 0 { 5705 text = c.generateGlobalNamePrefix() 5706 } 5707 if c.options.UnsupportedJSFeatures.Has(compat.Arrow) { 5708 text += "(function()" + space + "{" + newline 5709 } else { 5710 text += "(()" + space + "=>" + space + "{" + newline 5711 } 5712 prevOffset.AdvanceString(text) 5713 j.AddString(text) 5714 newlineBeforeComment = false 5715 } 5716 5717 // Put the cross-chunk prefix inside the IIFE 5718 if len(crossChunkPrefix) > 0 { 5719 newlineBeforeComment = true 5720 prevOffset.AdvanceBytes(crossChunkPrefix) 5721 j.AddBytes(crossChunkPrefix) 5722 } 5723 5724 // Start the metadata 5725 jMeta := helpers.Joiner{} 5726 if c.options.NeedsMetafile { 5727 // Print imports 5728 isFirstMeta := true 5729 jMeta.AddString("{\n \"imports\": [") 5730 for _, json := range jsonMetadataImports { 5731 if isFirstMeta { 5732 isFirstMeta = false 5733 } else { 5734 jMeta.AddString(",") 5735 } 5736 jMeta.AddString(json) 5737 } 5738 for _, compileResult := range compileResults { 5739 for _, json := range compileResult.JSONMetadataImports { 5740 if isFirstMeta { 5741 isFirstMeta = false 5742 } else { 5743 jMeta.AddString(",") 5744 } 5745 jMeta.AddString(json) 5746 } 5747 } 5748 if !isFirstMeta { 5749 jMeta.AddString("\n ") 5750 } 5751 5752 // Print exports 5753 jMeta.AddString("],\n \"exports\": [") 5754 var aliases []string 5755 if c.options.OutputFormat.KeepESMImportExportSyntax() { 5756 if chunk.isEntryPoint { 5757 if fileRepr := c.graph.Files[chunk.sourceIndex].InputFile.Repr.(*graph.JSRepr); fileRepr.Meta.Wrap == graph.WrapCJS { 5758 aliases = []string{"default"} 5759 } else { 5760 resolvedExports := fileRepr.Meta.ResolvedExports 5761 aliases = make([]string, 0, len(resolvedExports)) 5762 for alias := range resolvedExports { 5763 aliases = append(aliases, alias) 5764 } 5765 } 5766 } else { 5767 aliases = make([]string, 0, len(chunkRepr.exportsToOtherChunks)) 5768 for _, alias := range chunkRepr.exportsToOtherChunks { 5769 aliases = append(aliases, alias) 5770 } 5771 } 5772 } 5773 isFirstMeta = true 5774 sort.Strings(aliases) // Sort for determinism 5775 for _, alias := range aliases { 5776 if isFirstMeta { 5777 isFirstMeta = false 5778 } else { 5779 jMeta.AddString(",") 5780 } 5781 jMeta.AddString(fmt.Sprintf("\n %s", 5782 helpers.QuoteForJSON(alias, c.options.ASCIIOnly))) 5783 } 5784 if !isFirstMeta { 5785 jMeta.AddString("\n ") 5786 } 5787 jMeta.AddString("],\n") 5788 if chunk.isEntryPoint { 5789 entryPoint := c.graph.Files[chunk.sourceIndex].InputFile.Source.PrettyPath 5790 jMeta.AddString(fmt.Sprintf(" \"entryPoint\": %s,\n", helpers.QuoteForJSON(entryPoint, c.options.ASCIIOnly))) 5791 } 5792 if chunkRepr.hasCSSChunk { 5793 jMeta.AddString(fmt.Sprintf(" \"cssBundle\": %s,\n", helpers.QuoteForJSON(c.chunks[chunkRepr.cssChunkIndex].uniqueKey, c.options.ASCIIOnly))) 5794 } 5795 jMeta.AddString(" \"inputs\": {") 5796 } 5797 5798 // Concatenate the generated JavaScript chunks together 5799 var compileResultsForSourceMap []compileResultForSourceMap 5800 var legalCommentList []legalCommentEntry 5801 var metaOrder []uint32 5802 var metaBytes map[uint32][][]byte 5803 prevFileNameComment := uint32(0) 5804 if c.options.NeedsMetafile { 5805 metaOrder = make([]uint32, 0, len(compileResults)) 5806 metaBytes = make(map[uint32][][]byte, len(compileResults)) 5807 } 5808 for _, compileResult := range compileResults { 5809 if len(compileResult.ExtractedLegalComments) > 0 { 5810 legalCommentList = append(legalCommentList, legalCommentEntry{ 5811 sourceIndex: compileResult.sourceIndex, 5812 comments: compileResult.ExtractedLegalComments, 5813 }) 5814 } 5815 5816 // Add a comment with the file path before the file contents 5817 if c.options.Mode == config.ModeBundle && !c.options.MinifyWhitespace && 5818 prevFileNameComment != compileResult.sourceIndex && len(compileResult.JS) > 0 { 5819 if newlineBeforeComment { 5820 prevOffset.AdvanceString("\n") 5821 j.AddString("\n") 5822 } 5823 5824 path := c.graph.Files[compileResult.sourceIndex].InputFile.Source.PrettyPath 5825 5826 // Make sure newlines in the path can't cause a syntax error. This does 5827 // not minimize allocations because it's expected that this case never 5828 // comes up in practice. 5829 path = strings.ReplaceAll(path, "\r", "\\r") 5830 path = strings.ReplaceAll(path, "\n", "\\n") 5831 path = strings.ReplaceAll(path, "\u2028", "\\u2028") 5832 path = strings.ReplaceAll(path, "\u2029", "\\u2029") 5833 5834 text := fmt.Sprintf("%s// %s\n", indent, path) 5835 prevOffset.AdvanceString(text) 5836 j.AddString(text) 5837 prevFileNameComment = compileResult.sourceIndex 5838 } 5839 5840 // Don't include the runtime in source maps 5841 if c.graph.Files[compileResult.sourceIndex].InputFile.OmitFromSourceMapsAndMetafile { 5842 prevOffset.AdvanceString(string(compileResult.JS)) 5843 j.AddBytes(compileResult.JS) 5844 } else { 5845 // Save the offset to the start of the stored JavaScript 5846 compileResult.generatedOffset = prevOffset 5847 j.AddBytes(compileResult.JS) 5848 5849 // Ignore empty source map chunks 5850 if compileResult.SourceMapChunk.ShouldIgnore { 5851 prevOffset.AdvanceBytes(compileResult.JS) 5852 } else { 5853 prevOffset = sourcemap.LineColumnOffset{} 5854 5855 // Include this file in the source map 5856 if c.options.SourceMap != config.SourceMapNone { 5857 compileResultsForSourceMap = append(compileResultsForSourceMap, compileResultForSourceMap{ 5858 sourceMapChunk: compileResult.SourceMapChunk, 5859 generatedOffset: compileResult.generatedOffset, 5860 sourceIndex: compileResult.sourceIndex, 5861 }) 5862 } 5863 } 5864 5865 // Include this file in the metadata 5866 if c.options.NeedsMetafile { 5867 // Accumulate file sizes since a given file may be split into multiple parts 5868 bytes, ok := metaBytes[compileResult.sourceIndex] 5869 if !ok { 5870 metaOrder = append(metaOrder, compileResult.sourceIndex) 5871 } 5872 metaBytes[compileResult.sourceIndex] = append(bytes, compileResult.JS) 5873 } 5874 } 5875 5876 // Put a newline before the next file path comment 5877 if len(compileResult.JS) > 0 { 5878 newlineBeforeComment = true 5879 } 5880 } 5881 5882 // Stick the entry point tail at the end of the file. Deliberately don't 5883 // include any source mapping information for this because it's automatically 5884 // generated and doesn't correspond to a location in the input file. 5885 j.AddBytes(entryPointTail.JS) 5886 5887 // Put the cross-chunk suffix inside the IIFE 5888 if len(crossChunkSuffix) > 0 { 5889 if newlineBeforeComment { 5890 j.AddString(newline) 5891 } 5892 j.AddBytes(crossChunkSuffix) 5893 } 5894 5895 // Optionally wrap with an IIFE 5896 if c.options.OutputFormat == config.FormatIIFE { 5897 j.AddString("})();" + newline) 5898 } 5899 5900 // Make sure the file ends with a newline 5901 j.EnsureNewlineAtEnd() 5902 slashTag := "/script" 5903 if c.options.UnsupportedJSFeatures.Has(compat.InlineScript) { 5904 slashTag = "" 5905 } 5906 c.maybeAppendLegalComments(c.options.LegalComments, legalCommentList, chunk, &j, slashTag) 5907 5908 if len(c.options.JSFooter) > 0 { 5909 j.AddString(c.options.JSFooter) 5910 j.AddString("\n") 5911 } 5912 5913 // The JavaScript contents are done now that the source map comment is in 5914 chunk.intermediateOutput = c.breakJoinerIntoPieces(j) 5915 timer.End("Join JavaScript files") 5916 5917 if c.options.SourceMap != config.SourceMapNone { 5918 timer.Begin("Generate source map") 5919 canHaveShifts := chunk.intermediateOutput.pieces != nil 5920 chunk.outputSourceMap = c.generateSourceMapForChunk(compileResultsForSourceMap, chunkAbsDir, dataForSourceMaps, canHaveShifts) 5921 timer.End("Generate source map") 5922 } 5923 5924 // End the metadata lazily. The final output size is not known until the 5925 // final import paths are substituted into the output pieces generated below. 5926 if c.options.NeedsMetafile { 5927 pieces := make([][]intermediateOutput, len(metaOrder)) 5928 for i, sourceIndex := range metaOrder { 5929 slices := metaBytes[sourceIndex] 5930 outputs := make([]intermediateOutput, len(slices)) 5931 for j, slice := range slices { 5932 outputs[j] = c.breakOutputIntoPieces(slice) 5933 } 5934 pieces[i] = outputs 5935 } 5936 chunk.jsonMetadataChunkCallback = func(finalOutputSize int) helpers.Joiner { 5937 finalRelDir := c.fs.Dir(chunk.finalRelPath) 5938 for i, sourceIndex := range metaOrder { 5939 if i > 0 { 5940 jMeta.AddString(",") 5941 } 5942 count := 0 5943 for _, output := range pieces[i] { 5944 count += c.accurateFinalByteCount(output, finalRelDir) 5945 } 5946 jMeta.AddString(fmt.Sprintf("\n %s: {\n \"bytesInOutput\": %d\n %s}", 5947 helpers.QuoteForJSON(c.graph.Files[sourceIndex].InputFile.Source.PrettyPath, c.options.ASCIIOnly), 5948 count, c.generateExtraDataForFileJS(sourceIndex))) 5949 } 5950 if len(metaOrder) > 0 { 5951 jMeta.AddString("\n ") 5952 } 5953 jMeta.AddString(fmt.Sprintf("},\n \"bytes\": %d\n }", finalOutputSize)) 5954 return jMeta 5955 } 5956 } 5957 5958 c.generateIsolatedHashInParallel(chunk) 5959 chunk.isExecutable = isExecutable 5960 chunkWaitGroup.Done() 5961 } 5962 5963 func (c *linkerContext) generateGlobalNamePrefix() string { 5964 var text string 5965 globalName := c.options.GlobalName 5966 prefix := globalName[0] 5967 space := " " 5968 join := ";\n" 5969 5970 if c.options.MinifyWhitespace { 5971 space = "" 5972 join = ";" 5973 } 5974 5975 // Use "||=" to make the code more compact when it's supported 5976 if len(globalName) > 1 && !c.options.UnsupportedJSFeatures.Has(compat.LogicalAssignment) { 5977 if js_printer.CanEscapeIdentifier(prefix, c.options.UnsupportedJSFeatures, c.options.ASCIIOnly) { 5978 if c.options.ASCIIOnly { 5979 prefix = string(js_printer.QuoteIdentifier(nil, prefix, c.options.UnsupportedJSFeatures)) 5980 } 5981 text = fmt.Sprintf("var %s%s", prefix, join) 5982 } else { 5983 prefix = fmt.Sprintf("this[%s]", helpers.QuoteForJSON(prefix, c.options.ASCIIOnly)) 5984 } 5985 for _, name := range globalName[1:] { 5986 var dotOrIndex string 5987 if js_printer.CanEscapeIdentifier(name, c.options.UnsupportedJSFeatures, c.options.ASCIIOnly) { 5988 if c.options.ASCIIOnly { 5989 name = string(js_printer.QuoteIdentifier(nil, name, c.options.UnsupportedJSFeatures)) 5990 } 5991 dotOrIndex = fmt.Sprintf(".%s", name) 5992 } else { 5993 dotOrIndex = fmt.Sprintf("[%s]", helpers.QuoteForJSON(name, c.options.ASCIIOnly)) 5994 } 5995 prefix = fmt.Sprintf("(%s%s||=%s{})%s", prefix, space, space, dotOrIndex) 5996 } 5997 return fmt.Sprintf("%s%s%s=%s", text, prefix, space, space) 5998 } 5999 6000 if js_printer.CanEscapeIdentifier(prefix, c.options.UnsupportedJSFeatures, c.options.ASCIIOnly) { 6001 if c.options.ASCIIOnly { 6002 prefix = string(js_printer.QuoteIdentifier(nil, prefix, c.options.UnsupportedJSFeatures)) 6003 } 6004 text = fmt.Sprintf("var %s%s=%s", prefix, space, space) 6005 } else { 6006 prefix = fmt.Sprintf("this[%s]", helpers.QuoteForJSON(prefix, c.options.ASCIIOnly)) 6007 text = fmt.Sprintf("%s%s=%s", prefix, space, space) 6008 } 6009 6010 for _, name := range globalName[1:] { 6011 oldPrefix := prefix 6012 if js_printer.CanEscapeIdentifier(name, c.options.UnsupportedJSFeatures, c.options.ASCIIOnly) { 6013 if c.options.ASCIIOnly { 6014 name = string(js_printer.QuoteIdentifier(nil, name, c.options.UnsupportedJSFeatures)) 6015 } 6016 prefix = fmt.Sprintf("%s.%s", prefix, name) 6017 } else { 6018 prefix = fmt.Sprintf("%s[%s]", prefix, helpers.QuoteForJSON(name, c.options.ASCIIOnly)) 6019 } 6020 text += fmt.Sprintf("%s%s||%s{}%s%s%s=%s", oldPrefix, space, space, join, prefix, space, space) 6021 } 6022 6023 return text 6024 } 6025 6026 type compileResultCSS struct { 6027 css_printer.PrintResult 6028 6029 // This is the line and column offset since the previous CSS string 6030 // or the start of the file if this is the first CSS string. 6031 generatedOffset sourcemap.LineColumnOffset 6032 6033 // The source index can be invalid for short snippets that aren't necessarily 6034 // tied to any one file and/or that don't really need source mappings. The 6035 // source index is really only valid for the compile result that contains the 6036 // main contents of a file, which we try to only ever write out once. 6037 sourceIndex ast.Index32 6038 hasCharset bool 6039 } 6040 6041 func (c *linkerContext) generateChunkCSS(chunkIndex int, chunkWaitGroup *sync.WaitGroup) { 6042 defer c.recoverInternalError(chunkWaitGroup, runtime.SourceIndex) 6043 6044 chunk := &c.chunks[chunkIndex] 6045 6046 timer := c.timer.Fork() 6047 if timer != nil { 6048 timeName := fmt.Sprintf("Generate chunk %q", path.Clean(config.TemplateToString(chunk.finalTemplate))) 6049 timer.Begin(timeName) 6050 defer c.timer.Join(timer) 6051 defer timer.End(timeName) 6052 } 6053 6054 chunkRepr := chunk.chunkRepr.(*chunkReprCSS) 6055 compileResults := make([]compileResultCSS, len(chunkRepr.importsInChunkInOrder)) 6056 dataForSourceMaps := c.dataForSourceMaps() 6057 6058 // Note: This contains placeholders instead of what the placeholders are 6059 // substituted with. That should be fine though because this should only 6060 // ever be used for figuring out how many "../" to add to a relative path 6061 // from a chunk whose final path hasn't been calculated yet to a chunk 6062 // whose final path has already been calculated. That and placeholders are 6063 // never substituted with something containing a "/" so substitution should 6064 // never change the "../" count. 6065 chunkAbsDir := c.fs.Dir(c.fs.Join(c.options.AbsOutputDir, config.TemplateToString(chunk.finalTemplate))) 6066 6067 // Remove duplicate rules across files. This must be done in serial, not 6068 // in parallel, and must be done from the last rule to the first rule. 6069 timer.Begin("Prepare CSS ASTs") 6070 asts := make([]css_ast.AST, len(chunkRepr.importsInChunkInOrder)) 6071 var remover css_parser.DuplicateRuleRemover 6072 if c.options.MinifySyntax { 6073 remover = css_parser.MakeDuplicateRuleMangler(c.graph.Symbols) 6074 } 6075 for i := len(chunkRepr.importsInChunkInOrder) - 1; i >= 0; i-- { 6076 entry := chunkRepr.importsInChunkInOrder[i] 6077 switch entry.kind { 6078 case cssImportLayers: 6079 var rules []css_ast.Rule 6080 if len(entry.layers) > 0 { 6081 rules = append(rules, css_ast.Rule{Data: &css_ast.RAtLayer{Names: entry.layers}}) 6082 } 6083 rules, importRecords := wrapRulesWithConditions(rules, nil, entry.conditions, entry.conditionImportRecords) 6084 asts[i] = css_ast.AST{Rules: rules, ImportRecords: importRecords} 6085 6086 case cssImportExternalPath: 6087 var conditions *css_ast.ImportConditions 6088 if len(entry.conditions) > 0 { 6089 conditions = &entry.conditions[0] 6090 6091 // Handling a chain of nested conditions is complicated. We can't 6092 // necessarily join them together because a) there may be multiple 6093 // layer names and b) layer names are only supposed to be inserted 6094 // into the layer order if the parent conditions are applied. 6095 // 6096 // Instead we handle them by preserving the "@import" nesting using 6097 // imports of data URL stylesheets. This may seem strange but I think 6098 // this is the only way to do this in CSS. 6099 for i := len(entry.conditions) - 1; i > 0; i-- { 6100 astImport := css_ast.AST{ 6101 Rules: []css_ast.Rule{{Data: &css_ast.RAtImport{ 6102 ImportRecordIndex: uint32(len(entry.conditionImportRecords)), 6103 ImportConditions: &entry.conditions[i], 6104 }}}, 6105 ImportRecords: append(entry.conditionImportRecords, ast.ImportRecord{ 6106 Kind: ast.ImportAt, 6107 Path: entry.externalPath, 6108 }), 6109 } 6110 astResult := css_printer.Print(astImport, c.graph.Symbols, css_printer.Options{ 6111 MinifyWhitespace: c.options.MinifyWhitespace, 6112 ASCIIOnly: c.options.ASCIIOnly, 6113 }) 6114 entry.externalPath = logger.Path{Text: helpers.EncodeStringAsShortestDataURL("text/css", string(bytes.TrimSpace(astResult.CSS)))} 6115 } 6116 } 6117 asts[i] = css_ast.AST{ 6118 ImportRecords: append(append([]ast.ImportRecord{}, entry.conditionImportRecords...), ast.ImportRecord{ 6119 Kind: ast.ImportAt, 6120 Path: entry.externalPath, 6121 }), 6122 Rules: []css_ast.Rule{{Data: &css_ast.RAtImport{ 6123 ImportRecordIndex: uint32(len(entry.conditionImportRecords)), 6124 ImportConditions: conditions, 6125 }}}, 6126 } 6127 6128 case cssImportSourceIndex: 6129 file := &c.graph.Files[entry.sourceIndex] 6130 ast := file.InputFile.Repr.(*graph.CSSRepr).AST 6131 6132 // Filter out "@charset", "@import", and leading "@layer" rules 6133 rules := make([]css_ast.Rule, 0, len(ast.Rules)) 6134 didFindAtImport := false 6135 didFindAtLayer := false 6136 for _, rule := range ast.Rules { 6137 switch rule.Data.(type) { 6138 case *css_ast.RAtCharset: 6139 compileResults[i].hasCharset = true 6140 continue 6141 case *css_ast.RAtLayer: 6142 didFindAtLayer = true 6143 case *css_ast.RAtImport: 6144 if !didFindAtImport { 6145 didFindAtImport = true 6146 if didFindAtLayer { 6147 // Filter out the pre-import layers once we see the first 6148 // "@import". These layers are special-cased by the linker 6149 // and already appear as a separate entry in import order. 6150 end := 0 6151 for _, rule := range rules { 6152 if _, ok := rule.Data.(*css_ast.RAtLayer); !ok { 6153 rules[end] = rule 6154 end++ 6155 } 6156 } 6157 rules = rules[:end] 6158 } 6159 } 6160 continue 6161 } 6162 rules = append(rules, rule) 6163 } 6164 6165 rules, ast.ImportRecords = wrapRulesWithConditions(rules, ast.ImportRecords, entry.conditions, entry.conditionImportRecords) 6166 6167 // Remove top-level duplicate rules across files 6168 if c.options.MinifySyntax { 6169 rules = remover.RemoveDuplicateRulesInPlace(entry.sourceIndex, rules, ast.ImportRecords) 6170 } 6171 6172 ast.Rules = rules 6173 asts[i] = ast 6174 } 6175 } 6176 timer.End("Prepare CSS ASTs") 6177 6178 // Generate CSS for each file in parallel 6179 timer.Begin("Print CSS files") 6180 waitGroup := sync.WaitGroup{} 6181 for i, entry := range chunkRepr.importsInChunkInOrder { 6182 // Create a goroutine for this file 6183 waitGroup.Add(1) 6184 go func(i int, entry cssImportOrder, compileResult *compileResultCSS) { 6185 cssOptions := css_printer.Options{ 6186 MinifyWhitespace: c.options.MinifyWhitespace, 6187 LineLimit: c.options.LineLimit, 6188 ASCIIOnly: c.options.ASCIIOnly, 6189 LegalComments: c.options.LegalComments, 6190 SourceMap: c.options.SourceMap, 6191 UnsupportedFeatures: c.options.UnsupportedCSSFeatures, 6192 NeedsMetafile: c.options.NeedsMetafile, 6193 LocalNames: c.mangledProps, 6194 } 6195 6196 if entry.kind == cssImportSourceIndex { 6197 defer c.recoverInternalError(&waitGroup, entry.sourceIndex) 6198 file := &c.graph.Files[entry.sourceIndex] 6199 6200 // Only generate a source map if needed 6201 if file.InputFile.Loader.CanHaveSourceMap() && c.options.SourceMap != config.SourceMapNone { 6202 cssOptions.AddSourceMappings = true 6203 cssOptions.InputSourceMap = file.InputFile.InputSourceMap 6204 cssOptions.LineOffsetTables = dataForSourceMaps[entry.sourceIndex].LineOffsetTables 6205 } 6206 6207 cssOptions.InputSourceIndex = entry.sourceIndex 6208 compileResult.sourceIndex = ast.MakeIndex32(entry.sourceIndex) 6209 } 6210 6211 compileResult.PrintResult = css_printer.Print(asts[i], c.graph.Symbols, cssOptions) 6212 waitGroup.Done() 6213 }(i, entry, &compileResults[i]) 6214 } 6215 6216 waitGroup.Wait() 6217 timer.End("Print CSS files") 6218 timer.Begin("Join CSS files") 6219 j := helpers.Joiner{} 6220 prevOffset := sourcemap.LineColumnOffset{} 6221 newlineBeforeComment := false 6222 6223 if len(c.options.CSSBanner) > 0 { 6224 prevOffset.AdvanceString(c.options.CSSBanner) 6225 j.AddString(c.options.CSSBanner) 6226 prevOffset.AdvanceString("\n") 6227 j.AddString("\n") 6228 } 6229 6230 // Generate any prefix rules now 6231 var jsonMetadataImports []string 6232 { 6233 tree := css_ast.AST{} 6234 6235 // "@charset" is the only thing that comes before "@import" 6236 for _, compileResult := range compileResults { 6237 if compileResult.hasCharset { 6238 tree.Rules = append(tree.Rules, css_ast.Rule{Data: &css_ast.RAtCharset{Encoding: "UTF-8"}}) 6239 break 6240 } 6241 } 6242 6243 if len(tree.Rules) > 0 { 6244 result := css_printer.Print(tree, c.graph.Symbols, css_printer.Options{ 6245 MinifyWhitespace: c.options.MinifyWhitespace, 6246 LineLimit: c.options.LineLimit, 6247 ASCIIOnly: c.options.ASCIIOnly, 6248 NeedsMetafile: c.options.NeedsMetafile, 6249 }) 6250 jsonMetadataImports = result.JSONMetadataImports 6251 if len(result.CSS) > 0 { 6252 prevOffset.AdvanceBytes(result.CSS) 6253 j.AddBytes(result.CSS) 6254 newlineBeforeComment = true 6255 } 6256 } 6257 } 6258 6259 // Start the metadata 6260 jMeta := helpers.Joiner{} 6261 if c.options.NeedsMetafile { 6262 isFirstMeta := true 6263 jMeta.AddString("{\n \"imports\": [") 6264 for _, json := range jsonMetadataImports { 6265 if isFirstMeta { 6266 isFirstMeta = false 6267 } else { 6268 jMeta.AddString(",") 6269 } 6270 jMeta.AddString(json) 6271 } 6272 for _, compileResult := range compileResults { 6273 for _, json := range compileResult.JSONMetadataImports { 6274 if isFirstMeta { 6275 isFirstMeta = false 6276 } else { 6277 jMeta.AddString(",") 6278 } 6279 jMeta.AddString(json) 6280 } 6281 } 6282 if !isFirstMeta { 6283 jMeta.AddString("\n ") 6284 } 6285 if chunk.isEntryPoint { 6286 file := &c.graph.Files[chunk.sourceIndex] 6287 6288 // Do not generate "entryPoint" for CSS files that are the result of 6289 // importing CSS into JavaScript. We want this to be a 1:1 relationship 6290 // and there is already an output file for the JavaScript entry point. 6291 if _, ok := file.InputFile.Repr.(*graph.CSSRepr); ok { 6292 jMeta.AddString(fmt.Sprintf("],\n \"entryPoint\": %s,\n \"inputs\": {", 6293 helpers.QuoteForJSON(file.InputFile.Source.PrettyPath, c.options.ASCIIOnly))) 6294 } else { 6295 jMeta.AddString("],\n \"inputs\": {") 6296 } 6297 } else { 6298 jMeta.AddString("],\n \"inputs\": {") 6299 } 6300 } 6301 6302 // Concatenate the generated CSS chunks together 6303 var compileResultsForSourceMap []compileResultForSourceMap 6304 var legalCommentList []legalCommentEntry 6305 for _, compileResult := range compileResults { 6306 if len(compileResult.ExtractedLegalComments) > 0 && compileResult.sourceIndex.IsValid() { 6307 legalCommentList = append(legalCommentList, legalCommentEntry{ 6308 sourceIndex: compileResult.sourceIndex.GetIndex(), 6309 comments: compileResult.ExtractedLegalComments, 6310 }) 6311 } 6312 6313 if c.options.Mode == config.ModeBundle && !c.options.MinifyWhitespace && compileResult.sourceIndex.IsValid() { 6314 var newline string 6315 if newlineBeforeComment { 6316 newline = "\n" 6317 } 6318 comment := fmt.Sprintf("%s/* %s */\n", newline, c.graph.Files[compileResult.sourceIndex.GetIndex()].InputFile.Source.PrettyPath) 6319 prevOffset.AdvanceString(comment) 6320 j.AddString(comment) 6321 } 6322 if len(compileResult.CSS) > 0 { 6323 newlineBeforeComment = true 6324 } 6325 6326 // Save the offset to the start of the stored JavaScript 6327 compileResult.generatedOffset = prevOffset 6328 j.AddBytes(compileResult.CSS) 6329 6330 // Ignore empty source map chunks 6331 if compileResult.SourceMapChunk.ShouldIgnore { 6332 prevOffset.AdvanceBytes(compileResult.CSS) 6333 } else { 6334 prevOffset = sourcemap.LineColumnOffset{} 6335 6336 // Include this file in the source map 6337 if c.options.SourceMap != config.SourceMapNone && compileResult.sourceIndex.IsValid() { 6338 compileResultsForSourceMap = append(compileResultsForSourceMap, compileResultForSourceMap{ 6339 sourceMapChunk: compileResult.SourceMapChunk, 6340 generatedOffset: compileResult.generatedOffset, 6341 sourceIndex: compileResult.sourceIndex.GetIndex(), 6342 }) 6343 } 6344 } 6345 } 6346 6347 // Make sure the file ends with a newline 6348 j.EnsureNewlineAtEnd() 6349 slashTag := "/style" 6350 if c.options.UnsupportedCSSFeatures.Has(compat.InlineStyle) { 6351 slashTag = "" 6352 } 6353 c.maybeAppendLegalComments(c.options.LegalComments, legalCommentList, chunk, &j, slashTag) 6354 6355 if len(c.options.CSSFooter) > 0 { 6356 j.AddString(c.options.CSSFooter) 6357 j.AddString("\n") 6358 } 6359 6360 // The CSS contents are done now that the source map comment is in 6361 chunk.intermediateOutput = c.breakJoinerIntoPieces(j) 6362 timer.End("Join CSS files") 6363 6364 if c.options.SourceMap != config.SourceMapNone { 6365 timer.Begin("Generate source map") 6366 canHaveShifts := chunk.intermediateOutput.pieces != nil 6367 chunk.outputSourceMap = c.generateSourceMapForChunk(compileResultsForSourceMap, chunkAbsDir, dataForSourceMaps, canHaveShifts) 6368 timer.End("Generate source map") 6369 } 6370 6371 // End the metadata lazily. The final output size is not known until the 6372 // final import paths are substituted into the output pieces generated below. 6373 if c.options.NeedsMetafile { 6374 pieces := make([]intermediateOutput, len(compileResults)) 6375 for i, compileResult := range compileResults { 6376 pieces[i] = c.breakOutputIntoPieces(compileResult.CSS) 6377 } 6378 chunk.jsonMetadataChunkCallback = func(finalOutputSize int) helpers.Joiner { 6379 finalRelDir := c.fs.Dir(chunk.finalRelPath) 6380 isFirst := true 6381 for i, compileResult := range compileResults { 6382 if !compileResult.sourceIndex.IsValid() { 6383 continue 6384 } 6385 if isFirst { 6386 isFirst = false 6387 } else { 6388 jMeta.AddString(",") 6389 } 6390 jMeta.AddString(fmt.Sprintf("\n %s: {\n \"bytesInOutput\": %d\n }", 6391 helpers.QuoteForJSON(c.graph.Files[compileResult.sourceIndex.GetIndex()].InputFile.Source.PrettyPath, c.options.ASCIIOnly), 6392 c.accurateFinalByteCount(pieces[i], finalRelDir))) 6393 } 6394 if len(compileResults) > 0 { 6395 jMeta.AddString("\n ") 6396 } 6397 jMeta.AddString(fmt.Sprintf("},\n \"bytes\": %d\n }", finalOutputSize)) 6398 return jMeta 6399 } 6400 } 6401 6402 c.generateIsolatedHashInParallel(chunk) 6403 chunkWaitGroup.Done() 6404 } 6405 6406 func wrapRulesWithConditions( 6407 rules []css_ast.Rule, importRecords []ast.ImportRecord, 6408 conditions []css_ast.ImportConditions, conditionImportRecords []ast.ImportRecord, 6409 ) ([]css_ast.Rule, []ast.ImportRecord) { 6410 for i := len(conditions) - 1; i >= 0; i-- { 6411 item := conditions[i] 6412 6413 // Generate "@layer" wrappers. Note that empty "@layer" rules still have 6414 // a side effect (they set the layer order) so they cannot be removed. 6415 for _, t := range item.Layers { 6416 if len(rules) == 0 { 6417 if t.Children == nil { 6418 // Omit an empty "@layer {}" entirely 6419 continue 6420 } else { 6421 // Generate "@layer foo;" instead of "@layer foo {}" 6422 rules = nil 6423 } 6424 } 6425 var prelude []css_ast.Token 6426 if t.Children != nil { 6427 prelude = *t.Children 6428 } 6429 prelude, importRecords = css_ast.CloneTokensWithImportRecords(prelude, conditionImportRecords, nil, importRecords) 6430 rules = []css_ast.Rule{{Data: &css_ast.RKnownAt{ 6431 AtToken: "layer", 6432 Prelude: prelude, 6433 Rules: rules, 6434 }}} 6435 } 6436 6437 // Generate "@supports" wrappers. This is not done if the rule block is 6438 // empty because empty "@supports" rules have no effect. 6439 if len(rules) > 0 { 6440 for _, t := range item.Supports { 6441 t.Kind = css_lexer.TOpenParen 6442 t.Text = "(" 6443 var prelude []css_ast.Token 6444 prelude, importRecords = css_ast.CloneTokensWithImportRecords([]css_ast.Token{t}, conditionImportRecords, nil, importRecords) 6445 rules = []css_ast.Rule{{Data: &css_ast.RKnownAt{ 6446 AtToken: "supports", 6447 Prelude: prelude, 6448 Rules: rules, 6449 }}} 6450 } 6451 } 6452 6453 // Generate "@media" wrappers. This is not done if the rule block is 6454 // empty because empty "@media" rules have no effect. 6455 if len(rules) > 0 && len(item.Media) > 0 { 6456 var prelude []css_ast.Token 6457 prelude, importRecords = css_ast.CloneTokensWithImportRecords(item.Media, conditionImportRecords, nil, importRecords) 6458 rules = []css_ast.Rule{{Data: &css_ast.RKnownAt{ 6459 AtToken: "media", 6460 Prelude: prelude, 6461 Rules: rules, 6462 }}} 6463 } 6464 } 6465 6466 return rules, importRecords 6467 } 6468 6469 type legalCommentEntry struct { 6470 sourceIndex uint32 6471 comments []string 6472 } 6473 6474 // Add all unique legal comments to the end of the file. These are 6475 // deduplicated because some projects have thousands of files with the same 6476 // comment. The comment must be preserved in the output for legal reasons but 6477 // at the same time we want to generate a small bundle when minifying. 6478 func (c *linkerContext) maybeAppendLegalComments( 6479 legalComments config.LegalComments, 6480 legalCommentList []legalCommentEntry, 6481 chunk *chunkInfo, 6482 j *helpers.Joiner, 6483 slashTag string, 6484 ) { 6485 switch legalComments { 6486 case config.LegalCommentsNone, config.LegalCommentsInline: 6487 return 6488 } 6489 6490 type thirdPartyEntry struct { 6491 packagePath string 6492 comments []string 6493 } 6494 6495 var uniqueFirstPartyComments []string 6496 var thirdPartyComments []thirdPartyEntry 6497 hasFirstPartyComment := make(map[string]struct{}) 6498 6499 for _, entry := range legalCommentList { 6500 source := c.graph.Files[entry.sourceIndex].InputFile.Source 6501 packagePath := "" 6502 6503 // Try to extract a package name from the source path. If we can find a 6504 // "node_modules" path component in the path, then assume this is a legal 6505 // comment in third-party code and that everything after "node_modules" is 6506 // the package name and subpath. If we can't, then assume this is a legal 6507 // comment in first-party code. 6508 // 6509 // The rationale for this behavior: If we just include third-party comments 6510 // as-is and the third-party comments don't say what package they're from 6511 // (which isn't uncommon), then it'll look like that comment applies to 6512 // all code in the file which is very wrong. So we need to somehow say 6513 // where the comment comes from. But we don't want to say where every 6514 // comment comes from because people probably won't appreciate this for 6515 // first-party comments. And we don't want to include the whole path to 6516 // each third-part module because a) that could contain information about 6517 // the local machine that people don't want in their bundle and b) that 6518 // could differ depending on unimportant details like the package manager 6519 // used to install the packages (npm vs. pnpm vs. yarn). 6520 if source.KeyPath.Namespace != "dataurl" { 6521 path := source.KeyPath.Text 6522 previous := len(path) 6523 for previous > 0 { 6524 slash := strings.LastIndexAny(path[:previous], "\\/") 6525 component := path[slash+1 : previous] 6526 if component == "node_modules" { 6527 if previous < len(path) { 6528 packagePath = strings.ReplaceAll(path[previous+1:], "\\", "/") 6529 } 6530 break 6531 } 6532 previous = slash 6533 } 6534 } 6535 6536 if packagePath != "" { 6537 thirdPartyComments = append(thirdPartyComments, thirdPartyEntry{ 6538 packagePath: packagePath, 6539 comments: entry.comments, 6540 }) 6541 } else { 6542 for _, comment := range entry.comments { 6543 if _, ok := hasFirstPartyComment[comment]; !ok { 6544 hasFirstPartyComment[comment] = struct{}{} 6545 uniqueFirstPartyComments = append(uniqueFirstPartyComments, comment) 6546 } 6547 } 6548 } 6549 } 6550 6551 switch legalComments { 6552 case config.LegalCommentsEndOfFile: 6553 for _, comment := range uniqueFirstPartyComments { 6554 j.AddString(helpers.EscapeClosingTag(comment, slashTag)) 6555 j.AddString("\n") 6556 } 6557 6558 if len(thirdPartyComments) > 0 { 6559 j.AddString("/*! Bundled license information:\n") 6560 for _, entry := range thirdPartyComments { 6561 j.AddString(fmt.Sprintf("\n%s:\n", helpers.EscapeClosingTag(entry.packagePath, slashTag))) 6562 for _, comment := range entry.comments { 6563 comment = helpers.EscapeClosingTag(comment, slashTag) 6564 if strings.HasPrefix(comment, "//") { 6565 j.AddString(fmt.Sprintf(" (*%s *)\n", comment[2:])) 6566 } else if strings.HasPrefix(comment, "/*") && strings.HasSuffix(comment, "*/") { 6567 j.AddString(fmt.Sprintf(" (%s)\n", strings.ReplaceAll(comment[1:len(comment)-1], "\n", "\n "))) 6568 } 6569 } 6570 } 6571 j.AddString("*/\n") 6572 } 6573 6574 case config.LegalCommentsLinkedWithComment, config.LegalCommentsExternalWithoutComment: 6575 var jComments helpers.Joiner 6576 6577 for _, comment := range uniqueFirstPartyComments { 6578 jComments.AddString(comment) 6579 jComments.AddString("\n") 6580 } 6581 6582 if len(thirdPartyComments) > 0 { 6583 if len(uniqueFirstPartyComments) > 0 { 6584 jComments.AddString("\n") 6585 } 6586 jComments.AddString("Bundled license information:\n") 6587 for _, entry := range thirdPartyComments { 6588 jComments.AddString(fmt.Sprintf("\n%s:\n", entry.packagePath)) 6589 for _, comment := range entry.comments { 6590 jComments.AddString(fmt.Sprintf(" %s\n", strings.ReplaceAll(comment, "\n", "\n "))) 6591 } 6592 } 6593 } 6594 6595 chunk.externalLegalComments = jComments.Done() 6596 } 6597 } 6598 6599 func (c *linkerContext) appendIsolatedHashesForImportedChunks( 6600 hash hash.Hash, 6601 chunkIndex uint32, 6602 visited []uint32, 6603 visitedKey uint32, 6604 ) { 6605 // Only visit each chunk at most once. This is important because there may be 6606 // cycles in the chunk import graph. If there's a cycle, we want to include 6607 // the hash of every chunk involved in the cycle (along with all of their 6608 // dependencies). This depth-first traversal will naturally do that. 6609 if visited[chunkIndex] == visitedKey { 6610 return 6611 } 6612 visited[chunkIndex] = visitedKey 6613 chunk := &c.chunks[chunkIndex] 6614 6615 // Visit the other chunks that this chunk imports before visiting this chunk 6616 for _, chunkImport := range chunk.crossChunkImports { 6617 c.appendIsolatedHashesForImportedChunks(hash, chunkImport.chunkIndex, visited, visitedKey) 6618 } 6619 6620 // Mix in hashes for referenced asset paths (i.e. the "file" loader) 6621 for _, piece := range chunk.intermediateOutput.pieces { 6622 if piece.kind == outputPieceAssetIndex { 6623 file := c.graph.Files[piece.index] 6624 if len(file.InputFile.AdditionalFiles) != 1 { 6625 panic("Internal error") 6626 } 6627 relPath, _ := c.fs.Rel(c.options.AbsOutputDir, file.InputFile.AdditionalFiles[0].AbsPath) 6628 6629 // Make sure to always use forward slashes, even on Windows 6630 relPath = strings.ReplaceAll(relPath, "\\", "/") 6631 6632 // Mix in the hash for the relative path, which ends up as a JS string 6633 hashWriteLengthPrefixed(hash, []byte(relPath)) 6634 } 6635 } 6636 6637 // Mix in the hash for this chunk 6638 hash.Write(chunk.waitForIsolatedHash()) 6639 } 6640 6641 func (c *linkerContext) breakJoinerIntoPieces(j helpers.Joiner) intermediateOutput { 6642 // Optimization: If there can be no substitutions, just reuse the initial 6643 // joiner that was used when generating the intermediate chunk output 6644 // instead of creating another one and copying the whole file into it. 6645 if !j.Contains(c.uniqueKeyPrefix, c.uniqueKeyPrefixBytes) { 6646 return intermediateOutput{joiner: j} 6647 } 6648 return c.breakOutputIntoPieces(j.Done()) 6649 } 6650 6651 func (c *linkerContext) breakOutputIntoPieces(output []byte) intermediateOutput { 6652 var pieces []outputPiece 6653 prefix := c.uniqueKeyPrefixBytes 6654 for { 6655 // Scan for the next piece boundary 6656 boundary := bytes.Index(output, prefix) 6657 6658 // Try to parse the piece boundary 6659 var kind outputPieceIndexKind 6660 var index uint32 6661 if boundary != -1 { 6662 if start := boundary + len(prefix); start+9 > len(output) { 6663 boundary = -1 6664 } else { 6665 switch output[start] { 6666 case 'A': 6667 kind = outputPieceAssetIndex 6668 case 'C': 6669 kind = outputPieceChunkIndex 6670 } 6671 for j := 1; j < 9; j++ { 6672 c := output[start+j] 6673 if c < '0' || c > '9' { 6674 boundary = -1 6675 break 6676 } 6677 index = index*10 + uint32(c) - '0' 6678 } 6679 } 6680 } 6681 6682 // Validate the boundary 6683 switch kind { 6684 case outputPieceAssetIndex: 6685 if index >= uint32(len(c.graph.Files)) { 6686 boundary = -1 6687 } 6688 6689 case outputPieceChunkIndex: 6690 if index >= uint32(len(c.chunks)) { 6691 boundary = -1 6692 } 6693 6694 default: 6695 boundary = -1 6696 } 6697 6698 // If we're at the end, generate one final piece 6699 if boundary == -1 { 6700 pieces = append(pieces, outputPiece{ 6701 data: output, 6702 }) 6703 break 6704 } 6705 6706 // Otherwise, generate an interior piece and continue 6707 pieces = append(pieces, outputPiece{ 6708 data: output[:boundary], 6709 index: index, 6710 kind: kind, 6711 }) 6712 output = output[boundary+len(prefix)+9:] 6713 } 6714 return intermediateOutput{pieces: pieces} 6715 } 6716 6717 func (c *linkerContext) generateIsolatedHashInParallel(chunk *chunkInfo) { 6718 // Compute the hash in parallel. This is a speedup when it turns out the hash 6719 // isn't needed (well, as long as there are threads to spare). 6720 channel := make(chan []byte, 1) 6721 chunk.waitForIsolatedHash = func() []byte { 6722 data := <-channel 6723 channel <- data 6724 return data 6725 } 6726 go c.generateIsolatedHash(chunk, channel) 6727 } 6728 6729 func (c *linkerContext) generateIsolatedHash(chunk *chunkInfo, channel chan []byte) { 6730 hash := xxhash.New() 6731 6732 // Mix the file names and part ranges of all of the files in this chunk into 6733 // the hash. Objects that appear identical but that live in separate files or 6734 // that live in separate parts in the same file must not be merged. This only 6735 // needs to be done for JavaScript files, not CSS files. 6736 if chunkRepr, ok := chunk.chunkRepr.(*chunkReprJS); ok { 6737 for _, partRange := range chunkRepr.partsInChunkInOrder { 6738 var filePath string 6739 file := &c.graph.Files[partRange.sourceIndex] 6740 if file.InputFile.Source.KeyPath.Namespace == "file" { 6741 // Use the pretty path as the file name since it should be platform- 6742 // independent (relative paths and the "/" path separator) 6743 filePath = file.InputFile.Source.PrettyPath 6744 } else { 6745 // If this isn't in the "file" namespace, just use the full path text 6746 // verbatim. This could be a source of cross-platform differences if 6747 // plugins are storing platform-specific information in here, but then 6748 // that problem isn't caused by esbuild itself. 6749 filePath = file.InputFile.Source.KeyPath.Text 6750 } 6751 6752 // Include the path namespace in the hash 6753 hashWriteLengthPrefixed(hash, []byte(file.InputFile.Source.KeyPath.Namespace)) 6754 6755 // Then include the file path 6756 hashWriteLengthPrefixed(hash, []byte(filePath)) 6757 6758 // Also write the part range. These numbers are deterministic and allocated 6759 // per-file so this should be a well-behaved base for a hash. 6760 hashWriteUint32(hash, partRange.partIndexBegin) 6761 hashWriteUint32(hash, partRange.partIndexEnd) 6762 } 6763 } 6764 6765 // Hash the output path template as part of the content hash because we want 6766 // any import to be considered different if the import's output path has changed. 6767 for _, part := range chunk.finalTemplate { 6768 hashWriteLengthPrefixed(hash, []byte(part.Data)) 6769 } 6770 6771 // Also hash the public path. If provided, this is used whenever files 6772 // reference each other such as cross-chunk imports, asset file references, 6773 // and source map comments. We always include the hash in all chunks instead 6774 // of trying to figure out which chunks will include the public path for 6775 // simplicity and for robustness to code changes in the future. 6776 if c.options.PublicPath != "" { 6777 hashWriteLengthPrefixed(hash, []byte(c.options.PublicPath)) 6778 } 6779 6780 // Include the generated output content in the hash. This excludes the 6781 // randomly-generated import paths (the unique keys) and only includes the 6782 // data in the spans between them. 6783 if chunk.intermediateOutput.pieces != nil { 6784 for _, piece := range chunk.intermediateOutput.pieces { 6785 hashWriteLengthPrefixed(hash, piece.data) 6786 } 6787 } else { 6788 bytes := chunk.intermediateOutput.joiner.Done() 6789 hashWriteLengthPrefixed(hash, bytes) 6790 } 6791 6792 // Also include the source map data in the hash. The source map is named the 6793 // same name as the chunk name for ease of discovery. So we want the hash to 6794 // change if the source map data changes even if the chunk data doesn't change. 6795 // Otherwise the output path for the source map wouldn't change and the source 6796 // map wouldn't end up being updated. 6797 // 6798 // Note that this means the contents of all input files are included in the 6799 // hash because of "sourcesContent", so changing a comment in an input file 6800 // can now change the hash of the output file. This only happens when you 6801 // have source maps enabled (and "sourcesContent", which is on by default). 6802 // 6803 // The generated positions in the mappings here are in the output content 6804 // *before* the final paths have been substituted. This may seem weird. 6805 // However, I think this shouldn't cause issues because a) the unique key 6806 // values are all always the same length so the offsets are deterministic 6807 // and b) the final paths will be folded into the final hash later. 6808 hashWriteLengthPrefixed(hash, chunk.outputSourceMap.Prefix) 6809 hashWriteLengthPrefixed(hash, chunk.outputSourceMap.Mappings) 6810 hashWriteLengthPrefixed(hash, chunk.outputSourceMap.Suffix) 6811 6812 // Store the hash so far. All other chunks that import this chunk will mix 6813 // this hash into their final hash to ensure that the import path changes 6814 // if this chunk (or any dependencies of this chunk) is changed. 6815 channel <- hash.Sum(nil) 6816 } 6817 6818 func hashWriteUint32(hash hash.Hash, value uint32) { 6819 var lengthBytes [4]byte 6820 binary.LittleEndian.PutUint32(lengthBytes[:], value) 6821 hash.Write(lengthBytes[:]) 6822 } 6823 6824 // Hash the data in length-prefixed form because boundary locations are 6825 // important. We don't want "a" + "bc" to hash the same as "ab" + "c". 6826 func hashWriteLengthPrefixed(hash hash.Hash, bytes []byte) { 6827 hashWriteUint32(hash, uint32(len(bytes))) 6828 hash.Write(bytes) 6829 } 6830 6831 // Marking a symbol as unbound prevents it from being renamed or minified. 6832 // This is only used when a module is compiled independently. We use a very 6833 // different way of handling exports and renaming/minifying when bundling. 6834 func (c *linkerContext) preventExportsFromBeingRenamed(sourceIndex uint32) { 6835 repr, ok := c.graph.Files[sourceIndex].InputFile.Repr.(*graph.JSRepr) 6836 if !ok { 6837 return 6838 } 6839 hasImportOrExport := false 6840 6841 for _, part := range repr.AST.Parts { 6842 for _, stmt := range part.Stmts { 6843 switch s := stmt.Data.(type) { 6844 case *js_ast.SImport: 6845 // Ignore imports from internal (i.e. non-external) code. Since this 6846 // function is only called when we're not bundling, these imports are 6847 // all for files that were generated automatically and aren't part of 6848 // the original source code (e.g. the runtime or an injected file). 6849 // We shouldn't consider the file a module if the only ESM imports or 6850 // exports are automatically generated ones. 6851 if repr.AST.ImportRecords[s.ImportRecordIndex].SourceIndex.IsValid() { 6852 continue 6853 } 6854 6855 hasImportOrExport = true 6856 6857 case *js_ast.SLocal: 6858 if s.IsExport { 6859 js_ast.ForEachIdentifierBindingInDecls(s.Decls, func(loc logger.Loc, b *js_ast.BIdentifier) { 6860 c.graph.Symbols.Get(b.Ref).Flags |= ast.MustNotBeRenamed 6861 }) 6862 hasImportOrExport = true 6863 } 6864 6865 case *js_ast.SFunction: 6866 if s.IsExport { 6867 c.graph.Symbols.Get(s.Fn.Name.Ref).Kind = ast.SymbolUnbound 6868 hasImportOrExport = true 6869 } 6870 6871 case *js_ast.SClass: 6872 if s.IsExport { 6873 c.graph.Symbols.Get(s.Class.Name.Ref).Kind = ast.SymbolUnbound 6874 hasImportOrExport = true 6875 } 6876 6877 case *js_ast.SExportClause, *js_ast.SExportDefault, *js_ast.SExportStar: 6878 hasImportOrExport = true 6879 6880 case *js_ast.SExportFrom: 6881 hasImportOrExport = true 6882 } 6883 } 6884 } 6885 6886 // Heuristic: If this module has top-level import or export statements, we 6887 // consider this an ES6 module and only preserve the names of the exported 6888 // symbols. Everything else is minified since the names are private. 6889 // 6890 // Otherwise, we consider this potentially a script-type file instead of an 6891 // ES6 module. In that case, preserve the names of all top-level symbols 6892 // since they are all potentially exported (e.g. if this is used in a 6893 // <script> tag). All symbols in nested scopes are still minified. 6894 if !hasImportOrExport { 6895 for _, member := range repr.AST.ModuleScope.Members { 6896 c.graph.Symbols.Get(member.Ref).Flags |= ast.MustNotBeRenamed 6897 } 6898 } 6899 } 6900 6901 type compileResultForSourceMap struct { 6902 sourceMapChunk sourcemap.Chunk 6903 generatedOffset sourcemap.LineColumnOffset 6904 sourceIndex uint32 6905 } 6906 6907 func (c *linkerContext) generateSourceMapForChunk( 6908 results []compileResultForSourceMap, 6909 chunkAbsDir string, 6910 dataForSourceMaps []bundler.DataForSourceMap, 6911 canHaveShifts bool, 6912 ) (pieces sourcemap.SourceMapPieces) { 6913 j := helpers.Joiner{} 6914 j.AddString("{\n \"version\": 3") 6915 6916 // Only write out the sources for a given source index once 6917 sourceIndexToSourcesIndex := make(map[uint32]int) 6918 6919 // Generate the "sources" and "sourcesContent" arrays 6920 type item struct { 6921 path logger.Path 6922 prettyPath string 6923 quotedContents []byte 6924 } 6925 items := make([]item, 0, len(results)) 6926 nextSourcesIndex := 0 6927 for _, result := range results { 6928 if _, ok := sourceIndexToSourcesIndex[result.sourceIndex]; ok { 6929 continue 6930 } 6931 sourceIndexToSourcesIndex[result.sourceIndex] = nextSourcesIndex 6932 file := &c.graph.Files[result.sourceIndex] 6933 6934 // Simple case: no nested source map 6935 if file.InputFile.InputSourceMap == nil { 6936 var quotedContents []byte 6937 if !c.options.ExcludeSourcesContent { 6938 quotedContents = dataForSourceMaps[result.sourceIndex].QuotedContents[0] 6939 } 6940 items = append(items, item{ 6941 path: file.InputFile.Source.KeyPath, 6942 prettyPath: file.InputFile.Source.PrettyPath, 6943 quotedContents: quotedContents, 6944 }) 6945 nextSourcesIndex++ 6946 continue 6947 } 6948 6949 // Complex case: nested source map 6950 sm := file.InputFile.InputSourceMap 6951 for i, source := range sm.Sources { 6952 path := logger.Path{ 6953 Namespace: file.InputFile.Source.KeyPath.Namespace, 6954 Text: source, 6955 } 6956 6957 // If this file is in the "file" namespace, change the relative path in 6958 // the source map into an absolute path using the directory of this file 6959 if path.Namespace == "file" { 6960 path.Text = c.fs.Join(c.fs.Dir(file.InputFile.Source.KeyPath.Text), source) 6961 } 6962 6963 var quotedContents []byte 6964 if !c.options.ExcludeSourcesContent { 6965 quotedContents = dataForSourceMaps[result.sourceIndex].QuotedContents[i] 6966 } 6967 items = append(items, item{ 6968 path: path, 6969 prettyPath: source, 6970 quotedContents: quotedContents, 6971 }) 6972 } 6973 nextSourcesIndex += len(sm.Sources) 6974 } 6975 6976 // Write the sources 6977 j.AddString(",\n \"sources\": [") 6978 for i, item := range items { 6979 if i != 0 { 6980 j.AddString(", ") 6981 } 6982 6983 // Modify the absolute path to the original file to be relative to the 6984 // directory that will contain the output file for this chunk 6985 if item.path.Namespace == "file" { 6986 if relPath, ok := c.fs.Rel(chunkAbsDir, item.path.Text); ok { 6987 // Make sure to always use forward slashes, even on Windows 6988 item.prettyPath = strings.ReplaceAll(relPath, "\\", "/") 6989 } 6990 } 6991 6992 j.AddBytes(helpers.QuoteForJSON(item.prettyPath, c.options.ASCIIOnly)) 6993 } 6994 j.AddString("]") 6995 6996 if c.options.SourceRoot != "" { 6997 j.AddString(",\n \"sourceRoot\": ") 6998 j.AddBytes(helpers.QuoteForJSON(c.options.SourceRoot, c.options.ASCIIOnly)) 6999 } 7000 7001 // Write the sourcesContent 7002 if !c.options.ExcludeSourcesContent { 7003 j.AddString(",\n \"sourcesContent\": [") 7004 for i, item := range items { 7005 if i != 0 { 7006 j.AddString(", ") 7007 } 7008 j.AddBytes(item.quotedContents) 7009 } 7010 j.AddString("]") 7011 } 7012 7013 j.AddString(",\n \"mappings\": \"") 7014 7015 // Write the mappings 7016 mappingsStart := j.Length() 7017 prevEndState := sourcemap.SourceMapState{} 7018 prevColumnOffset := 0 7019 totalQuotedNameLen := 0 7020 for _, result := range results { 7021 chunk := result.sourceMapChunk 7022 offset := result.generatedOffset 7023 sourcesIndex := sourceIndexToSourcesIndex[result.sourceIndex] 7024 7025 // This should have already been checked earlier 7026 if chunk.ShouldIgnore { 7027 panic("Internal error") 7028 } 7029 7030 // Because each file for the bundle is converted to a source map once, 7031 // the source maps are shared between all entry points in the bundle. 7032 // The easiest way of getting this to work is to have all source maps 7033 // generate as if their source index is 0. We then adjust the source 7034 // index per entry point by modifying the first source mapping. This 7035 // is done by AppendSourceMapChunk() using the source index passed 7036 // here. 7037 startState := sourcemap.SourceMapState{ 7038 SourceIndex: sourcesIndex, 7039 GeneratedLine: offset.Lines, 7040 GeneratedColumn: offset.Columns, 7041 OriginalName: totalQuotedNameLen, 7042 } 7043 if offset.Lines == 0 { 7044 startState.GeneratedColumn += prevColumnOffset 7045 } 7046 7047 // Append the precomputed source map chunk 7048 sourcemap.AppendSourceMapChunk(&j, prevEndState, startState, chunk.Buffer) 7049 7050 // Generate the relative offset to start from next time 7051 prevOriginalName := prevEndState.OriginalName 7052 prevEndState = chunk.EndState 7053 prevEndState.SourceIndex += sourcesIndex 7054 if chunk.Buffer.FirstNameOffset.IsValid() { 7055 prevEndState.OriginalName += totalQuotedNameLen 7056 } else { 7057 // It's possible for a chunk to have mappings but for none of those 7058 // mappings to have an associated name. The name is optional and is 7059 // omitted when the mapping is for a non-name token or if the final 7060 // and original names are the same. In that case we need to restore 7061 // the previous original name end state since it wasn't modified after 7062 // all. If we don't do this, then files after this will adjust their 7063 // name offsets assuming that the previous generated mapping has this 7064 // file's offset, which is wrong. 7065 prevEndState.OriginalName = prevOriginalName 7066 } 7067 prevColumnOffset = chunk.FinalGeneratedColumn 7068 totalQuotedNameLen += len(chunk.QuotedNames) 7069 7070 // If this was all one line, include the column offset from the start 7071 if prevEndState.GeneratedLine == 0 { 7072 prevEndState.GeneratedColumn += startState.GeneratedColumn 7073 prevColumnOffset += startState.GeneratedColumn 7074 } 7075 } 7076 mappingsEnd := j.Length() 7077 7078 // Write the names 7079 isFirstName := true 7080 j.AddString("\",\n \"names\": [") 7081 for _, result := range results { 7082 for _, quotedName := range result.sourceMapChunk.QuotedNames { 7083 if isFirstName { 7084 isFirstName = false 7085 } else { 7086 j.AddString(", ") 7087 } 7088 j.AddBytes(quotedName) 7089 } 7090 } 7091 j.AddString("]") 7092 7093 // Finish the source map 7094 j.AddString("\n}\n") 7095 bytes := j.Done() 7096 7097 if !canHaveShifts { 7098 // If there cannot be any shifts, then we can avoid doing extra work later 7099 // on by preserving the source map as a single memory allocation throughout 7100 // the pipeline. That way we won't need to reallocate it. 7101 pieces.Prefix = bytes 7102 } else { 7103 // Otherwise if there can be shifts, then we need to split this into several 7104 // slices so that the shifts in the mappings array can be processed. This is 7105 // more expensive because everything will need to be recombined into a new 7106 // memory allocation at the end. 7107 pieces.Prefix = bytes[:mappingsStart] 7108 pieces.Mappings = bytes[mappingsStart:mappingsEnd] 7109 pieces.Suffix = bytes[mappingsEnd:] 7110 } 7111 return 7112 } 7113 7114 // Recover from a panic by logging it as an internal error instead of crashing 7115 func (c *linkerContext) recoverInternalError(waitGroup *sync.WaitGroup, sourceIndex uint32) { 7116 if r := recover(); r != nil { 7117 text := fmt.Sprintf("panic: %v", r) 7118 if sourceIndex != runtime.SourceIndex { 7119 text = fmt.Sprintf("%s (while printing %q)", text, c.graph.Files[sourceIndex].InputFile.Source.PrettyPath) 7120 } 7121 c.log.AddErrorWithNotes(nil, logger.Range{}, text, 7122 []logger.MsgData{{Text: helpers.PrettyPrintedStack()}}) 7123 waitGroup.Done() 7124 } 7125 } 7126 7127 func joinWithPublicPath(publicPath string, relPath string) string { 7128 if strings.HasPrefix(relPath, "./") { 7129 relPath = relPath[2:] 7130 7131 // Strip any amount of further no-op slashes (i.e. ".///././/x/y" => "x/y") 7132 for { 7133 if strings.HasPrefix(relPath, "/") { 7134 relPath = relPath[1:] 7135 } else if strings.HasPrefix(relPath, "./") { 7136 relPath = relPath[2:] 7137 } else { 7138 break 7139 } 7140 } 7141 } 7142 7143 // Use a relative path if there is no public path 7144 if publicPath == "" { 7145 publicPath = "." 7146 } 7147 7148 // Join with a slash 7149 slash := "/" 7150 if strings.HasSuffix(publicPath, "/") { 7151 slash = "" 7152 } 7153 return fmt.Sprintf("%s%s%s", publicPath, slash, relPath) 7154 }