Skip to content

Commit

Permalink
feat: add 'after' request context method (#677)
Browse files Browse the repository at this point in the history
  • Loading branch information
aralroca authored Dec 11, 2024
1 parent fe79ad9 commit 2b6554c
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 0 deletions.
29 changes: 29 additions & 0 deletions docs/api-reference/components/request-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export default function ServerComponent(props, requestContext: RequestContext) {

// Add styles
css,

// Run tasks after the response is sent
after,
} = requestContext;
// ... Server component implementation ...
}
Expand Down Expand Up @@ -320,3 +323,29 @@ css`
> We recommend using the `css` template literal for specific cases such as generating CSS animations based on dynamic JavaScript variables.
For more details, refer to the [Template literal `css`](/building-your-application/components-details/web-components#template-literal-css) documentation.

## `after`

`after(cb: () => void): void`

The `after` method allows you to schedule work to be executed after a response (or prerender) is finished. This is useful for tasks and other side effects that should not block the response, such as logging and analytics.

It can be used everywhere when you have access to the `RequestContext` (Middleware, API routes, Server components, etc).

Example:

```tsx
import { type RequestContext } from "brisa";

export default function SomeComponent({}, { after }: RequestContext) {
after(() => {
console.log("The response is sent");
});

return <div>Some content</div>;
}
```

> [!NOTE]
>
> **Good to know**: `after` is not a Dynamic API and calling it does not cause a route to become dynamic. If it's used within a static page, the callback will execute at build time.
20 changes: 20 additions & 0 deletions packages/brisa/src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,26 @@ export interface RequestContext extends Request {
* - [How to use `css`](https://brisa.build/api-reference/components/request-context#css)
*/
css(strings: TemplateStringsArray, ...values: string[]): void;

/**
* Description:
*
* The `after` method is used to execute a function after the response has been sent.
*
* Example:
*
* ```ts
* after(() => console.log('Hello World'));
* ```
*
* This log will be executed after the response has been sent.
*
* Docs:
*
* - [How to use `after`](https://brisa.build/api-reference/components/request-context#after)
*
*/
after(fn: Effect): void | Promise<void>;
}

type Effect = () => void | Promise<void>;
Expand Down
27 changes: 27 additions & 0 deletions packages/brisa/src/utils/extend-request-context/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,5 +374,32 @@ describe('brisa core', () => {
});
expect(requestContext.initiator).toBe(Initiator.SERVER_ACTION);
});

it('should add tasks to the request with "after"', () => {
const requestContext = extendRequestContext({
originalRequest: new Request('https://example.com'),
});

const mockAfter = mock(() => {});
requestContext.after(() => mockAfter());

expect((requestContext as any)._tasks).toHaveLength(1);
expect(mockAfter).not.toHaveBeenCalled();
});

it('should keep tasks from one req to another one', () => {
const requestContext = extendRequestContext({
originalRequest: new Request('https://example.com'),
});

requestContext.after(() => {});
expect((requestContext as any)._tasks).toHaveLength(1);

const requestContext2 = extendRequestContext({
originalRequest: requestContext,
});

expect((requestContext as any)._tasks).toHaveLength(1);
});
});
});
6 changes: 6 additions & 0 deletions packages/brisa/src/utils/extend-request-context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export default function extendRequestContext({
finalURL,
id,
}: ExtendRequestContext): RequestContext {
// After tasks
originalRequest._tasks ??= [];
originalRequest.after = (task) => {
originalRequest._tasks.push(task);
};

// finalURL
originalRequest.finalURL =
finalURL ?? originalRequest.finalURL ?? originalRequest.url;
Expand Down
29 changes: 29 additions & 0 deletions packages/brisa/src/utils/render-to-readable-stream/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3796,6 +3796,35 @@ describe('utils', () => {
);
});

it('should resolve request._tasks after the response stream', async () => {
const request = extendRequestContext({
originalRequest: extendRequestContext({
originalRequest: new Request('http://test.com/en'),
}),
});

const mockAfter = mock(() => {});
request.after(() => mockAfter());

const stream = renderToReadableStream(
<html>
<head></head>
<body></body>
</html>,
{ request },
);

const reader = stream.getReader();

while (true) {
const result = await reader.read();
if (result.done) break;
expect(mockAfter).not.toHaveBeenCalled();
}

expect(mockAfter).toHaveBeenCalled();
});

it('should include window.r with the route without i18n', () => {
const request = extendRequestContext({
originalRequest: extendRequestContext({
Expand Down
3 changes: 3 additions & 0 deletions packages/brisa/src/utils/render-to-readable-stream/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ export default function renderToReadableStream(

controller.close();

// Resolve tasks from "after" request method
await Promise.all((req as any)._tasks.map((task: () => void) => task()));

if (
isPage &&
!IS_PRODUCTION &&
Expand Down

0 comments on commit 2b6554c

Please sign in to comment.