@@ -1156,7 +1156,11 @@ changes:
1156
1156
Node.js default ` load` hook after the last user-supplied ` load` hook
1157
1157
* ` url` {string}
1158
1158
* ` 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 -->
1160
1164
* Returns: {Object|Promise} The asynchronous version takes either an object containing the
1161
1165
following properties, or a ` Promise ` that will resolve to such an object. The
1162
1166
synchronous version only accepts an object returned synchronously.
@@ -1354,36 +1358,32 @@ transpiler hooks should only be used for development and testing purposes.
1354
1358
` ` ` mjs
1355
1359
// coffeescript-hooks.mjs
1356
1360
import { 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' ;
1360
1362
import coffeescript from ' coffeescript' ;
1361
1363
1362
1364
const extensionsRegex = / \. (coffee| litcoffee| coffee\. md)$ / ;
1363
1365
1364
1366
export async function load (url , context , nextLoad ) {
1365
1367
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' });
1374
1371
// This hook converts CoffeeScript source code into JavaScript source code
1375
1372
// for all imported CoffeeScript files.
1376
1373
const transformedSource = coffeescript .compile (rawSource .toString (), url);
1377
1374
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.
1378
1378
return {
1379
- format,
1379
+ format: await getPackageType (url) ,
1380
1380
shortCircuit: true ,
1381
1381
source: transformedSource,
1382
1382
};
1383
1383
}
1384
1384
1385
1385
// Let Node.js handle all other URLs.
1386
- return nextLoad (url);
1386
+ return nextLoad (url, context );
1387
1387
}
1388
1388
1389
1389
async function getPackageType (url ) {
@@ -1394,72 +1394,51 @@ async function getPackageType(url) {
1394
1394
// this simple truthy check for whether `url` contains a file extension will
1395
1395
// work for most projects but does not cover some edge-cases (such as
1396
1396
// 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 );
1416
1403
}
1417
1404
` ` `
1418
1405
1419
1406
##### Synchronous version
1420
1407
1421
1408
` ` ` mjs
1422
1409
// 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' ;
1428
1412
import coffeescript from ' coffeescript' ;
1429
1413
1430
1414
const extensionsRegex = / \. (coffee| litcoffee| coffee\. md)$ / ;
1431
1415
1432
1416
function load (url , context , nextLoad ) {
1433
1417
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' });
1437
1419
const transformedSource = coffeescript .compile (rawSource .toString (), url);
1438
1420
1439
1421
return {
1440
- format,
1422
+ format: getPackageType (url) ,
1441
1423
shortCircuit: true ,
1442
1424
source: transformedSource,
1443
1425
};
1444
1426
}
1445
1427
1446
- return nextLoad (url);
1428
+ return nextLoad (url, context );
1447
1429
}
1448
1430
1449
1431
function 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
+ }
1455
1436
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 ;
1460
1441
}
1461
- if (type) return type;
1462
- return dir .length > 1 && getPackageType (resolvePath (dir, ' ..' ));
1463
1442
}
1464
1443
1465
1444
registerHooks ({ load });
@@ -1481,6 +1460,21 @@ console.log "Brought to you by Node.js version #{version}"
1481
1460
export scream = (str ) - > str .toUpperCase ()
1482
1461
` ` `
1483
1462
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
+
1484
1478
With the preceding hooks modules, running
1485
1479
` node -- import ' data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ./main.coffee`
1486
1480
or ` node --import ./coffeescript-sync-hooks.mjs ./main.coffee`
0 commit comments