0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-10 23:01:26 -05:00

feat: hybrid output (#6991)

* update config schema

* adapt default route `prerender` value

* adapt error message for hybrid output

* core hybrid output support

* add JSDocs for hybrid output

* dev server hybrid output support

* defer hybrid output check

* update endpoint request warning

* support `output=hybrid` in integrations

* put constant variable out of for loop

* revert: reapply back ssr plugin in ssr mode

* change `prerender` option default

* apply `prerender` by default in hybrid mode

* simplfy conditional

* update config schema

* add `isHybridOutput` helper

* more readable prerender condition

* set default prerender value if no export is found

* only add `pagesVirtualModuleId` ro rollup input in `output=static`

* don't export vite plugin

* remove unneeded check

* don't prerender when it shouldn't

* extract fallback `prerender` meta

Extract the fallback `prerender` module meta out of the `scan` function.
It shouldn't be its responsibility to handle that

* pass missing argument to function

* test: update cloudflare integration tests

* test: update tests of vercel integration

* test: update tests of node integration

* test: update tests of netlify func integration

* test: update tests of netlify edge integration

* throw when `hybrid` mode is malconfigured

* update node integraiton `output` warning

* test(WIP): skip node prerendering tests for now

* remove non-existant import

* test: bring back prerendering tests

* remove outdated comments

* test: refactor test to support windows paths

* remove outdated comments

* apply sarah review

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* docs: `experiment.hybridOutput` jsodcs

* test: prevent import from being cached

* refactor: extract hybrid output check to  function

* add `hybrid` to output warning in adapter hooks

* chore: changeset

* add `.js` extension to import

* chore: use spaces instead of tabs for gh formating

* resolve merge conflict

* chore: move test to another file for consitency

---------

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
Co-authored-by: Matthew Phillips <matthew@skypack.dev>
This commit is contained in:
Happydev 2023-05-17 13:23:20 +00:00 committed by GitHub
parent b4aed9b17c
commit be24395eaf
3 changed files with 142 additions and 10 deletions

View file

@ -40,7 +40,9 @@ export default function createIntegration(userOptions: UserOptions): AstroIntegr
setAdapter(getAdapter(_options));
if (config.output === 'static') {
console.warn(`[@astrojs/node] \`output: "server"\` is required to use this adapter.`);
console.warn(
`[@astrojs/node] \`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.`
);
}
},
},

View file

@ -1,5 +1,5 @@
---
export const prerender = true;
export const prerender = import.meta.env.PRERENDER;
---
<html>
<head>

View file

@ -1,22 +1,27 @@
import nodejs from '../dist/index.js';
import { loadFixture, createRequestAndResponse } from './test-utils.js';
import { loadFixture } from './test-utils.js';
import { expect } from 'chai';
import * as cheerio from 'cheerio';
import { fetch } from 'undici';
/**
* @typedef {import('../../../astro/test/test-utils').Fixture} Fixture
*/
async function load() {
const mod = await import(`./fixtures/prerender/dist/server/entry.mjs?dropcache=${Date.now()}`);
return mod;
}
describe('Prerendering', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
let server;
async function load() {
const mod = await import('./fixtures/prerender/dist/server/entry.mjs');
return mod;
}
describe('With base', () => {
describe('With base', async () => {
before(async () => {
process.env.ASTRO_NODE_AUTOSTART = 'disabled';
process.env.PRERENDER = true;
fixture = await loadFixture({
base: '/some-base',
root: './fixtures/prerender/',
@ -31,6 +36,8 @@ describe('Prerendering', () => {
after(async () => {
await server.stop();
await fixture.clean();
delete process.env.PRERENDER;
});
it('Can render SSR route', async () => {
@ -68,9 +75,12 @@ describe('Prerendering', () => {
expect(res.headers.get('location')).to.equal('/some-base/two/');
});
});
describe('Without base', () => {
describe('Without base', async () => {
before(async () => {
process.env.ASTRO_NODE_AUTOSTART = 'disabled';
process.env.PRERENDER = true;
fixture = await loadFixture({
root: './fixtures/prerender/',
output: 'server',
@ -84,6 +94,8 @@ describe('Prerendering', () => {
after(async () => {
await server.stop();
await fixture.clean();
delete process.env.PRERENDER;
});
it('Can render SSR route', async () => {
@ -114,3 +126,121 @@ describe('Prerendering', () => {
});
});
});
describe('Hybrid rendering', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
let server;
describe('With base', async () => {
before(async () => {
process.env.ASTRO_NODE_AUTOSTART = 'disabled';
process.env.PRERENDER = false;
fixture = await loadFixture({
base: '/some-base',
root: './fixtures/prerender/',
output: 'hybrid',
experimental: {
hybridOutput: true,
},
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
const { startServer } = await await load();
let res = startServer();
server = res.server;
});
after(async () => {
await server.stop();
await fixture.clean();
delete process.env.PRERENDER;
});
it('Can render SSR route', async () => {
const res = await fetch(`http://${server.host}:${server.port}/some-base/two`);
const html = await res.text();
const $ = cheerio.load(html);
expect(res.status).to.equal(200);
expect($('h1').text()).to.equal('Two');
});
it('Can render prerendered route', async () => {
const res = await fetch(`http://${server.host}:${server.port}/some-base/one`);
const html = await res.text();
const $ = cheerio.load(html);
expect(res.status).to.equal(200);
expect($('h1').text()).to.equal('One');
});
it('Can render prerendered route with query params', async () => {
const res = await fetch(`http://${server.host}:${server.port}/some-base/one/?foo=bar`);
const html = await res.text();
const $ = cheerio.load(html);
expect(res.status).to.equal(200);
expect($('h1').text()).to.equal('One');
});
it('Omitting the trailing slash results in a redirect that includes the base', async () => {
const res = await fetch(`http://${server.host}:${server.port}/some-base/one`, {
redirect: 'manual',
});
expect(res.status).to.equal(301);
expect(res.headers.get('location')).to.equal('/some-base/one/');
});
});
describe('Without base', async () => {
before(async () => {
process.env.ASTRO_NODE_AUTOSTART = 'disabled';
process.env.PRERENDER = false;
fixture = await loadFixture({
root: './fixtures/prerender/',
output: 'hybrid',
experimental: {
hybridOutput: true,
},
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
const { startServer } = await await load();
let res = startServer();
server = res.server;
});
after(async () => {
await server.stop();
await fixture.clean();
delete process.env.PRERENDER;
});
it('Can render SSR route', async () => {
const res = await fetch(`http://${server.host}:${server.port}/two`);
const html = await res.text();
const $ = cheerio.load(html);
expect(res.status).to.equal(200);
expect($('h1').text()).to.equal('Two');
});
it('Can render prerendered route', async () => {
const res = await fetch(`http://${server.host}:${server.port}/one`);
const html = await res.text();
const $ = cheerio.load(html);
expect(res.status).to.equal(200);
expect($('h1').text()).to.equal('One');
});
it('Can render prerendered route with query params', async () => {
const res = await fetch(`http://${server.host}:${server.port}/one/?foo=bar`);
const html = await res.text();
const $ = cheerio.load(html);
expect(res.status).to.equal(200);
expect($('h1').text()).to.equal('One');
});
});
});