Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add first version of kodadot style guide 💅 #2733

Merged
merged 4 commits into from
Apr 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Before you being:
- Make sure that you use [**pnpm**](https://pnpm.io/installation) as the package manager.
- Please have a read the [code of conduct](CODE_OF_CONDUCT.md)
- [Learn how to set up your environment for the first time](FIRST_TIME.md)
- Get familiar with our [coding conventions & recommendations](STYLE_GUIDE.md)

### Which issue should you pick?

Expand Down
199 changes: 199 additions & 0 deletions STYLE_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# KodaDot Style Guide v1.0

As codebases grow in size and complexity, it is necessary to establish and maintain some kind of **style guide to which each contributor should conform**. While tools like ESLint and Prettier can support us in this regard, additional rules are needed to allow us to better communicate with each other. Since our community is coming from all around the world, working together asynchronously, **clear communication** is of utmost importance.

The following set of conventions should make it easier for you to understand our code and aid you in making meaningful contributions to the project:

## Naming Conventions
Give your functions, components and files **speaking/self-explanatory names** (e.g. `getCollectionById()`, `KeyboardShortcuts.vue`, `setIdentity.ts`).
With a few exceptions, code and comments should be written in **English** only.

### Nuxt/Vue-specific Files
- **Pages** and **layouts** use **kebab-case** (`series-insight.vue`)
- **All other components** (in folder `/components`) use **PascalCase** (`CookieBanner.vue`)

### Others
- **Folders** are written in **lowercase** (`components/spotlight`)
- **Typescript**, **Javascript** and **GraphQL** files use **camelCase** (`globalVariables.ts`, `getKey.js`, `collectionById.graphql`)
- **SCSS** files use **kebab-case** (`initial-variables.scss`)
- **JSON** files use **snake_case** (`all_lang.json`) while **Markdown** files use **SCREAMING_SNAKE_CASE** (`CONTRIBUTING.md`)

## SFC Conventions
### Skeleton
99% of the time your SCSS should be **scoped**, so it won't bleed outside of your component and pollute the global namespace!
```vue
<template>
<div>
...
</div>
</template>

<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import { ComponentOne } from '@/components/ComponentOne.vue'

interface InterfaceName {
property: type
}

@Component({
components: {
ComponentOne
},
})
export default class ComponentName extends Vue {}
</script>

<style scoped lang="scss"></style>
```

### Property Decorators
We rely on the package 'nuxt-property-decorator', hence, we urge you to comply with the [Nuxt Class Component Syntax](https://github.com/nuxt-community/nuxt-property-decorator/)
```typescript
import {
Component,
Inject,
Model,
Prop,
Provide,
Vue,
Watch,
} from "nuxt-property-decorator"

@Component({})
export class MyComponent extends Vue {
@Inject() foo!: string

@Model("change") checked!: boolean

@Prop() propA!: number

@Provide() foo = "foo"

@Watch("child")
onChildChanged(val: string, oldVal: string) {}
}
```


### Using Components In Templates
Custom components and prop bindings should be used like this
```vue
<MyCustomComponent :property-name="doSomething">
kkukelka marked this conversation as resolved.
Show resolved Hide resolved
{{ someProperty }}
</MyCustomComponent>
```

Use shorthands for vue attributes
- `:href` instead of `v-bind:href`
- `@click` instead of `v-on:click`
- ...


### Fetching Data
Though we haven't yet transitioned most of our data fetching logic to Nuxt lifecycles, the following syntax should be considered best practice:
```typescript
// pages
import { Component } from 'nuxt-property-decorator'

@Component({
async asyncData({ app, $config, params }) {
const res = await app?.apolloProvider?.clients[$config.prefix].query({
query: queryGql,
variables: {
id: params.id
},
})

return {
data: res.data,
total: res.total,
}
}
})
kkukelka marked this conversation as resolved.
Show resolved Hide resolved
export default class ClassName extends Vue {
data?: Type
total?: Type

[...]
}
```


### Reusability Through Abstraction
If your component will be used on several occasions in many different contexts, you should think about how you pass data to your components and how events are handled.
Regarding event handling, you should always aim to emit events happening in your component, while the handling should be done in the parent or page itself. Thereby, the click of a button can trigger all kinds of functionality without being aware of its context.
```vue
<template>
<!-- ParentComponent.vue -->
<ChildComponent @remove="removeItemFromList" />
</template>
```
```vue
<template>
<!-- ChildComponent.vue -->
<div @click="$emit('remove')" />
</template>
```

Make reusable components as generic as possible. Therefore, the naming should only imply the functionality of the component itself and not what it does in the given context.
```vue
<script lang="ts">
import { Component, Prop, Vue } from 'nuxt-property-decorator'
import { ListItem } from '@/components/ListItem.vue'

interface IListItem {
name: string,
text?: string,
}

@Component({
components: {
ListItem
},
})
export default class List extends Vue {
@Prop({ type: Array, default: () => [] }) items!: IListItem[]

get getItemsWithText(): IListItem[] {
return this.items.filter(item => item.text) || []
}
}
</script>
```

## Vuex Conventions
- each module is defined in its own file -> Explorer Module has its state, actions, mutations, getters defined in `explorer.ts`
- **mutations** should only be **triggered via actions** and should be named in **SCREAMING_SNAKE_CASE**:
```typescript
// action
setLayoutClass({ commit }: { commit: Commit }, data) {
commit('SET_LAYOUT_CLASS', data)
},
// mutation
SET_LAYOUT_CLASS(state: PreferencesState, data) {
state.layoutClass = data
}
```
- **state properties** should always be **accessed via getters**:
```typescript
get classLayout() {
return this.$store.getters['preferences/getLayoutClass']
}
```

## General Conventions
### if...else
Even if the statement of a block is just one line, stick to a more elaborate syntax:
❗ bad
```js
if (something) return 1
```


✅ good
```js
if (something) {
return 1
}
```