• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayPrototypeMap,
5  Boolean,
6  JSONParse,
7  ObjectGetPrototypeOf,
8  ObjectPrototypeHasOwnProperty,
9  ObjectKeys,
10  PromisePrototypeCatch,
11  PromiseReject,
12  RegExpPrototypeTest,
13  SafeMap,
14  SafeSet,
15  StringPrototypeReplace,
16  StringPrototypeSlice,
17  StringPrototypeSplit,
18  StringPrototypeStartsWith,
19  SyntaxErrorPrototype,
20  globalThis: { WebAssembly },
21} = primordials;
22
23let _TYPES = null;
24function lazyTypes() {
25  if (_TYPES !== null) return _TYPES;
26  return _TYPES = require('internal/util/types');
27}
28
29const { readFileSync } = require('fs');
30const { extname, isAbsolute } = require('path');
31const {
32  stripBOM,
33  loadNativeModule
34} = require('internal/modules/cjs/helpers');
35const {
36  Module: CJSModule,
37  cjsParseCache
38} = require('internal/modules/cjs/loader');
39const internalURLModule = require('internal/url');
40const { defaultGetSource } = require(
41  'internal/modules/esm/get_source');
42const { defaultTransformSource } = require(
43  'internal/modules/esm/transform_source');
44const createDynamicModule = require(
45  'internal/modules/esm/create_dynamic_module');
46const { fileURLToPath, URL } = require('url');
47let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
48  debug = fn;
49});
50const { emitExperimentalWarning } = require('internal/util');
51const {
52  ERR_UNKNOWN_BUILTIN_MODULE,
53  ERR_INVALID_RETURN_PROPERTY_VALUE
54} = require('internal/errors').codes;
55const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache');
56const moduleWrap = internalBinding('module_wrap');
57const { ModuleWrap } = moduleWrap;
58const { getOptionValue } = require('internal/options');
59const experimentalImportMetaResolve =
60    getOptionValue('--experimental-import-meta-resolve');
61const asyncESM = require('internal/process/esm_loader');
62const { emitWarningSync } = require('internal/process/warning');
63const { TextDecoder } = require('internal/encoding');
64
65let cjsParse;
66async function initCJSParse() {
67  if (typeof WebAssembly === 'undefined') {
68    cjsParse = require('internal/deps/cjs-module-lexer/lexer').parse;
69  } else {
70    const { parse, init } =
71        require('internal/deps/cjs-module-lexer/dist/lexer');
72    await init();
73    cjsParse = parse;
74  }
75}
76
77const translators = new SafeMap();
78exports.translators = translators;
79exports.enrichCJSError = enrichCJSError;
80
81let DECODER = null;
82function assertBufferSource(body, allowString, hookName) {
83  if (allowString && typeof body === 'string') {
84    return;
85  }
86  const { isArrayBufferView, isAnyArrayBuffer } = lazyTypes();
87  if (isArrayBufferView(body) || isAnyArrayBuffer(body)) {
88    return;
89  }
90  throw new ERR_INVALID_RETURN_PROPERTY_VALUE(
91    `${allowString ? 'string, ' : ''}array buffer, or typed array`,
92    hookName,
93    'source',
94    body
95  );
96}
97
98function stringify(body) {
99  if (typeof body === 'string') return body;
100  assertBufferSource(body, false, 'transformSource');
101  DECODER = DECODER === null ? new TextDecoder() : DECODER;
102  return DECODER.decode(body);
103}
104
105function errPath(url) {
106  const parsed = new URL(url);
107  if (parsed.protocol === 'file:') {
108    return fileURLToPath(parsed);
109  }
110  return url;
111}
112
113async function importModuleDynamically(specifier, { url }) {
114  return asyncESM.ESMLoader.import(specifier, url);
115}
116
117function createImportMetaResolve(defaultParentUrl) {
118  return async function resolve(specifier, parentUrl = defaultParentUrl) {
119    return PromisePrototypeCatch(
120      asyncESM.ESMLoader.resolve(specifier, parentUrl),
121      (error) => (
122        error.code === 'ERR_UNSUPPORTED_DIR_IMPORT' ?
123          error.url : PromiseReject(error))
124    );
125  };
126}
127
128function initializeImportMeta(meta, { url }) {
129  // Alphabetical
130  if (experimentalImportMetaResolve)
131    meta.resolve = createImportMetaResolve(url);
132  meta.url = url;
133}
134
135// Strategy for loading a standard JavaScript module.
136translators.set('module', async function moduleStrategy(url) {
137  let { source } = await this._getSource(
138    url, { format: 'module' }, defaultGetSource);
139  assertBufferSource(source, true, 'getSource');
140  ({ source } = await this._transformSource(
141    source, { url, format: 'module' }, defaultTransformSource));
142  source = stringify(source);
143  maybeCacheSourceMap(url, source);
144  debug(`Translating StandardModule ${url}`);
145  const module = new ModuleWrap(url, undefined, source, 0, 0);
146  moduleWrap.callbackMap.set(module, {
147    initializeImportMeta,
148    importModuleDynamically,
149  });
150  return module;
151});
152
153function enrichCJSError(err) {
154  if (err == null || ObjectGetPrototypeOf(err) !== SyntaxErrorPrototype) {
155    return;
156  }
157  const stack = StringPrototypeSplit(err.stack, '\n');
158  /*
159  * The regular expression below targets the most common import statement
160  * usage. However, some cases are not matching, cases like import statement
161  * after a comment block and/or after a variable definition.
162  */
163  if (StringPrototypeStartsWith(err.message, 'Unexpected token \'export\'') ||
164    RegExpPrototypeTest(/^\s*import(?=[ {'"*])\s*(?![ (])/, stack[1])) {
165    // Emit the warning synchronously because we are in the middle of handling
166    // a SyntaxError that will throw and likely terminate the process before an
167    // asynchronous warning would be emitted.
168    emitWarningSync(
169      'To load an ES module, set "type": "module" in the package.json or use ' +
170      'the .mjs extension.'
171    );
172  }
173}
174
175// Strategy for loading a node-style CommonJS module
176const isWindows = process.platform === 'win32';
177const winSepRegEx = /\//g;
178translators.set('commonjs', async function commonjsStrategy(url, isMain) {
179  debug(`Translating CJSModule ${url}`);
180
181  let filename = internalURLModule.fileURLToPath(new URL(url));
182  if (isWindows)
183    filename = StringPrototypeReplace(filename, winSepRegEx, '\\');
184
185  if (!cjsParse) await initCJSParse();
186  const { module, exportNames } = cjsPreparseModuleExports(filename);
187  const namesWithDefault = exportNames.has('default') ?
188    [...exportNames] : ['default', ...exportNames];
189
190  return new ModuleWrap(url, undefined, namesWithDefault, function() {
191    debug(`Loading CJSModule ${url}`);
192
193    let exports;
194    if (asyncESM.ESMLoader.cjsCache.has(module)) {
195      exports = asyncESM.ESMLoader.cjsCache.get(module);
196      asyncESM.ESMLoader.cjsCache.delete(module);
197    } else {
198      try {
199        exports = CJSModule._load(filename, undefined, isMain);
200      } catch (err) {
201        enrichCJSError(err);
202        throw err;
203      }
204    }
205
206    for (const exportName of exportNames) {
207      if (!ObjectPrototypeHasOwnProperty(exports, exportName) ||
208          exportName === 'default')
209        continue;
210      // We might trigger a getter -> dont fail.
211      let value;
212      try {
213        value = exports[exportName];
214      } catch {}
215      this.setExport(exportName, value);
216    }
217    this.setExport('default', exports);
218  });
219});
220
221function cjsPreparseModuleExports(filename) {
222  let module = CJSModule._cache[filename];
223  if (module) {
224    const cached = cjsParseCache.get(module);
225    if (cached)
226      return { module, exportNames: cached.exportNames };
227  }
228  const loaded = Boolean(module);
229  if (!loaded) {
230    module = new CJSModule(filename);
231    module.filename = filename;
232    module.paths = CJSModule._nodeModulePaths(module.path);
233    CJSModule._cache[filename] = module;
234  }
235
236  let source;
237  try {
238    source = readFileSync(filename, 'utf8');
239  } catch {}
240
241  let exports, reexports;
242  try {
243    ({ exports, reexports } = cjsParse(source || ''));
244  } catch {
245    exports = [];
246    reexports = [];
247  }
248
249  const exportNames = new SafeSet(exports);
250
251  // Set first for cycles.
252  cjsParseCache.set(module, { source, exportNames, loaded });
253
254  if (reexports.length) {
255    module.filename = filename;
256    module.paths = CJSModule._nodeModulePaths(module.path);
257  }
258  for (const reexport of reexports) {
259    let resolved;
260    try {
261      resolved = CJSModule._resolveFilename(reexport, module);
262    } catch {
263      continue;
264    }
265    const ext = extname(resolved);
266    if ((ext === '.js' || ext === '.cjs' || !CJSModule._extensions[ext]) &&
267        isAbsolute(resolved)) {
268      const { exportNames: reexportNames } = cjsPreparseModuleExports(resolved);
269      for (const name of reexportNames)
270        exportNames.add(name);
271    }
272  }
273
274  return { module, exportNames };
275}
276
277// Strategy for loading a node builtin CommonJS module that isn't
278// through normal resolution
279translators.set('builtin', async function builtinStrategy(url) {
280  debug(`Translating BuiltinModule ${url}`);
281  // Slice 'node:' scheme
282  const id = StringPrototypeSlice(url, 5);
283  const module = loadNativeModule(id, url);
284  if (!StringPrototypeStartsWith(url, 'node:') || !module) {
285    throw new ERR_UNKNOWN_BUILTIN_MODULE(url);
286  }
287  debug(`Loading BuiltinModule ${url}`);
288  return module.getESMFacade();
289});
290
291// Strategy for loading a JSON file
292translators.set('json', async function jsonStrategy(url) {
293  emitExperimentalWarning('Importing JSON modules');
294  debug(`Translating JSONModule ${url}`);
295  debug(`Loading JSONModule ${url}`);
296  const pathname = StringPrototypeStartsWith(url, 'file:') ?
297    fileURLToPath(url) : null;
298  let modulePath;
299  let module;
300  if (pathname) {
301    modulePath = isWindows ?
302      StringPrototypeReplace(pathname, winSepRegEx, '\\') : pathname;
303    module = CJSModule._cache[modulePath];
304    if (module && module.loaded) {
305      const exports = module.exports;
306      return new ModuleWrap(url, undefined, ['default'], function() {
307        this.setExport('default', exports);
308      });
309    }
310  }
311  let { source } = await this._getSource(
312    url, { format: 'json' }, defaultGetSource);
313  assertBufferSource(source, true, 'getSource');
314  ({ source } = await this._transformSource(
315    source, { url, format: 'json' }, defaultTransformSource));
316  source = stringify(source);
317  if (pathname) {
318    // A require call could have been called on the same file during loading and
319    // that resolves synchronously. To make sure we always return the identical
320    // export, we have to check again if the module already exists or not.
321    module = CJSModule._cache[modulePath];
322    if (module && module.loaded) {
323      const exports = module.exports;
324      return new ModuleWrap(url, undefined, ['default'], function() {
325        this.setExport('default', exports);
326      });
327    }
328  }
329  try {
330    const exports = JSONParse(stripBOM(source));
331    module = {
332      exports,
333      loaded: true
334    };
335  } catch (err) {
336    // TODO (BridgeAR): We could add a NodeCore error that wraps the JSON
337    // parse error instead of just manipulating the original error message.
338    // That would allow to add further properties and maybe additional
339    // debugging information.
340    err.message = errPath(url) + ': ' + err.message;
341    throw err;
342  }
343  if (pathname) {
344    CJSModule._cache[modulePath] = module;
345  }
346  return new ModuleWrap(url, undefined, ['default'], function() {
347    debug(`Parsing JSONModule ${url}`);
348    this.setExport('default', module.exports);
349  });
350});
351
352// Strategy for loading a wasm module
353translators.set('wasm', async function(url) {
354  emitExperimentalWarning('Importing Web Assembly modules');
355  let { source } = await this._getSource(
356    url, { format: 'wasm' }, defaultGetSource);
357  assertBufferSource(source, false, 'getSource');
358  ({ source } = await this._transformSource(
359    source, { url, format: 'wasm' }, defaultTransformSource));
360  assertBufferSource(source, false, 'transformSource');
361  debug(`Translating WASMModule ${url}`);
362  let compiled;
363  try {
364    compiled = await WebAssembly.compile(source);
365  } catch (err) {
366    err.message = errPath(url) + ': ' + err.message;
367    throw err;
368  }
369
370  const imports =
371      ArrayPrototypeMap(WebAssembly.Module.imports(compiled),
372                        ({ module }) => module);
373  const exports =
374    ArrayPrototypeMap(WebAssembly.Module.exports(compiled),
375                      ({ name }) => name);
376
377  return createDynamicModule(imports, exports, url, (reflect) => {
378    const { exports } = new WebAssembly.Instance(compiled, reflect.imports);
379    for (const expt of ObjectKeys(exports))
380      reflect.exports[expt].set(exports[expt]);
381  }).module;
382});
383