@@ -1308,7 +1308,11 @@ changes:
13081308  Node.js default `  load`  hook after the last user-supplied ` `  hook
13091309  * `  url`  {string}
13101310  * `  context`  {Object|undefined} When omitted, defaults are provided. When provided, defaults are
1311-     merged in with preference to the provided properties. 
1311+     merged in with preference to the provided properties. In the default `  nextLoad` , if
1312+     the module pointed to by `  url`  does not have explicit module type information,
1313+     `  context .format `  is mandatory.
1314+     <!-- TODO(joyeecheung): make it at least optionally non-mandatory by allowing 
1315+          JS-style/TS-style module detection when the format is simply unknown --> 
13121316* Returns: {Object|Promise} The asynchronous version takes either an object containing the 
13131317  following properties, or a `  Promise `  that will resolve to such an object. The
13141318  synchronous version only accepts an object returned synchronously. 
@@ -1506,36 +1510,32 @@ transpiler hooks should only be used for development and testing purposes.
15061510` ` ` 
15071511//  coffeescript-hooks.mjs
15081512import  { readFile  } from  ' node:fs/promises' 
1509- import  { dirname , extname , resolve  as  resolvePath  } from  ' node:path' 
1510- import  { cwd  } from  ' node:process' 
1511- import  { fileURLToPath , pathToFileURL  } from  ' node:url' 
1513+ import  { findPackageJSON  } from  ' node:module' 
15121514import  coffeescript  from  ' coffeescript' 
15131515
15141516const  extensionsRegex  =  / \. (coffee| litcoffee| coffee\. md)$ /  ;
15151517
15161518export  async  function  load (url , context , nextLoad ) {
15171519  if  (extensionsRegex .test (url)) {
1518-     //  CoffeeScript files can be either CommonJS or ES modules, so we want any
1519-     //  CoffeeScript file to be treated by Node.js the same as a .js file at the
1520-     //  same location. To determine how Node.js would interpret an arbitrary .js
1521-     //  file, search up the file system for the nearest parent package.json file
1522-     //  and read its "type" field.
1523-     const  format  =  await  getPackageType (url);
1524- 
1525-     const  { source:  rawSource  } =  await  nextLoad (url, { ... context, format });
1520+     //  CoffeeScript files can be either CommonJS or ES modules. Use a custom format
1521+     //  to tell Node.js not to detect its module type.
1522+     const  { source:  rawSource  } =  await  nextLoad (url, { ... context, format:  ' coffee' 
15261523    //  This hook converts CoffeeScript source code into JavaScript source code
15271524    //  for all imported CoffeeScript files.
15281525    const  transformedSource  =  coffeescript .compile (rawSource .toString (), url);
15291526
1527+     //  To determine how Node.js would interpret the transpilation result,
1528+     //  search up the file system for the nearest parent package.json file
1529+     //  and read its "type" field.
15301530    return  {
1531-       format,
1531+       format:   await   getPackageType (url) ,
15321532      shortCircuit:  true ,
15331533      source:  transformedSource,
15341534    };
15351535  }
15361536
15371537  //  Let Node.js handle all other URLs.
1538-   return  nextLoad (url);
1538+   return  nextLoad (url, context );
15391539}
15401540
15411541async  function  getPackageType (url ) {
@@ -1546,72 +1546,51 @@ async function getPackageType(url) {
15461546  //  this simple truthy check for whether `url` contains a file extension will
15471547  //  work for most projects but does not cover some edge-cases (such as
15481548  //  extensionless files or a url ending in a trailing space)
1549-   const  isFilePath  =  !! extname (url);
1550-   //  If it is a file path, get the directory it's in
1551-   const  dir  =  isFilePath ? 
1552-     dirname (fileURLToPath (url)) : 
1553-     url;
1554-   //  Compose a file path to a package.json in the same directory,
1555-   //  which may or may not exist
1556-   const  packagePath  =  resolvePath (dir, ' package.json' 
1557-   //  Try to read the possibly nonexistent package.json
1558-   const  type  =  await  readFile (packagePath, { encoding:  ' utf8' 
1559-     .then ((filestring ) =>  JSON .parse (filestring).type )
1560-     .catch ((err ) =>  {
1561-       if  (err? .code  !==  ' ENOENT' console .error (err);
1562-     });
1563-   //  If package.json existed and contained a `type` field with a value, voilà
1564-   if  (type) return  type;
1565-   //  Otherwise, (if not at the root) continue checking the next directory up
1566-   //  If at the root, stop and return false
1567-   return  dir .length  >  1  &&  getPackageType (resolvePath (dir, ' ..' 
1549+   const  pJson  =  findPackageJSON (url);
1550+ 
1551+   return  readFile (pJson, ' utf8' 
1552+     .then (JSON .parse )
1553+     .then ((json ) =>  json? .type )
1554+     .catch (() =>  undefined );
15681555}
15691556` ` ` 
15701557
15711558##### Synchronous version 
15721559
15731560` ` ` 
15741561//  coffeescript-sync-hooks.mjs
1575- import  { readFileSync  } from  ' node:fs/promises' 
1576- import  { registerHooks  } from  ' node:module' 
1577- import  { dirname , extname , resolve  as  resolvePath  } from  ' node:path' 
1578- import  { cwd  } from  ' node:process' 
1579- import  { fileURLToPath , pathToFileURL  } from  ' node:url' 
1562+ import  { readFileSync  } from  ' node:fs' 
1563+ import  { registerHooks , findPackageJSON  } from  ' node:module' 
15801564import  coffeescript  from  ' coffeescript' 
15811565
15821566const  extensionsRegex  =  / \. (coffee| litcoffee| coffee\. md)$ /  ;
15831567
15841568function  load (url , context , nextLoad ) {
15851569  if  (extensionsRegex .test (url)) {
1586-     const  format  =  getPackageType (url);
1587- 
1588-     const  { source:  rawSource  } =  nextLoad (url, { ... context, format });
1570+     const  { source:  rawSource  } =  nextLoad (url, { ... context, format:  ' coffee' 
15891571    const  transformedSource  =  coffeescript .compile (rawSource .toString (), url);
15901572
15911573    return  {
1592-       format,
1574+       format:   getPackageType (url) ,
15931575      shortCircuit:  true ,
15941576      source:  transformedSource,
15951577    };
15961578  }
15971579
1598-   return  nextLoad (url);
1580+   return  nextLoad (url, context );
15991581}
16001582
16011583function  getPackageType (url ) {
1602-   const  isFilePath  =  !! extname (url);
1603-   const  dir  =  isFilePath ?  dirname (fileURLToPath (url)) :  url;
1604-   const  packagePath  =  resolvePath (dir, ' package.json' 
1605- 
1606-   let  type;
1584+   const  pJson  =  findPackageJSON (url);
1585+   if  (! pJson) {
1586+     return  undefined ;
1587+   }
16071588  try  {
1608-     const  filestring  =  readFileSync (packagePath, { encoding :   ' utf8 '  } );
1609-     type  =   JSON .parse (filestring) .type ;
1610-   } catch  (err)  {
1611-     if  (err ? . code   !==   ' ENOENT ' )  console . error (err) ;
1589+     const  file  =  readFileSync (pJson,  ' utf-8 ' 
1590+     return   JSON .parse (file) ? .type ;
1591+   } catch  {
1592+     return   undefined ;
16121593  }
1613-   if  (type) return  type;
1614-   return  dir .length  >  1  &&  getPackageType (resolvePath (dir, ' ..' 
16151594}
16161595
16171596registerHooks ({ load });
@@ -1633,6 +1612,21 @@ console.log "Brought to you by Node.js version #{version}"
16331612export  scream  =  (str ) - >  str .toUpperCase ()
16341613` ` ` 
16351614
1615+ For the sake of running the example, add a `  package .json `  file containing the
1616+ module type of the CoffeeScript files. 
1617+ 
1618+ ` ` ` 
1619+ {
1620+   " type" :  " module" 
1621+ }
1622+ ` ` ` 
1623+ 
1624+ This is only for running the example. In real world loaders, `  getPackageType ()`  must be
1625+ able to return an `  format`  known to Node.js even in the absence of an explicit type in a
1626+ ` package .json ` , or otherwise the ` `  call would throw ` ERR_UNKNOWN_FILE_EXTENSION ` 
1627+ (if undefined) or `  ERR_UNKNOWN_MODULE_FORMAT `  (if it's not a known format listed in
1628+ the [load hook][] documentation). 
1629+ 
16361630With the preceding hooks modules, running 
16371631` -- import  ' data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ` 
16381632or `  node --import ./coffeescript-sync-hooks.mjs ./main.coffee` 
0 commit comments