New code (#8559)
This commit is contained in:
parent
4ed410db50
commit
87d5b841af
27 changed files with 1 additions and 1436 deletions
File diff suppressed because one or more lines are too long
|
@ -1,176 +1,3 @@
|
||||||
# @astrojs/deno 🦖
|
# @astrojs/deno 🦖
|
||||||
|
|
||||||
This adapter allows Astro to deploy your SSR site to Deno targets.
|
This adapter is no longer maintained by Astro. Please see [the new repository for the Deno adapter](https://github.com/withastro/netlify-adapter) which is now maintained by the Deno organization.
|
||||||
|
|
||||||
Learn how to deploy your Astro site in our [Deno Deploy deployment guide](https://docs.astro.build/en/guides/deploy/deno/).
|
|
||||||
|
|
||||||
- <strong>[Why Astro Deno](#why-astro-deno)</strong>
|
|
||||||
- <strong>[Installation](#installation)</strong>
|
|
||||||
- <strong>[Usage](#usage)</strong>
|
|
||||||
- <strong>[Configuration](#configuration)</strong>
|
|
||||||
- <strong>[Examples](#examples)</strong>
|
|
||||||
- <strong>[Troubleshooting](#troubleshooting)</strong>
|
|
||||||
- <strong>[Contributing](#contributing)</strong>
|
|
||||||
- <strong>[Changelog](#changelog)</strong>
|
|
||||||
|
|
||||||
## Why Astro Deno
|
|
||||||
|
|
||||||
If you're using Astro as a static site builder—its behavior out of the box—you don't need an adapter.
|
|
||||||
|
|
||||||
If you wish to [use server-side rendering (SSR)](https://docs.astro.build/en/guides/server-side-rendering/), Astro requires an adapter that matches your deployment runtime.
|
|
||||||
|
|
||||||
[Deno](https://deno.land/) is a runtime similar to Node, but with an API that's more similar to the browser's API. This adapter provides access to Deno's API and creates a script to run your project on a Deno server.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Add the Deno adapter to enable SSR in your Astro project with the following `astro add` command. This will install the adapter and make the appropriate changes to your `astro.config.mjs` file in one step.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Using NPM
|
|
||||||
npx astro add deno
|
|
||||||
# Using Yarn
|
|
||||||
yarn astro add deno
|
|
||||||
# Using PNPM
|
|
||||||
pnpm astro add deno
|
|
||||||
```
|
|
||||||
|
|
||||||
If you prefer to install the adapter manually instead, complete the following two steps:
|
|
||||||
|
|
||||||
1. Install the Deno adapter to your project’s dependencies using your preferred package manager. If you’re using npm or aren’t sure, run this in the terminal:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install @astrojs/deno
|
|
||||||
```
|
|
||||||
|
|
||||||
1. Update your `astro.config.mjs` project configuration file with the changes below.
|
|
||||||
|
|
||||||
```js ins={3,6-7}
|
|
||||||
// astro.config.mjs
|
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import deno from '@astrojs/deno';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
output: 'server',
|
|
||||||
adapter: deno(),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, update your `preview` script in `package.json` to run `deno`:
|
|
||||||
|
|
||||||
```json ins={8}
|
|
||||||
// package.json
|
|
||||||
{
|
|
||||||
// ...
|
|
||||||
"scripts": {
|
|
||||||
"dev": "astro dev",
|
|
||||||
"start": "astro dev",
|
|
||||||
"build": "astro build",
|
|
||||||
"preview": "deno run --allow-net --allow-read --allow-env ./dist/server/entry.mjs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can now use this command to preview your production Astro site locally with Deno.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run preview
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
After [performing a build](https://docs.astro.build/en/guides/deploy/#building-your-site-locally) there will be a `dist/server/entry.mjs` module. You can start a server by importing this module in your Deno app:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import './dist/server/entry.mjs';
|
|
||||||
```
|
|
||||||
|
|
||||||
See the `start` option below for how you can have more control over starting the Astro server.
|
|
||||||
|
|
||||||
You can also run the script directly using deno:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
deno run --allow-net --allow-read --allow-env ./dist/server/entry.mjs
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
To configure this adapter, pass an object to the `deno()` function call in `astro.config.mjs`.
|
|
||||||
|
|
||||||
```js
|
|
||||||
// astro.config.mjs
|
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import deno from '@astrojs/deno';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
output: 'server',
|
|
||||||
adapter: deno({
|
|
||||||
//options go here
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### start
|
|
||||||
|
|
||||||
This adapter automatically starts a server when it is imported. You can turn this off with the `start` option:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import deno from '@astrojs/deno';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
output: 'server',
|
|
||||||
adapter: deno({
|
|
||||||
start: false,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
If you disable this, you need to write your own Deno web server. Import and call `handle` from the generated entry script to render requests:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { serve } from 'https://deno.land/std@0.167.0/http/server.ts';
|
|
||||||
import { handle } from './dist/server/entry.mjs';
|
|
||||||
|
|
||||||
serve((req: Request) => {
|
|
||||||
// Check the request, maybe do static file handling here.
|
|
||||||
|
|
||||||
return handle(req);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### port and hostname
|
|
||||||
|
|
||||||
You can set the port (default: `8085`) and hostname (default: `0.0.0.0`) for the deno server to use. If `start` is false, this has no effect; your own server must configure the port and hostname.
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import deno from '@astrojs/deno';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
output: 'server',
|
|
||||||
adapter: deno({
|
|
||||||
port: 8081,
|
|
||||||
hostname: 'myhost',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
The [Astro Deno](https://github.com/withastro/astro/tree/main/examples/deno) example includes a `preview` command that runs the entry script directly. Run `npm run build` then `npm run preview` to run the production deno server.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
For help, check out the `#support` channel on [Discord](https://astro.build/chat). Our friendly Support Squad members are here to help!
|
|
||||||
|
|
||||||
You can also check our [Astro Integration Documentation][astro-integration] for more on integrations.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
This package is maintained by Astro's Core team. You're welcome to submit an issue or PR!
|
|
||||||
|
|
||||||
## Changelog
|
|
||||||
|
|
||||||
See [CHANGELOG.md](CHANGELOG.md) for a history of changes to this integration.
|
|
||||||
|
|
||||||
[astro-integration]: https://docs.astro.build/en/guides/integrations-guide/
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@astrojs/deno",
|
|
||||||
"description": "Deploy your site to a Deno server",
|
|
||||||
"version": "5.0.1",
|
|
||||||
"type": "module",
|
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"author": "withastro",
|
|
||||||
"license": "MIT",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/withastro/astro.git",
|
|
||||||
"directory": "packages/integrations/deno"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"withastro",
|
|
||||||
"astro-adapter"
|
|
||||||
],
|
|
||||||
"bugs": "https://github.com/withastro/astro/issues",
|
|
||||||
"homepage": "https://docs.astro.build/en/guides/integrations-guide/deno/",
|
|
||||||
"exports": {
|
|
||||||
".": "./dist/index.js",
|
|
||||||
"./server.js": "./dist/server.js",
|
|
||||||
"./__deno_imports.js": "./dist/__deno_imports.js",
|
|
||||||
"./package.json": "./package.json"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
|
|
||||||
"build:ci": "astro-scripts build \"src/**/*.ts\"",
|
|
||||||
"dev": "astro-scripts dev \"src/**/*.ts\"",
|
|
||||||
"test": "deno test --allow-run --allow-env --allow-read --allow-net ./test/"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"esbuild": "^0.19.2"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"astro": "workspace:^3.1.4"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"astro": "workspace:*",
|
|
||||||
"astro-scripts": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
// 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() {}
|
|
|
@ -1,196 +0,0 @@
|
||||||
import type { AstroAdapter, AstroIntegration } from 'astro';
|
|
||||||
import esbuild from 'esbuild';
|
|
||||||
import * as fs from 'node:fs';
|
|
||||||
import * as npath from 'node:path';
|
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
import type { BuildConfig, Options } from './types';
|
|
||||||
|
|
||||||
const SHIM = `globalThis.process = {
|
|
||||||
argv: [],
|
|
||||||
env: Deno.env.toObject(),
|
|
||||||
};`;
|
|
||||||
|
|
||||||
const DENO_VERSION = `0.177.0`;
|
|
||||||
// REF: https://github.com/denoland/deno/tree/main/ext/node/polyfills
|
|
||||||
const COMPATIBLE_NODE_MODULES = [
|
|
||||||
'assert',
|
|
||||||
'assert/strict',
|
|
||||||
'async_hooks',
|
|
||||||
'buffer',
|
|
||||||
'child_process',
|
|
||||||
'cluster',
|
|
||||||
'console',
|
|
||||||
'constants',
|
|
||||||
'crypto',
|
|
||||||
'dgram',
|
|
||||||
'diagnostics_channel',
|
|
||||||
'dns',
|
|
||||||
'events',
|
|
||||||
'fs',
|
|
||||||
'fs/promises',
|
|
||||||
'http',
|
|
||||||
// 'http2',
|
|
||||||
'https',
|
|
||||||
'inspector',
|
|
||||||
'module',
|
|
||||||
'net',
|
|
||||||
'os',
|
|
||||||
'path',
|
|
||||||
'path/posix',
|
|
||||||
'path/win32',
|
|
||||||
'perf_hooks',
|
|
||||||
'process',
|
|
||||||
'punycode',
|
|
||||||
'querystring',
|
|
||||||
'readline',
|
|
||||||
'repl',
|
|
||||||
'stream',
|
|
||||||
'stream/promises',
|
|
||||||
'stream/web',
|
|
||||||
'string_decoder',
|
|
||||||
'sys',
|
|
||||||
'timers',
|
|
||||||
'timers/promises',
|
|
||||||
// 'tls',
|
|
||||||
'trace_events',
|
|
||||||
'tty',
|
|
||||||
'url',
|
|
||||||
'util',
|
|
||||||
'util/types',
|
|
||||||
// 'v8',
|
|
||||||
// 'vm',
|
|
||||||
// 'wasi',
|
|
||||||
// 'webcrypto',
|
|
||||||
'worker_threads',
|
|
||||||
'zlib',
|
|
||||||
];
|
|
||||||
|
|
||||||
// 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',
|
|
||||||
serverEntrypoint: '@astrojs/deno/server.js',
|
|
||||||
args: args ?? {},
|
|
||||||
exports: ['stop', 'handle', 'start', 'running'],
|
|
||||||
supportedAstroFeatures: {
|
|
||||||
hybridOutput: 'stable',
|
|
||||||
staticOutput: 'stable',
|
|
||||||
serverOutput: 'stable',
|
|
||||||
assets: {
|
|
||||||
supportKind: 'stable',
|
|
||||||
isSharpCompatible: false,
|
|
||||||
isSquooshCompatible: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const denoImportsShimPlugin = {
|
|
||||||
name: '@astrojs/deno:shim',
|
|
||||||
setup(build: esbuild.PluginBuild) {
|
|
||||||
build.onLoad({ filter: /__deno_imports\.js$/ }, async () => {
|
|
||||||
return {
|
|
||||||
contents: DENO_IMPORTS,
|
|
||||||
loader: 'js',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const denoRenameNodeModulesPlugin = {
|
|
||||||
name: '@astrojs/esbuild-rename-node-modules',
|
|
||||||
setup(build: esbuild.PluginBuild) {
|
|
||||||
const filter = new RegExp(COMPATIBLE_NODE_MODULES.map((mod) => `(^${mod}$)`).join('|'));
|
|
||||||
build.onResolve({ filter }, (args) => ({ path: 'node:' + args.path, external: true }));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function createIntegration(args?: Options): AstroIntegration {
|
|
||||||
let _buildConfig: BuildConfig;
|
|
||||||
let _vite: any;
|
|
||||||
return {
|
|
||||||
name: '@astrojs/deno',
|
|
||||||
hooks: {
|
|
||||||
'astro:config:done': ({ setAdapter, config }) => {
|
|
||||||
setAdapter(getAdapter(args));
|
|
||||||
_buildConfig = config.build;
|
|
||||||
|
|
||||||
if (config.output === 'static') {
|
|
||||||
console.warn(
|
|
||||||
`[@astrojs/deno] \`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.`
|
|
||||||
);
|
|
||||||
console.warn(
|
|
||||||
`[@astrojs/deno] Otherwise, this adapter is not required to deploy a static site to Deno.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'astro:build:setup': ({ vite, target }) => {
|
|
||||||
if (target === 'server') {
|
|
||||||
_vite = vite;
|
|
||||||
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' }];
|
|
||||||
|
|
||||||
if (Array.isArray(vite.resolve.alias)) {
|
|
||||||
vite.resolve.alias = [...vite.resolve.alias, ...aliases];
|
|
||||||
} else {
|
|
||||||
for (const alias of aliases) {
|
|
||||||
(vite.resolve.alias as Record<string, string>)[alias.find] = alias.replacement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 () => {
|
|
||||||
const entryUrl = new URL(_buildConfig.serverEntry, _buildConfig.server);
|
|
||||||
const pth = fileURLToPath(entryUrl);
|
|
||||||
|
|
||||||
await esbuild.build({
|
|
||||||
target: 'esnext',
|
|
||||||
platform: 'browser',
|
|
||||||
entryPoints: [pth],
|
|
||||||
outfile: pth,
|
|
||||||
allowOverwrite: true,
|
|
||||||
format: 'esm',
|
|
||||||
bundle: true,
|
|
||||||
external: [
|
|
||||||
...COMPATIBLE_NODE_MODULES.map((mod) => `node:${mod}`),
|
|
||||||
'@astrojs/markdown-remark',
|
|
||||||
],
|
|
||||||
plugins: [denoImportsShimPlugin, denoRenameNodeModulesPlugin],
|
|
||||||
banner: {
|
|
||||||
js: SHIM,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove chunks, if they exist. Since we have bundled via esbuild these chunks are trash.
|
|
||||||
try {
|
|
||||||
const chunkFileNames =
|
|
||||||
_vite?.build?.rollupOptions?.output?.chunkFileNames ?? `chunks/chunk.[hash].mjs`;
|
|
||||||
const chunkPath = npath.dirname(chunkFileNames);
|
|
||||||
const chunksDirUrl = new URL(chunkPath + '/', _buildConfig.server);
|
|
||||||
await fs.promises.rm(chunksDirUrl, { recursive: true, force: true });
|
|
||||||
} catch {}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
// Normal Imports
|
|
||||||
import type { SSRManifest } from 'astro';
|
|
||||||
import { App } from 'astro/app';
|
|
||||||
import type { Options } from './types';
|
|
||||||
|
|
||||||
// @ts-expect-error
|
|
||||||
import { fromFileUrl, serveFile, Server } from '@astrojs/deno/__deno_imports.js';
|
|
||||||
|
|
||||||
let _server: Server | undefined = undefined;
|
|
||||||
let _startPromise: Promise<void> | undefined = undefined;
|
|
||||||
|
|
||||||
async function* getPrerenderedFiles(clientRoot: URL): AsyncGenerator<URL> {
|
|
||||||
// @ts-expect-error
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientRoot = new URL('../client/', import.meta.url);
|
|
||||||
const app = new App(manifest);
|
|
||||||
const handler = async (request: Request, connInfo: any) => {
|
|
||||||
if (app.match(request)) {
|
|
||||||
let ip = connInfo?.remoteAddr?.hostname;
|
|
||||||
Reflect.set(request, Symbol.for('astro.clientAddress'), ip);
|
|
||||||
const response = await app.render(request);
|
|
||||||
if (app.setCookieHeaders) {
|
|
||||||
for (const setCookieHeader of app.setCookieHeaders(response)) {
|
|
||||||
response.headers.append('Set-Cookie', setCookieHeader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the request path wasn't found in astro,
|
|
||||||
// try to fetch a static file instead
|
|
||||||
const url = new URL(request.url);
|
|
||||||
const localPath = new URL('./' + app.removeBase(url.pathname), clientRoot);
|
|
||||||
|
|
||||||
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) {
|
|
||||||
// Render the astro custom 404 page
|
|
||||||
const response = await app.render(request);
|
|
||||||
|
|
||||||
if (app.setCookieHeaders) {
|
|
||||||
for (const setCookieHeader of app.setCookieHeaders(response)) {
|
|
||||||
response.headers.append('Set-Cookie', setCookieHeader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
|
|
||||||
// If the static file is found
|
|
||||||
} else {
|
|
||||||
return fileResp;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const port = options.port ?? 8085;
|
|
||||||
_server = new Server({
|
|
||||||
port,
|
|
||||||
hostname: options.hostname ?? '0.0.0.0',
|
|
||||||
handler,
|
|
||||||
});
|
|
||||||
|
|
||||||
_startPromise = Promise.resolve(_server.listenAndServe());
|
|
||||||
console.error(`Server running on port ${port}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createExports(manifest: SSRManifest, options: Options) {
|
|
||||||
const app = new App(manifest);
|
|
||||||
return {
|
|
||||||
async stop() {
|
|
||||||
if (_server) {
|
|
||||||
_server.close();
|
|
||||||
_server = undefined;
|
|
||||||
}
|
|
||||||
await Promise.resolve(_startPromise);
|
|
||||||
},
|
|
||||||
running() {
|
|
||||||
return _server !== undefined;
|
|
||||||
},
|
|
||||||
async start() {
|
|
||||||
return start(manifest, options);
|
|
||||||
},
|
|
||||||
async handle(request: Request) {
|
|
||||||
return app.render(request);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
export interface Options {
|
|
||||||
port?: number;
|
|
||||||
hostname?: string;
|
|
||||||
start?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BuildConfig {
|
|
||||||
server: URL;
|
|
||||||
serverEntry: string;
|
|
||||||
assets: string;
|
|
||||||
}
|
|
|
@ -1,121 +0,0 @@
|
||||||
/* Deno types consider DOM elements nullable */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
|
|
||||||
import { DOMParser } from 'https://deno.land/x/deno_dom@v0.1.35-alpha/deno-dom-wasm.ts';
|
|
||||||
import { assert, assertEquals } from 'https://deno.land/std@0.158.0/testing/asserts.ts';
|
|
||||||
import { runBuildAndStartApp, defaultTestPermissions } from './helpers.ts';
|
|
||||||
|
|
||||||
// this needs to be here and not in the specific test case, because
|
|
||||||
// the variables are loaded in the global scope of the built server
|
|
||||||
// module, which is only executed once upon the first load
|
|
||||||
const varContent = 'this is a value stored in env variable';
|
|
||||||
Deno.env.set('SOME_VARIABLE', varContent);
|
|
||||||
|
|
||||||
Deno.test({
|
|
||||||
name: 'Basics',
|
|
||||||
permissions: defaultTestPermissions,
|
|
||||||
sanitizeResources: false,
|
|
||||||
sanitizeOps: false,
|
|
||||||
async fn(t) {
|
|
||||||
const app = await runBuildAndStartApp('./fixtures/basics/');
|
|
||||||
|
|
||||||
await t.step('Works', async () => {
|
|
||||||
const resp = await fetch(app.url);
|
|
||||||
assertEquals(resp.status, 200);
|
|
||||||
|
|
||||||
const html = await resp.text();
|
|
||||||
assert(html);
|
|
||||||
|
|
||||||
const doc = new DOMParser().parseFromString(html, `text/html`);
|
|
||||||
const div = doc!.querySelector('#react');
|
|
||||||
|
|
||||||
assert(div, 'div exists');
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step('Custom 404', async () => {
|
|
||||||
const resp = await fetch(new URL('this-does-not-exist', app.url));
|
|
||||||
assertEquals(resp.status, 404);
|
|
||||||
|
|
||||||
const html = await resp.text();
|
|
||||||
assert(html);
|
|
||||||
|
|
||||||
const doc = new DOMParser().parseFromString(html, `text/html`);
|
|
||||||
const header = doc!.querySelector('#custom-404');
|
|
||||||
assert(header, 'displays custom 404');
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step('Loads style assets', async () => {
|
|
||||||
let resp = await fetch(app.url);
|
|
||||||
const html = await resp.text();
|
|
||||||
|
|
||||||
const doc = new DOMParser().parseFromString(html, `text/html`);
|
|
||||||
const style = doc!.querySelector('style');
|
|
||||||
|
|
||||||
assert(style?.textContent?.includes('Courier New'));
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step('Correctly loads run-time env variables', async () => {
|
|
||||||
const resp = await fetch(app.url);
|
|
||||||
const html = await resp.text();
|
|
||||||
|
|
||||||
const doc = new DOMParser().parseFromString(html, `text/html`);
|
|
||||||
const p = doc!.querySelector('p#env-value');
|
|
||||||
assertEquals(p!.innerText, varContent);
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step('Can use a module with top-level await', async () => {
|
|
||||||
const resp = await fetch(app.url);
|
|
||||||
const html = await resp.text();
|
|
||||||
|
|
||||||
const doc = new DOMParser().parseFromString(html, `text/html`);
|
|
||||||
const p = doc!.querySelector('p#module-value');
|
|
||||||
assertEquals(p!.innerText, 'bar');
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step('Works with Markdown', async () => {
|
|
||||||
const resp = await fetch(new URL('markdown', app.url));
|
|
||||||
const html = await resp.text();
|
|
||||||
|
|
||||||
const doc = new DOMParser().parseFromString(html, `text/html`);
|
|
||||||
const h1 = doc!.querySelector('h1');
|
|
||||||
assertEquals(h1!.innerText, 'Heading from Markdown');
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step('Works with MDX', async () => {
|
|
||||||
const resp = await fetch(new URL('mdx', app.url));
|
|
||||||
const html = await resp.text();
|
|
||||||
|
|
||||||
const doc = new DOMParser().parseFromString(html, `text/html`);
|
|
||||||
const h1 = doc!.querySelector('h1');
|
|
||||||
assertEquals(h1!.innerText, 'Heading from MDX');
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step('Astro.cookies', async () => {
|
|
||||||
const url = new URL('/admin', app.url);
|
|
||||||
const resp = await fetch(url, { redirect: 'manual' });
|
|
||||||
assertEquals(resp.status, 302);
|
|
||||||
|
|
||||||
const headers = resp.headers;
|
|
||||||
assertEquals(headers.get('set-cookie'), 'logged-in=false; Max-Age=77760000; Path=/');
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step('perendering', async () => {
|
|
||||||
const resp = await fetch(new URL('/prerender', app.url));
|
|
||||||
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');
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step('node compatibility', async () => {
|
|
||||||
const resp = await fetch(new URL('/nodecompat', app.url));
|
|
||||||
assertEquals(resp.status, 200);
|
|
||||||
await resp.text();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.stop();
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,24 +0,0 @@
|
||||||
/* Deno types consider DOM elements nullable */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
|
|
||||||
import { DOMParser } from 'https://deno.land/x/deno_dom@v0.1.35-alpha/deno-dom-wasm.ts';
|
|
||||||
import { assert, assertEquals } from 'https://deno.land/std@0.158.0/testing/asserts.ts';
|
|
||||||
import { runBuildAndStartAppFromSubprocess } from './helpers.ts';
|
|
||||||
|
|
||||||
Deno.test({
|
|
||||||
name: 'Dynamic import',
|
|
||||||
async fn(t) {
|
|
||||||
const app = await runBuildAndStartAppFromSubprocess('./fixtures/dynimport/');
|
|
||||||
|
|
||||||
await t.step('Works', async () => {
|
|
||||||
const resp = await fetch(app.url);
|
|
||||||
assertEquals(resp.status, 200);
|
|
||||||
const html = await resp.text();
|
|
||||||
assert(html);
|
|
||||||
const doc = new DOMParser().parseFromString(html, `text/html`);
|
|
||||||
const div = doc!.querySelector('#thing');
|
|
||||||
assert(div, 'div exists');
|
|
||||||
});
|
|
||||||
|
|
||||||
app.stop();
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import deno from '@astrojs/deno';
|
|
||||||
import react from '@astrojs/react';
|
|
||||||
import mdx from '@astrojs/mdx';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
adapter: deno(),
|
|
||||||
integrations: [react(), mdx()],
|
|
||||||
output: 'server'
|
|
||||||
})
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@test/deno-astro-basic",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"astro": "workspace:*",
|
|
||||||
"@astrojs/deno": "workspace:*",
|
|
||||||
"@astrojs/react": "workspace:*",
|
|
||||||
"@astrojs/mdx": "workspace:*",
|
|
||||||
"react": "^18.1.0",
|
|
||||||
"react-dom": "^18.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export default function() {
|
|
||||||
return (
|
|
||||||
<div id="react">testing</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
<h1 id="custom-404">Custom 404 Page</h1>
|
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
Astro.cookies.set('logged-in', false, {
|
|
||||||
maxAge: 60 * 60 * 24 * 900,
|
|
||||||
path: '/'
|
|
||||||
});
|
|
||||||
|
|
||||||
return Astro.redirect('/login');
|
|
||||||
---
|
|
|
@ -1,17 +0,0 @@
|
||||||
---
|
|
||||||
import { someData } from '../util/data';
|
|
||||||
import ReactComponent from '../components/React.jsx';
|
|
||||||
const envValue = import.meta.env.SOME_VARIABLE;
|
|
||||||
---
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Basic App on Deno</title>
|
|
||||||
<style>body { font-family: 'Courier New', Courier, monospace; }</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Basic App on Deno</h1>
|
|
||||||
<p id="env-value">{envValue}</p>
|
|
||||||
<p id="module-value">{someData.foo}</p>
|
|
||||||
<ReactComponent />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,10 +0,0 @@
|
||||||
---
|
|
||||||
---
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Testing</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Testing</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,6 +0,0 @@
|
||||||
---
|
|
||||||
title: Title
|
|
||||||
description: Description
|
|
||||||
---
|
|
||||||
|
|
||||||
# Heading from Markdown
|
|
|
@ -1,6 +0,0 @@
|
||||||
---
|
|
||||||
title: Title
|
|
||||||
description: Description
|
|
||||||
---
|
|
||||||
|
|
||||||
# Heading from MDX
|
|
|
@ -1,15 +0,0 @@
|
||||||
---
|
|
||||||
// unprefixed node built-in module
|
|
||||||
import path from 'node:path'
|
|
||||||
|
|
||||||
// prefixed node built-in module
|
|
||||||
import os from 'node:os'
|
|
||||||
---
|
|
||||||
<body>
|
|
||||||
<a href={path.posix.basename('/public/myfile.html')}>Go to my file</a>
|
|
||||||
<details>
|
|
||||||
<summary>CPU Architecture</summary>
|
|
||||||
<code>{os.arch()}</code>
|
|
||||||
</details>
|
|
||||||
<p>Everything went fine.</p>
|
|
||||||
</body>
|
|
|
@ -1,9 +0,0 @@
|
||||||
---
|
|
||||||
export const prerender = true;
|
|
||||||
---
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h1>test</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,14 +0,0 @@
|
||||||
export interface Data {
|
|
||||||
foo: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getData(): Promise<Data> {
|
|
||||||
return new Promise((resolve, _reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve({ foo: "bar" });
|
|
||||||
}, 100);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Testing top-level await, a feature supported in esnext
|
|
||||||
export const someData = await getData();
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import deno from '@astrojs/deno';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
adapter: deno(),
|
|
||||||
output: 'server'
|
|
||||||
})
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@test/deno-astro-dynimport",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"astro": "workspace:*",
|
|
||||||
"@astrojs/deno": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
---
|
|
||||||
|
|
||||||
---
|
|
||||||
<div id="thing">testing</div>
|
|
|
@ -1,11 +0,0 @@
|
||||||
---
|
|
||||||
const { default: Thing } = await import('../components/Thing.astro');
|
|
||||||
---
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>testing</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<Thing />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,77 +0,0 @@
|
||||||
import { fromFileUrl } from 'https://deno.land/std@0.110.0/path/mod.ts';
|
|
||||||
import { assert } from 'https://deno.land/std@0.158.0/testing/asserts.ts';
|
|
||||||
import { readableStreamFromReader } from 'https://deno.land/std@0.142.0/streams/conversion.ts';
|
|
||||||
|
|
||||||
const dir = new URL('./', import.meta.url);
|
|
||||||
const defaultURL = new URL('http://localhost:8085/');
|
|
||||||
|
|
||||||
export const defaultTestPermissions: Deno.PermissionOptions = {
|
|
||||||
read: true,
|
|
||||||
net: true,
|
|
||||||
run: true,
|
|
||||||
env: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
declare type ExitCallback = () => void;
|
|
||||||
|
|
||||||
export async function runBuild(fixturePath: string) {
|
|
||||||
const proc = Deno.run({
|
|
||||||
cmd: ['node', '../../../../../astro/astro.js', 'build', '--silent'],
|
|
||||||
cwd: fromFileUrl(new URL(fixturePath, dir)),
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
const status = await proc.status();
|
|
||||||
assert(status.success);
|
|
||||||
} finally {
|
|
||||||
proc.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function startModFromImport(baseUrl: URL): Promise<ExitCallback> {
|
|
||||||
const entryUrl = new URL('./dist/server/entry.mjs', baseUrl);
|
|
||||||
const mod = await import(entryUrl.toString());
|
|
||||||
|
|
||||||
if (!mod.running()) {
|
|
||||||
mod.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => mod.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function startModFromSubprocess(baseUrl: URL): Promise<ExitCallback> {
|
|
||||||
const entryUrl = new URL('./dist/server/entry.mjs', baseUrl);
|
|
||||||
const proc = Deno.run({
|
|
||||||
cmd: ['deno', 'run', '--allow-env', '--allow-net', fromFileUrl(entryUrl)],
|
|
||||||
cwd: fromFileUrl(baseUrl),
|
|
||||||
stderr: 'piped',
|
|
||||||
});
|
|
||||||
|
|
||||||
const stderr = readableStreamFromReader(proc.stderr);
|
|
||||||
const dec = new TextDecoder();
|
|
||||||
for await (const bytes of stderr) {
|
|
||||||
const msg = dec.decode(bytes);
|
|
||||||
if (msg.includes(`Server running`)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => proc.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function runBuildAndStartApp(fixturePath: string) {
|
|
||||||
const url = new URL(fixturePath, dir);
|
|
||||||
|
|
||||||
await runBuild(fixturePath);
|
|
||||||
const stop = await startModFromImport(url);
|
|
||||||
|
|
||||||
return { url: defaultURL, stop };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function runBuildAndStartAppFromSubprocess(fixturePath: string) {
|
|
||||||
const url = new URL(fixturePath, dir);
|
|
||||||
|
|
||||||
await runBuild(fixturePath);
|
|
||||||
const stop = await startModFromSubprocess(url);
|
|
||||||
|
|
||||||
return { url: defaultURL, stop };
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "../../../tsconfig.base.json",
|
|
||||||
"include": ["src"],
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "ES2022",
|
|
||||||
"outDir": "./dist",
|
|
||||||
// TODO: Due to the shim for Deno imports in `server.ts`, we can't use moduleResolution: 'node16' or the types get very weird.
|
|
||||||
"moduleResolution": "Node"
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue