Fix prerendered page handling on Deno (#6284)
* fix(deno): handle prerendered pages * test(deno): add prerender test * fix: defensively access vite.build.rollupOptions.external * fix(deno): support other formats of rollupOptions.external * fix(deno): crawl prerendered files for match * fix(deno): ignore deno error in server file * fix(deno): cross-platform serve file
This commit is contained in:
parent
ed92730925
commit
61113dd731
9 changed files with 116 additions and 9 deletions
5
.changeset/shaggy-moons-judge.md
Normal file
5
.changeset/shaggy-moons-judge.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@astrojs/deno': patch
|
||||
---
|
||||
|
||||
Fix prerendered page behavior
|
|
@ -6,7 +6,7 @@
|
|||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "deno run --allow-net --allow-read ./dist/server/entry.mjs",
|
||||
"preview": "deno run --allow-net --allow-read --allow-env ./dist/server/entry.mjs",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
---
|
||||
import Layout from '../components/Layout.astro';
|
||||
|
||||
export const prerender = true;
|
||||
---
|
||||
|
||||
<Layout title="Welcome to Astro (on Deno).">
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./server.js": "./dist/server.js",
|
||||
"./__deno_imports.js": "./dist/__deno_imports.js",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
10
packages/integrations/deno/src/__deno_imports.ts
Normal file
10
packages/integrations/deno/src/__deno_imports.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
// This file is a shim for any Deno-specific imports!
|
||||
// It will be replaced in the final Deno build.
|
||||
//
|
||||
// This allows us to prerender pages in Node.
|
||||
export class Server {
|
||||
listenAndServe() {}
|
||||
}
|
||||
|
||||
export function serveFile() {}
|
||||
export function fromFileUrl() {}
|
|
@ -20,6 +20,16 @@ const SHIM = `globalThis.process = {
|
|||
env: Deno.env.toObject(),
|
||||
};`;
|
||||
|
||||
const DENO_VERSION = `0.177.0`
|
||||
|
||||
// We shim deno-specific imports so we can run the code in Node
|
||||
// to prerender pages. In the final Deno build, this import is
|
||||
// replaced with the Deno-specific contents listed below.
|
||||
const DENO_IMPORTS_SHIM = `@astrojs/deno/__deno_imports.js`;
|
||||
const DENO_IMPORTS = `export { Server } from "https://deno.land/std@${DENO_VERSION}/http/server.ts"
|
||||
export { serveFile } from 'https://deno.land/std@${DENO_VERSION}/http/file_server.ts';
|
||||
export { fromFileUrl } from "https://deno.land/std@${DENO_VERSION}/path/mod.ts";`
|
||||
|
||||
export function getAdapter(args?: Options): AstroAdapter {
|
||||
return {
|
||||
name: '@astrojs/deno',
|
||||
|
@ -29,6 +39,18 @@ export function getAdapter(args?: Options): AstroAdapter {
|
|||
};
|
||||
}
|
||||
|
||||
const denoImportsShimPlugin = {
|
||||
name: '@astrojs/deno:shim',
|
||||
setup(build: esbuild.PluginBuild) {
|
||||
build.onLoad({ filter: /__deno_imports\.js$/ }, async (args) => {
|
||||
return {
|
||||
contents: DENO_IMPORTS,
|
||||
loader: 'js',
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
export default function createIntegration(args?: Options): AstroIntegration {
|
||||
let _buildConfig: BuildConfig;
|
||||
let _vite: any;
|
||||
|
@ -49,8 +71,11 @@ export default function createIntegration(args?: Options): AstroIntegration {
|
|||
'astro:build:setup': ({ vite, target }) => {
|
||||
if (target === 'server') {
|
||||
_vite = vite;
|
||||
vite.resolve = vite.resolve || {};
|
||||
vite.resolve.alias = vite.resolve.alias || {};
|
||||
vite.resolve = vite.resolve ?? {};
|
||||
vite.resolve.alias = vite.resolve.alias ?? {};
|
||||
vite.build = vite.build ?? {};
|
||||
vite.build.rollupOptions = vite.build.rollupOptions ?? {};
|
||||
vite.build.rollupOptions.external = vite.build.rollupOptions.external ?? [];
|
||||
|
||||
const aliases = [{ find: 'react-dom/server', replacement: 'react-dom/server.browser' }];
|
||||
|
||||
|
@ -61,10 +86,15 @@ export default function createIntegration(args?: Options): AstroIntegration {
|
|||
(vite.resolve.alias as Record<string, string>)[alias.find] = alias.replacement;
|
||||
}
|
||||
}
|
||||
|
||||
vite.ssr = {
|
||||
noExternal: true,
|
||||
};
|
||||
|
||||
if (Array.isArray(vite.build.rollupOptions.external)) {
|
||||
vite.build.rollupOptions.external.push(DENO_IMPORTS_SHIM);
|
||||
} else if (typeof vite.build.rollupOptions.external !== 'function') {
|
||||
vite.build.rollupOptions.external = [vite.build.rollupOptions.external, DENO_IMPORTS_SHIM]
|
||||
}
|
||||
}
|
||||
},
|
||||
'astro:build:done': async () => {
|
||||
|
@ -80,6 +110,9 @@ export default function createIntegration(args?: Options): AstroIntegration {
|
|||
format: 'esm',
|
||||
bundle: true,
|
||||
external: ['@astrojs/markdown-remark'],
|
||||
plugins: [
|
||||
denoImportsShimPlugin
|
||||
],
|
||||
banner: {
|
||||
js: SHIM,
|
||||
},
|
||||
|
|
|
@ -3,9 +3,7 @@ import type { SSRManifest } from 'astro';
|
|||
import { App } from 'astro/app';
|
||||
|
||||
// @ts-ignore
|
||||
import { Server } from 'https://deno.land/std@0.167.0/http/server.ts';
|
||||
// @ts-ignore
|
||||
import { fetch } from 'https://deno.land/x/file_fetch/mod.ts';
|
||||
import { Server, serveFile, fromFileUrl } from '@astrojs/deno/__deno_imports.js';
|
||||
|
||||
interface Options {
|
||||
port?: number;
|
||||
|
@ -16,6 +14,17 @@ interface Options {
|
|||
let _server: Server | undefined = undefined;
|
||||
let _startPromise: Promise<void> | undefined = undefined;
|
||||
|
||||
async function* getPrerenderedFiles(clientRoot: URL): AsyncGenerator<URL> {
|
||||
// @ts-ignore
|
||||
for await (const ent of Deno.readDir(clientRoot)) {
|
||||
if (ent.isDirectory) {
|
||||
yield* getPrerenderedFiles(new URL(`./${ent.name}/`, clientRoot))
|
||||
} else if (ent.name.endsWith('.html')) {
|
||||
yield new URL(`./${ent.name}`, clientRoot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function start(manifest: SSRManifest, options: Options) {
|
||||
if (options.start === false) {
|
||||
return;
|
||||
|
@ -40,7 +49,24 @@ export function start(manifest: SSRManifest, options: Options) {
|
|||
// try to fetch a static file instead
|
||||
const url = new URL(request.url);
|
||||
const localPath = new URL('./' + app.removeBase(url.pathname), clientRoot);
|
||||
const fileResp = await fetch(localPath.toString());
|
||||
|
||||
let fileResp = await serveFile(request, fromFileUrl(localPath));
|
||||
|
||||
// Attempt to serve `index.html` if 404
|
||||
if (fileResp.status == 404) {
|
||||
let fallback;
|
||||
for await (const file of getPrerenderedFiles(clientRoot)) {
|
||||
const pathname = file.pathname.replace(/\/(index)?\.html$/, '');
|
||||
if (localPath.pathname.endsWith(pathname)) {
|
||||
fallback = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fallback) {
|
||||
fileResp = await serveFile(request, fromFileUrl(fallback));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If the static file can't be found
|
||||
if (fileResp.status == 404) {
|
||||
|
|
|
@ -68,7 +68,7 @@ Deno.test({
|
|||
resp = await fetch(new URL(href!, baseUrl));
|
||||
assertEquals(resp.status, 200);
|
||||
const ct = resp.headers.get('content-type');
|
||||
assertEquals(ct, 'text/css');
|
||||
assertEquals(ct, 'text/css; charset=UTF-8');
|
||||
await resp.body!.cancel();
|
||||
});
|
||||
},
|
||||
|
@ -143,3 +143,24 @@ Deno.test({
|
|||
sanitizeResources: false,
|
||||
sanitizeOps: false,
|
||||
});
|
||||
|
||||
|
||||
Deno.test({
|
||||
name: 'perendering',
|
||||
permissions: defaultTestPermissions,
|
||||
async fn() {
|
||||
await startApp(async (baseUrl: URL) => {
|
||||
const resp = await fetch(new URL('/prerender', baseUrl));
|
||||
assertEquals(resp.status, 200);
|
||||
|
||||
const html = await resp.text();
|
||||
assert(html);
|
||||
|
||||
const doc = new DOMParser().parseFromString(html, `text/html`);
|
||||
const h1 = doc!.querySelector('h1');
|
||||
assertEquals(h1!.innerText, 'test');
|
||||
});
|
||||
},
|
||||
sanitizeResources: false,
|
||||
sanitizeOps: false,
|
||||
});
|
||||
|
|
9
packages/integrations/deno/test/fixtures/basics/src/pages/prerender.astro
vendored
Normal file
9
packages/integrations/deno/test/fixtures/basics/src/pages/prerender.astro
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
export const prerender = true;
|
||||
---
|
||||
|
||||
<html>
|
||||
<body>
|
||||
<h1>test</h1>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue