Skip to content

Commit

Permalink
feat: container APIs (#11051)
Browse files Browse the repository at this point in the history
* feat: container APIs

* chore: handle runtime mode

* chore: render slots

* more prototyping

* Adding a changeset

* fix some weirdness around types

* feat: allow to inject the manifest

* feat: accept a manifest

* more APIs

* add `route` to the options

* chore

* fix component instance

* chore: document stuff

* remove commented code

* chore: add test for renderers and fixed its types

* fix: update name of the example

* fix regression inside tests

* use `experimental_`

* remove errors

* need to understand the types here

* remove some options that I don't deem necessary for this phase

* remove superfluous comments

* chore: remove useless `@ts-ignore` directive

* chore: address feedback

* fix regression and remove astro config

* chore: fix regression

* Apply suggestions from code review

Co-authored-by: Sarah Rainsberger <[email protected]>

* ooops

* restore changes

---------

Co-authored-by: Matthew Phillips <[email protected]>
Co-authored-by: Sarah Rainsberger <[email protected]>
  • Loading branch information
3 people authored May 22, 2024
1 parent a6916e4 commit 12a1bcc
Show file tree
Hide file tree
Showing 27 changed files with 969 additions and 15 deletions.
35 changes: 35 additions & 0 deletions .changeset/brave-colts-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
"astro": minor
---

Introduces an experimental Container API to render `.astro` components in isolation.

This API introduces three new functions to allow you to create a new container and render an Astro component returning either a string or a Response:

- `create()`: creates a new instance of the container.
- `renderToString()`: renders a component and return a string.
- `renderToResponse()`: renders a component and returns the `Response` emitted by the rendering phase.

The first supported use of this new API is to enable unit testing. For example, with `vitest`, you can create a container to render your component with test data and check the result:

```js
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import { expect, test } from 'vitest';
import Card from '../src/components/Card.astro';

test('Card with slots', async () => {
const container = await AstroContainer.create();
const result = await container.renderToString(Card, {
slots: {
default: 'Card content',
},
});

expect(result).toContain('This is a card');
expect(result).toContain('Card content');
});
```

For a complete reference, see the [Container API docs](/en/reference/container-reference/).

For a feature overview, and to give feedback on this experimental API, see the [Container API roadmap discussion](https://github.com/withastro/roadmap/pull/916).
1 change: 1 addition & 0 deletions examples/container-with-vitest/.codesandbox/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM node:18-bullseye
24 changes: 24 additions & 0 deletions examples/container-with-vitest/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# build output
dist/
# generated types
.astro/

# dependencies
node_modules/

# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*


# environment variables
.env
.env.production

# macOS-specific files
.DS_Store

# jetbrains setting folder
.idea/
11 changes: 11 additions & 0 deletions examples/container-with-vitest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Astro + [Vitest](https://vitest.dev/) + Container API Example

```sh
npm create astro@latest -- --template container-with-vitest
```

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/with-vitest)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/with-vitest)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/with-vitest/devcontainer.json)

This example showcases Astro working with [Vitest](https://vitest.dev/) and how to test components using the Container API.
7 changes: 7 additions & 0 deletions examples/container-with-vitest/astro.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'astro/config';
import react from "@astrojs/react"

// https://astro.build/config
export default defineConfig({
integrations: [react()]
});
25 changes: 25 additions & 0 deletions examples/container-with-vitest/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@example/container-with-vitest",
"type": "module",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro",
"test": "vitest run"
},
"dependencies": {
"astro": "experimental--container",
"@astrojs/react": "^3.3.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"vitest": "^1.6.0"
},
"devDependencies": {
"@types/react-dom": "^18.3.0",
"@types/react": "^18.3.2"
}
}
9 changes: 9 additions & 0 deletions examples/container-with-vitest/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions examples/container-with-vitest/src/components/Card.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
---
<div>
This is a card
<slot />
</div>
14 changes: 14 additions & 0 deletions examples/container-with-vitest/src/components/Counter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useState } from 'react';

export default function({ initialCount }) {
const [count, setCount] = useState(initialCount || 0);
return (
<div className="rounded-t-lg overflow-hidden border-t border-l border-r border-gray-400 text-center p-4">
<h2 className="font-semibold text-lg">Counter</h2>
<h3 className="font-medium text-lg">Count: {count}</h3>
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
import Counter from './Counter.jsx';
---

<Counter initialCount={5} />
22 changes: 22 additions & 0 deletions examples/container-with-vitest/src/pages/[locale].astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
export function getStaticPaths() {
return [
{params: {locale: 'en'}},
];
}
const { locale } = Astro.params
---

<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>Astro</title>
</head>
<body>
<h1>Astro</h1>
<p>Locale: {locale}</p>
</body>
</html>
11 changes: 11 additions & 0 deletions examples/container-with-vitest/src/pages/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function GET() {
const json = {
foo: 'bar',
number: 1,
};
return new Response(JSON.stringify(json), {
headers: {
'content-type': 'application/json',
},
});
}
16 changes: 16 additions & 0 deletions examples/container-with-vitest/src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
---

<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>Astro</title>
</head>
<body>
<h1>Astro</h1>
</body>
</html>
15 changes: 15 additions & 0 deletions examples/container-with-vitest/test/Card.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import { expect, test } from 'vitest';
import Card from '../src/components/Card.astro';

test('Card with slots', async () => {
const container = await AstroContainer.create();
const result = await container.renderToString(Card, {
slots: {
default: 'Card content',
},
});

expect(result).toContain('This is a card');
expect(result).toContain('Card content');
});
19 changes: 19 additions & 0 deletions examples/container-with-vitest/test/ReactWrapper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import { expect, test } from 'vitest';
import ReactWrapper from '../src/components/ReactWrapper.astro';

test('ReactWrapper with react renderer', async () => {
const container = await AstroContainer.create({
renderers: [
{
name: '@astrojs/react',
clientEntrypoint: "@astrojs/react/client.js",
serverEntrypoint: "@astrojs/react/server.js",
}
]
});
const result = await container.renderToString(ReactWrapper);

expect(result).toContain('Counter');
expect(result).toContain('Count: <!-- -->5');
});
16 changes: 16 additions & 0 deletions examples/container-with-vitest/test/[locale].test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import { expect, test } from 'vitest';
import Locale from '../src/pages/[locale].astro';

test('Dynamic route', async () => {
const container = await AstroContainer.create();
// @ts-ignore
const result = await container.renderToString(Locale, {
params: {
"locale": 'en'
},
request: new Request('http://example.com/en'),
});

expect(result).toContain('Locale: en');
});
3 changes: 3 additions & 0 deletions examples/container-with-vitest/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "astro/tsconfigs/base"
}
9 changes: 9 additions & 0 deletions examples/container-with-vitest/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// <reference types="vitest" />
import { getViteConfig } from 'astro/config';

export default getViteConfig({
test: {
/* for example, use global to avoid globals imports (describe, test, expect): */
// globals: true,
},
});
4 changes: 4 additions & 0 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
"types": "./config.d.ts",
"default": "./config.mjs"
},
"./container": {
"types": "./dist/container/index.d.ts",
"default": "./dist/container/index.js"
},
"./app": "./dist/core/app/index.js",
"./app/node": "./dist/core/app/node.js",
"./client/*": "./dist/runtime/client/*",
Expand Down
Loading

0 comments on commit 12a1bcc

Please sign in to comment.