feather-render
v1.1.9
Published
A feather light render framework
Downloads
5
Readme
Feather Render
✨ A feather light render framework ✨ 621 bytes minified and gzipped - no dependencies - SSR support
Companion frameworks:
Live examples:
Getting started
Package
npm i feather-render
...or inline
<head>
<script src="feather-render.min.js"></script>
</head>
<body>
<script>
const { html, hydrate } = window.__feather__ || {};
</script>
</body>
Index
Usage
Documentation
Examples
Usage
Basic syntax
import { html } from 'feather-render';
const TodoItem = ({ todo }) => {
return html`
<li>${todo.title}</li>
`;
};
const TodoList = () => {
return html`
<ul>${todos.map(todo => TodoItem({ todo })).join('')}</ul>
`;
};
const Document = () => html`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Feather</title>
<script type="module" src="index.js"></script>
</head>
<body>
${TodoList()}
</body>
</html>
`;
Tip: Plugins for VSCode like lit-html or Inline HTML can be used for syntax highlighting.
Server-Side Rendering (SSR)
import express from 'express';
import { Document } from './components/Document';
const server = express();
server
.get('/', (req, res) => {
res.send(Document().toString());
})
.use(express.static('dist'));
server.listen(5000);
Client hydration
import { hydrate } from 'feather-render';
import { TodoList } from './components/TodoList.js';
hydrate(TodoList(), document.body);
Documentation
html()
const { refs, render, mount, unmount } = html`<div></div>`;
Parameters
string
- html template string to render
Return value: Render
refs
- list of id'ed elementsrender
- return of functional componentmount()
- run callback on mountunmount()
- run callback on unmount
html().mount()
mount(() => {
console.log('Component inserted in DOM');
});
Parameters
callback()
- function called when component is inserted in DOM
Return value
void
html().unmount()
unmount(() => {
console.log('Component removed from DOM');
});
Parameters
callback()
- function called after component is removed from DOM
Return value
void
hydrate()
hydrate(App(), document.body);
Parameters
element
-Render
fromhtml()
target
- where to mount the DOM
Return value
void
Examples
- Re-rendering
- Event listeners
- Fetching
- Other
Re-rendering
Primitive values
import { store } from 'feather-state';
import { html } from 'feather-render';
const { watch, ...state } = store({
greeting: 'Hello, World'
});
const Component = () => {
const { refs, render } = html`
<p id="paragraph">${state.greeting}</p>
`;
// Watch greeting + update DOM
watch(state, 'greeting', (next) => {
refs.paragraph?.replaceChildren(next);
});
// Change greeting state
setTimeout(() => {
state.greeting = 'Hello, back!';
}, 1000);
return render;
};
Lists
import { store } from 'feather-state';
import { html } from 'feather-render';
const { watch, ...state } = store({
todos: ['Todo 1', 'Todo 2'];
});
const TodoItem = ({ todo }) => {
return html`
<li>${todo}</ul>
`;
};
const TodoList = () => {
const { refs, render, mount } = html`
<ul id="todoList">
${state.todos.map(todo => (
TodoItem({ todo })
)).join('')}
</ul>
`;
const reRenderTodos = () => {
const fragment = new DocumentFragment();
for (let todo of todoStore.todos) {
const { element } = TodoItem({ todo });
element && fragment.appendChild(element);
}
refs.todoList?.replaceChildren(fragment);
};
// Watch todos + update DOM
watch(state, 'todos', () => {
reRenderTodos();
});
// Hydrate TodoItems
mount(() => {
reRenderTodos();
});
// Append todo in state
setTimeout(() => {
state.todos = [...state.todos, 'Todo 3'];
}, 1000);
return render;
};
Event listeners
Form submission
import { html } from 'feather-render';
const Component = () => {
const { refs, render, mount, unmount } = html`
<form id="form">
<p id="status">Fill in form</p>
<input type="text" />
<button type="submit">Submit</button>
</form>
`;
const handleSubmit = (event) => {
event.preventDefault();
refs.status?.replaceChildren('Submitting');
};
mount(() => {
refs.form?.addEventListener('submit', handleSubmit);
});
unmount(() => {
refs.form?.removeEventListener('submit', handleSubmit);
});
return render;
};
Fetching
Server and client
const App = () => {
const { render } = html``;
fetch('http://localhost:5000/api/v1/user')
.then(res => res.json())
.then(res => console.log(res));
return render;
};
Server or client
const isServer = () => typeof window === 'undefined';
const isClient = () => typeof window !== 'undefined';
const App = () => {
const { render } = html``;
if (isServer()) {
fetch('http://localhost:5000/api/v1/user')
.then(res => res.json())
.then(res => console.log(res));
}
if (isClient()) {
fetch('http://localhost:5000/api/v1/user')
.then(res => res.json())
.then(res => console.log(res));
}
return render;
};
On mount
const App = () => {
const { render, mount } = html``;
mount(() => {
fetch('http://localhost:5000/api/v1/user')
.then(res => res.json())
.then(res => console.log(res));
});
return render;
};
Other
Lazy / Suspense
const App = () => {
const { render } = html`
<div id="lazyParent"></div>
`;
import('./LazyComponent').then(({ LazyComponent }) => {
const { element } = LazyComponent();
element && refs.lazyParent?.replaceChildren(element);
});
return render;
};
Unique id's
let i = 0;
export function id(name: string) {
return `${name}_${i++}`;
}
import { id } from '../helpers/id';
const App = () => {
const uniqueId = id('unique');
const { refs, render, mount } = html`
<div id=${uniqueId}></div>
`;
mount(() => {
refs[uniqueId]?.replaceChildren('Component mounted');
});
return render;
};
Roadmap 🚀
- CLI tool
- Automatically hydrate child components
- Cleaner way of referencing values in
html
- Binding values, re-renders and listeners
- CSS in JS examples