async function loadModule(pathFolders, moduleName, customLoadModuleSource) {
  const { descriptor: rawDescriptor, paths } = await loadRawModule(pathFolders, moduleName, 'module.json');

  let scriptContent = await customLoadModuleSource(rawDescriptor, paths);
  const promises = [];
  if (scriptContent === null) {
    scriptContent = '';
    promises.push(...(rawDescriptor.scripts || []).map(name => loadSource(pathFolders, moduleName, name)));
  }
  promises.push(...(rawDescriptor.resources || []).map(name => loadResource(pathFolders, moduleName, name)));
  scriptContent += (await Promise.all(promises)).join('\n');

  const namespace = moduleNamespace(moduleName);
  const content = `self['${namespace}'] = self['${namespace}'] || {};\n${scriptContent}\n`;

  const descriptor = { content };
  if (rawDescriptor.extensions)
    descriptor.extensions = rawDescriptor.extensions;
  if (rawDescriptor.dependencies)
    descriptor.dependencies = rawDescriptor.dependencies;
  if (rawDescriptor.experiment)
    descriptor.experiment = rawDescriptor.experiment;
  return descriptor;

  /**
   * @param {!Array<string>} pathFolders
   * @param {string} moduleName
   * @param {string} name
   */
  async function loadResource(pathFolders, moduleName, name) {
    const resource = await loadSource(pathFolders, moduleName, name);
    const content = resource.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/'/g, '\\\'');
    return `Runtime.cachedResources['${moduleName}/${name}'] = '${content}';`;
  }
}
@param {!Array<string>} pathFolders
@param {string} moduleName
@param {!function(!Object):!Promise<string>} customLoadModuleSource