Gets hydration totally working
This commit is contained in:
parent
d22734c6e8
commit
82d02aa968
9 changed files with 106 additions and 25 deletions
|
@ -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": {
|
||||
|
|
20
examples/fast-build/src/pages/[pokemon].astro
Normal file
20
examples/fast-build/src/pages/[pokemon].astro
Normal 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>
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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}");
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue