How to use Baseline in your project

1. Introduction

Baseline is an initiative that provides clearer messaging on which web features are interoperable and safe to use today. Thanks to advances in Baseline tooling, you can now use Baseline directly in your projects as a Browserslist query so that your toolchain can change the output of code based on the Baseline target you choose.

In this codelab, you'll learn how to use Baseline in a sample project, and how to configure it to select a specific Baseline target. You'll also get to observe how the project toolchain's output changes depending on the Baseline target you've selected.

2. Set up the demo on your local machine

First, go to your preferred terminal application, clone the demo repository, and then enter the project directory:

git clone git@github.com:GoogleChromeLabs/baseline-demos.git
cd baseline-demos/tooling/webpack-browserslist-config-baseline

At this point, the demo will already have Baseline integrated, but you'll want to check out a commit that starts you out from scratch:

git checkout 94f12e34

With the repository cloned, the demo can now be spun up. This project uses nvm to manage Node versioning. If you have a reasonably recent version of Node globally installed, you probably don't need to complete this step—but if you do use nvm, run the following commands:

nvm install
nvm use

From here, install the project's packages:

npm install

If you want to see the demo, run the following command:

npm start

Then navigate to https://localhost:8080. The demo itself is a list of cards that is filterable using a form field at the top of the page. The app itself uses a mix of features that have reached a Baseline threshold.

When finished with the demo, go to your terminal and press Ctrl+C to stop running the demo at any time.

3. How to integrate Baseline into the project

This demo doesn't specify a Browserslist configuration at the start. Browserslist is a compact querying syntax that tells toolchains what minimum browser versions must be supported. For example, using a query of last 3 years will specify a wide range of targets. In this demo, we'll use an npm package named browserslist-config-baseline to specify a Browserslist query that aligns with Baseline targets you can use in your toolchain.

To get started, install browserslist-config-baseline like so:

npm install browserslist-config-baseline --save-dev

When installed, this package lets you specify an extends Browserslist query in your project that resolves to a list of Baseline browsers. These Baseline targets can be one of the following:

  • Moving targets, which update over time as new browsers are released:
    • Baseline Newly available, which aligns interoperable features implemented across the core browser set any time from the present, to 30 months ago.
    • Baseline Widely available, which includes interoperable features that have been implemented across the core browser set for 30 or more months ago.
  • Fixed targets, which represent browser versions at a fixed point in time. These are expressed as years from 2016 to the current year.

To start, we'll use browserslist-config-baseline to select the moving Baseline Widely available target for the project. To do this, open package.json and add the following:

"browserslist": "extends browserslist-config-baseline"

4. Observe changes in code output by selecting different Baseline targets

You just selected Baseline Widely available as a target for the demo project. Next, you'll want to build the project:

npm run build

There's a lot of extra output because the debug option for @babel/preset-env is specified as true in the project's babel.config.js. First, note the size of the CSS and JavaScript in the bundler stats:

assets by status 213 KiB [emitted]
  asset js/home.5f3c5480.js 208 KiB [emitted] [immutable] [minimized] (name: home) 2 related assets
  asset css/home.20db50ef.css 3.64 KiB [emitted] [immutable] (name: home) 1 related asset
  asset index.html 564 bytes [emitted]

Note here that the JavaScript bundle is 208 KiB, and the CSS is 3.64 KiB. Because this project uses core-js for JavaScript polyfills and autoprefixer to apply vendor-specific prefixes for CSS properties that are not yet fully interoperable. Both core-js and autoprefixer are affected by browserslist-config-baseline.

Another thing in the output to pay attention to is how your Browserslist query for Baseline Widely available is translated into a Browserslist query. In your project, that will look something similar to this:

Using targets: {
  "chrome": "108",
  "edge": "108",
  "firefox": "108",
  "ios": "16",
  "safari": "16"
}

Note the polyfills injected by core-js in the build output:

