diff --git a/TODO.md b/TODO.md
index 26c412a..768e37a 100644
--- a/TODO.md
+++ b/TODO.md
@@ -22,12 +22,17 @@ For example:
- fp
- [ ] Remove gradle plugins and whatnot until we actually build the whole framework
-## 0.1.2
+## 0.1.3
+- [ ] refactor tools/gradle start scripts to use dist instead of custom bin script
+ - [ ] have custom bin/* scripts which point to dist(s) for convenience
- [ ] di bug: @Singleton toSelf() causes stack overflow
-- [ ] `Outlet` component for rendering children like so:
+
+## 0.1.2
+- [x] `Outlet` component for rendering children like so:
```
```
+- [x] `Render` component
- [x] `data-` attributes need to function correctly (really any attribute with hyphen).
## 0.1.1
diff --git a/web-view-components/src/main/groovy/groowt/view/component/web/DefaultWebViewComponentScope.groovy b/web-view-components/src/main/groovy/groowt/view/component/web/DefaultWebViewComponentScope.groovy
index 030cabe..db4cdd1 100644
--- a/web-view-components/src/main/groovy/groowt/view/component/web/DefaultWebViewComponentScope.groovy
+++ b/web-view-components/src/main/groovy/groowt/view/component/web/DefaultWebViewComponentScope.groovy
@@ -14,6 +14,8 @@ class DefaultWebViewComponentScope extends DefaultComponentScope implements WebV
addWithAttr(DefaultCase)
addWithAttr(Each)
addWithAttr(Echo)
+ addWithAttr(Outlet)
+ addWithAttr(Render)
addWithAttr(Switch)
addWithAttr(WhenNotEmpty)
addWithAttr(WhenNotNull)
diff --git a/web-view-components/src/main/groovy/groowt/view/component/web/lib/Outlet.groovy b/web-view-components/src/main/groovy/groowt/view/component/web/lib/Outlet.groovy
new file mode 100644
index 0000000..6108771
--- /dev/null
+++ b/web-view-components/src/main/groovy/groowt/view/component/web/lib/Outlet.groovy
@@ -0,0 +1,22 @@
+package groowt.view.component.web.lib
+
+import groowt.view.View
+import groowt.view.component.runtime.DefaultComponentWriter
+
+class Outlet extends DelegatingWebViewComponent {
+
+ private final List givenChildren
+
+ Outlet(Map attr) {
+ givenChildren = attr.children ?: []
+ }
+
+ @Override
+ protected View getDelegate() {
+ return { Writer w ->
+ def cw = new DefaultComponentWriter(w, context.renderContext, context)
+ givenChildren.each { cw << it }
+ }
+ }
+
+}
diff --git a/web-view-components/src/main/groovy/groowt/view/component/web/lib/Render.groovy b/web-view-components/src/main/groovy/groowt/view/component/web/lib/Render.groovy
new file mode 100644
index 0000000..a4072c2
--- /dev/null
+++ b/web-view-components/src/main/groovy/groowt/view/component/web/lib/Render.groovy
@@ -0,0 +1,28 @@
+package groowt.view.component.web.lib
+
+import groowt.view.View
+
+class Render extends DelegatingWebViewComponent {
+
+ private final Object item
+
+ Render(Map attr) {
+ item = Objects.requireNonNull(attr.item, " attribute 'item' must not be null.")
+ }
+
+ @Override
+ protected View getDelegate() {
+ return { Writer w ->
+ if (item.respondsTo('renderTo', [Writer] as Class[])) {
+ item.renderTo(w)
+ } else if (item.respondsTo('render')) {
+ w << item.render()
+ } else {
+ throw new IllegalArgumentException(
+ ' must use an item which responds to either renderTo(Writer) or render().'
+ )
+ }
+ }
+ }
+
+}
diff --git a/web-view-components/src/test/groovy/groowt/view/component/web/lib/OutletTests.groovy b/web-view-components/src/test/groovy/groowt/view/component/web/lib/OutletTests.groovy
new file mode 100644
index 0000000..6503d91
--- /dev/null
+++ b/web-view-components/src/test/groovy/groowt/view/component/web/lib/OutletTests.groovy
@@ -0,0 +1,17 @@
+package groowt.view.component.web.lib
+
+import org.junit.jupiter.api.Test
+
+class OutletTests extends AbstractWebViewComponentTests {
+
+ @Test
+ void smokeScreen() {
+ doTest('', '')
+ }
+
+ @Test
+ void withChildren() {
+ doTest('', '012')
+ }
+
+}
diff --git a/web-view-components/src/test/groovy/groowt/view/component/web/lib/RenderTests.groovy b/web-view-components/src/test/groovy/groowt/view/component/web/lib/RenderTests.groovy
new file mode 100644
index 0000000..7e18147
--- /dev/null
+++ b/web-view-components/src/test/groovy/groowt/view/component/web/lib/RenderTests.groovy
@@ -0,0 +1,47 @@
+package groowt.view.component.web.lib
+
+import org.junit.jupiter.api.Test
+
+class RenderTests extends AbstractWebViewComponentTests {
+
+ static final class RenderToExample {
+
+ void renderTo(Writer w) {
+ w << 'Hello, World!'
+ }
+
+ }
+
+ static final class RenderExample {
+
+ String render() {
+ 'Hello, World!'
+ }
+
+ }
+
+ @Test
+ void renderToExample() {
+ doTest('''
+ ---
+ package groowt.view.component.web.lib
+ ---
+
+ '''.stripIndent().trim(),
+ 'Hello, World!'
+ )
+ }
+
+ @Test
+ void renderExample() {
+ doTest('''
+ ---
+ package groowt.view.component.web.lib
+ ---
+
+ '''.stripIndent().trim(),
+ 'Hello, World!'
+ )
+ }
+
+}