Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core(config): make module resolution async #13974

Merged
merged 8 commits into from
Jun 10, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
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
50 changes: 32 additions & 18 deletions lighthouse-core/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,17 +148,15 @@ function assertValidFlags(flags) {
}
}


/**
* @implements {LH.Config.Config}
*/
class Config {
/**
* @constructor
* @param {LH.Config.Json=} configJSON
* @param {LH.Flags=} flags
connorjclark marked this conversation as resolved.
Show resolved Hide resolved
*/
constructor(configJSON, flags) {
static async createConfigFromJson(configJSON, flags) {
Copy link
Collaborator Author

@connorjclark connorjclark May 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constructors can't be async, so this helper function exists now.

This comment was marked as spam.

const status = {msg: 'Create config', id: 'lh:init:config'};
log.time(status, 'verbose');
let configPath = flags?.configPath;
Expand Down Expand Up @@ -187,7 +185,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 @@ -197,14 +195,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.createConfigFromJson` should be used instead.
* @constructor
* @param {LH.Config.Json} configJSON
* @param {{settings: LH.Config.Settings, passes: ?LH.Config.Pass[], audits: ?LH.Config.AuditDefn[]}} opts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow, ancient typing style

*/
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 @@ -214,8 +226,6 @@ class Config {

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

log.timeEnd(status);
}

/**
Expand Down Expand Up @@ -498,12 +508,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 @@ -514,19 +524,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 @@ -536,6 +548,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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO for me: Make this FR config and add a generateLegacyConfig or something.

return new Config(configJson, flags);
return Config.createConfigFromJson(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.createConfigFromJson();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The defaultConfig is used by default.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

if (!config.categories || !config.audits) throw new Error('wut');

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