Note
|
I’m currently going through the code base to refactor it, modifying this along the way. Guidelines defined here are temporary and subject to changes |
The style guide is based on vuejs', and rules defined there are to be followed except when superseded by rules defined here.
All components filenames should reflect the component’s name, and be in PascalCase.
Ordering of words in the name should be in generic-to-specific order: ButtonNormal
instead of NormalButton
. This greatly improves file sorting.
Additionally, prefix component names with the following:
Prefix | Situation |
---|---|
Base |
Components that are only used in other components |
The |
Components that are only used once in pages |
Input |
UI Component that are made for user data input |
Components that… | Should inherit from… |
---|---|
are cards |
|
are user input fields |
|
are modals interacting with resources (add/edit/delete) |
|
are modals |
|
All components should be tested using a single test file at tests/components/ComponentName.test.js
Use the following boilerplate for the tests:
import { bootstrapComponentTest } from '../utils'
import ComponentName from '~/components/ComponentName.vue'
const { mnt, store } = bootstrapComponentTest(
ComponentName
// Put the default store here
// Put the default propsData here
)
describe('<ComponentName>', () => {
it('does what it is supposed to do', () => {
const ComponentName = mnt(/* Provide propsData if needed */)
// Do your actions (emit a click, etc)
expect(/* predicate */)./* assertion */
})
})
Split your vuex code into modules: one per 'resource'. This should roughly match the API’s different URLS, eg. api.schoolsyst.com/homework
should be handled by store/homework.js
.
Include one empty array with the resource’s plural name as the key, one boolean value set to false
named loaded
, and additionnal constants that may be reused in the code base (eg. possible values of choice-restricted API fields.)
For each mutation, when the API request fails, handle the error by showing the user a $toast.error
.
Each mutation should return…
- When the action succeeded
-
[true, object, null]
withobject
the value returned by the matching mutation, or the inner action’sobject
return value. - When the action fails because of a validation error
-
[false, validation, 'validation']
withvalidation
the value returned by thevalidate
getter, or by the inner action’svalidation
return value - When the action fails because of a network error
-
[false, error, 'network']
witherror
the error object from thetry
/catch
block.
More generally, the return value is an array with:
-
whether the action succeeded or not
-
the object/array that results from the underlying mutation
-
the failure level, if any. If it succeeded, this is
null
.
Include the following actions:
- load({ force: false, verbose: true })
-
-
If
loaded
is false orforce
is true-
Do a
GET
request -
Use the
SET
mutation to store the response in the store -
Call the
POSTLOAD
mutation (which setsloaded
to true.) -
If
verbose
is true-
Show a
$toast.success
with an appropriate message
-
-
-
- post({ data, force: false, verbose: true })
-
-
If
force
is false-
Validate
data
with thevalidate
getter -
Let validated be the boolean result of that validation
-
-
If validated is true or
force
is true-
Do a
POST
request withdata
-
get the UUID from the response
-
Do a
GET
request with that UUID -
Call the
ADD
mutation with that response -
If
verbose
is true-
Show the user a
$toast.success
with an appropriate message
-
-
-
Else
-
Show the user a
$toast
with an appropriate error message -
Return false and the validation object
-
-
- patch({ pk, data, force: false, verbose: true })
-
-
Let hydrated be the object with
data
applied -
If
force
is false-
Validate hydrated with the
validate
getter -
Let validated be the boolean result of that validation
-
-
If validated is true or
force
is true-
Do a
PATCH
request withdata
-
get the UUID from the response
-
Do a
GET
request with that UUID -
Call the
CHANGE
mutation with that response -
If
verbose
is true-
Show the user a
$toast.success
with an appropriate message
-
-
-
Else
-
Show the user a
$toast
with an appropriate error message -
Return false and the validation object
-
-
- remove({ pk, force: false, verbose: true })
-
-
If
force
is false-
Search for a resource with
pk
as its primary key in the store -
If no object can be found
-
Return
[false, null, 'validation']
-
-
-
Let object be the found object
-
Return
[true, object, null]
-
Include the following getters:
- one(value, { by: 'your_default_pk' })
-
-
Return a single object from the resources object with its
by
property matchingvalue
.
-
- all()
-
-
Return the resource array from the state
-
- order(objects, { by: 'your_default_sorting_method' })
-
-
Return the
objects
, sorted using theby
sorting method. (each sorting method is specific to the resource)
-
- Language
-
pug
- Indentation
-
2 spaces
In single-line component uses: Put the class shorthand .class after an attribute list and the #id shortand before:
ComponentName#id(attr="value").class.class2
In multiline uses (eg. the attributes list is split on multiple lines): Put all of the shorthands before the attribute list
component#id.class.class2(
:is="componentName"
:style="styles"
:class="{clickable, multiline, thin, inline}"
:data-variant="variant"
type="button"
@click="$emit('click')"
v-tooltip="tooltipContent"
)
Put attributes in the following order:
-
Definition:
is
(or:is
) -
List rendering:
v-for
-
Conditionals
v-if
,v-else-if
,v-else
,v-show
,v-cloak
-
Render modifiers:
v-pre
,v-once
-
Unique attributes:
ref
,key
(or:key
) note:key
should be on the same line asv-for
, even when you use one attribute per line. This helps differentiatekey
from other props -
Two-way binding:
v-model
-
Other directives:
-
Bound attributes:
:attribute-name
-
Regular attributes:
attribute-name
-
Value-less attributes:
attribute-name
(without="value"
) -
Events:
@event-name
,v-touch
-
Content:
v-html
,v-text
,v-tooltip
Always use the v- shorthands:
Use… |
Instead of |
:attr
|
|
@event
|
|
#name
|
|
Only the following should be written in attribute values
-
a unique scalar value
-
for template strings, interpolated values should be variables only.
-
-
an object literal with ≤ 5 words:
-
a key counts as a word
-
a value counts as a word
-
the value should be a scalar or a variable
-
-
-
a unique function call
-
a unique variable
For object literals, don’t add spaces around the braces (but do it in JS code)
For multi-values attributes (eg. style
or class
), prefer an object literal of computed values instead of a single computed value returning the entire object:
<template>
component(
:style="{backgroundColor, color: subjectNameColor}"
)
<script>
computed: {
backgroundColor() {
return this.color || 'var(--black)'
},
subjectNameColor() {
return this.color ? this.textColor()(this.color) : 'var(--white)'
},
}
Instead of:
<template>
component(:style="styles")
<script>
computed: {
styles() {
return {
backgroundColor: this.color || 'var(--black)',
color: this.color ? this.textColor()(this.color) : 'var(--white)'
}
}
}
In conditonnal directives, the value can be one of the above, or a logical-operator-separated list of variables, but there must be at most two operators.
template(v-if="loggedIn || passwordForgotten")
If the exact same condition is reused, make a computed property instead.
If your component declares props that have different purposes, separate them with a comment into three categories:
-
Data
-
Supporting data (non-essential, must declare a default)
-
Styling
props: {
// Data
color: {
type: String,
default: null
},
name: {
type: String,
default: null
},
// Supporting data
placeholderName: {
type: String,
default: 'Choisir...'
},
// Style
clickable: {
type: Boolean,
default: false
},
variant: {
type: String,
default: 'badge'
},
thin: {
type: Boolean,
default: false
},
noTooltip: {
type: Boolean,
default: false
}
},
Separate your styles into sections using the following comment decorations (including the surrounding blank lines):
//
// SECTION_NAME
//
Include the following sections (even if empty, except for Hack), in order:
-
Definitions
Includes stylus variables & functions as well as CSS variables -
Layout
Includesposition
,display
, all positioning, sizing & spacing-related properties -
Decoration
_Includes allborder-
properties,outline
&box-shadow
. -
Colors
Includescolor
,background-color
,opacity
. -
Typography
Includes allfont-
properties andtext-decoration
. -
Reactions
Includes selectors that use:hover
,:focus
or other interaction-related pseudo selectors or classes, as well astransition
,animation
and@keyframes
. -
Hacks
CSS Hacks / bad practice styling rules. Don’t include the section comment if this section is empty
-
The class name is not unique to the component
-
Constructing multiple selectors with
&
becomes more readable than listing all selectors explicitly (should not exceed 3 indentation levels)
-
Put 2 empty lines between selectors that apply to a different part of the component
-
Put 1 empty lines between selectors that apply to the same part of the component
Sometimes, we’re forced to use constant values when refering to something else (eg. the padding on this component should be equal to the navbar’s height).
These kind of rules, if left undocumented, are huge sources of confusion when values referenced change.
To work around this, add a comment above or after the rule:
//ref: <COMPONENT_NAME> [SELECTOR] REFERENCED_PROPERTY [(EXPLANATION)]
With:
- COMPONENT_NAME
-
The referenced component
- SELECTOR
-
The selector (if omitted, the selector is the component’s root element)
- REFERENCED_PROPERTY
-
The property the rule gets its value from
- EXPLANATION
-
Explain why you must do a reference.
This repository follows the gitmoji commit standard. Additonal conventions are added on top of the intent-indicating emoji. Here’s how commit titles should be constructed:
EMOJI [SCOPE_PREFIX SCOPE SCOPE_SUFFIX] IMPERATIVE_SENTENCE
With:
- EMOJI
-
The emoji used. Must follow the gitmoji standard. A second emoji can be appended to the first if it is one of the following:
-
💩 (Writing bad code that needs to be fixed)
-
🚧 (Work in progress)
-
- SCOPE_PREFIX, SCOPE_SUFFIX
-
Indicates if the changes apply to one particular area of the codebase. Must be one of the Scope Prefix/Suffix characters
- SCOPE
-
Indicates the file/topic affected.
- IMPERATIVE_SENTENCE
-
A sentence at the imperative tense, indicating the change applied to the codebase.
The scope prefix indicates what area of the codebase changed. Less specific than the scope itself, but useful because file names overlaps. (eg. homework is a page & vuex module)
= |
Store (vuex) |
< |
Components |
/ |
Pages |
$ |
Plugins |
% |
Layouts |
~ |
Middlewares |
# |
Assets |
Other (no scope prefix) |
> |
Components |
: |
Everything else |