safe point before auto-import cleanup

This commit is contained in:
Leonardo
2026-03-25 09:11:05 -03:00
parent 3f1786c9bf
commit bfe148ef12
7 changed files with 1532 additions and 11 deletions

3
.gitignore vendored
View File

@@ -6,7 +6,8 @@ coverage
.cache .cache
.output .output
# .env # .env
dist dist/
dist-*/
.DS_Store .DS_Store
.idea .idea
.eslintcache .eslintcache

136
docs/clean-auto-imports.md Normal file
View File

@@ -0,0 +1,136 @@
# clean-auto-imports.js
Script para remover imports manuais redundantes do projeto, considerando que o projeto usa `unplugin-auto-import` e `unplugin-vue-components` com `PrimeVueResolver`.
---
## Por que esse script existe
O projeto tem `vite.config.mjs` configurado com:
- **`unplugin-auto-import`** → auto-importa todas as APIs do Vue (`ref`, `computed`, `watch`, `onMounted`, etc.) em qualquer arquivo `.vue` ou `.js`
- **`unplugin-vue-components` + `PrimeVueResolver`** → auto-importa componentes do PrimeVue usados nos templates `.vue`
Com isso, manter os imports manuais desses símbolos é redundante. O script limpa tudo de uma vez.
---
## Como usar
### Dry-run (só lista o que seria alterado, sem escrever nada)
```bash
node scripts/clean-auto-imports.js --dry-run
```
### Aplicar as mudanças
```bash
node scripts/clean-auto-imports.js
```
### Após rodar, verificar se está tudo ok
```bash
npm run dev
```
---
## O que o script remove
### Em arquivos `.vue` e `.js`
Qualquer símbolo Vue dentro de `import { ... } from 'vue'` que já é auto-importado:
```js
// Antes
import { ref, computed, onMounted, watch } from 'vue'
// Depois (linha removida completamente)
```
Lista completa dos símbolos removidos:
`computed`, `createApp`, `customRef`, `defineAsyncComponent`, `defineComponent`, `effectScope`, `getCurrentInstance`, `getCurrentScope`, `h`, `inject`, `isProxy`, `isReactive`, `isReadonly`, `isRef`, `isShallow`, `markRaw`, `nextTick`, `onActivated`, `onBeforeMount`, `onBeforeUnmount`, `onBeforeUpdate`, `onDeactivated`, `onErrorCaptured`, `onMounted`, `onUnmounted`, `onUpdated`, `provide`, `reactive`, `readonly`, `ref`, `resolveComponent`, `shallowReactive`, `shallowReadonly`, `shallowRef`, `toRaw`, `toRef`, `toRefs`, `toValue`, `triggerRef`, `unref`, `useAttrs`, `useCssModule`, `useCssVars`, `useId`, `useModel`, `useSlots`, `useTemplateRef`, `watch`, `watchEffect`, `watchPostEffect`, `watchSyncEffect`
Tipos também removidos de `import type { ... } from 'vue'`:
`Component`, `ComputedRef`, `ComponentPublicInstance`, `DirectiveBinding`, `InjectionKey`, `PropType`, `Ref`, `ShallowRef`, `MaybeRef`, `MaybeRefOrGetter`, `VNode`, `WritableComputedRef`, e outros.
### Somente em arquivos `.vue`
Default imports de componentes PrimeVue:
```js
// Antes
import Button from 'primevue/button'
import DataTable from 'primevue/datatable'
import Select from 'primevue/select'
// Depois (linhas removidas)
```
---
## O que o script NÃO remove
### `main.js` é ignorado completamente
O arquivo de bootstrap do app nunca é tocado.
### Serviços e plugins do PrimeVue são mantidos
```js
import PrimeVue from 'primevue/config' // mantido
import ConfirmationService from 'primevue/confirmationservice' // mantido
import ToastService from 'primevue/toastservice' // mantido
```
### Composables do PrimeVue são mantidos
```js
import { useToast } from 'primevue/usetoast' // mantido
import { useConfirm } from 'primevue/useconfirm' // mantido
```
### Aliases Vue são mantidos se o original não está na lista
```js
import { ref as vRef } from 'vue' // "ref" está na lista → removido
import { defineProps as dp } from 'vue' // "defineProps" não está → mantido
```
### Imports de outros pacotes não são tocados
```js
import { useRouter } from 'vue-router' // mantido (não é do 'vue')
import { useAuthStore } from '@/stores' // mantido (local)
import { useFeriados } from '@/composables/useFeriados' // mantido
```
### Imports de componentes PrimeVue em arquivos `.js` são mantidos
O `PrimeVueResolver` só funciona em templates `.vue`. Se um `.js` importa um componente PrimeVue, provavelmente está sendo usado programaticamente — o script não toca esses casos.
---
## Troubleshooting
**Erro "X is not defined" após rodar o script**
Isso indica que o símbolo estava sendo usado fora do escopo que o auto-import alcança. Basta re-adicionar o import manualmente no arquivo específico.
**Quero revertir tudo**
Use o git:
```bash
git checkout src/
```
---
## Quando rodar novamente
O script é seguro para rodar múltiplas vezes (idempotente). Se novos arquivos forem criados com imports manuais, basta rodar novamente.

