To create a web app nowadays, the concept became more complicated. We, the developers must follow the standards of SPA, SSR, routing and navigation, performance, … etc
That’s why the idea of creating a framework came up! let’s explore the following features:
HTML
<section id="app" class="card"></section> <nav> <a href="#/">Home</a> - <a href="#/page1">Page 1</a> - <a href="#/page2">Page 2</a> - </nav>
<section id="app" class="card"></section> <nav> <a href="#/">Home</a> - <a href="#/page1">Page 1</a> - <a href="#/page2">Page 2</a> - </nav>
const HomeComponent = { render: () => { return ` <section> <h1>Home</h1> <p>This is Home page</p> </section> `; } };
const HomeComponent = { render: () => { return ` <section> <h1>Home</h1> <p>This is Home page</p> </section> `; } };
const Page1Component = { render: () => { return ` <section> <h1>Page 1</h1> <p>This is Page 1</p> </section> `; } }; const Page2Component = { render: () => { return ` <section> <h1>Page 2</h1> <p>This is Page 2</p> </section> `; } };
const Page1Component = { render: () => { return ` <section> <h1>Page 1</h1> <p>This is Page 1</p> </section> `; } }; const Page2Component = { render: () => { return ` <section> <h1>Page 2</h1> <p>This is Page 2</p> </section> `; } };
// Routes const routes = [ { path: "/", component: HomeComponent }, { path: "/page1", component: Page1Component }, { path: "/page2", component: Page2Component } ]; // Using Hash approach const parseLocation = hash => hash.slice(1).toLowerCase() || "/"; const findComponentByPath = path => routes.find(r => r.path.match(new RegExp(`^\\${path}$`, "gm"))) || false; const router = () => { console.log(document.location) const path = parseLocation(document.location.hash); const { component = ErrorComponent } = findComponentByPath(path, routes) || {}; document.getElementById("app").innerHTML = component.render(); }; window.addEventListener("hashchange", router); window.addEventListener("load", router) || router();
// Routes const routes = [ { path: "/", component: HomeComponent }, { path: "/page1", component: Page1Component }, { path: "/page2", component: Page2Component } ]; // Using Hash approach const parseLocation = hash => hash.slice(1).toLowerCase() || "/"; const findComponentByPath = path => routes.find(r => r.path.match(new RegExp(`^\\${path}$`, "gm"))) || false; const router = () => { console.log(document.location) const path = parseLocation(document.location.hash); const { component = ErrorComponent } = findComponentByPath(path, routes) || {}; document.getElementById("app").innerHTML = component.render(); }; window.addEventListener("hashchange", router); window.addEventListener("load", router) || router();
Our DOM
const vdom = h('div', { class: 'red' }, [ h('span', null, 'hello') ]) mount(vdom, document.getElementById('app'))
const vdom = h('div', { class: 'red' }, [ h('span', null, 'hello') ]) mount(vdom, document.getElementById('app'))
The render function
function h(tag, props, children) { return { tag, props, children } }
function h(tag, props, children) { return { tag, props, children } }
function mount(vnode, container) {}
function mount(vnode, container) {}
function mount(vnode, container) { const el = vnode.el = document.createElement(vnode.tag) // Props if (vnode.props) { for (const key in vnode.props) { const value = vnode.props[key] // Assuming that all props are attributes for simplicity el.setAttribute(key, value) } } // Children if (vnode.children) { if (typeof vnode.children === 'string') { el.textContent = vnode.children } else { vnode.children.forEach(child => { mount(child, el) }) } } container.appendChild(el) }
function mount(vnode, container) { const el = vnode.el = document.createElement(vnode.tag) // Props if (vnode.props) { for (const key in vnode.props) { const value = vnode.props[key] // Assuming that all props are attributes for simplicity el.setAttribute(key, value) } } // Children if (vnode.children) { if (typeof vnode.children === 'string') { el.textContent = vnode.children } else { vnode.children.forEach(child => { mount(child, el) }) } } container.appendChild(el) }