Skip to content

Commit

Permalink
HTML minification (#6706)
Browse files Browse the repository at this point in the history
* TDD pattern development

* add compact property when the user run pnpm run build

* add minification for pro

* fix yaml file collision

* fix yaml collision

* fix pageage file

* optimize unit test

* fix revert code

* fix comment

* update yaml

* fix default value

* add test for dev

* Update packages/astro/test/astro-minification-html.test.js

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* Update packages/astro/test/astro-minification-html.test.js

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* Update packages/astro/test/astro-minification-html.test.js

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* Update packages/astro/test/astro-minification-html.test.js

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* Update packages/astro/test/astro-minification-html.test.js

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* Update the docs to reflect it's opt-in

* Add tests for SSR

* Document how the tests remove the doctype line

* Expand on the changeset

* rename for slice -100

* Updates based on PR comments

* optimize description

* Update packages/astro/src/@types/astro.ts

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

---------

Co-authored-by: wuls <linsheng.wu@beantechs.com>
Co-authored-by: Matthew Phillips <matthew@skypack.dev>
Co-authored-by: Matthew Phillips <matthew@matthewphillips.info>
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
  • Loading branch information
6 people authored May 17, 2023
1 parent 5c3c672 commit 763ff2d
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 1 deletion.
17 changes: 17 additions & 0 deletions .changeset/tiny-snails-dance.md
Original file line number Diff line number Diff line change
@@ -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.
18 changes: 18 additions & 0 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/compile/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
6 changes: 6 additions & 0 deletions packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const ASTRO_CONFIG_DEFAULTS: AstroUserConfig & any = {
assets: '_astro',
serverEntry: 'entry.mjs',
},
compressHTML: false,
server: {
host: false,
port: 3000,
Expand Down Expand Up @@ -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')])
Expand Down Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion packages/astro/src/runtime/server/render/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig({
compressHTML: true,

});
8 changes: 8 additions & 0 deletions packages/astro/test/fixtures/minification-html/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@test/minification-html",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
---
<div>2</div>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
---
<div>3</div>
79 changes: 79 additions & 0 deletions packages/astro/test/minification-html.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 763ff2d

Please sign in to comment.