1096
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,8 +47,10 @@
"eslint-plugin-vue": "^9.23.0", "eslint-plugin-vue": "^9.23.0",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"rollup-plugin-visualizer": "^7.0.1",
"sass": "^1.55.0", "sass": "^1.55.0",
"tailwindcss": "^4.1.18", "tailwindcss": "^4.1.18",
"unplugin-auto-import": "^21.0.0",
"unplugin-vue-components": "^0.27.3", "unplugin-vue-components": "^0.27.3",
"vite": "^5.3.1", "vite": "^5.3.1",
"vitest": "^4.0.18" "vitest": "^4.0.18"

View File

@@ -0,0 +1,206 @@
/**
* clean-auto-imports.js
*
* Remove imports manuais redundantes de arquivos .vue e .js,
* considerando que o projeto usa:
* - unplugin-auto-import → auto-importa APIs do Vue
* - unplugin-vue-components + PrimeVueResolver → auto-importa componentes PrimeVue nos templates
*
* Uso: node scripts/clean-auto-imports.js
* Dry-run (sem escrever): node scripts/clean-auto-imports.js --dry-run
*/
import { readdir, readFile, writeFile } from 'node:fs/promises';
import { join, extname, relative, basename } from 'node:path';
import { fileURLToPath } from 'node:url';
const isDryRun = process.argv.includes('--dry-run');
// ─── Lista exata do auto-imports.d.ts gerado pelo unplugin-auto-import ────────
const VUE_AUTO_IMPORTS = new Set([
'EffectScope', 'computed', 'createApp', 'customRef', 'defineAsyncComponent',
'defineComponent', 'effectScope', 'getCurrentInstance', 'getCurrentScope',
'getCurrentWatcher', 'h', 'inject', 'isProxy', 'isReactive', 'isReadonly',
'isRef', 'isShallow', 'markRaw', 'nextTick', 'onActivated', 'onBeforeMount',
'onBeforeUnmount', 'onBeforeUpdate', 'onDeactivated', 'onErrorCaptured',
'onMounted', 'onRenderTracked', 'onRenderTriggered', 'onScopeDispose',
'onServerPrefetch', 'onUnmounted', 'onUpdated', 'onWatcherCleanup', 'provide',
'reactive', 'readonly', 'ref', 'resolveComponent', 'shallowReactive',
'shallowReadonly', 'shallowRef', 'toRaw', 'toRef', 'toRefs', 'toValue',
'triggerRef', 'unref', 'useAttrs', 'useCssModule', 'useCssVars', 'useId',
'useModel', 'useSlots', 'useTemplateRef', 'watch', 'watchEffect',
'watchPostEffect', 'watchSyncEffect',
// Tipos re-exportados (import type { ... } from 'vue')
'Component', 'Slot', 'Slots', 'ComponentPublicInstance', 'ComputedRef',
'DirectiveBinding', 'ExtractDefaultPropTypes', 'ExtractPropTypes',
'ExtractPublicPropTypes', 'InjectionKey', 'PropType', 'Ref', 'ShallowRef',
'MaybeRef', 'MaybeRefOrGetter', 'VNode', 'WritableComputedRef',
]);
// ─── Paths do PrimeVue que NÃO são componentes (serviços, plugins, diretivas) ─
const PRIMEVUE_KEEP_PATHS = new Set([
'primevue/config',
'primevue/confirmationservice',
'primevue/toastservice',
'primevue/dialogservice',
'primevue/useconfirm',
'primevue/usetoast',
'primevue/usedynamicdialog',
'primevue/ripple',
'primevue/tooltip',
'primevue/focustrap',
'primevue/styleclass',
'primevue/badgedirective',
'primevue/animateonscroll',
'primevue/passthrough',
'primevue/useStyle',
]);
// ─── Arquivos a ignorar ────────────────────────────────────────────────────────
const SKIP_FILES = new Set(['main.js', 'main.ts']);
// ─────────────────────────────────────────────────────────────────────────────
async function getAllFiles(dir) {
const results = [];
const entries = await readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
if (!['node_modules', '.git', 'dist', '.nuxt', 'public'].includes(entry.name)) {
results.push(...await getAllFiles(fullPath));
}
} else {
const ext = extname(entry.name);
if (['.vue', '.js', '.mjs'].includes(ext) && !SKIP_FILES.has(entry.name)) {
results.push(fullPath);
}
}
}
return results;
}
/**
* Remove do `import { ... } from 'vue'` (single e multi-line)
* os símbolos que já são auto-importados.
*/
function cleanVueImports(content) {
// Captura tanto single-line quanto multi-line: import [type] { ... } from 'vue'
const importRegex = /^import\s+(type\s+)?\{([^}]+)\}\s+from\s+['"]vue['"]\s*;?/gm;
let changed = false;
const modified = content.replace(importRegex, (match, typeKeyword, namedImports) => {
const symbols = namedImports
.split(',')
.map(s => s.trim())
.filter(Boolean);
// Para cada símbolo, verifica se está na lista de auto-imports
// Suporta aliases: "ref as vRef" → extrai "ref"
const keep = symbols.filter(sym => {
const name = sym.split(/\s+as\s+/)[0].trim();
return !VUE_AUTO_IMPORTS.has(name);
});
if (keep.length === symbols.length) return match; // nada a remover
changed = true;
if (keep.length === 0) return ''; // remove a linha toda
const isType = typeKeyword ? 'type ' : '';
return `import ${isType}{ ${keep.join(', ')} } from 'vue'`;
});
return { content: modified, changed };
}
/**
* Remove `import ComponentName from 'primevue/...'` em arquivos .vue.
* Mantém serviços/plugins listados em PRIMEVUE_KEEP_PATHS.
* Mantém named imports ({ useToast }) pois esses não são resolvidos pelo resolver de componentes.
*/
function cleanPrimeVueImports(content) {
// Apenas default imports de componentes (PascalCase)
const importRegex = /^import\s+([A-Z]\w*)\s+from\s+['"]primevue\/([^'"]+)['"]\s*;?\n?/gm;
let changed = false;
const modified = content.replace(importRegex, (match, componentName, path) => {
const fullPath = `primevue/${path}`;
if (PRIMEVUE_KEEP_PATHS.has(fullPath)) return match;
changed = true;
return '';
});
return { content: modified, changed };
}
/** Remove linhas em branco consecutivas excessivas (máx 2 \n seguidos) */
function collapseBlankLines(content) {
return content.replace(/\n{3,}/g, '\n\n');
}
async function processFile(filePath) {
const ext = extname(filePath);
const original = await readFile(filePath, 'utf-8');
let modified = original;
let changed = false;
// 1. Vue auto-imports → .vue e .js
const vueResult = cleanVueImports(modified);
if (vueResult.changed) {
modified = vueResult.content;
changed = true;
}
// 2. PrimeVue component imports → apenas .vue
if (ext === '.vue') {
const pvResult = cleanPrimeVueImports(modified);
if (pvResult.changed) {
modified = pvResult.content;
changed = true;
}
}
if (changed) {
modified = collapseBlankLines(modified);
if (!isDryRun) {
await writeFile(filePath, modified, 'utf-8');
}
}
return changed;
}
// ─── Main ─────────────────────────────────────────────────────────────────────
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const srcDir = join(__dirname, '..', 'src');
console.log(isDryRun ? '🔍 Dry-run ativo — nenhum arquivo será alterado\n' : '');
console.log(`Varrendo: ${srcDir}\n`);
const files = await getAllFiles(srcDir);
console.log(`${files.length} arquivos encontrados...\n`);
let modifiedCount = 0;
let skippedCount = 0;
for (const file of files) {
const wasModified = await processFile(file);
if (wasModified) {
console.log('✓', relative(srcDir, file));
modifiedCount++;
} else {
skippedCount++;
}
}
console.log(`\n─────────────────────────────────────────`);
console.log(`Modificados : ${modifiedCount} arquivos`);
console.log(`Sem mudança : ${skippedCount} arquivos`);
if (isDryRun) {
console.log('\n⚠ Dry-run: rode sem --dry-run para aplicar as mudanças.');
}

