Skip to content

Commit

Permalink
feat(deno): use Deno.serve (#659)
Browse files Browse the repository at this point in the history
* feat(deno): use deno serve

* feat: add serve and handler Deno apis

* feat: add idleTimeout support to deno

* docs: add idleTimeout docs for deno

* chore: fix package.json to allow brisa/server/deno

* test: adapt test to deno

* test: setup deno tests

* chore: change workflow

* chore: update package.json scripts

* chore: move deno.json

* chore: fix pipeline

* chore: fix space

* chore: fix iden

* chore: fix

* chore: add bun

* chore: fix deno.json

* chore: add @types/node

* chore: build

* chore: move deno.json to root

* chore: remove dep

* test: fix fixtures

* test: fix imports

* test: fix test
  • Loading branch information
aralroca authored Dec 8, 2024
1 parent e21b313 commit cff9139
Show file tree
Hide file tree
Showing 17 changed files with 463 additions and 11 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,26 @@ jobs:
run: bun run build && bun run create-brisa:build
- name: Run Node.js tests
run: bun run test:node

deno-tests:
runs-on: ${{ matrix.os }}
if: ${{ !contains(github.event.head_commit.message, 'docs:') }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Setup Bun.js
uses: oven-sh/setup-bun@v1
with:
bun-version: 1.1.38
- uses: actions/checkout@v4
- name: Setup Deno
uses: denoland/setup-deno@v1
with:
deno-version: "latest"
- name: Install dependencies (if needed)
run: bun install
- name: Build project
run: bun run build && bun run create-brisa:build
- name: Run Deno tests
run: deno task test:deno
Binary file modified bun.lockb
Binary file not shown.
6 changes: 6 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"tasks": {
"test:deno": "deno test --allow-all \"**/*.deno-test.js\""
},
"nodeModulesDir": "auto"
}
35 changes: 35 additions & 0 deletions docs/api-reference/server-apis/deno/handler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
description: The handler function is the user Brisa handler in Deno. You can use it to create a custom server.
---

# `handler`

## Reference

### `handler(req: Request, info: ServeHandlerInfo<Addr>): Response | Promise<Response>`

The `handler` function is the user Brisa handler to handle the incoming requests. You can use it to create a custom server.

## Example usage:

In the next example, we use the `handler` function to use it with `Deno.serve`:

```tsx 5
import { handler } from "brisa/server/deno";

async function customServer(req, info) {
// Your implementation here ...
await handler(req, info);
}

const server = Deno.listen({ port: 3000, handler: customServer });
```

## Types

```tsx
export function handler(
req: Request,
info: ServeHandlerInfo<Addr>,
): Response | Promise<Response>;
```
46 changes: 46 additions & 0 deletions docs/api-reference/server-apis/deno/serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
description: The serve function is used to start the Deno server and listen for incoming requests.
---

# `serve`

## Reference

### `serve(options: ServeOptions): { port: number; hostname: string; server: ReturnType<typeof Deno.serve>; }`

The `serve` function is used to start the Deno server and listen for incoming requests.

## Example usage:

In the next example, we use the `serve` function to start the Deno server.

```tsx 3-5
import { serve } from "brisa/server/deno";

const { server, port, hostname } = serve({
port: 3001,
});

console.log(
"Deno Server ready 🥳",
`listening on http://${hostname}:${port}...`,
);
```

> [!IMPORTANT]
>
> Keep in mind that the `serve` for Deno is not in `brisa/server` but in `brisa/server/deno`.
> [!CAUTION]
>
> It only makes sense to use it if you need a [custom server](/building-your-application/configuring/custom-server) for extra things from the serve but if you start the server in the same way as Brisa.
## Types

```tsx
export function serve(options: ServeOptions): {
port: number;
hostname: string;
server: ReturnType<typeof Deno.serve>;
};
```
4 changes: 2 additions & 2 deletions docs/api-reference/server-apis/node/handler.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ In the next example, we use the `handler` function to create a built-in [`http.c

