From 11985c020fe1f7f7755494929c5d59f44a45e990 Mon Sep 17 00:00:00 2001
From: ottomated <31470743+ottomated@users.noreply.github.com>
Date: Sun, 31 May 2026 08:41:49 -0700
Subject: [PATCH] docs: desloppify browser support page (#18333)
The text in https://svelte.dev/docs/svelte/browser-support is quite
redundant and has a lot of AI smell. I did my best to make it more
concise and remove some slop comments in the generation script.
Requires a companion PR in svelte.dev:
https://github.com/sveltejs/svelte.dev/pull/2013
### Before submitting the PR, please make sure you do the following
- [ ] It's really useful if your PR references an issue where it is
discussed ahead of time. In many cases, features are absent for a
reason. For large changes, please create an RFC:
https://github.com/sveltejs/rfcs
- [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`.
- [x] This message body should clearly illustrate what problems it
solves.
- [ ] Ideally, include a test that fails without this PR but passes with
it.
- [x] If this PR changes code within `packages/svelte/src`, add a
changeset (`npx changeset`).
### Tests and linting
- [x] Run the tests with `pnpm test` and lint the project with `pnpm
lint`
---------
Co-authored-by: Rich Harris
---
.prettierignore | 1 +
.../.generated/browser-support-features.md | 8 +-
.../07-misc/.generated/browser-support.md | 20 +-
.../docs/07-misc/05-browser-support.md | 23 +--
.../scripts/generate-browser-support.ts | 175 +++++++++---------
5 files changed, 109 insertions(+), 118 deletions(-)
diff --git a/.prettierignore b/.prettierignore
index 92d9bc797b..28f447f359 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -8,6 +8,7 @@ packages/**/config/*.js
# packages/svelte
packages/svelte/messages/**/*.md
packages/svelte/scripts/_bundle.js
+packages/svelte/scripts/_baseline/*.ts
packages/svelte/src/compiler/errors.js
packages/svelte/src/compiler/warnings.js
packages/svelte/src/internal/client/errors.js
diff --git a/documentation/docs/07-misc/.generated/browser-support-features.md b/documentation/docs/07-misc/.generated/browser-support-features.md
index 1e1a00d4d3..1ed6da9e61 100644
--- a/documentation/docs/07-misc/.generated/browser-support-features.md
+++ b/documentation/docs/07-misc/.generated/browser-support-features.md
@@ -1,7 +1,7 @@
| Feature | Chrome/Edge | Firefox | Safari |
-| --- | ---: | ---: | ---: |
-| `$state.snapshot` | 98 | 94 | 15.4 |
-| `bind:devicePixelContentBoxSize` | — | 93 | not supported |
-| `flip` from `svelte/animate` | — | 126 | — |
\ No newline at end of file
+| - | - | - | - |
+| [`$state.snapshot`](/docs/svelte/$state#$state.snapshot) | 98 | 94 | 15.4 |
+| [`bind:devicePixelContentBoxSize`](/docs/svelte/bind#Dimensions) | — | 93 | not supported |
+| [`flip` from `svelte/animate`](/docs/svelte/svelte-animate#flip) | — | 126 | — |
diff --git a/documentation/docs/07-misc/.generated/browser-support.md b/documentation/docs/07-misc/.generated/browser-support.md
index 8c58588960..1e8b962856 100644
--- a/documentation/docs/07-misc/.generated/browser-support.md
+++ b/documentation/docs/07-misc/.generated/browser-support.md
@@ -1,13 +1,15 @@
-| Browser | Minimum version |
-| ---------------- | --------------- |
-| Chrome/Edge | 87 |
-| Firefox | 83 |
-| Safari | 14 |
-| Opera | 73 |
-| Opera (Android) | 62 |
-| Samsung Internet | 14.0 |
-| Android WebView | 87 |
+| Browser | Minimum version |
+| - | - |
+| Chrome/Edge | 87 |
+| Firefox | 83 |
+| Safari | 14 |
+| Opera | 73 |
+| Opera (Android) | 62 |
+| Samsung Internet | 14.0 |
+| Android WebView | 87 |
+| Internet Explorer | not supported |
+
> [!NOTE] This equates to a Baseline target of 2020.
\ No newline at end of file
diff --git a/documentation/docs/07-misc/05-browser-support.md b/documentation/docs/07-misc/05-browser-support.md
index 1a8830bdb9..735f3126de 100644
--- a/documentation/docs/07-misc/05-browser-support.md
+++ b/documentation/docs/07-misc/05-browser-support.md
@@ -2,29 +2,14 @@
title: Browser support
---
-The table below shows the minimum browser versions Svelte's runtime and compiled output are expected to work in.
+The table below shows the minimum browser versions Svelte is expected to work in, derived from the browser APIs used by Svelte's internal code.
@include .generated/browser-support.md
-These numbers describe what Svelte's output _requires_ in order to run — they're derived from the APIs the code uses, not from a list of browsers the team commits to testing.
+This table only covers Svelte itself. It does not include [SvelteKit](/docs/kit), other Svelte libraries, or your own code.
-## What is covered
+## Exceptions
-- **Svelte's runtime.** Everything you import from `svelte` or its subpackages, in the form your bundler ships to the browser.
-- **Compiler output.** The JavaScript the Svelte compiler emits from your `.svelte` files, including the DOM operations, bindings and transitions used in your components.
-
-## What is not covered
-
-- **Your own code** inside ``;
}
if (tag === 'input') {
- // `bind:checked` requires type="checkbox"|"radio"; `bind:group` too.
+ // `bind:checked` and `bind:group` require type="checkbox" | "radio"
const type =
name === 'checked' || name === 'indeterminate'
? ' type="checkbox"'
@@ -567,7 +563,6 @@ function binding_fixture(name: string, props: BindingProperty): string {
* Compile a `.svelte` fixture to JS (no-op for `.js` fixtures), then
* bundle the result through the shared `bundle` helper. Fixtures are tiny
* so circular-dep warnings from the Svelte runtime are silenced.
- *
*/
async function bundle_fixture(feature: Feature): Promise {
const entry_code =
@@ -612,6 +607,8 @@ async function find_all_conditional_features(
const features = enumerate_features(subpackage_exports);
const rows: ConditionalRow[] = [];
+ const missing_doc_links: string[] = [];
+
for (let i = 0; i < features.length; i++) {
const feature = features[i];
process.stdout.write(`\r ${i + 1}/${features.length} ${feature.name}`.padEnd(80));
@@ -645,25 +642,35 @@ async function find_all_conditional_features(
}
}
+ let doc_link = doc_links[feature.name];
+ if (doc_link === undefined) {
+ doc_link = null;
+ missing_doc_links.push(feature.name);
+ }
+
rows.push({
name: feature.name,
+ doc_link,
versions,
baseline_year: final_year
});
}
process.stdout.write('\n');
+ if (missing_doc_links.length) {
+ throw new Error(`Missing documentation url for some features.
+Add them to the \`doc_links\` map in \`scripts/generate-browser-support.ts\`, or add an explicit \`null\` if they don't have a documentation url.
+${missing_doc_links.map((name) => ` - "${name}"`).join('\n')}`);
+ }
+
return rows;
}
-/**
- * Render the per-feature browser-requirements table from the auto-detected
- * rows. Sorted by Safari floor descending, then alphabetically.
- */
-function render_conditional_table(rows: ConditionalRow[], runtime_floor: RuntimeFloor): string {
- if (rows.length === 0) {
+function render_conditional_table(features: ConditionalRow[], runtime_floor: RuntimeFloor): string {
+ if (features.length === 0) {
return '_No features currently require browser versions newer than the runtime floor._';
}
+ features.sort((a, b) => a.name.localeCompare(b.name));
const browsers = [
['chrome', 'Chrome/Edge'],
@@ -671,32 +678,25 @@ function render_conditional_table(rows: ConditionalRow[], runtime_floor: Runtime
['safari', 'Safari']
] as const;
- const runtime_versions = browser_versions_for(runtime_floor);
-
- const sorted = [...rows].sort((a, b) => {
- const sa = Number(a.versions.safari ?? '0');
- const sb = Number(b.versions.safari ?? '0');
- if (sb !== sa) return sb - sa;
- return a.name.localeCompare(b.name);
- });
-
- const header = '| Feature | ' + browsers.map(([, label]) => `${label}`).join(' | ') + ' |';
- const sep = '| --- |' + browsers.map(() => ' ---: |').join('');
+ const floor_versions = browser_versions_for(runtime_floor);
- const body = sorted.map((entry) => {
- const cells = browsers.map(([key]) => {
- const v = entry.versions[key];
+ const rows: string[][] = [];
+ for (const row of features) {
+ const name_cell = row.doc_link ? `[${row.name}](${row.doc_link})` : row.name;
+ const versions = browsers.map(([key]) => {
+ const v = row.versions[key];
if (v === null) return 'not supported';
if (v === undefined) return '—';
- const floor_v = runtime_versions[key];
- return floor_v && Number(v) <= Number(floor_v)
- ? '—'
- : v;
+ const floor_v = floor_versions[key];
+ if (floor_v && Number(v) <= Number(floor_v))
+ return '—';
+
+ return v;
});
- return `| ${entry.name} | ${cells.join(' | ')} |`;
- });
+ rows.push([name_cell, ...versions]);
+ }
- return [header, sep, ...body].join('\n');
+ return render_markdown_table(['Feature', ...browsers.map(([, label]) => `${label}`)], rows);
}
function browser_versions_for(target: RuntimeFloor): Record {
@@ -750,53 +750,59 @@ function browser_versions_for(target: RuntimeFloor): Record {
return lookup;
}
-function render_table(versions: Record, target: RuntimeFloor): string {
- // Chrome and Edge ship from the same engine and historically resolve to the
- // same Baseline version. Collapse them into one row when they match, but
- // fall back to listing them separately if they ever drift.
- const chrome_edge: [string, string] | null =
- versions.chrome && versions.chrome === versions.edge ? ['Chrome/Edge', versions.chrome] : null;
-
- const base_rows: Array<[string, string]> = chrome_edge
- ? [chrome_edge, ['Chrome (Android)', versions.chrome_android]]
- : [
- ['Chrome', versions.chrome],
- ['Chrome (Android)', versions.chrome_android],
- ['Edge', versions.edge]
- ];
-
- const rows = [
- ...base_rows,
- ['Firefox', versions.firefox],
- ['Firefox (Android)', versions.firefox_android],
- ['Safari', versions.safari],
- ['Safari (iOS)', versions.safari_ios],
- ['Opera', versions.opera],
- ['Opera (Android)', versions.opera_android],
- ['Samsung Internet', versions.samsunginternet_android],
- ['Android WebView', versions.webview_android]
- ].filter(([label, version]) => version !== undefined) as Array<[string, string]>;
-
- const headings = ['Browser', 'Minimum version'];
-
- const widths = headings.map((heading, i) =>
- Math.max(heading.length, ...rows.map((r) => String(r[i]).length))
- );
+const BROWSER = {
+ chrome: 'Chrome',
+ edge: 'Edge',
+ firefox: 'Firefox',
+ safari: 'Safari',
+ opera: 'Opera',
+ samsung_internet: 'Samsung Internet',
+ webview_android: 'Android WebView',
+ internet_explorer: 'Internet Explorer'
+};
- const pad = (s: string, n: number) => s + ' '.repeat(Math.max(0, n - s.length));
+function render_browser_table(versions: Record, target: RuntimeFloor): string {
+ const rows: Array<[string, string]> = [
+ [BROWSER.chrome, versions.chrome],
+ [`${BROWSER.chrome} (Android)`, versions.chrome_android]
+ ];
- const header = `| ${headings.map((heading, i) => pad(heading, widths[i])).join(' | ')} |`;
- const sep = `| ${widths.map((width) => '-'.repeat(width)).join(' | ')} |`;
- const body = rows
- .map(([a, b]) => `| ${pad(a, widths[0])} | ${pad(String(b), widths[1])} |`)
- .join('\n');
+ if (versions.chrome === versions.edge) {
+ rows[0][0] += `/${BROWSER.edge}`;
+ } else {
+ rows.push([BROWSER.edge, versions.edge]);
+ }
+
+ rows.push(
+ [BROWSER.firefox, versions.firefox],
+ [`${BROWSER.firefox} (Android)`, versions.firefox_android],
+ [BROWSER.safari, versions.safari],
+ [`${BROWSER.safari} (iOS)`, versions.safari_ios],
+ [BROWSER.opera, versions.opera],
+ [`${BROWSER.opera} (Android)`, versions.opera_android],
+ [BROWSER.samsung_internet, versions.samsunginternet_android],
+ [BROWSER.webview_android, versions.webview_android],
+ [BROWSER.internet_explorer, 'not supported']
+ );
const target_label = target === 'newly' ? '"newly available"' : target;
- return `${header}\n${sep}\n${body}\n\n> [!NOTE] This equates to a Baseline target of ${target_label}.`;
+ return (
+ render_markdown_table(
+ ['Browser', 'Minimum version'],
+ rows.filter(([, version]) => version !== undefined)
+ ) +
+ `\n\n> [!NOTE] This equates to a Baseline target of ${target_label}.`
+ );
+}
+
+function render_markdown_table(headers: string[], rows: string[][]): string {
+ return `| ${headers.join(' | ')} |
+| ${headers.map(() => '-').join(' | ')} |
+${rows.map((row) => `| ${row.join(' | ')} |`).join('\n')}
+`;
}
-/* eslint-disable no-console */
async function main() {
console.log('Preparing scratch directory…');
// Wipe and recreate so stale bundles can't leak into the next scan.
@@ -844,14 +850,11 @@ async function main() {
const versions = browser_versions_for(target);
console.log('Rewriting docs page…');
- generate('browser-support.md', render_table(versions, target));
+ generate('browser-support.md', render_browser_table(versions, target));
generate('browser-support-features.md', render_conditional_table(conditional_rows, target));
console.log('Done.');
} finally {
- // Ensure cleanup happens even on failure — otherwise leftover bundle
- // files in `scripts/_baseline/` get picked up by `pnpm lint` on the
- // next CI step and produce spurious naming/no-console errors.
fs.rmSync(tmp_dir, { recursive: true, force: true });
}
}