Vite 7 and later use Baseline Widely Available as the default production build target.
Now put this in a Vite application:
Promise.withResolvers<number>();
document.startViewTransition(() => {
// Update the DOM.
});
Then run:
npx vite build
The build succeeds.
Regular TypeScript accepts the same code when the standard ESNext and DOM libraries are loaded:
npx tsc --noEmit --target ESNext --lib ESNext,DOM
# exit code 0
At the time of testing, however, neither API was Baseline Widely Available:
-
Promise.withResolvers()was Baseline 2024 Newly Available. -
Document.startViewTransition()was Baseline 2025 Newly Available.
That result is not a bug in Vite or TypeScript.
It exposes a gap between two different contracts:
| Layer | What it controls | What it does not control |
|---|---|---|
| Vite build target | Which JavaScript syntax must be transformed for the target browsers | Whether every runtime API you call is available |
TypeScript lib
|
Which global APIs exist in the type environment | Whether those APIs meet your Baseline policy |
| Baseline | Cross-browser availability of web platform features | Your build output or runtime polyfills |
A build target is not an API contract.
The 30-second version
- Use
typescript-baseline-libfor JavaScript built-ins such asPromise,Array, andMap. - Use
@baseline-types/dom-widely-availablefor browser APIs such asDocument,ResizeObserver, andfetch. - Load both from a dedicated TypeScript configuration and run it as a separate CI check.
The result is an ordinary tsc error. No additional source-code parser is involved.
Why the build passes
Vite describes its TypeScript support as transpile-only. It removes TypeScript syntax but does not run type checking by itself.
Its build target tells the transformer which JavaScript syntax needs to be lowered. It does not automatically reject every API that falls outside the corresponding Baseline target, and it does not generally inject API polyfills.
TypeScript checks APIs through declaration files such as lib.esnext.d.ts and lib.dom.d.ts. Those files model a selected ECMAScript or web platform surface. They are not filtered by Baseline status.
Both tools are doing their intended jobs. The missing layer is a declaration set that represents the API policy we want to enforce.
Add a Baseline API check
Install TypeScript and the two generated declaration packages:
npm install --save-dev \
typescript@^7 \
typescript-baseline-lib \
@baseline-types/dom-widely-available
Commit the lockfile. The package versions determine the exact Baseline snapshots evaluated in CI.
Keep your existing build configuration and add a dedicated TypeScript project.
tsconfig.baseline.json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noEmit": true,
"noLib": true,
"allowImportingTsExtensions": true,
"types": [
"typescript-baseline-lib",
"@baseline-types/dom-widely-available"
]
},
"include": [
"src/**/*.ts"
],
"exclude": [
"**/vite-env.d.ts"
]
}
The important options are:
{
"compilerOptions": {
"noLib": true,
"types": [
"typescript-baseline-lib",
"@baseline-types/dom-widely-available"
]
}
}
noLib removes TypeScript's standard ES and DOM libraries. The types entries replace them with the Baseline-oriented declarations.
Do not combine this configuration with the standard es* libraries. Doing so would reintroduce the APIs the gate is intended to remove.
I also recommend leaving skipLibCheck disabled. Declaration conflicts are useful signals here, not noise to suppress.
Why this config does not extend the build config
Many frontend projects already declare compilerOptions.lib.
Combining an inherited lib with noLib: true produces a configuration error. A standalone file is therefore the safest copy-paste starting point.
For a larger codebase, extract options such as strict, jsx, paths, and module resolution into a shared config that contains neither lib nor types. The normal build and Baseline check can then extend that shared configuration independently.
If the project contains TSX, add src/**/*.tsx to include and copy its existing jsx option into the Baseline config.
A typical Vite project contains: Vite's client declarations include asset modules and environment types, but they can also reference Worker APIs. Worker declarations are outside the scope of the DOM package used here. For a strict browser-only gate, exclude the existing If your application uses This avoids widening the Baseline type surface with unrelated Worker globals.Vite client declaration note
/// <reference types="vite/client" />
vite-env.d.ts and declare only the asset types your application uses:
// src/baseline-env.d.ts
declare module "*.css";
declare module "*.svg" {
const url: string;
export default url;
}
declare module "*.png" {
const url: string;
export default url;
}
import.meta.env, add only the required ImportMetaEnv declarations to the same file.
See what the gate catches
Add a mix of established and newer APIs:
const newestOdd = [2, 5, 8, 11].findLast(
(value) => value % 2 === 1,
);
const response = await fetch("https://example.com");
const observer = new ResizeObserver(() => {});
Promise.withResolvers<number>();
Array.fromAsync([1, 2, 3]);
new EyeDropper();
document.startViewTransition(() => {});
console.log(newestOdd, response.status, observer);
export {};
Run the dedicated check:
npx tsc -p tsconfig.baseline.json
The established APIs compile, while the APIs outside Widely Available are rejected:
error TS2550: Property 'withResolvers' does not exist on type 'PromiseConstructor'.
error TS2550: Property 'fromAsync' does not exist on type 'ArrayConstructor'.
error TS2304: Cannot find name 'EyeDropper'.
error TS2339: Property 'startViewTransition' does not exist on type 'Document'.
At the time of testing:
| API | Baseline status | Result |
|---|---|---|
Array.prototype.findLast() |
Widely Available | Accepted |
fetch() |
Widely Available | Accepted |
ResizeObserver |
Widely Available | Accepted |
Promise.withResolvers() |
2024 Newly Available | Rejected |
Array.fromAsync() |
2024 Newly Available | Rejected |
EyeDropper |
Limited availability | Rejected |
Document.startViewTransition() |
2025 Newly Available | Rejected |
Baseline status changes over time. These results were verified on July 23, 2026.
Turn it into a CI contract
Add a separate package script:
{
"scripts": {
"check:baseline": "tsc -p tsconfig.baseline.json"
}
}
If your existing CI job already installs dependencies, adding this command may be enough:
npm run check:baseline
A standalone GitHub Actions workflow looks like this:
# .github/workflows/baseline-types.yml
name: Baseline API check
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
baseline:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run check:baseline
For an existing project, introduce this as a separate check first. Review the initial findings, decide how each API should be handled, and only then make the check required.
That avoids turning a useful policy migration into an unexplained wall of CI failures.
Rolling target or fixed year?
A moving target is appropriate for applications that want to adopt APIs as they become Widely Available:
{
"compilerOptions": {
"types": [
"typescript-baseline-lib",
"@baseline-types/dom-widely-available"
]
}
}
The declaration set advances when the underlying compatibility data and packages are updated.
A fixed Baseline year is more suitable for a library contract, a long-lived release branch, or a product that updates its browser policy deliberately.
Install the matching DOM target:
npm install --save-dev @baseline-types/dom-2024
Then use the cumulative 2024 targets:
{
"compilerOptions": {
"noLib": true,
"types": [
"typescript-baseline-lib/year/2024",
"@baseline-types/dom-2024"
]
}
}
A year target contains APIs that became Baseline Newly Available by the end of that year. It is not the same as saying every API was already Widely Available during that year.
Do not combine a fixed year/* entry with the rolling root package.
Make polyfills explicit
Sometimes an API is outside the selected Baseline target, but your runtime intentionally provides it.
For example, core-js can provide Promise.withResolvers:
npm install core-js
Load the runtime implementation:
import "core-js/proposals/promise-with-resolvers";
Then add the matching audited declaration entry:
{
"compilerOptions": {
"noLib": true,
"types": [
"typescript-baseline-lib",
"typescript-baseline-lib/allow/promise-withresolvers",
"@baseline-types/dom-widely-available"
]
}
}
This permits only that API. The rest of the Baseline boundary remains unchanged.
The allow/* entry changes type availability only. It does not install or load a runtime polyfill.
That separation is intentional: the runtime implementation and the type-level exception should both be visible in review.
What this check does not guarantee
This is an API-surface gate, not a complete browser compatibility system.
It does not guarantee:
- That an API works without browser-specific bugs.
- That requirements such as HTTPS, permissions, or user activation are met.
- That your actual audience matches the Baseline browser population.
- That JavaScript syntax, CSS, or HTML features meet the same target.
- That dynamically accessed or deliberately untyped APIs will be detected.
The DOM declarations also preserve some supporting types to keep references valid. They should not be interpreted as a perfect rejection of every non-Baseline DOM symbol.
If your users include old embedded WebViews, managed enterprise browsers, or unusually long-lived devices, Baseline Widely Available may still be too broad. Browser analytics and runtime testing remain necessary.
For broader JavaScript checks, I also use eslint-plugin-baseline-js. It covers syntax and Web API cases that cannot be represented by a TypeScript declaration file.
Scope the check carefully in monorepos
noLib: true replaces the complete global TypeScript library.
That is useful for browser code, but it should not automatically be applied to every package in a monorepo.
For example, current Node.js declarations may depend on standard-library APIs that are newer than your selected Baseline target. A universal configuration combining browser, Node.js, test-runner, and framework globals can therefore produce dependency-level errors unrelated to your browser contract.
Run the gate over the source that actually shares the browser compatibility policy:
{
"include": [
"packages/browser/src/**/*.ts"
]
}
Keep separate build configurations for Node-only entry points, tooling, tests, and browser applications.
The boundary should follow the deployment environment, not the repository root.
Why generated declaration files?
Manually maintaining a list of APIs would become stale almost immediately.
typescript-baseline-lib generates its JavaScript declarations from the web-features compatibility dataset and TypeScript's own library declarations. The repository checks in its dataset snapshot, classification reports, declaration inventory, generated output, and audit results.
@baseline-types applies Baseline filtering to the generator used for TypeScript's DOM declarations.
The two projects deliberately cover different surfaces:
| Package | Surface |
|---|---|
typescript-baseline-lib |
JavaScript built-ins such as Promise, Array, Map, and Intl
|
@baseline-types/dom-* |
DOM and web platform APIs such as Document, Window, and ResizeObserver
|
The package lock then makes a given CI run reproducible.
Where this is going
typescript-baseline-lib is currently a public alpha. I recommend adopting it as an additional CI gate before making it the only TypeScript configuration for a production application.
The longer-term goal is first-class TypeScript support:
{
"compilerOptions": {
"lib": [
"baseline"
]
}
}
The corresponding TypeScript proposal is currently awaiting more real-world feedback.
If you try the package, the most useful feedback is concrete:
- Which project and TypeScript version did you test?
- Which API did the gate catch?
- Was the result expected?
- Did a framework or ambient type package introduce a conflict?
- Could the check remain enabled in CI?
Try it on one browser-only package:
npm install --save-dev \
typescript@^7 \
typescript-baseline-lib \
@baseline-types/dom-widely-available
npx tsc -p tsconfig.baseline.json
Browser compatibility policies should not depend on every reviewer remembering the support status of every API.
A small, reproducible CI check is a better contract.
Top comments (0)