-
Notifications
You must be signed in to change notification settings - Fork 4
Guidelines
These are some patterns and guidelines (in no particular order) that we try to
follow. The goal is to write metal-soy
code that is as
consistent and maintainable as possible.
To import components in our .js files split them in two blocks, first the components with absolute paths and after the components with relative paths, both blocks ordered alphabetically:
import Component from 'metal-component';
import core from 'metal';
import Soy from 'metal-soy';
import { debounce } from 'metal-debounce';
import { on } from 'metal-dom';
import templates from './MyComponent.soy';
Soy has two ways of declaring parameters to a template: in it's soydoc comment
or using the @param
command. The soydoc comment style has been deprecated (see
here), so prefer the latter:
{template myTemplate}
{@param name: string}
<h1>Hello {$name}!</h1>
{/template}
Keep all of the parameters in a single block, sorted alphabetically, with optional parameters coming after required. Most text editors make easy work of this since it should be the default order when the lines are run through a sorting routine.
{namespace MyComponent}
/**
* MyComponent
*/
{template .render}
{@param backURL: string}
{@param contactsCardTemplateTypes: ?}
{@param id: string}
{@param pathThemeImages: string}
{@param portletNamespace: string}
{@param? _handleEditCard: any}
{@param? _handleHideCreateCard: any}
{@param? _handleShowCreateCard: any}
{@param? showCreateCardModal_: bool}
In your javascript file, make sure that all params are listed in
the component's STATE
declaration as well, even if it could have been omitted
since it would only be referenced in the template. Also prefer using the
Config
helper exported by metal-soy
:
import Component, {Config} from 'metal-component';
class MyComponent extends Component {}
MyComponent.STATE = {
/**
* Make sure to add the required() flag if the prop does not have a `?` in
* your template.
**/
name: Config.string().required()
};
export default MyComponent;
When specifying types of params, try to be consistent and use the correct types:
- For primitives, use (
string
,number
, etc.).Map
s andList
s should usemap<T, U>
andlist<T>
. - For functions, use the
any
type, since there is no native function type for Soy. - For records, there is a record syntax that would be useful, however it
currently will lead to issues with your javascript component, so for the moment
just use
?
, which will still allow member access using the.
operator. See this issue for the status for record type syntax formetal-soy
.
Private attributes should be named with a trailing underscore (count_
,
myName_
, etc.), should use the internal()
flag, and will need to be
declared as optional in the template (@{param? name: string}
).
Prefer the ?:
operator over the ternary (? :
), when declaring default
values in your template:
/* Bad */
<h1>{isNonnull($name) ? $name : 'Foo'}</h1>
/* Good */
<h1>{$name ?: 'Foo'}</h1>
Prefer using the events
prop and calling this.emit()
internally, instead of
passing functions as props:
/* Bad */
{call MyEditor.render}
{param onChange: $_handleChange /}
{/call}
/* Good */
{call MyEditor.render}
{param events: ['change': $_handleChange] /}
{/call}
It is often the case that a component will need to handle adding class given a
variety of different conditions. Instead of trying to cram all of the logic onto
a single line with many {if}
checks or ternary statements, use a {let}
and space them out:
/* bad */
<div class="my-component modifier-class{if $foo} some-class{/if}{if $bar} some-bar{/if}{if $baz} some-baz{/if}"></div>
/* good */
{let $classes kind="text"}
my-component
{sp}modifier-class
{if $foo}
{sp}some-class
{/if}
{if $bar}
{sp}some-bar
{/if}
{if $baz}
{sp}some-baz
{/if}
{/let}
<div class="{$classes}"></div>
We also make sure to explicitly add spaces using the {sp}
command. It has the
nice side-effect of also making it harder to miss one because they stand out
visually.
Soy has a feature that allows all parameters in the parent scope to be
automatically passed to a template being called ({call .mySubtemplate data="all" /}
). There are several reasons to avoid this feature.
Because metal-soy
is both a templating and component system, it's very
common that calls to other templates are in fact calls to other components as
well (as opposed to helper templates declared in the same namespace). This means
that if the parent and child template have a prop by the same name, it's likely
that they correspond to different things. Even worse if that prop happens to be
optional for the child component. It is very easy to find yourself passing bad
data to a child template accidentally through data=all
, making it hard to debug
problems, without any warnings. We know this from experience.
It also makes reading the code much harder. Understanding which props are being used now requires exact knowledge of which params the component takes. Explicitly passing those props is much easier to read. To quote the Zen of Python, "Explicit is better than Implicit".