@@ -1308,7 +1308,11 @@ changes:
1308
1308
Node.js default ` load` hook after the last user-supplied ` load` hook
1309
1309
* ` url` {string}
1310
1310
* ` 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 -->
1312
1316
* Returns: {Object|Promise} The asynchronous version takes either an object containing the
1313
1317
following properties, or a ` Promise ` that will resolve to such an object. The
1314
1318
synchronous version only accepts an object returned synchronously.
@@ -1506,36 +1510,32 @@ transpiler hooks should only be used for development and testing purposes.
1506
1510
` ` ` mjs
1507
1511
// coffeescript-hooks.mjs
1508
1512
import { 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' ;
1512
1514
import coffeescript from ' coffeescript' ;
1513
1515
1514
1516
const extensionsRegex = / \. (coffee| litcoffee| coffee\. md)$ / ;
1515
1517
1516
1518
export async function load (url , context , nextLoad ) {
1517
1519
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' });
1526
1523
// This hook converts CoffeeScript source code into JavaScript source code
1527
1524
// for all imported CoffeeScript files.
1528
1525
const transformedSource = coffeescript .compile (rawSource .toString (), url);
1529
1526
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.
1530
1530
return {
1531
- format,
1531
+ format: await getPackageType (url) ,
1532
1532
shortCircuit: true ,
1533
1533
source: transformedSource,
1534
1534
};
1535
1535
}
1536
1536
1537
1537
// Let Node.js handle all other URLs.
1538
- return nextLoad (url);
1538
+ return nextLoad (url, context );
1539
1539
}
1540
1540
1541
1541
async function getPackageType (url ) {
@@ -1546,72 +1546,51 @@ async function getPackageType(url) {
1546
1546
// this simple truthy check for whether `url` contains a file extension will
1547
1547
// work for most projects but does not cover some edge-cases (such as
1548
1548
// 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 );
1568
1555
}
1569
1556
` ` `
1570
1557
1571
1558
##### Synchronous version
1572
1559
1573
1560
` ` ` mjs
1574
1561
// 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' ;
1580
1564
import coffeescript from ' coffeescript' ;
1581
1565
1582
1566
const extensionsRegex = / \. (coffee| litcoffee| coffee\. md)$ / ;
1583
1567
1584
1568
function load (url , context , nextLoad ) {
1585
1569
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' });
1589
1571
const transformedSource = coffeescript .compile (rawSource .toString (), url);
1590
1572
1591
1573
return {
1592
- format,
1574
+ format: getPackageType (url) ,
1593
1575
shortCircuit: true ,
1594
1576
source: transformedSource,
1595
1577
};
1596
1578
}
1597
1579
1598
- return nextLoad (url);
1580
+ return nextLoad (url, context );
1599
1581
}
1600
1582
1601
1583
function 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
+ }
1607
1588
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 ;
1612
1593
}
1613
- if (type) return type;
1614
- return dir .length > 1 && getPackageType (resolvePath (dir, ' ..' ));
1615
1594
}
1616
1595
1617
1596
registerHooks ({ load });
@@ -1633,6 +1612,21 @@ console.log "Brought to you by Node.js version #{version}"
1633
1612
export scream = (str ) - > str .toUpperCase ()
1634
1613
` ` `
1635
1614
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
+
1636
1630
With the preceding hooks modules, running
1637
1631
` node -- import ' data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ./main.coffee`
1638
1632
or ` node --import ./coffeescript-sync-hooks.mjs ./main.coffee`
0 commit comments