Fix use of frameworks in the static build (#2367)
* Fix use of frameworks in the static build * Adding a changeset * fix typescript * Empty out the directory before running the builds * Use a util to empty the directory * Only empty the outdir if needed * Move prepareOutDir to its own module * Prepare outDir is actually sync
This commit is contained in:
parent
ff9dbc6927
commit
2aa5ba5c52
8 changed files with 136 additions and 7 deletions
5
.changeset/long-eagles-care.md
Normal file
5
.changeset/long-eagles-care.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixes use of framework renderers in the static build
|
|
@ -279,6 +279,8 @@ export interface Renderer {
|
||||||
name: string;
|
name: string;
|
||||||
/** Import statement for renderer */
|
/** Import statement for renderer */
|
||||||
source?: string;
|
source?: string;
|
||||||
|
/** Import statement for the server renderer */
|
||||||
|
serverEntry: string;
|
||||||
/** Scripts to be injected before component */
|
/** Scripts to be injected before component */
|
||||||
polyfills?: string[];
|
polyfills?: string[];
|
||||||
/** Polyfills that need to run before hydration ever occurs */
|
/** Polyfills that need to run before hydration ever occurs */
|
||||||
|
|
28
packages/astro/src/core/build/fs.ts
Normal file
28
packages/astro/src/core/build/fs.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import type { AstroConfig } from '../../@types/astro';
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import npath from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
export function emptyDir(dir: string, skip?: Set<string>): void {
|
||||||
|
for (const file of fs.readdirSync(dir)) {
|
||||||
|
if (skip?.has(file)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const abs = npath.resolve(dir, file)
|
||||||
|
// baseline is Node 12 so can't use rmSync :(
|
||||||
|
if (fs.lstatSync(abs).isDirectory()) {
|
||||||
|
emptyDir(abs)
|
||||||
|
fs.rmdirSync(abs)
|
||||||
|
} else {
|
||||||
|
fs.unlinkSync(abs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareOutDir(astroConfig: AstroConfig) {
|
||||||
|
const outDir = fileURLToPath(astroConfig.dist);
|
||||||
|
if (fs.existsSync(outDir)) {
|
||||||
|
return emptyDir(outDir, new Set(['.git']));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import type { OutputChunk, OutputAsset, PreRenderedChunk, RollupOutput } from 'rollup';
|
import type { OutputChunk, OutputAsset, PreRenderedChunk, RollupOutput } from 'rollup';
|
||||||
import type { Plugin as VitePlugin, UserConfig } from '../vite';
|
import type { Plugin as VitePlugin, UserConfig } from '../vite';
|
||||||
import type { AstroConfig, RouteCache, SSRElement } from '../../@types/astro';
|
import type { AstroConfig, Renderer, RouteCache, SSRElement } from '../../@types/astro';
|
||||||
import type { AllPagesData } from './types';
|
import type { AllPagesData } from './types';
|
||||||
import type { LogOptions } from '../logger';
|
import type { LogOptions } from '../logger';
|
||||||
import type { ViteConfigWithSSR } from '../create-vite';
|
import type { ViteConfigWithSSR } from '../create-vite';
|
||||||
|
@ -19,6 +19,7 @@ import { rollupPluginAstroBuildCSS } from '../../vite-plugin-build-css/index.js'
|
||||||
import { getParamsAndProps } from '../ssr/index.js';
|
import { getParamsAndProps } from '../ssr/index.js';
|
||||||
import { createResult } from '../ssr/result.js';
|
import { createResult } from '../ssr/result.js';
|
||||||
import { renderPage } from '../../runtime/server/index.js';
|
import { renderPage } from '../../runtime/server/index.js';
|
||||||
|
import { prepareOutDir } from './fs.js';
|
||||||
|
|
||||||
export interface StaticBuildOptions {
|
export interface StaticBuildOptions {
|
||||||
allPages: AllPagesData;
|
allPages: AllPagesData;
|
||||||
|
@ -35,6 +36,8 @@ function addPageName(pathname: string, opts: StaticBuildOptions): void {
|
||||||
opts.pageNames.push(pathname.replace(/\/?$/, pathrepl).replace(/^\//, ''));
|
opts.pageNames.push(pathname.replace(/\/?$/, pathrepl).replace(/^\//, ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Determines of a Rollup chunk is an entrypoint page.
|
// Determines of a Rollup chunk is an entrypoint page.
|
||||||
function chunkIsPage(output: OutputAsset | OutputChunk, internals: BuildInternals) {
|
function chunkIsPage(output: OutputAsset | OutputChunk, internals: BuildInternals) {
|
||||||
if (output.type !== 'chunk') {
|
if (output.type !== 'chunk') {
|
||||||
|
@ -82,6 +85,11 @@ export async function staticBuild(opts: StaticBuildOptions) {
|
||||||
// Build internals needed by the CSS plugin
|
// Build internals needed by the CSS plugin
|
||||||
const internals = createBuildInternals();
|
const internals = createBuildInternals();
|
||||||
|
|
||||||
|
// Empty out the dist folder, if needed. Vite has a config for doing this
|
||||||
|
// but because we are running 2 vite builds in parallel, that would cause a race
|
||||||
|
// condition, so we are doing it ourselves
|
||||||
|
prepareOutDir(astroConfig);
|
||||||
|
|
||||||
// Run the SSR build and client build in parallel
|
// Run the SSR build and client build in parallel
|
||||||
const [ssrResult] = (await Promise.all([ssrBuild(opts, internals, pageInput), clientBuild(opts, internals, jsInput)])) as RollupOutput[];
|
const [ssrResult] = (await Promise.all([ssrBuild(opts, internals, pageInput), clientBuild(opts, internals, jsInput)])) as RollupOutput[];
|
||||||
|
|
||||||
|
@ -97,7 +105,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
|
||||||
logLevel: 'error',
|
logLevel: 'error',
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
build: {
|
build: {
|
||||||
emptyOutDir: true,
|
emptyOutDir: false,
|
||||||
minify: false,
|
minify: false,
|
||||||
outDir: fileURLToPath(astroConfig.dist),
|
outDir: fileURLToPath(astroConfig.dist),
|
||||||
ssr: true,
|
ssr: true,
|
||||||
|
@ -163,18 +171,41 @@ async function clientBuild(opts: StaticBuildOptions, internals: BuildInternals,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function collectRenderers(opts: StaticBuildOptions): Promise<Renderer[]> {
|
||||||
|
// All of the PageDatas have the same renderers, so just grab one.
|
||||||
|
const pageData = Object.values(opts.allPages)[0];
|
||||||
|
// These renderers have been loaded through Vite. To generate pages
|
||||||
|
// we need the ESM loaded version. This creates that.
|
||||||
|
const viteLoadedRenderers = pageData.preload[0];
|
||||||
|
|
||||||
|
const renderers = await Promise.all(viteLoadedRenderers.map(async r => {
|
||||||
|
const mod = await import(r.serverEntry);
|
||||||
|
return Object.create(r, {
|
||||||
|
ssr: {
|
||||||
|
value: mod.default
|
||||||
|
}
|
||||||
|
}) as Renderer;
|
||||||
|
}));
|
||||||
|
|
||||||
|
return renderers;
|
||||||
|
}
|
||||||
|
|
||||||
async function generatePages(result: RollupOutput, opts: StaticBuildOptions, internals: BuildInternals, facadeIdToPageDataMap: Map<string, PageBuildData>) {
|
async function generatePages(result: RollupOutput, opts: StaticBuildOptions, internals: BuildInternals, facadeIdToPageDataMap: Map<string, PageBuildData>) {
|
||||||
debug(opts.logging, 'generate', 'End build step, now generating');
|
debug(opts.logging, 'generate', 'End build step, now generating');
|
||||||
|
|
||||||
|
// Get renderers to be shared for each page generation.
|
||||||
|
const renderers = await collectRenderers(opts);
|
||||||
|
|
||||||
const generationPromises = [];
|
const generationPromises = [];
|
||||||
for (let output of result.output) {
|
for (let output of result.output) {
|
||||||
if (chunkIsPage(output, internals)) {
|
if (chunkIsPage(output, internals)) {
|
||||||
generationPromises.push(generatePage(output as OutputChunk, opts, internals, facadeIdToPageDataMap));
|
generationPromises.push(generatePage(output as OutputChunk, opts, internals, facadeIdToPageDataMap, renderers));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Promise.all(generationPromises);
|
await Promise.all(generationPromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generatePage(output: OutputChunk, opts: StaticBuildOptions, internals: BuildInternals, facadeIdToPageDataMap: Map<string, PageBuildData>) {
|
async function generatePage(output: OutputChunk, opts: StaticBuildOptions, internals: BuildInternals, facadeIdToPageDataMap: Map<string, PageBuildData>, renderers: Renderer[]) {
|
||||||
const { astroConfig } = opts;
|
const { astroConfig } = opts;
|
||||||
|
|
||||||
let url = new URL('./' + output.fileName, astroConfig.dist);
|
let url = new URL('./' + output.fileName, astroConfig.dist);
|
||||||
|
@ -198,6 +229,7 @@ async function generatePage(output: OutputChunk, opts: StaticBuildOptions, inter
|
||||||
internals,
|
internals,
|
||||||
linkIds,
|
linkIds,
|
||||||
Component,
|
Component,
|
||||||
|
renderers,
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderPromises = pageData.paths.map((path) => {
|
const renderPromises = pageData.paths.map((path) => {
|
||||||
|
@ -211,16 +243,17 @@ interface GeneratePathOptions {
|
||||||
internals: BuildInternals;
|
internals: BuildInternals;
|
||||||
linkIds: string[];
|
linkIds: string[];
|
||||||
Component: AstroComponentFactory;
|
Component: AstroComponentFactory;
|
||||||
|
renderers: Renderer[];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: GeneratePathOptions) {
|
async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: GeneratePathOptions) {
|
||||||
const { astroConfig, logging, origin, pageNames, routeCache } = opts;
|
const { astroConfig, logging, origin, pageNames, routeCache } = opts;
|
||||||
const { Component, internals, linkIds, pageData } = gopts;
|
const { Component, internals, linkIds, pageData, renderers } = gopts;
|
||||||
|
|
||||||
// This adds the page name to the array so it can be shown as part of stats.
|
// This adds the page name to the array so it can be shown as part of stats.
|
||||||
addPageName(pathname, opts);
|
addPageName(pathname, opts);
|
||||||
|
|
||||||
const [renderers, mod] = pageData.preload;
|
const [,mod] = pageData.preload;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [params, pageProps] = await getParamsAndProps({
|
const [params, pageProps] = await getParamsAndProps({
|
||||||
|
|
|
@ -52,9 +52,10 @@ async function resolveRenderer(viteServer: vite.ViteDevServer, renderer: string,
|
||||||
|
|
||||||
resolvedRenderer.name = name;
|
resolvedRenderer.name = name;
|
||||||
if (client) resolvedRenderer.source = path.posix.join(renderer, client);
|
if (client) resolvedRenderer.source = path.posix.join(renderer, client);
|
||||||
|
resolvedRenderer.serverEntry = path.posix.join(renderer, server);
|
||||||
if (Array.isArray(hydrationPolyfills)) resolvedRenderer.hydrationPolyfills = hydrationPolyfills.map((src: string) => path.posix.join(renderer, src));
|
if (Array.isArray(hydrationPolyfills)) resolvedRenderer.hydrationPolyfills = hydrationPolyfills.map((src: string) => path.posix.join(renderer, src));
|
||||||
if (Array.isArray(polyfills)) resolvedRenderer.polyfills = polyfills.map((src: string) => path.posix.join(renderer, src));
|
if (Array.isArray(polyfills)) resolvedRenderer.polyfills = polyfills.map((src: string) => path.posix.join(renderer, src));
|
||||||
const { url } = await viteServer.moduleGraph.ensureEntryFromUrl(path.posix.join(renderer, server));
|
const { url } = await viteServer.moduleGraph.ensureEntryFromUrl(resolvedRenderer.serverEntry);
|
||||||
const { default: rendererSSR } = await viteServer.ssrLoadModule(url);
|
const { default: rendererSSR } = await viteServer.ssrLoadModule(url);
|
||||||
resolvedRenderer.ssr = rendererSSR;
|
resolvedRenderer.ssr = rendererSSR;
|
||||||
|
|
||||||
|
|
19
packages/astro/test/fixtures/static-build-frameworks/src/components/PCounter.jsx
vendored
Normal file
19
packages/astro/test/fixtures/static-build-frameworks/src/components/PCounter.jsx
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { h } from 'preact';
|
||||||
|
import { useState } from 'preact/hooks';
|
||||||
|
|
||||||
|
export default function Counter({ children }) {
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
const add = () => setCount((i) => i + 1);
|
||||||
|
const subtract = () => setCount((i) => i - 1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div class="counter">
|
||||||
|
<button onClick={subtract}>-</button>
|
||||||
|
<pre>{count}</pre>
|
||||||
|
<button onClick={add}>+</button>
|
||||||
|
</div>
|
||||||
|
<div class="counter-message">{children}</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
12
packages/astro/test/fixtures/static-build-frameworks/src/pages/index.astro
vendored
Normal file
12
packages/astro/test/fixtures/static-build-frameworks/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
import PCounter from '../components/PCounter.jsx';
|
||||||
|
---
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Testing</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Testing</h1>
|
||||||
|
<PCounter client:load />
|
||||||
|
</body>
|
||||||
|
</html>
|
29
packages/astro/test/static-build-frameworks.test.js
Normal file
29
packages/astro/test/static-build-frameworks.test.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import cheerio from 'cheerio';
|
||||||
|
import { loadFixture } from './test-utils.js';
|
||||||
|
|
||||||
|
function addLeadingSlash(path) {
|
||||||
|
return path.startsWith('/') ? path : '/' + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Static build - frameworks', () => {
|
||||||
|
let fixture;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture({
|
||||||
|
projectRoot: './fixtures/static-build-frameworks/',
|
||||||
|
renderers: [
|
||||||
|
'@astrojs/renderer-preact'
|
||||||
|
],
|
||||||
|
buildOptions: {
|
||||||
|
experimentalStaticBuild: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await fixture.build();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can build preact', async () => {
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
expect(html).to.be.a('string');
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue