github.com/evanw/esbuild@v0.21.4/internal/bundler_tests/bundler_loader_test.go (about)

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