1'use strict'; 2 3const { 4 ArrayIsArray, 5 SafeSet, 6 SafeWeakMap, 7 ObjectFreeze, 8} = primordials; 9 10const { 11 privateSymbols: { 12 host_defined_option_symbol, 13 }, 14} = internalBinding('util'); 15const { 16 default_host_defined_options, 17 vm_dynamic_import_missing_flag, 18} = internalBinding('symbols'); 19 20const { 21 ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG, 22 ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING, 23 ERR_INVALID_ARG_VALUE, 24} = require('internal/errors').codes; 25const { getOptionValue } = require('internal/options'); 26const { 27 loadPreloadModules, 28 initializeFrozenIntrinsics, 29} = require('internal/process/pre_execution'); 30const { getCWDURL } = require('internal/util'); 31const { 32 setImportModuleDynamicallyCallback, 33 setInitializeImportMetaObjectCallback, 34} = internalBinding('module_wrap'); 35const assert = require('internal/assert'); 36 37let defaultConditions; 38/** 39 * Returns the default conditions for ES module loading. 40 */ 41function getDefaultConditions() { 42 assert(defaultConditions !== undefined); 43 return defaultConditions; 44} 45 46/** @type {Set<string>} */ 47let defaultConditionsSet; 48/** 49 * Returns the default conditions for ES module loading, as a Set. 50 */ 51function getDefaultConditionsSet() { 52 assert(defaultConditionsSet !== undefined); 53 return defaultConditionsSet; 54} 55 56/** 57 * Initializes the default conditions for ESM module loading. 58 * This function is called during pre-execution, before any user code is run. 59 */ 60function initializeDefaultConditions() { 61 const userConditions = getOptionValue('--conditions'); 62 const noAddons = getOptionValue('--no-addons'); 63 const addonConditions = noAddons ? [] : ['node-addons']; 64 65 defaultConditions = ObjectFreeze([ 66 'node', 67 'import', 68 ...addonConditions, 69 ...userConditions, 70 ]); 71 defaultConditionsSet = new SafeSet(defaultConditions); 72} 73 74/** 75 * @param {string[]} [conditions] 76 * @returns {Set<string>} 77 */ 78function getConditionsSet(conditions) { 79 if (conditions !== undefined && conditions !== getDefaultConditions()) { 80 if (!ArrayIsArray(conditions)) { 81 throw new ERR_INVALID_ARG_VALUE('conditions', conditions, 82 'expected an array'); 83 } 84 return new SafeSet(conditions); 85 } 86 return getDefaultConditionsSet(); 87} 88 89/** 90 * @callback ImportModuleDynamicallyCallback 91 * @param {string} specifier 92 * @param {ModuleWrap|ContextifyScript|Function|vm.Module} callbackReferrer 93 * @param {object} attributes 94 * @returns { Promise<void> } 95 */ 96 97/** 98 * @callback InitializeImportMetaCallback 99 * @param {object} meta 100 * @param {ModuleWrap|ContextifyScript|Function|vm.Module} callbackReferrer 101 */ 102 103/** 104 * @typedef {{ 105 * callbackReferrer: ModuleWrap|ContextifyScript|Function|vm.Module 106 * initializeImportMeta? : InitializeImportMetaCallback, 107 * importModuleDynamically? : ImportModuleDynamicallyCallback 108 * }} ModuleRegistry 109 */ 110 111/** 112 * @type {WeakMap<symbol, ModuleRegistry>} 113 */ 114const moduleRegistries = new SafeWeakMap(); 115 116/** 117 * V8 would make sure that as long as import() can still be initiated from 118 * the referrer, the symbol referenced by |host_defined_option_symbol| should 119 * be alive, which in term would keep the settings object alive through the 120 * WeakMap, and in turn that keeps the referrer object alive, which would be 121 * passed into the callbacks. 122 * The reference goes like this: 123 * [v8::internal::Script] (via host defined options) ----1--> [idSymbol] 124 * [callbackReferrer] (via host_defined_option_symbol) ------2------^ | 125 * ^----------3---- (via WeakMap)------ 126 * 1+3 makes sure that as long as import() can still be initiated, the 127 * referrer wrap is still around and can be passed into the callbacks. 128 * 2 is only there so that we can get the id symbol to configure the 129 * weak map. 130 * @param {ModuleWrap|ContextifyScript|Function} referrer The referrer to 131 * get the id symbol from. This is different from callbackReferrer which 132 * could be set by the caller. 133 * @param {ModuleRegistry} registry 134 */ 135function registerModule(referrer, registry) { 136 const idSymbol = referrer[host_defined_option_symbol]; 137 if (idSymbol === default_host_defined_options || 138 idSymbol === vm_dynamic_import_missing_flag) { 139 // The referrer is compiled without custom callbacks, so there is 140 // no registry to hold on to. We'll throw 141 // ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING when a callback is 142 // needed. 143 return; 144 } 145 // To prevent it from being GC'ed. 146 registry.callbackReferrer ??= referrer; 147 moduleRegistries.set(idSymbol, registry); 148} 149 150/** 151 * Defines the `import.meta` object for a given module. 152 * @param {symbol} symbol - Reference to the module. 153 * @param {Record<string, string | Function>} meta - The import.meta object to initialize. 154 */ 155function initializeImportMetaObject(symbol, meta) { 156 if (moduleRegistries.has(symbol)) { 157 const { initializeImportMeta, callbackReferrer } = moduleRegistries.get(symbol); 158 if (initializeImportMeta !== undefined) { 159 meta = initializeImportMeta(meta, callbackReferrer); 160 } 161 } 162} 163 164/** 165 * Asynchronously imports a module dynamically using a callback function. The native callback. 166 * @param {symbol} symbol - Reference to the module. 167 * @param {string} specifier - The module specifier string. 168 * @param {Record<string, string>} attributes - The import attributes object. 169 * @returns {Promise<import('internal/modules/esm/loader.js').ModuleExports>} - The imported module object. 170 * @throws {ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING} - If the callback function is missing. 171 */ 172async function importModuleDynamicallyCallback(symbol, specifier, attributes) { 173 if (moduleRegistries.has(symbol)) { 174 const { importModuleDynamically, callbackReferrer } = moduleRegistries.get(symbol); 175 if (importModuleDynamically !== undefined) { 176 return importModuleDynamically(specifier, callbackReferrer, attributes); 177 } 178 } 179 if (symbol === vm_dynamic_import_missing_flag) { 180 throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG(); 181 } 182 throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING(); 183} 184 185let _isLoaderWorker = false; 186/** 187 * Initializes handling of ES modules. 188 * This is configured during pre-execution. Specifically it's set to true for 189 * the loader worker in internal/main/worker_thread.js. 190 * @param {boolean} [isLoaderWorker=false] - A boolean indicating whether the loader is a worker or not. 191 */ 192function initializeESM(isLoaderWorker = false) { 193 _isLoaderWorker = isLoaderWorker; 194 initializeDefaultConditions(); 195 // Setup per-isolate callbacks that locate data or callbacks that we keep 196 // track of for different ESM modules. 197 setInitializeImportMetaObjectCallback(initializeImportMetaObject); 198 setImportModuleDynamicallyCallback(importModuleDynamicallyCallback); 199} 200 201/** 202 * Determine whether the current process is a loader worker. 203 * @returns {boolean} Whether the current process is a loader worker. 204 */ 205function isLoaderWorker() { 206 return _isLoaderWorker; 207} 208 209/** 210 * Register module customization hooks. 211 */ 212async function initializeHooks() { 213 const customLoaderURLs = getOptionValue('--experimental-loader'); 214 215 const { Hooks } = require('internal/modules/esm/hooks'); 216 const esmLoader = require('internal/process/esm_loader').esmLoader; 217 218 const hooks = new Hooks(); 219 esmLoader.setCustomizations(hooks); 220 221 // We need the loader customizations to be set _before_ we start invoking 222 // `--require`, otherwise loops can happen because a `--require` script 223 // might call `register(...)` before we've installed ourselves. These 224 // global values are magically set in `setupUserModules` just for us and 225 // we call them in the correct order. 226 // N.B. This block appears here specifically in order to ensure that 227 // `--require` calls occur before `--loader` ones do. 228 loadPreloadModules(); 229 initializeFrozenIntrinsics(); 230 231 const parentURL = getCWDURL().href; 232 for (let i = 0; i < customLoaderURLs.length; i++) { 233 await hooks.register( 234 customLoaderURLs[i], 235 parentURL, 236 ); 237 } 238 239 const preloadScripts = hooks.initializeGlobalPreload(); 240 241 return { __proto__: null, hooks, preloadScripts }; 242} 243 244module.exports = { 245 registerModule, 246 initializeESM, 247 initializeHooks, 248 getDefaultConditions, 249 getConditionsSet, 250 loaderWorkerId: 'internal/modules/esm/worker', 251 isLoaderWorker, 252}; 253