Gets hydration totally working

This commit is contained in:
Matthew Phillips 2021-12-22 12:13:36 -05:00
parent d22734c6e8
commit 82d02aa968
9 changed files with 106 additions and 25 deletions

View file

@ -6,6 +6,7 @@
"dev": "astro dev --experimental-static-build",
"start": "astro dev",
"build": "astro build --experimental-static-build",
"scan-build": "astro build",
"preview": "astro preview"
},
"devDependencies": {

View file

@ -0,0 +1,20 @@
---
import Greeting from '../components/Greeting.vue';
export async function getStaticPaths() {
const response = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=2000`);
const result = await response.json();
const allPokemon = result.results;
return allPokemon.map(pokemon => ({params: {pokemon: pokemon.name}, props: {pokemon}}));
}
---
<html lang="en">
<head>
<title>Hello</title>
</head>
<body>
<h1>{Astro.props.pokemon.name}</h1>
<Greeting client:load />
</body>
</html>

View file

@ -9,7 +9,9 @@ import type { BuildInternals } from '../../core/build/internal.js';
import type { AstroComponentFactory } from '../../runtime/server';
import fs from 'fs';
import npath from 'path';
import { fileURLToPath } from 'url';
import glob from 'fast-glob';
import vite from '../vite.js';
import { debug, info, error } from '../../core/logger.js';
import { createBuildInternals } from '../../core/build/internal.js';
@ -30,8 +32,11 @@ export interface StaticBuildOptions {
export async function staticBuild(opts: StaticBuildOptions) {
const { allPages, astroConfig } = opts;
// The pages
const pageInput = new Set<string>();
// The JavaScript entrypoints.
const jsInput: Set<string> = new Set();
const jsInput = new Set<string>();
// A map of each page .astro file, to the PageBuildData which contains information
// about that page, such as its paths.
@ -39,12 +44,13 @@ export async function staticBuild(opts: StaticBuildOptions) {
for (const [component, pageData] of Object.entries(allPages)) {
const [renderers, mod] = pageData.preload;
const metadata = mod.$$metadata;
const topLevelImports = new Set([
// Any component that gets hydrated
...mod.$$metadata.hydratedComponentPaths(),
...metadata.hydratedComponentPaths(),
// Any hydration directive like astro/client/idle.js
...mod.$$metadata.hydrationDirectiveSpecifiers(),
...metadata.hydrationDirectiveSpecifiers(),
// The client path for each renderer
...renderers.filter(renderer => !!renderer.source).map(renderer => renderer.source!),
]);
@ -54,18 +60,22 @@ export async function staticBuild(opts: StaticBuildOptions) {
}
let astroModuleId = new URL('./' + component, astroConfig.projectRoot).pathname;
jsInput.add(astroModuleId);
pageInput.add(astroModuleId);
facadeIdToPageDataMap.set(astroModuleId, pageData);
}
// Build internals needed by the CSS plugin
const internals = createBuildInternals();
// Perform the SSR build
const result = (await ssrBuild(opts, internals, jsInput)) as RollupOutput;
// Run the SSR build and client build in parallel
const [ssrResult] = await Promise.all([
ssrBuild(opts, internals, pageInput),
clientBuild(opts, internals, jsInput)
]) as RollupOutput[];
// Generate each of the pages.
await generatePages(result, opts, internals, facadeIdToPageDataMap);
await generatePages(ssrResult, opts, internals, facadeIdToPageDataMap);
await cleanSsrOutput(opts);
}
async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, input: Set<string>) {
@ -76,7 +86,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
mode: 'production',
build: {
emptyOutDir: true,
minify: false, // 'esbuild', // significantly faster than "terser" but may produce slightly-bigger bundles
minify: false,
outDir: fileURLToPath(astroConfig.dist),
ssr: true,
rollupOptions: {
@ -88,7 +98,42 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
target: 'es2020', // must match an esbuild target
},
plugins: [
vitePluginNewBuild(input, internals),
vitePluginNewBuild(input, internals, 'mjs'),
rollupPluginAstroBuildCSS({
internals,
}),
...(viteConfig.plugins || []),
],
publicDir: viteConfig.publicDir,
root: viteConfig.root,
envPrefix: 'PUBLIC_',
server: viteConfig.server,
base: astroConfig.buildOptions.site ? new URL(astroConfig.buildOptions.site).pathname : '/',
});
}
async function clientBuild(opts: StaticBuildOptions, internals: BuildInternals, input: Set<string>) {
const { astroConfig, viteConfig } = opts;
return await vite.build({
logLevel: 'error',
mode: 'production',
build: {
emptyOutDir: false,
minify: 'esbuild',
outDir: fileURLToPath(astroConfig.dist),
rollupOptions: {
input: Array.from(input),
output: {
format: 'esm',
},
preserveEntrySignatures: 'exports-only',
},
target: 'es2020', // must match an esbuild target
},
plugins: [
vitePluginNewBuild(input, internals, 'js'),
rollupPluginAstroBuildCSS({
internals,
}),
@ -166,7 +211,7 @@ async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: G
mod,
});
info(logging, 'generate', `Generating: ${pathname}`);
debug(logging, 'generate', `Generating: ${pathname}`);
const result = createResult({ astroConfig, origin, params, pathname, renderers });
result.links = new Set<SSRElement>(
@ -183,8 +228,9 @@ async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: G
if(typeof hashedFilePath !== 'string') {
throw new Error(`Cannot find the built path for ${specifier}`);
}
console.log("WE GOT", hashedFilePath)
return hashedFilePath;
const relPath = npath.posix.relative(pathname, '/' + hashedFilePath);
const fullyRelativePath = relPath[0] === '.' ? relPath : './' + relPath;
return fullyRelativePath;
};
let html = await renderPage(result, Component, pageProps, null);
@ -197,7 +243,19 @@ async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: G
}
}
export function vitePluginNewBuild(input: Set<string>, internals: BuildInternals): VitePlugin {
async function cleanSsrOutput(opts: StaticBuildOptions) {
// The SSR output is all .mjs files, the client output is not.
const files = await glob('**/*.mjs', {
cwd: opts.astroConfig.dist.pathname,
//ignore: ['node_modules/**'].concat(filePathsToIgnore.map((ignore) => `${ignore}/**`)),
});
await Promise.all(files.map(async filename => {
const url = new URL(filename, opts.astroConfig.dist);
await fs.promises.rm(url);
}));
}
export function vitePluginNewBuild(input: Set<string>, internals: BuildInternals, ext: 'js' | 'mjs'): VitePlugin {
return {
name: '@astro/rollup-plugin-new-build',
@ -213,10 +271,10 @@ export function vitePluginNewBuild(input: Set<string>, internals: BuildInternals
outputOptions(outputOptions) {
Object.assign(outputOptions, {
entryFileNames(_chunk: PreRenderedChunk) {
return 'assets/[name].[hash].mjs';
return 'assets/[name].[hash].' + ext;
},
chunkFileNames(_chunk: PreRenderedChunk) {
return 'assets/[name].[hash].mjs';
return 'assets/[name].[hash].' + ext;
},
});
return outputOptions;
@ -239,8 +297,6 @@ export function vitePluginNewBuild(input: Set<string>, internals: BuildInternals
internals.entrySpecifierToBundleMap.set(specifier, chunk.fileName);
}
}
console.log(internals.entrySpecifierToBundleMap);
}
};
}

View file

@ -121,7 +121,7 @@ export async function generateHydrateScript(scriptOptions: HydrateScriptOptions,
}
hydrationSource += renderer.source
? `const [{ ${componentExport.value}: Component }, { default: hydrate }] = await Promise.all([import("${componentUrl}"), import("${await result.resolve(renderer.source)}")]);
? `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);
`
: `await import("${componentUrl}");

View file

@ -157,6 +157,7 @@ Did you mean to enable ${formatList(probableRendererNames.map((r) => '`' + r + '
// Call the renderers `check` hook to see if any claim this component.
let renderer: Renderer | undefined;
debugger;
if (metadata.hydrate !== 'only') {
for (const r of renderers) {
if (await r.ssr.check(Component, props, children)) {

View file

@ -26,12 +26,12 @@ export class Metadata {
private metadataCache: Map<any, ComponentMetadata | null>;
constructor(fileURL: string, opts: CreateMetadataOptions) {
constructor(filePathname: string, opts: CreateMetadataOptions) {
this.modules = opts.modules;
this.hoisted = opts.hoisted;
this.hydratedComponents = opts.hydratedComponents;
this.hydrationDirectives = opts.hydrationDirectives;
this.fileURL = new URL(fileURL);
this.fileURL = new URL(filePathname, 'http://example.com');
this.metadataCache = new Map<any, ComponentMetadata | null>();
}
@ -128,6 +128,6 @@ export class Metadata {
}
}
export function createMetadata(fileURL: string, options: CreateMetadataOptions) {
return new Metadata(fileURL, options);
export function createMetadata(filePathname: string, options: CreateMetadataOptions) {
return new Metadata(filePathname, options);
}

View file

@ -29,8 +29,10 @@ function isSSR(options: undefined | boolean | { ssr: boolean }): boolean {
async function compile(config: AstroConfig, filename: string, source: string, viteTransform: TransformHook, opts: boolean | undefined) {
// pages and layouts should be transformed as full documents (implicit <head> <body> etc)
// everything else is treated as a fragment
const normalizedID = fileURLToPath(new URL(`file://${filename}`));
const filenameURL = new URL(`file://${filename}`);
const normalizedID = fileURLToPath(filenameURL);
const isPage = normalizedID.startsWith(fileURLToPath(config.pages)) || normalizedID.startsWith(fileURLToPath(config.layouts));
const pathname = filenameURL.pathname.substr(config.projectRoot.pathname.length - 1)
let cssTransformError: Error | undefined;
@ -39,6 +41,7 @@ async function compile(config: AstroConfig, filename: string, source: string, vi
// result passed to esbuild, but also available in the catch handler.
const transformResult = await transform(source, {
as: isPage ? 'document' : 'fragment',
pathname,
projectRoot: config.projectRoot.toString(),
site: config.buildOptions.site,
sourcefile: filename,

View file

@ -69,7 +69,7 @@ export function rollupPluginAstroBuildHTML(options: PluginOptions): VitePlugin {
const [renderers, mod] = pageData.preload;
// Hydrated components are statically identified.
for (const path of mod.$$metadata.getAllHydratedComponentPaths()) {
for (const path of mod.$$metadata.hydratedComponentPaths()) {
jsInput.add(path);
}

View file

@ -3,7 +3,7 @@ import { renderToString } from 'vue/server-renderer';
import StaticHtml from './static-html.js';
function check(Component) {
return !!Component['ssrRender'];
return !!Component['ssrRender'] || !!Component.render;
}
async function renderToStaticMarkup(Component, props, children) {