├── .github └── PULL_REQUEST_TEMPLATE.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md └── riotjs-style-guide.svg /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | Proposed changes: 4 | 5 | * .. 6 | * .. 7 | 8 | Resolves # . 9 | 10 | ## Checklist 11 | 12 | * [ ] The changes only affect or contain one style guide rule or section. 13 | * [ ] The changes are in [style guide format](https://github.com/voorhoede/riotjs-style-guide/blob/master/CONTRIBUTING.md#format). 14 | * [ ] The new style guide section has an entry in the [Table of Contents](https://github.com/voorhoede/riotjs-style-guide/blob/master/README.md#table-of-contents) (only if changes introduce new section). 15 | * [ ] The changes can be merged into the target branch without conflicts. 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | For new additions or changes to the guide, create a branch and submit a Pull Request. 4 | Only add/change 1 style guide rule per Pull Request. 5 | The Pull Request serves as a place to discuss and refine the additions/changes. 6 | 7 | ## Format 8 | 9 | Use [Github Flavoured Markdown](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown). 10 | 11 | Use short and descriptive rule names as 2nd level heading: 12 | 13 | ```markdown 14 | ## Keep expressions simple 15 | ``` 16 | 17 | Optionally add an short summary for the rule. 18 | 19 | ```markdown 20 | RiotJS allows any type of JavaScript expression as inline expression (`{ expression }`). 21 | ``` 22 | 23 | Describe **why** the rule exists. What purpose does it serve? 24 | 25 | ```markdown 26 | ### Why? 27 | 28 | Keep inline expressions simple because: 29 | 30 | * complex inline expressions are hard to read 31 | * inline expressions cant be reused 32 | ``` 33 | 34 | Describe **how** (and how not) to apply the rule. 35 | 36 | ```markdown 37 | ### How? 38 | 39 | If an expression becomes complex, move it to a tag property or method. 40 | ``` 41 | 42 | When using code snippets: 43 | * use ```html for improved syntax highlighting of Riot tag elements. 44 | * use ```javascript for improved syntax highlighting when script only. 45 | * use `` and `/* recommended */` or `` and `/* avoid */` at the start of the snippet to indicate if it's a good or bad practice. 46 | 47 | For example: 48 | 49 | ```html 50 | 51 | { this.timestamp() } 52 | 53 | 54 | 55 | { ((new Date()).getUTCMonth()+1) + ' ' + (new Date()).getUTCFullYear() } 56 | 57 | ``` 58 | 59 | Add a [↑ back to Table of Contents](README.md#table-of-contents) link at the end of each rule, so the reader can easily navigate back: 60 | 61 | ```markdown 62 | [↑ back to Table of Contents](#table-of-contents) 63 | ``` 64 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # CC0 1.0 Universal 2 | 3 | ## Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | **1. Copyright and Related Rights.** A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | **2. Waiver.** To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | **3. Public License Fallback.** Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | **4. Limitations and Disclaimers.** 94 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 95 | surrendered, licensed or otherwise affected by this document. 96 | 97 | b. Affirmer offers the Work as-is and makes no representations or warranties 98 | of any kind concerning the Work, express, implied, statutory or otherwise, 99 | including without limitation warranties of title, merchantability, fitness 100 | for a particular purpose, non infringement, or the absence of latent or 101 | other defects, accuracy, or the present or absence of errors, whether or not 102 | discoverable, all to the greatest extent permissible under applicable law. 103 | 104 | c. Affirmer disclaims responsibility for clearing rights of other persons 105 | that may apply to the Work or any use thereof, including without limitation 106 | any person's Copyright and Related Rights in the Work. Further, Affirmer 107 | disclaims responsibility for obtaining any necessary consents, permissions 108 | or other rights required for any use of the Work. 109 | 110 | d. Affirmer understands and acknowledges that Creative Commons is not a 111 | party to this document and has no duty or obligation with respect to this 112 | CC0 or use of the Work. 113 | 114 | For more information, please see 115 | 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RiotJS Style Guide 2 | 3 | Opinionated *RiotJS Style Guide* for teams by [De Voorhoede](https://twitter.com/devoorhoede). 4 | 5 | [![RiotJS Style Guide badge](https://cdn.rawgit.com/voorhoede/riotjs-style-guide/master/riotjs-style-guide.svg)](https://github.com/voorhoede/riotjs-style-guide) 6 | 7 | 8 | ## Purpose 9 | 10 | This guide provides a uniform way to structure your [RiotJS](http://riotjs.com/) code. Making it 11 | 12 | * easier for developers / team members to understand and find things. 13 | * easier for IDEs to interpret the code and provide assistance. 14 | * easier to (re)use build tools you already use. 15 | * easier to cache and serve bundles of code separately. 16 | 17 | This guide is inspired by the [AngularJS Style Guide](https://github.com/johnpapa/angular-styleguide) by John Papa. 18 | 19 | ## Demos 20 | 21 | Our [RiotJS demos](https://github.com/voorhoede/riotjs-demos#riotjs-demos-) are a companion to this guide, illustrating the guidelines with practical examples. 22 | 23 | ## Table of Contents 24 | 25 | * [Module based development](#module-based-development) 26 | * [Tag module names](#tag-module-names) 27 | * [1 module = 1 directory](#1-module--1-directory) 28 | * [Use `*.tag.html` extension](#use-taghtml-extension) 29 | * [Use ` 164 | ``` 165 | 166 | In case of [pre-compilation](http://riotjs.com/guide/compiler/#pre-compilation), set the [custom extension](http://riotjs.com/guide/compiler/#custom-extension): 167 | ```bash 168 | riot --ext tag.html modules/ dist/tags.js 169 | ``` 170 | 171 | In case you're using the [Webpack tag loader](https://github.com/srackham/tag-loader), [configure the loader](http://webpack.github.io/docs/using-loaders.html#configuration) to match the extension: 172 | ```javascript 173 | { test: /\.tag.html$/, loader: 'tag' } 174 | ``` 175 | 176 | [↑ back to Table of Contents](#table-of-contents) 177 | 178 | 179 | ## Use ` 200 | 201 | 202 | 203 | 204 |

