@@ -1308,7 +1308,11 @@ changes:
13081308 Node.js default ` load` hook after the last user-supplied ` load` 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` ` ` mjs
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` ` ` mjs
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+ ` ` ` json
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 ` nextLoad` 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` node -- import ' data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ./main.coffee`
16381632or ` node --import ./coffeescript-sync-hooks.mjs ./main.coffee`
0 commit comments