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:
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.
- Pages and layouts use kebab-case (
series-insight.vue
) - All other components (in folder
/components
) use PascalCase (CookieBanner.vue
)
- 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
) - Image files use kebab-case (
my-image.webp
) and .webp is the preffered image format
99% of the time your SCSS should be scoped, which makes sure your CSS won't bleed outside of your component and pollute the global namespace!
<template>
<div>
...
</div>
</template>
<script lang="ts" setup>
...
</script>
<style scoped lang="scss"></style>
Since we want to upgrade to Nuxt 3 in the near future, we should pre-emptively work towards a compatible codebase, such that the transition will be as smooth as possible. Therefore, every new feature is required to be written in the new Composition API and should follow the following recommendations:
<script lang="ts" setup>
import type { CarouselNFT } from '@/components/base/types'
// declaring props
const props = defineProps<{
nfts: CarouselNFT[]
}>()
// reactive state
const count = ref(0)
// functions that mutate state and trigger updates
function increment() {
count.value++
}
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// a computed ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
watch(count, (newCount) => {
console.log(`count is ${newCount}`)
})
// lifecycle hooks
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
For more details make sure to checkout Vue's official documentation.
Custom components and prop bindings should be used like this
<MyCustomComponent :propertyName="doSomething">
{{ someProperty }}
</MyCustomComponent>
Use shorthands for vue attributes
:href
instead ofv-bind:href
@click
instead ofv-on:click
- ...
Though we haven't yet transitioned most of our data fetching logic to Nuxt lifecycles, the following syntax should be considered best practice:
// useGraphql is a composable function that is auto-imported without having to use an explicit import statement
// you can then call a specific GraphQL query like this in any of your SFCs
const { data } = useGraphql({
queryName: 'buyEventByProfile',
variables: {
id: address,
},
})
For reference you can take a look at useCarousel.ts
and its usage throughout the app. It will show you how to best abstract such calls into its own composables, which is one of the core concepts behind the Composition API.
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.
<template>
<!-- ParentComponent.vue -->
<ChildComponent @remove="removeItemFromList" />
</template>
<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.
<script lang="ts" setup>
// List.vue
import { computed } from 'vue'
import ListItem from './ListItem.vue'
import type { IListItem } from '@/components/base/types'
const props = defineProps<{
items: IListItem[]
}>()
const getItemsWithText = computed(() => props.items.filter(item => item.text) || [])
</script>
Even if the statement of a block is just one line, stick to a more elaborate syntax and consistent way to do brace-style
❗ bad
if (something) return 1
if (something) return 1
else return 2
function foo()
{
return true;
}
✅ good
if (something) {
return 1
}
if (something) {
return 1
} else {
return 2
}
function foo() {
return true
}
Try to use more functional approaches since loop is really hard to maintain.
❗ bad
for (let x = 0; x < 10; x++) {
const element = list[x]
// your statement
}
✅ good
// Best es6 forEach loop
list.forEach(element => ...)
When working with images, it is recommended to utilize HTML tags and using a URL path, instead of directly importing images using JavaScript
❗ bad
// bad
import logo from './path/to/my-image.webp'
✅ good
<img src="/my-image.webp" alt="my-image" />
<!-- or -->
<img src="https://cdn.com/my-image.webp" alt="my-image" />