The year is { this.year }

205 | 206 | this.year = (new Date()).getUTCFullYear(); 207 |
208 | ``` 209 | 210 | [↑ back to Table of Contents](#table-of-contents) 211 | 212 | 213 | ## Keep tag expressions simple 214 | 215 | Riot's inline [expressions](http://riotjs.com/guide/#expressions) are 100% Javascript. This makes them extemely powerful, but potentially also very complex. Therefore you should **keep tag expressions simple**. 216 | 217 | ### Why? 218 | 219 | * Complex inline expressions are hard to read. 220 | * Inline expressions can't be reused elsewehere. This can lead to code duplication and code rot. 221 | * IDEs typically don't have support for expression syntax, so your IDE can't autocomplete or validate. 222 | 223 | ### How? 224 | 225 | Move complex expressions to tag methods or tag properties. 226 | 227 | ```html 228 | 229 | 230 | { year() + '-' + month() } 231 | 232 | 237 | 238 | 239 | 240 | 241 | { (new Date()).getUTCFullYear() + '-' + ('0' + ((new Date()).getUTCMonth()+1)).slice(-2) } 242 | 243 | ``` 244 | 245 | [↑ back to Table of Contents](#table-of-contents) 246 | 247 | 248 | ## Keep tag options primitive 249 | 250 | Riot supports passing options to tag instances using attributes on tag elements. Inside the tag instance these options are available through `opts`. For example the value of `my-attr` on `` will be available inside `my-tag` via `opts.myAttr`. 251 | 252 | While Riot supports passing complex JavaScript objects via these attributes, you should try to **keep the tag options as primitive as possible**. Try to only use [JavaScript primitives](https://developer.mozilla.org/en-US/docs/Glossary/Primitive) (strings, numbers, booleans) and functions. Avoid complex objects. 253 | 254 | Exceptions to this rule are situations which can only be solved using objects (eg. collections or recursive tags) or well-known objects inside your app (eg. a product in a web shop). 255 | 256 | ### Why? 257 | 258 | * By using an attribute for each option separately the tag has a clear and expressive API. 259 | * By using only primitives and functions as option values our tag APIs are similar to the APIs of native HTML(5) elements. Which makes our custom elements directly familiar. 260 | * By using an attribute for each option, other developers can easily understand what is passed to the tag instance. 261 | * When passing complex objects it's not apparent which properties and methods of the objects are actually being used by the custom tags. This makes it hard to refactor code and can lead to code rot. 262 | 263 | ### How? 264 | 265 | Use a tag attribute per option, with a primitive or function as value: 266 | 267 | ```html 268 | 269 | 277 | 278 | 279 | 280 | ``` 281 | ```html 282 | 283 | 284 | { opts.text } 285 |
    286 |
  • 287 | 291 |
  • 292 |
