Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/compress-html-jsx-default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'astro': major
---

Makes `'jsx'` the default value for `compressHTML`

Astro now strips whitespace from your HTML using JSX rules by default, the same way frameworks like React do. Whitespace and line breaks around elements are removed, but meaningful whitespace within a single line — like a space between two inline elements — is preserved. To keep a space that would otherwise be removed, write it explicitly in your source, for example with `{" "}`.

This can change rendered output where whitespace between inline elements was previously meaningful. To keep Astro's earlier behavior, set `compressHTML: true` for HTML-aware compression, or `compressHTML: false` to preserve all whitespace.
2 changes: 1 addition & 1 deletion packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
"test:integration": "astro-scripts test \"test/*.test.ts\" --parallel --strip-types"
},
"dependencies": {
"@astrojs/compiler-rs": "^0.1.10",
"@astrojs/compiler-rs": "^0.2.2",
"@astrojs/internal-helpers": "workspace:*",
"@astrojs/markdown-satteri": "workspace:*",
"@astrojs/telemetry": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ export async function renderPath({
relativeLocation: locationSite,
from: fromPath,
});
if (config.compressHTML === true) {
if (config.compressHTML) {
body = body.replaceAll('\n', '');
}
if (route.type !== 'redirect') {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/config/schemas/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const ASTRO_CONFIG_DEFAULTS = {
devToolbar: {
enabled: true,
},
compressHTML: true,
compressHTML: 'jsx',
server: {
host: false,
port: 4321,
Expand Down
12 changes: 6 additions & 6 deletions packages/astro/src/types/public/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,22 +474,22 @@ export interface AstroUserConfig<
* @docs
* @name compressHTML
* @type {boolean | "jsx"}
* @default `true`
* @default `'jsx'`
* @description
*
* Controls how Astro handles whitespace in your HTML. This affects both development mode and the final build output.
*
* By default, Astro removes whitespace from your HTML, including line breaks, in a lossless manner from `.astro` components. Some whitespace may be preserved as needed to maintain the visual rendering of your HTML.
* Since v7.0, Astro applies by default the JSX whitespace rules used by frameworks like React. This removes whitespace and line breaks around elements, collapses multi-line text onto a single line, and preserves whitespace within a single line (e.g. a space between two inline elements). To keep a space that would otherwise be removed, include it explicitly in the source through constructs such as `{" "}`.
*
* Since 6.2.0, this option can also be set to `"jsx"`, Astro will apply the JSX whitespace stripping rules used by frameworks like React. Leading and trailing whitespace is only preserved when explicitly included in the source code through constructs such as `{" "}`, and is otherwise removed entirely.
* Setting this option to `true` instead removes whitespace, including line breaks, in a lossless manner from `.astro` components. Some whitespace may be preserved as needed to maintain the visual rendering of your HTML.
*
* Setting this option to false disables HTML compression and preserves all whitespace.
* Setting this option to `false` disables HTML compression and preserves all whitespace.
*
* ```js
* {
* compressHTML: false
* compressHTML: true
* // or:
* // compressHTML: 'jsx'
* // compressHTML: false
* }
* ```
*/
Expand Down
16 changes: 8 additions & 8 deletions packages/astro/test/astro-script-template-dedup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,26 @@ describe('Scripts inside template elements', () => {
const $ = cheerio.load(html);

// One script inside the <template> (inert), one outside (executes)
assert.equal($('script').length, 1);
assert.equal(countSubstring(html, '<script type="module">'), 1);
assert.equal($('script').length, 2);
assert.equal(countSubstring(html, '<script type="module">'), 2);
});

it('renders script outside template when component is used outside first, then in template', async () => {
const html = await fixture.readFile('/component-then-template/index.html');
const $ = cheerio.load(html);

// One script outside (executes, deduplicated normally), one inside the template
assert.equal($('script').length, 1);
assert.equal(countSubstring(html, '<script type="module">'), 1);
assert.equal($('script').length, 2);
assert.equal(countSubstring(html, '<script type="module">'), 2);
});

it('renders script outside nested templates', async () => {
const html = await fixture.readFile('/nested-templates/index.html');
const $ = cheerio.load(html);

// One script outside (executes), one inside the nested templates
assert.equal($('script').length, 1);
assert.equal(countSubstring(html, '<script type="module">'), 1);
assert.equal($('script').length, 2);
assert.equal(countSubstring(html, '<script type="module">'), 2);
});

it('deduplicates scripts outside template while keeping one inside', async () => {
Expand All @@ -57,7 +57,7 @@ describe('Scripts inside template elements', () => {

// Two components outside the template should still deduplicate to one script.
// The template gets its own (inert) copy. Total: 2 scripts.
assert.equal($('script').length, 1);
assert.equal(countSubstring(html, '<script type="module">'), 1);
assert.equal($('script').length, 2);
assert.equal(countSubstring(html, '<script type="module">'), 2);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineConfig } from 'astro/config';

// https://astro.build/config
// Intentionally does not set `compressHTML` so the default value is exercised.
export default defineConfig({});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@test/minification-html-default",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
---
<html lang="en">
<head>
<meta charset="utf-8" />
<title>default minification</title>
</head>
<body>
<p id="multi-line">
Hello
world
</p>
<div id="inline">
<span>hello</span>
<em>world</em>
</div>
<pre id="preserved">
keep
this
whitespace
</pre>
</body>
</html>
55 changes: 54 additions & 1 deletion packages/astro/test/minification-html.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,62 @@ describe('HTML minification (JSX mode)', () => {

it('should preserve inline text and elements on the same line', () => {
assert.ok(
html.includes('<span>hello</span><em>world</em>'),
html.includes('<span>hello</span> <em>world</em>'),
'inline elements should preserve spacing',
);
});
});
});

describe('HTML minification (default)', () => {
let html: string;
before(async () => {
// This fixture intentionally leaves `compressHTML` unset to exercise the default.
const fixture = await loadFixture({
root: './fixtures/minification-html-default/',
build: { inlineStylesheets: 'never' },
outDir: './dist/minification-html-default/',
});
await fixture.build();
html = await fixture.readFile('/index.html');
});

it('strips whitespace between inline elements, following JSX rules', () => {
// The discriminator: HTML-aware compression (`true`) keeps a single space
// here, while the default JSX rules remove it entirely.
assert.ok(
html.includes('<span>hello</span><em>world</em>'),
'whitespace between inline elements should be removed',
);
});

it('collapses multi-line text into a single line', () => {
assert.ok(html.includes('<p id="multi-line">Hello world</p>'));
});

it('preserves whitespace inside <pre> tags', () => {
assert.match(html, /<pre id="preserved">\n\s+keep\n\s+this\n\s+whitespace/);
});

it('removes every newline outside of <pre>', () => {
const withoutPre = html.replace(/<pre[\s\S]*?<\/pre>/g, '');
assert.equal(NEW_LINES.test(withoutPre), false);
});
});

describe('HTML minification (compressHTML: true differs from the default)', () => {
it('keeps a single space between inline elements', async () => {
const fixture = await loadFixture({
root: './fixtures/minification-html-default/',
build: { inlineStylesheets: 'never' },
outDir: './dist/minification-html-true/',
compressHTML: true,
});
await fixture.build();
const html = await fixture.readFile('/index.html');
assert.ok(
html.includes('<span>hello</span> <em>world</em>'),
'HTML-aware compression should preserve inline whitespace as a single space',
);
});
});
2 changes: 1 addition & 1 deletion packages/astro/test/serializeManifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('astro:config/client', () => {
format: 'directory',
},
trailingSlash: 'always',
compressHTML: true,
compressHTML: 'jsx',
site: 'https://example.com',
}),
);
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/test/units/manifest/serialized.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,10 @@ describe('serializedManifestPlugin - dev mode', () => {
});

describe('compressHTML', () => {
it('is true by default', async () => {
it("is 'jsx' by default", async () => {
const settings = await createBasicSettings({});
const manifest = await getManifest(settings);
assert.equal(manifest.compressHTML, true);
assert.equal(manifest.compressHTML, 'jsx');
});

it('is false when explicitly disabled', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/test/units/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export function createBasicPipeline(
adapterName?: string;
clientDirectives?: Map<string, string>;
inlinedScripts?: Map<string, string>;
compressHTML?: boolean;
compressHTML?: boolean | 'jsx';
i18n?: SSRManifest['i18n'];
middleware?: SSRManifest['middleware'];
routeCache?: RouteCache;
Expand Down
Loading
Loading