@@ -1164,7 +1164,11 @@ changes:
1164
1164
Node.js default ` load` hook after the last user-supplied ` load` hook
1165
1165
* ` url` {string}
1166
1166
* ` context` {Object|undefined} When omitted, defaults are provided. When provided, defaults are
1167
- merged in with preference to the provided properties.
1167
+ merged in with preference to the provided properties. In the default ` nextLoad` , if
1168
+ the module pointed to by ` url` does not have explicit module type information,
1169
+ ` context .format ` is mandatory.
1170
+ <!-- TODO(joyeecheung): make it at least optionally non-mandatory by allowing
1171
+ JS-style/TS-style module detection when the format is simply unknown -->
1168
1172
* Returns: {Object|Promise} The asynchronous version takes either an object containing the
1169
1173
following properties, or a ` Promise ` that will resolve to such an object. The
1170
1174
synchronous version only accepts an object returned synchronously.
@@ -1362,36 +1366,32 @@ transpiler hooks should only be used for development and testing purposes.
1362
1366
` ` ` mjs
1363
1367
// coffeescript-hooks.mjs
1364
1368
import { readFile } from ' node:fs/promises' ;
1365
- import { dirname , extname , resolve as resolvePath } from ' node:path' ;
1366
- import { cwd } from ' node:process' ;
1367
- import { fileURLToPath , pathToFileURL } from ' node:url' ;
1369
+ import { findPackageJSON } from ' node:module' ;
1368
1370
import coffeescript from ' coffeescript' ;
1369
1371
1370
1372
const extensionsRegex = / \. (coffee| litcoffee| coffee\. md)$ / ;
1371
1373
1372
1374
export async function load (url , context , nextLoad ) {
1373
1375
if (extensionsRegex .test (url)) {
1374
- // CoffeeScript files can be either CommonJS or ES modules, so we want any
1375
- // CoffeeScript file to be treated by Node.js the same as a .js file at the
1376
- // same location. To determine how Node.js would interpret an arbitrary .js
1377
- // file, search up the file system for the nearest parent package.json file
1378
- // and read its "type" field.
1379
- const format = await getPackageType (url);
1380
-
1381
- const { source: rawSource } = await nextLoad (url, { ... context, format });
1376
+ // CoffeeScript files can be either CommonJS or ES modules. Use a custom format
1377
+ // to tell Node.js not to detect its module type.
1378
+ const { source: rawSource } = await nextLoad (url, { ... context, format: ' coffee' });
1382
1379
// This hook converts CoffeeScript source code into JavaScript source code
1383
1380
// for all imported CoffeeScript files.
1384
1381
const transformedSource = coffeescript .compile (rawSource .toString (), url);
1385
1382
1383
+ // To determine how Node.js would interpret the transpilation result,
1384
+ // search up the file system for the nearest parent package.json file
1385
+ // and read its "type" field.
1386
1386
return {
1387
- format,
1387
+ format: await getPackageType (url) ,
1388
1388
shortCircuit: true ,
1389
1389
source: transformedSource,
1390
1390
};
1391
1391
}
1392
1392
1393
1393
// Let Node.js handle all other URLs.
1394
- return nextLoad (url);
1394
+ return nextLoad (url, context );
1395
1395
}
1396
1396
1397
1397
async function getPackageType (url ) {
@@ -1402,72 +1402,51 @@ async function getPackageType(url) {
1402
1402
// this simple truthy check for whether `url` contains a file extension will
1403
1403
// work for most projects but does not cover some edge-cases (such as
1404
1404
// extensionless files or a url ending in a trailing space)
1405
- const isFilePath = !! extname (url);
1406
- // If it is a file path, get the directory it's in
1407
- const dir = isFilePath ?
1408
- dirname (fileURLToPath (url)) :
1409
- url;
1410
- // Compose a file path to a package.json in the same directory,
1411
- // which may or may not exist
1412
- const packagePath = resolvePath (dir, ' package.json' );
1413
- // Try to read the possibly nonexistent package.json
1414
- const type = await readFile (packagePath, { encoding: ' utf8' })
1415
- .then ((filestring ) => JSON .parse (filestring).type )
1416
- .catch ((err ) => {
1417
- if (err? .code !== ' ENOENT' ) console .error (err);
1418
- });
1419
- // If package.json existed and contained a `type` field with a value, voilà
1420
- if (type) return type;
1421
- // Otherwise, (if not at the root) continue checking the next directory up
1422
- // If at the root, stop and return false
1423
- return dir .length > 1 && getPackageType (resolvePath (dir, ' ..' ));
1405
+ const pJson = findPackageJSON (url);
1406
+
1407
+ return readFile (pJson, ' utf8' )
1408
+ .then (JSON .parse )
1409
+ .then ((json ) => json? .type )
1410
+ .catch (() => undefined );
1424
1411
}
1425
1412
` ` `
1426
1413
1427
1414
##### Synchronous version
1428
1415
1429
1416
` ` ` mjs
1430
1417
// coffeescript-sync-hooks.mjs
1431
- import { readFileSync } from ' node:fs/promises' ;
1432
- import { registerHooks } from ' node:module' ;
1433
- import { dirname , extname , resolve as resolvePath } from ' node:path' ;
1434
- import { cwd } from ' node:process' ;
1435
- import { fileURLToPath , pathToFileURL } from ' node:url' ;
1418
+ import { readFileSync } from ' node:fs' ;
1419
+ import { registerHooks , findPackageJSON } from ' node:module' ;
1436
1420
import coffeescript from ' coffeescript' ;
1437
1421
1438
1422
const extensionsRegex = / \. (coffee| litcoffee| coffee\. md)$ / ;
1439
1423
1440
1424
function load (url , context , nextLoad ) {
1441
1425
if (extensionsRegex .test (url)) {
1442
- const format = getPackageType (url);
1443
-
1444
- const { source: rawSource } = nextLoad (url, { ... context, format });
1426
+ const { source: rawSource } = nextLoad (url, { ... context, format: ' coffee' });
1445
1427
const transformedSource = coffeescript .compile (rawSource .toString (), url);
1446
1428
1447
1429
return {
1448
- format,
1430
+ format: getPackageType (url) ,
1449
1431
shortCircuit: true ,
1450
1432
source: transformedSource,
1451
1433
};
1452
1434
}
1453
1435
1454
- return nextLoad (url);
1436
+ return nextLoad (url, context );
1455
1437
}
1456
1438
1457
1439
function getPackageType (url ) {
1458
- const isFilePath = !! extname (url);
1459
- const dir = isFilePath ? dirname (fileURLToPath (url)) : url;
1460
- const packagePath = resolvePath (dir, ' package.json' );
1461
-
1462
- let type;
1440
+ const pJson = findPackageJSON (url);
1441
+ if (! pJson) {
1442
+ return undefined ;
1443
+ }
1463
1444
try {
1464
- const filestring = readFileSync (packagePath, { encoding : ' utf8 ' } );
1465
- type = JSON .parse (filestring) .type ;
1466
- } catch (err) {
1467
- if (err ? . code !== ' ENOENT ' ) console . error (err) ;
1445
+ const file = readFileSync (pJson, ' utf-8 ' );
1446
+ return JSON .parse (file) ? .type ;
1447
+ } catch {
1448
+ return undefined ;
1468
1449
}
1469
- if (type) return type;
1470
- return dir .length > 1 && getPackageType (resolvePath (dir, ' ..' ));
1471
1450
}
1472
1451
1473
1452
registerHooks ({ load });
@@ -1489,6 +1468,21 @@ console.log "Brought to you by Node.js version #{version}"
1489
1468
export scream = (str ) - > str .toUpperCase ()
1490
1469
` ` `
1491
1470
1471
+ For the sake of running the example, add a ` package .json ` file containing the
1472
+ module type of the CoffeeScript files.
1473
+
1474
+ ` ` ` json
1475
+ {
1476
+ " type" : " module"
1477
+ }
1478
+ ` ` `
1479
+
1480
+ This is only for running the example. In real world loaders, ` getPackageType ()` must be
1481
+ able to return an ` format` known to Node.js even in the absence of an explicit type in a
1482
+ ` package .json ` , or otherwise the ` nextLoad` call would throw ` ERR_UNKNOWN_FILE_EXTENSION `
1483
+ (if undefined) or ` ERR_UNKNOWN_MODULE_FORMAT ` (if it's not a known format listed in
1484
+ the [load hook][] documentation).
1485
+
1492
1486
With the preceding hooks modules, running
1493
1487
` node -- import ' data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ./main.coffee`
1494
1488
or ` node --import ./coffeescript-sync-hooks.mjs ./main.coffee`
0 commit comments