[Code Quality] Lighthouse CI

2023-10-17 hit count image

Let's see how to execute Lighthouse provided Google Chrome on the local or the CI environment and how to measure automatically the performance of the web page in the CI environment.

Outline

Google Chrome provides a feature named Lighthouse, and we can use it to measure the performance of the web app or web page.

Lighthouse CI - Google Chrome Lighthouse

Lighthouse is a tool to analyze the performance of the web app or web page, and show the best practices.

In this blog post, I will introduce how to execute Lighthouse on the local or the CI environment.

Lighthouse CI

Lighthouse CI is a tool for us to execute Lighthouse and check the result of it on the CI environment.

To execute Lighthouse CI on the local or the CI environment, The environment of 16 or higher of NodeJS is required.

Install Lighthouse CI

Execute the following command on the NodeJS environment to install Lighthouse CI.

npm install --save-dev @lhci/cli

If you don’t have the package.json, you can install Lighthouse CI on the global environment by executting the following command.

npm install -g @lhci/cli

Configure Lighthouse CI

To use Lighthouse CI, we need to configure Lighthouse. Make the .lighthouserc.js file on the root directory of the project, and modify it like the following.

module.exports = {
  ci: {
    assert: {
      preset: 'lighthouse:recommended',
    },
  },
};

If you configure it like this, you can use the recommended configuration provided Lighthouse.

Configure target

Now, we need to configure the scan target of Lighthouse. You can set the static files or URL to the target.

Static files

Static files are a way to inspect the build results of a web site or web page under development. Open the .lighthouserc.js file that is the configuration file of Lighthouse, and modify it like the following to scan the static files.

module.exports = {
  ci: {
    collect: {
      staticDistDir: '_site/',
    },
    assert: {
      preset: 'lighthouse:recommended',
    },
  },
};

I use Jekyll to manage the blog. Jekyll can build blog posts written in Markdown into HTML files using the command bundle exec jekyll build. The files built in this way are created in a folder called _site.

By setting the folder of static files created in this way to the staticDistDir option of collect, you can set what Lighthosue will scan.

Specific URL

If you can’t set the static files, you can set the URL of the target pages to make Lighthouse inspect them. To scan the specific URL, open the .lighthouserc.js file that is the Lighthouse configuration file, and modify it like the following.

module.exports = {
  ci: {
    collect: {
      url: [
        // Korean
        'https://deku.posstree.com/ko/flutter/',
        'https://deku.posstree.com/ko/react/',
        'https://deku.posstree.com/ko/react-native/',
        // English
        'https://deku.posstree.com/en/flutter/',
        'https://deku.posstree.com/en/react/',
        'https://deku.posstree.com/en/react-native/',
        // Japanese
        'https://deku.posstree.com/flutter/',
        'https://deku.posstree.com/react/',
        'https://deku.posstree.com/react-native/'
      ]
    },
    assert: {
      preset: 'lighthouse:recommended',
    },
  },
};

After setting it like this, Lighthouse will inspect the target URL.

Subfolder issue

I configured the target folder to staticDistDir, there is an issue with the files under subfolders not being scanned. To solve this issue, I decided to use sitemap.xml.

On JavaScript to read the sitemap.xml file, I use the xml-js library.

So, executing the following command to install the xml-js library.

npm install --save-dev xml-js

And then, open the .lighthouserc.js file and modify it like the following.

const fs = require('fs');
const convert = require('xml-js');

const SITEMAP_PATH = './_site/sitemap.xml';
const SITE_URL = 'https://deku.posstree.com';

const data = fs.readFileSync(SITEMAP_PATH);
const result = convert.xml2json(data, { compact: true, spaces: 4 });
const json = JSON.parse(result);
const url = json.urlset.url.map((item) => item.loc._text.replace(SITE_URL, ''));

module.exports = {
  ci: {
    collect: {
      staticDistDir: './_site/',
      url: url,
    },
    assert: {
      preset: 'lighthouse:recommended',
    },
  },
};

You can use it by modifying SITEMAP_PATH and SITE_URL according to your project. After modifying the file and executing it, the Lighthouse configuration reads the sitemap.xml and removes unnecessary part(SITE_URL), and configures it to the Lighthouse option to inspect them by Lighthouse.

After modifying the .lighthouserc.js file and executing Lighthouse, you can see all pages on sitemap.xml are scanned well.

Execute Lighthouse

All configuration of using Lighthouse is done. Now, open the package.json file and add the execution script like the following to prepare executing Lighthouse.