```tsx 6
import http from "node:http";
import { handler } from "brisa/server";
import { handler } from "brisa/server/node";

async function customServer(req, res) {
// Your implementation here ...
Expand Down Expand Up @@ -57,4 +57,4 @@ export function handler(
req: http.IncomingMessage,
res: http.ServerResponse,
): Promise<void>;
```
```
2 changes: 1 addition & 1 deletion docs/building-your-application/configuring/idle-timeout.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ export type Configuration = {

> [!NOTE]
>
> This `idleTimeout` is working in Bun and Node.js runtimes.
> This `idleTimeout` is working in Bun, Deno and Node.js runtimes.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"release-adapter-vercel:canary": "bun run --cwd packages/adapter-vercel build && npm publish --workspace=packages/adapter-vercel --tag next --access public",
"test": "bun run --filter 'brisa' test",
"test:node": "node --test \"**/*.node-test.js\"",
"test:deno": "deno test --allow-all \"**/*.deno-test.js\"",
"test:coverage": "bun run --cwd packages/brisa --coverage",
"tsc:check": "bun run --cwd packages/brisa tsc:check",
"update-version": "bun run scripts/update-brisa-version.ts",
Expand Down
9 changes: 7 additions & 2 deletions packages/brisa/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@
"bun": "./server/node.js",
"node": "./server/node.js"
},
"./server/deno": {
"import": "./server/deno.js",
"require": "./server/deno.js",
"bun": "./server/deno.js",
"node": "./server/deno.js"
},
"./test": {
"import": "./test/index.js",
"require": "./test/index.js",
Expand Down Expand Up @@ -110,15 +116,14 @@
"build:core-macros": "bun build --minify --target=bun --outdir=macros src/core/macros/index.ts && bun run build:bin",
"build:core-client": "bun build --minify --outdir=client src/core/client/index.ts && bun run build:core-client-simplified && bun run build:bin",
"build:core-client-simplified": "bun build --minify --outdir=client-simplified src/core/client/index.ts --define '__TRAILING_SLASH__=false' --define '__WEB_CONTEXT_PLUGINS__=false' --define '__BASE_PATH__=\"\"' --define '__ASSET_PREFIX__=\"\"' --define '__USE_LOCALE__=false' --define '__USE_PAGE_TRANSLATION__=false'",
"build:core-server": "bun build --minify --target=node --outdir=server src/core/server/index.ts src/core/server/node.ts && bun run build:bin && cp src/types/server.d.ts server/index.d.ts && cp src/types/server.node.d.ts server/node.d.ts",
"build:core-server": "bun build --minify --target=node --outdir=server src/core/server/index.ts src/core/server/node.ts src/core/server/deno.ts && bun run build:bin && cp src/types/server.d.ts server/index.d.ts && cp src/types/server.node.d.ts server/node.d.ts && cp src/types/server.deno.d.ts server/deno.d.ts",
"build:core-test": "bun build --minify --target=bun --outdir=test src/core/test/index.ts && bun run build:bin && cp src/types/test.d.ts test/index.d.ts",
"build:jsx-dev-runtime": "bun build --minify --target=bun --outdir=jsx-dev-runtime src/jsx-runtime/index.ts && cp src/types/index.d.ts jsx-dev-runtime/index.d.ts",
"build:jsx-runtime": "bun build --minify --target=bun --outdir=jsx-runtime src/jsx-runtime/index.ts && cp src/types/index.d.ts jsx-runtime/index.d.ts",
"clean": "rm -rf out jsx-runtime jsx-dev-runtime cli client server cli.js",
"release": "bun run build && npm publish && bun run clean",
"release:canary": "bun run build && npm publish --tag next && bun run clean",
"test": "bun test",
"test:node": "node --test \"**/*.node-test.js\"",
"test:coverage": "bun test --coverage",
"tsc:check": "tsc --noEmit --skipLibCheck"
},
Expand Down
145 changes: 145 additions & 0 deletions packages/brisa/src/cli/serve/deno-serve/handler.deno-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { assertEquals, assert } from 'jsr:@std/assert';
import { resolve } from 'node:path';

const FIXTURES_DIR = resolve(
import.meta.dirname,
'..',
'..',
'..',
'__fixtures__',
);

const absolutePath = new URL('../../../../server/deno.js', import.meta.url)
.href;

Deno.test('should resolve a page', async () => {
globalThis.mockConstants = {
IS_SERVE_PROCESS: true,
ROOT_DIR: FIXTURES_DIR,
SRC_DIR: resolve(FIXTURES_DIR, 'js'),
BUILD_DIR: resolve(FIXTURES_DIR, 'js'),
ASSETS_DIR: resolve(FIXTURES_DIR, 'public'),
PAGES_DIR: resolve(FIXTURES_DIR, 'js', 'pages'),
CONFIG: {},
HEADERS: {
CACHE_CONTROL: 'no-cache, no-store, must-revalidate',
},
LOG_PREFIX: {},
};

const req = new Request('http://localhost/');
const { handler } = await import(absolutePath);

const response = await handler(req);
assertEquals(response.status, 200);
assert((await response.text()).includes('<title>Brisa</title><'));
assertEquals(
response.headers.get('cache-control'),
'no-cache, no-store, must-revalidate',
);
});

Deno.test('should redirect to the locale', async () => {
globalThis.mockConstants = {
IS_SERVE_PROCESS: true,
ROOT_DIR: FIXTURES_DIR,
SRC_DIR: resolve(FIXTURES_DIR, 'js'),
BUILD_DIR: resolve(FIXTURES_DIR, 'js'),
ASSETS_DIR: resolve(FIXTURES_DIR, 'public'),
PAGES_DIR: resolve(FIXTURES_DIR, 'js', 'pages'),
I18N_CONFIG: {
locales: ['en', 'es'],
defaultLocale: 'es',
},
LOCALES_SET: new Set(['en', 'es']),
CONFIG: {},
HEADERS: {
CACHE_CONTROL: 'no-cache, no-store, must-revalidate',
},
LOG_PREFIX: {},
};

const req = new Request('http://localhost/somepage');
const { handler } = await import(absolutePath);

const response = await handler(req);
assertEquals(response.status, 301);
assertEquals(response.headers.get('location'), '/es/somepage');
});

Deno.test('should redirect to trailingSlash', async () => {
globalThis.mockConstants = {
IS_SERVE_PROCESS: true,
ROOT_DIR: FIXTURES_DIR,
SRC_DIR: resolve(FIXTURES_DIR, 'js'),
BUILD_DIR: resolve(FIXTURES_DIR, 'js'),
ASSETS_DIR: resolve(FIXTURES_DIR, 'public'),
PAGES_DIR: resolve(FIXTURES_DIR, 'js', 'pages'),
CONFIG: {
trailingSlash: true,
},
HEADERS: {
CACHE_CONTROL: 'no-cache, no-store, must-revalidate',
},
LOG_PREFIX: {},
};

const req = new Request('http://localhost/somepage');
const { handler } = await import(absolutePath);

const response = await handler(req);
assertEquals(response.status, 301);
assertEquals(response.headers.get('location'), 'http://localhost/somepage/');
});

Deno.test('should redirect locale and trailing slash', async () => {
globalThis.mockConstants = {
IS_SERVE_PROCESS: true,
ROOT_DIR: FIXTURES_DIR,
SRC_DIR: resolve(FIXTURES_DIR, 'js'),
BUILD_DIR: resolve(FIXTURES_DIR, 'js'),
ASSETS_DIR: resolve(FIXTURES_DIR, 'public'),
PAGES_DIR: resolve(FIXTURES_DIR, 'js', 'pages'),
I18N_CONFIG: {
locales: ['en', 'es'],
defaultLocale: 'es',
},
LOCALES_SET: new Set(['en', 'es']),
CONFIG: {
trailingSlash: true,
},
HEADERS: {
CACHE_CONTROL: 'no-cache, no-store, must-revalidate',
},
LOG_PREFIX: {},
};

const req = new Request('http://localhost/somepage');
const { handler } = await import(absolutePath);

const response = await handler(req);
assertEquals(response.status, 301);
assertEquals(response.headers.get('location'), '/es/somepage/');
});

Deno.test('should return 404 if the asset does not exist', async () => {
globalThis.mockConstants = {
IS_SERVE_PROCESS: true,
ROOT_DIR: FIXTURES_DIR,
SRC_DIR: resolve(FIXTURES_DIR, 'js'),
BUILD_DIR: resolve(FIXTURES_DIR, 'js'),
ASSETS_DIR: resolve(FIXTURES_DIR, 'public'),
PAGES_DIR: resolve(FIXTURES_DIR, 'js', 'pages'),
CONFIG: {},
HEADERS: {
CACHE_CONTROL: 'no-cache, no-store, must-revalidate',
},
LOG_PREFIX: {},
};

const req = new Request('http://localhost/not-found.ico');
const { handler } = await import(absolutePath);

const response = await handler(req);
assertEquals(response.status, 404);
});
35 changes: 35 additions & 0 deletions packages/brisa/src/cli/serve/deno-serve/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { ServeOptions, TLSOptions } from 'bun';
import { getServeOptions } from '../serve-options';

const serveOptions = await getServeOptions();

export default function serve(options: ServeOptions & { tls?: TLSOptions }) {
// @ts-ignore
const server = Deno.serve({
port: options.port,
hostname: options.hostname,
cert: options.tls?.cert,
key: options.tls?.key,
responseWriteTimeout: options.idleTimeout,
handler,
});

globalThis.brisaServer = server;

return { port: server.addr.port, hostname: server.addr.hostname, server };
}

export async function handler(req: Request, connInfo: any) {
const bunServer = {
upgrade: () => {},
requestIP: () => connInfo.remoteAddr,
} as any;

const res = await serveOptions.fetch.call(bunServer, req, bunServer);

if (!res) {
return new Response('Not Found', { status: 404 });
}

return res;
}
19 changes: 14 additions & 5 deletions packages/brisa/src/cli/serve/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,22 @@ import nodeServe from './node-serve';
import handler from './node-serve/handler';
import bunServe from './bun-serve';
import { runtimeVersion } from '@/utils/js-runtime-util';
import denoServe from './deno-serve';

const { LOG_PREFIX, JS_RUNTIME, VERSION, IS_PRODUCTION } = constants;

function getServe(options: ServeOptions) {
if (JS_RUNTIME === 'node') {
return nodeServe.bind(null, { port: Number(options.port) });
}

if (JS_RUNTIME === 'deno') {
return denoServe.bind(null, options);
}

return bunServe.bind(null, options);
}

async function init(options: ServeOptions) {
if (cluster.isPrimary && constants.CONFIG?.clustering) {
console.log(
Expand Down Expand Up @@ -43,11 +56,7 @@ async function init(options: ServeOptions) {
}

try {
const serve =
JS_RUNTIME === 'bun'
? bunServe.bind(null, options)
: nodeServe.bind(null, { port: Number(options.port) });

const serve = getServe(options);
const { hostname, port } = await serve();
const runtimeMsg = `🚀 Brisa ${VERSION}: Runtime on ${runtimeVersion(JS_RUNTIME)}`;
const listeningMsg = `listening on http://${hostname}:${port}`;
Expand Down
3 changes: 3 additions & 0 deletions packages/brisa/src/core/server/deno.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import serve, { handler } from '@/cli/serve/deno-serve';

export { handler, serve };
Loading

0 comments on commit cff9139

Please sign in to comment.