diff --git a/packages/astro/src/runtime/server/hydration.ts b/packages/astro/src/runtime/server/hydration.ts index 2ffdc9144c..d543aeee6c 100644 --- a/packages/astro/src/runtime/server/hydration.ts +++ b/packages/astro/src/runtime/server/hydration.ts @@ -1,6 +1,6 @@ import type { AstroComponentMetadata } from '../../@types/astro'; import type { SSRElement, SSRResult } from '../../@types/astro'; -import { hydrationSpecifier, serializeListValue } from './util.js'; +import { hydrationSpecifier, serializeListValue, naiveMinify } from './util.js'; import serializeJavaScript from 'serialize-javascript'; // Serializes props passed into a component so that they can be reused during hydration. @@ -91,7 +91,8 @@ interface HydrateScriptOptions { export async function generateHydrateScript(scriptOptions: HydrateScriptOptions, metadata: Required): Promise { const { renderer, result, astroId, props } = scriptOptions; const { hydrate, componentUrl, componentExport } = metadata; - + const PROPS_PLACEHOLDER = `/*astro:PROPS*/`; + const HYDRATE_ARGS_PLACEHOLDER = `/*astro:HYDRATE_ARGS*/`; if (!componentExport) { throw new Error(`Unable to resolve a componentExport for "${metadata.displayName}"! Please open an issue.`); } @@ -107,19 +108,32 @@ export async function generateHydrateScript(scriptOptions: HydrateScriptOptions, ? `const [{ ${componentExport.value}: Component }, { default: hydrate }] = await Promise.all([import("${await result.resolve(componentUrl)}"), import("${await result.resolve( renderer.source )}")]); - return (el, children) => hydrate(el)(Component, ${serializeProps(props)}, children); + return (el, children) => hydrate(el)(Component, ${PROPS_PLACEHOLDER}, children); ` : `await import("${await result.resolve(componentUrl)}"); return () => {}; `; - + hydrationSource = ` + import setup from '${await result.resolve(hydrationSpecifier(hydrate))}'; + setup("${astroId}", {name:"${metadata.displayName}",${metadata.hydrateArgs ? `value:${HYDRATE_ARGS_PLACEHOLDER}` : ''}}, async () => { + ${hydrationSource} + }); + `; + // Minify output (we know all these identifiers) + // Important! Use placeholder comments to avoid + // minifying user content inside of strings + hydrationSource = naiveMinify(hydrationSource, { + setup: 'a', + hydrate: 'b', + Component: 'c', + children: 'd', + el: 'e', + }) + .replace(PROPS_PLACEHOLDER, serializeProps(props)) + .replace(HYDRATE_ARGS_PLACEHOLDER, JSON.stringify(metadata.hydrateArgs)); const hydrationScript = { props: { type: 'module', 'data-astro-component-hydration': true }, - children: `import setup from '${await result.resolve(hydrationSpecifier(hydrate))}'; -setup("${astroId}", {name:"${metadata.displayName}",${metadata.hydrateArgs ? `value: ${JSON.stringify(metadata.hydrateArgs)}` : ''}}, async () => { - ${hydrationSource} -}); -`, + children: hydrationSource, }; return hydrationScript; diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index aa3980b150..3641508503 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -460,7 +460,7 @@ export async function renderPage(result: SSRResult, Component: AstroComponentFac }); }); if (needsHydrationStyles) { - styles.push(renderElement('style', { props: { 'astro-style': true }, children: 'astro-root, astro-fragment { display: contents; }' })); + styles.push(renderElement('style', { props: { 'astro-style': true }, children: 'astro-root,astro-fragment{display:contents;}' })); } const links = Array.from(result.links) diff --git a/packages/astro/src/runtime/server/util.ts b/packages/astro/src/runtime/server/util.ts index 22f38f9708..43ce50f093 100644 --- a/packages/astro/src/runtime/server/util.ts +++ b/packages/astro/src/runtime/server/util.ts @@ -43,3 +43,17 @@ export function serializeListValue(value: any) { export function hydrationSpecifier(hydrate: string) { return `astro/client/${hydrate}.js`; } + +/** + * Minifies JS without parsing, just replacing whitespace and passed identifiers. + * @param source a string of JS + * @param identifiers any specific identifiers that should be replaced + * @returns a minified source string + */ +export function naiveMinify(source: string, identifiers: Record = {}): string { + source = source.trim().replace(/\s+/g, ' ').replace(/([^a-z])\s+/g, '$1').replace(/\s+([^a-zA-Z])/g, '$1'); + for (const [identifier, char] of Object.entries(identifiers)) { + source = source.replaceAll(identifier, char); + } + return source; +}