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",
|
"dev": "astro dev --experimental-static-build",
|
||||||
"start": "astro dev",
|
"start": "astro dev",
|
||||||
"build": "astro build --experimental-static-build",
|
"build": "astro build --experimental-static-build",
|
||||||
|
"scan-build": "astro build",
|
||||||
"preview": "astro preview"
|
"preview": "astro preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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 type { AstroComponentFactory } from '../../runtime/server';
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import npath from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
import glob from 'fast-glob';
|
||||||
import vite from '../vite.js';
|
import vite from '../vite.js';
|
||||||
import { debug, info, error } from '../../core/logger.js';
|
import { debug, info, error } from '../../core/logger.js';
|
||||||
import { createBuildInternals } from '../../core/build/internal.js';
|
import { createBuildInternals } from '../../core/build/internal.js';
|
||||||
|
@ -30,8 +32,11 @@ export interface StaticBuildOptions {
|
||||||
export async function staticBuild(opts: StaticBuildOptions) {
|
export async function staticBuild(opts: StaticBuildOptions) {
|
||||||
const { allPages, astroConfig } = opts;
|
const { allPages, astroConfig } = opts;
|
||||||
|
|
||||||
|
// The pages
|
||||||
|
const pageInput = new Set<string>();
|
||||||
|
|
||||||
// The JavaScript entrypoints.
|
// 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
|
// A map of each page .astro file, to the PageBuildData which contains information
|
||||||
// about that page, such as its paths.
|
// 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)) {
|
for (const [component, pageData] of Object.entries(allPages)) {
|
||||||
const [renderers, mod] = pageData.preload;
|
const [renderers, mod] = pageData.preload;
|
||||||
|
const metadata = mod.$$metadata;
|
||||||
|
|
||||||
const topLevelImports = new Set([
|
const topLevelImports = new Set([
|
||||||
// Any component that gets hydrated
|
// Any component that gets hydrated
|
||||||
...mod.$$metadata.hydratedComponentPaths(),
|
...metadata.hydratedComponentPaths(),
|
||||||
// Any hydration directive like astro/client/idle.js
|
// Any hydration directive like astro/client/idle.js
|
||||||
...mod.$$metadata.hydrationDirectiveSpecifiers(),
|
...metadata.hydrationDirectiveSpecifiers(),
|
||||||
// The client path for each renderer
|
// The client path for each renderer
|
||||||
...renderers.filter(renderer => !!renderer.source).map(renderer => renderer.source!),
|
...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;
|
let astroModuleId = new URL('./' + component, astroConfig.projectRoot).pathname;
|
||||||
jsInput.add(astroModuleId);
|
pageInput.add(astroModuleId);
|
||||||
facadeIdToPageDataMap.set(astroModuleId, pageData);
|
facadeIdToPageDataMap.set(astroModuleId, pageData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build internals needed by the CSS plugin
|
// Build internals needed by the CSS plugin
|
||||||
const internals = createBuildInternals();
|
const internals = createBuildInternals();
|
||||||
|
|
||||||
// Perform the SSR build
|
// Run the SSR build and client build in parallel
|
||||||
const result = (await ssrBuild(opts, internals, jsInput)) as RollupOutput;
|
const [ssrResult] = await Promise.all([
|
||||||
|
ssrBuild(opts, internals, pageInput),
|
||||||
|
clientBuild(opts, internals, jsInput)
|
||||||
|
]) as RollupOutput[];
|
||||||
|
|
||||||
// Generate each of the pages.
|
// 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>) {
|
async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, input: Set<string>) {
|
||||||
|
@ -76,7 +86,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
build: {
|
build: {
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
minify: false, // 'esbuild', // significantly faster than "terser" but may produce slightly-bigger bundles
|
minify: false,
|
||||||
outDir: fileURLToPath(astroConfig.dist),
|
outDir: fileURLToPath(astroConfig.dist),
|
||||||
ssr: true,
|
ssr: true,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
|
@ -88,7 +98,42 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
|
||||||
target: 'es2020', // must match an esbuild target
|
target: 'es2020', // must match an esbuild target
|
||||||
},
|
},
|
||||||
plugins: [
|
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({
|
rollupPluginAstroBuildCSS({
|
||||||
internals,
|
internals,
|
||||||
}),
|
}),
|
||||||
|
@ -166,7 +211,7 @@ async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: G
|
||||||
mod,
|
mod,
|
||||||
});
|
});
|
||||||
|
|
||||||
info(logging, 'generate', `Generating: ${pathname}`);
|
debug(logging, 'generate', `Generating: ${pathname}`);
|
||||||
|
|
||||||
const result = createResult({ astroConfig, origin, params, pathname, renderers });
|
const result = createResult({ astroConfig, origin, params, pathname, renderers });
|
||||||
result.links = new Set<SSRElement>(
|
result.links = new Set<SSRElement>(
|
||||||
|
@ -183,8 +228,9 @@ async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: G
|
||||||
if(typeof hashedFilePath !== 'string') {
|
if(typeof hashedFilePath !== 'string') {
|
||||||
throw new Error(`Cannot find the built path for ${specifier}`);
|
throw new Error(`Cannot find the built path for ${specifier}`);
|
||||||
}
|
}
|
||||||
console.log("WE GOT", hashedFilePath)
|
const relPath = npath.posix.relative(pathname, '/' + hashedFilePath);
|
||||||
return hashedFilePath;
|
const fullyRelativePath = relPath[0] === '.' ? relPath : './' + relPath;
|
||||||
|
return fullyRelativePath;
|
||||||
};
|
};
|
||||||
|
|
||||||
let html = await renderPage(result, Component, pageProps, null);
|
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 {
|
return {
|
||||||
name: '@astro/rollup-plugin-new-build',
|
name: '@astro/rollup-plugin-new-build',
|
||||||
|
|
||||||
|
@ -213,10 +271,10 @@ export function vitePluginNewBuild(input: Set<string>, internals: BuildInternals
|
||||||
outputOptions(outputOptions) {
|
outputOptions(outputOptions) {
|
||||||
Object.assign(outputOptions, {
|
Object.assign(outputOptions, {
|
||||||
entryFileNames(_chunk: PreRenderedChunk) {
|
entryFileNames(_chunk: PreRenderedChunk) {
|
||||||
return 'assets/[name].[hash].mjs';
|
return 'assets/[name].[hash].' + ext;
|
||||||
},
|
},
|
||||||
chunkFileNames(_chunk: PreRenderedChunk) {
|
chunkFileNames(_chunk: PreRenderedChunk) {
|
||||||
return 'assets/[name].[hash].mjs';
|
return 'assets/[name].[hash].' + ext;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return outputOptions;
|
return outputOptions;
|
||||||
|
@ -239,8 +297,6 @@ export function vitePluginNewBuild(input: Set<string>, internals: BuildInternals
|
||||||
internals.entrySpecifierToBundleMap.set(specifier, chunk.fileName);
|
internals.entrySpecifierToBundleMap.set(specifier, chunk.fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(internals.entrySpecifierToBundleMap);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,7 +121,7 @@ export async function generateHydrateScript(scriptOptions: HydrateScriptOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrationSource += renderer.source
|
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);
|
return (el, children) => hydrate(el)(Component, ${serializeProps(props)}, children);
|
||||||
`
|
`
|
||||||
: `await import("${componentUrl}");
|
: `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.
|
// Call the renderers `check` hook to see if any claim this component.
|
||||||
let renderer: Renderer | undefined;
|
let renderer: Renderer | undefined;
|
||||||
|
debugger;
|
||||||
if (metadata.hydrate !== 'only') {
|
if (metadata.hydrate !== 'only') {
|
||||||
for (const r of renderers) {
|
for (const r of renderers) {
|
||||||
if (await r.ssr.check(Component, props, children)) {
|
if (await r.ssr.check(Component, props, children)) {
|
||||||
|
|
|
@ -26,12 +26,12 @@ export class Metadata {
|
||||||
|
|
||||||
private metadataCache: Map<any, ComponentMetadata | null>;
|
private metadataCache: Map<any, ComponentMetadata | null>;
|
||||||
|
|
||||||
constructor(fileURL: string, opts: CreateMetadataOptions) {
|
constructor(filePathname: string, opts: CreateMetadataOptions) {
|
||||||
this.modules = opts.modules;
|
this.modules = opts.modules;
|
||||||
this.hoisted = opts.hoisted;
|
this.hoisted = opts.hoisted;
|
||||||
this.hydratedComponents = opts.hydratedComponents;
|
this.hydratedComponents = opts.hydratedComponents;
|
||||||
this.hydrationDirectives = opts.hydrationDirectives;
|
this.hydrationDirectives = opts.hydrationDirectives;
|
||||||
this.fileURL = new URL(fileURL);
|
this.fileURL = new URL(filePathname, 'http://example.com');
|
||||||
this.metadataCache = new Map<any, ComponentMetadata | null>();
|
this.metadataCache = new Map<any, ComponentMetadata | null>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +128,6 @@ export class Metadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createMetadata(fileURL: string, options: CreateMetadataOptions) {
|
export function createMetadata(filePathname: string, options: CreateMetadataOptions) {
|
||||||
return new Metadata(fileURL, options);
|
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) {
|
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)
|
// pages and layouts should be transformed as full documents (implicit <head> <body> etc)
|
||||||
// everything else is treated as a fragment
|
// 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 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;
|
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.
|
// result passed to esbuild, but also available in the catch handler.
|
||||||
const transformResult = await transform(source, {
|
const transformResult = await transform(source, {
|
||||||
as: isPage ? 'document' : 'fragment',
|
as: isPage ? 'document' : 'fragment',
|
||||||
|
pathname,
|
||||||
projectRoot: config.projectRoot.toString(),
|
projectRoot: config.projectRoot.toString(),
|
||||||
site: config.buildOptions.site,
|
site: config.buildOptions.site,
|
||||||
sourcefile: filename,
|
sourcefile: filename,
|
||||||
|
|
|
@ -69,7 +69,7 @@ export function rollupPluginAstroBuildHTML(options: PluginOptions): VitePlugin {
|
||||||
const [renderers, mod] = pageData.preload;
|
const [renderers, mod] = pageData.preload;
|
||||||
|
|
||||||
// Hydrated components are statically identified.
|
// Hydrated components are statically identified.
|
||||||
for (const path of mod.$$metadata.getAllHydratedComponentPaths()) {
|
for (const path of mod.$$metadata.hydratedComponentPaths()) {
|
||||||
jsInput.add(path);
|
jsInput.add(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { renderToString } from 'vue/server-renderer';
|
||||||
import StaticHtml from './static-html.js';
|
import StaticHtml from './static-html.js';
|
||||||
|
|
||||||
function check(Component) {
|
function check(Component) {
|
||||||
return !!Component['ssrRender'];
|
return !!Component['ssrRender'] || !!Component.render;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderToStaticMarkup(Component, props, children) {
|
async function renderToStaticMarkup(Component, props, children) {
|
||||||
|
|
Loading…
Reference in a new issue