73
src/auto-imports.d.ts vendored Normal file
View File

@@ -0,0 +1,73 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
const EffectScope: typeof import('vue').EffectScope
const computed: typeof import('vue').computed
const createApp: typeof import('vue').createApp
const customRef: typeof import('vue').customRef
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
const defineComponent: typeof import('vue').defineComponent
const effectScope: typeof import('vue').effectScope
const getCurrentInstance: typeof import('vue').getCurrentInstance
const getCurrentScope: typeof import('vue').getCurrentScope
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
const h: typeof import('vue').h
const inject: typeof import('vue').inject
const isProxy: typeof import('vue').isProxy
const isReactive: typeof import('vue').isReactive
const isReadonly: typeof import('vue').isReadonly
const isRef: typeof import('vue').isRef
const isShallow: typeof import('vue').isShallow
const markRaw: typeof import('vue').markRaw
const nextTick: typeof import('vue').nextTick
const onActivated: typeof import('vue').onActivated
const onBeforeMount: typeof import('vue').onBeforeMount
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
const onDeactivated: typeof import('vue').onDeactivated
const onErrorCaptured: typeof import('vue').onErrorCaptured
const onMounted: typeof import('vue').onMounted
const onRenderTracked: typeof import('vue').onRenderTracked
const onRenderTriggered: typeof import('vue').onRenderTriggered
const onScopeDispose: typeof import('vue').onScopeDispose
const onServerPrefetch: typeof import('vue').onServerPrefetch
const onUnmounted: typeof import('vue').onUnmounted
const onUpdated: typeof import('vue').onUpdated
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
const provide: typeof import('vue').provide
const reactive: typeof import('vue').reactive
const readonly: typeof import('vue').readonly
const ref: typeof import('vue').ref
const resolveComponent: typeof import('vue').resolveComponent
const shallowReactive: typeof import('vue').shallowReactive
const shallowReadonly: typeof import('vue').shallowReadonly
const shallowRef: typeof import('vue').shallowRef
const toRaw: typeof import('vue').toRaw
const toRef: typeof import('vue').toRef
const toRefs: typeof import('vue').toRefs
const toValue: typeof import('vue').toValue
const triggerRef: typeof import('vue').triggerRef
const unref: typeof import('vue').unref
const useAttrs: typeof import('vue').useAttrs
const useCssModule: typeof import('vue').useCssModule
const useCssVars: typeof import('vue').useCssVars
const useId: typeof import('vue').useId
const useModel: typeof import('vue').useModel
const useSlots: typeof import('vue').useSlots
const useTemplateRef: typeof import('vue').useTemplateRef
const watch: typeof import('vue').watch
const watchEffect: typeof import('vue').watchEffect
const watchPostEffect: typeof import('vue').watchPostEffect
const watchSyncEffect: typeof import('vue').watchSyncEffect
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}

