| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 6acbb23 commit c5af5a4
8 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -139,8 +139,9 @@ There are three types of specifiers: | |||
| 139 | 139 | * _Absolute specifiers_ like `'file:///opt/nodejs/config.js'`. They refer | |
| 140 | 140 | directly and explicitly to a full path. | |
| 141 | 141 | ||
| 142 | - Bare specifier resolutions are handled by the [Node.js module resolution | ||
| 143 | - algorithm][]. All other specifier resolutions are always only resolved with | ||
| 142 | + Bare specifier resolutions are handled by the [Node.js module | ||
| 143 | + resolution and loading algorithm][]. | ||
| 144 | + All other specifier resolutions are always only resolved with | ||
| 144 | 145 | the standard relative [URL][] resolution semantics. | |
| 145 | 146 | ||
| 146 | 147 | Like in CommonJS, module files within packages can be accessed by appending a | |
@@ -1007,28 +1008,6 @@ and there is no security. | |||
| 1007 | 1008 | // https-loader.mjs | |
| 1008 | 1009 | import { get } from 'node:https'; | |
| 1009 | 1010 | ||
| 1010 | - export function resolve(specifier, context, nextResolve) { | ||
| 1011 | - const { parentURL = null } = context; | ||
| 1012 | - | ||
| 1013 | - // Normally Node.js would error on specifiers starting with 'https://', so | ||
| 1014 | - // this hook intercepts them and converts them into absolute URLs to be | ||
| 1015 | - // passed along to the later hooks below. | ||
| 1016 | - if (specifier.startsWith('https://')) { | ||
| 1017 | - return { | ||
| 1018 | - shortCircuit: true, | ||
| 1019 | - url: specifier, | ||
| 1020 | - }; | ||
| 1021 | - } else if (parentURL && parentURL.startsWith('https://')) { | ||
| 1022 | - return { | ||
| 1023 | - shortCircuit: true, | ||
| 1024 | - url: new URL(specifier, parentURL).href, | ||
| 1025 | - }; | ||
| 1026 | - } | ||
| 1027 | - | ||
| 1028 | - // Let Node.js handle all other specifiers. | ||
| 1029 | - return nextResolve(specifier); | ||
| 1030 | - } | ||
| 1031 | - | ||
| 1032 | 1011 | export function load(url, context, nextLoad) { | |
| 1033 | 1012 | // For JavaScript to be loaded over the network, we need to fetch and | |
| 1034 | 1013 | // return it. | |
@@ -1069,9 +1048,7 @@ prints the current version of CoffeeScript per the module at the URL in | |||
| 1069 | 1048 | #### Transpiler loader | |
| 1070 | 1049 | ||
| 1071 | 1050 | Sources that are in formats Node.js doesn't understand can be converted into | |
| 1072 | - JavaScript using the [`load` hook][load hook]. Before that hook gets called, | ||
| 1073 | - however, a [`resolve` hook][resolve hook] needs to tell Node.js not to | ||
| 1074 | - throw an error on unknown file types. | ||
| 1051 | + JavaScript using the [`load` hook][load hook]. | ||
| 1075 | 1052 | ||
| 1076 | 1053 | This is less performant than transpiling source files before running | |
| 1077 | 1054 | Node.js; a transpiler loader should only be used for development and testing | |
@@ -1087,25 +1064,6 @@ import CoffeeScript from 'coffeescript'; | |||
| 1087 | 1064 | ||
| 1088 | 1065 | const baseURL = pathToFileURL(`${cwd()}/`).href; | |
| 1089 | 1066 | ||
| 1090 | - // CoffeeScript files end in .coffee, .litcoffee, or .coffee.md. | ||
| 1091 | - const extensionsRegex = /\.coffee$|\.litcoffee$|\.coffee\.md$/; | ||
| 1092 | - | ||
| 1093 | - export async function resolve(specifier, context, nextResolve) { | ||
| 1094 | - if (extensionsRegex.test(specifier)) { | ||
| 1095 | - const { parentURL = baseURL } = context; | ||
| 1096 | - | ||
| 1097 | - // Node.js normally errors on unknown file extensions, so return a URL for | ||
| 1098 | - // specifiers ending in the CoffeeScript file extensions. | ||
| 1099 | - return { | ||
| 1100 | - shortCircuit: true, | ||
| 1101 | - url: new URL(specifier, parentURL).href, | ||
| 1102 | - }; | ||
| 1103 | - } | ||
| 1104 | - | ||
| 1105 | - // Let Node.js handle all other specifiers. | ||
| 1106 | - return nextResolve(specifier); | ||
| 1107 | - } | ||
| 1108 | - | ||
| 1109 | 1067 | export async function load(url, context, nextLoad) { | |
| 1110 | 1068 | if (extensionsRegex.test(url)) { | |
| 1111 | 1069 | // Now that we patched resolve to let CoffeeScript URLs through, we need to | |
@@ -1198,27 +1156,99 @@ loaded from disk but before Node.js executes it; and so on for any `.coffee`, | |||
| 1198 | 1156 | `.litcoffee` or `.coffee.md` files referenced via `import` statements of any | |
| 1199 | 1157 | loaded file. | |
| 1200 | 1158 | ||
| 1201 | - ## Resolution algorithm | ||
| 1159 | + #### "import map" loader | ||
| 1160 | + | ||
| 1161 | + The previous two loaders defined `load` hooks. This is an example of a loader | ||
| 1162 | + that does its work via the `resolve` hook. This loader reads an | ||
| 1163 | + `import-map.json` file that specifies which specifiers to override to another | ||
| 1164 | + URL (this is a very simplistic implemenation of a small subset of the | ||
| 1165 | + "import maps" specification). | ||
| 1166 | + | ||
| 1167 | + ```js | ||
| 1168 | + // import-map-loader.js | ||
| 1169 | + import fs from 'node:fs/promises'; | ||
| 1170 | + | ||
| 1171 | + const { imports } = JSON.parse(await fs.readFile('import-map.json')); | ||
| 1172 | + | ||
| 1173 | + export async function resolve(specifier, context, nextResolve) { | ||
| 1174 | + if (Object.hasOwn(imports, specifier)) { | ||
| 1175 | + return nextResolve(imports[specifier], context); | ||
| 1176 | + } | ||
| 1177 | + | ||
| 1178 | + return nextResolve(specifier, context); | ||
| 1179 | + } | ||
| 1180 | + ``` | ||
| 1181 | + | ||
| 1182 | + Let's assume we have these files: | ||
| 1183 | + | ||
| 1184 | + ```js | ||
| 1185 | + // main.js | ||
| 1186 | + import 'a-module'; | ||
| 1187 | + ``` | ||
| 1188 | + | ||
| 1189 | + ```json | ||
| 1190 | + // import-map.json | ||
| 1191 | + { | ||
| 1192 | + "imports": { | ||
| 1193 | + "a-module": "./some-module.js" | ||
| 1194 | + } | ||
| 1195 | + } | ||
| 1196 | + ``` | ||
| 1197 | + | ||
| 1198 | + ```js | ||
| 1199 | + // some-module.js | ||
| 1200 | + console.log('some module!'); | ||
| 1201 | + ``` | ||
| 1202 | + | ||
| 1203 | + If you run `node --experimental-loader ./import-map-loader.js main.js` | ||
| 1204 | + the output will be `some module!`. | ||
| 1205 | + | ||
| 1206 | + ## Resolution and loading algorithm | ||
| 1202 | 1207 | ||
| 1203 | 1208 | ### Features | |
| 1204 | 1209 | ||
| 1205 | - The resolver has the following properties: | ||
| 1210 | + The default resolver has the following properties: | ||
| 1206 | 1211 | ||
| 1207 | 1212 | * FileURL-based resolution as is used by ES modules | |
| 1208 | - * Support for builtin module loading | ||
| 1209 | 1213 | * Relative and absolute URL resolution | |
| 1210 | 1214 | * No default extensions | |
| 1211 | 1215 | * No folder mains | |
| 1212 | 1216 | * Bare specifier package resolution lookup through node\_modules | |
| 1217 | + * Does not fail on unknown extensions or protocols | ||
| 1218 | + * Can optionally provide a hint of the format to the loading phase | ||
| 1219 | + | ||
| 1220 | + The default loader has the following properties | ||
| 1213 | 1221 | ||
| 1214 | - ### Resolver algorithm | ||
| 1222 | + * Support for builtin module loading via `node:` URLs | ||
| 1223 | + * Support for "inline" module loading via `data:` URLs | ||
| 1224 | + * Support for `file:` module loading | ||
| 1225 | + * Fails on any other URL protocol | ||
| 1226 | + * Fails on unknown extensions for `file:` loading | ||
| 1227 | + (supports only `.cjs`, `.js`, and `.mjs`) | ||
| 1228 | + | ||
| 1229 | + ### Resolution algorithm | ||
| 1215 | 1230 | ||
| 1216 | 1231 | The algorithm to load an ES module specifier is given through the | |
| 1217 | 1232 | **ESM\_RESOLVE** method below. It returns the resolved URL for a | |
| 1218 | 1233 | module specifier relative to a parentURL. | |
| 1219 | 1234 | ||
| 1235 | + The resolution algorithm determines the full resolved URL for a module | ||
| 1236 | + load, along with its suggested module format. The resolution algorithm | ||
| 1237 | + does not determine whether the resolved URL protocol can be loaded, | ||
| 1238 | + or whether the file extensions are permitted, instead these validations | ||
| 1239 | + are applied by Node.js during the load phase | ||
| 1240 | + (for example, if it was asked to load a URL that has a protocol that is | ||
| 1241 | + not `file:`, `data:`, `node:`, or if `--experimental-network-imports` | ||
| 1242 | + is enabled, `https:`). | ||
| 1243 | + | ||
| 1244 | + The algorithm also tries to determine the format of the file based | ||
| 1245 | + on the extension (see `ESM_FILE_FORMAT` algorithm below). If it does | ||
| 1246 | + not recognize the file extension (eg if it is not `.mjs`, `.cjs`, or | ||
| 1247 | + `.json`), then a format of `undefined` is returned, | ||
| 1248 | + which will throw during the load phase. | ||
| 1249 | + | ||
| 1220 | 1250 | The algorithm to determine the module format of a resolved URL is | |
| 1221 | - provided by **ESM\_FORMAT**, which returns the unique module | ||
| 1251 | + provided by **ESM\_FILE\_FORMAT**, which returns the unique module | ||
| 1222 | 1252 | format for any file. The _"module"_ format is returned for an ECMAScript | |
| 1223 | 1253 | Module, while the _"commonjs"_ format is used to indicate loading through the | |
| 1224 | 1254 | legacy CommonJS loader. Additional formats such as _"addon"_ can be extended in | |
@@ -1245,7 +1275,7 @@ The resolver can throw the following errors: | |||
| 1245 | 1275 | * _Unsupported Directory Import_: The resolved path corresponds to a directory, | |
| 1246 | 1276 | which is not a supported target for module imports. | |
| 1247 | 1277 | ||
| 1248 | - ### Resolver Algorithm Specification | ||
| 1278 | + ### Resolution Algorithm Specification | ||
| 1249 | 1279 | ||
| 1250 | 1280 | **ESM\_RESOLVE**(_specifier_, _parentURL_) | |
| 1251 | 1281 | ||
@@ -1279,7 +1309,7 @@ The resolver can throw the following errors: | |||
| 1279 | 1309 | > 8. Otherwise, | |
| 1280 | 1310 | > 1. Set _format_ the module format of the content type associated with the | |
| 1281 | 1311 | > URL _resolved_. | |
| 1282 | - > 9. Load _resolved_ as module format, _format_. | ||
| 1312 | + > 9. Return _format_ and _resolved_ to the loading phase | ||
| 1283 | 1313 | ||
| 1284 | 1314 | **PACKAGE\_RESOLVE**(_packageSpecifier_, _parentURL_) | |
| 1285 | 1315 | ||
@@ -1484,9 +1514,9 @@ _isImports_, _conditions_) | |||
| 1484 | 1514 | > 7. If _pjson?.type_ exists and is _"module"_, then | |
| 1485 | 1515 | > 1. If _url_ ends in _".js"_, then | |
| 1486 | 1516 | > 1. Return _"module"_. | |
| 1487 | - > 2. Throw an _Unsupported File Extension_ error. | ||
| 1517 | + > 2. Return **undefined**. | ||
| 1488 | 1518 | > 8. Otherwise, | |
| 1489 | - > 1. Throw an _Unsupported File Extension_ error. | ||
| 1519 | + > 1. Return **undefined**. | ||
| 1490 | 1520 | ||
| 1491 | 1521 | **LOOKUP\_PACKAGE\_SCOPE**(_url_) | |
| 1492 | 1522 | ||
@@ -1552,7 +1582,7 @@ success! | |||
| 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 | |
@@ -1580,7 +1610,6 @@ success! | |||
| 1580 | 1610 | [custom https loader]: #https-loader | |
| 1581 | 1611 | [load hook]: #loadurl-context-nextload | |
| 1582 | 1612 | [percent-encoded]: url.md#percent-encoding-in-urls | |
| 1583 | - [resolve hook]: #resolvespecifier-context-nextresolve | ||
| 1584 | 1613 | [special scheme]: https://url.spec.whatwg.org/#special-scheme | |
| 1585 | 1614 | [status code]: process.md#exit-codes | |
| 1586 | 1615 | [the official standard format]: https://tc39.github.io/ecma262/#sec-modules | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -77,6 +77,8 @@ async function defaultLoad(url, context) { | |||
| 77 | 77 | source, | |
| 78 | 78 | } = context; | |
| 79 | 79 | ||
| 80 | + throwIfUnsupportedURLScheme(new URL(url), experimentalNetworkImports); | ||
| 81 | + | ||
| 80 | 82 | if (format == null) { | |
| 81 | 83 | format = await defaultGetFormat(url, context); | |
| 82 | 84 | } | |
@@ -100,6 +102,36 @@ async function defaultLoad(url, context) { | |||
| 100 | 102 | }; | |
| 101 | 103 | } | |
| 102 | 104 | ||
| 105 | + /** | ||
| 106 | + * throws an error if the protocol is not one of the protocols | ||
| 107 | + * that can be loaded in the default loader | ||
| 108 | + * @param {URL} parsed | ||
| 109 | + * @param {boolean} experimentalNetworkImports | ||
| 110 | + */ | ||
| 111 | + function throwIfUnsupportedURLScheme(parsed, experimentalNetworkImports) { | ||
| 112 | + // Avoid accessing the `protocol` property due to the lazy getters. | ||
| 113 | + const protocol = parsed?.protocol; | ||
| 114 | + if ( | ||
| 115 | + protocol && | ||
| 116 | + protocol !== 'file:' && | ||
| 117 | + protocol !== 'data:' && | ||
| 118 | + protocol !== 'node:' && | ||
| 119 | + ( | ||
| 120 | + !experimentalNetworkImports || | ||
| 121 | + ( | ||
| 122 | + protocol !== 'https:' && | ||
| 123 | + protocol !== 'http:' | ||
| 124 | + ) | ||
| 125 | + ) | ||
| 126 | + ) { | ||
| 127 | + const schemes = ['file', 'data', 'node']; | ||
| 128 | + if (experimentalNetworkImports) { | ||
| 129 | + ArrayPrototypePush(schemes, 'https', 'http'); | ||
| 130 | + } | ||
| 131 | + throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(parsed, schemes); | ||
| 132 | + } | ||
| 133 | + } | ||
| 134 | + | ||
| 103 | 135 | module.exports = { | |
| 104 | 136 | defaultLoad, | |
| 105 | 137 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,7 +3,6 @@ | |||
| 3 | 3 | const { | |
| 4 | 4 | ArrayIsArray, | |
| 5 | 5 | ArrayPrototypeJoin, | |
| 6 | - ArrayPrototypePush, | ||
| 7 | 6 | ArrayPrototypeShift, | |
| 8 | 7 | JSONParse, | |
| 9 | 8 | JSONStringify, | |
@@ -57,7 +56,6 @@ const { | |||
| 57 | 56 | ERR_PACKAGE_PATH_NOT_EXPORTED, | |
| 58 | 57 | ERR_UNSUPPORTED_DIR_IMPORT, | |
| 59 | 58 | ERR_NETWORK_IMPORT_DISALLOWED, | |
| 60 | - ERR_UNSUPPORTED_ESM_URL_SCHEME, | ||
| 61 | 59 | } = require('internal/errors').codes; | |
| 62 | 60 | ||
| 63 | 61 | const { Module: CJSModule } = require('internal/modules/cjs/loader'); | |
@@ -1036,37 +1034,6 @@ function checkIfDisallowedImport(specifier, parsed, parsedParentURL) { | |||
| 1036 | 1034 | } | |
| 1037 | 1035 | } | |
| 1038 | 1036 | ||
| 1039 | - function throwIfUnsupportedURLProtocol(url) { | ||
| 1040 | - // Avoid accessing the `protocol` property due to the lazy getters. | ||
| 1041 | - const protocol = url.protocol; | ||
| 1042 | - if (protocol !== 'file:' && protocol !== 'data:' && | ||
| 1043 | - protocol !== 'node:') { | ||
| 1044 | - throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url); | ||
| 1045 | - } | ||
| 1046 | - } | ||
| 1047 | - | ||
| 1048 | - function throwIfUnsupportedURLScheme(parsed, experimentalNetworkImports) { | ||
| 1049 | - // Avoid accessing the `protocol` property due to the lazy getters. | ||
| 1050 | - const protocol = parsed?.protocol; | ||
| 1051 | - if ( | ||
| 1052 | - protocol && | ||
| 1053 | - protocol !== 'file:' && | ||
| 1054 | - protocol !== 'data:' && | ||
| 1055 | - ( | ||
| 1056 | - !experimentalNetworkImports || | ||
| 1057 | - ( | ||
| 1058 | - protocol !== 'https:' && | ||
| 1059 | - protocol !== 'http:' | ||
| 1060 | - ) | ||
| 1061 | - ) | ||
| 1062 | - ) { | ||
| 1063 | - const schemes = ['file', 'data']; | ||
| 1064 | - if (experimentalNetworkImports) { | ||
| 1065 | - ArrayPrototypePush(schemes, 'https', 'http'); | ||
| 1066 | - } | ||
| 1067 | - throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(parsed, schemes); | ||
| 1068 | - } | ||
| 1069 | - } | ||
| 1070 | 1037 | ||
| 1071 | 1038 | async function defaultResolve(specifier, context = {}) { | |
| 1072 | 1039 | let { parentURL, conditions } = context; | |
@@ -1142,7 +1109,6 @@ async function defaultResolve(specifier, context = {}) { | |||
| 1142 | 1109 | // This must come after checkIfDisallowedImport | |
| 1143 | 1110 | if (parsed && parsed.protocol === 'node:') return { url: specifier }; | |
| 1144 | 1111 | ||
| 1145 | - throwIfUnsupportedURLScheme(parsed, experimentalNetworkImports); | ||
| 1146 | 1112 | ||
| 1147 | 1113 | const isMain = parentURL === undefined; | |
| 1148 | 1114 | if (isMain) { | |
@@ -1189,8 +1155,6 @@ async function defaultResolve(specifier, context = {}) { | |||
| 1189 | 1155 | throw error; | |
| 1190 | 1156 | } | |
| 1191 | 1157 | ||
| 1192 | - throwIfUnsupportedURLProtocol(url); | ||
| 1193 | - | ||
| 1194 | 1158 | return { | |
| 1195 | 1159 | // Do NOT cast `url` to a string: that will work even when there are real | |
| 1196 | 1160 | // problems, silencing them | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -59,7 +59,7 @@ function expectFsNamespace(result) { | |||
| 59 | 59 | 'ERR_UNSUPPORTED_ESM_URL_SCHEME'); | |
| 60 | 60 | if (common.isWindows) { | |
| 61 | 61 | const msg = | |
| 62 | - 'Only URLs with a scheme in: file and data are supported by the default ' + | ||
| 62 | + 'Only URLs with a scheme in: file, data, and node are supported by the default ' + | ||
| 63 | 63 | 'ESM loader. On Windows, absolute paths must be valid file:// URLs. ' + | |
| 64 | 64 | "Received protocol 'c:'"; | |
| 65 | 65 | expectModuleError(import('C:\\example\\foo.mjs'), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -31,6 +31,8 @@ const fixtures = dirname.slice(0, dirname.lastIndexOf('/', dirname.length - 2) + | |||
| 31 | 31 | }) | |
| 32 | 32 | ) | |
| 33 | 33 | ); | |
| 34 | + assert.strictEqual(await import.meta.resolve('http://some-absolute/url'), 'http://some-absolute/url'); | ||
| 35 | + assert.strictEqual(await import.meta.resolve('some://weird/protocol'), 'some://weird/protocol'); | ||
| 34 | 36 | assert.strictEqual(await import.meta.resolve('baz/', fixtures), | |
| 35 | 37 | fixtures + 'node_modules/baz/'); | |
| 36 | 38 | })().then(mustCall()); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments