uldu

Ultra lightweight utility to create DOM nodes.

Performance Tests
Tested with Chrome dev tools:
- reload document
- start recording performance
- create 10 calendars
- update the year 10 times, e.g. switch 5 times to the next year and 5 times to
the previous year in the first calendar
- stop recording
(Clicking the lib title performs these actions automatically)
This aims to somehow reflect the lifetime of an application, create a view and
update the whole view at least once. In the case of uldu there is no dom diffing,
just replacing an outdated view.
Average of 10 records :

JavaScript bundle size of the test documents:

Basic Usage
import {
TEXT_NODE_NAME,
createDom,
render,
renderClean,
} from 'uldu';
A DOM node with some text:
render(['p', 'A paragraph'], document.body);
<body>
<p>A paragraph</p>
</body>
Optional attributes:
render(['p', {class: 'foo', id: 'bar'}, 'A paragraph'], document.body);
<body>
<p class="foo" id="bar">A paragraph</p>
</body>
Nested nodes:
render(['p', 'This is ', ['b', 'awesome']], document.body);
<body>
<p>This is <b>awesome</b></p>
</body>
Document fragments:
render([
['p', 'Paragraph 1'],
['p', 'Paragraph 2'],
], document.body);
<body>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</body>
Text nodes:
render([TEXT_NODE_NAME, 'This is really ', ['b', 'awesome']], document.body);
<body>This is really <b>awesome</b></body>
More advanced example
Live example
Github repo
Calendar.Templates = class {
static calendar(year, today, holidays) {
return (
['div', {'class': 'calendar'},
this.today(today),
this.year(year, today, holidays),
]
);
}
static today(today) {
const todayStr = [
WEEK_DAYS_LONG[today.day],
MONTH_NAMES[today.month],
String(today.date)
].join(' ');
return (
['header',
['span', {'data-handler': 'previous-year'},
['i', {'class': 'material-icons'}, 'chevron_left'],
],
['h1', todayStr],
['span', {'data-handler': 'next-year'},
['i', {'class': 'material-icons'}, 'chevron_right'],
],
]
);
}
static year(year, today, holidays) {
const tables = Array.from(range(12))
.map(month => this.month(year, month, today, holidays));
return ['section', ...tables];
}
static month(year, month, today, holidays) {
const weeksOfMonth = getWeeksOfMonth(year, month);
const weekDays = rotate(WEEK_DAYS_SHORT, 1);
const weekLabels = weekDays.map(wday => ['th', wday]);
const weekRows =
weeksOfMonth.map(week => this.week(year, month, week, today, holidays));
const holidaysList = this.holidays(year, month, holidays);
return (
['table',
['caption',
['span', {'class': 'month-name'}, MONTH_NAMES[month]],
['span', {'class': 'year-number'}, String(year)],
],
['thead',
['tr',
['th', 'Week'],
...weekLabels,
]
],
['tbody', ...weekRows],
holidaysList.length === 0 ?
[] :
['tfoot',
['tr',
['td', {'colspan': '8'}, holidaysList]
]
]
]
);
}
static week(year, month, week, today, holidays) {
const [weekNumber, weekDays] = week;
const weekRow = weekDays.map((day, index) => {
const className = classes(
isToday(today, year, month, day) && 'today',
isHoliday(index, holidays, year, month, day) && 'holiday');
return ['td', {'class': className}, day > 0 ? String(day) : ''];
});
return (
['tr',
['td',
['span', {'class': 'week-number'}, String(weekNumber)]
],
...weekRow
]
);
}
static holidays(year, month, holidays, withWeekNubers = true) {
const holidaysOfMonth = holidays.getHolidays(year, month);
return (holidaysOfMonth.length === 0 ?
[] :
['ul', {'class': 'holidays'},
...holidaysOfMonth.map(([day, name]) => ['li', `${day}. ${name}`])
]
);
}
}