A crazy-small framework for building brutal/brutalist web applications
ESlint compromised in an attempt to steal credentials.
This is terrible. And it is the ESlint that comes with Babel. Another great reason to not use enormous/bloated toolchains/transpilation and things like JSX/Babel/React. Just use small code you can read.
Brutal.js! FTW 😄
126 source lines of code. 2 functions: R
and render
Basic usage:
render(App(), document.getElementById('root'));
This is React/JSX:
function ButtonWidget(props) {
return (
<button onClick={() => showModal(props.name)}>
Show {props.name} Modal
</button>
);
}
This is R/brutal.js:
function ButtonWidget({name}) {
return R`
<button click=${() => showModal(name)}>
Show ${name} Modal
</button>
`;
}
- Event listeners are named by event names directly. No
on-
prefix. So useclick
notonclick
- There is never any need to quote an attribute, brutal does it for you, so
title=${title}
nevertitle="${title}"
- every bit of HTML is tagged with an R and written with backticks. Technically, this is an ES6 template literal and template tag function.
- Every replaced value is enclosed in
${...}
instead of{...}
To me, brutalist means as close to the basic raw HTML/ JavaScript as possible. There's more to do on the roadmap, but for many projects, these simple functions are enough. For example, take a look at a working TodoMVC example made with brutal.js. Everything in brutal is "as close to the metal" ( the JS / HTML ) as possible. This is ensured by their being minimal JS code, minimal opinionation (everything is just HTML elements and event handlers), leaving you free to structure things however you like.
npm i --save brutalist-web
<script src=node_modules/brutalist-web/r.js></script>
There isn't one!
Brutal is already perfect!
Perfection, in this field, I think — is complete with regard to its specified purpose.
No design is perfect, there's always tradeoffs. But with regard to implementing it's purpose, it is perfect. But with regard to how it implements it, it's not perfect, and no design is. There's always trade-offs, and from one perspective, things can be improved, which from another perspective, makes things worse.
But functionally, brutal.js is perfect. It just does the minimum of what you want it to do. How it does that, can be improved, no doubt!
If you have an idea, create a PR!
So it is perfect, except for some bugs. If you find those, open an issue!
If you know HTML and JS, you know brutal.js. Give it a spin, open an issue, make a PR, and let me know how you're using it, and where you think it should go.
The above function as an arrow in React/JSX:
const ButtonWidget = ({name}) => (<button onClick={() => showModal(name)}>Show {name} Modal</button>);
That same arrow in R/brutal.js:
const ButtonWidget = ({name}) => R`<button click=${() => showModal(name)}>Show ${name} Modal</button>`;
brutal.js costs you 1 more character, and saves you around 90Kb of framework to get to that point.
Just ~4Kb unzipped uminified. Compared to ~10x to 30x that gzipped minified for big frameworks.
Note: you can get a 101 SLOC ~3Kb version without any XSS protection.
Brutal is somewhat similar to lit-html.
It's also much smaller (4Kb compared to ~25kb unzipped unminified) and simpler.
And much more limited.
Brutal just supports adding event listeners to HTML, and templating values.
It does not support Promises, case-sensitive attribute names, or other "framework"-like complexities. If you want fetched data in your HTML output, fetch it before your HTML render, then render.
Some "Frameworks" want to restrict what you can do and "allow" and "disallow" you to do certain things, based on the "opinions" of their creators. Who cares what they think? Do what you want! Decide for yourself! Be a real human with a mind of their own and write your own code.
Maybe frameworks are just psychological emanations of the desire/need to control and be controlled? But that's not all humans can be/ not all how groups can interact. It's time for some "frameworks" that stop telling you what you can and can't do, and help you do what you want to do.
</manifesto>
With additional features come exponential or gemoetrically more code and bugs and interactions. This is mostly undesirable.
Brutal just wants to help you build things in a simple way and quickly. It has all the power of HTML/CSS/JS, just in a convenient JSX like syntax, but without any big files or build steps. Just works in the browser right now.
In this sense, Brutal.js is an anti-framework.
But that's not even it's aim.
It's aim is to get as close to the raw material (HTML/CSS/JS) as possible and let you decide how to work with it based on your function. It's meant to make it fast and easy for you to build what you want.
It doesn't have to be as hard as the frameworks think it does.
Use Brutal to write simple functions that render to HTML right off the bat in all modern browsers, without the burden of massive amounts of code, opinionated conceptual models, learning curves and technical-debt/lock in.
The simple way the web was meant to be.
Note the following section was adapted from / inspired by the README.md of lit-html an unrelated but syntax-similar framework. Lit-html does not support adding event listeners, and Brutal does support adding event listeners.
Any valid DOM/HTML event can be added. Here's a simple, literate and working example to create an editable div in Brutal.js:
const EditableDiv = content =>
R`<div class=edit
dblclick=${ editContent }
blur=${ endEdit }>${content}</div>`;
load = () => render(EditableDiv('hello world'), document.body);
function editContent({ dblClick: { srcElement: el }}) {
if (el.matches('.edit')) {
el.setAttribute('contenteditable','');
} else {
el.closest('.edit').setAttribute('contenteditable','');
}
}
function endEdit({ blur: { srcElement: el }}) {
el.removeAttribute('contenteditable');
}
Brutal is designed to be lightweight and fast. It utilizes the built-in JS and HTML parsers - it doesn't include any expression or markup parser of its own.
Anything coercible to strings are supported:
const Foo = () => R`foo is ${foo}`;
const BlueDiv = () => R`<div class="${blue}"></div>`;
const items = [1, 2, 3];
const Items = () => R`<ul>${items.map(i => R`<li>${i}</li>`)}</ul>`;
Remember: always use R
const Header = title => R`<h1>${title}</h1>`;
const App = () => R`
${Header('The head')}
<p>And the body</p>
`;
These features compose so you can render iterables of functions that return arrays of nested templates, etc...
Brutal has basically all of the benefits of HTML-in-JS systems like JSX, like:
There's no need to load an expression parser and evaluator.
Since template literals are evaluated in JavaScript, their expressions have access to every variable in that scope, including globals, module and block scopes, and this
inside methods.
If the main use of templates is to inject values into HTML, this breaks down a major barrier between templates and values.
They're just JavaScript expressions.
In a type-checking environment like TypeScript, expressions are checked because they are just regular script. Hover-over docs and code-completion just work as well.
Only ~1.5Kb minified gzipped compared to ~100kb for other frameworks. If you have 1 million requests per day this will save you 3 terabytes of data a month.
No tooling required. Understood by all JS editors and tools.
Because template literals use ${}
as the expression delimiter, CSS's use of {}
isn't interpreted as an expression. You can include style tags in your templates as you would expect:
R`<style>
:host {
background: burlywood;
}
</style>
`;