- This package contains custom rules to enforce the order of declarations within
<script setup>in Vue 3.
The declarations in <script setup> are sorted in the following fixed order:
"type",
"defineProps",
"defineEmits",
"defineSlots",
"defineModel",
"defineOptions",
"class",
"plainVars",
"reactiveVars",
"composables",
"computed",
"watchers",
"lifecycle",
"unknowns",
"functions",
"defineExpose"
A single blank line (which corresponds to two consecutive newline characters) is inserted between different groups.
This means that if you have a group of define declarations followed by another group (such as "plainVars"),
there will be one blank line between these groups in the final sorted output.
Example: Consider the following two groups:
Group 1 (defineProps):
const aa = defineProps<{ msg: string }>();Group 2 (plain variable declarations):
const hello = "Hello World!";
const count = 0The final sorted output will be:
const aa = defineProps<{ msg: string }>();
const hello = "Hello World!";
const count = 0Notice the blank line between the two groups, which helps visually separate different types of declarations.
By default, the rule enforces the predefined order of declarations within <script setup>.
However, you can customize the declaration order by specifying the sectionOrder option in eslint.config.js.
By default, the rule follows this order:
"type",
"defineProps",
"defineEmits",
"defineSlots",
"defineModel",
"defineOptions",
"class",
"plainVars",
"reactiveVars",
"composables",
"computed",
"watchers",
"lifecycle",
"unknowns",
"functions",
"defineExpose"If you want to specify a custom order, you can do so in eslint.config.js by providing a sectionOrder array.
Example: Prioritizing defineProps and plainVars
// eslint.config.js
export default [
{
files: ["**/*.vue"],
languageOptions: {
parser: vueEslintParser,
parserOptions: {
parser: typescriptEslintParser,
ecmaVersion: 2022,
sourceType: "module",
},
},
plugins: {
"vue3-script-setup": {
rules: {
"declaration-order": eslintVueSetupOrderRule,
},
},
},
rules: {
"vue3-script-setup/declaration-order": [
"error",
{
sectionOrder: ["defineProps", "plainVars"], // this!!
},
],
},
},
];In this case:
- defineProps will always be placed before plainVars.
- Other declarations will follow their default order.
If an invalid section is provided in sectionOrder, an ESLint error will be thrown.
For example, this incorrect configuration:
"vue3-script-setup/declaration-order": [
"error",
{
sectionOrder: ["defineProps", "invalidSection"],
},
],will result in the following error:
Error: Invalid "sectionOrder" option: "invalidSection" is not a recognized section. Valid sections: defineProps, defineEmits, defineOthers, plainVars, reactiveVars, composables, computed, watchers, lifecycle, functions, unknowns.
This ensures that only valid sections are allowed, preventing misconfiguration.
With this customization, you can fine-tune the declaration order to suit your projectβs coding style while still enforcing consistency. π
By default, the rule enforces the predefined order of declarations within <script setup>.
However, you can customize the declaration order by specifying the lifecycleOrder option in eslint.config.js.
By default, the rule follows this order:
onBeforeMount: 0,
onMounted: 1,
onBeforeUpdate: 2,
onUpdated: 3,
onBeforeUnmount: 4,
onUnmounted: 5,
onErrorCaptured: 6,
onRenderTracked: 7,
onRenderTriggered: 8,
onActivated: 9,
onDeactivated: 10,
onServerPrefetch: 11,If you want to specify a custom order, you can do so in eslint.config.js by providing a lifecycleOrder object.
Example: Prioritizing defineProps and plainVars
// eslint.config.js
export default [
{
files: ["**/*.vue"],
languageOptions: {
parser: vueEslintParser,
parserOptions: {
parser: typescriptEslintParser,
ecmaVersion: 2022,
sourceType: "module",
},
},
plugins: {
"vue3-script-setup": {
rules: {
"declaration-order": eslintVueSetupOrderRule,
},
},
},
rules: {
"vue3-script-setup/declaration-order": [
"error",
{
lifecycleOrder: { // this!!
onMounted: 0,
onBeforeMount: 1,
}
},
],
},
},
];In this case:
- onMounted will always be placed before onBeforeMount.
You can prevent the rule from moving a specific declaration by adding a // eslint-vue-setup-order:keep comment directly before or inline at the end of the declaration line.
When the rule encounters a node with this marker it is treated as pinned: it stays at its original position in the source, and the remaining (non-pinned) declarations are sorted into the free slots around it.
The comment must be directly adjacent to the declaration (no blank lines in between). If there is a blank line between the comment and the declaration, the comment will not pin the declaration.
<script setup>
// eslint-vue-setup-order:keep
const count = ref(0);
const emits = defineEmits();
const hello = "Hello World!";
</script><script setup>
const emits = defineEmits();
const count = ref(0); // eslint-vue-setup-order:keep
const hello = "Hello World!";
</script><script setup>
const hello = "Hello World!";
// eslint-vue-setup-order:keep
const count = ref(0);
const emits = defineEmits();
</script>In all these examples, count (a reactiveVars declaration) stays in its original position while the other declarations are sorted into the remaining free slots around it.
Note: The
// eslint-vue-setup-order:keepcomment must be directly adjacent to the declaration it pins (either on the line immediately before or at the end of the same line). A blank line between the comment and the declaration breaks the adjacency and prevents pinning. The comment itself is preserved as-is when the rule applies its auto-fix.
By default, the rule treats function calls whose names start with use as composables.
However, some utilities such as storeToRefs or mapState are composable in nature even though they do not follow the use* naming convention.
You can classify those functions as composables by using the composableAliases option in eslint.config.js.
If a function name is included in composableAliases, declarations initialized from that call will be sorted in the composables section.
Example:
const store = storeToRefs();
const state = mapState();With the following configuration, both declarations above are treated as composables.
// eslint.config.js
export default [
{
files: ["**/*.vue"],
languageOptions: {
parser: vueEslintParser,
parserOptions: {
parser: typescriptEslintParser,
ecmaVersion: 2022,
sourceType: "module",
},
},
plugins: {
"vue3-script-setup": {
rules: {
"declaration-order": eslintVueSetupOrderRule,
},
},
},
rules: {
"vue3-script-setup/declaration-order": [
"error",
{
composableAliases: ["storeToRefs", "mapState"], // this!!
},
],
},
},
];In this case:
storeToRefswill be treated as a composable.mapStatewill be treated as a composable.- They will be sorted in the
composablessection even though their names do not start withuse.
By default, declarations within the same section are placed consecutively with no blank line between them.
You can enable the spaceBetweenItems option to insert a blank line between every declaration inside a section, improving readability.
When spaceBetweenItems is set to true, each individual declaration within a section is separated by a blank line.
Blank lines between different sections are always present regardless of this option.
// eslint.config.js
export default [
{
files: ["**/*.vue"],
languageOptions: {
parser: vueEslintParser,
parserOptions: {
parser: typescriptEslintParser,
ecmaVersion: 2022,
sourceType: "module",
},
},
plugins: {
"vue3-script-setup": {
rules: {
"declaration-order": eslintVueSetupOrderRule,
},
},
},
rules: {
"vue3-script-setup/declaration-order": [
"error",
{
spaceBetweenItems: true, // <= like this
},
],
},
},
];Without spaceBetweenItems (default):
const count = ref(0);
const msg = ref("");With spaceBetweenItems: true:
const count = ref(0);
const msg = ref("");pnpm install eslint-vue-setup-rules
OR
yarn add eslint-vue-setup-rules
OR
pnpm install https://github.com/KumJungMin/eslint-vue-setup-order
Then, add the ESLint plugin to your eslint.config.js (for ESLint v9 using the flat config pattern):
// eslint.config.js
import vueSetupRules from "eslint-vue-setup-rules";
export default [
{
// Add the plugin object here:
plugins: { "vue3-script-setup": vueSetupRules },
rules: { "vue3-script-setup/declaration-order": "error" },
},
{
files: ["**/*.vue"],
languageOptions: {
parser: vueEslintParser,
parserOptions: {
parser: typescriptEslintParser,
ecmaVersion: 2022,
sourceType: "module",
},
},
},
// ... other settings
];Alternatively, you can add the rule file directly to your project:
- Add the file
rules/declaration-order.jsto your project directory:
your-project/
βββ src/
β βββ eslint-rules/
β βββ declaration-order.js
βββ eslint.config.js
βββ ...- Then, update your
eslint.config.jsto include the custom rule:
import eslintVueSetupOrderRule from "./src/eslint-rules/declaration-order.js";
export default [
{
files: ["**/*.vue"],
languageOptions: {
parser: vueEslintParser,
parserOptions: {
parser: typescriptEslintParser,
ecmaVersion: 2022,
sourceType: "module",
},
},
plugins: {
"vue3-script-setup": {
rules: {
"declaration-order": eslintVueSetupOrderRule, // this!
},
},
},
rules: {
"vue3-script-setup/declaration-order": "error",
},
},
// ... other settings
];When you run the command:
npx eslint .
If the declaration order is incorrect, the rule will automatically fix it. For example:
Before:
<script setup lang="ts">
import { onBeforeMount, ref } from "vue";
const count = ref(0);
const msg = ref("");
const aa = defineProps<{ msg: string }>();
const emits = defineEmits();
const hello = "Hello World!";
const changeMsg = () => {};
function handleClick() {
emits("click");
}
onBeforeMount(() => {
console.log("onBeforeMount");
});
</script>
<template>
</template>After (fixed):
<script setup lang="ts">
import { onBeforeMount, ref } from "vue";
const aa = defineProps<{ msg: string }>();
const emits = defineEmits();
const hello = "Hello World!";
const count = ref(0);
const msg = ref("");
onBeforeMount(() => {
console.log("onBeforeMount");
});
const changeMsg = () => {};
function handleClick() {
emits("click");
}
</script>