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