Conventions
When working with multiple developers on the same projects, it's always a good idea to establish code conventions so the code becomes truly representative of the Company/App. With clear agreements, a team can code 'as one'. While architectural concerns already help in this regard, what about the actual code within the files? This is exactly why code conventions exist - to ensure consistency, predictability, maintainability, and last but not least, performance.
Vue API
We have Vue 3.5 now. Let's use the Composition API.
Dependencies Imports
To keep consistency, we will always make an effort to enforce this sequence:
- imports from 'vue'
- other imports from node_modules
- internal dependencies in ASC order
- @Composables
- @Helpers
- @Types
- UI in ASC order
- @Atoms
- @Molecules
- @Organisms
- @Templates
- @Layouts
- Modules in ASC order
- internal dependencies from the current domain
- './styles'
- './components'
- './providers'
Here is an example:
👎🏼 BAD
<script setup>
import { AuthForm } from '@Auth';
import { defineStore } from 'pinia';
import { hidePrompt, showPrompt } from '@Helpers';
import InternalComponent from '../../components';
import { computed, useAttrs } from 'vue';
import { AButton } from '@Atoms';
</script>👍🏼 GOOD
<script setup>
import { computed, useAttrs } from 'vue';
import { defineStore } from 'pinia';
import { hidePrompt, showPrompt } from '@Helpers';
import { AButton } from '@Atoms';
import { AuthForm } from '@Auth';
import InternalComponent from '../../components';
</script>Sorting
To make this subject as simple as possible, we will always sort everything in ASC order. Please, don't do it manually! Use your IDE shortcut for it: F9
Here are a few examples:
One Liners
👎🏼 BAD
const someRef = ref();
const word = ref();
const other = ref();👍🏼 GOOD
const other = ref();
const someRef = ref();
const word = ref();Mixed Content
In these cases, we sort the one-liners first and then the multi-liners:
👎🏼 BAD
const someRef = ref();
const word = ref();
const multipleLiner = ref({
something: 1
})
const other = ref();👍🏼 GOOD
const other = ref();
const someRef = ref();
const word = ref();
const multipleLiner = ref({
something: 1
})TIP
HTML content has its own rules and there will be a section to cover that.
Feature Implementation
Quoting the Vue official docs:
Some users moving from Options API found their Composition API code less organized, and concluded that Composition API is "worse" in terms of code organization. We recommend users with such opinions to look at that problem from a different perspective.
It is true that Composition API no longer provides the "guard rails" that guide you to put your code into respective buckets. In return, you get to author component code like how you would write normal JavaScript. This means you can and should apply any code organization best practices to your Composition API code as you would when writing normal JavaScript. If you can write well-organized JavaScript, you should also be able to write well-organized Composition API code.
With that in mind, here are some conventions that I've set when implementing features, especially in views. These conventions try to mimic the Options API best practices. When I say mimic, this is the reason:
The style guide is currently a bit outdated. Most examples are in Options API only, and there are no rules regarding
<script setup>and Composition API. We are planning to improve it in the future.
After the dependency imports, here is how we are implementing features; the way that we are grouping them:
1 - Directives
- Should be named just like this:
vMyDirectiveName - Make sure that this is actually a directive and not a composable or a helper
Components are the main building blocks, while composables are focused on reusing stateful logic. Custom directives, on the other hand, are mainly intended for reusing logic that involves low-level DOM access on plain elements.
2 - Macros
- always in ASC order
- defineEmits, defineProps, etc.
👎🏼 BAD
<script setup>
defineProps<{
prop1: string;
prop2: number;
}>();
defineEmits<{
onSubmit: [input: string]
}>();
</script>👍🏼 GOOD
<script setup>
defineEmits<{
onSubmit: [input: string]
}>();
defineProps<{
prop1: string;
prop2: number;
}>();
</script>3 - Stores
4 - Helpers
5 - Composables
6 - Reactivity
defineModel()refreadonlyreactivecomputedinjected
7 - Provide
8 - Watchers
9 - Lifecycle
10 - Methods
In order to improve code legibility, do not use ES6+ syntax when declaring your functions since the syntax between the functions and other variables (ref, reactive, props, emits, etc.) would have no difference.
Keep in mind that even Evan You - the original creator - is not using ES6+ syntax in Vue core and it's also suggested in the official docs. Let's follow his example! 😃
👎🏼 BAD
<script setup>
// using ES6+
const emits = defineEmits([]);
const props = defineProps({});
const myRef = ref(1);
const myComputedProperty = () => myRef.value * -1;
const myMethod = (param) => {};
// No syntax difference from emits, props,
// myRef, myComputedProperty to myMethod :/
</script>👍🏼 GOOD
<script setup>
const emits = defineEmits([]);
const props = defineProps({});
const myRef = ref(1);
const myComputedProperty = () => myRef.value * -1;
function myMethod(param) {
console.log(param);
}
// Oh yeah!
</script>