github.com/evanw/esbuild@v0.21.4/internal/bundler_tests/bundler_dce_test.go (about) 1 package bundler_tests 2 3 import ( 4 "regexp" 5 "testing" 6 7 "github.com/evanw/esbuild/internal/compat" 8 "github.com/evanw/esbuild/internal/config" 9 ) 10 11 var dce_suite = suite{ 12 name: "dce", 13 } 14 15 func TestPackageJsonSideEffectsFalseKeepNamedImportES6(t *testing.T) { 16 dce_suite.expectBundled(t, bundled{ 17 files: map[string]string{ 18 "/Users/user/project/src/entry.js": ` 19 import {foo} from "demo-pkg" 20 console.log(foo) 21 `, 22 "/Users/user/project/node_modules/demo-pkg/index.js": ` 23 export const foo = 123 24 console.log('hello') 25 `, 26 "/Users/user/project/node_modules/demo-pkg/package.json": ` 27 { 28 "sideEffects": false 29 } 30 `, 31 }, 32 entryPaths: []string{"/Users/user/project/src/entry.js"}, 33 options: config.Options{ 34 Mode: config.ModeBundle, 35 AbsOutputFile: "/out.js", 36 }, 37 }) 38 } 39 40 func TestPackageJsonSideEffectsFalseKeepNamedImportCommonJS(t *testing.T) { 41 dce_suite.expectBundled(t, bundled{ 42 files: map[string]string{ 43 "/Users/user/project/src/entry.js": ` 44 import {foo} from "demo-pkg" 45 console.log(foo) 46 `, 47 "/Users/user/project/node_modules/demo-pkg/index.js": ` 48 exports.foo = 123 49 console.log('hello') 50 `, 51 "/Users/user/project/node_modules/demo-pkg/package.json": ` 52 { 53 "sideEffects": false 54 } 55 `, 56 }, 57 entryPaths: []string{"/Users/user/project/src/entry.js"}, 58 options: config.Options{ 59 Mode: config.ModeBundle, 60 AbsOutputFile: "/out.js", 61 }, 62 }) 63 } 64 65 func TestPackageJsonSideEffectsFalseKeepStarImportES6(t *testing.T) { 66 dce_suite.expectBundled(t, bundled{ 67 files: map[string]string{ 68 "/Users/user/project/src/entry.js": ` 69 import * as ns from "demo-pkg" 70 console.log(ns) 71 `, 72 "/Users/user/project/node_modules/demo-pkg/index.js": ` 73 export const foo = 123 74 console.log('hello') 75 `, 76 "/Users/user/project/node_modules/demo-pkg/package.json": ` 77 { 78 "sideEffects": false 79 } 80 `, 81 }, 82 entryPaths: []string{"/Users/user/project/src/entry.js"}, 83 options: config.Options{ 84 Mode: config.ModeBundle, 85 AbsOutputFile: "/out.js", 86 }, 87 }) 88 } 89 90 func TestPackageJsonSideEffectsFalseKeepStarImportCommonJS(t *testing.T) { 91 dce_suite.expectBundled(t, bundled{ 92 files: map[string]string{ 93 "/Users/user/project/src/entry.js": ` 94 import * as ns from "demo-pkg" 95 console.log(ns) 96 `, 97 "/Users/user/project/node_modules/demo-pkg/index.js": ` 98 exports.foo = 123 99 console.log('hello') 100 `, 101 "/Users/user/project/node_modules/demo-pkg/package.json": ` 102 { 103 "sideEffects": false 104 } 105 `, 106 }, 107 entryPaths: []string{"/Users/user/project/src/entry.js"}, 108 options: config.Options{ 109 Mode: config.ModeBundle, 110 AbsOutputFile: "/out.js", 111 }, 112 }) 113 } 114 115 func TestPackageJsonSideEffectsTrueKeepES6(t *testing.T) { 116 dce_suite.expectBundled(t, bundled{ 117 files: map[string]string{ 118 "/Users/user/project/src/entry.js": ` 119 import "demo-pkg" 120 console.log('unused import') 121 `, 122 "/Users/user/project/node_modules/demo-pkg/index.js": ` 123 export const foo = 123 124 console.log('hello') 125 `, 126 "/Users/user/project/node_modules/demo-pkg/package.json": ` 127 { 128 "sideEffects": true 129 } 130 `, 131 }, 132 entryPaths: []string{"/Users/user/project/src/entry.js"}, 133 options: config.Options{ 134 Mode: config.ModeBundle, 135 AbsOutputFile: "/out.js", 136 }, 137 }) 138 } 139 140 func TestPackageJsonSideEffectsTrueKeepCommonJS(t *testing.T) { 141 dce_suite.expectBundled(t, bundled{ 142 files: map[string]string{ 143 "/Users/user/project/src/entry.js": ` 144 import "demo-pkg" 145 console.log('unused import') 146 `, 147 "/Users/user/project/node_modules/demo-pkg/index.js": ` 148 exports.foo = 123 149 console.log('hello') 150 `, 151 "/Users/user/project/node_modules/demo-pkg/package.json": ` 152 { 153 "sideEffects": true 154 } 155 `, 156 }, 157 entryPaths: []string{"/Users/user/project/src/entry.js"}, 158 options: config.Options{ 159 Mode: config.ModeBundle, 160 AbsOutputFile: "/out.js", 161 }, 162 }) 163 } 164 165 func TestPackageJsonSideEffectsFalseKeepBareImportAndRequireES6(t *testing.T) { 166 dce_suite.expectBundled(t, bundled{ 167 files: map[string]string{ 168 "/Users/user/project/src/entry.js": ` 169 import "demo-pkg" 170 require('demo-pkg') 171 console.log('unused import') 172 `, 173 "/Users/user/project/node_modules/demo-pkg/index.js": ` 174 export const foo = 123 175 console.log('hello') 176 `, 177 "/Users/user/project/node_modules/demo-pkg/package.json": ` 178 { 179 "sideEffects": false 180 } 181 `, 182 }, 183 entryPaths: []string{"/Users/user/project/src/entry.js"}, 184 options: config.Options{ 185 Mode: config.ModeBundle, 186 AbsOutputFile: "/out.js", 187 }, 188 expectedScanLog: `Users/user/project/src/entry.js: WARNING: Ignoring this import because "Users/user/project/node_modules/demo-pkg/index.js" was marked as having no side effects 189 Users/user/project/node_modules/demo-pkg/package.json: NOTE: "sideEffects" is false in the enclosing "package.json" file: 190 `, 191 }) 192 } 193 194 func TestPackageJsonSideEffectsFalseKeepBareImportAndRequireCommonJS(t *testing.T) { 195 dce_suite.expectBundled(t, bundled{ 196 files: map[string]string{ 197 "/Users/user/project/src/entry.js": ` 198 import "demo-pkg" 199 require('demo-pkg') 200 console.log('unused import') 201 `, 202 "/Users/user/project/node_modules/demo-pkg/index.js": ` 203 exports.foo = 123 204 console.log('hello') 205 `, 206 "/Users/user/project/node_modules/demo-pkg/package.json": ` 207 { 208 "sideEffects": false 209 } 210 `, 211 }, 212 entryPaths: []string{"/Users/user/project/src/entry.js"}, 213 options: config.Options{ 214 Mode: config.ModeBundle, 215 AbsOutputFile: "/out.js", 216 }, 217 expectedScanLog: `Users/user/project/src/entry.js: WARNING: Ignoring this import because "Users/user/project/node_modules/demo-pkg/index.js" was marked as having no side effects 218 Users/user/project/node_modules/demo-pkg/package.json: NOTE: "sideEffects" is false in the enclosing "package.json" file: 219 `, 220 }) 221 } 222 223 func TestPackageJsonSideEffectsFalseRemoveBareImportES6(t *testing.T) { 224 dce_suite.expectBundled(t, bundled{ 225 files: map[string]string{ 226 "/Users/user/project/src/entry.js": ` 227 import "demo-pkg" 228 console.log('unused import') 229 `, 230 "/Users/user/project/node_modules/demo-pkg/index.js": ` 231 export const foo = 123 232 console.log('hello') 233 `, 234 "/Users/user/project/node_modules/demo-pkg/package.json": ` 235 { 236 "sideEffects": false 237 } 238 `, 239 }, 240 entryPaths: []string{"/Users/user/project/src/entry.js"}, 241 options: config.Options{ 242 Mode: config.ModeBundle, 243 AbsOutputFile: "/out.js", 244 }, 245 expectedScanLog: `Users/user/project/src/entry.js: WARNING: Ignoring this import because "Users/user/project/node_modules/demo-pkg/index.js" was marked as having no side effects 246 Users/user/project/node_modules/demo-pkg/package.json: NOTE: "sideEffects" is false in the enclosing "package.json" file: 247 `, 248 }) 249 } 250 251 func TestPackageJsonSideEffectsFalseRemoveBareImportCommonJS(t *testing.T) { 252 dce_suite.expectBundled(t, bundled{ 253 files: map[string]string{ 254 "/Users/user/project/src/entry.js": ` 255 import "demo-pkg" 256 console.log('unused import') 257 `, 258 "/Users/user/project/node_modules/demo-pkg/index.js": ` 259 exports.foo = 123 260 console.log('hello') 261 `, 262 "/Users/user/project/node_modules/demo-pkg/package.json": ` 263 { 264 "sideEffects": false 265 } 266 `, 267 }, 268 entryPaths: []string{"/Users/user/project/src/entry.js"}, 269 options: config.Options{ 270 Mode: config.ModeBundle, 271 AbsOutputFile: "/out.js", 272 }, 273 expectedScanLog: `Users/user/project/src/entry.js: WARNING: Ignoring this import because "Users/user/project/node_modules/demo-pkg/index.js" was marked as having no side effects 274 Users/user/project/node_modules/demo-pkg/package.json: NOTE: "sideEffects" is false in the enclosing "package.json" file: 275 `, 276 }) 277 } 278 279 func TestPackageJsonSideEffectsFalseRemoveNamedImportES6(t *testing.T) { 280 dce_suite.expectBundled(t, bundled{ 281 files: map[string]string{ 282 "/Users/user/project/src/entry.js": ` 283 import {foo} from "demo-pkg" 284 console.log('unused import') 285 `, 286 "/Users/user/project/node_modules/demo-pkg/index.js": ` 287 export const foo = 123 288 console.log('hello') 289 `, 290 "/Users/user/project/node_modules/demo-pkg/package.json": ` 291 { 292 "sideEffects": false 293 } 294 `, 295 }, 296 entryPaths: []string{"/Users/user/project/src/entry.js"}, 297 options: config.Options{ 298 Mode: config.ModeBundle, 299 AbsOutputFile: "/out.js", 300 }, 301 }) 302 } 303 304 func TestPackageJsonSideEffectsFalseRemoveNamedImportCommonJS(t *testing.T) { 305 dce_suite.expectBundled(t, bundled{ 306 files: map[string]string{ 307 "/Users/user/project/src/entry.js": ` 308 import {foo} from "demo-pkg" 309 console.log('unused import') 310 `, 311 "/Users/user/project/node_modules/demo-pkg/index.js": ` 312 exports.foo = 123 313 console.log('hello') 314 `, 315 "/Users/user/project/node_modules/demo-pkg/package.json": ` 316 { 317 "sideEffects": false 318 } 319 `, 320 }, 321 entryPaths: []string{"/Users/user/project/src/entry.js"}, 322 options: config.Options{ 323 Mode: config.ModeBundle, 324 AbsOutputFile: "/out.js", 325 }, 326 }) 327 } 328 329 func TestPackageJsonSideEffectsFalseRemoveStarImportES6(t *testing.T) { 330 dce_suite.expectBundled(t, bundled{ 331 files: map[string]string{ 332 "/Users/user/project/src/entry.js": ` 333 import * as ns from "demo-pkg" 334 console.log('unused import') 335 `, 336 "/Users/user/project/node_modules/demo-pkg/index.js": ` 337 export const foo = 123 338 console.log('hello') 339 `, 340 "/Users/user/project/node_modules/demo-pkg/package.json": ` 341 { 342 "sideEffects": false 343 } 344 `, 345 }, 346 entryPaths: []string{"/Users/user/project/src/entry.js"}, 347 options: config.Options{ 348 Mode: config.ModeBundle, 349 AbsOutputFile: "/out.js", 350 }, 351 }) 352 } 353 354 func TestPackageJsonSideEffectsFalseRemoveStarImportCommonJS(t *testing.T) { 355 dce_suite.expectBundled(t, bundled{ 356 files: map[string]string{ 357 "/Users/user/project/src/entry.js": ` 358 import * as ns from "demo-pkg" 359 console.log('unused import') 360 `, 361 "/Users/user/project/node_modules/demo-pkg/index.js": ` 362 exports.foo = 123 363 console.log('hello') 364 `, 365 "/Users/user/project/node_modules/demo-pkg/package.json": ` 366 { 367 "sideEffects": false 368 } 369 `, 370 }, 371 entryPaths: []string{"/Users/user/project/src/entry.js"}, 372 options: config.Options{ 373 Mode: config.ModeBundle, 374 AbsOutputFile: "/out.js", 375 }, 376 }) 377 } 378 379 func TestPackageJsonSideEffectsArrayRemove(t *testing.T) { 380 dce_suite.expectBundled(t, bundled{ 381 files: map[string]string{ 382 "/Users/user/project/src/entry.js": ` 383 import {foo} from "demo-pkg" 384 console.log('unused import') 385 `, 386 "/Users/user/project/node_modules/demo-pkg/index.js": ` 387 export const foo = 123 388 console.log('hello') 389 `, 390 "/Users/user/project/node_modules/demo-pkg/package.json": ` 391 { 392 "sideEffects": [] 393 } 394 `, 395 }, 396 entryPaths: []string{"/Users/user/project/src/entry.js"}, 397 options: config.Options{ 398 Mode: config.ModeBundle, 399 AbsOutputFile: "/out.js", 400 }, 401 }) 402 } 403 404 func TestPackageJsonSideEffectsArrayKeep(t *testing.T) { 405 dce_suite.expectBundled(t, bundled{ 406 files: map[string]string{ 407 "/Users/user/project/src/entry.js": ` 408 import {foo} from "demo-pkg" 409 console.log('unused import') 410 `, 411 "/Users/user/project/node_modules/demo-pkg/index.js": ` 412 export const foo = 123 413 console.log('hello') 414 `, 415 "/Users/user/project/node_modules/demo-pkg/package.json": ` 416 { 417 "sideEffects": ["./index.js"] 418 } 419 `, 420 }, 421 entryPaths: []string{"/Users/user/project/src/entry.js"}, 422 options: config.Options{ 423 Mode: config.ModeBundle, 424 AbsOutputFile: "/out.js", 425 }, 426 }) 427 } 428 429 func TestPackageJsonSideEffectsArrayKeepMainUseModule(t *testing.T) { 430 dce_suite.expectBundled(t, bundled{ 431 files: map[string]string{ 432 "/Users/user/project/src/entry.js": ` 433 import {foo} from "demo-pkg" 434 console.log('unused import') 435 `, 436 "/Users/user/project/node_modules/demo-pkg/index-main.js": ` 437 export const foo = 123 438 console.log('TEST FAILED') 439 `, 440 "/Users/user/project/node_modules/demo-pkg/index-module.js": ` 441 export const foo = 123 442 console.log('TEST FAILED') 443 `, 444 "/Users/user/project/node_modules/demo-pkg/package.json": ` 445 { 446 "main": "index-main.js", 447 "module": "index-module.js", 448 "sideEffects": ["./index-main.js"] 449 } 450 `, 451 }, 452 entryPaths: []string{"/Users/user/project/src/entry.js"}, 453 options: config.Options{ 454 Mode: config.ModeBundle, 455 AbsOutputFile: "/out.js", 456 MainFields: []string{"module"}, 457 }, 458 }) 459 } 460 461 func TestPackageJsonSideEffectsArrayKeepMainUseMain(t *testing.T) { 462 dce_suite.expectBundled(t, bundled{ 463 files: map[string]string{ 464 "/Users/user/project/src/entry.js": ` 465 import {foo} from "demo-pkg" 466 console.log('unused import') 467 `, 468 "/Users/user/project/node_modules/demo-pkg/index-main.js": ` 469 export const foo = 123 470 console.log('this should be kept') 471 `, 472 "/Users/user/project/node_modules/demo-pkg/index-module.js": ` 473 export const foo = 123 474 console.log('TEST FAILED') 475 `, 476 "/Users/user/project/node_modules/demo-pkg/package.json": ` 477 { 478 "main": "index-main.js", 479 "module": "index-module.js", 480 "sideEffects": ["./index-main.js"] 481 } 482 `, 483 }, 484 entryPaths: []string{"/Users/user/project/src/entry.js"}, 485 options: config.Options{ 486 Mode: config.ModeBundle, 487 AbsOutputFile: "/out.js", 488 MainFields: []string{"main"}, 489 }, 490 }) 491 } 492 493 func TestPackageJsonSideEffectsArrayKeepMainImplicitModule(t *testing.T) { 494 dce_suite.expectBundled(t, bundled{ 495 files: map[string]string{ 496 "/Users/user/project/src/entry.js": ` 497 import {foo} from "demo-pkg" 498 console.log('unused import') 499 `, 500 "/Users/user/project/node_modules/demo-pkg/index-main.js": ` 501 export const foo = 123 502 console.log('TEST FAILED') 503 `, 504 "/Users/user/project/node_modules/demo-pkg/index-module.js": ` 505 export const foo = 123 506 console.log('TEST FAILED') 507 `, 508 "/Users/user/project/node_modules/demo-pkg/package.json": ` 509 { 510 "main": "index-main.js", 511 "module": "index-module.js", 512 "sideEffects": ["./index-main.js"] 513 } 514 `, 515 }, 516 entryPaths: []string{"/Users/user/project/src/entry.js"}, 517 options: config.Options{ 518 Mode: config.ModeBundle, 519 AbsOutputFile: "/out.js", 520 }, 521 }) 522 } 523 524 func TestPackageJsonSideEffectsArrayKeepMainImplicitMain(t *testing.T) { 525 dce_suite.expectBundled(t, bundled{ 526 files: map[string]string{ 527 "/Users/user/project/src/entry.js": ` 528 import {foo} from "demo-pkg" 529 import "./require-demo-pkg" 530 console.log('unused import') 531 `, 532 "/Users/user/project/src/require-demo-pkg.js": ` 533 // This causes "index-main.js" to be selected 534 require('demo-pkg') 535 `, 536 "/Users/user/project/node_modules/demo-pkg/index-main.js": ` 537 export const foo = 123 538 console.log('this should be kept') 539 `, 540 "/Users/user/project/node_modules/demo-pkg/index-module.js": ` 541 export const foo = 123 542 console.log('TEST FAILED') 543 `, 544 "/Users/user/project/node_modules/demo-pkg/package.json": ` 545 { 546 "main": "index-main.js", 547 "module": "index-module.js", 548 "sideEffects": ["./index-main.js"] 549 } 550 `, 551 }, 552 entryPaths: []string{"/Users/user/project/src/entry.js"}, 553 options: config.Options{ 554 Mode: config.ModeBundle, 555 AbsOutputFile: "/out.js", 556 }, 557 }) 558 } 559 560 func TestPackageJsonSideEffectsArrayKeepModuleUseModule(t *testing.T) { 561 dce_suite.expectBundled(t, bundled{ 562 files: map[string]string{ 563 "/Users/user/project/src/entry.js": ` 564 import {foo} from "demo-pkg" 565 console.log('unused import') 566 `, 567 "/Users/user/project/node_modules/demo-pkg/index-main.js": ` 568 export const foo = 123 569 console.log('TEST FAILED') 570 `, 571 "/Users/user/project/node_modules/demo-pkg/index-module.js": ` 572 export const foo = 123 573 console.log('this should be kept') 574 `, 575 "/Users/user/project/node_modules/demo-pkg/package.json": ` 576 { 577 "main": "index-main.js", 578 "module": "index-module.js", 579 "sideEffects": ["./index-module.js"] 580 } 581 `, 582 }, 583 entryPaths: []string{"/Users/user/project/src/entry.js"}, 584 options: config.Options{ 585 Mode: config.ModeBundle, 586 AbsOutputFile: "/out.js", 587 MainFields: []string{"module"}, 588 }, 589 }) 590 } 591 592 func TestPackageJsonSideEffectsArrayKeepModuleUseMain(t *testing.T) { 593 dce_suite.expectBundled(t, bundled{ 594 files: map[string]string{ 595 "/Users/user/project/src/entry.js": ` 596 import {foo} from "demo-pkg" 597 console.log('unused import') 598 `, 599 "/Users/user/project/node_modules/demo-pkg/index-main.js": ` 600 export const foo = 123 601 console.log('TEST FAILED') 602 `, 603 "/Users/user/project/node_modules/demo-pkg/index-module.js": ` 604 export const foo = 123 605 console.log('TEST FAILED') 606 `, 607 "/Users/user/project/node_modules/demo-pkg/package.json": ` 608 { 609 "main": "index-main.js", 610 "module": "index-module.js", 611 "sideEffects": ["./index-module.js"] 612 } 613 `, 614 }, 615 entryPaths: []string{"/Users/user/project/src/entry.js"}, 616 options: config.Options{ 617 Mode: config.ModeBundle, 618 AbsOutputFile: "/out.js", 619 MainFields: []string{"main"}, 620 }, 621 }) 622 } 623 624 func TestPackageJsonSideEffectsArrayKeepModuleImplicitModule(t *testing.T) { 625 dce_suite.expectBundled(t, bundled{ 626 files: map[string]string{ 627 "/Users/user/project/src/entry.js": ` 628 import {foo} from "demo-pkg" 629 console.log('unused import') 630 `, 631 "/Users/user/project/node_modules/demo-pkg/index-main.js": ` 632 export const foo = 123 633 console.log('TEST FAILED') 634 `, 635 "/Users/user/project/node_modules/demo-pkg/index-module.js": ` 636 export const foo = 123 637 console.log('this should be kept') 638 `, 639 "/Users/user/project/node_modules/demo-pkg/package.json": ` 640 { 641 "main": "index-main.js", 642 "module": "index-module.js", 643 "sideEffects": ["./index-module.js"] 644 } 645 `, 646 }, 647 entryPaths: []string{"/Users/user/project/src/entry.js"}, 648 options: config.Options{ 649 Mode: config.ModeBundle, 650 AbsOutputFile: "/out.js", 651 }, 652 }) 653 } 654 655 func TestPackageJsonSideEffectsArrayKeepModuleImplicitMain(t *testing.T) { 656 dce_suite.expectBundled(t, bundled{ 657 files: map[string]string{ 658 "/Users/user/project/src/entry.js": ` 659 import {foo} from "demo-pkg" 660 import "./require-demo-pkg" 661 console.log('unused import') 662 `, 663 "/Users/user/project/src/require-demo-pkg.js": ` 664 // This causes "index-main.js" to be selected 665 require('demo-pkg') 666 `, 667 "/Users/user/project/node_modules/demo-pkg/index-main.js": ` 668 export const foo = 123 669 console.log('this should be kept') 670 `, 671 "/Users/user/project/node_modules/demo-pkg/index-module.js": ` 672 export const foo = 123 673 console.log('TEST FAILED') 674 `, 675 "/Users/user/project/node_modules/demo-pkg/package.json": ` 676 { 677 "main": "index-main.js", 678 "module": "index-module.js", 679 "sideEffects": ["./index-module.js"] 680 } 681 `, 682 }, 683 entryPaths: []string{"/Users/user/project/src/entry.js"}, 684 options: config.Options{ 685 Mode: config.ModeBundle, 686 AbsOutputFile: "/out.js", 687 }, 688 }) 689 } 690 691 func TestPackageJsonSideEffectsArrayGlob(t *testing.T) { 692 dce_suite.expectBundled(t, bundled{ 693 files: map[string]string{ 694 "/Users/user/project/src/entry.js": ` 695 import "demo-pkg/keep/this/file" 696 import "demo-pkg/remove/this/file" 697 `, 698 "/Users/user/project/node_modules/demo-pkg/keep/this/file.js": ` 699 console.log('this should be kept') 700 `, 701 "/Users/user/project/node_modules/demo-pkg/remove/this/file.js": ` 702 console.log('TEST FAILED') 703 `, 704 "/Users/user/project/node_modules/demo-pkg/package.json": ` 705 { 706 "sideEffects": [ 707 "./ke?p/*/file.js", 708 "./remove/this/file.j", 709 "./re?ve/this/file.js" 710 ] 711 } 712 `, 713 }, 714 entryPaths: []string{"/Users/user/project/src/entry.js"}, 715 options: config.Options{ 716 Mode: config.ModeBundle, 717 AbsOutputFile: "/out.js", 718 }, 719 expectedScanLog: `Users/user/project/src/entry.js: WARNING: Ignoring this import because "Users/user/project/node_modules/demo-pkg/remove/this/file.js" was marked as having no side effects 720 Users/user/project/node_modules/demo-pkg/package.json: NOTE: It was excluded from the "sideEffects" array in the enclosing "package.json" file: 721 `, 722 }) 723 } 724 725 func TestPackageJsonSideEffectsNestedDirectoryRemove(t *testing.T) { 726 dce_suite.expectBundled(t, bundled{ 727 files: map[string]string{ 728 "/Users/user/project/src/entry.js": ` 729 import {foo} from "demo-pkg/a/b/c" 730 console.log('unused import') 731 `, 732 "/Users/user/project/node_modules/demo-pkg/package.json": ` 733 { 734 "sideEffects": false 735 } 736 `, 737 "/Users/user/project/node_modules/demo-pkg/a/b/c/index.js": ` 738 export const foo = 123 739 console.log('hello') 740 `, 741 }, 742 entryPaths: []string{"/Users/user/project/src/entry.js"}, 743 options: config.Options{ 744 Mode: config.ModeBundle, 745 AbsOutputFile: "/out.js", 746 }, 747 }) 748 } 749 750 func TestPackageJsonSideEffectsKeepExportDefaultExpr(t *testing.T) { 751 dce_suite.expectBundled(t, bundled{ 752 files: map[string]string{ 753 "/Users/user/project/src/entry.js": ` 754 import foo from "demo-pkg" 755 console.log(foo) 756 `, 757 "/Users/user/project/node_modules/demo-pkg/index.js": ` 758 export default exprWithSideEffects() 759 `, 760 "/Users/user/project/node_modules/demo-pkg/package.json": ` 761 { 762 "sideEffects": false 763 } 764 `, 765 }, 766 entryPaths: []string{"/Users/user/project/src/entry.js"}, 767 options: config.Options{ 768 Mode: config.ModeBundle, 769 AbsOutputFile: "/out.js", 770 }, 771 }) 772 } 773 774 func TestPackageJsonSideEffectsFalseNoWarningInNodeModulesIssue999(t *testing.T) { 775 dce_suite.expectBundled(t, bundled{ 776 files: map[string]string{ 777 "/Users/user/project/src/entry.js": ` 778 import "demo-pkg" 779 console.log('used import') 780 `, 781 "/Users/user/project/node_modules/demo-pkg/index.js": ` 782 import "demo-pkg2" 783 console.log('unused import') 784 `, 785 "/Users/user/project/node_modules/demo-pkg2/index.js": ` 786 export const foo = 123 787 console.log('hello') 788 `, 789 "/Users/user/project/node_modules/demo-pkg2/package.json": ` 790 { 791 "sideEffects": false 792 } 793 `, 794 }, 795 entryPaths: []string{"/Users/user/project/src/entry.js"}, 796 options: config.Options{ 797 Mode: config.ModeBundle, 798 AbsOutputFile: "/out.js", 799 }, 800 }) 801 } 802 803 func TestPackageJsonSideEffectsFalseIntermediateFilesUnused(t *testing.T) { 804 dce_suite.expectBundled(t, bundled{ 805 files: map[string]string{ 806 "/Users/user/project/src/entry.js": ` 807 import {foo} from "demo-pkg" 808 `, 809 "/Users/user/project/node_modules/demo-pkg/index.js": ` 810 export {foo} from "./foo.js" 811 throw 'REMOVE THIS' 812 `, 813 "/Users/user/project/node_modules/demo-pkg/foo.js": ` 814 export const foo = 123 815 `, 816 "/Users/user/project/node_modules/demo-pkg/package.json": ` 817 { "sideEffects": false } 818 `, 819 }, 820 entryPaths: []string{"/Users/user/project/src/entry.js"}, 821 options: config.Options{ 822 Mode: config.ModeBundle, 823 AbsOutputFile: "/out.js", 824 }, 825 }) 826 } 827 828 func TestPackageJsonSideEffectsFalseIntermediateFilesUsed(t *testing.T) { 829 dce_suite.expectBundled(t, bundled{ 830 files: map[string]string{ 831 "/Users/user/project/src/entry.js": ` 832 import {foo} from "demo-pkg" 833 console.log(foo) 834 `, 835 "/Users/user/project/node_modules/demo-pkg/index.js": ` 836 export {foo} from "./foo.js" 837 throw 'keep this' 838 `, 839 "/Users/user/project/node_modules/demo-pkg/foo.js": ` 840 export const foo = 123 841 `, 842 "/Users/user/project/node_modules/demo-pkg/package.json": ` 843 { "sideEffects": false } 844 `, 845 }, 846 entryPaths: []string{"/Users/user/project/src/entry.js"}, 847 options: config.Options{ 848 Mode: config.ModeBundle, 849 AbsOutputFile: "/out.js", 850 }, 851 }) 852 } 853 854 func TestPackageJsonSideEffectsFalseIntermediateFilesChainAll(t *testing.T) { 855 dce_suite.expectBundled(t, bundled{ 856 files: map[string]string{ 857 "/Users/user/project/src/entry.js": ` 858 import {foo} from "a" 859 console.log(foo) 860 `, 861 "/Users/user/project/node_modules/a/index.js": ` 862 export {foo} from "b" 863 `, 864 "/Users/user/project/node_modules/a/package.json": ` 865 { "sideEffects": false } 866 `, 867 "/Users/user/project/node_modules/b/index.js": ` 868 export {foo} from "c" 869 throw 'keep this' 870 `, 871 "/Users/user/project/node_modules/b/package.json": ` 872 { "sideEffects": false } 873 `, 874 "/Users/user/project/node_modules/c/index.js": ` 875 export {foo} from "d" 876 `, 877 "/Users/user/project/node_modules/c/package.json": ` 878 { "sideEffects": false } 879 `, 880 "/Users/user/project/node_modules/d/index.js": ` 881 export const foo = 123 882 `, 883 "/Users/user/project/node_modules/d/package.json": ` 884 { "sideEffects": false } 885 `, 886 }, 887 entryPaths: []string{"/Users/user/project/src/entry.js"}, 888 options: config.Options{ 889 Mode: config.ModeBundle, 890 AbsOutputFile: "/out.js", 891 }, 892 }) 893 } 894 895 func TestPackageJsonSideEffectsFalseIntermediateFilesChainOne(t *testing.T) { 896 dce_suite.expectBundled(t, bundled{ 897 files: map[string]string{ 898 "/Users/user/project/src/entry.js": ` 899 import {foo} from "a" 900 console.log(foo) 901 `, 902 "/Users/user/project/node_modules/a/index.js": ` 903 export {foo} from "b" 904 `, 905 "/Users/user/project/node_modules/b/index.js": ` 906 export {foo} from "c" 907 throw 'keep this' 908 `, 909 "/Users/user/project/node_modules/b/package.json": ` 910 { "sideEffects": false } 911 `, 912 "/Users/user/project/node_modules/c/index.js": ` 913 export {foo} from "d" 914 `, 915 "/Users/user/project/node_modules/d/index.js": ` 916 export const foo = 123 917 `, 918 }, 919 entryPaths: []string{"/Users/user/project/src/entry.js"}, 920 options: config.Options{ 921 Mode: config.ModeBundle, 922 AbsOutputFile: "/out.js", 923 }, 924 }) 925 } 926 927 func TestPackageJsonSideEffectsFalseIntermediateFilesDiamond(t *testing.T) { 928 dce_suite.expectBundled(t, bundled{ 929 files: map[string]string{ 930 "/Users/user/project/src/entry.js": ` 931 import {foo} from "a" 932 console.log(foo) 933 `, 934 "/Users/user/project/node_modules/a/index.js": ` 935 export * from "b1" 936 export * from "b2" 937 `, 938 "/Users/user/project/node_modules/b1/index.js": ` 939 export {foo} from "c" 940 throw 'keep this 1' 941 `, 942 "/Users/user/project/node_modules/b1/package.json": ` 943 { "sideEffects": false } 944 `, 945 "/Users/user/project/node_modules/b2/index.js": ` 946 export {foo} from "c" 947 throw 'keep this 2' 948 `, 949 "/Users/user/project/node_modules/b2/package.json": ` 950 { "sideEffects": false } 951 `, 952 "/Users/user/project/node_modules/c/index.js": ` 953 export {foo} from "d" 954 `, 955 "/Users/user/project/node_modules/d/index.js": ` 956 export const foo = 123 957 `, 958 }, 959 entryPaths: []string{"/Users/user/project/src/entry.js"}, 960 options: config.Options{ 961 Mode: config.ModeBundle, 962 AbsOutputFile: "/out.js", 963 }, 964 }) 965 } 966 967 func TestPackageJsonSideEffectsFalseOneFork(t *testing.T) { 968 dce_suite.expectBundled(t, bundled{ 969 files: map[string]string{ 970 "/Users/user/project/src/entry.js": ` 971 import("a").then(x => assert(x.foo === "foo")) 972 `, 973 "/Users/user/project/node_modules/a/index.js": ` 974 export {foo} from "b" 975 `, 976 "/Users/user/project/node_modules/b/index.js": ` 977 export {foo, bar} from "c" 978 export {baz} from "d" 979 `, 980 "/Users/user/project/node_modules/b/package.json": ` 981 { "sideEffects": false } 982 `, 983 "/Users/user/project/node_modules/c/index.js": ` 984 export let foo = "foo" 985 export let bar = "bar" 986 `, 987 "/Users/user/project/node_modules/d/index.js": ` 988 export let baz = "baz" 989 `, 990 }, 991 entryPaths: []string{"/Users/user/project/src/entry.js"}, 992 options: config.Options{ 993 Mode: config.ModeBundle, 994 AbsOutputFile: "/out.js", 995 }, 996 }) 997 } 998 999 func TestPackageJsonSideEffectsFalseAllFork(t *testing.T) { 1000 dce_suite.expectBundled(t, bundled{ 1001 files: map[string]string{ 1002 "/Users/user/project/src/entry.js": ` 1003 import("a").then(x => assert(x.foo === "foo")) 1004 `, 1005 "/Users/user/project/node_modules/a/index.js": ` 1006 export {foo} from "b" 1007 `, 1008 "/Users/user/project/node_modules/b/index.js": ` 1009 export {foo, bar} from "c" 1010 export {baz} from "d" 1011 `, 1012 "/Users/user/project/node_modules/b/package.json": ` 1013 { "sideEffects": false } 1014 `, 1015 "/Users/user/project/node_modules/c/index.js": ` 1016 export let foo = "foo" 1017 export let bar = "bar" 1018 `, 1019 "/Users/user/project/node_modules/c/package.json": ` 1020 { "sideEffects": false } 1021 `, 1022 "/Users/user/project/node_modules/d/index.js": ` 1023 export let baz = "baz" 1024 `, 1025 "/Users/user/project/node_modules/d/package.json": ` 1026 { "sideEffects": false } 1027 `, 1028 }, 1029 entryPaths: []string{"/Users/user/project/src/entry.js"}, 1030 options: config.Options{ 1031 Mode: config.ModeBundle, 1032 AbsOutputFile: "/out.js", 1033 }, 1034 }) 1035 } 1036 1037 func TestJSONLoaderRemoveUnused(t *testing.T) { 1038 dce_suite.expectBundled(t, bundled{ 1039 files: map[string]string{ 1040 "/entry.js": ` 1041 import unused from "./example.json" 1042 console.log('unused import') 1043 `, 1044 "/example.json": `{"data": true}`, 1045 }, 1046 entryPaths: []string{"/entry.js"}, 1047 options: config.Options{ 1048 Mode: config.ModeBundle, 1049 AbsOutputFile: "/out.js", 1050 }, 1051 }) 1052 } 1053 1054 func TestTextLoaderRemoveUnused(t *testing.T) { 1055 dce_suite.expectBundled(t, bundled{ 1056 files: map[string]string{ 1057 "/entry.js": ` 1058 import unused from "./example.txt" 1059 console.log('unused import') 1060 `, 1061 "/example.txt": `some data`, 1062 }, 1063 entryPaths: []string{"/entry.js"}, 1064 options: config.Options{ 1065 Mode: config.ModeBundle, 1066 AbsOutputFile: "/out.js", 1067 }, 1068 }) 1069 } 1070 1071 func TestBase64LoaderRemoveUnused(t *testing.T) { 1072 dce_suite.expectBundled(t, bundled{ 1073 files: map[string]string{ 1074 "/entry.js": ` 1075 import unused from "./example.data" 1076 console.log('unused import') 1077 `, 1078 "/example.data": `some data`, 1079 }, 1080 entryPaths: []string{"/entry.js"}, 1081 options: config.Options{ 1082 Mode: config.ModeBundle, 1083 AbsOutputFile: "/out.js", 1084 ExtensionToLoader: map[string]config.Loader{ 1085 ".js": config.LoaderJS, 1086 ".data": config.LoaderBase64, 1087 }, 1088 }, 1089 }) 1090 } 1091 1092 func TestDataURLLoaderRemoveUnused(t *testing.T) { 1093 dce_suite.expectBundled(t, bundled{ 1094 files: map[string]string{ 1095 "/entry.js": ` 1096 import unused from "./example.data" 1097 console.log('unused import') 1098 `, 1099 "/example.data": `some data`, 1100 }, 1101 entryPaths: []string{"/entry.js"}, 1102 options: config.Options{ 1103 Mode: config.ModeBundle, 1104 AbsOutputFile: "/out.js", 1105 ExtensionToLoader: map[string]config.Loader{ 1106 ".js": config.LoaderJS, 1107 ".data": config.LoaderDataURL, 1108 }, 1109 }, 1110 }) 1111 } 1112 1113 func TestFileLoaderRemoveUnused(t *testing.T) { 1114 dce_suite.expectBundled(t, bundled{ 1115 files: map[string]string{ 1116 "/entry.js": ` 1117 import unused from "./example.data" 1118 console.log('unused import') 1119 `, 1120 "/example.data": `some data`, 1121 }, 1122 entryPaths: []string{"/entry.js"}, 1123 options: config.Options{ 1124 Mode: config.ModeBundle, 1125 AbsOutputFile: "/out.js", 1126 ExtensionToLoader: map[string]config.Loader{ 1127 ".js": config.LoaderJS, 1128 ".data": config.LoaderFile, 1129 }, 1130 }, 1131 }) 1132 } 1133 1134 func TestRemoveUnusedImportMeta(t *testing.T) { 1135 dce_suite.expectBundled(t, bundled{ 1136 files: map[string]string{ 1137 "/entry.js": ` 1138 function foo() { 1139 console.log(import.meta.url, import.meta.path) 1140 } 1141 console.log('foo is unused') 1142 `, 1143 }, 1144 entryPaths: []string{"/entry.js"}, 1145 options: config.Options{ 1146 Mode: config.ModeBundle, 1147 AbsOutputFile: "/out.js", 1148 }, 1149 }) 1150 } 1151 1152 func TestRemoveUnusedPureCommentCalls(t *testing.T) { 1153 dce_suite.expectBundled(t, bundled{ 1154 files: map[string]string{ 1155 "/entry.js": ` 1156 function bar() {} 1157 let bare = foo(bar); 1158 1159 let at_yes = /* @__PURE__ */ foo(bar); 1160 let at_no = /* @__PURE__ */ foo(bar()); 1161 let new_at_yes = /* @__PURE__ */ new foo(bar); 1162 let new_at_no = /* @__PURE__ */ new foo(bar()); 1163 1164 let nospace_at_yes = /*@__PURE__*/ foo(bar); 1165 let nospace_at_no = /*@__PURE__*/ foo(bar()); 1166 let nospace_new_at_yes = /*@__PURE__*/ new foo(bar); 1167 let nospace_new_at_no = /*@__PURE__*/ new foo(bar()); 1168 1169 let num_yes = /* #__PURE__ */ foo(bar); 1170 let num_no = /* #__PURE__ */ foo(bar()); 1171 let new_num_yes = /* #__PURE__ */ new foo(bar); 1172 let new_num_no = /* #__PURE__ */ new foo(bar()); 1173 1174 let nospace_num_yes = /*#__PURE__*/ foo(bar); 1175 let nospace_num_no = /*#__PURE__*/ foo(bar()); 1176 let nospace_new_num_yes = /*#__PURE__*/ new foo(bar); 1177 let nospace_new_num_no = /*#__PURE__*/ new foo(bar()); 1178 1179 let dot_yes = /* @__PURE__ */ foo(sideEffect()).dot(bar); 1180 let dot_no = /* @__PURE__ */ foo(sideEffect()).dot(bar()); 1181 let new_dot_yes = /* @__PURE__ */ new foo(sideEffect()).dot(bar); 1182 let new_dot_no = /* @__PURE__ */ new foo(sideEffect()).dot(bar()); 1183 1184 let nested_yes = [1, /* @__PURE__ */ foo(bar), 2]; 1185 let nested_no = [1, /* @__PURE__ */ foo(bar()), 2]; 1186 let new_nested_yes = [1, /* @__PURE__ */ new foo(bar), 2]; 1187 let new_nested_no = [1, /* @__PURE__ */ new foo(bar()), 2]; 1188 1189 let single_at_yes = // @__PURE__ 1190 foo(bar); 1191 let single_at_no = // @__PURE__ 1192 foo(bar()); 1193 let new_single_at_yes = // @__PURE__ 1194 new foo(bar); 1195 let new_single_at_no = // @__PURE__ 1196 new foo(bar()); 1197 1198 let single_num_yes = // #__PURE__ 1199 foo(bar); 1200 let single_num_no = // #__PURE__ 1201 foo(bar()); 1202 let new_single_num_yes = // #__PURE__ 1203 new foo(bar); 1204 let new_single_num_no = // #__PURE__ 1205 new foo(bar()); 1206 1207 let bad_no = /* __PURE__ */ foo(bar); 1208 let new_bad_no = /* __PURE__ */ new foo(bar); 1209 1210 let parens_no = (/* @__PURE__ */ foo)(bar); 1211 let new_parens_no = new (/* @__PURE__ */ foo)(bar); 1212 1213 let exp_no = /* @__PURE__ */ foo() ** foo(); 1214 let new_exp_no = /* @__PURE__ */ new foo() ** foo(); 1215 `, 1216 }, 1217 entryPaths: []string{"/entry.js"}, 1218 options: config.Options{ 1219 Mode: config.ModeBundle, 1220 AbsOutputFile: "/out.js", 1221 }, 1222 }) 1223 } 1224 1225 func TestRemoveUnusedNoSideEffectsTaggedTemplates(t *testing.T) { 1226 dce_suite.expectBundled(t, bundled{ 1227 files: map[string]string{ 1228 "/entry.js": ` 1229 // @__NO_SIDE_EFFECTS__ 1230 function foo() {} 1231 1232 foo` + "`remove`" + `; 1233 foo` + "`remove${null}`" + `; 1234 foo` + "`remove${123}`" + `; 1235 1236 use(foo` + "`keep`" + `); 1237 foo` + "`remove this part ${keep} and this ${alsoKeep}`" + `; 1238 ` + "`remove this part ${keep} and this ${alsoKeep}`" + `; 1239 `, 1240 }, 1241 entryPaths: []string{"/entry.js"}, 1242 options: config.Options{ 1243 Mode: config.ModeBundle, 1244 AbsOutputFile: "/out.js", 1245 MinifySyntax: true, 1246 }, 1247 }) 1248 } 1249 1250 func TestTreeShakingReactElements(t *testing.T) { 1251 dce_suite.expectBundled(t, bundled{ 1252 files: map[string]string{ 1253 "/entry.jsx": ` 1254 function Foo() {} 1255 1256 let a = <div/> 1257 let b = <Foo>{a}</Foo> 1258 let c = <>{b}</> 1259 1260 let d = <div/> 1261 let e = <Foo>{d}</Foo> 1262 let f = <>{e}</> 1263 console.log(f) 1264 `, 1265 }, 1266 entryPaths: []string{"/entry.jsx"}, 1267 options: config.Options{ 1268 Mode: config.ModeBundle, 1269 AbsOutputFile: "/out.js", 1270 }, 1271 }) 1272 } 1273 1274 func TestDisableTreeShaking(t *testing.T) { 1275 defines := config.ProcessDefines(map[string]config.DefineData{ 1276 "pure": {Flags: config.CallCanBeUnwrappedIfUnused}, 1277 "some.fn": {Flags: config.CallCanBeUnwrappedIfUnused}, 1278 }) 1279 dce_suite.expectBundled(t, bundled{ 1280 files: map[string]string{ 1281 "/entry.jsx": ` 1282 import './remove-me' 1283 function RemoveMe1() {} 1284 let removeMe2 = 0 1285 class RemoveMe3 {} 1286 1287 import './keep-me' 1288 function KeepMe1() {} 1289 let keepMe2 = <KeepMe1/> 1290 function keepMe3() { console.log('side effects') } 1291 let keepMe4 = /* @__PURE__ */ keepMe3() 1292 let keepMe5 = pure() 1293 let keepMe6 = some.fn() 1294 `, 1295 "/remove-me.js": ` 1296 export default 'unused' 1297 `, 1298 "/keep-me/index.js": ` 1299 console.log('side effects') 1300 `, 1301 "/keep-me/package.json": ` 1302 { "sideEffects": false } 1303 `, 1304 }, 1305 entryPaths: []string{"/entry.jsx"}, 1306 options: config.Options{ 1307 Mode: config.ModeBundle, 1308 AbsOutputFile: "/out.js", 1309 IgnoreDCEAnnotations: true, 1310 Defines: &defines, 1311 }, 1312 }) 1313 } 1314 1315 func TestDeadCodeFollowingJump(t *testing.T) { 1316 dce_suite.expectBundled(t, bundled{ 1317 files: map[string]string{ 1318 "/entry.js": ` 1319 function testReturn() { 1320 if (true) return y + z() 1321 if (FAIL) return FAIL 1322 if (x) { var y } 1323 function z() { KEEP_ME() } 1324 return FAIL 1325 } 1326 1327 function testThrow() { 1328 if (true) throw y + z() 1329 if (FAIL) return FAIL 1330 if (x) { var y } 1331 function z() { KEEP_ME() } 1332 return FAIL 1333 } 1334 1335 function testBreak() { 1336 while (true) { 1337 if (true) { 1338 y + z() 1339 break 1340 } 1341 if (FAIL) return FAIL 1342 if (x) { var y } 1343 function z() { KEEP_ME() } 1344 return FAIL 1345 } 1346 } 1347 1348 function testContinue() { 1349 while (true) { 1350 if (true) { 1351 y + z() 1352 continue 1353 } 1354 if (FAIL) return FAIL 1355 if (x) { var y } 1356 function z() { KEEP_ME() } 1357 return FAIL 1358 } 1359 } 1360 1361 function testStmts() { 1362 return [a, b, c, d, e, f, g, h, i] 1363 1364 while (x) { var a } 1365 while (FAIL) { let FAIL } 1366 1367 do { var b } while (x) 1368 do { let FAIL } while (FAIL) 1369 1370 for (var c; ;) ; 1371 for (let FAIL; ;) ; 1372 1373 for (var d in x) ; 1374 for (let FAIL in FAIL) ; 1375 1376 for (var e of x) ; 1377 for (let FAIL of FAIL) ; 1378 1379 if (x) { var f } 1380 if (FAIL) { let FAIL } 1381 1382 if (x) ; else { var g } 1383 if (FAIL) ; else { let FAIL } 1384 1385 { var h } 1386 { let FAIL } 1387 1388 x: { var i } 1389 x: { let FAIL } 1390 } 1391 1392 testReturn() 1393 testThrow() 1394 testBreak() 1395 testContinue() 1396 testStmts() 1397 `, 1398 }, 1399 entryPaths: []string{"/entry.js"}, 1400 options: config.Options{ 1401 Mode: config.ModeBundle, 1402 AbsOutputFile: "/out.js", 1403 MinifySyntax: true, 1404 }, 1405 }) 1406 } 1407 1408 func TestRemoveTrailingReturn(t *testing.T) { 1409 dce_suite.expectBundled(t, bundled{ 1410 files: map[string]string{ 1411 "/entry.js": ` 1412 function foo() { 1413 if (a) b() 1414 return 1415 } 1416 function bar() { 1417 if (a) b() 1418 return KEEP_ME 1419 } 1420 export default [ 1421 foo, 1422 bar, 1423 function () { 1424 if (a) b() 1425 return 1426 }, 1427 function () { 1428 if (a) b() 1429 return KEEP_ME 1430 }, 1431 () => { 1432 if (a) b() 1433 return 1434 }, 1435 () => { 1436 if (a) b() 1437 return KEEP_ME 1438 }, 1439 ] 1440 `, 1441 }, 1442 entryPaths: []string{"/entry.js"}, 1443 options: config.Options{ 1444 Mode: config.ModeBundle, 1445 AbsOutputFile: "/out.js", 1446 MinifySyntax: true, 1447 OutputFormat: config.FormatESModule, 1448 }, 1449 }) 1450 } 1451 1452 func TestImportReExportOfNamespaceImport(t *testing.T) { 1453 dce_suite.expectBundled(t, bundled{ 1454 files: map[string]string{ 1455 "/Users/user/project/entry.js": ` 1456 import * as ns from 'pkg' 1457 console.log(ns.foo) 1458 `, 1459 "/Users/user/project/node_modules/pkg/index.js": ` 1460 export { default as foo } from './foo' 1461 export { default as bar } from './bar' 1462 `, 1463 "/Users/user/project/node_modules/pkg/package.json": ` 1464 { "sideEffects": false } 1465 `, 1466 "/Users/user/project/node_modules/pkg/foo.js": ` 1467 module.exports = 123 1468 `, 1469 "/Users/user/project/node_modules/pkg/bar.js": ` 1470 module.exports = 'abc' 1471 `, 1472 }, 1473 entryPaths: []string{"/Users/user/project/entry.js"}, 1474 options: config.Options{ 1475 Mode: config.ModeBundle, 1476 AbsOutputFile: "/out.js", 1477 }, 1478 }) 1479 } 1480 1481 func TestTreeShakingImportIdentifier(t *testing.T) { 1482 dce_suite.expectBundled(t, bundled{ 1483 files: map[string]string{ 1484 "/entry.js": ` 1485 import * as a from './a' 1486 new a.Keep() 1487 `, 1488 "/a.js": ` 1489 import * as b from './b' 1490 export class Keep extends b.Base {} 1491 export class REMOVE extends b.Base {} 1492 `, 1493 "/b.js": ` 1494 export class Base {} 1495 `, 1496 }, 1497 entryPaths: []string{"/entry.js"}, 1498 options: config.Options{ 1499 Mode: config.ModeBundle, 1500 AbsOutputFile: "/out.js", 1501 }, 1502 }) 1503 } 1504 1505 func TestTreeShakingObjectProperty(t *testing.T) { 1506 dce_suite.expectBundled(t, bundled{ 1507 files: map[string]string{ 1508 "/entry.js": ` 1509 let remove1 = { x: 'x' } 1510 let remove2 = { x() {} } 1511 let remove3 = { get x() {} } 1512 let remove4 = { set x(_) {} } 1513 let remove5 = { async x() {} } 1514 let remove6 = { ['x']: 'x' } 1515 let remove7 = { ['x']() {} } 1516 let remove8 = { get ['x']() {} } 1517 let remove9 = { set ['x'](_) {} } 1518 let remove10 = { async ['x']() {} } 1519 let remove11 = { [0]: 'x' } 1520 let remove12 = { [null]: 'x' } 1521 let remove13 = { [undefined]: 'x' } 1522 let remove14 = { [false]: 'x' } 1523 let remove15 = { [0n]: 'x' } 1524 let remove16 = { toString() {} } 1525 1526 let keep1 = { x } 1527 let keep2 = { x: x } 1528 let keep3 = { ...x } 1529 let keep4 = { [x]: 'x' } 1530 let keep5 = { [x]() {} } 1531 let keep6 = { get [x]() {} } 1532 let keep7 = { set [x](_) {} } 1533 let keep8 = { async [x]() {} } 1534 let keep9 = { [{ toString() {} }]: 'x' } 1535 `, 1536 }, 1537 entryPaths: []string{"/entry.js"}, 1538 options: config.Options{ 1539 Mode: config.ModePassThrough, 1540 TreeShaking: true, 1541 AbsOutputFile: "/out.js", 1542 }, 1543 }) 1544 } 1545 1546 func TestTreeShakingClassProperty(t *testing.T) { 1547 dce_suite.expectBundled(t, bundled{ 1548 files: map[string]string{ 1549 "/entry.js": ` 1550 let remove1 = class { x } 1551 let remove2 = class { x = x } 1552 let remove3 = class { x() {} } 1553 let remove4 = class { get x() {} } 1554 let remove5 = class { set x(_) {} } 1555 let remove6 = class { async x() {} } 1556 let remove7 = class { ['x'] = x } 1557 let remove8 = class { ['x']() {} } 1558 let remove9 = class { get ['x']() {} } 1559 let remove10 = class { set ['x'](_) {} } 1560 let remove11 = class { async ['x']() {} } 1561 let remove12 = class { [0] = 'x' } 1562 let remove13 = class { [null] = 'x' } 1563 let remove14 = class { [undefined] = 'x' } 1564 let remove15 = class { [false] = 'x' } 1565 let remove16 = class { [0n] = 'x' } 1566 let remove17 = class { toString() {} } 1567 1568 let keep1 = class { [x] = 'x' } 1569 let keep2 = class { [x]() {} } 1570 let keep3 = class { get [x]() {} } 1571 let keep4 = class { set [x](_) {} } 1572 let keep5 = class { async [x]() {} } 1573 let keep6 = class { [{ toString() {} }] = 'x' } 1574 `, 1575 }, 1576 entryPaths: []string{"/entry.js"}, 1577 options: config.Options{ 1578 Mode: config.ModePassThrough, 1579 TreeShaking: true, 1580 AbsOutputFile: "/out.js", 1581 }, 1582 }) 1583 } 1584 1585 func TestTreeShakingClassStaticProperty(t *testing.T) { 1586 dce_suite.expectBundled(t, bundled{ 1587 files: map[string]string{ 1588 "/entry.js": ` 1589 let remove1 = class { static x } 1590 let remove3 = class { static x() {} } 1591 let remove4 = class { static get x() {} } 1592 let remove5 = class { static set x(_) {} } 1593 let remove6 = class { static async x() {} } 1594 let remove8 = class { static ['x']() {} } 1595 let remove9 = class { static get ['x']() {} } 1596 let remove10 = class { static set ['x'](_) {} } 1597 let remove11 = class { static async ['x']() {} } 1598 let remove12 = class { static [0] = 'x' } 1599 let remove13 = class { static [null] = 'x' } 1600 let remove14 = class { static [undefined] = 'x' } 1601 let remove15 = class { static [false] = 'x' } 1602 let remove16 = class { static [0n] = 'x' } 1603 let remove17 = class { static toString() {} } 1604 1605 let keep1 = class { static x = x } 1606 let keep2 = class { static ['x'] = x } 1607 let keep3 = class { static [x] = 'x' } 1608 let keep4 = class { static [x]() {} } 1609 let keep5 = class { static get [x]() {} } 1610 let keep6 = class { static set [x](_) {} } 1611 let keep7 = class { static async [x]() {} } 1612 let keep8 = class { static [{ toString() {} }] = 'x' } 1613 `, 1614 }, 1615 entryPaths: []string{"/entry.js"}, 1616 options: config.Options{ 1617 Mode: config.ModePassThrough, 1618 TreeShaking: true, 1619 AbsOutputFile: "/out.js", 1620 }, 1621 }) 1622 } 1623 1624 func TestTreeShakingUnaryOperators(t *testing.T) { 1625 dce_suite.expectBundled(t, bundled{ 1626 files: map[string]string{ 1627 "/entry.js": ` 1628 // These operators may have side effects 1629 let keep; 1630 +keep; 1631 -keep; 1632 ~keep; 1633 delete keep; 1634 ++keep; 1635 --keep; 1636 keep++; 1637 keep--; 1638 1639 // These operators never have side effects 1640 let REMOVE; 1641 !REMOVE; 1642 void REMOVE; 1643 `, 1644 }, 1645 entryPaths: []string{"/entry.js"}, 1646 options: config.Options{ 1647 Mode: config.ModeBundle, 1648 AbsOutputFile: "/out.js", 1649 OutputFormat: config.FormatIIFE, 1650 }, 1651 }) 1652 } 1653 1654 func TestTreeShakingBinaryOperators(t *testing.T) { 1655 dce_suite.expectBundled(t, bundled{ 1656 files: map[string]string{ 1657 "/entry.js": ` 1658 // These operators may have side effects 1659 let keep, keep2; 1660 keep + keep2; 1661 keep - keep2; 1662 keep * keep2; 1663 keep / keep2; 1664 keep % keep2; 1665 keep ** keep2; 1666 keep < keep2; 1667 keep <= keep2; 1668 keep > keep2; 1669 keep >= keep2; 1670 keep in keep2; 1671 keep instanceof keep2; 1672 keep << keep2; 1673 keep >> keep2; 1674 keep >>> keep2; 1675 keep == keep2; 1676 keep != keep2; 1677 keep | keep2; 1678 keep & keep2; 1679 keep ^ keep2; 1680 keep = keep2; 1681 keep += keep2; 1682 keep -= keep2; 1683 keep *= keep2; 1684 keep /= keep2; 1685 keep %= keep2; 1686 keep **= keep2; 1687 keep <<= keep2; 1688 keep >>= keep2; 1689 keep >>>= keep2; 1690 keep |= keep2; 1691 keep &= keep2; 1692 keep ^= keep2; 1693 keep ??= keep2; 1694 keep ||= keep2; 1695 keep &&= keep2; 1696 1697 // These operators never have side effects 1698 let REMOVE, REMOVE2; 1699 REMOVE === REMOVE2; 1700 REMOVE !== REMOVE2; 1701 REMOVE, REMOVE2; 1702 REMOVE ?? REMOVE2; 1703 REMOVE || REMOVE2; 1704 REMOVE && REMOVE2; 1705 `, 1706 }, 1707 entryPaths: []string{"/entry.js"}, 1708 options: config.Options{ 1709 Mode: config.ModeBundle, 1710 AbsOutputFile: "/out.js", 1711 }, 1712 }) 1713 } 1714 1715 func TestTreeShakingNoBundleESM(t *testing.T) { 1716 dce_suite.expectBundled(t, bundled{ 1717 files: map[string]string{ 1718 "/entry.js": ` 1719 function keep() {} 1720 function unused() {} 1721 keep() 1722 `, 1723 }, 1724 entryPaths: []string{"/entry.js"}, 1725 options: config.Options{ 1726 Mode: config.ModeConvertFormat, 1727 OutputFormat: config.FormatESModule, 1728 AbsOutputFile: "/out.js", 1729 }, 1730 }) 1731 } 1732 1733 func TestTreeShakingNoBundleCJS(t *testing.T) { 1734 dce_suite.expectBundled(t, bundled{ 1735 files: map[string]string{ 1736 "/entry.js": ` 1737 function keep() {} 1738 function unused() {} 1739 keep() 1740 `, 1741 }, 1742 entryPaths: []string{"/entry.js"}, 1743 options: config.Options{ 1744 Mode: config.ModeConvertFormat, 1745 OutputFormat: config.FormatCommonJS, 1746 AbsOutputFile: "/out.js", 1747 }, 1748 }) 1749 } 1750 1751 func TestTreeShakingNoBundleIIFE(t *testing.T) { 1752 dce_suite.expectBundled(t, bundled{ 1753 files: map[string]string{ 1754 "/entry.js": ` 1755 function keep() {} 1756 function REMOVE() {} 1757 keep() 1758 `, 1759 }, 1760 entryPaths: []string{"/entry.js"}, 1761 options: config.Options{ 1762 Mode: config.ModeConvertFormat, 1763 OutputFormat: config.FormatIIFE, 1764 AbsOutputFile: "/out.js", 1765 }, 1766 }) 1767 } 1768 1769 func TestTreeShakingInESMWrapper(t *testing.T) { 1770 dce_suite.expectBundled(t, bundled{ 1771 files: map[string]string{ 1772 "/entry.js": ` 1773 import {keep1} from './lib' 1774 console.log(keep1(), require('./cjs')) 1775 `, 1776 "/cjs.js": ` 1777 import {keep2} from './lib' 1778 export default keep2() 1779 `, 1780 "/lib.js": ` 1781 export let keep1 = () => 'keep1' 1782 export let keep2 = () => 'keep2' 1783 export let REMOVE = () => 'REMOVE' 1784 `, 1785 }, 1786 entryPaths: []string{"/entry.js"}, 1787 options: config.Options{ 1788 Mode: config.ModeBundle, 1789 OutputFormat: config.FormatESModule, 1790 AbsOutputFile: "/out.js", 1791 }, 1792 }) 1793 } 1794 1795 func TestDCETypeOf(t *testing.T) { 1796 dce_suite.expectBundled(t, bundled{ 1797 files: map[string]string{ 1798 "/entry.js": ` 1799 // These should be removed because they have no side effects 1800 typeof x_REMOVE 1801 typeof v_REMOVE 1802 typeof f_REMOVE 1803 typeof g_REMOVE 1804 typeof a_REMOVE 1805 var v_REMOVE 1806 function f_REMOVE() {} 1807 function* g_REMOVE() {} 1808 async function a_REMOVE() {} 1809 1810 // These technically have side effects due to TDZ, but this is not currently handled 1811 typeof c_remove 1812 typeof l_remove 1813 typeof s_remove 1814 const c_remove = 0 1815 let l_remove 1816 class s_remove {} 1817 `, 1818 }, 1819 entryPaths: []string{"/entry.js"}, 1820 options: config.Options{ 1821 Mode: config.ModeBundle, 1822 OutputFormat: config.FormatESModule, 1823 AbsOutputFile: "/out.js", 1824 }, 1825 }) 1826 } 1827 1828 func TestDCETypeOfEqualsString(t *testing.T) { 1829 dce_suite.expectBundled(t, bundled{ 1830 files: map[string]string{ 1831 "/entry.js": ` 1832 var hasBar = typeof bar !== 'undefined' 1833 if (false) console.log(hasBar) 1834 `, 1835 }, 1836 entryPaths: []string{"/entry.js"}, 1837 options: config.Options{ 1838 Mode: config.ModeBundle, 1839 OutputFormat: config.FormatIIFE, 1840 AbsOutputFile: "/out.js", 1841 }, 1842 }) 1843 } 1844 1845 func TestDCETypeOfEqualsStringMangle(t *testing.T) { 1846 dce_suite.expectBundled(t, bundled{ 1847 files: map[string]string{ 1848 "/entry.js": ` 1849 // Everything here should be removed as dead code due to tree shaking 1850 var hasBar = typeof bar !== 'undefined' 1851 if (false) console.log(hasBar) 1852 `, 1853 }, 1854 entryPaths: []string{"/entry.js"}, 1855 options: config.Options{ 1856 Mode: config.ModeBundle, 1857 OutputFormat: config.FormatIIFE, 1858 MinifySyntax: true, 1859 AbsOutputFile: "/out.js", 1860 }, 1861 }) 1862 } 1863 1864 func TestDCETypeOfEqualsStringGuardCondition(t *testing.T) { 1865 dce_suite.expectBundled(t, bundled{ 1866 files: map[string]string{ 1867 "/entry.js": ` 1868 // Everything here should be removed as dead code due to tree shaking 1869 var REMOVE_1 = typeof x !== 'undefined' ? x : null 1870 var REMOVE_1 = typeof x != 'undefined' ? x : null 1871 var REMOVE_1 = typeof x === 'undefined' ? null : x 1872 var REMOVE_1 = typeof x == 'undefined' ? null : x 1873 var REMOVE_1 = typeof x !== 'undefined' && x 1874 var REMOVE_1 = typeof x != 'undefined' && x 1875 var REMOVE_1 = typeof x === 'undefined' || x 1876 var REMOVE_1 = typeof x == 'undefined' || x 1877 var REMOVE_1 = 'undefined' !== typeof x ? x : null 1878 var REMOVE_1 = 'undefined' != typeof x ? x : null 1879 var REMOVE_1 = 'undefined' === typeof x ? null : x 1880 var REMOVE_1 = 'undefined' == typeof x ? null : x 1881 var REMOVE_1 = 'undefined' !== typeof x && x 1882 var REMOVE_1 = 'undefined' != typeof x && x 1883 var REMOVE_1 = 'undefined' === typeof x || x 1884 var REMOVE_1 = 'undefined' == typeof x || x 1885 1886 // Everything here should be removed as dead code due to tree shaking 1887 var REMOVE_2 = typeof x === 'object' ? x : null 1888 var REMOVE_2 = typeof x == 'object' ? x : null 1889 var REMOVE_2 = typeof x !== 'object' ? null : x 1890 var REMOVE_2 = typeof x != 'object' ? null : x 1891 var REMOVE_2 = typeof x === 'object' && x 1892 var REMOVE_2 = typeof x == 'object' && x 1893 var REMOVE_2 = typeof x !== 'object' || x 1894 var REMOVE_2 = typeof x != 'object' || x 1895 var REMOVE_2 = 'object' === typeof x ? x : null 1896 var REMOVE_2 = 'object' == typeof x ? x : null 1897 var REMOVE_2 = 'object' !== typeof x ? null : x 1898 var REMOVE_2 = 'object' != typeof x ? null : x 1899 var REMOVE_2 = 'object' === typeof x && x 1900 var REMOVE_2 = 'object' == typeof x && x 1901 var REMOVE_2 = 'object' !== typeof x || x 1902 var REMOVE_2 = 'object' != typeof x || x 1903 1904 // Everything here should be kept as live code because it has side effects 1905 var keep_1 = typeof x !== 'object' ? x : null 1906 var keep_1 = typeof x != 'object' ? x : null 1907 var keep_1 = typeof x === 'object' ? null : x 1908 var keep_1 = typeof x == 'object' ? null : x 1909 var keep_1 = typeof x !== 'object' && x 1910 var keep_1 = typeof x != 'object' && x 1911 var keep_1 = typeof x === 'object' || x 1912 var keep_1 = typeof x == 'object' || x 1913 var keep_1 = 'object' !== typeof x ? x : null 1914 var keep_1 = 'object' != typeof x ? x : null 1915 var keep_1 = 'object' === typeof x ? null : x 1916 var keep_1 = 'object' == typeof x ? null : x 1917 var keep_1 = 'object' !== typeof x && x 1918 var keep_1 = 'object' != typeof x && x 1919 var keep_1 = 'object' === typeof x || x 1920 var keep_1 = 'object' == typeof x || x 1921 1922 // Everything here should be kept as live code because it has side effects 1923 var keep_2 = typeof x !== 'undefined' ? y : null 1924 var keep_2 = typeof x != 'undefined' ? y : null 1925 var keep_2 = typeof x === 'undefined' ? null : y 1926 var keep_2 = typeof x == 'undefined' ? null : y 1927 var keep_2 = typeof x !== 'undefined' && y 1928 var keep_2 = typeof x != 'undefined' && y 1929 var keep_2 = typeof x === 'undefined' || y 1930 var keep_2 = typeof x == 'undefined' || y 1931 var keep_2 = 'undefined' !== typeof x ? y : null 1932 var keep_2 = 'undefined' != typeof x ? y : null 1933 var keep_2 = 'undefined' === typeof x ? null : y 1934 var keep_2 = 'undefined' == typeof x ? null : y 1935 var keep_2 = 'undefined' !== typeof x && y 1936 var keep_2 = 'undefined' != typeof x && y 1937 var keep_2 = 'undefined' === typeof x || y 1938 var keep_2 = 'undefined' == typeof x || y 1939 1940 // Everything here should be kept as live code because it has side effects 1941 var keep_3 = typeof x !== 'undefined' ? null : x 1942 var keep_3 = typeof x != 'undefined' ? null : x 1943 var keep_3 = typeof x === 'undefined' ? x : null 1944 var keep_3 = typeof x == 'undefined' ? x : null 1945 var keep_3 = typeof x !== 'undefined' || x 1946 var keep_3 = typeof x != 'undefined' || x 1947 var keep_3 = typeof x === 'undefined' && x 1948 var keep_3 = typeof x == 'undefined' && x 1949 var keep_3 = 'undefined' !== typeof x ? null : x 1950 var keep_3 = 'undefined' != typeof x ? null : x 1951 var keep_3 = 'undefined' === typeof x ? x : null 1952 var keep_3 = 'undefined' == typeof x ? x : null 1953 var keep_3 = 'undefined' !== typeof x || x 1954 var keep_3 = 'undefined' != typeof x || x 1955 var keep_3 = 'undefined' === typeof x && x 1956 var keep_3 = 'undefined' == typeof x && x 1957 `, 1958 }, 1959 entryPaths: []string{"/entry.js"}, 1960 options: config.Options{ 1961 Mode: config.ModeBundle, 1962 OutputFormat: config.FormatIIFE, 1963 AbsOutputFile: "/out.js", 1964 }, 1965 }) 1966 } 1967 1968 func TestDCETypeOfCompareStringGuardCondition(t *testing.T) { 1969 dce_suite.expectBundled(t, bundled{ 1970 files: map[string]string{ 1971 "/entry.js": ` 1972 // Everything here should be removed as dead code due to tree shaking 1973 var REMOVE_1 = typeof x <= 'u' ? x : null 1974 var REMOVE_1 = typeof x < 'u' ? x : null 1975 var REMOVE_1 = typeof x >= 'u' ? null : x 1976 var REMOVE_1 = typeof x > 'u' ? null : x 1977 var REMOVE_1 = typeof x <= 'u' && x 1978 var REMOVE_1 = typeof x < 'u' && x 1979 var REMOVE_1 = typeof x >= 'u' || x 1980 var REMOVE_1 = typeof x > 'u' || x 1981 var REMOVE_1 = 'u' >= typeof x ? x : null 1982 var REMOVE_1 = 'u' > typeof x ? x : null 1983 var REMOVE_1 = 'u' <= typeof x ? null : x 1984 var REMOVE_1 = 'u' < typeof x ? null : x 1985 var REMOVE_1 = 'u' >= typeof x && x 1986 var REMOVE_1 = 'u' > typeof x && x 1987 var REMOVE_1 = 'u' <= typeof x || x 1988 var REMOVE_1 = 'u' < typeof x || x 1989 1990 // Everything here should be kept as live code because it has side effects 1991 var keep_1 = typeof x <= 'u' ? y : null 1992 var keep_1 = typeof x < 'u' ? y : null 1993 var keep_1 = typeof x >= 'u' ? null : y 1994 var keep_1 = typeof x > 'u' ? null : y 1995 var keep_1 = typeof x <= 'u' && y 1996 var keep_1 = typeof x < 'u' && y 1997 var keep_1 = typeof x >= 'u' || y 1998 var keep_1 = typeof x > 'u' || y 1999 var keep_1 = 'u' >= typeof x ? y : null 2000 var keep_1 = 'u' > typeof x ? y : null 2001 var keep_1 = 'u' <= typeof x ? null : y 2002 var keep_1 = 'u' < typeof x ? null : y 2003 var keep_1 = 'u' >= typeof x && y 2004 var keep_1 = 'u' > typeof x && y 2005 var keep_1 = 'u' <= typeof x || y 2006 var keep_1 = 'u' < typeof x || y 2007 2008 // Everything here should be kept as live code because it has side effects 2009 var keep_2 = typeof x <= 'u' ? null : x 2010 var keep_2 = typeof x < 'u' ? null : x 2011 var keep_2 = typeof x >= 'u' ? x : null 2012 var keep_2 = typeof x > 'u' ? x : null 2013 var keep_2 = typeof x <= 'u' || x 2014 var keep_2 = typeof x < 'u' || x 2015 var keep_2 = typeof x >= 'u' && x 2016 var keep_2 = typeof x > 'u' && x 2017 var keep_2 = 'u' >= typeof x ? null : x 2018 var keep_2 = 'u' > typeof x ? null : x 2019 var keep_2 = 'u' <= typeof x ? x : null 2020 var keep_2 = 'u' < typeof x ? x : null 2021 var keep_2 = 'u' >= typeof x || x 2022 var keep_2 = 'u' > typeof x || x 2023 var keep_2 = 'u' <= typeof x && x 2024 var keep_2 = 'u' < typeof x && x 2025 `, 2026 }, 2027 entryPaths: []string{"/entry.js"}, 2028 options: config.Options{ 2029 Mode: config.ModeBundle, 2030 OutputFormat: config.FormatIIFE, 2031 AbsOutputFile: "/out.js", 2032 }, 2033 }) 2034 } 2035 2036 // These unused imports should be removed since they aren't used, and removing 2037 // them makes the code shorter. 2038 func TestRemoveUnusedImports(t *testing.T) { 2039 dce_suite.expectBundled(t, bundled{ 2040 files: map[string]string{ 2041 "/entry.js": ` 2042 import a from 'a' 2043 import * as b from 'b' 2044 import {c} from 'c' 2045 `, 2046 }, 2047 entryPaths: []string{"/entry.js"}, 2048 options: config.Options{ 2049 Mode: config.ModePassThrough, 2050 MinifySyntax: true, 2051 AbsOutputFile: "/out.js", 2052 }, 2053 }) 2054 } 2055 2056 // These unused imports should be kept since the direct eval could potentially 2057 // reference them, even though they appear to be unused. 2058 func TestRemoveUnusedImportsEval(t *testing.T) { 2059 dce_suite.expectBundled(t, bundled{ 2060 files: map[string]string{ 2061 "/entry.js": ` 2062 import a from 'a' 2063 import * as b from 'b' 2064 import {c} from 'c' 2065 eval('foo(a, b, c)') 2066 `, 2067 }, 2068 entryPaths: []string{"/entry.js"}, 2069 options: config.Options{ 2070 Mode: config.ModePassThrough, 2071 MinifySyntax: true, 2072 AbsOutputFile: "/out.js", 2073 }, 2074 }) 2075 } 2076 2077 // These unused imports should be removed even though there is a direct eval 2078 // because they may be types, not values, so keeping them will likely cause 2079 // module instantiation failures. It's still true that direct eval could 2080 // access them of course, but that's very unlikely while module instantiation 2081 // failure is very likely so we bias towards the likely case here instead. 2082 func TestRemoveUnusedImportsEvalTS(t *testing.T) { 2083 dce_suite.expectBundled(t, bundled{ 2084 files: map[string]string{ 2085 "/entry.ts": ` 2086 import a from 'a' 2087 import * as b from 'b' 2088 import {c} from 'c' 2089 eval('foo(a, b, c)') 2090 `, 2091 }, 2092 entryPaths: []string{"/entry.js"}, 2093 options: config.Options{ 2094 Mode: config.ModePassThrough, 2095 MinifySyntax: true, 2096 AbsOutputFile: "/out.js", 2097 }, 2098 }) 2099 } 2100 2101 func TestDCEClassStaticBlocks(t *testing.T) { 2102 dce_suite.expectBundled(t, bundled{ 2103 files: map[string]string{ 2104 "/entry.ts": ` 2105 class A_REMOVE { 2106 static {} 2107 } 2108 class B_REMOVE { 2109 static { 123 } 2110 } 2111 class C_REMOVE { 2112 static { /* @__PURE__*/ foo() } 2113 } 2114 class D_REMOVE { 2115 static { try {} catch {} } 2116 } 2117 class E_REMOVE { 2118 static { try { /* @__PURE__*/ foo() } catch {} } 2119 } 2120 class F_REMOVE { 2121 static { try { 123 } catch { 123 } finally { 123 } } 2122 } 2123 2124 class A_keep { 2125 static { foo } 2126 } 2127 class B_keep { 2128 static { this.foo } 2129 } 2130 class C_keep { 2131 static { try { foo } catch {} } 2132 } 2133 class D_keep { 2134 static { try {} finally { foo } } 2135 } 2136 `, 2137 }, 2138 entryPaths: []string{"/entry.js"}, 2139 options: config.Options{ 2140 Mode: config.ModeBundle, 2141 AbsOutputFile: "/out.js", 2142 }, 2143 }) 2144 } 2145 2146 func TestDCEClassStaticBlocksMinifySyntax(t *testing.T) { 2147 dce_suite.expectBundled(t, bundled{ 2148 files: map[string]string{ 2149 "/entry.ts": ` 2150 class A_REMOVE { 2151 static {} 2152 } 2153 class B_REMOVE { 2154 static { 123 } 2155 } 2156 class C_REMOVE { 2157 static { /* @__PURE__*/ foo() } 2158 } 2159 class D_REMOVE { 2160 static { try {} catch {} } 2161 } 2162 class E_REMOVE { 2163 static { try { /* @__PURE__*/ foo() } catch {} } 2164 } 2165 class F_REMOVE { 2166 static { try { 123 } catch { 123 } finally { 123 } } 2167 } 2168 2169 class A_keep { 2170 static { foo } 2171 } 2172 class B_keep { 2173 static { this.foo } 2174 } 2175 class C_keep { 2176 static { try { foo } catch {} } 2177 } 2178 class D_keep { 2179 static { try {} finally { foo } } 2180 } 2181 `, 2182 }, 2183 entryPaths: []string{"/entry.js"}, 2184 options: config.Options{ 2185 Mode: config.ModeBundle, 2186 AbsOutputFile: "/out.js", 2187 MinifySyntax: true, 2188 }, 2189 }) 2190 } 2191 2192 func TestDCEVarExports(t *testing.T) { 2193 dce_suite.expectBundled(t, bundled{ 2194 files: map[string]string{ 2195 "/a.js": ` 2196 var foo = { bar: 123 } 2197 module.exports = foo 2198 `, 2199 "/b.js": ` 2200 var exports = { bar: 123 } 2201 module.exports = exports 2202 `, 2203 "/c.js": ` 2204 var module = { bar: 123 } 2205 exports.foo = module 2206 `, 2207 }, 2208 entryPaths: []string{"/a.js", "/b.js", "/c.js"}, 2209 options: config.Options{ 2210 Mode: config.ModeBundle, 2211 AbsOutputDir: "/out", 2212 }, 2213 }) 2214 } 2215 2216 func TestDCETemplateLiteral(t *testing.T) { 2217 dce_suite.expectBundled(t, bundled{ 2218 files: map[string]string{ 2219 "/entry.js": "" + 2220 "var remove;\n" + 2221 "var alsoKeep;\n" + 2222 "let a = `${keep}`\n" + 2223 "let b = `${123}`\n" + 2224 "let c = `${keep ? 1 : 2n}`\n" + 2225 "let d = `${remove ? 1 : 2n}`\n" + 2226 "let e = `${alsoKeep}`\n", 2227 }, 2228 entryPaths: []string{"/entry.js"}, 2229 options: config.Options{ 2230 Mode: config.ModeBundle, 2231 AbsOutputDir: "/out", 2232 }, 2233 }) 2234 } 2235 2236 // Calls to the runtime "__publicField" function are not considered side effects 2237 func TestTreeShakingLoweredClassStaticField(t *testing.T) { 2238 dce_suite.expectBundled(t, bundled{ 2239 files: map[string]string{ 2240 "/entry.js": ` 2241 class REMOVE_ME { 2242 static x = 'x' 2243 static y = 'y' 2244 static z = 'z' 2245 } 2246 function REMOVE_ME_TOO() { 2247 new REMOVE_ME() 2248 } 2249 class KeepMe1 { 2250 static x = 'x' 2251 static y = sideEffects() 2252 static z = 'z' 2253 } 2254 class KeepMe2 { 2255 static x = 'x' 2256 static y = 'y' 2257 static z = 'z' 2258 } 2259 new KeepMe2() 2260 `, 2261 }, 2262 entryPaths: []string{"/entry.js"}, 2263 options: config.Options{ 2264 Mode: config.ModeBundle, 2265 AbsOutputDir: "/out", 2266 UnsupportedJSFeatures: compat.ClassStaticField, 2267 }, 2268 }) 2269 } 2270 2271 func TestTreeShakingLoweredClassStaticFieldMinified(t *testing.T) { 2272 dce_suite.expectBundled(t, bundled{ 2273 files: map[string]string{ 2274 "/entry.js": ` 2275 class REMOVE_ME { 2276 static x = 'x' 2277 static y = 'y' 2278 static z = 'z' 2279 } 2280 function REMOVE_ME_TOO() { 2281 new REMOVE_ME() 2282 } 2283 class KeepMe1 { 2284 static x = 'x' 2285 static y = sideEffects() 2286 static z = 'z' 2287 } 2288 class KeepMe2 { 2289 static x = 'x' 2290 static y = 'y' 2291 static z = 'z' 2292 } 2293 new KeepMe2() 2294 `, 2295 }, 2296 entryPaths: []string{"/entry.js"}, 2297 options: config.Options{ 2298 Mode: config.ModeBundle, 2299 AbsOutputDir: "/out", 2300 UnsupportedJSFeatures: compat.ClassStaticField, 2301 MinifySyntax: true, 2302 }, 2303 }) 2304 } 2305 2306 // Assignments are considered side effects 2307 func TestTreeShakingLoweredClassStaticFieldAssignment(t *testing.T) { 2308 dce_suite.expectBundled(t, bundled{ 2309 files: map[string]string{ 2310 "/entry.ts": ` 2311 class KeepMe1 { 2312 static x = 'x' 2313 static y = 'y' 2314 static z = 'z' 2315 } 2316 class KeepMe2 { 2317 static x = 'x' 2318 static y = sideEffects() 2319 static z = 'z' 2320 } 2321 class KeepMe3 { 2322 static x = 'x' 2323 static y = 'y' 2324 static z = 'z' 2325 } 2326 new KeepMe3() 2327 `, 2328 }, 2329 entryPaths: []string{"/entry.js"}, 2330 options: config.Options{ 2331 Mode: config.ModeBundle, 2332 AbsOutputDir: "/out", 2333 UnsupportedJSFeatures: compat.ClassStaticField, 2334 TS: config.TSOptions{Config: config.TSConfig{ 2335 UseDefineForClassFields: config.False, 2336 }}, 2337 }, 2338 }) 2339 } 2340 2341 func TestInlineIdentityFunctionCalls(t *testing.T) { 2342 dce_suite.expectBundled(t, bundled{ 2343 files: map[string]string{ 2344 "/identity.js": ` 2345 function DROP(x) { return x } 2346 console.log(DROP(1)) 2347 DROP(foo()) 2348 DROP(1) 2349 `, 2350 2351 "/identity-last.js": ` 2352 function DROP(x) { return [x] } 2353 function DROP(x) { return x } 2354 console.log(DROP(1)) 2355 DROP(foo()) 2356 DROP(1) 2357 `, 2358 2359 "/identity-cross-module.js": ` 2360 import { DROP } from './identity-cross-module-def' 2361 console.log(DROP(1)) 2362 DROP(foo()) 2363 DROP(1) 2364 `, 2365 2366 "/identity-cross-module-def.js": ` 2367 export function DROP(x) { return x } 2368 `, 2369 2370 "/identity-no-args.js": ` 2371 function keep(x) { return x } 2372 console.log(keep()) 2373 keep() 2374 `, 2375 2376 "/identity-two-args.js": ` 2377 function keep(x) { return x } 2378 console.log(keep(1, 2)) 2379 keep(1, 2) 2380 `, 2381 2382 "/identity-first.js": ` 2383 function keep(x) { return x } 2384 function keep(x) { return [x] } 2385 console.log(keep(1)) 2386 keep(foo()) 2387 keep(1) 2388 `, 2389 2390 "/identity-generator.js": ` 2391 function* keep(x) { return x } 2392 console.log(keep(1)) 2393 keep(foo()) 2394 keep(1) 2395 `, 2396 2397 "/identity-async.js": ` 2398 async function keep(x) { return x } 2399 console.log(keep(1)) 2400 keep(foo()) 2401 keep(1) 2402 `, 2403 2404 "/reassign.js": ` 2405 function keep(x) { return x } 2406 keep = reassigned 2407 console.log(keep(1)) 2408 keep(foo()) 2409 keep(1) 2410 `, 2411 2412 "/reassign-inc.js": ` 2413 function keep(x) { return x } 2414 keep++ 2415 console.log(keep(1)) 2416 keep(foo()) 2417 keep(1) 2418 `, 2419 2420 "/reassign-div.js": ` 2421 function keep(x) { return x } 2422 keep /= reassigned 2423 console.log(keep(1)) 2424 keep(foo()) 2425 keep(1) 2426 `, 2427 2428 "/reassign-array.js": ` 2429 function keep(x) { return x } 2430 [keep] = reassigned 2431 console.log(keep(1)) 2432 keep(foo()) 2433 keep(1) 2434 `, 2435 2436 "/reassign-object.js": ` 2437 function keep(x) { return x } 2438 ({keep} = reassigned) 2439 console.log(keep(1)) 2440 keep(foo()) 2441 keep(1) 2442 `, 2443 2444 "/not-identity-two-args.js": ` 2445 function keep(x, y) { return x } 2446 console.log(keep(1)) 2447 keep(foo()) 2448 keep(1) 2449 `, 2450 2451 "/not-identity-default.js": ` 2452 function keep(x = foo()) { return x } 2453 console.log(keep(1)) 2454 keep(foo()) 2455 keep(1) 2456 `, 2457 2458 "/not-identity-array.js": ` 2459 function keep([x]) { return x } 2460 console.log(keep(1)) 2461 keep(foo()) 2462 keep(1) 2463 `, 2464 2465 "/not-identity-object.js": ` 2466 function keep({x}) { return x } 2467 console.log(keep(1)) 2468 keep(foo()) 2469 keep(1) 2470 `, 2471 2472 "/not-identity-rest.js": ` 2473 function keep(...x) { return x } 2474 console.log(keep(1)) 2475 keep(foo()) 2476 keep(1) 2477 `, 2478 2479 "/not-identity-return.js": ` 2480 function keep(x) { return [x] } 2481 console.log(keep(1)) 2482 keep(foo()) 2483 keep(1) 2484 `, 2485 }, 2486 entryPaths: []string{ 2487 "/identity.js", 2488 "/identity-last.js", 2489 "/identity-first.js", 2490 "/identity-generator.js", 2491 "/identity-async.js", 2492 "/identity-cross-module.js", 2493 "/identity-no-args.js", 2494 "/identity-two-args.js", 2495 "/reassign.js", 2496 "/reassign-inc.js", 2497 "/reassign-div.js", 2498 "/reassign-array.js", 2499 "/reassign-object.js", 2500 "/not-identity-two-args.js", 2501 "/not-identity-default.js", 2502 "/not-identity-array.js", 2503 "/not-identity-object.js", 2504 "/not-identity-rest.js", 2505 "/not-identity-return.js", 2506 }, 2507 options: config.Options{ 2508 Mode: config.ModeBundle, 2509 AbsOutputDir: "/out", 2510 MinifySyntax: true, 2511 }, 2512 }) 2513 } 2514 2515 func TestInlineEmptyFunctionCalls(t *testing.T) { 2516 dce_suite.expectBundled(t, bundled{ 2517 files: map[string]string{ 2518 "/empty.js": ` 2519 function DROP() {} 2520 console.log(DROP(foo(), bar())) 2521 console.log(DROP(foo(), 1)) 2522 console.log(DROP(1, foo())) 2523 console.log(DROP(1)) 2524 console.log(DROP()) 2525 DROP(foo(), bar()) 2526 DROP(foo(), 1) 2527 DROP(1, foo()) 2528 DROP(1) 2529 DROP() 2530 `, 2531 2532 "/empty-comma.js": ` 2533 function DROP() {} 2534 console.log((DROP(), DROP(), foo())) 2535 console.log((DROP(), foo(), DROP())) 2536 console.log((foo(), DROP(), DROP())) 2537 for (DROP(); DROP(); DROP()) DROP(); 2538 DROP(), DROP(), foo(); 2539 DROP(), foo(), DROP(); 2540 foo(), DROP(), DROP(); 2541 `, 2542 2543 "/empty-if-else.js": ` 2544 function DROP() {} 2545 if (foo) { let bar = baz(); bar(); bar() } else DROP(); 2546 `, 2547 2548 "/empty-last.js": ` 2549 function DROP() { return x } 2550 function DROP() { return } 2551 console.log(DROP()) 2552 DROP() 2553 `, 2554 2555 "/empty-cross-module.js": ` 2556 import { DROP } from './empty-cross-module-def' 2557 console.log(DROP()) 2558 DROP() 2559 `, 2560 2561 "/empty-cross-module-def.js": ` 2562 export function DROP() {} 2563 `, 2564 2565 "/empty-first.js": ` 2566 function keep() { return } 2567 function keep() { return x } 2568 console.log(keep()) 2569 keep(foo()) 2570 keep(1) 2571 `, 2572 2573 "/empty-generator.js": ` 2574 function* keep() {} 2575 console.log(keep()) 2576 keep(foo()) 2577 keep(1) 2578 `, 2579 2580 "/empty-async.js": ` 2581 async function keep() {} 2582 console.log(keep()) 2583 keep(foo()) 2584 keep(1) 2585 `, 2586 2587 "/reassign.js": ` 2588 function keep() {} 2589 keep = reassigned 2590 console.log(keep()) 2591 keep(foo()) 2592 keep(1) 2593 `, 2594 2595 "/reassign-inc.js": ` 2596 function keep() {} 2597 keep++ 2598 console.log(keep(1)) 2599 keep(foo()) 2600 keep(1) 2601 `, 2602 2603 "/reassign-div.js": ` 2604 function keep() {} 2605 keep /= reassigned 2606 console.log(keep(1)) 2607 keep(foo()) 2608 keep(1) 2609 `, 2610 2611 "/reassign-array.js": ` 2612 function keep() {} 2613 [keep] = reassigned 2614 console.log(keep(1)) 2615 keep(foo()) 2616 keep(1) 2617 `, 2618 2619 "/reassign-object.js": ` 2620 function keep() {} 2621 ({keep} = reassigned) 2622 console.log(keep(1)) 2623 keep(foo()) 2624 keep(1) 2625 `, 2626 }, 2627 entryPaths: []string{ 2628 "/empty.js", 2629 "/empty-comma.js", 2630 "/empty-if-else.js", 2631 "/empty-last.js", 2632 "/empty-cross-module.js", 2633 "/empty-first.js", 2634 "/empty-generator.js", 2635 "/empty-async.js", 2636 "/reassign.js", 2637 "/reassign-inc.js", 2638 "/reassign-div.js", 2639 "/reassign-array.js", 2640 "/reassign-object.js", 2641 }, 2642 options: config.Options{ 2643 Mode: config.ModeBundle, 2644 AbsOutputDir: "/out", 2645 MinifySyntax: true, 2646 }, 2647 }) 2648 } 2649 2650 func TestInlineFunctionCallBehaviorChanges(t *testing.T) { 2651 dce_suite.expectBundled(t, bundled{ 2652 files: map[string]string{ 2653 "/entry.js": ` 2654 function empty() {} 2655 function id(x) { return x } 2656 2657 export let shouldBeWrapped = [ 2658 id(foo.bar)(), 2659 id(foo[bar])(), 2660 id(foo?.bar)(), 2661 id(foo?.[bar])(), 2662 2663 (empty(), foo.bar)(), 2664 (empty(), foo[bar])(), 2665 (empty(), foo?.bar)(), 2666 (empty(), foo?.[bar])(), 2667 2668 id(eval)(), 2669 id(eval)?.(), 2670 (empty(), eval)(), 2671 (empty(), eval)?.(), 2672 2673 id(foo.bar)` + "``" + `, 2674 id(foo[bar])` + "``" + `, 2675 id(foo?.bar)` + "``" + `, 2676 id(foo?.[bar])` + "``" + `, 2677 2678 (empty(), foo.bar)` + "``" + `, 2679 (empty(), foo[bar])` + "``" + `, 2680 (empty(), foo?.bar)` + "``" + `, 2681 (empty(), foo?.[bar])` + "``" + `, 2682 2683 delete id(foo), 2684 delete id(foo.bar), 2685 delete id(foo[bar]), 2686 delete id(foo?.bar), 2687 delete id(foo?.[bar]), 2688 2689 delete (empty(), foo), 2690 delete (empty(), foo.bar), 2691 delete (empty(), foo[bar]), 2692 delete (empty(), foo?.bar), 2693 delete (empty(), foo?.[bar]), 2694 2695 delete empty(), 2696 ] 2697 2698 export let shouldNotBeWrapped = [ 2699 id(foo)(), 2700 (empty(), foo)(), 2701 2702 id(foo)` + "``" + `, 2703 (empty(), foo)` + "``" + `, 2704 ] 2705 2706 export let shouldNotBeDoubleWrapped = [ 2707 delete (empty(), foo(), bar()), 2708 delete id((foo(), bar())), 2709 ] 2710 `, 2711 }, 2712 entryPaths: []string{"/entry.js"}, 2713 options: config.Options{ 2714 Mode: config.ModePassThrough, 2715 AbsOutputDir: "/out", 2716 MinifySyntax: true, 2717 }, 2718 }) 2719 } 2720 2721 func TestInlineFunctionCallForInitDecl(t *testing.T) { 2722 dce_suite.expectBundled(t, bundled{ 2723 files: map[string]string{ 2724 "/entry.js": ` 2725 function empty() {} 2726 function id(x) { return x } 2727 2728 for (var y = empty(); false; ) ; 2729 for (var z = id(123); false; ) ; 2730 `, 2731 }, 2732 entryPaths: []string{"/entry.js"}, 2733 options: config.Options{ 2734 Mode: config.ModeBundle, 2735 AbsOutputDir: "/out", 2736 MinifySyntax: true, 2737 }, 2738 }) 2739 } 2740 2741 func TestConstValueInliningNoBundle(t *testing.T) { 2742 dce_suite.expectBundled(t, bundled{ 2743 files: map[string]string{ 2744 "/top-level.js": ` 2745 // These should be kept because they are top-level and tree shaking is not enabled 2746 const n_keep = null 2747 const u_keep = undefined 2748 const i_keep = 1234567 2749 const f_keep = 123.456 2750 const s_keep = '' 2751 2752 // Values should still be inlined 2753 console.log( 2754 // These are doubled to avoid the "inline const/let into next statement if used once" optimization 2755 n_keep, n_keep, 2756 u_keep, u_keep, 2757 i_keep, i_keep, 2758 f_keep, f_keep, 2759 s_keep, s_keep, 2760 ) 2761 `, 2762 "/nested-block.js": ` 2763 { 2764 const REMOVE_n = null 2765 const REMOVE_u = undefined 2766 const REMOVE_i = 1234567 2767 const REMOVE_f = 123.456 2768 const s_keep = '' // String inlining is intentionally not supported right now 2769 console.log( 2770 // These are doubled to avoid the "inline const/let into next statement if used once" optimization 2771 REMOVE_n, REMOVE_n, 2772 REMOVE_u, REMOVE_u, 2773 REMOVE_i, REMOVE_i, 2774 REMOVE_f, REMOVE_f, 2775 s_keep, s_keep, 2776 ) 2777 } 2778 `, 2779 "/nested-function.js": ` 2780 function nested() { 2781 const REMOVE_n = null 2782 const REMOVE_u = undefined 2783 const REMOVE_i = 1234567 2784 const REMOVE_f = 123.456 2785 const s_keep = '' // String inlining is intentionally not supported right now 2786 console.log( 2787 // These are doubled to avoid the "inline const/let into next statement if used once" optimization 2788 REMOVE_n, REMOVE_n, 2789 REMOVE_u, REMOVE_u, 2790 REMOVE_i, REMOVE_i, 2791 REMOVE_f, REMOVE_f, 2792 s_keep, s_keep, 2793 ) 2794 } 2795 `, 2796 "/namespace-export.ts": ` 2797 namespace ns { 2798 const x_REMOVE = 1 2799 export const y_keep = 2 2800 console.log( 2801 x_REMOVE, x_REMOVE, 2802 y_keep, y_keep, 2803 ) 2804 } 2805 `, 2806 2807 "/comment-before.js": ` 2808 { 2809 //! comment 2810 const REMOVE = 1 2811 x = [REMOVE, REMOVE] 2812 } 2813 `, 2814 "/directive-before.js": ` 2815 function nested() { 2816 'directive' 2817 const REMOVE = 1 2818 x = [REMOVE, REMOVE] 2819 } 2820 `, 2821 "/semicolon-before.js": ` 2822 { 2823 ; 2824 const REMOVE = 1 2825 x = [REMOVE, REMOVE] 2826 } 2827 `, 2828 "/debugger-before.js": ` 2829 { 2830 debugger 2831 const REMOVE = 1 2832 x = [REMOVE, REMOVE] 2833 } 2834 `, 2835 "/type-before.ts": ` 2836 { 2837 declare let x 2838 const REMOVE = 1 2839 x = [REMOVE, REMOVE] 2840 } 2841 `, 2842 "/exprs-before.js": ` 2843 function nested() { 2844 const x = [, '', {}, 0n, /./, function() {}, () => {}] 2845 const y_REMOVE = 1 2846 function foo() { 2847 return y_REMOVE 2848 } 2849 } 2850 `, 2851 2852 "/disabled-tdz.js": ` 2853 foo() 2854 const x_keep = 1 2855 function foo() { 2856 return x_keep 2857 } 2858 `, 2859 "/backwards-reference-top-level.js": ` 2860 const x = y 2861 const y = 1 2862 console.log( 2863 x, x, 2864 y, y, 2865 ) 2866 `, 2867 "/backwards-reference-nested-function.js": ` 2868 function foo() { 2869 const x = y 2870 const y = 1 2871 console.log( 2872 x, x, 2873 y, y, 2874 ) 2875 } 2876 `, 2877 "/issue-3125.js": ` 2878 function foo() { 2879 const f = () => x 2880 const x = 0 2881 return f() 2882 } 2883 `, 2884 }, 2885 entryPaths: []string{ 2886 "/top-level.js", 2887 "/nested-block.js", 2888 "/nested-function.js", 2889 "/namespace-export.ts", 2890 2891 "/comment-before.js", 2892 "/directive-before.js", 2893 "/semicolon-before.js", 2894 "/debugger-before.js", 2895 "/type-before.ts", 2896 "/exprs-before.js", 2897 2898 "/disabled-tdz.js", 2899 "/backwards-reference-top-level.js", 2900 "/backwards-reference-nested-function.js", 2901 "/issue-3125.js", 2902 }, 2903 options: config.Options{ 2904 Mode: config.ModePassThrough, 2905 AbsOutputDir: "/out", 2906 MinifySyntax: true, 2907 }, 2908 }) 2909 } 2910 2911 func TestConstValueInliningBundle(t *testing.T) { 2912 dce_suite.expectBundled(t, bundled{ 2913 files: map[string]string{ 2914 "/exported-entry.js": ` 2915 const x_REMOVE = 1 2916 export const y_keep = 2 2917 console.log( 2918 x_REMOVE, 2919 y_keep, 2920 ) 2921 `, 2922 2923 "/re-exported-entry.js": ` 2924 import { x_REMOVE, y_keep } from './re-exported-constants' 2925 console.log(x_REMOVE, y_keep) 2926 export { y_keep } 2927 `, 2928 "/re-exported-constants.js": ` 2929 export const x_REMOVE = 1 2930 export const y_keep = 2 2931 `, 2932 2933 "/re-exported-2-entry.js": ` 2934 export { y_keep } from './re-exported-2-constants' 2935 `, 2936 "/re-exported-2-constants.js": ` 2937 export const x_REMOVE = 1 2938 export const y_keep = 2 2939 `, 2940 2941 "/re-exported-star-entry.js": ` 2942 export * from './re-exported-star-constants' 2943 `, 2944 "/re-exported-star-constants.js": ` 2945 export const x_keep = 1 2946 export const y_keep = 2 2947 `, 2948 2949 "/cross-module-entry.js": ` 2950 import { x_REMOVE, y_keep } from './cross-module-constants' 2951 console.log(x_REMOVE, y_keep) 2952 `, 2953 "/cross-module-constants.js": ` 2954 export const x_REMOVE = 1 2955 foo() 2956 export const y_keep = 1 2957 export function foo() { 2958 return [x_REMOVE, y_keep] 2959 } 2960 `, 2961 2962 "/print-shorthand-entry.js": ` 2963 import { foo, _bar } from './print-shorthand-constants' 2964 // The inlined constants must still be present in the output! We don't 2965 // want the printer to use the shorthand syntax here to refer to the 2966 // name of the constant itself because the constant declaration is omitted. 2967 console.log({ foo, _bar }) 2968 `, 2969 "/print-shorthand-constants.js": ` 2970 export const foo = 123 2971 export const _bar = -321 2972 `, 2973 2974 "/circular-import-entry.js": ` 2975 import './circular-import-constants' 2976 `, 2977 "/circular-import-constants.js": ` 2978 export const foo = 123 // Inlining should be prevented by the cycle 2979 export function bar() { 2980 return foo 2981 } 2982 import './circular-import-cycle' 2983 `, 2984 "/circular-import-cycle.js": ` 2985 import { bar } from './circular-import-constants' 2986 console.log(bar()) // This accesses "foo" before it's initialized 2987 `, 2988 2989 "/circular-re-export-entry.js": ` 2990 import { baz } from './circular-re-export-constants' 2991 console.log(baz) 2992 `, 2993 "/circular-re-export-constants.js": ` 2994 export const foo = 123 // Inlining should be prevented by the cycle 2995 export function bar() { 2996 return foo 2997 } 2998 export { baz } from './circular-re-export-cycle' 2999 `, 3000 "/circular-re-export-cycle.js": ` 3001 export const baz = 0 3002 import { bar } from './circular-re-export-constants' 3003 console.log(bar()) // This accesses "foo" before it's initialized 3004 `, 3005 3006 "/circular-re-export-star-entry.js": ` 3007 import './circular-re-export-star-constants' 3008 `, 3009 "/circular-re-export-star-constants.js": ` 3010 export const foo = 123 // Inlining should be prevented by the cycle 3011 export function bar() { 3012 return foo 3013 } 3014 export * from './circular-re-export-star-cycle' 3015 `, 3016 "/circular-re-export-star-cycle.js": ` 3017 import { bar } from './circular-re-export-star-constants' 3018 console.log(bar()) // This accesses "foo" before it's initialized 3019 `, 3020 3021 "/non-circular-export-entry.js": ` 3022 import { foo, bar } from './non-circular-export-constants' 3023 console.log(foo, bar()) 3024 `, 3025 "/non-circular-export-constants.js": ` 3026 const foo = 123 // Inlining should be prevented by the cycle 3027 function bar() { 3028 return foo 3029 } 3030 export { foo, bar } 3031 `, 3032 }, 3033 entryPaths: []string{ 3034 "/exported-entry.js", 3035 "/re-exported-entry.js", 3036 "/re-exported-2-entry.js", 3037 "/re-exported-star-entry.js", 3038 "/cross-module-entry.js", 3039 "/print-shorthand-entry.js", 3040 "/circular-import-entry.js", 3041 "/circular-re-export-entry.js", 3042 "/circular-re-export-star-entry.js", 3043 "/non-circular-export-entry.js", 3044 }, 3045 options: config.Options{ 3046 Mode: config.ModeBundle, 3047 OutputFormat: config.FormatESModule, 3048 AbsOutputDir: "/out", 3049 MinifySyntax: true, 3050 MangleProps: regexp.MustCompile("^_"), 3051 }, 3052 }) 3053 } 3054 3055 // Assignment to an inlined constant is not allowed since that would cause a 3056 // syntax error in the output. We don't just keep the reference there because 3057 // the declaration may actually have been completely removed already by the 3058 // time we discover the assignment. I think making these cases an error is 3059 // fine because it's bad code anyway. 3060 func TestConstValueInliningAssign(t *testing.T) { 3061 dce_suite.expectBundled(t, bundled{ 3062 files: map[string]string{ 3063 "/const-assign.js": ` 3064 const x = 1 3065 x = 2 3066 `, 3067 "/const-update.js": ` 3068 const x = 1 3069 x += 2 3070 `, 3071 }, 3072 entryPaths: []string{ 3073 "/const-assign.js", 3074 "/const-update.js", 3075 }, 3076 options: config.Options{ 3077 Mode: config.ModePassThrough, 3078 AbsOutputDir: "/out", 3079 MinifySyntax: true, 3080 }, 3081 expectedScanLog: `const-assign.js: ERROR: Cannot assign to "x" because it is a constant 3082 const-assign.js: NOTE: The symbol "x" was declared a constant here: 3083 const-update.js: ERROR: Cannot assign to "x" because it is a constant 3084 const-update.js: NOTE: The symbol "x" was declared a constant here: 3085 `, 3086 }) 3087 } 3088 3089 func TestConstValueInliningDirectEval(t *testing.T) { 3090 dce_suite.expectBundled(t, bundled{ 3091 files: map[string]string{ 3092 "/top-level-no-eval.js": ` 3093 const x = 1 3094 console.log(x, evil('x')) 3095 `, 3096 "/top-level-eval.js": ` 3097 const x = 1 3098 console.log(x, eval('x')) 3099 `, 3100 "/nested-no-eval.js": ` 3101 (() => { 3102 const x = 1 3103 console.log(x, evil('x')) 3104 })() 3105 `, 3106 "/nested-eval.js": ` 3107 (() => { 3108 const x = 1 3109 console.log(x, eval('x')) 3110 })() 3111 `, 3112 "/ts-namespace-no-eval.ts": ` 3113 namespace y { 3114 export const x = 1 3115 console.log(x, evil('x')) 3116 } 3117 `, 3118 "/ts-namespace-eval.ts": ` 3119 namespace z { 3120 export const x = 1 3121 console.log(x, eval('x')) 3122 } 3123 `, 3124 }, 3125 entryPaths: []string{ 3126 "/top-level-no-eval.js", 3127 "/top-level-eval.js", 3128 "/nested-no-eval.js", 3129 "/nested-eval.js", 3130 "/ts-namespace-no-eval.ts", 3131 "/ts-namespace-eval.ts", 3132 }, 3133 options: config.Options{ 3134 Mode: config.ModePassThrough, 3135 AbsOutputDir: "/out", 3136 MinifySyntax: true, 3137 }, 3138 }) 3139 } 3140 3141 func TestCrossModuleConstantFoldingNumber(t *testing.T) { 3142 dce_suite.expectBundled(t, bundled{ 3143 files: map[string]string{ 3144 "/enum-constants.ts": ` 3145 export enum x { 3146 a = 3, 3147 b = 6, 3148 } 3149 `, 3150 "/enum-entry.ts": ` 3151 import { x } from './enum-constants' 3152 console.log([ 3153 +x.b, 3154 -x.b, 3155 ~x.b, 3156 !x.b, 3157 typeof x.b, 3158 ], [ 3159 x.a + x.b, 3160 x.a - x.b, 3161 x.a * x.b, 3162 x.a / x.b, 3163 x.a % x.b, 3164 x.a ** x.b, 3165 ], [ 3166 x.a < x.b, 3167 x.a > x.b, 3168 x.a <= x.b, 3169 x.a >= x.b, 3170 x.a == x.b, 3171 x.a != x.b, 3172 x.a === x.b, 3173 x.a !== x.b, 3174 ], [ 3175 x.b << 1, 3176 x.b >> 1, 3177 x.b >>> 1, 3178 ], [ 3179 x.a & x.b, 3180 x.a | x.b, 3181 x.a ^ x.b, 3182 ], [ 3183 x.a && x.b, 3184 x.a || x.b, 3185 x.a ?? x.b, 3186 x.a ? 'y' : 'n', 3187 !x.b ? 'y' : 'n', 3188 ]) 3189 `, 3190 3191 "/const-constants.js": ` 3192 export const a = 3 3193 export const b = 6 3194 `, 3195 "/const-entry.js": ` 3196 import { a, b } from './const-constants' 3197 console.log([ 3198 +b, 3199 -b, 3200 ~b, 3201 !b, 3202 typeof b, 3203 ], [ 3204 a + b, 3205 a - b, 3206 a * b, 3207 a / b, 3208 a % b, 3209 a ** b, 3210 ], [ 3211 a < b, 3212 a > b, 3213 a <= b, 3214 a >= b, 3215 a == b, 3216 a != b, 3217 a === b, 3218 a !== b, 3219 ], [ 3220 b << 1, 3221 b >> 1, 3222 b >>> 1, 3223 ], [ 3224 a & b, 3225 a | b, 3226 a ^ b, 3227 ], [ 3228 a && b, 3229 a || b, 3230 a ?? b, 3231 a ? 'y' : 'n', 3232 !b ? 'y' : 'n', 3233 ]) 3234 `, 3235 3236 "/nested-constants.ts": ` 3237 export const a = 2 3238 export const b = 4 3239 export const c = 8 3240 export enum x { 3241 a = 16, 3242 b = 32, 3243 c = 64, 3244 } 3245 `, 3246 "/nested-entry.ts": ` 3247 import { a, b, c, x } from './nested-constants' 3248 console.log({ 3249 'should be 4': ~(~a & ~b) & (b | c), 3250 'should be 32': ~(~x.a & ~x.b) & (x.b | x.c), 3251 }) 3252 `, 3253 }, 3254 entryPaths: []string{ 3255 "/enum-entry.ts", 3256 "/const-entry.js", 3257 "/nested-entry.ts", 3258 }, 3259 options: config.Options{ 3260 Mode: config.ModeBundle, 3261 AbsOutputDir: "/out", 3262 MinifySyntax: true, 3263 }, 3264 }) 3265 } 3266 3267 func TestCrossModuleConstantFoldingString(t *testing.T) { 3268 dce_suite.expectBundled(t, bundled{ 3269 files: map[string]string{ 3270 "/enum-constants.ts": ` 3271 export enum x { 3272 a = 'foo', 3273 b = 'bar', 3274 } 3275 `, 3276 "/enum-entry.ts": ` 3277 import { x } from './enum-constants' 3278 console.log([ 3279 typeof x.b, 3280 ], [ 3281 x.a + x.b, 3282 ], [ 3283 x.a < x.b, 3284 x.a > x.b, 3285 x.a <= x.b, 3286 x.a >= x.b, 3287 x.a == x.b, 3288 x.a != x.b, 3289 x.a === x.b, 3290 x.a !== x.b, 3291 ], [ 3292 x.a && x.b, 3293 x.a || x.b, 3294 x.a ?? x.b, 3295 x.a ? 'y' : 'n', 3296 !x.b ? 'y' : 'n', 3297 ]) 3298 `, 3299 3300 "/const-constants.js": ` 3301 export const a = 'foo' 3302 export const b = 'bar' 3303 `, 3304 "/const-entry.js": ` 3305 import { a, b } from './const-constants' 3306 console.log([ 3307 typeof b, 3308 ], [ 3309 a + b, 3310 ], [ 3311 a < b, 3312 a > b, 3313 a <= b, 3314 a >= b, 3315 a == b, 3316 a != b, 3317 a === b, 3318 a !== b, 3319 ], [ 3320 a && b, 3321 a || b, 3322 a ?? b, 3323 a ? 'y' : 'n', 3324 !b ? 'y' : 'n', 3325 ]) 3326 `, 3327 3328 "/nested-constants.ts": ` 3329 export const a = 'foo' 3330 export const b = 'bar' 3331 export const c = 'baz' 3332 export enum x { 3333 a = 'FOO', 3334 b = 'BAR', 3335 c = 'BAZ', 3336 } 3337 `, 3338 "/nested-entry.ts": ` 3339 import { a, b, c, x } from './nested-constants' 3340 console.log({ 3341 'should be foobarbaz': a + b + c, 3342 'should be FOOBARBAZ': x.a + x.b + x.c, 3343 }) 3344 `, 3345 }, 3346 entryPaths: []string{ 3347 "/enum-entry.ts", 3348 "/const-entry.js", 3349 "/nested-entry.ts", 3350 }, 3351 options: config.Options{ 3352 Mode: config.ModeBundle, 3353 AbsOutputDir: "/out", 3354 MinifySyntax: true, 3355 }, 3356 }) 3357 } 3358 3359 func TestMultipleDeclarationTreeShaking(t *testing.T) { 3360 dce_suite.expectBundled(t, bundled{ 3361 files: map[string]string{ 3362 "/var2.js": ` 3363 var x = 1 3364 console.log(x) 3365 var x = 2 3366 `, 3367 "/var3.js": ` 3368 var x = 1 3369 console.log(x) 3370 var x = 2 3371 console.log(x) 3372 var x = 3 3373 `, 3374 "/function2.js": ` 3375 function x() { return 1 } 3376 console.log(x()) 3377 function x() { return 2 } 3378 `, 3379 "/function3.js": ` 3380 function x() { return 1 } 3381 console.log(x()) 3382 function x() { return 2 } 3383 console.log(x()) 3384 function x() { return 3 } 3385 `, 3386 }, 3387 entryPaths: []string{ 3388 "/var2.js", 3389 "/var3.js", 3390 "/function2.js", 3391 "/function3.js", 3392 }, 3393 options: config.Options{ 3394 Mode: config.ModeBundle, 3395 AbsOutputDir: "/out", 3396 MinifySyntax: false, 3397 }, 3398 }) 3399 } 3400 3401 func TestMultipleDeclarationTreeShakingMinifySyntax(t *testing.T) { 3402 dce_suite.expectBundled(t, bundled{ 3403 files: map[string]string{ 3404 "/var2.js": ` 3405 var x = 1 3406 console.log(x) 3407 var x = 2 3408 `, 3409 "/var3.js": ` 3410 var x = 1 3411 console.log(x) 3412 var x = 2 3413 console.log(x) 3414 var x = 3 3415 `, 3416 "/function2.js": ` 3417 function x() { return 1 } 3418 console.log(x()) 3419 function x() { return 2 } 3420 `, 3421 "/function3.js": ` 3422 function x() { return 1 } 3423 console.log(x()) 3424 function x() { return 2 } 3425 console.log(x()) 3426 function x() { return 3 } 3427 `, 3428 }, 3429 entryPaths: []string{ 3430 "/var2.js", 3431 "/var3.js", 3432 "/function2.js", 3433 "/function3.js", 3434 }, 3435 options: config.Options{ 3436 Mode: config.ModeBundle, 3437 AbsOutputDir: "/out", 3438 MinifySyntax: true, 3439 }, 3440 }) 3441 } 3442 3443 // Pure call removal should still run iterators, which can have side effects 3444 func TestPureCallsWithSpread(t *testing.T) { 3445 dce_suite.expectBundled(t, bundled{ 3446 files: map[string]string{ 3447 "/entry.js": ` 3448 /* @__PURE__ */ foo(...args); 3449 /* @__PURE__ */ new foo(...args); 3450 `, 3451 }, 3452 entryPaths: []string{"/entry.js"}, 3453 options: config.Options{ 3454 Mode: config.ModeBundle, 3455 AbsOutputFile: "/out.js", 3456 MinifySyntax: true, 3457 }, 3458 }) 3459 } 3460 3461 func TestTopLevelFunctionInliningWithSpread(t *testing.T) { 3462 dce_suite.expectBundled(t, bundled{ 3463 files: map[string]string{ 3464 "/entry.js": ` 3465 function empty1() {} 3466 function empty2() {} 3467 function empty3() {} 3468 3469 function identity1(x) { return x } 3470 function identity2(x) { return x } 3471 function identity3(x) { return x } 3472 3473 empty1() 3474 empty2(args) 3475 empty3(...args) 3476 3477 identity1() 3478 identity2(args) 3479 identity3(...args) 3480 `, 3481 3482 "/inner.js": ` 3483 export function empty1() {} 3484 export function empty2() {} 3485 export function empty3() {} 3486 3487 export function identity1(x) { return x } 3488 export function identity2(x) { return x } 3489 export function identity3(x) { return x } 3490 `, 3491 3492 "/entry-outer.js": ` 3493 import { 3494 empty1, 3495 empty2, 3496 empty3, 3497 3498 identity1, 3499 identity2, 3500 identity3, 3501 } from './inner.js' 3502 3503 empty1() 3504 empty2(args) 3505 empty3(...args) 3506 3507 identity1() 3508 identity2(args) 3509 identity3(...args) 3510 `, 3511 }, 3512 entryPaths: []string{"/entry.js", "/entry-outer.js"}, 3513 options: config.Options{ 3514 Mode: config.ModeBundle, 3515 AbsOutputDir: "/out", 3516 MinifySyntax: true, 3517 }, 3518 }) 3519 } 3520 3521 func TestNestedFunctionInliningWithSpread(t *testing.T) { 3522 dce_suite.expectBundled(t, bundled{ 3523 files: map[string]string{ 3524 "/entry.js": ` 3525 function empty1() {} 3526 function empty2() {} 3527 function empty3() {} 3528 3529 function identity1(x) { return x } 3530 function identity2(x) { return x } 3531 function identity3(x) { return x } 3532 3533 check( 3534 empty1(), 3535 empty2(args), 3536 empty3(...args), 3537 3538 identity1(), 3539 identity2(args), 3540 identity3(...args), 3541 ) 3542 `, 3543 3544 "/inner.js": ` 3545 export function empty1() {} 3546 export function empty2() {} 3547 export function empty3() {} 3548 3549 export function identity1(x) { return x } 3550 export function identity2(x) { return x } 3551 export function identity3(x) { return x } 3552 `, 3553 3554 "/entry-outer.js": ` 3555 import { 3556 empty1, 3557 empty2, 3558 empty3, 3559 3560 identity1, 3561 identity2, 3562 identity3, 3563 } from './inner.js' 3564 3565 check( 3566 empty1(), 3567 empty2(args), 3568 empty3(...args), 3569 3570 identity1(), 3571 identity2(args), 3572 identity3(...args), 3573 ) 3574 `, 3575 }, 3576 entryPaths: []string{"/entry.js", "/entry-outer.js"}, 3577 options: config.Options{ 3578 Mode: config.ModeBundle, 3579 AbsOutputDir: "/out", 3580 MinifySyntax: true, 3581 }, 3582 }) 3583 } 3584 3585 func TestPackageJsonSideEffectsFalseCrossPlatformSlash(t *testing.T) { 3586 dce_suite.expectBundled(t, bundled{ 3587 files: map[string]string{ 3588 "/Users/user/project/src/entry.js": ` 3589 import "demo-pkg/foo" 3590 import "demo-pkg/bar" 3591 `, 3592 "/Users/user/project/node_modules/demo-pkg/foo.js": ` 3593 console.log('foo') 3594 `, 3595 "/Users/user/project/node_modules/demo-pkg/bar/index.js": ` 3596 console.log('bar') 3597 `, 3598 "/Users/user/project/node_modules/demo-pkg/package.json": ` 3599 { 3600 "sideEffects": [ 3601 "**/foo.js", 3602 "bar/index.js" 3603 ] 3604 } 3605 `, 3606 }, 3607 entryPaths: []string{"/Users/user/project/src/entry.js"}, 3608 options: config.Options{ 3609 Mode: config.ModeBundle, 3610 AbsOutputFile: "/out.js", 3611 }, 3612 }) 3613 } 3614 3615 func TestTreeShakingJSWithAssociatedCSS(t *testing.T) { 3616 dce_suite.expectBundled(t, bundled{ 3617 files: map[string]string{ 3618 "/project/test.jsx": ` 3619 import { Button } from 'pkg/button' 3620 import { Menu } from 'pkg/menu' 3621 render(<Button/>) 3622 `, 3623 "/project/node_modules/pkg/button.js": ` 3624 import './button.css' 3625 export let Button 3626 `, 3627 "/project/node_modules/pkg/button.css": ` 3628 button { color: red } 3629 `, 3630 "/project/node_modules/pkg/menu.js": ` 3631 import './menu.css' 3632 export let Menu 3633 `, 3634 "/project/node_modules/pkg/menu.css": ` 3635 menu { color: red } 3636 `, 3637 }, 3638 entryPaths: []string{"/project/test.jsx"}, 3639 options: config.Options{ 3640 Mode: config.ModeBundle, 3641 AbsOutputDir: "/out", 3642 }, 3643 }) 3644 } 3645 3646 func TestTreeShakingJSWithAssociatedCSSReExportSideEffectsFalse(t *testing.T) { 3647 dce_suite.expectBundled(t, bundled{ 3648 files: map[string]string{ 3649 "/project/test.jsx": ` 3650 import { Button } from 'pkg' 3651 render(<Button/>) 3652 `, 3653 "/project/node_modules/pkg/entry.js": ` 3654 export { Button } from './components' 3655 `, 3656 "/project/node_modules/pkg/package.json": `{ 3657 "main": "./entry.js", 3658 "sideEffects": false 3659 }`, 3660 "/project/node_modules/pkg/components.jsx": ` 3661 require('./button.css') 3662 export const Button = () => <button/> 3663 `, 3664 "/project/node_modules/pkg/button.css": ` 3665 button { color: red } 3666 `, 3667 }, 3668 entryPaths: []string{"/project/test.jsx"}, 3669 options: config.Options{ 3670 Mode: config.ModeBundle, 3671 AbsOutputDir: "/out", 3672 }, 3673 }) 3674 } 3675 3676 func TestTreeShakingJSWithAssociatedCSSReExportSideEffectsFalseOnlyJS(t *testing.T) { 3677 dce_suite.expectBundled(t, bundled{ 3678 files: map[string]string{ 3679 "/project/test.jsx": ` 3680 import { Button } from 'pkg' 3681 render(<Button/>) 3682 `, 3683 "/project/node_modules/pkg/entry.js": ` 3684 export { Button } from './components' 3685 `, 3686 "/project/node_modules/pkg/package.json": `{ 3687 "main": "./entry.js", 3688 "sideEffects": ["*.css"] 3689 }`, 3690 "/project/node_modules/pkg/components.jsx": ` 3691 require('./button.css') 3692 export const Button = () => <button/> 3693 `, 3694 "/project/node_modules/pkg/button.css": ` 3695 button { color: red } 3696 `, 3697 }, 3698 entryPaths: []string{"/project/test.jsx"}, 3699 options: config.Options{ 3700 Mode: config.ModeBundle, 3701 AbsOutputDir: "/out", 3702 }, 3703 }) 3704 } 3705 3706 func TestTreeShakingJSWithAssociatedCSSExportStarSideEffectsFalse(t *testing.T) { 3707 dce_suite.expectBundled(t, bundled{ 3708 files: map[string]string{ 3709 "/project/test.jsx": ` 3710 import { Button } from 'pkg' 3711 render(<Button/>) 3712 `, 3713 "/project/node_modules/pkg/entry.js": ` 3714 export * from './components' 3715 `, 3716 "/project/node_modules/pkg/package.json": `{ 3717 "main": "./entry.js", 3718 "sideEffects": false 3719 }`, 3720 "/project/node_modules/pkg/components.jsx": ` 3721 require('./button.css') 3722 export const Button = () => <button/> 3723 `, 3724 "/project/node_modules/pkg/button.css": ` 3725 button { color: red } 3726 `, 3727 }, 3728 entryPaths: []string{"/project/test.jsx"}, 3729 options: config.Options{ 3730 Mode: config.ModeBundle, 3731 AbsOutputDir: "/out", 3732 }, 3733 }) 3734 } 3735 3736 func TestTreeShakingJSWithAssociatedCSSExportStarSideEffectsFalseOnlyJS(t *testing.T) { 3737 dce_suite.expectBundled(t, bundled{ 3738 files: map[string]string{ 3739 "/project/test.jsx": ` 3740 import { Button } from 'pkg' 3741 render(<Button/>) 3742 `, 3743 "/project/node_modules/pkg/entry.js": ` 3744 export * from './components' 3745 `, 3746 "/project/node_modules/pkg/package.json": `{ 3747 "main": "./entry.js", 3748 "sideEffects": ["*.css"] 3749 }`, 3750 "/project/node_modules/pkg/components.jsx": ` 3751 require('./button.css') 3752 export const Button = () => <button/> 3753 `, 3754 "/project/node_modules/pkg/button.css": ` 3755 button { color: red } 3756 `, 3757 }, 3758 entryPaths: []string{"/project/test.jsx"}, 3759 options: config.Options{ 3760 Mode: config.ModeBundle, 3761 AbsOutputDir: "/out", 3762 }, 3763 }) 3764 } 3765 3766 func TestTreeShakingJSWithAssociatedCSSUnusedNestedImportSideEffectsFalse(t *testing.T) { 3767 dce_suite.expectBundled(t, bundled{ 3768 files: map[string]string{ 3769 "/project/test.jsx": ` 3770 import { Button } from 'pkg/button' 3771 render(<Button/>) 3772 `, 3773 "/project/node_modules/pkg/package.json": `{ 3774 "sideEffects": false 3775 }`, 3776 "/project/node_modules/pkg/button.jsx": ` 3777 import styles from './styles' 3778 export const Button = () => <button/> 3779 `, 3780 "/project/node_modules/pkg/styles.js": ` 3781 import './styles.css' 3782 export default {} 3783 `, 3784 "/project/node_modules/pkg/styles.css": ` 3785 button { color: red } 3786 `, 3787 }, 3788 entryPaths: []string{"/project/test.jsx"}, 3789 options: config.Options{ 3790 Mode: config.ModeBundle, 3791 AbsOutputDir: "/out", 3792 }, 3793 }) 3794 } 3795 3796 func TestTreeShakingJSWithAssociatedCSSUnusedNestedImportSideEffectsFalseOnlyJS(t *testing.T) { 3797 dce_suite.expectBundled(t, bundled{ 3798 files: map[string]string{ 3799 "/project/test.jsx": ` 3800 import { Button } from 'pkg/button' 3801 render(<Button/>) 3802 `, 3803 "/project/node_modules/pkg/package.json": `{ 3804 "sideEffects": ["*.css"] 3805 }`, 3806 "/project/node_modules/pkg/button.jsx": ` 3807 import styles from './styles' 3808 export const Button = () => <button/> 3809 `, 3810 "/project/node_modules/pkg/styles.js": ` 3811 import './styles.css' 3812 export default {} 3813 `, 3814 "/project/node_modules/pkg/styles.css": ` 3815 button { color: red } 3816 `, 3817 }, 3818 entryPaths: []string{"/project/test.jsx"}, 3819 options: config.Options{ 3820 Mode: config.ModeBundle, 3821 AbsOutputDir: "/out", 3822 }, 3823 }) 3824 } 3825 3826 func TestPreserveDirectivesMinifyPassThrough(t *testing.T) { 3827 dce_suite.expectBundled(t, bundled{ 3828 files: map[string]string{ 3829 "/entry.js": ` 3830 //! 1 3831 'use 1' 3832 //! 2 3833 'use 2' 3834 //! 3 3835 'use 3' 3836 entry() 3837 //! 4 3838 'use 4' 3839 //! 5 3840 'use 5' 3841 //! 6 3842 'use 6' 3843 `, 3844 }, 3845 entryPaths: []string{"/entry.js"}, 3846 options: config.Options{ 3847 Mode: config.ModePassThrough, 3848 AbsOutputFile: "/out.js", 3849 MinifySyntax: true, 3850 }, 3851 }) 3852 } 3853 3854 func TestPreserveDirectivesMinifyIIFE(t *testing.T) { 3855 dce_suite.expectBundled(t, bundled{ 3856 files: map[string]string{ 3857 "/entry.js": ` 3858 //! 1 3859 'use 1' 3860 //! 2 3861 'use 2' 3862 //! 3 3863 'use 3' 3864 entry() 3865 //! 4 3866 'use 4' 3867 //! 5 3868 'use 5' 3869 //! 6 3870 'use 6' 3871 `, 3872 }, 3873 entryPaths: []string{"/entry.js"}, 3874 options: config.Options{ 3875 Mode: config.ModeConvertFormat, 3876 OutputFormat: config.FormatIIFE, 3877 AbsOutputFile: "/out.js", 3878 MinifySyntax: true, 3879 }, 3880 }) 3881 } 3882 3883 func TestPreserveDirectivesMinifyBundle(t *testing.T) { 3884 dce_suite.expectBundled(t, bundled{ 3885 files: map[string]string{ 3886 "/entry.js": ` 3887 //! 1 3888 'use 1' 3889 //! 2 3890 'use 2' 3891 //! 3 3892 'use 3' 3893 entry() 3894 //! 4 3895 'use 4' 3896 //! 5 3897 'use 5' 3898 //! 6 3899 'use 6' 3900 import "./nested.js" 3901 `, 3902 "/nested.js": ` 3903 //! A 3904 'use A' 3905 //! B 3906 'use B' 3907 //! C 3908 'use C' 3909 nested() 3910 //! D 3911 'use D' 3912 //! E 3913 'use E' 3914 //! F 3915 'use F' 3916 `, 3917 }, 3918 entryPaths: []string{"/entry.js"}, 3919 options: config.Options{ 3920 Mode: config.ModeBundle, 3921 OutputFormat: config.FormatIIFE, 3922 AbsOutputFile: "/out.js", 3923 MinifySyntax: true, 3924 }, 3925 }) 3926 } 3927 3928 // See: https://github.com/rollup/rollup/pull/5024 3929 func TestNoSideEffectsComment(t *testing.T) { 3930 dce_suite.expectBundled(t, bundled{ 3931 files: map[string]string{ 3932 "/expr-fn.js": ` 3933 //! These should all have "no side effects" 3934 x([ 3935 /* #__NO_SIDE_EFFECTS__ */ function() {}, 3936 /* #__NO_SIDE_EFFECTS__ */ function y() {}, 3937 /* #__NO_SIDE_EFFECTS__ */ function*() {}, 3938 /* #__NO_SIDE_EFFECTS__ */ function* y() {}, 3939 /* #__NO_SIDE_EFFECTS__ */ async function() {}, 3940 /* #__NO_SIDE_EFFECTS__ */ async function y() {}, 3941 /* #__NO_SIDE_EFFECTS__ */ async function*() {}, 3942 /* #__NO_SIDE_EFFECTS__ */ async function* y() {}, 3943 ]) 3944 `, 3945 "/expr-arrow.js": ` 3946 //! These should all have "no side effects" 3947 x([ 3948 /* #__NO_SIDE_EFFECTS__ */ y => y, 3949 /* #__NO_SIDE_EFFECTS__ */ () => {}, 3950 /* #__NO_SIDE_EFFECTS__ */ (y) => (y), 3951 /* #__NO_SIDE_EFFECTS__ */ async y => y, 3952 /* #__NO_SIDE_EFFECTS__ */ async () => {}, 3953 /* #__NO_SIDE_EFFECTS__ */ async (y) => (y), 3954 ]) 3955 `, 3956 3957 "/stmt-fn.js": ` 3958 //! These should all have "no side effects" 3959 // #__NO_SIDE_EFFECTS__ 3960 function a() {} 3961 // #__NO_SIDE_EFFECTS__ 3962 function* b() {} 3963 // #__NO_SIDE_EFFECTS__ 3964 async function c() {} 3965 // #__NO_SIDE_EFFECTS__ 3966 async function* d() {} 3967 `, 3968 "/stmt-export-fn.js": ` 3969 //! These should all have "no side effects" 3970 /* @__NO_SIDE_EFFECTS__ */ export function a() {} 3971 /* @__NO_SIDE_EFFECTS__ */ export function* b() {} 3972 /* @__NO_SIDE_EFFECTS__ */ export async function c() {} 3973 /* @__NO_SIDE_EFFECTS__ */ export async function* d() {} 3974 `, 3975 "/stmt-local.js": ` 3976 //! Only "c0" and "c2" should have "no side effects" (Rollup only respects "const" and only for the first one) 3977 /* #__NO_SIDE_EFFECTS__ */ var v0 = function() {}, v1 = function() {} 3978 /* #__NO_SIDE_EFFECTS__ */ let l0 = function() {}, l1 = function() {} 3979 /* #__NO_SIDE_EFFECTS__ */ const c0 = function() {}, c1 = function() {} 3980 /* #__NO_SIDE_EFFECTS__ */ var v2 = () => {}, v3 = () => {} 3981 /* #__NO_SIDE_EFFECTS__ */ let l2 = () => {}, l3 = () => {} 3982 /* #__NO_SIDE_EFFECTS__ */ const c2 = () => {}, c3 = () => {} 3983 `, 3984 "/stmt-export-local.js": ` 3985 //! Only "c0" and "c2" should have "no side effects" (Rollup only respects "const" and only for the first one) 3986 /* #__NO_SIDE_EFFECTS__ */ export var v0 = function() {}, v1 = function() {} 3987 /* #__NO_SIDE_EFFECTS__ */ export let l0 = function() {}, l1 = function() {} 3988 /* #__NO_SIDE_EFFECTS__ */ export const c0 = function() {}, c1 = function() {} 3989 /* #__NO_SIDE_EFFECTS__ */ export var v2 = () => {}, v3 = () => {} 3990 /* #__NO_SIDE_EFFECTS__ */ export let l2 = () => {}, l3 = () => {} 3991 /* #__NO_SIDE_EFFECTS__ */ export const c2 = () => {}, c3 = () => {} 3992 `, 3993 3994 "/ns-export-fn.ts": ` 3995 namespace ns { 3996 //! These should all have "no side effects" 3997 /* @__NO_SIDE_EFFECTS__ */ export function a() {} 3998 /* @__NO_SIDE_EFFECTS__ */ export function* b() {} 3999 /* @__NO_SIDE_EFFECTS__ */ export async function c() {} 4000 /* @__NO_SIDE_EFFECTS__ */ export async function* d() {} 4001 } 4002 `, 4003 "/ns-export-local.ts": ` 4004 namespace ns { 4005 //! Only "c0" and "c2" should have "no side effects" (Rollup only respects "const" and only for the first one) 4006 /* #__NO_SIDE_EFFECTS__ */ export var v0 = function() {}, v1 = function() {} 4007 /* #__NO_SIDE_EFFECTS__ */ export let l0 = function() {}, l1 = function() {} 4008 /* #__NO_SIDE_EFFECTS__ */ export const c0 = function() {}, c1 = function() {} 4009 /* #__NO_SIDE_EFFECTS__ */ export var v2 = () => {}, v3 = () => {} 4010 /* #__NO_SIDE_EFFECTS__ */ export let l2 = () => {}, l3 = () => {} 4011 /* #__NO_SIDE_EFFECTS__ */ export const c2 = () => {}, c3 = () => {} 4012 } 4013 `, 4014 4015 "/stmt-export-default-before-fn-anon.js": `/*! This should have "no side effects" */ /* #__NO_SIDE_EFFECTS__ */ export default function() {}`, 4016 "/stmt-export-default-before-fn-name.js": `/*! This should have "no side effects" */ /* #__NO_SIDE_EFFECTS__ */ export default function f() {}`, 4017 "/stmt-export-default-before-gen-fn-anon.js": `/*! This should have "no side effects" */ /* #__NO_SIDE_EFFECTS__ */ export default function*() {}`, 4018 "/stmt-export-default-before-gen-fn-name.js": `/*! This should have "no side effects" */ /* #__NO_SIDE_EFFECTS__ */ export default function* f() {}`, 4019 "/stmt-export-default-before-async-fn-anon.js": `/*! This should have "no side effects" */ /* #__NO_SIDE_EFFECTS__ */ export default async function() {}`, 4020 "/stmt-export-default-before-async-fn-name.js": `/*! This should have "no side effects" */ /* #__NO_SIDE_EFFECTS__ */ export default async function f() {}`, 4021 "/stmt-export-default-before-async-gen-fn-anon.js": `/*! This should have "no side effects" */ /* #__NO_SIDE_EFFECTS__ */ export default async function*() {}`, 4022 "/stmt-export-default-before-async-gen-fn-name.js": `/*! This should have "no side effects" */ /* #__NO_SIDE_EFFECTS__ */ export default async function* f() {}`, 4023 4024 "/stmt-export-default-after-fn-anon.js": `/*! This should have "no side effects" */ export default /* @__NO_SIDE_EFFECTS__ */ function() {}`, 4025 "/stmt-export-default-after-fn-name.js": `/*! This should have "no side effects" */ export default /* @__NO_SIDE_EFFECTS__ */ function f() {}`, 4026 "/stmt-export-default-after-gen-fn-anon.js": `/*! This should have "no side effects" */ export default /* @__NO_SIDE_EFFECTS__ */ function*() {}`, 4027 "/stmt-export-default-after-gen-fn-name.js": `/*! This should have "no side effects" */ export default /* @__NO_SIDE_EFFECTS__ */ function* f() {}`, 4028 "/stmt-export-default-after-async-fn-anon.js": `/*! This should have "no side effects" */ export default /* @__NO_SIDE_EFFECTS__ */ async function() {}`, 4029 "/stmt-export-default-after-async-fn-name.js": `/*! This should have "no side effects" */ export default /* @__NO_SIDE_EFFECTS__ */ async function f() {}`, 4030 "/stmt-export-default-after-async-gen-fn-anon.js": `/*! This should have "no side effects" */ export default /* @__NO_SIDE_EFFECTS__ */ async function*() {}`, 4031 "/stmt-export-default-after-async-gen-fn-name.js": `/*! This should have "no side effects" */ export default /* @__NO_SIDE_EFFECTS__ */ async function* f() {}`, 4032 }, 4033 entryPaths: []string{ 4034 "/expr-fn.js", 4035 "/expr-arrow.js", 4036 4037 "/stmt-fn.js", 4038 "/stmt-export-fn.js", 4039 "/stmt-local.js", 4040 "/stmt-export-local.js", 4041 4042 "/ns-export-fn.ts", 4043 "/ns-export-local.ts", 4044 4045 "/stmt-export-default-before-fn-anon.js", 4046 "/stmt-export-default-before-fn-name.js", 4047 "/stmt-export-default-before-gen-fn-anon.js", 4048 "/stmt-export-default-before-gen-fn-name.js", 4049 "/stmt-export-default-before-async-fn-anon.js", 4050 "/stmt-export-default-before-async-fn-name.js", 4051 "/stmt-export-default-before-async-gen-fn-anon.js", 4052 "/stmt-export-default-before-async-gen-fn-name.js", 4053 4054 "/stmt-export-default-after-fn-anon.js", 4055 "/stmt-export-default-after-fn-name.js", 4056 "/stmt-export-default-after-gen-fn-anon.js", 4057 "/stmt-export-default-after-gen-fn-name.js", 4058 "/stmt-export-default-after-async-fn-anon.js", 4059 "/stmt-export-default-after-async-fn-name.js", 4060 "/stmt-export-default-after-async-gen-fn-anon.js", 4061 "/stmt-export-default-after-async-gen-fn-name.js", 4062 }, 4063 options: config.Options{ 4064 AbsOutputDir: "/out", 4065 }, 4066 }) 4067 } 4068 4069 func TestNoSideEffectsCommentIgnoreAnnotations(t *testing.T) { 4070 dce_suite.expectBundled(t, bundled{ 4071 files: map[string]string{ 4072 "/expr-fn.js": ` 4073 x([ 4074 /* #__NO_SIDE_EFFECTS__ */ function() {}, 4075 /* #__NO_SIDE_EFFECTS__ */ function y() {}, 4076 /* #__NO_SIDE_EFFECTS__ */ function*() {}, 4077 /* #__NO_SIDE_EFFECTS__ */ function* y() {}, 4078 /* #__NO_SIDE_EFFECTS__ */ async function() {}, 4079 /* #__NO_SIDE_EFFECTS__ */ async function y() {}, 4080 /* #__NO_SIDE_EFFECTS__ */ async function*() {}, 4081 /* #__NO_SIDE_EFFECTS__ */ async function* y() {}, 4082 ]) 4083 `, 4084 "/expr-arrow.js": ` 4085 x([ 4086 /* #__NO_SIDE_EFFECTS__ */ y => y, 4087 /* #__NO_SIDE_EFFECTS__ */ () => {}, 4088 /* #__NO_SIDE_EFFECTS__ */ (y) => (y), 4089 /* #__NO_SIDE_EFFECTS__ */ async y => y, 4090 /* #__NO_SIDE_EFFECTS__ */ async () => {}, 4091 /* #__NO_SIDE_EFFECTS__ */ async (y) => (y), 4092 ]) 4093 `, 4094 4095 "/stmt-fn.js": ` 4096 // #__NO_SIDE_EFFECTS__ 4097 function a() {} 4098 // #__NO_SIDE_EFFECTS__ 4099 function* b() {} 4100 // #__NO_SIDE_EFFECTS__ 4101 async function c() {} 4102 // #__NO_SIDE_EFFECTS__ 4103 async function* d() {} 4104 `, 4105 "/stmt-export-fn.js": ` 4106 /* @__NO_SIDE_EFFECTS__ */ export function a() {} 4107 /* @__NO_SIDE_EFFECTS__ */ export function* b() {} 4108 /* @__NO_SIDE_EFFECTS__ */ export async function c() {} 4109 /* @__NO_SIDE_EFFECTS__ */ export async function* d() {} 4110 `, 4111 "/stmt-local.js": ` 4112 /* #__NO_SIDE_EFFECTS__ */ var v0 = function() {}, v1 = function() {} 4113 /* #__NO_SIDE_EFFECTS__ */ let l0 = function() {}, l1 = function() {} 4114 /* #__NO_SIDE_EFFECTS__ */ const c0 = function() {}, c1 = function() {} 4115 /* #__NO_SIDE_EFFECTS__ */ var v2 = () => {}, v3 = () => {} 4116 /* #__NO_SIDE_EFFECTS__ */ let l2 = () => {}, l3 = () => {} 4117 /* #__NO_SIDE_EFFECTS__ */ const c2 = () => {}, c3 = () => {} 4118 `, 4119 "/stmt-export-local.js": ` 4120 /* #__NO_SIDE_EFFECTS__ */ export var v0 = function() {}, v1 = function() {} 4121 /* #__NO_SIDE_EFFECTS__ */ export let l0 = function() {}, l1 = function() {} 4122 /* #__NO_SIDE_EFFECTS__ */ export const c0 = function() {}, c1 = function() {} 4123 /* #__NO_SIDE_EFFECTS__ */ export var v2 = () => {}, v3 = () => {} 4124 /* #__NO_SIDE_EFFECTS__ */ export let l2 = () => {}, l3 = () => {} 4125 /* #__NO_SIDE_EFFECTS__ */ export const c2 = () => {}, c3 = () => {} 4126 `, 4127 4128 "/ns-export-fn.ts": ` 4129 namespace ns { 4130 /* @__NO_SIDE_EFFECTS__ */ export function a() {} 4131 /* @__NO_SIDE_EFFECTS__ */ export function* b() {} 4132 /* @__NO_SIDE_EFFECTS__ */ export async function c() {} 4133 /* @__NO_SIDE_EFFECTS__ */ export async function* d() {} 4134 } 4135 `, 4136 "/ns-export-local.ts": ` 4137 namespace ns { 4138 /* #__NO_SIDE_EFFECTS__ */ export var v0 = function() {}, v1 = function() {} 4139 /* #__NO_SIDE_EFFECTS__ */ export let l0 = function() {}, l1 = function() {} 4140 /* #__NO_SIDE_EFFECTS__ */ export const c0 = function() {}, c1 = function() {} 4141 /* #__NO_SIDE_EFFECTS__ */ export var v2 = () => {}, v3 = () => {} 4142 /* #__NO_SIDE_EFFECTS__ */ export let l2 = () => {}, l3 = () => {} 4143 /* #__NO_SIDE_EFFECTS__ */ export const c2 = () => {}, c3 = () => {} 4144 } 4145 `, 4146 4147 "/stmt-export-default-before-fn-anon.js": `/* #__NO_SIDE_EFFECTS__ */ export default function() {}`, 4148 "/stmt-export-default-before-fn-name.js": `/* #__NO_SIDE_EFFECTS__ */ export default function f() {}`, 4149 "/stmt-export-default-before-gen-fn-anon.js": `/* #__NO_SIDE_EFFECTS__ */ export default function*() {}`, 4150 "/stmt-export-default-before-gen-fn-name.js": `/* #__NO_SIDE_EFFECTS__ */ export default function* f() {}`, 4151 "/stmt-export-default-before-async-fn-anon.js": `/* #__NO_SIDE_EFFECTS__ */ export default async function() {}`, 4152 "/stmt-export-default-before-async-fn-name.js": `/* #__NO_SIDE_EFFECTS__ */ export default async function f() {}`, 4153 "/stmt-export-default-before-async-gen-fn-anon.js": `/* #__NO_SIDE_EFFECTS__ */ export default async function*() {}`, 4154 "/stmt-export-default-before-async-gen-fn-name.js": `/* #__NO_SIDE_EFFECTS__ */ export default async function* f() {}`, 4155 4156 "/stmt-export-default-after-fn-anon.js": `export default /* @__NO_SIDE_EFFECTS__ */ function() {}`, 4157 "/stmt-export-default-after-fn-name.js": `export default /* @__NO_SIDE_EFFECTS__ */ function f() {}`, 4158 "/stmt-export-default-after-gen-fn-anon.js": `export default /* @__NO_SIDE_EFFECTS__ */ function*() {}`, 4159 "/stmt-export-default-after-gen-fn-name.js": `export default /* @__NO_SIDE_EFFECTS__ */ function* f() {}`, 4160 "/stmt-export-default-after-async-fn-anon.js": `export default /* @__NO_SIDE_EFFECTS__ */ async function() {}`, 4161 "/stmt-export-default-after-async-fn-name.js": `export default /* @__NO_SIDE_EFFECTS__ */ async function f() {}`, 4162 "/stmt-export-default-after-async-gen-fn-anon.js": `export default /* @__NO_SIDE_EFFECTS__ */ async function*() {}`, 4163 "/stmt-export-default-after-async-gen-fn-name.js": `export default /* @__NO_SIDE_EFFECTS__ */ async function* f() {}`, 4164 }, 4165 entryPaths: []string{ 4166 "/expr-fn.js", 4167 "/expr-arrow.js", 4168 4169 "/stmt-fn.js", 4170 "/stmt-export-fn.js", 4171 "/stmt-local.js", 4172 "/stmt-export-local.js", 4173 4174 "/ns-export-fn.ts", 4175 "/ns-export-local.ts", 4176 4177 "/stmt-export-default-before-fn-anon.js", 4178 "/stmt-export-default-before-fn-name.js", 4179 "/stmt-export-default-before-gen-fn-anon.js", 4180 "/stmt-export-default-before-gen-fn-name.js", 4181 "/stmt-export-default-before-async-fn-anon.js", 4182 "/stmt-export-default-before-async-fn-name.js", 4183 "/stmt-export-default-before-async-gen-fn-anon.js", 4184 "/stmt-export-default-before-async-gen-fn-name.js", 4185 4186 "/stmt-export-default-after-fn-anon.js", 4187 "/stmt-export-default-after-fn-name.js", 4188 "/stmt-export-default-after-gen-fn-anon.js", 4189 "/stmt-export-default-after-gen-fn-name.js", 4190 "/stmt-export-default-after-async-fn-anon.js", 4191 "/stmt-export-default-after-async-fn-name.js", 4192 "/stmt-export-default-after-async-gen-fn-anon.js", 4193 "/stmt-export-default-after-async-gen-fn-name.js", 4194 }, 4195 options: config.Options{ 4196 AbsOutputDir: "/out", 4197 IgnoreDCEAnnotations: true, 4198 }, 4199 }) 4200 } 4201 4202 func TestNoSideEffectsCommentMinifyWhitespace(t *testing.T) { 4203 dce_suite.expectBundled(t, bundled{ 4204 files: map[string]string{ 4205 "/expr-fn.js": ` 4206 x([ 4207 /* #__NO_SIDE_EFFECTS__ */ function() {}, 4208 /* #__NO_SIDE_EFFECTS__ */ function y() {}, 4209 /* #__NO_SIDE_EFFECTS__ */ function*() {}, 4210 /* #__NO_SIDE_EFFECTS__ */ function* y() {}, 4211 /* #__NO_SIDE_EFFECTS__ */ async function() {}, 4212 /* #__NO_SIDE_EFFECTS__ */ async function y() {}, 4213 /* #__NO_SIDE_EFFECTS__ */ async function*() {}, 4214 /* #__NO_SIDE_EFFECTS__ */ async function* y() {}, 4215 ]) 4216 `, 4217 "/expr-arrow.js": ` 4218 x([ 4219 /* #__NO_SIDE_EFFECTS__ */ y => y, 4220 /* #__NO_SIDE_EFFECTS__ */ () => {}, 4221 /* #__NO_SIDE_EFFECTS__ */ (y) => (y), 4222 /* #__NO_SIDE_EFFECTS__ */ async y => y, 4223 /* #__NO_SIDE_EFFECTS__ */ async () => {}, 4224 /* #__NO_SIDE_EFFECTS__ */ async (y) => (y), 4225 ]) 4226 `, 4227 4228 "/stmt-fn.js": ` 4229 // #__NO_SIDE_EFFECTS__ 4230 function a() {} 4231 // #__NO_SIDE_EFFECTS__ 4232 function* b() {} 4233 // #__NO_SIDE_EFFECTS__ 4234 async function c() {} 4235 // #__NO_SIDE_EFFECTS__ 4236 async function* d() {} 4237 `, 4238 "/stmt-export-fn.js": ` 4239 /* @__NO_SIDE_EFFECTS__ */ export function a() {} 4240 /* @__NO_SIDE_EFFECTS__ */ export function* b() {} 4241 /* @__NO_SIDE_EFFECTS__ */ export async function c() {} 4242 /* @__NO_SIDE_EFFECTS__ */ export async function* d() {} 4243 `, 4244 "/stmt-local.js": ` 4245 /* #__NO_SIDE_EFFECTS__ */ var v0 = function() {}, v1 = function() {} 4246 /* #__NO_SIDE_EFFECTS__ */ let l0 = function() {}, l1 = function() {} 4247 /* #__NO_SIDE_EFFECTS__ */ const c0 = function() {}, c1 = function() {} 4248 /* #__NO_SIDE_EFFECTS__ */ var v2 = () => {}, v3 = () => {} 4249 /* #__NO_SIDE_EFFECTS__ */ let l2 = () => {}, l3 = () => {} 4250 /* #__NO_SIDE_EFFECTS__ */ const c2 = () => {}, c3 = () => {} 4251 `, 4252 "/stmt-export-local.js": ` 4253 /* #__NO_SIDE_EFFECTS__ */ export var v0 = function() {}, v1 = function() {} 4254 /* #__NO_SIDE_EFFECTS__ */ export let l0 = function() {}, l1 = function() {} 4255 /* #__NO_SIDE_EFFECTS__ */ export const c0 = function() {}, c1 = function() {} 4256 /* #__NO_SIDE_EFFECTS__ */ export var v2 = () => {}, v3 = () => {} 4257 /* #__NO_SIDE_EFFECTS__ */ export let l2 = () => {}, l3 = () => {} 4258 /* #__NO_SIDE_EFFECTS__ */ export const c2 = () => {}, c3 = () => {} 4259 `, 4260 4261 "/ns-export-fn.ts": ` 4262 namespace ns { 4263 /* @__NO_SIDE_EFFECTS__ */ export function a() {} 4264 /* @__NO_SIDE_EFFECTS__ */ export function* b() {} 4265 /* @__NO_SIDE_EFFECTS__ */ export async function c() {} 4266 /* @__NO_SIDE_EFFECTS__ */ export async function* d() {} 4267 } 4268 `, 4269 "/ns-export-local.ts": ` 4270 namespace ns { 4271 /* #__NO_SIDE_EFFECTS__ */ export var v0 = function() {}, v1 = function() {} 4272 /* #__NO_SIDE_EFFECTS__ */ export let l0 = function() {}, l1 = function() {} 4273 /* #__NO_SIDE_EFFECTS__ */ export const c0 = function() {}, c1 = function() {} 4274 /* #__NO_SIDE_EFFECTS__ */ export var v2 = () => {}, v3 = () => {} 4275 /* #__NO_SIDE_EFFECTS__ */ export let l2 = () => {}, l3 = () => {} 4276 /* #__NO_SIDE_EFFECTS__ */ export const c2 = () => {}, c3 = () => {} 4277 } 4278 `, 4279 4280 "/stmt-export-default-before-fn-anon.js": `/* #__NO_SIDE_EFFECTS__ */ export default function() {}`, 4281 "/stmt-export-default-before-fn-name.js": `/* #__NO_SIDE_EFFECTS__ */ export default function f() {}`, 4282 "/stmt-export-default-before-gen-fn-anon.js": `/* #__NO_SIDE_EFFECTS__ */ export default function*() {}`, 4283 "/stmt-export-default-before-gen-fn-name.js": `/* #__NO_SIDE_EFFECTS__ */ export default function* f() {}`, 4284 "/stmt-export-default-before-async-fn-anon.js": `/* #__NO_SIDE_EFFECTS__ */ export default async function() {}`, 4285 "/stmt-export-default-before-async-fn-name.js": `/* #__NO_SIDE_EFFECTS__ */ export default async function f() {}`, 4286 "/stmt-export-default-before-async-gen-fn-anon.js": `/* #__NO_SIDE_EFFECTS__ */ export default async function*() {}`, 4287 "/stmt-export-default-before-async-gen-fn-name.js": `/* #__NO_SIDE_EFFECTS__ */ export default async function* f() {}`, 4288 4289 "/stmt-export-default-after-fn-anon.js": `export default /* @__NO_SIDE_EFFECTS__ */ function() {}`, 4290 "/stmt-export-default-after-fn-name.js": `export default /* @__NO_SIDE_EFFECTS__ */ function f() {}`, 4291 "/stmt-export-default-after-gen-fn-anon.js": `export default /* @__NO_SIDE_EFFECTS__ */ function*() {}`, 4292 "/stmt-export-default-after-gen-fn-name.js": `export default /* @__NO_SIDE_EFFECTS__ */ function* f() {}`, 4293 "/stmt-export-default-after-async-fn-anon.js": `export default /* @__NO_SIDE_EFFECTS__ */ async function() {}`, 4294 "/stmt-export-default-after-async-fn-name.js": `export default /* @__NO_SIDE_EFFECTS__ */ async function f() {}`, 4295 "/stmt-export-default-after-async-gen-fn-anon.js": `export default /* @__NO_SIDE_EFFECTS__ */ async function*() {}`, 4296 "/stmt-export-default-after-async-gen-fn-name.js": `export default /* @__NO_SIDE_EFFECTS__ */ async function* f() {}`, 4297 }, 4298 entryPaths: []string{ 4299 "/expr-fn.js", 4300 "/expr-arrow.js", 4301 4302 "/stmt-fn.js", 4303 "/stmt-export-fn.js", 4304 "/stmt-local.js", 4305 "/stmt-export-local.js", 4306 4307 "/ns-export-fn.ts", 4308 "/ns-export-local.ts", 4309 4310 "/stmt-export-default-before-fn-anon.js", 4311 "/stmt-export-default-before-fn-name.js", 4312 "/stmt-export-default-before-gen-fn-anon.js", 4313 "/stmt-export-default-before-gen-fn-name.js", 4314 "/stmt-export-default-before-async-fn-anon.js", 4315 "/stmt-export-default-before-async-fn-name.js", 4316 "/stmt-export-default-before-async-gen-fn-anon.js", 4317 "/stmt-export-default-before-async-gen-fn-name.js", 4318 4319 "/stmt-export-default-after-fn-anon.js", 4320 "/stmt-export-default-after-fn-name.js", 4321 "/stmt-export-default-after-gen-fn-anon.js", 4322 "/stmt-export-default-after-gen-fn-name.js", 4323 "/stmt-export-default-after-async-fn-anon.js", 4324 "/stmt-export-default-after-async-fn-name.js", 4325 "/stmt-export-default-after-async-gen-fn-anon.js", 4326 "/stmt-export-default-after-async-gen-fn-name.js", 4327 }, 4328 options: config.Options{ 4329 AbsOutputDir: "/out", 4330 MinifyWhitespace: true, 4331 }, 4332 }) 4333 } 4334 4335 func TestNoSideEffectsCommentUnusedCalls(t *testing.T) { 4336 dce_suite.expectBundled(t, bundled{ 4337 files: map[string]string{ 4338 "/stmt-fn.js": ` 4339 /* @__NO_SIDE_EFFECTS__ */ function f(y) { sideEffect(y) } 4340 /* @__NO_SIDE_EFFECTS__ */ function* g(y) { sideEffect(y) } 4341 f('removeThisCall') 4342 g('removeThisCall') 4343 f(onlyKeepThisIdentifier) 4344 g(onlyKeepThisIdentifier) 4345 x(f('keepThisCall')) 4346 x(g('keepThisCall')) 4347 `, 4348 "/stmt-local.js": ` 4349 /* @__NO_SIDE_EFFECTS__ */ const f = function (y) { sideEffect(y) } 4350 /* @__NO_SIDE_EFFECTS__ */ const g = function* (y) { sideEffect(y) } 4351 f('removeThisCall') 4352 g('removeThisCall') 4353 f(onlyKeepThisIdentifier) 4354 g(onlyKeepThisIdentifier) 4355 x(f('keepThisCall')) 4356 x(g('keepThisCall')) 4357 `, 4358 "/expr-fn.js": ` 4359 const f = /* @__NO_SIDE_EFFECTS__ */ function (y) { sideEffect(y) } 4360 const g = /* @__NO_SIDE_EFFECTS__ */ function* (y) { sideEffect(y) } 4361 f('removeThisCall') 4362 g('removeThisCall') 4363 f(onlyKeepThisIdentifier) 4364 g(onlyKeepThisIdentifier) 4365 x(f('keepThisCall')) 4366 x(g('keepThisCall')) 4367 `, 4368 "/stmt-export-default-fn.js": ` 4369 /* @__NO_SIDE_EFFECTS__ */ export default function f(y) { sideEffect(y) } 4370 f('removeThisCall') 4371 f(onlyKeepThisIdentifier) 4372 x(f('keepThisCall')) 4373 `, 4374 }, 4375 entryPaths: []string{ 4376 "/stmt-fn.js", 4377 "/stmt-local.js", 4378 "/expr-fn.js", 4379 "/stmt-export-default-fn.js", 4380 }, 4381 options: config.Options{ 4382 AbsOutputDir: "/out", 4383 TreeShaking: true, 4384 MinifySyntax: true, 4385 }, 4386 }) 4387 } 4388 4389 func TestNoSideEffectsCommentTypeScriptDeclare(t *testing.T) { 4390 dce_suite.expectBundled(t, bundled{ 4391 files: map[string]string{ 4392 "/entry.ts": ` 4393 // These should not cause us to crash 4394 /* @__NO_SIDE_EFFECTS__ */ declare function f1(y) { sideEffect(y) } 4395 /* @__NO_SIDE_EFFECTS__ */ declare const f2 = function (y) { sideEffect(y) } 4396 /* @__NO_SIDE_EFFECTS__ */ declare const f3 = (y) => { sideEffect(y) } 4397 declare const f4 = /* @__NO_SIDE_EFFECTS__ */ function (y) { sideEffect(y) } 4398 declare const f5 = /* @__NO_SIDE_EFFECTS__ */ (y) => { sideEffect(y) } 4399 namespace ns { 4400 /* @__NO_SIDE_EFFECTS__ */ export declare function f1(y) { sideEffect(y) } 4401 /* @__NO_SIDE_EFFECTS__ */ export declare const f2 = function (y) { sideEffect(y) } 4402 /* @__NO_SIDE_EFFECTS__ */ export declare const f3 = (y) => { sideEffect(y) } 4403 export declare const f4 = /* @__NO_SIDE_EFFECTS__ */ function (y) { sideEffect(y) } 4404 export declare const f5 = /* @__NO_SIDE_EFFECTS__ */ (y) => { sideEffect(y) } 4405 } 4406 `, 4407 }, 4408 entryPaths: []string{ 4409 "/entry.ts", 4410 }, 4411 options: config.Options{ 4412 AbsOutputDir: "/out", 4413 }, 4414 }) 4415 } 4416 4417 func TestDCEOfIIFE(t *testing.T) { 4418 dce_suite.expectBundled(t, bundled{ 4419 files: map[string]string{ 4420 "/remove-these.js": ` 4421 (() => {})(); 4422 (() => {})(keepThisButRemoveTheIIFE); 4423 (() => { /* @__PURE__ */ removeMe() })(); 4424 var someVar; 4425 (x => {})(someVar); 4426 var removeThis = /* @__PURE__ */ (() => stuff())(); 4427 var removeThis2 = (() => 123)(); 4428 `, 4429 "/keep-these.js": ` 4430 undef = (() => {})(); 4431 (() => { keepMe() })(); 4432 ((x = keepMe()) => {})(); 4433 var someVar; 4434 (([y]) => {})(someVar); 4435 (({z}) => {})(someVar); 4436 var keepThis = /* @__PURE__ */ (() => stuff())(); 4437 keepThis(); 4438 ((_ = keepMe()) => {})(); 4439 var isPure = ((x, y) => 123)(); 4440 use(isPure); 4441 var isNotPure = ((x = foo, y = bar) => 123)(); 4442 use(isNotPure); 4443 (async () => ({ get then() { notPure() } }))(); 4444 (async function() { return { get then() { notPure() } }; })(); 4445 `, 4446 }, 4447 entryPaths: []string{ 4448 "/remove-these.js", 4449 "/keep-these.js", 4450 }, 4451 options: config.Options{ 4452 AbsOutputDir: "/out", 4453 MinifySyntax: true, 4454 TreeShaking: true, 4455 }, 4456 }) 4457 } 4458 4459 func TestDCEOfDestructuring(t *testing.T) { 4460 dce_suite.expectBundled(t, bundled{ 4461 files: map[string]string{ 4462 "/entry.js": ` 4463 // Identifier bindings 4464 var remove1 4465 var remove2 = null 4466 var KEEP1 = x 4467 4468 // Array patterns 4469 var [remove3] = [] 4470 var [remove4, ...remove5] = [...[1, 2], 3] 4471 var [, , remove6] = [, , 3] 4472 var [KEEP2] = [x] 4473 var [KEEP3] = [...{}] 4474 4475 // Object patterns (not handled right now) 4476 var { KEEP4 } = {} 4477 `, 4478 }, 4479 entryPaths: []string{"/entry.js"}, 4480 options: config.Options{ 4481 Mode: config.ModeBundle, 4482 AbsOutputDir: "/out", 4483 }, 4484 }) 4485 } 4486 4487 func TestDCEOfDecorators(t *testing.T) { 4488 dce_suite.expectBundled(t, bundled{ 4489 files: map[string]string{ 4490 "/keep-these.js": ` 4491 import { fn } from './decorator' 4492 @fn class Class {} 4493 class Field { @fn field } 4494 class Method { @fn method() {} } 4495 class Accessor { @fn accessor accessor } 4496 class StaticField { @fn static field } 4497 class StaticMethod { @fn static method() {} } 4498 class StaticAccessor { @fn static accessor accessor } 4499 `, 4500 "/decorator.js": ` 4501 export const fn = () => { 4502 console.log('side effect') 4503 } 4504 `, 4505 }, 4506 entryPaths: []string{"/keep-these.js"}, 4507 options: config.Options{ 4508 Mode: config.ModeBundle, 4509 AbsOutputDir: "/out", 4510 }, 4511 }) 4512 } 4513 4514 func TestDCEOfExperimentalDecorators(t *testing.T) { 4515 dce_suite.expectBundled(t, bundled{ 4516 files: map[string]string{ 4517 "/keep-these.ts": ` 4518 import { fn } from './decorator' 4519 @fn class Class {} 4520 class Field { @fn field } 4521 class Method { @fn method() {} } 4522 class Accessor { @fn accessor accessor } 4523 class Parameter { foo(@fn bar) {} } 4524 class StaticField { @fn static field } 4525 class StaticMethod { @fn static method() {} } 4526 class StaticAccessor { @fn static accessor accessor } 4527 class StaticParameter { static foo(@fn bar) {} } 4528 `, 4529 "/decorator.ts": ` 4530 export const fn = () => { 4531 console.log('side effect') 4532 } 4533 `, 4534 "/tsconfig.json": `{ 4535 "compilerOptions": { 4536 "experimentalDecorators": true 4537 } 4538 }`, 4539 }, 4540 entryPaths: []string{"/keep-these.ts"}, 4541 options: config.Options{ 4542 Mode: config.ModeBundle, 4543 AbsOutputDir: "/out", 4544 }, 4545 }) 4546 } 4547 4548 func TestDCEOfUsingDeclarations(t *testing.T) { 4549 dce_suite.expectBundled(t, bundled{ 4550 files: map[string]string{ 4551 "/entry.js": ` 4552 // Note: Only remove "using" if it's null or undefined and not awaited 4553 4554 using null_remove = null 4555 using null_keep = null 4556 await using await_null_keep = null 4557 4558 // This has a side effect: throwing an error 4559 using throw_keep = {} 4560 4561 using dispose_keep = { [Symbol.dispose]() { console.log('side effect') } } 4562 await using await_asyncDispose_keep = { [Symbol.asyncDispose]() { console.log('side effect') } } 4563 4564 using undef_remove = undefined 4565 using undef_keep = undefined 4566 await using await_undef_keep = undefined 4567 4568 // Assume these have no side effects 4569 const Symbol_dispose_remove = Symbol.dispose 4570 const Symbol_asyncDispose_remove = Symbol.asyncDispose 4571 4572 console.log( 4573 null_keep, 4574 undef_keep, 4575 ) 4576 `, 4577 }, 4578 entryPaths: []string{"/entry.js"}, 4579 options: config.Options{ 4580 Mode: config.ModeBundle, 4581 TreeShaking: true, 4582 AbsOutputDir: "/out", 4583 }, 4584 }) 4585 } 4586 4587 func TestDCEOfExprAfterKeepNamesIssue3195(t *testing.T) { 4588 dce_suite.expectBundled(t, bundled{ 4589 files: map[string]string{ 4590 "/entry.js": ` 4591 (() => { 4592 function f() {} 4593 firstImportantSideEffect(f()); 4594 })(); 4595 (() => { 4596 function g() {} 4597 debugger; 4598 secondImportantSideEffect(g()); 4599 })(); 4600 `, 4601 }, 4602 entryPaths: []string{"/entry.js"}, 4603 options: config.Options{ 4604 MinifySyntax: true, 4605 KeepNames: true, 4606 AbsOutputFile: "/out.js", 4607 }, 4608 }) 4609 } 4610 4611 func TestDropLabels(t *testing.T) { 4612 dce_suite.expectBundled(t, bundled{ 4613 files: map[string]string{ 4614 "/entry.js": ` 4615 keep_1: require('foo1') 4616 DROP_1: require('bar1') 4617 exports.bar = function() { 4618 if (x) DROP_2: require('foo2') 4619 if (y) keep_2: require('bar2') 4620 } 4621 `, 4622 }, 4623 entryPaths: []string{"/entry.js"}, 4624 options: config.Options{ 4625 Mode: config.ModeBundle, 4626 DropLabels: []string{ 4627 "DROP_1", 4628 "DROP_2", 4629 }, 4630 ExternalSettings: config.ExternalSettings{ 4631 PreResolve: config.ExternalMatchers{ 4632 Exact: map[string]bool{ 4633 "foo1": true, 4634 "bar2": true, 4635 }, 4636 }, 4637 }, 4638 AbsOutputFile: "/out.js", 4639 OutputFormat: config.FormatCommonJS, 4640 }, 4641 }) 4642 } 4643 4644 func TestRemoveCodeAfterLabelWithReturn(t *testing.T) { 4645 dce_suite.expectBundled(t, bundled{ 4646 files: map[string]string{ 4647 "/entry.js": ` 4648 function earlyReturn() { 4649 // This comes up when doing conditional compilation with "DropLabels" 4650 keep: { 4651 onlyWithKeep() 4652 return 4653 } 4654 onlyWithoutKeep() 4655 } 4656 function loop() { 4657 if (foo()) { 4658 keep: { 4659 bar() 4660 return; 4661 } 4662 } 4663 } 4664 `, 4665 }, 4666 entryPaths: []string{"/entry.js"}, 4667 options: config.Options{ 4668 AbsOutputFile: "/out.js", 4669 MinifySyntax: true, 4670 }, 4671 }) 4672 } 4673 4674 func TestDropLabelTreeShakingBugIssue3311(t *testing.T) { 4675 dce_suite.expectBundled(t, bundled{ 4676 files: map[string]string{ 4677 "/entry.js": ` 4678 const myFunc = ()=> { 4679 DROP: {console.log("drop")} 4680 console.log("keep") 4681 } 4682 export default myFunc 4683 `, 4684 }, 4685 entryPaths: []string{"/entry.js"}, 4686 options: config.Options{ 4687 Mode: config.ModeBundle, 4688 AbsOutputFile: "/out.js", 4689 DropLabels: []string{"DROP"}, 4690 OutputFormat: config.FormatESModule, 4691 }, 4692 }) 4693 } 4694 4695 func TestDCEOfSymbolInstances(t *testing.T) { 4696 dce_suite.expectBundled(t, bundled{ 4697 files: map[string]string{ 4698 "/class.js": ` 4699 class Remove1 {} 4700 class Remove2 { *[Symbol.iterator]() {} } 4701 class Remove3 { *[Symbol['iterator']]() {} } 4702 4703 class Keep1 { *[Symbol.iterator]() {} [keep] } 4704 class Keep2 { [keep]; *[Symbol.iterator]() {} } 4705 class Keep3 { *[Symbol.wtf]() {} } 4706 `, 4707 "/object.js": ` 4708 let remove1 = {} 4709 let remove2 = { *[Symbol.iterator]() {} } 4710 let remove3 = { *[Symbol['iterator']]() {} } 4711 4712 let keep1 = { *[Symbol.iterator]() {}, [keep]: null } 4713 let keep2 = { [keep]: null, *[Symbol.iterator]() {} } 4714 let keep3 = { *[Symbol.wtf]() {} } 4715 `, 4716 }, 4717 entryPaths: []string{ 4718 "/class.js", 4719 "/object.js", 4720 }, 4721 options: config.Options{ 4722 Mode: config.ModeBundle, 4723 AbsOutputDir: "/out", 4724 }, 4725 }) 4726 }