HTML minification (#6706)

* 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>
This commit is contained in:
wulinsheng123 2023-05-17 21:26:49 +08:00 committed by GitHub
parent 5c3c672d26
commit 763ff2d1e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 169 additions and 1 deletions

View file

@ -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.

View file

@ -466,6 +466,24 @@ export interface AstroUserConfig {
*/ */
site?: string; 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 * @docs
* @name base * @name base

View file

@ -37,6 +37,7 @@ export async function compile({
// use `sourcemap: "both"` so that sourcemap is included in the code // use `sourcemap: "both"` so that sourcemap is included in the code
// result passed to esbuild, but also available in the catch handler. // result passed to esbuild, but also available in the catch handler.
transformResult = await transform(source, { transformResult = await transform(source, {
compact: astroConfig.compressHTML,
filename, filename,
normalizedFilename: normalizeFilename(filename, astroConfig.root), normalizedFilename: normalizeFilename(filename, astroConfig.root),
sourcemap: 'both', sourcemap: 'both',

View file

@ -23,6 +23,7 @@ const ASTRO_CONFIG_DEFAULTS: AstroUserConfig & any = {
assets: '_astro', assets: '_astro',
serverEntry: 'entry.mjs', serverEntry: 'entry.mjs',
}, },
compressHTML: false,
server: { server: {
host: false, host: false,
port: 3000, port: 3000,
@ -72,6 +73,7 @@ export const AstroConfigSchema = z.object({
.default(ASTRO_CONFIG_DEFAULTS.cacheDir) .default(ASTRO_CONFIG_DEFAULTS.cacheDir)
.transform((val) => new URL(val)), .transform((val) => new URL(val)),
site: z.string().url().optional(), site: z.string().url().optional(),
compressHTML: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.compressHTML),
base: z.string().optional().default(ASTRO_CONFIG_DEFAULTS.base), base: z.string().optional().default(ASTRO_CONFIG_DEFAULTS.base),
trailingSlash: z trailingSlash: z
.union([z.literal('always'), z.literal('never'), z.literal('ignore')]) .union([z.literal('always'), z.literal('never'), z.literal('ignore')])
@ -225,6 +227,10 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: URL) {
.string() .string()
.default(ASTRO_CONFIG_DEFAULTS.srcDir) .default(ASTRO_CONFIG_DEFAULTS.srcDir)
.transform((val) => new URL(appendForwardSlash(val), fileProtocolRoot)), .transform((val) => new URL(appendForwardSlash(val), fileProtocolRoot)),
compressHTML: z
.boolean()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.compressHTML),
publicDir: z publicDir: z
.string() .string()
.default(ASTRO_CONFIG_DEFAULTS.publicDir) .default(ASTRO_CONFIG_DEFAULTS.publicDir)

View file

@ -79,7 +79,6 @@ export async function renderPage(
result._metadata.headInTree = result._metadata.headInTree =
result.componentMetadata.get((componentFactory as any).moduleId)?.containsHead ?? false; result.componentMetadata.get((componentFactory as any).moduleId)?.containsHead ?? false;
const pageProps: Record<string, any> = { ...(props ?? {}), 'server:root': true }; const pageProps: Record<string, any> = { ...(props ?? {}), 'server:root': true };
let output: ComponentIterable; let output: ComponentIterable;
let head = ''; let head = '';
try { try {

View file

@ -0,0 +1,7 @@
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
compressHTML: true,
});

View file

@ -0,0 +1,8 @@
{
"name": "@test/minification-html",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}

View file

@ -0,0 +1,3 @@
---
---
<div>2</div>

View file

@ -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>

View file

@ -0,0 +1,3 @@
---
---
<div>3</div>

View file

@ -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);
});
});
});

View file

@ -2810,6 +2810,12 @@ importers:
specifier: workspace:* specifier: workspace:*
version: link:../../.. version: link:../../..
packages/astro/test/fixtures/minification-html:
dependencies:
astro:
specifier: workspace:*
version: link:../../..
packages/astro/test/fixtures/multiple-renderers: packages/astro/test/fixtures/multiple-renderers:
dependencies: dependencies:
'@test/astro-renderer-one': '@test/astro-renderer-one':