diff --git a/resources/js/Components/Component.js b/resources/js/Components/Component.js
new file mode 100644
index 000000000..f061d9e15
--- /dev/null
+++ b/resources/js/Components/Component.js
@@ -0,0 +1,156 @@
+/* eslint-disable no-unused-vars */
+import * as Mithril from 'mithril';
+
+export interface ComponentAttrs extends Mithril.Attributes {}
+
+/**
+ * @abstract
+ *
+ * The `Component` class defines a user interface 'building block'. A component
+ * generates a virtual DOM to be rendered on each redraw.
+ *
+ * Essentially, this is a wrapper for Mithril's components that adds several useful features:
+ *
+ * - In the `oninit` and `onbeforeupdate` lifecycle hooks, we store vnode attrs in `this.attrs.
+ * This allows us to use attrs across components without having to pass the vnode to every single
+ * method.
+ * - The static `initAttrs` method allows a convenient way to provide defaults (or to otherwise
+ * modify) the attrs that have been passed into a component.
+ * - When the component is created in the DOM, we store its DOM element under `this.element`;
+ * this lets us use jQuery to modify child DOM state from internal methods via the `this.$()`
+ * method.
+ * - A convenience `component` method, which serves as an alternative to hyperscript and JSX.
+ *
+ * As with other Mithril components, components extending Component can be initialized
+ * and nested using JSX, hyperscript, or a combination of both. The `component` method can also
+ * be used.
+ *
+ * @example
+ * return m('div', Hello World
);
+ *
+ * @example
+ * return m('div', MyComponent.component({foo: 'bar'), m('p', 'Hello World!'));
+ *
+ * @see https://mithril.js.org/components.html
+ */
+export default class Component implements ComponentAttrs {
+ /**
+ * The root DOM element for the component.
+ *
+ * @protected
+ */
+ element: Element;
+
+ /**
+ * The attributes passed into the component.
+ *
+ * @see https://mithril.js.org/components.html#passing-data-to-components
+ * @protected
+ */
+ attrs;
+
+ /**
+ * @inheritdoc
+ * @abstract
+ */
+ view(vnode: Mithril.vnode) {}
+
+ /**
+ * @inheritdoc
+ */
+ oninit(vnode: Mithril.vnode) {
+ this.setAttrs(vnode.attrs);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ oncreate(vnode: Mithril.vnode) {
+ this.element = vnode.dom;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onbeforeupdate(vnode: Mithril.vnode) {
+ this.setAttrs(vnode.attrs);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onupdate(vnode: Mithril.vnode) {
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onbeforeremove(vnode: Mithril.vnode) {
+ }
+
+ /**
+ * @inheritdoc
+ */
+ onremove(vnode: Mithril.vnode) {
+ }
+
+ /**
+ * Returns a jQuery object for this component's element. If you pass in a
+ * selector string, this method will return a jQuery object, using the current
+ * element as its buffer.
+ *
+ * For example, calling `component.$('li')` will return a jQuery object
+ * containing all of the `li` elements inside the DOM element of this
+ * component.
+ *
+ * @param [selector] a jQuery-compatible selector string
+ * @returns the jQuery object for the DOM node
+ * @final
+ * @protected
+ */
+ /* $(selector?: string): JQuery {
+ const $element: JQuery = $(this.element);
+ return selector ? $element.find(selector) : $element;
+ }; */
+
+
+ /**
+ * Convenience method to attach a component without JSX.
+ * Has the same effect as calling `m(THIS_CLASS, attrs, children)`.
+ *
+ * @see https://mithril.js.org/hyperscript.html#mselector,-attributes,-children
+ */
+ static component(attrs = {}, children = null): Mithril.vnode {
+ const componentAttrs: Record = { ...attrs};
+
+ return Mithril.m(this, componentAttrs, children);
+ }
+
+ /**
+ * Saves a reference to the vnode attrs after running them through initAttrs,
+ * and checking for common issues.
+ *
+ * @private
+ */
+ setAttrs(attrs = {}): void {
+ this.initAttrs(attrs);
+ if (attrs) {
+ if ('children' in attrs) {
+ throw new Error(`[${this.constructor.name}] The "children" attribute of attrs should never be used. Either pass children in as the vnode children or rename the attribute`);
+ }
+ if ('tag' in attrs) {
+ throw new Error(`[${this.constructor.name}] You cannot use the "tag" attribute name with Mithril 2.`);
+ }
+ }
+ this.attrs = attrs;
+ }
+
+ /**
+ * Initialize the component's attrs.
+ *
+ * This can be used to assign default values for missing, optional attrs.
+ *
+ * @protected
+ */
+ initAttrs(attrs): void {}
+}
diff --git a/resources/js/Components/Page.js b/resources/js/Components/Page.js
new file mode 100644
index 000000000..58d213a66
--- /dev/null
+++ b/resources/js/Components/Page.js
@@ -0,0 +1,8 @@
+import Component from './Component';
+
+/**
+ * The `Page` component
+ *
+ * @abstract
+ */
+export default class Page extends Component {}
diff --git a/resources/js/Views/SetupPage.js b/resources/js/Views/SetupPage.js
index 1dcd6a743..8e8a2afd9 100644
--- a/resources/js/Views/SetupPage.js
+++ b/resources/js/Views/SetupPage.js
@@ -1,5 +1,12 @@
-import m from 'mithril';
+import Page from '../Components/Page';
-export default {
- view: () => Hello World!
-};
+export default class SetupPage extends Page {
+ // eslint-disable-next-line no-unused-vars
+ view(vnode) {
+ return (
+ <>
+ Hello World!
+ >
+ );
+ }
+}
diff --git a/resources/js/utils.js b/resources/js/utils.js
new file mode 100644
index 000000000..7b4ac2bd0
--- /dev/null
+++ b/resources/js/utils.js
@@ -0,0 +1,6 @@
+/**
+ * Check if class A is the same as or a subclass of class B.
+ */
+export default function subclassOf(A, B) {
+ return A && (A === B || A.prototype instanceof B);
+}