Skip to content

Commit 587f9d6

Browse files
authored
feat: Add initial support for Lighthouse settings (#474)
Add a new settings property, which enables testing as Desktop and setting a custom locale when generating reports
1 parent 8b2f4cf commit 587f9d6

File tree

8 files changed

+129
-14
lines changed

8 files changed

+129
-14
lines changed

.env.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ AUDITS=[{"url":"https://www.example.com","thresholds":{"performance":0.5}},{"pat
33
# Ignored when a url is configured for an audit
44
PUBLISH_DIR=FULL_PATH_TO_LOCAL_BUILD_DIRECTORY
55
# JSON string of thresholds to enforce
6-
THRESHOLDS={"performance":0.9,"accessibility":0.9,"best-practices":0.9,"seo":0.9,"pwa":0.9}
6+
THRESHOLDS={"performance":0.9,"accessibility":0.9,"best-practices":0.9,"seo":0.9,"pwa":0.9}
7+
# SETTINGS
8+
SETTINGS={"locale":"en", "preset":"desktop"}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ yarn-error.log
77
reports
88
cypress/videos
99
cypress/screenshots
10+
coverage

README.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ If you would like to run the Lighthouse build plugin for multiple site paths, su
88

99
### Install plugin through the Netlify UI
1010

11-
You can install this plugin in the Netlify UI from this [direct in-app installation link](https://app.netlify.com/plugins/@netlify/plugin-lighthouse/install) or from the [Plugins directory](https://app.netlify.com/plugins).
11+
You can install this plugin in the Netlify UI from this [direct in-app installation link](https://app.netlify.com/plugins/@netlify/plugin-lighthouse/install) or from the [Plugins directory](https://app.netlify.com/plugins).
1212

1313
### Install plugin through the `netlify.toml` file
1414

@@ -71,22 +71,22 @@ Example `netlify.toml` file that audits the site root path and a contact page:
7171
```
7272
[[plugins]]
7373
package = "@netlify/plugin-lighthouse"
74-
74+
7575
# Generate a Lighthouse report for the site's root path
7676
[[plugins.inputs.audits]]
7777
path = ""
78-
78+
7979
# Generate a Lighthouse report for the contact site path
8080
[[plugins.inputs.audits]]
8181
path = "contact"
8282
8383
```
8484

85-
8685
The lighthouse report results are automatically printed to the **Deploy log** in the Netlify UI. For example:
86+
8787
```
8888
2:35:07 PM: ────────────────────────────────────────────────────────────────
89-
2:35:07 PM: 2. onPostBuild command from @netlify/plugin-lighthouse
89+
2:35:07 PM: 2. onPostBuild command from @netlify/plugin-lighthouse
9090
2:35:07 PM: ────────────────────────────────────────────────────────────────
9191
2:35:07 PM: ​
9292
2:35:07 PM: Serving and scanning site from directory dist
@@ -104,6 +104,17 @@ The lighthouse report results are automatically printed to the **Deploy log** in
104104
2:35:17 PM: }
105105
```
106106

107+
We currently support the following settings, which are passed directly to Lighthouse:
108+
109+
```
110+
[[plugins]]
111+
package = "@netlify/plugin-lighthouse"
112+
113+
[plugins.inputs.settings]
114+
preset = "desktop" # Optionally run Lighthouse using a desktop configuration
115+
locale = "es" # Any Lighthouse-supported locale, used to generate reports in a different language
116+
```
117+
107118
## Running Locally
108119

109120
Fork and clone this repo.
@@ -126,6 +137,7 @@ If you have multiple audits (directories, paths, etc) defined in your build, we
126137
<img width="1400" alt="Deploy details with multiple audit Lighthouse results" src="https://user-images.githubusercontent.com/79875905/160019057-d29dffab-49f3-4fbf-a1ac-1f314e0cd837.png">
127138

128139
Some items of note:
140+
129141
- The [Lighthouse Build Plugin](https://app.netlify.com/plugins/@netlify/plugin-lighthouse/install) must be installed on your site(s) in order for these score visualizations to be displayed.
130142
- This Labs feature is currently only enabled at the user-level, so it will need to be enabled for each individual team member that wishes to see the Lighthouse scores displayed.
131143

manifest.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ inputs:
1616
- name: audits
1717
required: false
1818
description: A list of audits to perform. Each list item is an object with either a url/path to scan and an optional thresholds mapping.
19+
20+
- name: settings
21+
required: false
22+
description: Lighthouse-specific settings, used to modify reporting criteria

src/index.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const chalk = require('chalk');
66
const fs = require('fs').promises;
77
const minify = require('html-minifier').minify;
88
const { getConfiguration } = require('./config');
9+
const { getSettings } = require('./settings');
910
const { getBrowserPath, runLighthouse } = require('./lighthouse');
1011
const { makeReplacements } = require('./replacements');
1112

@@ -136,14 +137,18 @@ const getUtils = ({ utils }) => {
136137
return { failBuild, show };
137138
};
138139

139-
const runAudit = async ({ path, url, thresholds, output_path }) => {
140+
const runAudit = async ({ path, url, thresholds, output_path, settings }) => {
140141
try {
141142
const { server } = getServer({ serveDir: path, auditUrl: url });
142143
const browserPath = await getBrowserPath();
143144
const { error, results } = await new Promise((resolve) => {
144145
const instance = server.listen(async () => {
145146
try {
146-
const results = await runLighthouse(browserPath, server.url);
147+
const results = await runLighthouse(
148+
browserPath,
149+
server.url,
150+
settings,
151+
);
147152
resolve({ error: false, results });
148153
} catch (error) {
149154
resolve({ error });
@@ -261,6 +266,8 @@ module.exports = {
261266
inputs,
262267
});
263268

269+
const settings = getSettings(inputs?.settings);
270+
264271
const allErrors = [];
265272
const data = [];
266273
for (const { path, url, thresholds, output_path } of audits) {
@@ -269,6 +276,7 @@ module.exports = {
269276
url,
270277
thresholds,
271278
output_path,
279+
settings,
272280
});
273281
if (summary) {
274282
console.log({ results: summary });

src/lighthouse.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const getBrowserPath = async () => {
3030
return info.executablePath;
3131
};
3232

33-
const runLighthouse = async (browserPath, url) => {
33+
const runLighthouse = async (browserPath, url, settings) => {
3434
let chrome;
3535
try {
3636
const logLevel = 'info';
@@ -45,11 +45,15 @@ const runLighthouse = async (browserPath, url) => {
4545
],
4646
logLevel,
4747
});
48-
const results = await lighthouse(url, {
49-
port: chrome.port,
50-
output: 'html',
51-
logLevel,
52-
});
48+
const results = await lighthouse(
49+
url,
50+
{
51+
port: chrome.port,
52+
output: 'html',
53+
logLevel,
54+
},
55+
settings,
56+
);
5357
if (results.lhr.runtimeError) {
5458
throw new Error(results.lhr.runtimeError.message);
5559
}

src/settings.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const desktopConfig = require('lighthouse/lighthouse-core/config/desktop-config');
2+
const fullConfig = require('lighthouse/lighthouse-core/config/full-config');
3+
4+
/*
5+
* Check for settings added in `.env` file and merge with input settings
6+
* specified in `netlify.toml`
7+
*/
8+
const mergeSettingsSources = (inputSettings = {}) => {
9+
let envSettings = {};
10+
if (typeof process.env.SETTINGS === 'string') {
11+
try {
12+
envSettings = JSON.parse(process.env.SETTINGS);
13+
} catch (e) {
14+
throw new Error(`Invalid JSON for 'settings' input: ${e.message}`);
15+
}
16+
}
17+
18+
// Shallow merge of input and env settings, with input taking priority.
19+
// Review the need for a deep merge if/when more complex settings are added.
20+
return Object.assign({}, envSettings, inputSettings);
21+
};
22+
23+
const getSettings = (inputSettings) => {
24+
const settings = mergeSettingsSources(inputSettings);
25+
if (Object.keys(settings).length === 0) return;
26+
27+
// Set a base-level config based on the preset input value
28+
// (desktop is currently the only supported option)
29+
const derivedSettings =
30+
settings.preset === 'desktop' ? desktopConfig : fullConfig;
31+
32+
// The following are added to the `settings` object of the selected base config
33+
// We add individually to avoid passing anything unexpected to Lighthouse.
34+
if (settings.locale) {
35+
derivedSettings.settings.locale = settings.locale;
36+
}
37+
38+
return derivedSettings;
39+
};
40+
41+
module.exports = {
42+
getSettings,
43+
};

src/settings.test.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const { getSettings } = require('./settings');
2+
3+
describe('replacements', () => {
4+
it('should return nothing with no settings set', () => {
5+
expect(getSettings()).toEqual(undefined);
6+
});
7+
8+
it('should return a template config with preset set to desktop', () => {
9+
const derivedSettings = getSettings({ preset: 'desktop' });
10+
expect(derivedSettings.extends).toEqual('lighthouse:default');
11+
expect(derivedSettings.settings.formFactor).toEqual('desktop');
12+
});
13+
14+
it('should add a locale if set', () => {
15+
const derivedSettings = getSettings({ locale: 'es' });
16+
expect(derivedSettings.settings.locale).toEqual('es');
17+
});
18+
19+
it('should use values from process.env.SETTINGS', () => {
20+
process.env.SETTINGS = JSON.stringify({
21+
preset: 'desktop',
22+
locale: 'ar',
23+
});
24+
const derivedSettings = getSettings();
25+
expect(derivedSettings.settings.formFactor).toEqual('desktop');
26+
expect(derivedSettings.settings.locale).toEqual('ar');
27+
});
28+
29+
it('should prefer values from input over process.env.SETTINGS', () => {
30+
process.env.SETTINGS = JSON.stringify({
31+
locale: 'ar',
32+
});
33+
const derivedSettings = getSettings({ locale: 'es' });
34+
expect(derivedSettings.settings.locale).toEqual('es');
35+
});
36+
37+
it('should error with incorrect syntax for process.env.SETTINGS', () => {
38+
process.env.SETTINGS = 'not json';
39+
expect(getSettings).toThrow(/Invalid JSON/);
40+
});
41+
});

0 commit comments

Comments
 (0)