summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/tiny-snails-dance.md17
-rw-r--r--packages/astro/src/@types/astro.ts18
-rw-r--r--packages/astro/src/core/compile/compile.ts1
-rw-r--r--packages/astro/src/core/config/schema.ts6
-rw-r--r--packages/astro/src/runtime/server/render/page.ts1
-rw-r--r--packages/astro/test/fixtures/minification-html/astro.config.mjs7
-rw-r--r--packages/astro/test/fixtures/minification-html/package.json8
-rw-r--r--packages/astro/test/fixtures/minification-html/src/pages/aside.astro3
-rw-r--r--packages/astro/test/fixtures/minification-html/src/pages/index.astro21
-rw-r--r--packages/astro/test/fixtures/minification-html/src/pages/page.astro3
-rw-r--r--packages/astro/test/minification-html.test.js79
-rw-r--r--pnpm-lock.yaml6
12 files changed, 169 insertions, 1 deletions
diff --git a/.changeset/tiny-snails-dance.md b/.changeset/tiny-snails-dance.md
new file mode 100644
index 000000000..ef25e0459
--- /dev/null
+++ b/.changeset/tiny-snails-dance.md
@@ -0,0 +1,17 @@
+---
+'astro': minor
+---
+
+Adds an opt-in way to minify the HTML output.
+
+Using the `compressHTML` option Astro will remove whitespace from Astro components. This only applies to components written in `.astro` format and happens in the compiler to maximize performance. You can enable with:
+
+```js
+import { defineConfig } from 'astro/config';
+
+export default defineConfig({
+ compressHTML: true
+});
+```
+
+Compression occurs both in development mode and in the final build.
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 19606b070..2441238aa 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -466,6 +466,24 @@ export interface AstroUserConfig {
*/
site?: string;
+
+ /**
+ * @docs
+ * @name compressHTML
+ * @type {boolean}
+ * @default `false`
+ * @description
+ * This is an option to minify your HTML output and reduce the size of your HTML files. When enabled, Astro removes all whitespace from your HTML, including line breaks, from `.astro` components. This occurs both in development mode and in the final build.
+ * To enable this, set the `compressHTML` flag to `true`.
+ *
+ * ```js
+ * {
+ * compressHTML: true
+ * }
+ * ```
+ */
+ compressHTML?: boolean;
+
/**
* @docs
* @name base
diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts
index 7554ec119..4c76c4c65 100644
--- a/packages/astro/src/core/compile/compile.ts
+++ b/packages/astro/src/core/compile/compile.ts
@@ -37,6 +37,7 @@ export async function compile({
// use `sourcemap: "both"` so that sourcemap is included in the code
// result passed to esbuild, but also available in the catch handler.
transformResult = await transform(source, {
+ compact: astroConfig.compressHTML,
filename,
normalizedFilename: normalizeFilename(filename, astroConfig.root),
sourcemap: 'both',
diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts
index 15dcd7bfd..2948b2fd9 100644
--- a/packages/astro/src/core/config/schema.ts
+++ b/packages/astro/src/core/config/schema.ts
@@ -23,6 +23,7 @@ const ASTRO_CONFIG_DEFAULTS: AstroUserConfig & any = {
assets: '_astro',
serverEntry: 'entry.mjs',
},
+ compressHTML: false,
server: {
host: false,
port: 3000,
@@ -72,6 +73,7 @@ export const AstroConfigSchema = z.object({
.default(ASTRO_CONFIG_DEFAULTS.cacheDir)
.transform((val) => new URL(val)),
site: z.string().url().optional(),
+ compressHTML: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.compressHTML),
base: z.string().optional().default(ASTRO_CONFIG_DEFAULTS.base),
trailingSlash: z
.union([z.literal('always'), z.literal('never'), z.literal('ignore')])
@@ -225,6 +227,10 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: URL) {
.string()
.default(ASTRO_CONFIG_DEFAULTS.srcDir)
.transform((val) => new URL(appendForwardSlash(val), fileProtocolRoot)),
+ compressHTML: z
+ .boolean()
+ .optional()
+ .default(ASTRO_CONFIG_DEFAULTS.compressHTML),
publicDir: z
.string()
.default(ASTRO_CONFIG_DEFAULTS.publicDir)
diff --git a/packages/astro/src/runtime/server/render/page.ts b/packages/astro/src/runtime/server/render/page.ts
index ce2d165a7..f4a02a75f 100644
--- a/packages/astro/src/runtime/server/render/page.ts
+++ b/packages/astro/src/runtime/server/render/page.ts
@@ -79,7 +79,6 @@ export async function renderPage(
result._metadata.headInTree =
result.componentMetadata.get((componentFactory as any).moduleId)?.containsHead ?? false;
const pageProps: Record<string, any> = { ...(props ?? {}), 'server:root': true };
-
let output: ComponentIterable;
let head = '';
try {
diff --git a/packages/astro/test/fixtures/minification-html/astro.config.mjs b/packages/astro/test/fixtures/minification-html/astro.config.mjs
new file mode 100644
index 000000000..4cc49df62
--- /dev/null
+++ b/packages/astro/test/fixtures/minification-html/astro.config.mjs
@@ -0,0 +1,7 @@
+import { defineConfig } from 'astro/config';
+
+// https://astro.build/config
+export default defineConfig({
+ compressHTML: true,
+
+});
diff --git a/packages/astro/test/fixtures/minification-html/package.json b/packages/astro/test/fixtures/minification-html/package.json
new file mode 100644
index 000000000..01c5bd4b8
--- /dev/null
+++ b/packages/astro/test/fixtures/minification-html/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@test/minification-html",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/astro/test/fixtures/minification-html/src/pages/aside.astro b/packages/astro/test/fixtures/minification-html/src/pages/aside.astro
new file mode 100644
index 000000000..3a388c276
--- /dev/null
+++ b/packages/astro/test/fixtures/minification-html/src/pages/aside.astro
@@ -0,0 +1,3 @@
+---
+---
+<div>2</div>
diff --git a/packages/astro/test/fixtures/minification-html/src/pages/index.astro b/packages/astro/test/fixtures/minification-html/src/pages/index.astro
new file mode 100644
index 000000000..8feb4e2e0
--- /dev/null
+++ b/packages/astro/test/fixtures/minification-html/src/pages/index.astro
@@ -0,0 +1,21 @@
+---
+import Aside from './aside.astro'
+import Page from './page.astro'
+---
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width" />
+ <title>minimum html</title>
+ <style>
+ .body{
+ background: red;
+ }
+ </style>
+ </head>
+ <body>
+ <Aside/>
+ <main></main>
+ <Page></Page>
+ </body>
+</html>
diff --git a/packages/astro/test/fixtures/minification-html/src/pages/page.astro b/packages/astro/test/fixtures/minification-html/src/pages/page.astro
new file mode 100644
index 000000000..c8976bc03
--- /dev/null
+++ b/packages/astro/test/fixtures/minification-html/src/pages/page.astro
@@ -0,0 +1,3 @@
+---
+---
+<div>3</div>
diff --git a/packages/astro/test/minification-html.test.js b/packages/astro/test/minification-html.test.js
new file mode 100644
index 000000000..d53ab5003
--- /dev/null
+++ b/packages/astro/test/minification-html.test.js
@@ -0,0 +1,79 @@
+import { expect } from 'chai';
+import { loadFixture } from './test-utils.js';
+import testAdapter from './test-adapter.js';
+
+const NEW_LINES = /[\r\n]+/gm;
+
+/**
+ * The doctype declaration is on a line between the rest of the HTML.
+ * This function removes the doctype so that we can check if the rest of the HTML is without
+ * whitespace.
+ */
+function removeDoctypeLine(html) {
+ return html.slice(20);
+}
+
+/**
+ * In the dev environment, two more script tags will be injected than in the production environment
+ * so that we can check if the rest of the HTML is without whitespace
+ */
+function removeDoctypeLineInDev(html){
+ return html.slice(-100)
+}
+
+describe('HTML minification', () => {
+ describe('in DEV enviroment', () => {
+ let fixture;
+ let devServer;
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/minification-html/',
+ });
+ devServer = await fixture.startDevServer();
+ });
+
+ after(async () => {
+ devServer.stop();
+ });
+
+ it('should emit compressed HTML in the emitted file', async () => {
+ let res = await fixture.fetch(`/`);
+ expect(res.status).to.equal(200);
+ const html = await res.text();
+ expect(NEW_LINES.test(removeDoctypeLineInDev(html))).to.equal(false);
+ });
+ });
+
+ describe('Build SSG', () => {
+ let fixture;
+ before(async () => {
+ fixture = await loadFixture({ root: './fixtures/minification-html/' });
+ await fixture.build();
+ });
+
+ it('should emit compressed HTML in the emitted file', async () => {
+ const html = await fixture.readFile('/index.html');
+ expect(NEW_LINES.test(removeDoctypeLine(html))).to.equal(false);
+ });
+ });
+
+ describe('Build SSR', () => {
+ let fixture;
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/minification-html/',
+ output: 'server',
+ adapter: testAdapter()
+ });
+ await fixture.build();
+ });
+
+ it('should emit compressed HTML in the emitted file', async () => {
+ const app = await fixture.loadTestAdapterApp();
+ const request = new Request('http://example.com/');
+ const response = await app.render(request);
+ const html = await response.text();
+ expect(NEW_LINES.test(removeDoctypeLine(html))).to.equal(false);
+ });
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8415bb973..dc7a6d5f9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2810,6 +2810,12 @@ importers:
specifier: workspace:*
version: link:../../..
+ packages/astro/test/fixtures/minification-html:
+ dependencies:
+ astro:
+ specifier: workspace:*
+ version: link:../../..
+
packages/astro/test/fixtures/multiple-renderers:
dependencies:
'@test/astro-renderer-one':