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 image ### 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 }); } }