The corejs3 polyfill added the following polyfills:
  es.iterator.constructor { "chrome":"108", "edge":"108", "firefox":"108", "ios":"16", "safari":"16" }
  es.iterator.filter { "chrome":"108", "edge":"108", "firefox":"108", "ios":"16", "safari":"16" }
  es.iterator.map { "chrome":"108", "edge":"108", "firefox":"108", "ios":"16", "safari":"16" }

This output can change if you change your Baseline target. Say your application must support a much older set of browsers because of a more strict SLA. If that would be the case for you, you'd likely select a more conservative target. In your package.json file, change the Browserslist config to reflect the following:

"browserslist": "extends browserslist-config-baseline/2016"

This selects Baseline 2016 as the target, and will be translated to a Browerslist query. You can note the differences in the toolchain output after you re-run the build:

npm run build

First, note the change in file size for the project's JavaScript and CSS in the bundler stats:

assets by status 237 KiB [emitted]
  asset js/home.b228612d.js 232 KiB [emitted] [immutable] [minimized] (name: home) 2 related assets
  asset css/home.0c3e4fd7.css 3.91 KiB [emitted] [immutable] (name: home) 1 related asset
  asset index.html 564 bytes [emitted]

You'll note that the JavaScript bundle has increased in size by nearly 30 KiB. The project's CSS is only slightly larger, owing to autoprefixer adding more vendor prefixes based on the 2016 Baseline target. Also note the change in the Browserslist query:

Using targets: {
  "chrome": "53",
  "edge": "14",
  "firefox": "49",
  "ios": "10",
  "safari": "10"
}

Compared to the Baseline Widely available target, these browser versions are much, much earlier—early enough that the version of Edge being targeted in this case is pre-Chromium.

The polyfills of injected by core-js also will change, which is considerably more than when Baseline Widely available was selected as the target:

The corejs3 polyfill added the following polyfills:
  es.array.filter { "edge":"14" }
  es.iterator.constructor { "chrome":"53", "edge":"14", "firefox":"49", "ios":"10", "safari":"10" }
  es.iterator.filter { "chrome":"53", "edge":"14", "firefox":"49", "ios":"10", "safari":"10" }
  es.object.to-string { "edge":"14", "firefox":"49" }
  es.array.includes { "firefox":"49" }
  es.string.includes { "edge":"14" }
  es.array.map { "firefox":"49" }
  es.iterator.map { "chrome":"53", "edge":"14", "firefox":"49", "ios":"10", "safari":"10" }
  es.symbol { "edge":"14", "firefox":"49" }
  es.symbol.description { "chrome":"53", "edge":"14", "firefox":"49", "ios":"10", "safari":"10" }
  es.array.iterator { "chrome":"53", "edge":"14", "firefox":"49" }
  web.dom-collections.iterator { "chrome":"53", "edge":"14", "firefox":"49", "ios":"10", "safari":"10" }
  es.array.push { "chrome":"53", "edge":"14", "firefox":"49", "ios":"10", "safari":"10" }
  es.regexp.to-string { "edge":"14" }
  es.array.from { "edge":"14", "firefox":"49" }
  es.regexp.exec { "chrome":"53", "edge":"14", "firefox":"49", "ios":"10", "safari":"10" }
  es.regexp.test { "edge":"14" }
  es.error.cause { "chrome":"53", "edge":"14", "firefox":"49", "ios":"10", "safari":"10" }

The takeaway here is that your Baseline target can have a significant impact in how your application is transformed by your project's toolchain; The application in this example is very basic, with not a lot of cutting-edge developer experience features in either React or the application itself. For considerably more complex applications, you could expect much different outcomes—possibly even more in the way of added polyfills, transforms, and other sources of additional code to conform to the Baseline target you choose.

5. Targeting downstream browsers with browserslist-config-baseline

To review, the core browser set that Baseline targets includes the following browsers:

  • Chrome
  • Chrome on Android
  • Firefox
  • Firefox on Android
  • Edge
  • Safari on macOS
  • Safari on iOS

