@@ -1156,7 +1156,11 @@ changes:
11561156 Node.js default ` load` hook after the last user-supplied ` load` hook
11571157 * ` url` {string}
11581158 * ` context` {Object|undefined} When omitted, defaults are provided. When provided, defaults are
1159- merged in with preference to the provided properties.
1159+ merged in with preference to the provided properties. In the default ` nextLoad` , if
1160+ the module pointed to by ` url` does not have explicit module type information,
1161+ ` context .format ` is mandatory.
1162+ <!-- TODO(joyeecheung): make it at least optionally non-mandatory by allowing
1163+ JS-style/TS-style module detection when the format is simply unknown -->
11601164* Returns: {Object|Promise} The asynchronous version takes either an object containing the
11611165 following properties, or a ` Promise ` that will resolve to such an object. The
11621166 synchronous version only accepts an object returned synchronously.
@@ -1354,36 +1358,32 @@ transpiler hooks should only be used for development and testing purposes.
13541358` ` ` mjs
13551359// coffeescript-hooks.mjs
13561360import { readFile } from ' node:fs/promises' ;
1357- import { dirname , extname , resolve as resolvePath } from ' node:path' ;
1358- import { cwd } from ' node:process' ;
1359- import { fileURLToPath , pathToFileURL } from ' node:url' ;
1361+ import { findPackageJSON } from ' node:module' ;
13601362import coffeescript from ' coffeescript' ;
13611363
13621364const extensionsRegex = / \. (coffee| litcoffee| coffee\. md)$ / ;
13631365
13641366export async function load (url , context , nextLoad ) {
13651367 if (extensionsRegex .test (url)) {
1366- // CoffeeScript files can be either CommonJS or ES modules, so we want any
1367- // CoffeeScript file to be treated by Node.js the same as a .js file at the
1368- // same location. To determine how Node.js would interpret an arbitrary .js
1369- // file, search up the file system for the nearest parent package.json file
1370- // and read its "type" field.
1371- const format = await getPackageType (url);
1372-
1373- const { source: rawSource } = await nextLoad (url, { ... context, format });
1368+ // CoffeeScript files can be either CommonJS or ES modules. Use a custom format
1369+ // to tell Node.js not to detect its module type.
1370+ const { source: rawSource } = await nextLoad (url, { ... context, format: ' coffee' });
13741371 // This hook converts CoffeeScript source code into JavaScript source code
13751372 // for all imported CoffeeScript files.
13761373 const transformedSource = coffeescript .compile (rawSource .toString (), url);
13771374
1375+ // To determine how Node.js would interpret the transpilation result,
1376+ // search up the file system for the nearest parent package.json file
1377+ // and read its "type" field.
13781378 return {
1379- format,
1379+ format: await getPackageType (url) ,
13801380 shortCircuit: true ,
13811381 source: transformedSource,
13821382 };
13831383 }
13841384
13851385 // Let Node.js handle all other URLs.
1386- return nextLoad (url);
1386+ return nextLoad (url, context );
13871387}
13881388
13891389async function getPackageType (url ) {
@@ -1394,72 +1394,51 @@ async function getPackageType(url) {
13941394 // this simple truthy check for whether `url` contains a file extension will
13951395 // work for most projects but does not cover some edge-cases (such as
13961396 // extensionless files or a url ending in a trailing space)
1397- const isFilePath = !! extname (url);
1398- // If it is a file path, get the directory it's in
1399- const dir = isFilePath ?
1400- dirname (fileURLToPath (url)) :
1401- url;
1402- // Compose a file path to a package.json in the same directory,
1403- // which may or may not exist
1404- const packagePath = resolvePath (dir, ' package.json' );
1405- // Try to read the possibly nonexistent package.json
1406- const type = await readFile (packagePath, { encoding: ' utf8' })
1407- .then ((filestring ) => JSON .parse (filestring).type )
1408- .catch ((err ) => {
1409- if (err? .code !== ' ENOENT' ) console .error (err);
1410- });
1411- // If package.json existed and contained a `type` field with a value, voilà
1412- if (type) return type;
1413- // Otherwise, (if not at the root) continue checking the next directory up
1414- // If at the root, stop and return false
1415- return dir .length > 1 && getPackageType (resolvePath (dir, ' ..' ));
1397+ const pJson = findPackageJSON (url);
1398+
1399+ return readFile (pJson, ' utf8' )
1400+ .then (JSON .parse )
1401+ .then ((json ) => json? .type )
1402+ .catch (() => undefined );
14161403}
14171404` ` `
14181405
14191406##### Synchronous version
14201407
14211408` ` ` mjs
14221409// coffeescript-sync-hooks.mjs
1423- import { readFileSync } from ' node:fs/promises' ;
1424- import { registerHooks } from ' node:module' ;
1425- import { dirname , extname , resolve as resolvePath } from ' node:path' ;
1426- import { cwd } from ' node:process' ;
1427- import { fileURLToPath , pathToFileURL } from ' node:url' ;
1410+ import { readFileSync } from ' node:fs' ;
1411+ import { registerHooks , findPackageJSON } from ' node:module' ;
14281412import coffeescript from ' coffeescript' ;
14291413
14301414const extensionsRegex = / \. (coffee| litcoffee| coffee\. md)$ / ;
14311415
14321416function load (url , context , nextLoad ) {
14331417 if (extensionsRegex .test (url)) {
1434- const format = getPackageType (url);
1435-
1436- const { source: rawSource } = nextLoad (url, { ... context, format });
1418+ const { source: rawSource } = nextLoad (url, { ... context, format: ' coffee' });
14371419 const transformedSource = coffeescript .compile (rawSource .toString (), url);
14381420
14391421 return {
1440- format,
1422+ format: getPackageType (url) ,
14411423 shortCircuit: true ,
14421424 source: transformedSource,
14431425 };
14441426 }
14451427
1446- return nextLoad (url);
1428+ return nextLoad (url, context );
14471429}
14481430
14491431function getPackageType (url ) {
1450- const isFilePath = !! extname (url);
1451- const dir = isFilePath ? dirname (fileURLToPath (url)) : url;
1452- const packagePath = resolvePath (dir, ' package.json' );
1453-
1454- let type;
1432+ const pJson = findPackageJSON (url);
1433+ if (! pJson) {
1434+ return undefined ;
1435+ }
14551436 try {
1456- const filestring = readFileSync (packagePath, { encoding : ' utf8 ' } );
1457- type = JSON .parse (filestring) .type ;
1458- } catch (err) {
1459- if (err ? . code !== ' ENOENT ' ) console . error (err) ;
1437+ const file = readFileSync (pJson, ' utf-8 ' );
1438+ return JSON .parse (file) ? .type ;
1439+ } catch {
1440+ return undefined ;
14601441 }
1461- if (type) return type;
1462- return dir .length > 1 && getPackageType (resolvePath (dir, ' ..' ));
14631442}
14641443
14651444registerHooks ({ load });
@@ -1481,6 +1460,21 @@ console.log "Brought to you by Node.js version #{version}"
14811460export scream = (str ) - > str .toUpperCase ()
14821461` ` `
14831462
1463+ For the sake of running the example, add a ` package .json ` file containing the
1464+ module type of the CoffeeScript files.
1465+
1466+ ` ` ` json
1467+ {
1468+ " type" : " module"
1469+ }
1470+ ` ` `
1471+
1472+ This is only for running the example. In real world loaders, ` getPackageType ()` must be
1473+ able to return an ` format` known to Node.js even in the absence of an explicit type in a
1474+ ` package .json ` , or otherwise the ` nextLoad` call would throw ` ERR_UNKNOWN_FILE_EXTENSION `
1475+ (if undefined) or ` ERR_UNKNOWN_MODULE_FORMAT ` (if it's not a known format listed in
1476+ the [load hook][] documentation).
1477+
14841478With the preceding hooks modules, running
14851479` node -- import ' data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ./main.coffee`
14861480or ` node --import ./coffeescript-sync-hooks.mjs ./main.coffee`
0 commit comments