diff --git a/examples/preact/BUILD.bazel b/examples/preact/BUILD.bazel
new file mode 100644
index 00000000..10783b36
--- /dev/null
+++ b/examples/preact/BUILD.bazel
@@ -0,0 +1,46 @@
+load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin")
+load("//:index.bzl", "prerender_pages", "web_resources_devserver")
+load("//tools/jasmine:defs.bzl", "jasmine_web_test_suite")
+load("//tools/typescript:defs.bzl", "ts_project")
+
+copy_to_bin(
+ name = "package",
+ srcs = ["package.json"],
+)
+
+prerender_pages(
+ name = "site",
+ src = "site.tsx",
+ tsconfig = "//:tsconfig",
+ source_map = True,
+ # Need `"type": "module"` to load `*.js` files output by `*.tsx` compilation.
+ data = [":package"],
+ lib_deps = [
+ "//:node_modules/@rules_prerender/preact",
+ "//:node_modules/preact",
+ ],
+ deps = ["//examples/preact/component"],
+)
+
+web_resources_devserver(
+ name = "devserver",
+ resources = ":site",
+)
+
+ts_project(
+ name = "test_lib",
+ srcs = ["test.mts"],
+ data = [":devserver"],
+ testonly = True,
+ deps = [
+ "//common/testing:devserver",
+ "//common/testing:webdriver",
+ "//:node_modules/@types/jasmine",
+ ],
+)
+
+jasmine_web_test_suite(
+ name = "test",
+ browsers = ["//tools/browsers:chromium-local"],
+ deps = [":test_lib"],
+)
diff --git a/examples/preact/component/BUILD.bazel b/examples/preact/component/BUILD.bazel
new file mode 100644
index 00000000..d21c3230
--- /dev/null
+++ b/examples/preact/component/BUILD.bazel
@@ -0,0 +1,26 @@
+load("//tools/typescript:defs.bzl", "ts_project")
+load("//:index.bzl", "css_library", "prerender_component")
+
+prerender_component(
+ name = "component",
+ srcs = ["component.tsx"],
+ scripts = [":script"],
+ styles = [":style"],
+ tsconfig = "//:tsconfig",
+ source_map = True,
+ visibility = ["//examples/preact:__pkg__"],
+ lib_deps = [
+ "//:node_modules/@rules_prerender/preact",
+ "//:node_modules/preact",
+ ],
+)
+
+ts_project(
+ name = "script",
+ srcs = ["script.mts"],
+)
+
+css_library(
+ name = "style",
+ srcs = ["style.css"],
+)
diff --git a/examples/preact/component/component.tsx b/examples/preact/component/component.tsx
new file mode 100644
index 00000000..ede74d64
--- /dev/null
+++ b/examples/preact/component/component.tsx
@@ -0,0 +1,19 @@
+import { Template, includeScript, inlineStyle } from '@rules_prerender/preact';
+import { ComponentChildren, VNode } from 'preact';
+
+export function Component({ text, children }: {
+ text: string,
+ children: ComponentChildren,
+}): VNode {
+ return
+
+ {text}
+
+
+ {includeScript('./script.mjs', import.meta)}
+ {inlineStyle('./style.css', import.meta)}
+
+
This text to be replaced by page JavaScript.
+ {children}
+
;
+}
diff --git a/examples/preact/component/script.mts b/examples/preact/component/script.mts
new file mode 100644
index 00000000..024c5c61
--- /dev/null
+++ b/examples/preact/component/script.mts
@@ -0,0 +1 @@
+document.getElementById('replace')!.textContent = 'Hello from JavaScript!';
diff --git a/examples/preact/component/style.css b/examples/preact/component/style.css
new file mode 100644
index 00000000..d7d3458a
--- /dev/null
+++ b/examples/preact/component/style.css
@@ -0,0 +1,3 @@
+h2 {
+ color: red;
+}
diff --git a/examples/preact/package.json b/examples/preact/package.json
new file mode 100644
index 00000000..3dbc1ca5
--- /dev/null
+++ b/examples/preact/package.json
@@ -0,0 +1,3 @@
+{
+ "type": "module"
+}
diff --git a/examples/preact/site.tsx b/examples/preact/site.tsx
new file mode 100644
index 00000000..96b4ed63
--- /dev/null
+++ b/examples/preact/site.tsx
@@ -0,0 +1,18 @@
+import { PrerenderResource, renderToHtml } from '@rules_prerender/preact';
+import { Component } from './component/component.js';
+
+export default function*(): Generator {
+ yield PrerenderResource.of('/index.html', renderToHtml(
+
+
+ Preact
+
+
+
+
+ Goodbye, World!
+
+
+
+ ));
+}
diff --git a/examples/preact/test.mts b/examples/preact/test.mts
new file mode 100644
index 00000000..81c49bb4
--- /dev/null
+++ b/examples/preact/test.mts
@@ -0,0 +1,28 @@
+import { useDevserver } from '../../common/testing/devserver.mjs';
+import { useWebDriver, getColor } from '../../common/testing/webdriver.mjs';
+
+describe('preact', () => {
+ const devserver = useDevserver('examples/preact/devserver.sh');
+ const wd = useWebDriver(devserver);
+
+ it('renders', async () => {
+ const browser = wd.get();
+ await browser.url('/');
+
+ expect(await browser.getTitle()).toBe('Preact');
+
+ // Test JavaScript execution.
+ expect(await browser.$('#replace').getText())
+ .toBe('Hello from JavaScript!');
+
+ // Test CSS applied within declarative shadow DOM.
+ const hello = await browser.$('#component').shadow$('h2');
+ expect(await hello.getText()).toBe('Hello, World!');
+ expect(await getColor(browser, hello)).toBe('rgb(255, 0, 0)'); // Red.
+
+ // Test CSS did *not* apply in component light DOM.
+ const goodbye = await browser.$('h2');
+ expect(await goodbye.getText()).toBe('Goodbye, World!');
+ expect(await getColor(browser, goodbye)).toBe('rgb(0, 0, 0)'); // Black.
+ });
+});