UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

284 lines (260 loc) 9.48 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/modules/esm/get_format.js import { getOptionValue } from "nstdlib/lib/internal/options"; import { extensionFormatMap, getFormatOfExtensionlessFile, mimeToFormat, } from "nstdlib/lib/internal/modules/esm/formats"; import { containsModuleSyntax } from "nstdlib/stub/binding/contextify"; import { getPackageScopeConfig, getPackageType, } from "nstdlib/lib/internal/modules/package_json_reader"; import { fileURLToPath } from "nstdlib/lib/internal/url"; import { codes as __codes__ } from "nstdlib/lib/internal/errors"; import * as __hoisted_internal_modules_helpers__ from "nstdlib/lib/internal/modules/helpers"; const detectModule = getOptionValue("--experimental-detect-module"); const { ERR_UNKNOWN_FILE_EXTENSION } = __codes__; const protocolHandlers = { __proto__: null, "data:": getDataProtocolModuleFormat, "file:": getFileProtocolModuleFormat, "node:"() { return "builtin"; }, }; /** * Determine whether the given ambiguous source contains CommonJS or ES module syntax. * @param {string | Buffer | undefined} source * @param {URL} url */ function detectModuleFormat(source, url) { if (!source) { return detectModule ? null : "commonjs"; } if (!detectModule) { return "commonjs"; } return containsModuleSyntax(`${source}`, fileURLToPath(url), url) ? "module" : "commonjs"; } /** * @param {URL} parsed * @returns {string | null} */ function getDataProtocolModuleFormat(parsed) { const { 1: mime } = RegExp.prototype.exec.call( /^([^/]+\/[^;,]+)(?:[^,]*?)(;base64)?,/, parsed.pathname, ) || [null, null, null]; return mimeToFormat(mime); } const DOT_CODE = 46; const SLASH_CODE = 47; /** * Returns the file extension from a URL. Should give similar result to * `require('node:path').extname(require('node:url').fileURLToPath(url))` * when used with a `file:` URL. * @param {URL} url * @returns {string} */ function extname(url) { const { pathname } = url; for (let i = pathname.length - 1; i > 0; i--) { switch (String.prototype.charCodeAt.call(pathname, i)) { case SLASH_CODE: return ""; case DOT_CODE: return String.prototype.charCodeAt.call(pathname, i - 1) === SLASH_CODE ? "" : String.prototype.slice.call(pathname, i); } } return ""; } /** * Determine whether the given file URL is under a `node_modules` folder. * This function assumes that the input has already been verified to be a `file:` URL, * and is a file rather than a folder. * @param {URL} url */ function underNodeModules(url) { if (url.protocol !== "file:") { return false; } // We determine module types for other protocols based on MIME header return String.prototype.includes.call(url.pathname, "/node_modules/"); } let typelessPackageJsonFilesWarnedAbout; function warnTypelessPackageJsonFile(pjsonPath, url) { typelessPackageJsonFilesWarnedAbout ??= new Set(); if (!typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) { const warning = `${url} parsed as an ES module because module syntax was detected;` + ` to avoid the performance penalty of syntax detection, add "type": "module" to ${pjsonPath}`; process.emitWarning(warning, { code: "MODULE_TYPELESS_PACKAGE_JSON", }); typelessPackageJsonFilesWarnedAbout.add(pjsonPath); } } /** * @param {URL} url * @param {{parentURL: string; source?: Buffer}} context * @param {boolean} ignoreErrors * @returns {string} */ function getFileProtocolModuleFormat( url, context = { __proto__: null }, ignoreErrors, ) { const { source } = context; const ext = extname(url); if (ext === ".js") { const { type: packageType, pjsonPath } = getPackageScopeConfig(url); if (packageType !== "none") { return packageType; } // The controlling `package.json` file has no `type` field. switch (getOptionValue("--experimental-default-type")) { case "module": { // The user explicitly passed `--experimental-default-type=module`. // An exception to the type flag making ESM the default everywhere is that package scopes under `node_modules` // should retain the assumption that a lack of a `type` field means CommonJS. return underNodeModules(url) ? "commonjs" : "module"; } case "commonjs": { // The user explicitly passed `--experimental-default-type=commonjs`. return "commonjs"; } default: { // The user did not pass `--experimental-default-type`. // `source` is undefined when this is called from `defaultResolve`; // but this gets called again from `defaultLoad`/`defaultLoadSync`. // For ambiguous files (no type field, .js extension) we return // undefined from `resolve` and re-run the check in `load`. const format = detectModuleFormat(source, url); if (format === "module") { // This module has a .js extension, a package.json with no `type` field, and ESM syntax. // Warn about the missing `type` field so that the user can avoid the performance penalty of detection. warnTypelessPackageJsonFile(pjsonPath, url); } return format; } } } if (ext === ".ts" && getOptionValue("--experimental-strip-types")) { const { type: packageType, pjsonPath } = getPackageScopeConfig(url); if (packageType !== "none") { return `${packageType}-typescript`; } // The controlling `package.json` file has no `type` field. switch (getOptionValue("--experimental-default-type")) { case "module": { // The user explicitly passed `--experimental-default-type=module`. // An exception to the type flag making ESM the default everywhere is that package scopes under `node_modules` // should retain the assumption that a lack of a `type` field means CommonJS. return underNodeModules(url) ? "commonjs-typescript" : "module-typescript"; } case "commonjs": { // The user explicitly passed `--experimental-default-type=commonjs`. return "commonjs-typescript"; } default: { // The user did not pass `--experimental-default-type`. // `source` is undefined when this is called from `defaultResolve`; // but this gets called again from `defaultLoad`/`defaultLoadSync`. let parsedSource; if (source) { const { stripTypeScriptTypes } = __hoisted_internal_modules_helpers__; parsedSource = stripTypeScriptTypes(source, url); } const detectedFormat = detectModuleFormat(parsedSource, url); // When source is undefined, default to module-typescript. const format = detectedFormat ? `${detectedFormat}-typescript` : "module-typescript"; if (format === "module-typescript") { // This module has a .js extension, a package.json with no `type` field, and ESM syntax. // Warn about the missing `type` field so that the user can avoid the performance penalty of detection. warnTypelessPackageJsonFile(pjsonPath, url); } return format; } } } if (ext === "") { const packageType = getPackageType(url); if (packageType === "module") { return getFormatOfExtensionlessFile(url); } if (packageType !== "none") { return packageType; // 'commonjs' or future package types } // The controlling `package.json` file has no `type` field. switch (getOptionValue("--experimental-default-type")) { case "module": { // The user explicitly passed `--experimental-default-type=module`. return underNodeModules(url) ? "commonjs" : getFormatOfExtensionlessFile(url); } case "commonjs": { // The user explicitly passed `--experimental-default-type=commonjs`. return "commonjs"; } default: { // The user did not pass `--experimental-default-type`. if (!source) { return null; } const format = getFormatOfExtensionlessFile(url); if (format === "wasm") { return format; } return detectModuleFormat(source, url); } } } const format = extensionFormatMap[ext]; if (format) { return format; } // Explicit undefined return indicates load hook should rerun format check if (ignoreErrors) { return undefined; } const filepath = fileURLToPath(url); throw new ERR_UNKNOWN_FILE_EXTENSION(ext, filepath); } /** * @param {URL} url * @param {{parentURL: string}} context * @returns {Promise<string> | string | undefined} only works when enabled */ function defaultGetFormatWithoutErrors(url, context) { const protocol = url.protocol; if (!Object.prototype.hasOwnProperty.call(protocolHandlers, protocol)) { return null; } return protocolHandlers[protocol](url, context, true); } /** * @param {URL} url * @param {{parentURL: string}} context * @returns {Promise<string> | string | undefined} only works when enabled */ function defaultGetFormat(url, context) { const protocol = url.protocol; if (!Object.prototype.hasOwnProperty.call(protocolHandlers, protocol)) { return null; } return protocolHandlers[protocol](url, context, false); } export { defaultGetFormat }; export { defaultGetFormatWithoutErrors }; export { extensionFormatMap }; export { extname };