@@ -578,13 +578,22 @@ function tryExtensions(basePath, exts, isMain) {
578578/**
579579 * Find the longest (possibly multi-dot) extension registered in `Module._extensions`.
580580 * @param {string } filename The filename to find the longest registered extension for.
581+ * @param {boolean } isMain Whether the module is the main module.
581582 * @returns {string }
582583 */
583- function findLongestRegisteredExtension ( filename ) {
584+ function findLongestRegisteredExtension ( filename , isMain = false ) {
584585 const name = path . basename ( filename ) ;
585586 let currentExtension ;
586587 let index ;
587588 let startIndex = 0 ;
589+ // We need to check that --experimental-ext is an entry point
590+ // And doesn't already have a loader (example: .json or .node)
591+ // If unknown let the .js loader handle it
592+ const experimentalExtension = getOptionValue ( '--experimental-ext' ) ;
593+ if ( experimentalExtension && isMain && ! Module . _extensions [ `.${ experimentalExtension } ` ] ) {
594+ return '.js' ;
595+ }
596+
588597 while ( ( index = StringPrototypeIndexOf ( name , '.' , startIndex ) ) !== - 1 ) {
589598 startIndex = index + 1 ;
590599 if ( index === 0 ) { continue ; } // Skip dotfiles like .gitignore
@@ -1474,7 +1483,7 @@ Module.prototype.load = function(filename) {
14741483 this . filename ??= filename ;
14751484 this . paths ??= Module . _nodeModulePaths ( path . dirname ( filename ) ) ;
14761485
1477- const extension = findLongestRegisteredExtension ( filename ) ;
1486+ const extension = findLongestRegisteredExtension ( filename , this [ kIsMainSymbol ] ) ;
14781487
14791488 Module . _extensions [ extension ] ( this , filename ) ;
14801489 this . loaded = true ;
@@ -1849,39 +1858,71 @@ function getRequireESMError(mod, pkg, content, filename) {
18491858 return err ;
18501859}
18511860
1861+ /**
1862+ * Get the module format for a given file extension.
1863+ * @param {string } filename The name of the file.
1864+ * @param {boolean } isMain Whether the module is the main module.
1865+ * @returns {object } An object containing the format and package information.
1866+ */
1867+ function getModuleFormatForExtension ( filename , isMain = false ) {
1868+ let pkg ;
1869+ const extensionMap = {
1870+ '.cjs' : ( ) => 'commonjs' ,
1871+ '.mjs' : ( ) => 'module' ,
1872+ '.js' : ( filename ) => {
1873+ pkg = packageJsonReader . getNearestParentPackageJSON ( filename ) ;
1874+ const typeFromPjson = pkg ?. data . type ;
1875+ if ( typeFromPjson === 'module' || typeFromPjson === 'commonjs' || ! typeFromPjson ) {
1876+ return typeFromPjson ;
1877+ }
1878+ } ,
1879+ '.mts' : ( ) => ( getOptionValue ( '--experimental-strip-types' ) ? 'module-typescript' : undefined ) ,
1880+ '.cts' : ( ) => ( getOptionValue ( '--experimental-strip-types' ) ? 'commonjs-typescript' : undefined ) ,
1881+ '.ts' : ( filename ) => {
1882+ if ( ! getOptionValue ( '--experimental-strip-types' ) ) { return ; }
1883+ pkg = packageJsonReader . getNearestParentPackageJSON ( filename ) ;
1884+ switch ( pkg ?. data . type ) {
1885+ case 'module' :
1886+ return 'module-typescript' ;
1887+ case 'commonjs' :
1888+ return 'commonjs-typescript' ;
1889+ default :
1890+ return 'typescript' ;
1891+ }
1892+ } ,
1893+ } ;
1894+
1895+ // Allow experimental extension via --experimental-ext
1896+ const experimentalExtension = getOptionValue ( '--experimental-ext' ) ;
1897+ if ( experimentalExtension ) {
1898+ emitExperimentalWarning ( '--experimental-ext' ) ;
1899+ if ( isMain ) {
1900+ const format = extensionMap [ `.${ experimentalExtension } ` ] ( filename ) ;
1901+ return { format, pkg } ;
1902+ }
1903+ }
1904+
1905+ const keys = ObjectKeys ( extensionMap ) ;
1906+ for ( let i = 0 ; i < keys . length ; i ++ ) {
1907+ const ext = keys [ i ] ;
1908+ if ( StringPrototypeEndsWith ( filename , ext ) ) {
1909+ const format = extensionMap [ ext ] ( filename ) ;
1910+ return { format, pkg } ;
1911+ }
1912+ }
1913+ // Unknown extension, return undefined to let loadSource handle it.
1914+ return { pkg, format : undefined } ;
1915+ }
1916+
18521917/**
18531918 * Built-in handler for `.js` files.
18541919 * @param {Module } module The module to compile
18551920 * @param {string } filename The file path of the module
18561921 */
18571922Module . _extensions [ '.js' ] = function ( module , filename ) {
1858- let format , pkg ;
1859- const tsEnabled = getOptionValue ( '--experimental-strip-types' ) ;
1860- if ( StringPrototypeEndsWith ( filename , '.cjs' ) ) {
1861- format = 'commonjs' ;
1862- } else if ( StringPrototypeEndsWith ( filename , '.mjs' ) ) {
1863- format = 'module' ;
1864- } else if ( StringPrototypeEndsWith ( filename , '.js' ) ) {
1865- pkg = packageJsonReader . getNearestParentPackageJSON ( filename ) ;
1866- const typeFromPjson = pkg ?. data . type ;
1867- if ( typeFromPjson === 'module' || typeFromPjson === 'commonjs' || ! typeFromPjson ) {
1868- format = typeFromPjson ;
1869- }
1870- } else if ( StringPrototypeEndsWith ( filename , '.mts' ) && tsEnabled ) {
1871- format = 'module-typescript' ;
1872- } else if ( StringPrototypeEndsWith ( filename , '.cts' ) && tsEnabled ) {
1873- format = 'commonjs-typescript' ;
1874- } else if ( StringPrototypeEndsWith ( filename , '.ts' ) && tsEnabled ) {
1875- pkg = packageJsonReader . getNearestParentPackageJSON ( filename ) ;
1876- const typeFromPjson = pkg ?. data . type ;
1877- if ( typeFromPjson === 'module' ) {
1878- format = 'module-typescript' ;
1879- } else if ( typeFromPjson === 'commonjs' ) {
1880- format = 'commonjs-typescript' ;
1881- } else {
1882- format = 'typescript' ;
1883- }
1884- }
1923+ // If the file is extensionless, or the extension is unknown
1924+ // it will fall into this handler.
1925+ const { format, pkg } = getModuleFormatForExtension ( filename , module [ kIsMainSymbol ] ) ;
18851926 const { source, format : loadedFormat } = loadSource ( module , filename , format ) ;
18861927 // Function require shouldn't be used in ES modules when require(esm) is disabled.
18871928 if ( ( loadedFormat === 'module' || loadedFormat === 'module-typescript' ) &&
0 commit comments