Support multiple `<script>` in `.vue` files
achaphiv opened this issue · comments
Version:
=> Found "eslint-plugin-simple-import-sort@7.0.0"
My .eslintrc.js
module.exports = {
root: true,
env: {
'browser': true,
'node': true,
// `defineProps` and `defineEmits`
'vue/setup-compiler-macros': true,
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:vue/vue3-recommended',
'@vue/eslint-config-prettier',
'plugin:nuxt/recommended',
],
plugins: ['simple-import-sort'],
settings: {
'import/resolver': {
typescript: {},
},
},
// add your custom rules here
rules: {
'simple-import-sort/imports': 'warn',
'simple-import-sort/exports': 'warn',
},
}
Vue v3 introduced <script setup>
.
Sometimes you need two <script>
sections. E.G.
<script setup lang="ts">
import SomeComponent from '@/components/SomeComponent.vue'
</script>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
layout: 'main',
})
</script>
However running this plugin produced this mangled output:
<script setup lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
import SomeComponent from '@/components/SomeComponent.vue'
export default defineComponent({
layout: 'siteMain',
})
</script>
I.E. it incorrectly merged the two <script>
into one.
I expected the two <script>
tags to remain separate. So the example input given here would be unchanged.
Hi! Are you sure this is a simple-import-sort issue, and not a eslint-plugin-vue issue? Is only simple-import-sort+vue the problem, or can you get similar effects with other ESLint rules that touch multiple lines, such as curly
and quotes
? (See https://github.com/lydell/eslint-plugin-svelte-multiline-fix-issue for some example code.)
TLDR: I suspect this is actually an issue with vue-eslint-parser
. So I will close.
I thought it was this plugin, because I had initially tried eslint-plugin-sort-imports-es6-autofix
, and that plugin didn't have a problem with multiple <script>
tags.
However that plugin doesn't have grouping logic, so it never actually tried to combine lines across the <script>
tags.
But another plugin, eslint-plugin-import
, does do <script>
combining.
So it seems like vue-eslint-parser
is the common factor.
@achaphiv I'm having this same issue and it only occurs when this plugin is enabled. I'm also using vue-eslint-parser
, but it's really difficult to test this without using it since a bunch of other eslint issues happen when using a <script setup lang="ts">
tag as well as a <script lang="ts">
tag in a vue file when not using vue-eslint-parser
.
Are you saying your script tags were combined when using eslint-plugin-import
but itself, or with simple-import-sort
? I tried using just eslint-plugin-import
and it sorts, but it doesn't combine the script tags. It just doesn't sort as nicely as simple-import-sort
in my opinion.
I ended up getting a configuration that works. The key is that the <script setup>
tag needs to be after the other <script>
tag.
.eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
globals: {
window: true,
module: true,
},
overrides: [],
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['vue', '@typescript-eslint', 'simple-import-sort', 'import'],
rules: {
quotes: ['error', 'single', { avoidEscape: true }],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'vue/multi-word-component-names': 'off',
'vue/component-tags-order': [
'error',
{
order: ['script:not([setup])', 'script[setup]', 'template', 'style'],
},
],
'simple-import-sort/imports': [
'error',
{
groups: [
// Packages `vue` related packages come first.
['^vue', '^\\w', '^@?\\w'],
// View and component first
['^(@/views)(/.*|$)'],
// Internal packages.
['^(@/scripts)(/.*|$)'],
// Side effect imports.
['^\\u0000'],
// Parent imports. Put `..` last.
['^\\.\\.(?!/?$)', '^\\.\\./?$'],
// Other relative imports. Put same-folder imports and `.` last.
['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
// Style imports.
['^.+\\.?(css)$'],
],
},
],
'simple-import-sort/exports': 'error',
'import/newline-after-import': 'error',
'import/no-duplicates': 'error',
},
};
ExampleComponent (using inertia.js persistent layouts):
<script lang="ts">
import Blank from '@/views/layouts/blank.vue';
export default {
layout: Blank,
};
</script>
<script setup lang="ts">
import route from 'ziggy-js';
import { Link } from '@inertiajs/vue3';
import FlashMessages from '@/views/components/flash-messages.vue';
import ClientLogoIconOnly from '@/views/components/logos/client-logo-icon-only.vue';
</script>
<template>
// your template
</template>
In the end I switched to: https://github.com/trivago/prettier-plugin-sort-imports
I forget the exact details why. It just worked better for me.
As a side note, I found performance was much better to have a separate overrides
just for *.vue
files. It made linting overall faster when processing *.ts
files.
Here's my full config for reference:
.prettierrc.cjs
:
module.exports = {
semi: false,
singleQuote: true,
quoteProps: 'consistent',
arrowParens: 'always',
htmlWhitespaceSensitivity: 'ignore',
plugins: [
require('@trivago/prettier-plugin-sort-imports'),
require('prettier-plugin-tailwindcss')
],
// https://github.com/trivago/prettier-plugin-sort-imports
importOrder: [
// `<svg>` components
'\\.svg(\\?.+)?$',
// Vue components (absolute)
'^@/.*\\.vue$',
// Vue components (relative)
'\\.vue$',
// External
'<THIRD_PARTY_MODULES>',
// Internal
'^@/',
// Sibling
'^[./]',
],
importOrderSeparation: true,
importOrderSortSpecifiers: true,
}
.eslintrc.cjs
:
module.exports = {
root: true,
env: {
browser: true,
node: true,
es6: true,
},
parser: '@typescript-eslint/parser',
// https://eslint.org/docs/user-guide/configuring/plugins#naming-convention
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'plugin:import/typescript',
],
plugins: ['@typescript-eslint', 'prettier', 'import'],
settings: {
'import/resolver': {
typescript: {},
},
},
rules: {
// sloppy code
'no-console': 'warn',
'no-lonely-if': 'warn',
// unused code
'no-empty': 'warn',
'require-await': 'warn',
// style
'curly': 'warn',
'lines-between-class-members': 'warn',
'no-var': 'warn',
'object-shorthand': 'warn',
'prefer-const': 'warn',
'@typescript-eslint/array-type': [
'warn',
{
default: 'generic',
readonly: 'generic',
},
],
'@typescript-eslint/consistent-type-imports': [
'warn',
{
// Disable to allow `typeof import('@/components/ui/BaseH5.vue')['default']`
disallowTypeAnnotations: false,
},
],
'prettier/prettier': 'warn',
'import/extensions': [
'warn',
'always',
{
'': 'never',
'js': 'never',
'jsx': 'never',
'ts': 'never',
'tsx': 'never',
},
],
'import/newline-after-import': 'warn',
'import/no-duplicates': 'warn',
},
overrides: [
{
files: ['*.vue'],
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
extends: [
'plugin:vue/vue3-recommended',
// Declared again to override `vue/vue3-recommended`
'plugin:prettier/recommended',
],
plugins: ['vue'],
// add your custom rules here
rules: {
// Disabled because of false positives with multiple `<script>`
'import/no-duplicates': 'off',
// sloppy code
'vue/multi-word-component-names': 'warn',
// unused code
'vue/no-empty-component-block': 'warn',
'vue/no-unused-components': 'warn',
'vue/no-useless-mustaches': 'warn',
'vue/no-useless-v-bind': 'warn',
// style
'vue/v-on-function-call': ['warn', 'never'],
'vue/component-definition-name-casing': ['warn', 'PascalCase'],
'vue/component-name-in-template-casing': [
'warn',
'PascalCase',
{ registeredComponentsOnly: false },
],
'vue/component-tags-order': [
'warn',
{
order: [
'template',
'route',
'script:not([setup])',
'script[setup]',
'style',
],
},
],
'vue/no-duplicate-attr-inheritance': ['warn'],
'vue/padding-line-between-blocks': ['warn'],
'vue/v-for-delimiter-style': ['warn', 'of'],
},
overrides: [
{
files: ['**/src/layouts/**/*.vue', '**/src/pages/**/*.vue'],
rules: {
'vue/multi-word-component-names': 'off',
},
},
],
},
],
}