github.com/evanw/esbuild@v0.21.4/internal/bundler_tests/bundler_loader_test.go (about) 1 package bundler_tests 2 3 import ( 4 "testing" 5 6 "github.com/evanw/esbuild/internal/bundler" 7 "github.com/evanw/esbuild/internal/compat" 8 "github.com/evanw/esbuild/internal/config" 9 ) 10 11 var loader_suite = suite{ 12 name: "loader", 13 } 14 15 func TestLoaderFile(t *testing.T) { 16 loader_suite.expectBundled(t, bundled{ 17 files: map[string]string{ 18 "/entry.js": ` 19 console.log(require('./test.svg')) 20 `, 21 "/test.svg": "<svg></svg>", 22 }, 23 entryPaths: []string{"/entry.js"}, 24 options: config.Options{ 25 Mode: config.ModeBundle, 26 AbsOutputDir: "/out/", 27 ExtensionToLoader: map[string]config.Loader{ 28 ".js": config.LoaderJS, 29 ".svg": config.LoaderFile, 30 }, 31 }, 32 }) 33 } 34 35 func TestLoaderFileMultipleNoCollision(t *testing.T) { 36 loader_suite.expectBundled(t, bundled{ 37 files: map[string]string{ 38 "/entry.js": ` 39 console.log( 40 require('./a/test.txt'), 41 require('./b/test.txt'), 42 ) 43 `, 44 45 // Two files with the same contents but different paths 46 "/a/test.txt": "test", 47 "/b/test.txt": "test", 48 }, 49 entryPaths: []string{"/entry.js"}, 50 options: config.Options{ 51 Mode: config.ModeBundle, 52 AbsOutputFile: "/dist/out.js", 53 ExtensionToLoader: map[string]config.Loader{ 54 ".js": config.LoaderJS, 55 ".txt": config.LoaderFile, 56 }, 57 }, 58 }) 59 } 60 61 func TestJSXSyntaxInJSWithJSXLoader(t *testing.T) { 62 loader_suite.expectBundled(t, bundled{ 63 files: map[string]string{ 64 "/entry.js": ` 65 console.log(<div/>) 66 `, 67 }, 68 entryPaths: []string{"/entry.js"}, 69 options: config.Options{ 70 Mode: config.ModeBundle, 71 AbsOutputFile: "/out.js", 72 ExtensionToLoader: map[string]config.Loader{ 73 ".js": config.LoaderJSX, 74 }, 75 }, 76 }) 77 } 78 79 func TestJSXPreserveCapitalLetter(t *testing.T) { 80 loader_suite.expectBundled(t, bundled{ 81 files: map[string]string{ 82 "/entry.jsx": ` 83 import { mustStartWithUpperCaseLetter as Test } from './foo' 84 console.log(<Test/>) 85 `, 86 "/foo.js": ` 87 export class mustStartWithUpperCaseLetter {} 88 `, 89 }, 90 entryPaths: []string{"/entry.jsx"}, 91 options: config.Options{ 92 Mode: config.ModeBundle, 93 AbsOutputFile: "/out.js", 94 JSX: config.JSXOptions{ 95 Parse: true, 96 Preserve: true, 97 }, 98 }, 99 }) 100 } 101 102 func TestJSXPreserveCapitalLetterMinify(t *testing.T) { 103 loader_suite.expectBundled(t, bundled{ 104 files: map[string]string{ 105 "/entry.jsx": ` 106 import { mustStartWithUpperCaseLetter as XYYYY } from './foo' 107 console.log(<XYYYY tag-must-start-with-capital-letter />) 108 `, 109 "/foo.js": ` 110 export class mustStartWithUpperCaseLetter {} 111 `, 112 }, 113 entryPaths: []string{"/entry.jsx"}, 114 options: config.Options{ 115 Mode: config.ModeBundle, 116 AbsOutputFile: "/out.js", 117 MinifyIdentifiers: true, 118 JSX: config.JSXOptions{ 119 Parse: true, 120 Preserve: true, 121 }, 122 }, 123 }) 124 } 125 126 func TestJSXPreserveCapitalLetterMinifyNested(t *testing.T) { 127 loader_suite.expectBundled(t, bundled{ 128 files: map[string]string{ 129 "/entry.jsx": ` 130 x = () => { 131 class XYYYYY {} // This should be named "Y" due to frequency analysis 132 return <XYYYYY tag-must-start-with-capital-letter /> 133 } 134 `, 135 }, 136 entryPaths: []string{"/entry.jsx"}, 137 options: config.Options{ 138 Mode: config.ModeBundle, 139 AbsOutputFile: "/out.js", 140 MinifyIdentifiers: true, 141 JSX: config.JSXOptions{ 142 Parse: true, 143 Preserve: true, 144 }, 145 }, 146 }) 147 } 148 149 func TestRequireCustomExtensionString(t *testing.T) { 150 loader_suite.expectBundled(t, bundled{ 151 files: map[string]string{ 152 "/entry.js": ` 153 console.log(require('./test.custom')) 154 `, 155 "/test.custom": `#include <stdio.h>`, 156 }, 157 entryPaths: []string{"/entry.js"}, 158 options: config.Options{ 159 Mode: config.ModeBundle, 160 AbsOutputFile: "/out.js", 161 ExtensionToLoader: map[string]config.Loader{ 162 ".js": config.LoaderJS, 163 ".custom": config.LoaderText, 164 }, 165 }, 166 }) 167 } 168 169 func TestRequireCustomExtensionBase64(t *testing.T) { 170 loader_suite.expectBundled(t, bundled{ 171 files: map[string]string{ 172 "/entry.js": ` 173 console.log(require('./test.custom')) 174 `, 175 "/test.custom": "a\x00b\x80c\xFFd", 176 }, 177 entryPaths: []string{"/entry.js"}, 178 options: config.Options{ 179 Mode: config.ModeBundle, 180 AbsOutputFile: "/out.js", 181 ExtensionToLoader: map[string]config.Loader{ 182 ".js": config.LoaderJS, 183 ".custom": config.LoaderBase64, 184 }, 185 }, 186 }) 187 } 188 189 func TestRequireCustomExtensionDataURL(t *testing.T) { 190 loader_suite.expectBundled(t, bundled{ 191 files: map[string]string{ 192 "/entry.js": ` 193 console.log(require('./test.custom')) 194 `, 195 "/test.custom": "a\x00b\x80c\xFFd", 196 }, 197 entryPaths: []string{"/entry.js"}, 198 options: config.Options{ 199 Mode: config.ModeBundle, 200 AbsOutputFile: "/out.js", 201 ExtensionToLoader: map[string]config.Loader{ 202 ".js": config.LoaderJS, 203 ".custom": config.LoaderDataURL, 204 }, 205 }, 206 }) 207 } 208 209 func TestRequireCustomExtensionPreferLongest(t *testing.T) { 210 loader_suite.expectBundled(t, bundled{ 211 files: map[string]string{ 212 "/entry.js": ` 213 console.log(require('./test.txt'), require('./test.base64.txt')) 214 `, 215 "/test.txt": `test.txt`, 216 "/test.base64.txt": `test.base64.txt`, 217 }, 218 entryPaths: []string{"/entry.js"}, 219 options: config.Options{ 220 Mode: config.ModeBundle, 221 AbsOutputFile: "/out.js", 222 ExtensionToLoader: map[string]config.Loader{ 223 ".js": config.LoaderJS, 224 ".txt": config.LoaderText, 225 ".base64.txt": config.LoaderBase64, 226 }, 227 }, 228 }) 229 } 230 231 func TestAutoDetectMimeTypeFromExtension(t *testing.T) { 232 loader_suite.expectBundled(t, bundled{ 233 files: map[string]string{ 234 "/entry.js": ` 235 console.log(require('./test.svg')) 236 `, 237 "/test.svg": "a\x00b\x80c\xFFd", 238 }, 239 entryPaths: []string{"/entry.js"}, 240 options: config.Options{ 241 Mode: config.ModeBundle, 242 AbsOutputFile: "/out.js", 243 ExtensionToLoader: map[string]config.Loader{ 244 ".js": config.LoaderJS, 245 ".svg": config.LoaderDataURL, 246 }, 247 }, 248 }) 249 } 250 251 func TestLoaderJSONCommonJSAndES6(t *testing.T) { 252 loader_suite.expectBundled(t, bundled{ 253 files: map[string]string{ 254 "/entry.js": ` 255 const x_json = require('./x.json') 256 import y_json from './y.json' 257 import {small, if as fi} from './z.json' 258 console.log(x_json, y_json, small, fi) 259 `, 260 "/x.json": `{"x": true}`, 261 "/y.json": `{"y1": true, "y2": false}`, 262 "/z.json": `{ 263 "big": "this is a big long line of text that should be discarded", 264 "small": "some small text", 265 "if": "test keyword imports" 266 }`, 267 }, 268 entryPaths: []string{"/entry.js"}, 269 options: config.Options{ 270 Mode: config.ModeBundle, 271 AbsOutputFile: "/out.js", 272 }, 273 }) 274 } 275 276 func TestLoaderJSONInvalidIdentifierES6(t *testing.T) { 277 loader_suite.expectBundled(t, bundled{ 278 files: map[string]string{ 279 "/entry.js": ` 280 import * as ns from './test.json' 281 import * as ns2 from './test2.json' 282 console.log(ns['invalid-identifier'], ns2) 283 `, 284 "/test.json": `{"invalid-identifier": true}`, 285 "/test2.json": `{"invalid-identifier": true}`, 286 }, 287 entryPaths: []string{"/entry.js"}, 288 options: config.Options{ 289 Mode: config.ModeBundle, 290 AbsOutputFile: "/out.js", 291 }, 292 }) 293 } 294 295 func TestLoaderJSONMissingES6(t *testing.T) { 296 loader_suite.expectBundled(t, bundled{ 297 files: map[string]string{ 298 "/entry.js": ` 299 import {missing} from './test.json' 300 `, 301 "/test.json": `{"present": true}`, 302 }, 303 entryPaths: []string{"/entry.js"}, 304 options: config.Options{ 305 Mode: config.ModeBundle, 306 AbsOutputFile: "/out.js", 307 }, 308 expectedCompileLog: `entry.js: ERROR: No matching export in "test.json" for import "missing" 309 `, 310 }) 311 } 312 313 func TestLoaderTextCommonJSAndES6(t *testing.T) { 314 loader_suite.expectBundled(t, bundled{ 315 files: map[string]string{ 316 "/entry.js": ` 317 const x_txt = require('./x.txt') 318 import y_txt from './y.txt' 319 console.log(x_txt, y_txt) 320 `, 321 "/x.txt": "x", 322 "/y.txt": "y", 323 }, 324 entryPaths: []string{"/entry.js"}, 325 options: config.Options{ 326 Mode: config.ModeBundle, 327 AbsOutputFile: "/out.js", 328 }, 329 }) 330 } 331 332 func TestLoaderBase64CommonJSAndES6(t *testing.T) { 333 loader_suite.expectBundled(t, bundled{ 334 files: map[string]string{ 335 "/entry.js": ` 336 const x_b64 = require('./x.b64') 337 import y_b64 from './y.b64' 338 console.log(x_b64, y_b64) 339 `, 340 "/x.b64": "x", 341 "/y.b64": "y", 342 }, 343 entryPaths: []string{"/entry.js"}, 344 options: config.Options{ 345 Mode: config.ModeBundle, 346 AbsOutputFile: "/out.js", 347 ExtensionToLoader: map[string]config.Loader{ 348 ".js": config.LoaderJS, 349 ".b64": config.LoaderBase64, 350 }, 351 }, 352 }) 353 } 354 355 func TestLoaderDataURLCommonJSAndES6(t *testing.T) { 356 loader_suite.expectBundled(t, bundled{ 357 files: map[string]string{ 358 "/entry.js": ` 359 const x_url = require('./x.txt') 360 import y_url from './y.txt' 361 console.log(x_url, y_url) 362 `, 363 "/x.txt": "x", 364 "/y.txt": "y", 365 }, 366 entryPaths: []string{"/entry.js"}, 367 options: config.Options{ 368 Mode: config.ModeBundle, 369 AbsOutputFile: "/out.js", 370 ExtensionToLoader: map[string]config.Loader{ 371 ".js": config.LoaderJS, 372 ".txt": config.LoaderDataURL, 373 }, 374 }, 375 }) 376 } 377 378 func TestLoaderFileCommonJSAndES6(t *testing.T) { 379 loader_suite.expectBundled(t, bundled{ 380 files: map[string]string{ 381 "/entry.js": ` 382 const x_url = require('./x.txt') 383 import y_url from './y.txt' 384 console.log(x_url, y_url) 385 `, 386 "/x.txt": "x", 387 "/y.txt": "y", 388 }, 389 entryPaths: []string{"/entry.js"}, 390 options: config.Options{ 391 Mode: config.ModeBundle, 392 AbsOutputFile: "/out.js", 393 ExtensionToLoader: map[string]config.Loader{ 394 ".js": config.LoaderJS, 395 ".txt": config.LoaderFile, 396 }, 397 }, 398 }) 399 } 400 401 func TestLoaderFileRelativePathJS(t *testing.T) { 402 loader_suite.expectBundled(t, bundled{ 403 files: map[string]string{ 404 "/src/entries/entry.js": ` 405 import x from '../images/image.png' 406 console.log(x) 407 `, 408 "/src/images/image.png": "x", 409 }, 410 entryPaths: []string{"/src/entries/entry.js"}, 411 options: config.Options{ 412 Mode: config.ModeBundle, 413 AbsOutputBase: "/src", 414 AbsOutputDir: "/out", 415 ExtensionToLoader: map[string]config.Loader{ 416 ".js": config.LoaderJS, 417 ".png": config.LoaderFile, 418 }, 419 }, 420 }) 421 } 422 423 func TestLoaderFileRelativePathCSS(t *testing.T) { 424 loader_suite.expectBundled(t, bundled{ 425 files: map[string]string{ 426 "/src/entries/entry.css": ` 427 div { 428 background: url(../images/image.png); 429 } 430 `, 431 "/src/images/image.png": "x", 432 }, 433 entryPaths: []string{"/src/entries/entry.css"}, 434 options: config.Options{ 435 Mode: config.ModeBundle, 436 AbsOutputBase: "/src", 437 AbsOutputDir: "/out", 438 ExtensionToLoader: map[string]config.Loader{ 439 ".css": config.LoaderCSS, 440 ".png": config.LoaderFile, 441 }, 442 }, 443 }) 444 } 445 446 func TestLoaderFileRelativePathAssetNamesJS(t *testing.T) { 447 loader_suite.expectBundled(t, bundled{ 448 files: map[string]string{ 449 "/src/entries/entry.js": ` 450 import x from '../images/image.png' 451 console.log(x) 452 `, 453 "/src/images/image.png": "x", 454 }, 455 entryPaths: []string{"/src/entries/entry.js"}, 456 options: config.Options{ 457 Mode: config.ModeBundle, 458 AbsOutputBase: "/src", 459 AbsOutputDir: "/out", 460 AssetPathTemplate: []config.PathTemplate{ 461 {Data: "", Placeholder: config.DirPlaceholder}, 462 {Data: "/", Placeholder: config.NamePlaceholder}, 463 {Data: "-", Placeholder: config.HashPlaceholder}, 464 }, 465 ExtensionToLoader: map[string]config.Loader{ 466 ".js": config.LoaderJS, 467 ".png": config.LoaderFile, 468 }, 469 }, 470 }) 471 } 472 473 func TestLoaderFileExtPathAssetNamesJS(t *testing.T) { 474 loader_suite.expectBundled(t, bundled{ 475 files: map[string]string{ 476 "/src/entries/entry.js": ` 477 import x from '../images/image.png' 478 import y from '../uploads/file.txt' 479 console.log(x, y) 480 `, 481 "/src/images/image.png": "x", 482 "/src/uploads/file.txt": "y", 483 }, 484 entryPaths: []string{"/src/entries/entry.js"}, 485 options: config.Options{ 486 Mode: config.ModeBundle, 487 AbsOutputBase: "/src", 488 AbsOutputDir: "/out", 489 AssetPathTemplate: []config.PathTemplate{ 490 {Data: "", Placeholder: config.ExtPlaceholder}, 491 {Data: "/", Placeholder: config.NamePlaceholder}, 492 {Data: "-", Placeholder: config.HashPlaceholder}, 493 }, 494 ExtensionToLoader: map[string]config.Loader{ 495 ".js": config.LoaderJS, 496 ".png": config.LoaderFile, 497 ".txt": config.LoaderFile, 498 }, 499 }, 500 }) 501 } 502 503 func TestLoaderFileRelativePathAssetNamesCSS(t *testing.T) { 504 loader_suite.expectBundled(t, bundled{ 505 files: map[string]string{ 506 "/src/entries/entry.css": ` 507 div { 508 background: url(../images/image.png); 509 } 510 `, 511 "/src/images/image.png": "x", 512 }, 513 entryPaths: []string{"/src/entries/entry.css"}, 514 options: config.Options{ 515 Mode: config.ModeBundle, 516 AbsOutputBase: "/src", 517 AbsOutputDir: "/out", 518 AssetPathTemplate: []config.PathTemplate{ 519 {Data: "", Placeholder: config.DirPlaceholder}, 520 {Data: "/", Placeholder: config.NamePlaceholder}, 521 {Data: "-", Placeholder: config.HashPlaceholder}, 522 }, 523 ExtensionToLoader: map[string]config.Loader{ 524 ".css": config.LoaderCSS, 525 ".png": config.LoaderFile, 526 }, 527 }, 528 }) 529 } 530 531 func TestLoaderFilePublicPathJS(t *testing.T) { 532 loader_suite.expectBundled(t, bundled{ 533 files: map[string]string{ 534 "/src/entries/entry.js": ` 535 import x from '../images/image.png' 536 console.log(x) 537 `, 538 "/src/images/image.png": "x", 539 }, 540 entryPaths: []string{"/src/entries/entry.js"}, 541 options: config.Options{ 542 Mode: config.ModeBundle, 543 AbsOutputBase: "/src", 544 AbsOutputDir: "/out", 545 PublicPath: "https://example.com", 546 ExtensionToLoader: map[string]config.Loader{ 547 ".js": config.LoaderJS, 548 ".png": config.LoaderFile, 549 }, 550 }, 551 }) 552 } 553 554 func TestLoaderFilePublicPathCSS(t *testing.T) { 555 loader_suite.expectBundled(t, bundled{ 556 files: map[string]string{ 557 "/src/entries/entry.css": ` 558 div { 559 background: url(../images/image.png); 560 } 561 `, 562 "/src/images/image.png": "x", 563 }, 564 entryPaths: []string{"/src/entries/entry.css"}, 565 options: config.Options{ 566 Mode: config.ModeBundle, 567 AbsOutputBase: "/src", 568 AbsOutputDir: "/out", 569 PublicPath: "https://example.com", 570 ExtensionToLoader: map[string]config.Loader{ 571 ".css": config.LoaderCSS, 572 ".png": config.LoaderFile, 573 }, 574 }, 575 }) 576 } 577 578 func TestLoaderFilePublicPathAssetNamesJS(t *testing.T) { 579 loader_suite.expectBundled(t, bundled{ 580 files: map[string]string{ 581 "/src/entries/entry.js": ` 582 import x from '../images/image.png' 583 console.log(x) 584 `, 585 "/src/images/image.png": "x", 586 }, 587 entryPaths: []string{"/src/entries/entry.js"}, 588 options: config.Options{ 589 Mode: config.ModeBundle, 590 AbsOutputBase: "/src", 591 AbsOutputDir: "/out", 592 PublicPath: "https://example.com", 593 AssetPathTemplate: []config.PathTemplate{ 594 {Data: "", Placeholder: config.DirPlaceholder}, 595 {Data: "/", Placeholder: config.NamePlaceholder}, 596 {Data: "-", Placeholder: config.HashPlaceholder}, 597 }, 598 ExtensionToLoader: map[string]config.Loader{ 599 ".js": config.LoaderJS, 600 ".png": config.LoaderFile, 601 }, 602 }, 603 }) 604 } 605 606 func TestLoaderFilePublicPathAssetNamesCSS(t *testing.T) { 607 loader_suite.expectBundled(t, bundled{ 608 files: map[string]string{ 609 "/src/entries/entry.css": ` 610 div { 611 background: url(../images/image.png); 612 } 613 `, 614 "/src/images/image.png": "x", 615 }, 616 entryPaths: []string{"/src/entries/entry.css"}, 617 options: config.Options{ 618 Mode: config.ModeBundle, 619 AbsOutputBase: "/src", 620 AbsOutputDir: "/out", 621 PublicPath: "https://example.com", 622 AssetPathTemplate: []config.PathTemplate{ 623 {Data: "", Placeholder: config.DirPlaceholder}, 624 {Data: "/", Placeholder: config.NamePlaceholder}, 625 {Data: "-", Placeholder: config.HashPlaceholder}, 626 }, 627 ExtensionToLoader: map[string]config.Loader{ 628 ".css": config.LoaderCSS, 629 ".png": config.LoaderFile, 630 }, 631 }, 632 }) 633 } 634 635 func TestLoaderFileOneSourceTwoDifferentOutputPathsJS(t *testing.T) { 636 loader_suite.expectBundled(t, bundled{ 637 files: map[string]string{ 638 "/src/entries/entry.js": ` 639 import '../shared/common.js' 640 `, 641 "/src/entries/other/entry.js": ` 642 import '../../shared/common.js' 643 `, 644 "/src/shared/common.js": ` 645 import x from './common.png' 646 console.log(x) 647 `, 648 "/src/shared/common.png": "x", 649 }, 650 entryPaths: []string{ 651 "/src/entries/entry.js", 652 "/src/entries/other/entry.js", 653 }, 654 options: config.Options{ 655 Mode: config.ModeBundle, 656 AbsOutputBase: "/src", 657 AbsOutputDir: "/out", 658 ExtensionToLoader: map[string]config.Loader{ 659 ".js": config.LoaderJS, 660 ".png": config.LoaderFile, 661 }, 662 }, 663 }) 664 } 665 666 func TestLoaderFileOneSourceTwoDifferentOutputPathsCSS(t *testing.T) { 667 loader_suite.expectBundled(t, bundled{ 668 files: map[string]string{ 669 "/src/entries/entry.css": ` 670 @import "../shared/common.css"; 671 `, 672 "/src/entries/other/entry.css": ` 673 @import "../../shared/common.css"; 674 `, 675 "/src/shared/common.css": ` 676 div { 677 background: url(common.png); 678 } 679 `, 680 "/src/shared/common.png": "x", 681 }, 682 entryPaths: []string{ 683 "/src/entries/entry.css", 684 "/src/entries/other/entry.css", 685 }, 686 options: config.Options{ 687 Mode: config.ModeBundle, 688 AbsOutputBase: "/src", 689 AbsOutputDir: "/out", 690 ExtensionToLoader: map[string]config.Loader{ 691 ".css": config.LoaderCSS, 692 ".png": config.LoaderFile, 693 }, 694 }, 695 }) 696 } 697 698 func TestLoaderJSONNoBundle(t *testing.T) { 699 loader_suite.expectBundled(t, bundled{ 700 files: map[string]string{ 701 "/test.json": `{"test": 123, "invalid-identifier": true}`, 702 }, 703 entryPaths: []string{"/test.json"}, 704 options: config.Options{ 705 AbsOutputFile: "/out.js", 706 }, 707 }) 708 } 709 710 func TestLoaderJSONNoBundleES6(t *testing.T) { 711 loader_suite.expectBundled(t, bundled{ 712 files: map[string]string{ 713 "/test.json": `{"test": 123, "invalid-identifier": true}`, 714 }, 715 entryPaths: []string{"/test.json"}, 716 options: config.Options{ 717 Mode: config.ModeConvertFormat, 718 OutputFormat: config.FormatESModule, 719 UnsupportedJSFeatures: compat.ArbitraryModuleNamespaceNames, 720 AbsOutputFile: "/out.js", 721 }, 722 }) 723 } 724 725 func TestLoaderJSONNoBundleES6ArbitraryModuleNamespaceNames(t *testing.T) { 726 loader_suite.expectBundled(t, bundled{ 727 files: map[string]string{ 728 "/test.json": `{"test": 123, "invalid-identifier": true}`, 729 }, 730 entryPaths: []string{"/test.json"}, 731 options: config.Options{ 732 Mode: config.ModeConvertFormat, 733 OutputFormat: config.FormatESModule, 734 AbsOutputFile: "/out.js", 735 }, 736 }) 737 } 738 739 func TestLoaderJSONNoBundleCommonJS(t *testing.T) { 740 loader_suite.expectBundled(t, bundled{ 741 files: map[string]string{ 742 "/test.json": `{"test": 123, "invalid-identifier": true}`, 743 }, 744 entryPaths: []string{"/test.json"}, 745 options: config.Options{ 746 Mode: config.ModeConvertFormat, 747 OutputFormat: config.FormatCommonJS, 748 AbsOutputFile: "/out.js", 749 }, 750 }) 751 } 752 753 func TestLoaderJSONNoBundleIIFE(t *testing.T) { 754 loader_suite.expectBundled(t, bundled{ 755 files: map[string]string{ 756 "/test.json": `{"test": 123, "invalid-identifier": true}`, 757 }, 758 entryPaths: []string{"/test.json"}, 759 options: config.Options{ 760 Mode: config.ModeConvertFormat, 761 OutputFormat: config.FormatIIFE, 762 AbsOutputFile: "/out.js", 763 }, 764 }) 765 } 766 767 func TestLoaderJSONSharedWithMultipleEntriesIssue413(t *testing.T) { 768 loader_suite.expectBundled(t, bundled{ 769 files: map[string]string{ 770 "/a.js": ` 771 import data from './data.json' 772 console.log('a:', data) 773 `, 774 "/b.js": ` 775 import data from './data.json' 776 console.log('b:', data) 777 `, 778 "/data.json": `{"test": 123}`, 779 }, 780 entryPaths: []string{"/a.js", "/b.js"}, 781 options: config.Options{ 782 Mode: config.ModeBundle, 783 OutputFormat: config.FormatESModule, 784 AbsOutputDir: "/out", 785 }, 786 }) 787 } 788 789 func TestLoaderFileWithQueryParameter(t *testing.T) { 790 loader_suite.expectBundled(t, bundled{ 791 files: map[string]string{ 792 "/entry.js": ` 793 // Each of these should have a separate identity (i.e. end up in the output file twice) 794 import foo from './file.txt?foo' 795 import bar from './file.txt?bar' 796 console.log(foo, bar) 797 `, 798 "/file.txt": `This is some text`, 799 }, 800 entryPaths: []string{"/entry.js"}, 801 options: config.Options{ 802 Mode: config.ModeBundle, 803 AbsOutputDir: "/out", 804 ExtensionToLoader: map[string]config.Loader{ 805 ".js": config.LoaderJS, 806 ".txt": config.LoaderFile, 807 }, 808 }, 809 }) 810 } 811 812 func TestLoaderFromExtensionWithQueryParameter(t *testing.T) { 813 loader_suite.expectBundled(t, bundled{ 814 files: map[string]string{ 815 "/entry.js": ` 816 import foo from './file.abc?query.xyz' 817 console.log(foo) 818 `, 819 "/file.abc": `This should not be base64 encoded`, 820 }, 821 entryPaths: []string{"/entry.js"}, 822 options: config.Options{ 823 Mode: config.ModeBundle, 824 AbsOutputDir: "/out", 825 ExtensionToLoader: map[string]config.Loader{ 826 ".js": config.LoaderJS, 827 ".abc": config.LoaderText, 828 ".xyz": config.LoaderBase64, 829 }, 830 }, 831 }) 832 } 833 834 func TestLoaderDataURLTextCSS(t *testing.T) { 835 loader_suite.expectBundled(t, bundled{ 836 files: map[string]string{ 837 "/entry.css": ` 838 @import "data:text/css,body{color:%72%65%64}"; 839 @import "data:text/css;base64,Ym9keXtiYWNrZ3JvdW5kOmJsdWV9"; 840 @import "data:text/css;charset=UTF-8,body{color:%72%65%64}"; 841 @import "data:text/css;charset=UTF-8;base64,Ym9keXtiYWNrZ3JvdW5kOmJsdWV9"; 842 `, 843 }, 844 entryPaths: []string{"/entry.css"}, 845 options: config.Options{ 846 Mode: config.ModeBundle, 847 AbsOutputDir: "/out", 848 }, 849 }) 850 } 851 852 func TestLoaderDataURLTextCSSCannotImport(t *testing.T) { 853 loader_suite.expectBundled(t, bundled{ 854 files: map[string]string{ 855 "/entry.css": ` 856 @import "data:text/css,@import './other.css';"; 857 `, 858 "/other.css": ` 859 div { should-not-be-imported: true } 860 `, 861 }, 862 entryPaths: []string{"/entry.css"}, 863 options: config.Options{ 864 Mode: config.ModeBundle, 865 AbsOutputDir: "/out", 866 }, 867 expectedScanLog: `<data:text/css,@import './other.css';>: ERROR: Could not resolve "./other.css" 868 `, 869 }) 870 } 871 872 func TestLoaderDataURLTextJavaScript(t *testing.T) { 873 loader_suite.expectBundled(t, bundled{ 874 files: map[string]string{ 875 "/entry.js": ` 876 import "data:text/javascript,console.log('%31%32%33')"; 877 import "data:text/javascript;base64,Y29uc29sZS5sb2coMjM0KQ=="; 878 import "data:text/javascript;charset=UTF-8,console.log(%31%32%33)"; 879 import "data:text/javascript;charset=UTF-8;base64,Y29uc29sZS5sb2coMjM0KQ=="; 880 `, 881 }, 882 entryPaths: []string{"/entry.js"}, 883 options: config.Options{ 884 Mode: config.ModeBundle, 885 AbsOutputDir: "/out", 886 }, 887 }) 888 } 889 890 func TestLoaderDataURLTextJavaScriptCannotImport(t *testing.T) { 891 loader_suite.expectBundled(t, bundled{ 892 files: map[string]string{ 893 "/entry.js": ` 894 import "data:text/javascript,import './other.js'" 895 `, 896 "/other.js": ` 897 shouldNotBeImported = true 898 `, 899 }, 900 entryPaths: []string{"/entry.js"}, 901 options: config.Options{ 902 Mode: config.ModeBundle, 903 AbsOutputDir: "/out", 904 }, 905 expectedScanLog: `<data:text/javascript,import './other.js'>: ERROR: Could not resolve "./other.js" 906 `, 907 }) 908 } 909 910 // The "+" character must not be interpreted as a " " character 911 func TestLoaderDataURLTextJavaScriptPlusCharacter(t *testing.T) { 912 loader_suite.expectBundled(t, bundled{ 913 files: map[string]string{ 914 "/entry.js": ` 915 import "data:text/javascript,console.log(1+2)"; 916 `, 917 }, 918 entryPaths: []string{"/entry.js"}, 919 options: config.Options{ 920 Mode: config.ModeBundle, 921 AbsOutputDir: "/out", 922 }, 923 }) 924 } 925 926 func TestLoaderDataURLApplicationJSON(t *testing.T) { 927 loader_suite.expectBundled(t, bundled{ 928 files: map[string]string{ 929 "/entry.js": ` 930 import a from 'data:application/json,"%31%32%33"'; 931 import b from 'data:application/json;base64,eyJ3b3JrcyI6dHJ1ZX0='; 932 import c from 'data:application/json;charset=UTF-8,%31%32%33'; 933 import d from 'data:application/json;charset=UTF-8;base64,eyJ3b3JrcyI6dHJ1ZX0='; 934 console.log([ 935 a, b, c, d, 936 ]) 937 `, 938 }, 939 entryPaths: []string{"/entry.js"}, 940 options: config.Options{ 941 Mode: config.ModeBundle, 942 AbsOutputDir: "/out", 943 }, 944 }) 945 } 946 947 func TestLoaderDataURLUnknownMIME(t *testing.T) { 948 loader_suite.expectBundled(t, bundled{ 949 files: map[string]string{ 950 "/entry.js": ` 951 import a from 'data:some/thing;what,someData%31%32%33'; 952 import b from 'data:other/thing;stuff;base64,c29tZURhdGEyMzQ='; 953 console.log(a, b) 954 `, 955 }, 956 entryPaths: []string{"/entry.js"}, 957 options: config.Options{ 958 Mode: config.ModeBundle, 959 AbsOutputDir: "/out", 960 }, 961 }) 962 } 963 964 func TestLoaderDataURLExtensionBasedMIME(t *testing.T) { 965 loader_suite.expectBundled(t, bundled{ 966 files: map[string]string{ 967 "/entry.foo": ` 968 export { default as css } from "./example.css" 969 export { default as eot } from "./example.eot" 970 export { default as gif } from "./example.gif" 971 export { default as htm } from "./example.htm" 972 export { default as html } from "./example.html" 973 export { default as jpeg } from "./example.jpeg" 974 export { default as jpg } from "./example.jpg" 975 export { default as js } from "./example.js" 976 export { default as json } from "./example.json" 977 export { default as mjs } from "./example.mjs" 978 export { default as otf } from "./example.otf" 979 export { default as pdf } from "./example.pdf" 980 export { default as png } from "./example.png" 981 export { default as sfnt } from "./example.sfnt" 982 export { default as svg } from "./example.svg" 983 export { default as ttf } from "./example.ttf" 984 export { default as wasm } from "./example.wasm" 985 export { default as webp } from "./example.webp" 986 export { default as woff } from "./example.woff" 987 export { default as woff2 } from "./example.woff2" 988 export { default as xml } from "./example.xml" 989 `, 990 "/example.css": `css`, 991 "/example.eot": `eot`, 992 "/example.gif": `gif`, 993 "/example.htm": `htm`, 994 "/example.html": `html`, 995 "/example.jpeg": `jpeg`, 996 "/example.jpg": `jpg`, 997 "/example.js": `js`, 998 "/example.json": `json`, 999 "/example.mjs": `mjs`, 1000 "/example.otf": `otf`, 1001 "/example.pdf": `pdf`, 1002 "/example.png": `png`, 1003 "/example.sfnt": `sfnt`, 1004 "/example.svg": `svg`, 1005 "/example.ttf": `ttf`, 1006 "/example.wasm": `wasm`, 1007 "/example.webp": `webp`, 1008 "/example.woff": `woff`, 1009 "/example.woff2": `woff2`, 1010 "/example.xml": `xml`, 1011 }, 1012 entryPaths: []string{"/entry.foo"}, 1013 options: config.Options{ 1014 Mode: config.ModeBundle, 1015 AbsOutputDir: "/out", 1016 ExtensionToLoader: map[string]config.Loader{ 1017 ".foo": config.LoaderJS, 1018 ".css": config.LoaderDataURL, 1019 ".eot": config.LoaderDataURL, 1020 ".gif": config.LoaderDataURL, 1021 ".htm": config.LoaderDataURL, 1022 ".html": config.LoaderDataURL, 1023 ".jpeg": config.LoaderDataURL, 1024 ".jpg": config.LoaderDataURL, 1025 ".js": config.LoaderDataURL, 1026 ".json": config.LoaderDataURL, 1027 ".mjs": config.LoaderDataURL, 1028 ".otf": config.LoaderDataURL, 1029 ".pdf": config.LoaderDataURL, 1030 ".png": config.LoaderDataURL, 1031 ".sfnt": config.LoaderDataURL, 1032 ".svg": config.LoaderDataURL, 1033 ".ttf": config.LoaderDataURL, 1034 ".wasm": config.LoaderDataURL, 1035 ".webp": config.LoaderDataURL, 1036 ".woff": config.LoaderDataURL, 1037 ".woff2": config.LoaderDataURL, 1038 ".xml": config.LoaderDataURL, 1039 }, 1040 }, 1041 }) 1042 } 1043 1044 // Percent-encoded data URLs should switch over to base64 1045 // data URLs if it would result in a smaller size 1046 func TestLoaderDataURLBase64VsPercentEncoding(t *testing.T) { 1047 loader_suite.expectBundled(t, bundled{ 1048 files: map[string]string{ 1049 "/entry.js": ` 1050 import a from './shouldUsePercent_1.txt' 1051 import b from './shouldUsePercent_2.txt' 1052 import c from './shouldUseBase64_1.txt' 1053 import d from './shouldUseBase64_2.txt' 1054 console.log( 1055 a, 1056 b, 1057 c, 1058 d, 1059 ) 1060 `, 1061 "/shouldUsePercent_1.txt": "\n\n\n", 1062 "/shouldUsePercent_2.txt": "\n\n\n\n", 1063 "/shouldUseBase64_1.txt": "\n\n\n\n\n", 1064 "/shouldUseBase64_2.txt": "\n\n\n\n\n\n", 1065 }, 1066 entryPaths: []string{"/entry.js"}, 1067 options: config.Options{ 1068 Mode: config.ModeBundle, 1069 AbsOutputFile: "/out.js", 1070 ExtensionToLoader: map[string]config.Loader{ 1071 ".js": config.LoaderJS, 1072 ".txt": config.LoaderDataURL, 1073 }, 1074 }, 1075 }) 1076 } 1077 1078 func TestLoaderDataURLBase64InvalidUTF8(t *testing.T) { 1079 loader_suite.expectBundled(t, bundled{ 1080 files: map[string]string{ 1081 "/entry.js": ` 1082 import a from './binary.txt' 1083 console.log(a) 1084 `, 1085 "/binary.txt": "\xFF", 1086 }, 1087 entryPaths: []string{"/entry.js"}, 1088 options: config.Options{ 1089 Mode: config.ModeBundle, 1090 AbsOutputFile: "/out.js", 1091 ExtensionToLoader: map[string]config.Loader{ 1092 ".js": config.LoaderJS, 1093 ".txt": config.LoaderDataURL, 1094 }, 1095 }, 1096 }) 1097 } 1098 1099 func TestLoaderDataURLEscapePercents(t *testing.T) { 1100 loader_suite.expectBundled(t, bundled{ 1101 files: map[string]string{ 1102 "/entry.js": ` 1103 import a from './percents.txt' 1104 console.log(a) 1105 `, 1106 "/percents.txt": ` 1107 %, %3, %33, %333 1108 %, %e, %ee, %eee 1109 %, %E, %EE, %EEE 1110 `, 1111 }, 1112 entryPaths: []string{"/entry.js"}, 1113 options: config.Options{ 1114 Mode: config.ModeBundle, 1115 AbsOutputFile: "/out.js", 1116 ExtensionToLoader: map[string]config.Loader{ 1117 ".js": config.LoaderJS, 1118 ".txt": config.LoaderDataURL, 1119 }, 1120 }, 1121 }) 1122 } 1123 1124 func TestLoaderCopyWithBundleFromJS(t *testing.T) { 1125 loader_suite.expectBundled(t, bundled{ 1126 files: map[string]string{ 1127 "/Users/user/project/src/entry.js": ` 1128 import x from "../assets/some.file" 1129 console.log(x) 1130 `, 1131 "/Users/user/project/assets/some.file": `stuff`, 1132 }, 1133 entryPaths: []string{"/Users/user/project/src/entry.js"}, 1134 options: config.Options{ 1135 Mode: config.ModeBundle, 1136 AbsOutputBase: "/Users/user/project", 1137 AbsOutputDir: "/out", 1138 ExtensionToLoader: map[string]config.Loader{ 1139 ".js": config.LoaderJS, 1140 ".file": config.LoaderCopy, 1141 }, 1142 }, 1143 }) 1144 } 1145 1146 func TestLoaderCopyWithBundleFromCSS(t *testing.T) { 1147 loader_suite.expectBundled(t, bundled{ 1148 files: map[string]string{ 1149 "/Users/user/project/src/entry.css": ` 1150 body { 1151 background: url(../assets/some.file); 1152 } 1153 `, 1154 "/Users/user/project/assets/some.file": `stuff`, 1155 }, 1156 entryPaths: []string{"/Users/user/project/src/entry.css"}, 1157 options: config.Options{ 1158 Mode: config.ModeBundle, 1159 AbsOutputBase: "/Users/user/project", 1160 AbsOutputDir: "/out", 1161 ExtensionToLoader: map[string]config.Loader{ 1162 ".css": config.LoaderCSS, 1163 ".file": config.LoaderCopy, 1164 }, 1165 }, 1166 }) 1167 } 1168 1169 func TestLoaderCopyWithBundleEntryPoint(t *testing.T) { 1170 loader_suite.expectBundled(t, bundled{ 1171 files: map[string]string{ 1172 "/Users/user/project/src/entry.js": ` 1173 import x from "../assets/some.file" 1174 console.log(x) 1175 `, 1176 "/Users/user/project/src/entry.css": ` 1177 body { 1178 background: url(../assets/some.file); 1179 } 1180 `, 1181 "/Users/user/project/assets/some.file": `stuff`, 1182 }, 1183 entryPaths: []string{ 1184 "/Users/user/project/src/entry.js", 1185 "/Users/user/project/src/entry.css", 1186 "/Users/user/project/assets/some.file", 1187 }, 1188 options: config.Options{ 1189 Mode: config.ModeBundle, 1190 AbsOutputBase: "/Users/user/project", 1191 AbsOutputDir: "/out", 1192 ExtensionToLoader: map[string]config.Loader{ 1193 ".js": config.LoaderJS, 1194 ".css": config.LoaderCSS, 1195 ".file": config.LoaderCopy, 1196 }, 1197 }, 1198 }) 1199 } 1200 1201 func TestLoaderCopyWithTransform(t *testing.T) { 1202 loader_suite.expectBundled(t, bundled{ 1203 files: map[string]string{ 1204 "/Users/user/project/src/entry.js": `console.log('entry')`, 1205 "/Users/user/project/assets/some.file": `stuff`, 1206 }, 1207 entryPaths: []string{ 1208 "/Users/user/project/src/entry.js", 1209 "/Users/user/project/assets/some.file", 1210 }, 1211 options: config.Options{ 1212 Mode: config.ModePassThrough, 1213 AbsOutputBase: "/Users/user/project", 1214 AbsOutputDir: "/out", 1215 ExtensionToLoader: map[string]config.Loader{ 1216 ".js": config.LoaderJS, 1217 ".file": config.LoaderCopy, 1218 }, 1219 }, 1220 }) 1221 } 1222 1223 func TestLoaderCopyWithFormat(t *testing.T) { 1224 loader_suite.expectBundled(t, bundled{ 1225 files: map[string]string{ 1226 "/Users/user/project/src/entry.js": `console.log('entry')`, 1227 "/Users/user/project/assets/some.file": `stuff`, 1228 }, 1229 entryPaths: []string{ 1230 "/Users/user/project/src/entry.js", 1231 "/Users/user/project/assets/some.file", 1232 }, 1233 options: config.Options{ 1234 Mode: config.ModeConvertFormat, 1235 OutputFormat: config.FormatIIFE, 1236 AbsOutputBase: "/Users/user/project", 1237 AbsOutputDir: "/out", 1238 ExtensionToLoader: map[string]config.Loader{ 1239 ".js": config.LoaderJS, 1240 ".file": config.LoaderCopy, 1241 }, 1242 }, 1243 }) 1244 } 1245 1246 func TestJSXAutomaticNoNameCollision(t *testing.T) { 1247 loader_suite.expectBundled(t, bundled{ 1248 files: map[string]string{ 1249 "/entry.jsx": ` 1250 import { Link } from "@remix-run/react" 1251 const x = <Link {...y} key={z} /> 1252 `, 1253 }, 1254 entryPaths: []string{"/entry.jsx"}, 1255 options: config.Options{ 1256 Mode: config.ModeConvertFormat, 1257 OutputFormat: config.FormatCommonJS, 1258 AbsOutputFile: "/out.js", 1259 JSX: config.JSXOptions{ 1260 AutomaticRuntime: true, 1261 }, 1262 }, 1263 }) 1264 } 1265 1266 func TestAssertTypeJSONWrongLoader(t *testing.T) { 1267 loader_suite.expectBundled(t, bundled{ 1268 files: map[string]string{ 1269 "/entry.js": ` 1270 import foo from './foo.json' assert { type: 'json' } 1271 console.log(foo) 1272 `, 1273 "/foo.json": `{}`, 1274 }, 1275 entryPaths: []string{"/entry.js"}, 1276 options: config.Options{ 1277 Mode: config.ModeBundle, 1278 ExtensionToLoader: map[string]config.Loader{ 1279 ".js": config.LoaderJS, 1280 ".json": config.LoaderJS, 1281 }, 1282 }, 1283 expectedScanLog: `entry.js: ERROR: The file "foo.json" was loaded with the "js" loader 1284 entry.js: NOTE: This import assertion requires the loader to be "json" instead: 1285 NOTE: You need to either reconfigure esbuild to ensure that the loader for this file is "json" or you need to remove this import assertion. 1286 `, 1287 }) 1288 } 1289 1290 func TestWithTypeJSONOverrideLoader(t *testing.T) { 1291 loader_suite.expectBundled(t, bundled{ 1292 files: map[string]string{ 1293 "/entry.js": ` 1294 import foo from './foo.js' with { type: 'json' } 1295 console.log(foo) 1296 `, 1297 "/foo.js": `{ "this is json not js": true }`, 1298 }, 1299 entryPaths: []string{"/entry.js"}, 1300 options: config.Options{ 1301 Mode: config.ModeBundle, 1302 }, 1303 }) 1304 } 1305 1306 func TestWithBadType(t *testing.T) { 1307 loader_suite.expectBundled(t, bundled{ 1308 files: map[string]string{ 1309 "/entry.js": ` 1310 import foo from './foo.json' with { type: '' } 1311 import bar from './foo.json' with { type: 'garbage' } 1312 console.log(bar) 1313 `, 1314 "/foo.json": `{}`, 1315 }, 1316 entryPaths: []string{"/entry.js"}, 1317 options: config.Options{ 1318 Mode: config.ModeBundle, 1319 }, 1320 expectedScanLog: `entry.js: ERROR: Importing with a type attribute of "" is not supported 1321 entry.js: ERROR: Importing with a type attribute of "garbage" is not supported 1322 `, 1323 }) 1324 } 1325 1326 func TestWithBadAttribute(t *testing.T) { 1327 loader_suite.expectBundled(t, bundled{ 1328 files: map[string]string{ 1329 "/entry.js": ` 1330 import foo from './foo.json' with { '': 'json' } 1331 import bar from './foo.json' with { garbage: 'json' } 1332 console.log(bar) 1333 `, 1334 "/foo.json": `{}`, 1335 }, 1336 entryPaths: []string{"/entry.js"}, 1337 options: config.Options{ 1338 Mode: config.ModeBundle, 1339 }, 1340 expectedScanLog: `entry.js: ERROR: Importing with the "" attribute is not supported 1341 entry.js: ERROR: Importing with the "garbage" attribute is not supported 1342 `, 1343 }) 1344 } 1345 1346 func TestEmptyLoaderJS(t *testing.T) { 1347 loader_suite.expectBundled(t, bundled{ 1348 files: map[string]string{ 1349 "/entry.js": ` 1350 import './a.empty' 1351 import * as ns from './b.empty' 1352 import def from './c.empty' 1353 import { named } from './d.empty' 1354 console.log(ns, def, named) 1355 `, 1356 "/a.empty": `throw 'FAIL'`, 1357 "/b.empty": `throw 'FAIL'`, 1358 "/c.empty": `throw 'FAIL'`, 1359 "/d.empty": `throw 'FAIL'`, 1360 }, 1361 entryPaths: []string{"/entry.js"}, 1362 options: config.Options{ 1363 Mode: config.ModeBundle, 1364 SourceMap: config.SourceMapExternalWithoutComment, 1365 NeedsMetafile: true, 1366 ExtensionToLoader: map[string]config.Loader{ 1367 ".js": config.LoaderJS, 1368 ".empty": config.LoaderEmpty, 1369 }, 1370 }, 1371 expectedCompileLog: `entry.js: WARNING: Import "named" will always be undefined because the file "d.empty" has no exports 1372 `, 1373 }) 1374 } 1375 1376 func TestEmptyLoaderCSS(t *testing.T) { 1377 loader_suite.expectBundled(t, bundled{ 1378 files: map[string]string{ 1379 "/entry.css": ` 1380 @import 'a.empty'; 1381 a { background: url(b.empty) } 1382 `, 1383 "/a.empty": `body { color: fail }`, 1384 "/b.empty": `fail`, 1385 }, 1386 entryPaths: []string{"/entry.css"}, 1387 options: config.Options{ 1388 Mode: config.ModeBundle, 1389 SourceMap: config.SourceMapExternalWithoutComment, 1390 NeedsMetafile: true, 1391 ExtensionToLoader: map[string]config.Loader{ 1392 ".css": config.LoaderCSS, 1393 ".empty": config.LoaderEmpty, 1394 }, 1395 }, 1396 }) 1397 } 1398 1399 func TestExtensionlessLoaderJS(t *testing.T) { 1400 loader_suite.expectBundled(t, bundled{ 1401 files: map[string]string{ 1402 "/entry.js": ` 1403 import './what' 1404 `, 1405 "/what": `foo()`, 1406 }, 1407 entryPaths: []string{"/entry.js"}, 1408 options: config.Options{ 1409 Mode: config.ModeBundle, 1410 ExtensionToLoader: map[string]config.Loader{ 1411 ".js": config.LoaderJS, 1412 "": config.LoaderJS, 1413 }, 1414 }, 1415 }) 1416 } 1417 1418 func TestExtensionlessLoaderCSS(t *testing.T) { 1419 loader_suite.expectBundled(t, bundled{ 1420 files: map[string]string{ 1421 "/entry.css": ` 1422 @import './what'; 1423 `, 1424 "/what": `.foo { color: red }`, 1425 }, 1426 entryPaths: []string{"/entry.css"}, 1427 options: config.Options{ 1428 Mode: config.ModeBundle, 1429 ExtensionToLoader: map[string]config.Loader{ 1430 ".css": config.LoaderCSS, 1431 "": config.LoaderCSS, 1432 }, 1433 }, 1434 }) 1435 } 1436 1437 // Make sure custom entry point output names are respected for the copy loader 1438 func TestLoaderCopyEntryPointAdvanced(t *testing.T) { 1439 loader_suite.expectBundled(t, bundled{ 1440 files: map[string]string{ 1441 "/project/entry.js": ` 1442 import xyz from './xyz.copy' 1443 console.log(xyz) 1444 `, 1445 "/project/TEST FAILED.copy": `some stuff`, 1446 "/project/xyz.copy": `more stuff`, 1447 }, 1448 entryPathsAdvanced: []bundler.EntryPoint{ 1449 { 1450 InputPath: "/project/entry.js", 1451 OutputPath: "js/input/path", 1452 InputPathInFileNamespace: true, 1453 }, 1454 { 1455 InputPath: "/project/TEST FAILED.copy", 1456 OutputPath: "copy/input/path", 1457 InputPathInFileNamespace: true, 1458 }, 1459 }, 1460 options: config.Options{ 1461 Mode: config.ModeBundle, 1462 AbsOutputDir: "/out", 1463 ExtensionToLoader: map[string]config.Loader{ 1464 ".js": config.LoaderJS, 1465 ".copy": config.LoaderCopy, 1466 }, 1467 }, 1468 }) 1469 } 1470 1471 // Make sure we don't turn "src/index.copy" into "src.copy" for files copied 1472 // via the file loader. This is sometimes done for JS files to try to generate 1473 // more useful names because lots of developers name their code "index.js" due 1474 // to node's implicit "index.js" path resolution logic. 1475 func TestLoaderCopyUseIndex(t *testing.T) { 1476 loader_suite.expectBundled(t, bundled{ 1477 files: map[string]string{ 1478 "/Users/user/project/src/index.copy": `some stuff`, 1479 }, 1480 entryPaths: []string{"/Users/user/project/src/index.copy"}, 1481 options: config.Options{ 1482 Mode: config.ModeBundle, 1483 AbsOutputDir: "/out", 1484 ExtensionToLoader: map[string]config.Loader{ 1485 ".copy": config.LoaderCopy, 1486 }, 1487 }, 1488 }) 1489 } 1490 1491 // Make sure that if "outfile" is used, a file copied with the copy loader is 1492 // written out to that path. We don't want the file name to come from the 1493 // original source name instead of the "outfile" name, for example. 1494 func TestLoaderCopyExplicitOutputFile(t *testing.T) { 1495 loader_suite.expectBundled(t, bundled{ 1496 files: map[string]string{ 1497 "/project/TEST FAILED.copy": `some stuff`, 1498 }, 1499 entryPaths: []string{"/project/TEST FAILED.copy"}, 1500 options: config.Options{ 1501 Mode: config.ModeBundle, 1502 AbsOutputFile: "/out/this.worked", 1503 ExtensionToLoader: map[string]config.Loader{ 1504 ".copy": config.LoaderCopy, 1505 }, 1506 }, 1507 }) 1508 } 1509 1510 func TestLoaderCopyStartsWithDotAbsPath(t *testing.T) { 1511 loader_suite.expectBundled(t, bundled{ 1512 files: map[string]string{ 1513 "/project/src/.htaccess": `some stuff`, 1514 "/project/src/entry.js": `some.stuff()`, 1515 "/project/src/.ts": `foo as number`, 1516 }, 1517 entryPaths: []string{ 1518 "/project/src/.htaccess", 1519 "/project/src/entry.js", 1520 "/project/src/.ts", 1521 }, 1522 options: config.Options{ 1523 Mode: config.ModeBundle, 1524 AbsOutputDir: "/out", 1525 ExtensionToLoader: map[string]config.Loader{ 1526 ".js": config.LoaderJS, 1527 ".ts": config.LoaderTS, 1528 ".htaccess": config.LoaderCopy, 1529 }, 1530 }, 1531 }) 1532 } 1533 1534 func TestLoaderCopyStartsWithDotRelPath(t *testing.T) { 1535 loader_suite.expectBundled(t, bundled{ 1536 files: map[string]string{ 1537 "/project/src/.htaccess": `some stuff`, 1538 "/project/src/entry.js": `some.stuff()`, 1539 "/project/src/.ts": `foo as number`, 1540 }, 1541 entryPaths: []string{ 1542 "./.htaccess", 1543 "./entry.js", 1544 "./.ts", 1545 }, 1546 absWorkingDir: "/project/src", 1547 options: config.Options{ 1548 Mode: config.ModeBundle, 1549 AbsOutputDir: "/out", 1550 ExtensionToLoader: map[string]config.Loader{ 1551 ".js": config.LoaderJS, 1552 ".ts": config.LoaderTS, 1553 ".htaccess": config.LoaderCopy, 1554 }, 1555 }, 1556 }) 1557 } 1558 1559 func TestLoaderCopyWithInjectedFileNoBundle(t *testing.T) { 1560 loader_suite.expectBundled(t, bundled{ 1561 files: map[string]string{ 1562 "/src/entry.ts": `console.log('in entry.ts')`, 1563 "/src/inject.js": `console.log('in inject.js')`, 1564 }, 1565 entryPaths: []string{"/src/entry.ts"}, 1566 options: config.Options{ 1567 AbsOutputDir: "/out", 1568 InjectPaths: []string{"/src/inject.js"}, 1569 ExtensionToLoader: map[string]config.Loader{ 1570 ".ts": config.LoaderTS, 1571 ".js": config.LoaderCopy, 1572 }, 1573 }, 1574 expectedScanLog: `ERROR: Cannot inject "src/inject.js" with the "copy" loader without bundling enabled 1575 `, 1576 }) 1577 } 1578 1579 func TestLoaderCopyWithInjectedFileBundle(t *testing.T) { 1580 loader_suite.expectBundled(t, bundled{ 1581 files: map[string]string{ 1582 "/src/entry.ts": `console.log('in entry.ts')`, 1583 "/src/inject.js": `console.log('in inject.js')`, 1584 }, 1585 entryPaths: []string{"/src/entry.ts"}, 1586 options: config.Options{ 1587 Mode: config.ModeBundle, 1588 AbsOutputDir: "/out", 1589 InjectPaths: []string{"/src/inject.js"}, 1590 ExtensionToLoader: map[string]config.Loader{ 1591 ".ts": config.LoaderTS, 1592 ".js": config.LoaderCopy, 1593 }, 1594 }, 1595 }) 1596 } 1597 1598 func TestLoaderBundleWithImportAttributes(t *testing.T) { 1599 loader_suite.expectBundled(t, bundled{ 1600 files: map[string]string{ 1601 "/entry.js": ` 1602 import x from "./data.json" 1603 import y from "./data.json" assert { type: 'json' } 1604 import z from "./data.json" with { type: 'json' } 1605 console.log(x === y, x !== z) 1606 `, 1607 "/data.json": `{ "works": true }`, 1608 }, 1609 entryPaths: []string{"/entry.js"}, 1610 options: config.Options{ 1611 Mode: config.ModeBundle, 1612 AbsOutputFile: "/out.js", 1613 }, 1614 }) 1615 } 1616 1617 func TestLoaderBundleWithTypeJSONOnlyDefaultExport(t *testing.T) { 1618 loader_suite.expectBundled(t, bundled{ 1619 files: map[string]string{ 1620 "/entry.js": ` 1621 import x, {foo as x2} from "./data.json" 1622 import y, {foo as y2} from "./data.json" with { type: 'json' } 1623 `, 1624 "/data.json": `{ "foo": 123 }`, 1625 }, 1626 entryPaths: []string{"/entry.js"}, 1627 options: config.Options{ 1628 Mode: config.ModeBundle, 1629 AbsOutputFile: "/out.js", 1630 }, 1631 expectedCompileLog: `entry.js: ERROR: No matching export in "data.json with { type: 'json' }" for import "foo" 1632 `, 1633 }) 1634 } 1635 1636 func TestLoaderJSONPrototype(t *testing.T) { 1637 loader_suite.expectBundled(t, bundled{ 1638 files: map[string]string{ 1639 "/entry.js": ` 1640 import data from "./data.json" 1641 console.log(data) 1642 `, 1643 "/data.json": `{ 1644 "": "The property below should be converted to a computed property:", 1645 "__proto__": { "foo": "bar" } 1646 }`, 1647 }, 1648 entryPaths: []string{"/entry.js"}, 1649 options: config.Options{ 1650 Mode: config.ModeBundle, 1651 AbsOutputFile: "/out.js", 1652 MinifySyntax: true, 1653 }, 1654 }) 1655 } 1656 1657 func TestLoaderJSONPrototypeES5(t *testing.T) { 1658 loader_suite.expectBundled(t, bundled{ 1659 files: map[string]string{ 1660 "/entry.js": ` 1661 import data from "./data.json" 1662 console.log(data) 1663 `, 1664 "/data.json": `{ 1665 "": "The property below should NOT be converted to a computed property for ES5:", 1666 "__proto__": { "foo": "bar" } 1667 }`, 1668 }, 1669 entryPaths: []string{"/entry.js"}, 1670 options: config.Options{ 1671 Mode: config.ModeBundle, 1672 AbsOutputFile: "/out.js", 1673 MinifySyntax: true, 1674 UnsupportedJSFeatures: es(5), 1675 }, 1676 }) 1677 }