{
  "scripts": {
    "lighthouse": "lhci autorun"
  },
  "devDependencies": {
    "@lhci/cli": "^0.11.0"
  }
}

And then, execute the following command to run Lighthouse.

npm run lighthouse

If you don’t have the package.json file, you can execute the command of Lighthouse directly like the following.

lhci autorun
# npx lhci autorun

If there is no problem, you can see the .lighthouseci folder is created and the results stored in it. Also, you can see the result of Lighthouse on the terminal like the following.

...
  ✘  viewport failure for minScore assertion
       Does not have a `<meta name="viewport">` tag with `width` or `initial-scale`
       https://web.dev/viewport/

        expected: >=0.9
           found: 0
      all values: 0, 0, 0

You can fix them by seeing the results on the terminal or opening the result files(.html) in the .lighthouseci folder on the browser.

Execute on CI environment

Next, you can measure the performance with Lighthouse configured above on the CI environment.

GitHub Actions

Let’s see how to use GitHub Actions to execute Lighthouse. To execute Lightouse by GitHub Actions, make the .github/workflows/ci.yml file and modify it like the following.

name: CI
on: [push]
jobs:
  lighthouseci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 16
      - run: npm install
      - run: npm run lighthouse

If you don’t have the package.json file, modify the parts of npm install and npm run lighthouse like the following.

- run: npm install -g @lhci/cli
- run: lhci autorun

If you configured Lighthouse inspects the static files, you should add the build command to build the static files before executing the npm run lighthouse or lhci autorun commands.

GitLab CI

To execute Lighthosue on GitLab CI, make the .gitlab-ci.yml file and modify it like the following.

lighthouse:
  image: node:16-slim
  script:
    - apt-get update
    - apt-get install -y libgtk-3.0 libgbm-dev libnss3 libatk-bridge2.0-0 libasound2
    - npm install --global npm@latest
    - npm install
    - npm run lighthouse
  only:
    refs:
      - merge_requests

If you don’t have the package.json file, modify the parts of npm install and npm run lighthouse like the following.

- npm install -g @lhci/cli
- lhci autorun

If you configured Lighthouse inspects the static files, you should add the build command to build the static files before executing the npm run lighthouse or lhci autorun commands.

Basic authentication

When using Lighthouse to scan web pages with URL, sometimes web pages with Basic authentication enabled are needed to scan.

To use Lighthouse to inspect the web pages with Basic authentication enabled, you need to add some more configuration. First, execute the following command to install puppeteer.

npm install --save-dev puppeteer

And then, make the puppeteerScript.js file on the root directory of the project and modify it like the following.

const BASIC_AUTH_USER_NAME = process.env.BASIC_AUTH_USER_NAME;
const BASIC_AUTH_PASSWORD = process.env.BASIC_AUTH_PASSWORD;

async function main(browser, { url }) {
  const page = await browser.newPage();
  await page.authenticate({
    username: BASIC_AUTH_USER_NAME,
    password: BASIC_AUTH_PASSWORD,
  });
  await page.goto(url);
}

module.exports = main;

Store the ID and password of Basic authentication to the environment variables named BASIC_AUTH_USER_NAME and BASIC_AUTH_PASSWORD. And then, open the .lighthouserc.js file and configure puppeteer like the following.

module.exports = {
  ci: {
    collect: {
      url: [
        // Korean
        'https://deku.posstree.com/ko/flutter/',
        'https://deku.posstree.com/ko/react/',
        'https://deku.posstree.com/ko/react-native/',
        // English
        'https://deku.posstree.com/en/flutter/',
        'https://deku.posstree.com/en/react/',
        'https://deku.posstree.com/en/react-native/',
        // Japanese
        'https://deku.posstree.com/flutter/',
        'https://deku.posstree.com/react/',
        'https://deku.posstree.com/react-native/'
      ]
    },
    puppeteerScript: './puppeteerScript.js',
    puppeteerLaunchOptions: {
      defaultViewport: null,
      args: ['--no-sandbox'],
      headless: true,
    },
    assert: {
      preset: 'lighthouse:recommended',
    },
  },
};

After modifying it, you can see the page pages with Basic authentication enabled are scanned well by Lighthouse when you execute it.

Completed

Done! we’ve seen how to use Lighthouse CI to run Lightouse on the local or the CI environment. Fast web pages or web apps have a huge impact on user experience (UX). Also, The items measured by Lighthouse have a great impact on SEO, so I recommend that you use Lighthouse to improve the performance of your web pages or web apps.

Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!

App promotion

You can use the applications that are created by this blog writer Deku.
Deku created the applications with Flutter.

If you have interested, please try to download them for free.

Posts