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:
parent
5c3c672d26
commit
763ff2d1e4
12 changed files with 169 additions and 1 deletions
17
.changeset/tiny-snails-dance.md
Normal file
17
.changeset/tiny-snails-dance.md
Normal 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.
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
7
packages/astro/test/fixtures/minification-html/astro.config.mjs
vendored
Normal file
7
packages/astro/test/fixtures/minification-html/astro.config.mjs
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
compressHTML: true,
|
||||
|
||||
});
|
8
packages/astro/test/fixtures/minification-html/package.json
vendored
Normal file
8
packages/astro/test/fixtures/minification-html/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@test/minification-html",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
3
packages/astro/test/fixtures/minification-html/src/pages/aside.astro
vendored
Normal file
3
packages/astro/test/fixtures/minification-html/src/pages/aside.astro
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
---
|
||||
<div>2</div>
|
21
packages/astro/test/fixtures/minification-html/src/pages/index.astro
vendored
Normal file
21
packages/astro/test/fixtures/minification-html/src/pages/index.astro
vendored
Normal 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>
|
3
packages/astro/test/fixtures/minification-html/src/pages/page.astro
vendored
Normal file
3
packages/astro/test/fixtures/minification-html/src/pages/page.astro
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
---
|
||||
<div>3</div>
|
79
packages/astro/test/minification-html.test.js
Normal file
79
packages/astro/test/minification-html.test.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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':
|
||||
|
|
Loading…
Reference in a new issue