@@ -143,8 +143,9 @@ There are three types of specifiers: |
143 | 143 | * _Absolute specifiers_ like `'file:///opt/nodejs/config.js'`. They refer |
144 | 144 | directly and explicitly to a full path. |
145 | 145 |
|
146 | | -Bare specifier resolutions are handled by the [Node.js module resolution |
147 | | -algorithm][]. All other specifier resolutions are always only resolved with |
| 146 | +Bare specifier resolutions are handled by the [Node.js module |
| 147 | +resolution and loading algorithm][]. |
| 148 | +All other specifier resolutions are always only resolved with |
148 | 149 | the standard relative [URL][] resolution semantics. |
149 | 150 |
|
150 | 151 | Like in CommonJS, module files within packages can be accessed by appending a |
@@ -1029,28 +1030,6 @@ and there is no security. |
1029 | 1030 | // https-loader.mjs |
1030 | 1031 | import { get } from 'node:https'; |
1031 | 1032 |
|
1032 | | -export function resolve(specifier, context, nextResolve) { |
1033 | | - const { parentURL = null } = context; |
1034 | | -
|
1035 | | - // Normally Node.js would error on specifiers starting with 'https://', so |
1036 | | - // this hook intercepts them and converts them into absolute URLs to be |
1037 | | - // passed along to the later hooks below. |
1038 | | - if (specifier.startsWith('https://')) { |
1039 | | - return { |
1040 | | - shortCircuit: true, |
1041 | | - url: specifier, |
1042 | | - }; |
1043 | | - } else if (parentURL && parentURL.startsWith('https://')) { |
1044 | | - return { |
1045 | | - shortCircuit: true, |
1046 | | - url: new URL(specifier, parentURL).href, |
1047 | | - }; |
1048 | | - } |
1049 | | -
|
1050 | | - // Let Node.js handle all other specifiers. |
1051 | | - return nextResolve(specifier); |
1052 | | -} |
1053 | | -
|
1054 | 1033 | export function load(url, context, nextLoad) { |
1055 | 1034 | // For JavaScript to be loaded over the network, we need to fetch and |
1056 | 1035 | // return it. |
@@ -1091,9 +1070,7 @@ prints the current version of CoffeeScript per the module at the URL in |
1091 | 1070 | #### Transpiler loader |
1092 | 1071 |
|
1093 | 1072 | Sources that are in formats Node.js doesn't understand can be converted into |
1094 | | -JavaScript using the [`load` hook][load hook]. Before that hook gets called, |
1095 | | -however, a [`resolve` hook][resolve hook] needs to tell Node.js not to |
1096 | | -throw an error on unknown file types. |
| 1073 | +JavaScript using the [`load` hook][load hook]. |
1097 | 1074 |
|
1098 | 1075 | This is less performant than transpiling source files before running |
1099 | 1076 | Node.js; a transpiler loader should only be used for development and testing |
@@ -1109,25 +1086,6 @@ import CoffeeScript from 'coffeescript'; |
1109 | 1086 |
|
1110 | 1087 | const baseURL = pathToFileURL(`${cwd()}/`).href; |
1111 | 1088 |
|
1112 | | -// CoffeeScript files end in .coffee, .litcoffee, or .coffee.md. |
1113 | | -const extensionsRegex = /\.coffee$|\.litcoffee$|\.coffee\.md$/; |
1114 | | -
|
1115 | | -export function resolve(specifier, context, nextResolve) { |
1116 | | - if (extensionsRegex.test(specifier)) { |
1117 | | - const { parentURL = baseURL } = context; |
1118 | | -
|
1119 | | - // Node.js normally errors on unknown file extensions, so return a URL for |
1120 | | - // specifiers ending in the CoffeeScript file extensions. |
1121 | | - return { |
1122 | | - shortCircuit: true, |
1123 | | - url: new URL(specifier, parentURL).href, |
1124 | | - }; |
1125 | | - } |
1126 | | -
|
1127 | | - // Let Node.js handle all other specifiers. |
1128 | | - return nextResolve(specifier); |
1129 | | -} |
1130 | | -
|
1131 | 1089 | export async function load(url, context, nextLoad) { |
1132 | 1090 | if (extensionsRegex.test(url)) { |
1133 | 1091 | // Now that we patched resolve to let CoffeeScript URLs through, we need to |
@@ -1220,27 +1178,99 @@ loaded from disk but before Node.js executes it; and so on for any `.coffee`, |
1220 | 1178 | `.litcoffee` or `.coffee.md` files referenced via `import` statements of any |
1221 | 1179 | loaded file. |
1222 | 1180 |
|
1223 | | -## Resolution algorithm |
| 1181 | +#### "import map" loader |
| 1182 | +
|
| 1183 | +The previous two loaders defined `load` hooks. This is an example of a loader |
| 1184 | +that does its work via the `resolve` hook. This loader reads an |
| 1185 | +`import-map.json` file that specifies which specifiers to override to another |
| 1186 | +URL (this is a very simplistic implemenation of a small subset of the |
| 1187 | +"import maps" specification). |
| 1188 | +
|
| 1189 | +```js |
| 1190 | +// import-map-loader.js |
| 1191 | +import fs from 'node:fs/promises'; |
| 1192 | + |
| 1193 | +const { imports } = JSON.parse(await fs.readFile('import-map.json')); |
| 1194 | + |
| 1195 | +export async function resolve(specifier, context, nextResolve) { |
| 1196 | + if (Object.hasOwn(imports, specifier)) { |
| 1197 | + return nextResolve(imports[specifier], context); |
| 1198 | + } |
| 1199 | + |
| 1200 | + return nextResolve(specifier, context); |
| 1201 | +} |
| 1202 | +``` |
| 1203 | +
|
| 1204 | +Let's assume we have these files: |
| 1205 | +
|
| 1206 | +```js |
| 1207 | +// main.js |
| 1208 | +import 'a-module'; |
| 1209 | +``` |
| 1210 | +
|
| 1211 | +```json |
| 1212 | +// import-map.json |
| 1213 | +{ |
| 1214 | + "imports": { |
| 1215 | + "a-module": "./some-module.js" |
| 1216 | + } |
| 1217 | +} |
| 1218 | +``` |
| 1219 | +
|
| 1220 | +```js |
| 1221 | +// some-module.js |
| 1222 | +console.log('some module!'); |
| 1223 | +``` |
| 1224 | +
|
| 1225 | +If you run `node --experimental-loader ./import-map-loader.js main.js` |
| 1226 | +the output will be `some module!`. |
| 1227 | +
|
| 1228 | +## Resolution and loading algorithm |
1224 | 1229 |
|
1225 | 1230 | ### Features |
1226 | 1231 |
|
1227 | | -The resolver has the following properties: |
| 1232 | +The default resolver has the following properties: |
1228 | 1233 |
|
1229 | 1234 | * FileURL-based resolution as is used by ES modules |
1230 | | -* Support for builtin module loading |
1231 | 1235 | * Relative and absolute URL resolution |
1232 | 1236 | * No default extensions |
1233 | 1237 | * No folder mains |
1234 | 1238 | * Bare specifier package resolution lookup through node\_modules |
| 1239 | +* Does not fail on unknown extensions or protocols |
| 1240 | +* Can optionally provide a hint of the format to the loading phase |
| 1241 | +
|
| 1242 | +The default loader has the following properties |
1235 | 1243 |
|
1236 | | -### Resolver algorithm |
| 1244 | +* Support for builtin module loading via `node:` URLs |
| 1245 | +* Support for "inline" module loading via `data:` URLs |
| 1246 | +* Support for `file:` module loading |
| 1247 | +* Fails on any other URL protocol |
| 1248 | +* Fails on unknown extensions for `file:` loading |
| 1249 | + (supports only `.cjs`, `.js`, and `.mjs`) |
| 1250 | +
|
| 1251 | +### Resolution algorithm |
1237 | 1252 |
|
1238 | 1253 | The algorithm to load an ES module specifier is given through the |
1239 | 1254 | **ESM\_RESOLVE** method below. It returns the resolved URL for a |
1240 | 1255 | module specifier relative to a parentURL. |
1241 | 1256 |
|
| 1257 | +The resolution algorithm determines the full resolved URL for a module |
| 1258 | +load, along with its suggested module format. The resolution algorithm |
| 1259 | +does not determine whether the resolved URL protocol can be loaded, |
| 1260 | +or whether the file extensions are permitted, instead these validations |
| 1261 | +are applied by Node.js during the load phase |
| 1262 | +(for example, if it was asked to load a URL that has a protocol that is |
| 1263 | +not `file:`, `data:`, `node:`, or if `--experimental-network-imports` |
| 1264 | +is enabled, `https:`). |
| 1265 | +
|
| 1266 | +The algorithm also tries to determine the format of the file based |
| 1267 | +on the extension (see `ESM_FILE_FORMAT` algorithm below). If it does |
| 1268 | +not recognize the file extension (eg if it is not `.mjs`, `.cjs`, or |
| 1269 | +`.json`), then a format of `undefined` is returned, |
| 1270 | +which will throw during the load phase. |
| 1271 | +
|
1242 | 1272 | The algorithm to determine the module format of a resolved URL is |
1243 | | -provided by **ESM\_FORMAT**, which returns the unique module |
| 1273 | +provided by **ESM\_FILE\_FORMAT**, which returns the unique module |
1244 | 1274 | format for any file. The _"module"_ format is returned for an ECMAScript |
1245 | 1275 | Module, while the _"commonjs"_ format is used to indicate loading through the |
1246 | 1276 | legacy CommonJS loader. Additional formats such as _"addon"_ can be extended in |
@@ -1267,7 +1297,7 @@ The resolver can throw the following errors: |
1267 | 1297 | * _Unsupported Directory Import_: The resolved path corresponds to a directory, |
1268 | 1298 | which is not a supported target for module imports. |
1269 | 1299 |
|
1270 | | -### Resolver Algorithm Specification |
| 1300 | +### Resolution Algorithm Specification |
1271 | 1301 |
|
1272 | 1302 | **ESM\_RESOLVE**(_specifier_, _parentURL_) |
1273 | 1303 |
|
@@ -1301,7 +1331,7 @@ The resolver can throw the following errors: |
1301 | 1331 | > 8. Otherwise, |
1302 | 1332 | > 1. Set _format_ the module format of the content type associated with the |
1303 | 1333 | > URL _resolved_. |
1304 | | -> 9. Load _resolved_ as module format, _format_. |
| 1334 | +> 9. Return _format_ and _resolved_ to the loading phase |
1305 | 1335 |
|
1306 | 1336 | **PACKAGE\_RESOLVE**(_packageSpecifier_, _parentURL_) |
1307 | 1337 |
|
@@ -1506,9 +1536,9 @@ _isImports_, _conditions_) |
1506 | 1536 | > 7. If _pjson?.type_ exists and is _"module"_, then |
1507 | 1537 | > 1. If _url_ ends in _".js"_, then |
1508 | 1538 | > 1. Return _"module"_. |
1509 | | -> 2. Throw an _Unsupported File Extension_ error. |
| 1539 | +> 2. Return **undefined**. |
1510 | 1540 | > 8. Otherwise, |
1511 | | -> 1. Throw an _Unsupported File Extension_ error. |
| 1541 | +> 1. Return **undefined**. |
1512 | 1542 |
|
1513 | 1543 | **LOOKUP\_PACKAGE\_SCOPE**(_url_) |
1514 | 1544 |
|
@@ -1552,7 +1582,7 @@ for ESM specifiers is [commonjs-extension-resolution-loader][]. |
1552 | 1582 | [Import Assertions proposal]: https://github.com/tc39/proposal-import-assertions |
1553 | 1583 | [JSON modules]: #json-modules |
1554 | 1584 | [Loaders API]: #loaders |
1555 | | -[Node.js Module Resolution Algorithm]: #resolver-algorithm-specification |
| 1585 | +[Node.js Module Resolution And Loading Algorithm]: #resolution-algorithm-specification |
1556 | 1586 | [Terminology]: #terminology |
1557 | 1587 | [URL]: https://url.spec.whatwg.org/ |
1558 | 1588 | [`"exports"`]: packages.md#exports |
@@ -1581,7 +1611,6 @@ for ESM specifiers is [commonjs-extension-resolution-loader][]. |
1581 | 1611 | [custom https loader]: #https-loader |
1582 | 1612 | [load hook]: #loadurl-context-nextload |
1583 | 1613 | [percent-encoded]: url.md#percent-encoding-in-urls |
1584 | | -[resolve hook]: #resolvespecifier-context-nextresolve |
1585 | 1614 | [special scheme]: https://url.spec.whatwg.org/#special-scheme |
1586 | 1615 | [status code]: process.md#exit-codes |
1587 | 1616 | [the official standard format]: https://tc39.github.io/ecma262/#sec-modules |
|