summaryrefslogtreecommitdiff
path: root/packages/integrations/netlify
diff options
context:
space:
mode:
authorGravatar Happydev <81974850+MoustaphaDev@users.noreply.github.com> 2023-05-17 13:23:20 +0000
committerGravatar GitHub <noreply@github.com> 2023-05-17 09:23:20 -0400
commit719002ca5b128744fb4316d4a52c5dcd46a42759 (patch)
treef43782264925e7d7cb3675f1c859f39c898e2458 /packages/integrations/netlify
parent2b9230ed22dd379633ece3fcd9d485dfc3064441 (diff)
downloadastro-719002ca5b128744fb4316d4a52c5dcd46a42759.tar.gz
astro-719002ca5b128744fb4316d4a52c5dcd46a42759.tar.zst
astro-719002ca5b128744fb4316d4a52c5dcd46a42759.zip
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>
Diffstat (limited to 'packages/integrations/netlify')
-rw-r--r--packages/integrations/netlify/src/integration-edge-functions.ts4
-rw-r--r--packages/integrations/netlify/src/integration-functions.ts4
-rw-r--r--packages/integrations/netlify/test/edge-functions/deps.ts8
-rw-r--r--packages/integrations/netlify/test/edge-functions/dynamic-import.test.js5
-rw-r--r--packages/integrations/netlify/test/edge-functions/edge-basic.test.ts7
-rw-r--r--packages/integrations/netlify/test/edge-functions/fixtures/prerender/astro.config.mjs28
-rw-r--r--packages/integrations/netlify/test/edge-functions/fixtures/prerender/src/pages/index.astro2
-rw-r--r--packages/integrations/netlify/test/edge-functions/prerender.test.ts81
-rw-r--r--packages/integrations/netlify/test/edge-functions/root-dynamic.test.ts8
-rw-r--r--packages/integrations/netlify/test/edge-functions/test-utils.ts65
-rw-r--r--packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/one.astro2
-rw-r--r--packages/integrations/netlify/test/functions/prerender.test.js45
12 files changed, 206 insertions, 53 deletions
diff --git a/packages/integrations/netlify/src/integration-edge-functions.ts b/packages/integrations/netlify/src/integration-edge-functions.ts
index b11710430..2f65bccda 100644
--- a/packages/integrations/netlify/src/integration-edge-functions.ts
+++ b/packages/integrations/netlify/src/integration-edge-functions.ts
@@ -134,7 +134,9 @@ export function netlifyEdgeFunctions({ dist }: NetlifyEdgeFunctionsOptions = {})
entryFile = config.build.serverEntry.replace(/\.m?js/, '');
if (config.output === 'static') {
- console.warn(`[@astrojs/netlify] \`output: "server"\` is required to use this adapter.`);
+ console.warn(
+ `[@astrojs/netlify] \`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.`
+ );
console.warn(
`[@astrojs/netlify] Otherwise, this adapter is not required to deploy a static site to Netlify.`
);
diff --git a/packages/integrations/netlify/src/integration-functions.ts b/packages/integrations/netlify/src/integration-functions.ts
index 609dc2500..348b007f5 100644
--- a/packages/integrations/netlify/src/integration-functions.ts
+++ b/packages/integrations/netlify/src/integration-functions.ts
@@ -43,7 +43,9 @@ function netlifyFunctions({
entryFile = config.build.serverEntry.replace(/\.m?js/, '');
if (config.output === 'static') {
- console.warn(`[@astrojs/netlify] \`output: "server"\` is required to use this adapter.`);
+ console.warn(
+ `[@astrojs/netlify] \`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.`
+ );
console.warn(
`[@astrojs/netlify] Otherwise, this adapter is not required to deploy a static site to Netlify.`
);
diff --git a/packages/integrations/netlify/test/edge-functions/deps.ts b/packages/integrations/netlify/test/edge-functions/deps.ts
index 498b7e09e..c6ced8814 100644
--- a/packages/integrations/netlify/test/edge-functions/deps.ts
+++ b/packages/integrations/netlify/test/edge-functions/deps.ts
@@ -1,5 +1,11 @@
// @ts-nocheck
export { fromFileUrl } from 'https://deno.land/std@0.110.0/path/mod.ts';
-export { assertEquals, assert } from 'https://deno.land/std@0.132.0/testing/asserts.ts';
+export {
+ assertEquals,
+ assert,
+ assertExists,
+} from 'https://deno.land/std@0.132.0/testing/asserts.ts';
export * from 'https://deno.land/x/deno_dom/deno-dom-wasm.ts';
export * from 'https://deno.land/std@0.142.0/streams/conversion.ts';
+export * as cheerio from 'https://cdn.skypack.dev/cheerio?dts';
+export * as fs from 'https://deno.land/std/fs/mod.ts';
diff --git a/packages/integrations/netlify/test/edge-functions/dynamic-import.test.js b/packages/integrations/netlify/test/edge-functions/dynamic-import.test.js
index ff4adb490..febd689b6 100644
--- a/packages/integrations/netlify/test/edge-functions/dynamic-import.test.js
+++ b/packages/integrations/netlify/test/edge-functions/dynamic-import.test.js
@@ -4,8 +4,8 @@ import { assertEquals, assert, DOMParser } from './deps.ts';
Deno.test({
name: 'Dynamic imports',
async fn() {
- let close = await runBuild('./fixtures/dynimport/');
- let stop = await runApp('./fixtures/dynimport/prod.js');
+ await runBuild('./fixtures/dynimport/');
+ const stop = await runApp('./fixtures/dynimport/prod.js');
try {
const response = await fetch('http://127.0.0.1:8085/');
@@ -20,7 +20,6 @@ Deno.test({
// eslint-disable-next-line no-console
console.error(err);
} finally {
- await close();
await stop();
}
},
diff --git a/packages/integrations/netlify/test/edge-functions/edge-basic.test.ts b/packages/integrations/netlify/test/edge-functions/edge-basic.test.ts
index ecdbda4e0..9f2a7bde3 100644
--- a/packages/integrations/netlify/test/edge-functions/edge-basic.test.ts
+++ b/packages/integrations/netlify/test/edge-functions/edge-basic.test.ts
@@ -1,4 +1,4 @@
-import { runBuild } from './test-utils.ts';
+import { loadFixture } from './test-utils.ts';
import { assertEquals, assert, DOMParser } from './deps.ts';
Deno.env.set('SECRET_STUFF', 'secret');
@@ -10,7 +10,8 @@ Deno.test({
name: 'Edge Basics',
skip: true,
async fn() {
- let close = await runBuild('./fixtures/edge-basic/');
+ const fixture = loadFixture('./fixtures/edge-basic/');
+ await fixture.runBuild();
const { default: handler } = await import(
'./fixtures/edge-basic/.netlify/edge-functions/entry.js'
);
@@ -26,6 +27,6 @@ Deno.test({
const envDiv = doc.querySelector('#env');
assertEquals(envDiv?.innerText, 'secret');
- await close();
+ await fixture.cleanup();
},
});
diff --git a/packages/integrations/netlify/test/edge-functions/fixtures/prerender/astro.config.mjs b/packages/integrations/netlify/test/edge-functions/fixtures/prerender/astro.config.mjs
index cd758352b..c579d74ef 100644
--- a/packages/integrations/netlify/test/edge-functions/fixtures/prerender/astro.config.mjs
+++ b/packages/integrations/netlify/test/edge-functions/fixtures/prerender/astro.config.mjs
@@ -1,9 +1,23 @@
-import { defineConfig } from 'astro/config';
-import { netlifyEdgeFunctions } from '@astrojs/netlify';
+import { defineConfig } from "astro/config";
+import { netlifyEdgeFunctions } from "@astrojs/netlify";
+
+const isHybridMode = process.env.PRERENDER === "false";
+
+/** @type {import('astro').AstroConfig} */
+const partialConfig = {
+ output: isHybridMode ? "hybrid" : "server",
+ ...(isHybridMode
+ ? ({
+ experimental: {
+ hybridOutput: true,
+ },
+ })
+ : ({})),
+};
export default defineConfig({
- adapter: netlifyEdgeFunctions({
- dist: new URL('./dist/', import.meta.url),
- }),
- output: 'server',
-})
+ adapter: netlifyEdgeFunctions({
+ dist: new URL("./dist/", import.meta.url),
+ }),
+ ...partialConfig,
+});
diff --git a/packages/integrations/netlify/test/edge-functions/fixtures/prerender/src/pages/index.astro b/packages/integrations/netlify/test/edge-functions/fixtures/prerender/src/pages/index.astro
index 075253550..b6b833e53 100644
--- a/packages/integrations/netlify/test/edge-functions/fixtures/prerender/src/pages/index.astro
+++ b/packages/integrations/netlify/test/edge-functions/fixtures/prerender/src/pages/index.astro
@@ -1,5 +1,5 @@
---
-export const prerender = true
+export const prerender = import.meta.env.PRERENDER;
---
<html>
diff --git a/packages/integrations/netlify/test/edge-functions/prerender.test.ts b/packages/integrations/netlify/test/edge-functions/prerender.test.ts
index 5d858ef73..4d4dfc9c6 100644
--- a/packages/integrations/netlify/test/edge-functions/prerender.test.ts
+++ b/packages/integrations/netlify/test/edge-functions/prerender.test.ts
@@ -1,15 +1,76 @@
-import { runBuild } from './test-utils.ts';
-import { assertEquals } from './deps.ts';
+import { loadFixture } from './test-utils.ts';
+import { assertEquals, assertExists, cheerio, fs } from './deps.ts';
Deno.test({
name: 'Prerender',
- async fn() {
- let close = await runBuild('./fixtures/prerender/');
- const { default: handler } = await import(
- './fixtures/prerender/.netlify/edge-functions/entry.js'
- );
- const response = await handler(new Request('http://example.com/index.html'));
- assertEquals(response, undefined, 'No response because this is an asset');
- await close();
+ async fn(t) {
+ const environmentVariables = {
+ PRERENDER: 'true',
+ };
+ const fixture = loadFixture('./fixtures/prerender/', environmentVariables);
+ await fixture.runBuild();
+
+ await t.step('Handler can process requests to non-existing routes', async () => {
+ const { default: handler } = await import(
+ './fixtures/prerender/.netlify/edge-functions/entry.js'
+ );
+ assertExists(handler);
+ const response = await handler(new Request('http://example.com/index.html'));
+ assertEquals(response, undefined, "No response because this route doesn't exist");
+ });
+
+ await t.step('Prerendered route exists', async () => {
+ let content: string | null = null;
+ try {
+ const path = new URL('./fixtures/prerender/dist/index.html', import.meta.url);
+ content = Deno.readTextFileSync(path);
+ } catch (e) {}
+ assertExists(content);
+ const $ = cheerio.load(content);
+ assertEquals($('h1').text(), 'testing');
+ });
+
+ Deno.env.delete('PRERENDER');
+ await fixture.cleanup();
+ },
+});
+
+Deno.test({
+ name: 'Hybrid rendering',
+ async fn(t) {
+ const environmentVariables = {
+ PRERENDER: 'false',
+ };
+ const fixture = loadFixture('./fixtures/prerender/', environmentVariables);
+ await fixture.runBuild();
+
+ const stop = await fixture.runApp('./fixtures/prerender/prod.js');
+ await t.step('Can fetch server route', async () => {
+ const response = await fetch('http://127.0.0.1:8085/');
+ assertEquals(response.status, 200);
+
+ const html = await response.text();
+ const $ = cheerio.load(html);
+ assertEquals($('h1').text(), 'testing');
+ });
+ stop();
+
+ await t.step('Handler can process requests to non-existing routes', async () => {
+ const { default: handler } = await import(
+ './fixtures/prerender/.netlify/edge-functions/entry.js'
+ );
+ const response = await handler(new Request('http://example.com/index.html'));
+ assertEquals(response, undefined, "No response because this route doesn't exist");
+ });
+
+ await t.step('Has no prerendered route', async () => {
+ let prerenderedRouteExists = false;
+ try {
+ const path = new URL('./fixtures/prerender/dist/index.html', import.meta.url);
+ prerenderedRouteExists = fs.existsSync(path);
+ } catch (e) {}
+ assertEquals(prerenderedRouteExists, false);
+ });
+ await fixture.cleanup();
},
});
diff --git a/packages/integrations/netlify/test/edge-functions/root-dynamic.test.ts b/packages/integrations/netlify/test/edge-functions/root-dynamic.test.ts
index c853e2bfc..0e38bc46e 100644
--- a/packages/integrations/netlify/test/edge-functions/root-dynamic.test.ts
+++ b/packages/integrations/netlify/test/edge-functions/root-dynamic.test.ts
@@ -1,4 +1,4 @@
-import { runBuild } from './test-utils.ts';
+import { loadFixture } from './test-utils.ts';
import { assertEquals, assert, DOMParser } from './deps.ts';
Deno.test({
@@ -6,12 +6,14 @@ Deno.test({
ignore: true,
name: 'Assets are preferred over HTML routes',
async fn() {
- let close = await runBuild('./fixtures/root-dynamic/');
+ const fixture = loadFixture('./fixtures/root-dynamic/');
+ await fixture.runBuild();
+
const { default: handler } = await import(
'./fixtures/root-dynamic/.netlify/edge-functions/entry.js'
);
const response = await handler(new Request('http://example.com/styles.css'));
assertEquals(response, undefined, 'No response because this is an asset');
- await close();
+ await fixture.cleanup();
},
});
diff --git a/packages/integrations/netlify/test/edge-functions/test-utils.ts b/packages/integrations/netlify/test/edge-functions/test-utils.ts
index 2025c45b3..ed6e4c20c 100644
--- a/packages/integrations/netlify/test/edge-functions/test-utils.ts
+++ b/packages/integrations/netlify/test/edge-functions/test-utils.ts
@@ -1,29 +1,50 @@
import { fromFileUrl, readableStreamFromReader } from './deps.ts';
const dir = new URL('./', import.meta.url);
-export async function runBuild(fixturePath: string) {
- let proc = Deno.run({
- cmd: ['node', '../../../../../../astro/astro.js', 'build', '--silent'],
- cwd: fromFileUrl(new URL(fixturePath, dir)),
- });
- await proc.status();
- return async () => await proc.close();
-}
+export function loadFixture(fixturePath: string, envionmentVariables?: Record<string, string>) {
+ async function runBuild() {
+ const proc = Deno.run({
+ cmd: ['node', '../../../../../../astro/astro.js', 'build'],
+ env: envionmentVariables,
+ cwd: fromFileUrl(new URL(fixturePath, dir)),
+ });
+ await proc.status();
+ proc.close();
+ }
-export async function runApp(entryPath: string) {
- const entryUrl = new URL(entryPath, dir);
- let proc = Deno.run({
- cmd: ['deno', 'run', '--allow-env', '--allow-net', fromFileUrl(entryUrl)],
- //cwd: fromFileUrl(entryUrl),
- stderr: 'piped',
- });
- const stderr = readableStreamFromReader(proc.stderr);
- const dec = new TextDecoder();
- for await (let bytes of stderr) {
- let msg = dec.decode(bytes);
- if (msg.includes(`Server running`)) {
- break;
+ async function runApp(entryPath: string) {
+ const entryUrl = new URL(entryPath, dir);
+ let proc = Deno.run({
+ cmd: ['deno', 'run', '--allow-env', '--allow-net', fromFileUrl(entryUrl)],
+ env: envionmentVariables,
+ //cwd: fromFileUrl(entryUrl),
+ stderr: 'piped',
+ });
+ const stderr = readableStreamFromReader(proc.stderr);
+ const dec = new TextDecoder();
+ for await (let bytes of stderr) {
+ let msg = dec.decode(bytes);
+ if (msg.includes(`Server running`)) {
+ break;
+ }
}
+ return () => proc.close();
}
- return () => proc.close();
+
+ async function cleanup() {
+ const netlifyPath = new URL('.netlify', new URL(fixturePath, dir));
+ const distPath = new URL('dist', new URL(fixturePath, dir));
+
+ // remove the netlify folder
+ await Deno.remove(netlifyPath, { recursive: true });
+
+ // remove the dist folder
+ await Deno.remove(distPath, { recursive: true });
+ }
+
+ return {
+ runApp,
+ runBuild,
+ cleanup,
+ };
}
diff --git a/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/one.astro b/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/one.astro
index 12146450e..342e98cfa 100644
--- a/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/one.astro
+++ b/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/one.astro
@@ -1,5 +1,5 @@
---
-export const prerender = true;
+export const prerender = import.meta.env.PRERENDER;
---
<html>
<head>
diff --git a/packages/integrations/netlify/test/functions/prerender.test.js b/packages/integrations/netlify/test/functions/prerender.test.js
index 324ebc5c5..9718df083 100644
--- a/packages/integrations/netlify/test/functions/prerender.test.js
+++ b/packages/integrations/netlify/test/functions/prerender.test.js
@@ -1,12 +1,14 @@
import { expect } from 'chai';
import netlifyAdapter from '../../dist/index.js';
import { loadFixture, testIntegration } from './test-utils.js';
+import { after } from 'node:test';
describe('Mixed Prerendering with SSR', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
before(async () => {
+ process.env.PRERENDER = true;
fixture = await loadFixture({
root: new URL('./fixtures/prerender/', import.meta.url).toString(),
output: 'server',
@@ -18,13 +20,56 @@ describe('Mixed Prerendering with SSR', () => {
});
await fixture.build();
});
+
+ after(() => {
+ delete process.env.PRERENDER;
+ });
+
it('Wildcard 404 is sorted last', async () => {
const redir = await fixture.readFile('/_redirects');
const baseRouteIndex = redir.indexOf('/ /.netlify/functions/entry 200');
const oneRouteIndex = redir.indexOf('/one /one/index.html 200');
const fourOhFourWildCardIndex = redir.indexOf('/* /.netlify/functions/entry 404');
+ expect(oneRouteIndex).to.not.be.equal(-1);
expect(fourOhFourWildCardIndex).to.be.greaterThan(baseRouteIndex);
expect(fourOhFourWildCardIndex).to.be.greaterThan(oneRouteIndex);
});
});
+
+describe('Mixed Hybrid rendering with SSR', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+
+ before(async () => {
+ process.env.PRERENDER = false;
+ fixture = await loadFixture({
+ root: new URL('./fixtures/prerender/', import.meta.url).toString(),
+ output: 'hybrid',
+ experimental: {
+ hybridOutput: true,
+ },
+ adapter: netlifyAdapter({
+ dist: new URL('./fixtures/prerender/dist/', import.meta.url),
+ }),
+ site: `http://example.com`,
+ integrations: [testIntegration()],
+ });
+ await fixture.build();
+ });
+
+ after(() => {
+ delete process.env.PRERENDER;
+ });
+
+ it('outputs a correct redirect file', async () => {
+ const redir = await fixture.readFile('/_redirects');
+ const baseRouteIndex = redir.indexOf('/one /.netlify/functions/entry 200');
+ const rootRouteIndex = redir.indexOf('/ /index.html 200');
+ const fourOhFourIndex = redir.indexOf('/404 /404.html 200');
+
+ expect(rootRouteIndex).to.not.be.equal(-1);
+ expect(baseRouteIndex).to.not.be.equal(-1);
+ expect(fourOhFourIndex).to.not.be.equal(-1);
+ });
+});