293 |
294 | ``` 295 | 296 | [↑ back to Table of Contents](#table-of-contents) 297 | 298 | 299 | ## Harness your tag options 300 | 301 | In Riot your tag options are your API. A robust and predictable API makes your tags easy to use by other developers. 302 | 303 | Tag options are passed via custom HTML attributes. The values of these attributes can be Riot expressions (`attr="{ var }"`) or plain strings (`attr="value"`) or missing entirely. You should **harness your tag options** to allow for these different cases. 304 | 305 | ## Why? 306 | 307 | Harnessing your tag options ensures your tag will always function (defensive programming). Even when other developers later use your tags in ways you haven't thought of yet. 308 | 309 | ## How? 310 | 311 | * Use defaults for option values. 312 | * Use type conversion to cast option values to expected type. 313 | * Check if option exists before using it. 314 | 315 | For instance [Riot's `` example](http://riotjs.com/guide/#example) could be improved to also work if no `items` are provided, by using a default: 316 | 317 | ```javascript 318 | this.items = opts.items || []; // default to empty list 319 | ``` 320 | Ensuring different use cases all work: 321 | ```html 322 | 323 | 324 | ``` 325 | 326 | The `` in [keep tag options primitive](https://github.com/voorhoede/riotjs-style-guide#keep-tag-options-primitive) expects numbers for `min`, `max` and `step`. Use type conversion: 327 | 328 | ```javascript 329 | // if step option is valid, use as number otherwise default to one. 330 | this.step = !isNaN(Number(opts.step)) ? Number(opts.step) : 1; 331 | ``` 332 | Ensuring different use cases all work: 333 | ```html 334 | 335 | 336 | 337 | ``` 338 | 339 | The `` also supports *optional* `on-slide` and `on-end` callback. Check option exists and is in expected format before using it: 340 | 341 | ```javascript 342 | slider.on('slide', (values, handle) => { 343 | if (typeof opts.onSlide === 'function') { 344 | opts.onSlide(values, handle); 345 | } 346 | } 347 | ``` 348 | Ensuring different use cases all work: 349 | ```html 350 | 351 | 352 | 353 | ``` 354 | 355 | [↑ back to Table of Contents](#table-of-contents) 356 | 357 | 358 | ## Assign `this` to `tag` 359 | 360 | Within the context of a Riot tag element, `this` is bound to the [tag instance](http://riotjs.com/api/#tag-instance). 361 | Therefore when you need to reference it in a different context, ensure `this` is available as `tag`. 362 | 363 | ### Why? 364 | 365 | * By assigning `this` to a variable named `tag` the variable tells developers it's bound to the [tag instance](http://riotjs.com/api/#tag-instance) wherever it's used. 366 | 367 | ### How? 368 | 369 | ```javascript 370 | /* recommended */ 371 | // ES5: assign `this` to `tag` variable 372 | var tag = this; 373 | window.onresize = function() { 374 | tag.adjust(); 375 | } 376 | 377 | // ES6: assign `this` to `tag` constant 378 | const tag = this; 379 | window.onresize = function() { 380 | tag.adjust(); 381 | } 382 | 383 | // ES6: you can still use `this` with fat arrows 384 | window.onresize = () => { 385 | this.adjust(); 386 | } 387 | 388 | /* avoid */ 389 | var self = this; 390 | var _this = this; 391 | // etc 392 | ``` 393 | 394 | [↑ back to Table of Contents](#table-of-contents) 395 | 396 | 397 | ## Put tag properties and methods on top 398 | 399 | Inside a Riot tag element you typically put its markup first, followed by its script. 400 | Properties and methods bound to the tag (`this`) in the script are directly available in the markup. You should put those tag properties and methods alphabetized at the top of the script. Tag methods longer than a one-liner should be linked to separate functions later in the script. 401 | 402 | ### Why? 403 | 404 | * Placing the tag properties and methods at the top of the script allows you to instantly identify which parts of the tag can be used in the markup. 405 | * Alphabetizing the properties and methods makes them easy to find. 406 | * By keeping each tag method or property declaration a one-liner you can get a full overview of the tag at a glance. 407 | * By moving the full functions behind the tag methods down, you initially hide implementation details. 408 | 409 | ### How? 410 | 411 | Put tag properties and methods on top: 412 | 413 | ```javascript 414 | /* recommended: alphabetized properties then methods */ 415 | var tag = this; 416 | tag.text = ''; 417 | tag.todos = []; 418 | tag.add = add; 419 | tag.edit = edit; 420 | tag.toggle = toggle; 421 | 422 | function add(event) { 423 | /* ... */ 424 | } 425 | 426 | function edit(event) { 427 | /* ... */ 428 | } 429 | 430 | function toggle(event) { 431 | /* ... */ 432 | } 433 | 434 | /* avoid: don't spread out tag properties and methods over script */ 435 | var tag = this; 436 | 437 | tag.todos = []; 438 | tag.add = function(event) { 439 | /* ... */ 440 | } 441 | 442 | tag.text = ''; 443 | tag.edit = function(event) { 444 | /* ... */ 445 | } 446 | 447 | tag.toggle = function(event) { 448 | /* ... */ 449 | } 450 | ``` 451 | 452 | Also put mixins and observables up top: 453 | 454 | ```javascript 455 | /* recommended */ 456 | var tag = this; 457 | // alphabetized properties 458 | // alphabetized methods 459 | tag.mixin('someBehaviour'); 460 | tag.on('mount', onMount); 461 | tag.on('update', onUpdate); 462 | // etc 463 | ``` 464 | 465 | [↑ back to Table of Contents](#table-of-contents) 466 | 467 | 468 | ## Avoid fake ES6 syntax 469 | 470 | Riot supports a [shorthand *ES6 like* method syntax](http://riotjs.com/guide/#tag-syntax). Riot compiles the shorthand syntax `methodName() { }` into `this.methodName = function() {}.bind(this)`. Since this is non-standard you should **avoid fake ES6 method shorthand syntax**. 471 | 472 | ### Why? 473 | 474 | * The fake ES6 shorthand syntax is non-standard and can therefore confuse developers. 475 | * The tag scripts are not actual ES6 classes, so IDEs won't be able to interpret the fake ES6 class method syntax. 476 | * It should always be clear which methods are bound to the tag and thus available in the markup. The shorthand syntax obscures the principle of writing code which is transparent and easy to understand. 477 | 478 | ### How? 479 | 480 | Use `tag.methodName =` instead of magic `methodName() { }` syntax: 481 | 482 | ```javascript 483 | /* recommended */ 484 | var tag = this; 485 | tag.todos = []; 486 | tag.add = add; 487 | 488 | function add() { 489 | if (tag.text) { 490 | tag.todos.push({ title: tag.text }); 491 | tag.text = tag.input.value = ''; 492 | } 493 | } 494 | 495 | /* avoid */ 496 | todos = []; 497 | 498 | add() { 499 | if (this.text) { 500 | this.todos.push({ title: this.text }); 501 | this.text = this.input.value = ''; 502 | } 503 | } 504 | ``` 505 | 506 | **Tip**: [Disable transformation of the fake ES6 syntax](http://riotjs.com/guide/compiler/#no-transformation) during pre-compilation by setting `type` to `none`: 507 | 508 | ```bash 509 | riot --type none 510 | ``` 511 | 512 | [↑ back to Table of Contents](#table-of-contents) 513 | 514 | 515 | ## Avoid `tag.parent` 516 | 517 | Riot supports [nested tags](http://riotjs.com/guide/#nested-tags) which have access to their parent context through `tag.parent`. Accessing context outside your tag module violates the [FIRST](https://addyosmani.com/first/) rule of [module based development](#module-based-development). Therefore you should **avoid using `tag.parent`**. 518 | 519 | The exception to this rule are anonymous child tags in a [for each loop](http://riotjs.com/guide/#loops) as they are defined directly inside the tag module. 520 | 521 | ### Why? 522 | 523 | * A tag module, like any module, must work in isolation. If a tag needs to access its parent, this rule is broken. 524 | * If a tag needs access to its parent, it can no longer be reused in a different context. 525 | * By accessing its parent a child tag can modify properties on its parent. This can lead to unexpected behaviour. 526 | 527 | ### How? 528 | 529 | * Pass values from the parent to the child tag using attribute expressions. 530 | * Pass methods defined on the parent tag to the child tag using callbacks in attribute expressions. 531 | 532 | ```html 533 | 534 | 535 | 536 | 537 | 538 | 539 | { opts.value } 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | value: { parent.value } 549 | 550 | ``` 551 | ```html 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | ``` 572 | ```html 573 | 574 | 575 | 579 | 580 | 581 | ``` 582 | 583 | [↑ back to Table of Contents](#table-of-contents) 584 | 585 | 586 | ## Use `each ... in` syntax 587 | 588 | Riot supports multiple notations for [loops](http://riotjs.com/guide/#loops): item in array (`each="{ item in items }"`); key, value in object (`each="{ key, value in items }"`) and a shorthand (`each="{ items }"`) notation. This shorthand can lead to confusion. Therefore you should **use the `each ... in` syntax**. 589 | 590 | ### Why? 591 | 592 | Riot creates a new tag instance for each item the `each` directive loops through. When using the shorthand notation, the methods and properties of the current item are bound to the current tag instance (local `this`). This is not obvious when looking at the markup and may thus confuse other developers. Therefore you should **use the `each ... in` syntax**. 593 | 594 | ### How? 595 | 596 | Use `each="{ item in items }"` or `each="{ key, value in items }"` instead of `each="{ items }"` syntax: 597 | 598 | ```html 599 | 600 |
    601 |
  • 602 | 605 |
  • 606 |
607 | 608 | 609 |
    610 |
  • 611 | 614 |
  • 615 |
616 | 617 | 618 |
    619 |
  • 620 | 623 |
  • 624 |
625 | ``` 626 | 627 | [↑ back to Table of Contents](#table-of-contents) 628 | 629 | 630 | ## Put styles in external files 631 | 632 | For developer convenience, Riot allows you to define a tag element's style in a [nested ` 803 | 804 | ``` 805 | Note: this is a working concept, but could be much cleaner using build scripts. 806 | 807 | [↑ back to Table of Contents](#table-of-contents) 808 | 809 | 810 | ## Lint your tag files 811 | 812 | Linters improve code consistency and help trace syntax errors. With some extra configuration Riot tag files can also be linted. 813 | 814 | ### Why? 815 | 816 | * Linting tag files ensures all developers use the same code style. 817 | * Linting tag files helps you trace syntax errors before it's too late. 818 | 819 | ### How? 820 | 821 | To allow linters to extract the scripts from your `*.tag.html` files, [put script inside a `