Note: These are just my personal notes. Rules which are formalised are moved to voorhoede/riotjs-style-guide
Opinionated RiotJS Style Guide for teams by De Voorhoede.
This guide provides a uniform way to structure your RiotJS code. Making it
- easier for developers / team mebers to understand and find things.
- easier for IDEs to interpret the code and provide assistance.
- easier to (re)use build tools you already use.
- easier to cache and serve bundles of code separately.
This guide is inspired by AngularJS Style Guide by John Papa.
Riot fits well in the "Module based development" philosophy, applied in all Voorhoede projects.
Modules should be
- Focused (single responsibility)
- Independent
- Reusable
- Small
- Testable
(https://addyosmani.com/first/)
- custom component tags
- view logic
- UI, not services, not data models, not presentational / appearance components like
.panel
.
- spec compliant, include hyphen
-
. - meaningful (not overspecific, not overly abstract)
- keep it short: 2 or 3 words
- pronouncable, as we want to be able talk about them
- avoid: tag, container, wrapper, etc.
Why?
Bundle all files which construct a component into a single place. This makes them easy to find, move and reuse.
How?
Use component name as directory name and file basename. The file extension depends on the purpose of the file.
modules/
my-example/
my-example.tag.html
...
README.md
Why?
- improves IDE support (signals how to interpret)
Why?
- Inline expressions can't be reused elsewehere. This can lead to code duplication and code rot.
- Complex inline expressions are hard to read.
- IDEs typically don't have support for expression syntax, so your IDE can't autocomplete or validate.
How?
/* recommended */
<datetime>{ this.timestamp() }</datetime>
/* avoid */
<datetime>
{ ((new Date()).getUTCMonth()+1) + ' ' + (new Date()).getUTCFullYear() }
</datetime>
Why?
- prevents markup being interpreted as script.
- improves IDE support (signals how to interpret)
- tells developers where markup stops and scripting starts
How?
/* recommended */
<my-example>
<h1>The year is { this.year }</h1>
<script>
this.year = (new Date()).getUTCFullYear();
</script>
</my-example>
/* avoid */
<my-example>
<h1>The year is { this.year }</h1>
this.year = (new Date()).getUTCFullYear();
</my-example>
Within the scope of a Riot tag element, this
is bound to the tag instance.
Therefore when you need to reference it in a different scope, ensure this
is available as tag
.
Why?
By assigning this
to a variable named tag
the variable tells developers it's bound to the tag instance wherever it's used.
How?
/* recommended */
// ES5: assign `this` to `tag`
var tag = this;
window.onresize = function() {
tag.adjust();
}
// ES6: you may still use `this`
window.onresize = () => {
this.adjust();
}
/* avoid */
var self = this;
var _this = this;
// etc
function myExampleTag(opts) {}
Riot exposes the parent tag on a tag instance through tag.parent
.
Why?:
- modules should be isolated. Accessing
tag.parent
breaks this rule.
How?:
- Pass values a tag needs from its parent to a child (via expression)
- Use callback expression
[examples]
Why?
- can use pre-processors
- can use your own build tools
- can minify, serve and cache separately
How?
Why?
How?
Use the component tag name as selector, as parent selector or as namespace prefix:
my-example { }
my-example li { }
.my-example__item { }
- If a component can just as easily be build without any vendor framework - including RiotJS, then do so. This promotes reusability across projects.