View File

@@ -3,6 +3,8 @@ import { fileURLToPath, URL } from 'node:url';
import { PrimeVueResolver } from '@primevue/auto-import-resolver'; import { PrimeVueResolver } from '@primevue/auto-import-resolver';
import tailwindcss from '@tailwindcss/vite'; import tailwindcss from '@tailwindcss/vite';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
import { visualizer } from 'rollup-plugin-visualizer'; // bundle analyzer
import AutoImport from 'unplugin-auto-import/vite'; // auto import Vue composables
import Components from 'unplugin-vue-components/vite'; import Components from 'unplugin-vue-components/vite';
import { defineConfig } from 'vitest/config'; import { defineConfig } from 'vitest/config';
@@ -15,21 +17,38 @@ export default defineConfig({
'@': fileURLToPath(new URL('./src', import.meta.url)) '@': fileURLToPath(new URL('./src', import.meta.url))
} }
}, },
//optimizeDeps: {
// noDiscovery: true
//},
plugins: [ plugins: [
vue(), vue(),
tailwindcss(), tailwindcss(),
// Auto import de APIs do Vue (ref, computed, onMounted, etc)
AutoImport({
imports: ['vue'],
dts: 'src/auto-imports.d.ts' // gera tipagem automática
}),
// Auto import de componentes do PrimeVue
Components({ Components({
resolvers: [PrimeVueResolver()] resolvers: [PrimeVueResolver()]
}),
// Visualizador de bundle (gera stats.html no build)
visualizer({
filename: 'dist/stats-before.html', // depois você muda pra stats-after.html
open: true,
gzipSize: true,
brotliSize: true
}) })
], ],
resolve: { resolve: {
alias: { alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)) '@': fileURLToPath(new URL('./src', import.meta.url))
} }
}, },
css: { css: {
preprocessorOptions: { preprocessorOptions: {
scss: { scss: {