However, you can target what are known as "downstream browsers". These browsers are those whose engines are derived from a browser in the core browser set—most often Chromium. These include browsers such as Opera, Samsung Internet, and others. You can configure browserslist-config-baseline to target them in your package.json file like so:

"browserslist": "extends browserslist-config-baseline/with-downstream"

This targets downstream browsers in accordance with the Baseline Widely available target. To see how this resolves to a Browserslist query, rebuild the project:

npm start

Then note the change in the Browserslist query:

Using targets: {
  "android": "108",
  "chrome": "108",
  "edge": "108",
  "firefox": "108",
  "ios": "16",
  "opera": "94",
  "opera_mobile": "80",
  "safari": "16",
  "samsung": "21"
}

You can also target downstream browsers by year. For example:

"browserslist": "extends browserslist-config-baseline/with-downstream/2016"

With this configuration, your Browserslist query will change accordingly:

Using targets: {
  "android": "53",
  "chrome": "53",
  "edge": "14",
  "firefox": "49",
  "ios": "10",
  "opera": "40",
  "opera_mobile": "80",
  "safari": "10",
  "samsung": "6.2"
}

6. Linters and other tools

browserslist-config-baseline is a convenient tool for bundlers and other parts of your toolchain, but there's also value in other tools, such as linters which have adopted Baseline targets as a part of their configuration.

A good example of linter support for Baseline includes ESLint, which as a part of its CSS linting, provides a use-baseline rule using @eslint/css that lets you target either Baseline Newly, Baseline Widely available, or Baseline years. There's also a similar rule in the community @html-eslint/eslint-plugin package that lets you do the same for HTML features in your eslint.config.js file:

export default [
  /* Omitted JS linting rules ... */
  // Lint CSS files for Baseline:
  {
    files: ["**/*.css"],
    plugins: {
      css
    },
    language: "css/css",
    rules: {
      "css/no-duplicate-imports": "error",
      // Lint CSS files to make sure they are using
      // only Baseline Widely available features:
      "css/use-baseline": ["warn", {
        available: "widely"
      }]
    },
  },
  // Lint HTML and JSX files for Baseline:
  {
    files: ["**/*.html"],
    ...html.configs["flat/recommended"],
    rules: {
      // Lint HTML files to make sure they are using
      // only Baseline Widely available features:
      "@html-eslint/use-baseline": ["warn", {
        available: "widely"
      }]
    }
  }
];

There are a few things to note in this configuration:

  1. Both the HTML and CSS linting packages use a use-baseline rule, and it is set to Widely available using the available: "widely" configuration option.
  2. For both linting packages, the log level for linter violations is set to "warn". This can be set to "error" to drop out with an error code to prevent a build from occurring.

You may have seen the linter output when running npm run build before, but to see the linter output by itself, you can run the following:

npm run lint

The output you'll see highlights several warnings in the project's CSS:

/var/www/baseline-demos/tooling/webpack-browserslist-config-baseline/src/css/normalize.css
  222:3  warning  Property 'outline' is not a widely available baseline feature  css/use-baseline
/var/www/baseline-demos/tooling/webpack-browserslist-config-baseline/src/css/styles.css
  62:3   warning  Property 'outline' is not a widely available baseline feature                                css/use-baseline
  81:23  warning  Value 'subgrid' of property 'grid-template-rows' is not a widely available baseline feature  css/use-baseline

7. Wrapping up

As you can see, using browserslist-config-baseline in your project does require some understanding of underlying build tools and Browserslist, but the act of placing it into your projects should be possible with a little work. The primary benefit of using it is that you no longer need to think about the browsers you support in terms of version numbers, but rather a Baseline target that does the heavy lifting for you.

Additionally, there is a version of this demo that runs on Rollup, and this Codelab can largely be followed using that demo as well.

Baseline support is also beginning to appear in other bundling tools. Vite, for example, which uses Rollup under the hood, now targets Baseline Widely available browsers by default since version 7.

However you decide to go about it, introducing browserslist-config-baseline into your project's toolchain— and selecting a Baseline target that works for you—you can target browsers in a simpler and more reliable way.