Skip to content

Latest commit

Β 

History

History
546 lines (446 loc) Β· 12.3 KB

File metadata and controls

546 lines (446 loc) Β· 12.3 KB

vue3-script-setup-custom-rules

  • This package contains custom rules to enforce the order of declarations within <script setup> in Vue 3.

πŸ›  Concept

πŸ“Œ Order

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"

πŸ“Œ Separation Between Groups

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 = 0

The final sorted output will be:

const aa = defineProps<{ msg: string }>();

const hello = "Hello World!";
const count = 0

Notice the blank line between the two groups, which helps visually separate different types of declarations.



πŸ›  Section Order Customization

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.

πŸ“Œ Default Order

By default, the rule follows this order:

"type",
"defineProps",
"defineEmits",
"defineSlots",
"defineModel",
"defineOptions",
"class",
"plainVars",
"reactiveVars",
"composables",
"computed",
"watchers",
"lifecycle",
"unknowns",
"functions",
"defineExpose"

πŸ“Œ Customizing the Order

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.

πŸ“Œ Invalid Section Order Handling

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. πŸš€



πŸ›  lifecycle Order Customization

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.

πŸ“Œ Default Order

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,

πŸ“Œ Customizing the Order

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.

πŸ›  Pinning a Declaration in Place

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.

πŸ“Œ How It Works

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.

πŸ“Œ Examples

Leading comment (on the line before):

<script setup>
// eslint-vue-setup-order:keep
const count = ref(0);

const emits = defineEmits();

const hello = "Hello World!";
</script>

Inline comment (at the end of the line):

<script setup>
const emits = defineEmits();

const count = ref(0); // eslint-vue-setup-order:keep

const hello = "Hello World!";
</script>

Pinned in the middle:

<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:keep comment 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.

πŸ›  composableAliases Option

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.

πŸ“Œ How It Works

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.

πŸ“Œ Configuration Example

// 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!!
        },
      ],
    },
  },
];

πŸ“Œ Result

In this case:

  • storeToRefs will be treated as a composable.
  • mapState will be treated as a composable.
  • They will be sorted in the composables section even though their names do not start with use.

πŸ›  spaceBetweenItems Option

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.

πŸ“Œ How It Works

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.

πŸ“Œ Configuration Example

// 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
        },
      ],
    },
  },
];

πŸ“Œ Result

Without spaceBetweenItems (default):

const count = ref(0);
const msg = ref("");

With spaceBetweenItems: true:

const count = ref(0);

const msg = ref("");

πŸ›  How to Apply

πŸ“Œ Method 1: Install via npm

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
];

πŸ“Œ Method 2: Include the Rules File in Your Project

Alternatively, you can add the rule file directly to your project:

  1. Add the file rules/declaration-order.js to your project directory:
your-project/
β”œβ”€β”€ src/
β”‚   └── eslint-rules/
β”‚       └── declaration-order.js
β”œβ”€β”€ eslint.config.js
└── ...
  1. Then, update your eslint.config.js to 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
];

πŸ›  Testing

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>