Skip to content

Commit

Permalink
feat: implement css template literal for server components
Browse files Browse the repository at this point in the history
  • Loading branch information
aralroca committed Mar 27, 2024
1 parent 44ac9e8 commit 955689e
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 9 deletions.
19 changes: 18 additions & 1 deletion packages/brisa/src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,26 @@ export interface RequestContext extends Request {
*
* Docs:
*
* - [How to use `id`](https://brisa.build/building-your-application/data-fetching/request-context)
* - [How to use `id`](https://brisa.build/building-your-application/data-fetching/request-context#id)
*/
id: string;

/**
* Description:
*
* The `css` method is used to inject CSS into server components.
*
* Example:
*
* ```ts
* css`div { background-color: ${color}; }`
* ```
*
* Docs:
*
* - [How to use `css`](https://brisa.build/building-your-application/data-fetching/request-context#css)
*/
css(strings: TemplateStringsArray, ...values: string[]): void;
}

type Effect = () => void | Promise<void>;
Expand Down
32 changes: 32 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 @@ -259,5 +259,37 @@ describe("brisa core", () => {
const indicate = requestContext.indicate("foo");
expect(indicate.error.value).toBeUndefined();
});

it("should work css function", () => {
const request = new Request("https://example.com");
const route = {
path: "/",
} as any;
const requestContext = extendRequestContext({
originalRequest: request,
route,
});
const { css } = requestContext;
css`
body {
color: red;
}
`;
css`
body {
background: blue;
}
`;
expect((requestContext as any)._style).toBe(
"body { color: red; }body { background: blue; }",
);
(requestContext as any)._style = "";
css`
body {
color: yellow;
}
`;
expect((requestContext as any)._style).toBe("body { color: yellow; }");
});
});
});
9 changes: 9 additions & 0 deletions packages/brisa/src/utils/extend-request-context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,14 @@ export default function extendRequestContext({
error: {},
});

// css
originalRequest._style = "";
originalRequest.css = (
template: TemplateStringsArray,
...values: string[]
) => {
originalRequest._style += String.raw(template, ...values);
};

return originalRequest as RequestContext;
}
67 changes: 67 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 @@ -712,6 +712,73 @@ describe("utils", () => {
);
});

it("should render the style tag when the css is used in the component", async () => {
const Component = ({}, { css }: RequestContext) => {
css`
.red {
color: red;
}
`;
css`
.blue {
color: blue;
}
`;

return <div class="red">Hello</div>;
};

const stream = renderToReadableStream(<Component />, testOptions);
const result = await Bun.readableStreamToText(stream);

expect(result).toBe(
toInline(`
<style>.red { color: red; }.blue { color: blue; }</style>
<div class="red">Hello</div>
`),
);
});

it("should add different styles in different components", async () => {
const Component = ({}, { css }: RequestContext) => {
css`
.red {
color: red;
}
`;

return <div class="red">Hello</div>;
};

const Component2 = ({}, { css }: RequestContext) => {
css`
.blue {
color: blue;
}
`;

return <div class="blue">Hello</div>;
};

const stream = renderToReadableStream(
<>
<Component />
<Component2 />
</>,
testOptions,
);

const result = await Bun.readableStreamToText(stream);
expect(result).toBe(
toInline(`
<style>.red { color: red; }</style>
<div class="red">Hello</div>
<style>.blue { color: blue; }</style>
<div class="blue">Hello</div>
`),
);
});

it("should render the suspense component before if the async component support it", async () => {
const Component = async () => {
await Promise.resolve();
Expand Down
10 changes: 10 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 @@ -19,6 +19,7 @@ import overrideClientTranslations from "@/utils/translate-core/override-client-t
import processServerComponentProps from "@/utils/process-server-component-props";
import extendRequestContext from "@/utils/extend-request-context";
import type { Options } from "@/types/server";
import { toInline } from "@/helpers";

type ProviderType = ReturnType<typeof contextProvider>;

Expand Down Expand Up @@ -388,6 +389,15 @@ async function enqueueComponent(
request,
)) as JSX.Element;

// Inject CSS
if ((request as any)._style) {
controller.enqueue(
`<style>${toInline((request as any)._style)}</style>`,
suspenseId,
);
(request as any)._style = "";
}

// Async generator list
if (typeof componentValue.next === "function") {
for await (let val of componentValue) {
Expand Down
16 changes: 8 additions & 8 deletions packages/docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { defineConfig } from "vitepress";

const pkg = require('../package.json')
const pkg = require("../package.json");

// https://vitepress.dev/reference/site-config
export default defineConfig({
Expand Down Expand Up @@ -31,15 +31,15 @@ export default defineConfig({
text: pkg.version,
items: [
{
text: 'Changelog',
link: 'https://github.com/brisa-build/brisa/releases'
text: "Changelog",
link: "https://github.com/brisa-build/brisa/releases",
},
{
text: 'Contributing',
link: 'https://github.com/brisa-build/brisa/blob/main/CONTRIBUTING.md'
}
]
}
text: "Contributing",
link: "https://github.com/brisa-build/brisa/blob/main/CONTRIBUTING.md",
},
],
},
],
sidebar: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,31 @@ For more details, take a look to:
- [`indicate`](/building-your-application/data-fetching/web-context#indicate) in web components, similar method but from [`WebContext`](/building-your-application/data-fetching/web-context).
- [`indicate[Event]`](/api-reference/extended-html-attributes/indicateEvent) HTML extended attribute to use it in server components to register the server action indicator.
- [`indicator`](/api-reference/extended-html-attributes/indicator) HTML extended attribute to use it in any element of server/web components.

## `css`

`css(strings: TemplateStringsArray, ...values: string[]): void`

The `css` template literal is used to inject CSS into the DOM. It allows developers to define styles directly within server components using a template literal.

Unlike web components, this `css` template literal in server components does not encapsulate. This code would affect all `div`s on the page:

Example:

```ts
css`
div {
background-color: ${color};
}
`;
```

> [!TIP]
>
> 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`](/components-details/web-components#template-literal-css) documentation.

## `id`

TODO

0 comments on commit 955689e

Please sign in to comment.