Components With Props

Faster, smoother development and reusability with components and props.

Our site is currently one "component" which serves as an 11ty page/template. We don't really have reuse nor the traditional concept of components with props. In this step, we break out part of our page into a Heading component which can be used on any page, can accept "props", and can be developed in isolation.

Let's say our site content will be in site and our software components in components:

Our vitest.config.js file needs to be pointed at both the site and components directories:

import { defineConfig } from "vitest/config";

export default defineConfig({
	esbuild: {
		jsx: "transform",
		jsxInject: "import { jsx } from 'jsx-async-runtime/jsx-runtime'",
		jsxFactory: "jsx",
		jsxImportSource: "jsx-async-runtime",
	},
	test: {
		environment: "happy-dom",
		include: ["./components/**/*.test.tsx", "./site/**/*.test.tsx"],
	},
});

We should do something similar in tsconfig.json:

{
	"compilerOptions": {
		"module": "ESNext",
		"target": "ESNext",
		"moduleResolution": "Node",
		"skipLibCheck": true,
		"jsx": "react-jsx",
		"jsxImportSource": "jsx-async-runtime"
	},
	"include": ["components", "site"],
	"exclude": ["node_modules", "_site"]
}

With this in place, let's make components/Heading.tsx (note that it didn't need the .11ty. in the filename):

export type HeadingProps = {
	name?: string;
};

export function Heading({ name = "TSX" }: HeadingProps): JSX.Element {
	return <h1>Hello {name}</h1>;
}

We can test the default and passed-in-value cases in components/Heading.test.tsx:

import { expect, test } from "vitest";
import { renderToString } from "jsx-async-runtime";
import { screen } from "@testing-library/dom";
import { Heading } from "./Heading";

test("render heading with default name", async () => {
	const result = <Heading />;
	document.body.innerHTML = await renderToString(result);
	expect(screen.getByText("Hello TSX")).toBeTruthy();
});

test("render heading with custom name", async () => {
	const result = <Heading name={`World`} />;
	document.body.innerHTML = await renderToString(result);
	expect(screen.getByText("Hello World")).toBeTruthy();
});

Running this in the IDE shows that our component works in isolation -- using a fake browser (Happy-DOM):

Component Test

In fact, you can go into true TDD mode and run Vitest in watch mode. It's so fast, you get answers as you type. No need to re-run your site and revisit your browser.

Now, let's go back to our site/index.11ty.tsx template and point it at a component:

import { Heading } from "../components/Heading";

export function Index(): JSX.Element {
	return <Heading />;
}

export const render = Index;

Tests all pass, the page still builds the same. All good.

In the next step, we'll take our component and make it even more testable.