Skip to content

Commit

Permalink
core(config): make module resolution async (#13974)
Browse files Browse the repository at this point in the history
  • Loading branch information
connorjclark committed Jun 10, 2022
1 parent 76371e0 commit e3c0e21
Show file tree
Hide file tree
Showing 19 changed files with 481 additions and 454 deletions.
2 changes: 1 addition & 1 deletion lighthouse-cli/bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ async function begin() {
}

if (cliFlags.printConfig) {
const config = lighthouse.generateConfig(configJson, cliFlags);
const config = await lighthouse.generateConfig(configJson, cliFlags);
process.stdout.write(config.getPrintString());
return;
}

This comment has been minimized.

Copy link
@oxd7
Expand Down
30 changes: 16 additions & 14 deletions lighthouse-core/config/config-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,17 +211,18 @@ const bundledModules = new Map(/* BUILD_REPLACE_BUNDLED_MODULES */);
* See build-bundle.js
* @param {string} requirePath
*/
function requireWrapper(requirePath) {
async function requireWrapper(requirePath) {
// This is async because eventually this function needs to do async dynamic imports.
return bundledModules.get(requirePath) || require(requirePath);
}

/**
* @param {string} gathererPath
* @param {Array<string>} coreGathererList
* @param {string=} configDir
* @return {LH.Config.GathererDefn}
* @return {Promise<LH.Config.GathererDefn>}
*/
function requireGatherer(gathererPath, coreGathererList, configDir) {
async function requireGatherer(gathererPath, coreGathererList, configDir) {
const coreGatherer = coreGathererList.find(a => a === `${gathererPath}.js`);

let requirePath = `../gather/gatherers/${gathererPath}`;
Expand All @@ -230,7 +231,7 @@ function requireGatherer(gathererPath, coreGathererList, configDir) {
requirePath = resolveModulePath(gathererPath, configDir, 'gatherer');
}

const GathererClass = /** @type {GathererConstructor} */ (requireWrapper(requirePath));
const GathererClass = /** @type {GathererConstructor} */ (await requireWrapper(requirePath));

return {
instance: new GathererClass(),
Expand All @@ -243,7 +244,7 @@ function requireGatherer(gathererPath, coreGathererList, configDir) {
* @param {string} auditPath
* @param {Array<string>} coreAuditList
* @param {string=} configDir
* @return {LH.Config.AuditDefn['implementation']}
* @return {Promise<LH.Config.AuditDefn['implementation']>}
*/
function requireAudit(auditPath, coreAuditList, configDir) {
// See if the audit is a Lighthouse core audit.
Expand Down Expand Up @@ -328,9 +329,9 @@ function resolveSettings(settingsJson = {}, overrides = undefined) {
* @param {LH.Config.Json} configJSON
* @param {string | undefined} configDir
* @param {{plugins?: string[]} | undefined} flags
* @return {LH.Config.Json}
* @return {Promise<LH.Config.Json>}
*/
function mergePlugins(configJSON, configDir, flags) {
async function mergePlugins(configJSON, configDir, flags) {
const configPlugins = configJSON.plugins || [];
const flagPlugins = flags?.plugins || [];
const pluginNames = new Set([...configPlugins, ...flagPlugins]);
Expand All @@ -342,7 +343,7 @@ function mergePlugins(configJSON, configDir, flags) {
const pluginPath = isBundledEnvironment() ?
pluginName :
resolveModulePath(pluginName, configDir, 'plugin');
const rawPluginJson = requireWrapper(pluginPath);
const rawPluginJson = await requireWrapper(pluginPath);
const pluginJson = ConfigPlugin.parsePlugin(rawPluginJson, pluginName);

configJSON = mergeConfigFragment(configJSON, pluginJson);
Expand All @@ -360,9 +361,9 @@ function mergePlugins(configJSON, configDir, flags) {
* @param {LH.Config.GathererJson} gathererJson
* @param {Array<string>} coreGathererList
* @param {string=} configDir
* @return {LH.Config.GathererDefn}
* @return {Promise<LH.Config.GathererDefn>}
*/
function resolveGathererToDefn(gathererJson, coreGathererList, configDir) {
async function resolveGathererToDefn(gathererJson, coreGathererList, configDir) {
const gathererDefn = expandGathererShorthand(gathererJson);
if (gathererDefn.instance) {
return {
Expand Down Expand Up @@ -391,21 +392,21 @@ function resolveGathererToDefn(gathererJson, coreGathererList, configDir) {
* leaving only an array of AuditDefns.
* @param {LH.Config.Json['audits']} audits
* @param {string=} configDir
* @return {Array<LH.Config.AuditDefn>|null}
* @return {Promise<Array<LH.Config.AuditDefn>|null>}
*/
function resolveAuditsToDefns(audits, configDir) {
async function resolveAuditsToDefns(audits, configDir) {
if (!audits) {
return null;
}

const coreList = Runner.getAuditList();
const auditDefns = audits.map(auditJson => {
const auditDefnsPromises = audits.map(async (auditJson) => {
const auditDefn = expandAuditShorthand(auditJson);
let implementation;
if ('implementation' in auditDefn) {
implementation = auditDefn.implementation;
} else {
implementation = requireAudit(auditDefn.path, coreList, configDir);
implementation = await requireAudit(auditDefn.path, coreList, configDir);
}

return {
Expand All @@ -414,6 +415,7 @@ function resolveAuditsToDefns(audits, configDir) {
options: auditDefn.options || {},
};
});
const auditDefns = await Promise.all(auditDefnsPromises);

const mergedAuditDefns = mergeOptionsOfItems(auditDefns);
mergedAuditDefns.forEach(audit => validation.assertValidAudit(audit));
Expand Down
55 changes: 36 additions & 19 deletions lighthouse-core/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,18 @@ function assertValidFlags(flags) {
}
}


/**
* @implements {LH.Config.Config}
*/
class Config {
/**
* @constructor
* @param {LH.Config.Json=} configJSON
* Resolves the provided config (inherits from extended config, if set), resolves
* all referenced modules, and validates.
* @param {LH.Config.Json=} configJSON If not provided, uses the default config.
* @param {LH.Flags=} flags
* @return {Promise<Config>}
*/
constructor(configJSON, flags) {
static async fromJson(configJSON, flags) {
const status = {msg: 'Create config', id: 'lh:init:config'};
log.time(status, 'verbose');
let configPath = flags?.configPath;
Expand Down Expand Up @@ -188,7 +189,7 @@ class Config {
const configDir = configPath ? path.dirname(configPath) : undefined;

// Validate and merge in plugins (if any).
configJSON = mergePlugins(configJSON, configDir, flags);
configJSON = await mergePlugins(configJSON, configDir, flags);

if (flags) {
assertValidFlags(flags);
Expand All @@ -198,14 +199,28 @@ class Config {
// Augment passes with necessary defaults and require gatherers.
const passesWithDefaults = Config.augmentPassesWithDefaults(configJSON.passes);
Config.adjustDefaultPassForThrottling(settings, passesWithDefaults);
const passes = Config.requireGatherers(passesWithDefaults, configDir);
const passes = await Config.requireGatherers(passesWithDefaults, configDir);

const audits = await Config.requireAudits(configJSON.audits, configDir);

const config = new Config(configJSON, {settings, passes, audits});
log.timeEnd(status);
return config;
}

/**
* @deprecated `Config.fromJson` should be used instead.
* @constructor
* @param {LH.Config.Json} configJSON
* @param {{settings: LH.Config.Settings, passes: ?LH.Config.Pass[], audits: ?LH.Config.AuditDefn[]}} opts
*/
constructor(configJSON, opts) {
/** @type {LH.Config.Settings} */
this.settings = settings;
this.settings = opts.settings;
/** @type {?Array<LH.Config.Pass>} */
this.passes = passes;
this.passes = opts.passes;
/** @type {?Array<LH.Config.AuditDefn>} */
this.audits = Config.requireAudits(configJSON.audits, configDir);
this.audits = opts.audits;
/** @type {?Record<string, LH.Config.Category>} */
this.categories = configJSON.categories || null;
/** @type {?Record<string, LH.Config.Group>} */
Expand All @@ -215,8 +230,6 @@ class Config {

assertValidPasses(this.passes, this.audits);
validation.assertValidCategories(this.categories, this.audits, this.groups);

log.timeEnd(status);
}

/**
Expand Down Expand Up @@ -499,12 +512,12 @@ class Config {
* leaving only an array of AuditDefns.
* @param {LH.Config.Json['audits']} audits
* @param {string=} configDir
* @return {Config['audits']}
* @return {Promise<Config['audits']>}
*/
static requireAudits(audits, configDir) {
static async requireAudits(audits, configDir) {
const status = {msg: 'Requiring audits', id: 'lh:config:requireAudits'};
log.time(status, 'verbose');
const auditDefns = resolveAuditsToDefns(audits, configDir);
const auditDefns = await resolveAuditsToDefns(audits, configDir);
log.timeEnd(status);
return auditDefns;
}
Expand All @@ -515,19 +528,21 @@ class Config {
* provided) using `resolveModulePath`, returning an array of full Passes.
* @param {?Array<Required<LH.Config.PassJson>>} passes
* @param {string=} configDir
* @return {Config['passes']}
* @return {Promise<Config['passes']>}
*/
static requireGatherers(passes, configDir) {
static async requireGatherers(passes, configDir) {
if (!passes) {
return null;
}
const status = {msg: 'Requiring gatherers', id: 'lh:config:requireGatherers'};
log.time(status, 'verbose');

const coreList = Runner.getGathererList();
const fullPasses = passes.map(pass => {
const gathererDefns = pass.gatherers
.map(gatherer => resolveGathererToDefn(gatherer, coreList, configDir));
const fullPassesPromises = passes.map(async (pass) => {
const gathererDefns = await Promise.all(
pass.gatherers
.map(gatherer => resolveGathererToDefn(gatherer, coreList, configDir))
);

// De-dupe gatherers by artifact name because artifact IDs must be unique at runtime.
const uniqueDefns = Array.from(
Expand All @@ -537,6 +552,8 @@ class Config {

return Object.assign(pass, {gatherers: uniqueDefns});
});
const fullPasses = await Promise.all(fullPassesPromises);

log.timeEnd(status);
return fullPasses;
}
Expand Down
19 changes: 10 additions & 9 deletions lighthouse-core/fraggle-rock/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ function resolveArtifactDependencies(artifact, gatherer, artifactDefnsBySymbol)
*
* @param {LH.Config.ArtifactJson[]|null|undefined} artifacts
* @param {string|undefined} configDir
* @return {LH.Config.AnyArtifactDefn[] | null}
* @return {Promise<LH.Config.AnyArtifactDefn[] | null>}
*/
function resolveArtifactsToDefns(artifacts, configDir) {
async function resolveArtifactsToDefns(artifacts, configDir) {
if (!artifacts) return null;

const status = {msg: 'Resolve artifact definitions', id: 'lh:config:resolveArtifactsToDefns'};
Expand All @@ -133,12 +133,12 @@ function resolveArtifactsToDefns(artifacts, configDir) {
const artifactDefnsBySymbol = new Map();

const coreGathererList = Runner.getGathererList();
const artifactDefns = artifacts.map(artifactJson => {
const artifactDefnsPromises = artifacts.map(async (artifactJson) => {
/** @type {LH.Config.GathererJson} */
// @ts-expect-error - remove when legacy runner path is removed.
const gathererJson = artifactJson.gatherer;

const gatherer = resolveGathererToDefn(gathererJson, coreGathererList, configDir);
const gatherer = await resolveGathererToDefn(gathererJson, coreGathererList, configDir);
if (!isFRGathererDefn(gatherer)) {
throw new Error(`${gatherer.instance.name} gatherer does not have a Fraggle Rock meta obj`);
}
Expand All @@ -156,6 +156,7 @@ function resolveArtifactsToDefns(artifacts, configDir) {
if (symbol) artifactDefnsBySymbol.set(symbol, artifact);
return artifact;
});
const artifactDefns = await Promise.all(artifactDefnsPromises);

log.timeEnd(status);
return artifactDefns;
Expand Down Expand Up @@ -242,28 +243,28 @@ function resolveNavigationsToDefns(navigations, artifactDefns, settings) {
/**
* @param {LH.Config.Json|undefined} configJSON
* @param {ConfigContext} context
* @return {{config: LH.Config.FRConfig, warnings: string[]}}
* @return {Promise<{config: LH.Config.FRConfig, warnings: string[]}>}
*/
function initializeConfig(configJSON, context) {
async function initializeConfig(configJSON, context) {
const status = {msg: 'Initialize config', id: 'lh:config'};
log.time(status, 'verbose');

let {configWorkingCopy, configDir} = resolveWorkingCopy(configJSON, context); // eslint-disable-line prefer-const

configWorkingCopy = resolveExtensions(configWorkingCopy);
configWorkingCopy = mergePlugins(configWorkingCopy, configDir, context.settingsOverrides);
configWorkingCopy = await mergePlugins(configWorkingCopy, configDir, context.settingsOverrides);

const settings = resolveSettings(configWorkingCopy.settings || {}, context.settingsOverrides);
overrideSettingsForGatherMode(settings, context);

const artifacts = resolveArtifactsToDefns(configWorkingCopy.artifacts, configDir);
const artifacts = await resolveArtifactsToDefns(configWorkingCopy.artifacts, configDir);
const navigations = resolveNavigationsToDefns(configWorkingCopy.navigations, artifacts, settings);

/** @type {LH.Config.FRConfig} */
let config = {
artifacts,
navigations,
audits: resolveAuditsToDefns(configWorkingCopy.audits, configDir),
audits: await resolveAuditsToDefns(configWorkingCopy.audits, configDir),
categories: configWorkingCopy.categories || null,
groups: configWorkingCopy.groups || null,
settings,
Expand Down
3 changes: 2 additions & 1 deletion lighthouse-core/fraggle-rock/gather/navigation-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,8 @@ async function navigationGather(requestor, options) {
const {configContext = {}} = options;
log.setLevel(configContext.logLevel || 'error');

const {config} = initializeConfig(options.config, {...configContext, gatherMode: 'navigation'});
const {config} =
await initializeConfig(options.config, {...configContext, gatherMode: 'navigation'});
const computedCache = new Map();
const internalOptions = {
skipAboutBlank: configContext.skipAboutBlank,
Expand Down
3 changes: 2 additions & 1 deletion lighthouse-core/fraggle-rock/gather/snapshot-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ async function snapshotGather(options) {
const {configContext = {}} = options;
log.setLevel(configContext.logLevel || 'error');

const {config} = initializeConfig(options.config, {...configContext, gatherMode: 'snapshot'});
const {config} =
await initializeConfig(options.config, {...configContext, gatherMode: 'snapshot'});
const driver = new Driver(options.page);
await driver.connect();

Expand Down
3 changes: 2 additions & 1 deletion lighthouse-core/fraggle-rock/gather/timespan-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ async function startTimespanGather(options) {
const {configContext = {}} = options;
log.setLevel(configContext.logLevel || 'error');

const {config} = initializeConfig(options.config, {...configContext, gatherMode: 'timespan'});
const {config} =
await initializeConfig(options.config, {...configContext, gatherMode: 'timespan'});
const driver = new Driver(options.page);
await driver.connect();

Expand Down
2 changes: 1 addition & 1 deletion lighthouse-core/fraggle-rock/user-flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ async function auditGatherSteps(gatherSteps, options) {
// Step specific configs take precedence over a config for the entire flow.
const configJson = gatherStep.config || options.config;
const {gatherMode} = artifacts.GatherContext;
const {config} = initializeConfig(configJson, {...configContext, gatherMode});
const {config} = await initializeConfig(configJson, {...configContext, gatherMode});
runnerOptions = {
config,
computedCache: new Map(),
Expand Down
6 changes: 3 additions & 3 deletions lighthouse-core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ async function legacyNavigation(url, flags = {}, configJSON, userConnection) {
flags.logLevel = flags.logLevel || 'error';
log.setLevel(flags.logLevel);

const config = generateConfig(configJSON, flags);
const config = await generateConfig(configJSON, flags);
const computedCache = new Map();
const options = {config, computedCache};
const connection = userConnection || new ChromeProtocol(flags.port, flags.hostname);
Expand All @@ -83,10 +83,10 @@ async function legacyNavigation(url, flags = {}, configJSON, userConnection) {
* not present, the default config is used.
* @param {LH.Flags=} flags Optional settings for the Lighthouse run. If present,
* they will override any settings in the config.
* @return {Config}
* @return {Promise<Config>}
*/
function generateConfig(configJson, flags) {
return new Config(configJson, flags);
return Config.fromJson(configJson, flags);
}

lighthouse.legacyNavigation = legacyNavigation;
Expand Down
3 changes: 1 addition & 2 deletions lighthouse-core/scripts/print-a11y-scoring.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
// node lighthouse-core/scripts/print-a11y-scoring.js

import Config from '../config/config.js';
import defaultConfig from '../config/default-config.js';

const config = new Config(defaultConfig);
const config = await Config.fromJson();
if (!config.categories || !config.audits) throw new Error('wut');

const auditRefs = config.categories.accessibility.auditRefs;
Expand Down
Loading

0 comments on commit e3c0e21

Please sign in to comment.