# Proposta de Melhorias para projeto em Vue
Este documento tem como objetivo demonstrar, após análise do projeto de front da **Maxipas**, melhorias a respeito da arquitetura e recursos do mesmo. Tais melhorias são insights obtidos através de experiências em outros projetos reais com deploys em produção, porém não são absolutas e estão totalmente abertas à sugestões e críticas.
### 1. Devops & CI/CD
Como trata-se de uma aplicação client-side, uma opção com ótima relação custo/benefício seria a distribuição através do CloudFront (AWS) do build armazenado no S3 (AWS). O Processo de CI/CD fica mais simples e a utilização de containers desnecessária.
```yaml
build_prod:
image: node:14-alpine
stage: build
before_script:
- yarn
- export AUTH_API_URL=$PROD_AUTH_API_URL
script:
- yarn build:prod
artifacts:
paths:
- dist/spa
expire_in: 20 mins
environment:
name: production
except:
- schedules
- triggers
only:
- main
deploy_prod:
stage: deploy
image:
name: amazon/aws-cli
entrypoint: [""]
script:
- echo $PROD_S3_BUCKET_NAME
- echo $PROD_CDN_DISTRIBUTION_ID
- aws --version
- aws s3 cp dist/spa s3://$PROD_S3_BUCKET_NAME --recursive
- aws cloudfront create-invalidation --distribution-id $PROD_CDN_DISTRIBUTION_ID --paths "/*"
environment:
name: production
only:
- main
```
### 2. TS vs JS
Utilizar TypeScript pode representar um ganho de performance a médio e longo prazo, pois padroniza a comunicação entre componentes e serviços.
Exemplo de declarações de types:
```typescript=
export type User = {
name: string;
surname: string;
email: string;
permissions: Permission[]
}
export type Permission = {
name: string;
modules: Module[];
}
export type Module = {
name: string;
description: string;
path: string;
position: number;
active: boolean;
icon: string;
}
```
### 3. Estrutura e Componentes
Vue é framework progressivo, onde a estrutura de componentes pode ser amplamente utilizada para dividir responsabilidades e multiplicar a reusabilidade.
Exemplo de estrutura de componentes reutilizáveis.
```
> src/
> components/
> Forms/
> GenericFilter.vue
> Autocomplete.vue
> InfiniteScroll.vue
> Tables/
> GenericTable.vue
> RowItem.vue
> TableSkeleton.vue
> Generic
> CustomCard.vue
> CustomAvatar.vue
> CustomDialog.vue
> pages/
> Home.vue
> Profile.vue
> Users.vue
```
### 4.Composition API vs Options API
Composition API torna o código mais simples e reutilizável, facilitando a integração entre componentes. Tem uma melhor relação com o TS e substitui a utilização de Mixins, além de muitos outros benefícios, como injeção de dependências, hooks de ciclo de vida, diminuição no bundle de prod.
https://vuejs.org/guide/extras/composition-api-faq.html
Exemplo de estrutura do Composition API
```typescript=
// Importação manual de Hooks
import {
defineComponent,
ref,
Ref,
onMounted,
defineAsyncComponent,
computed,
} from 'vue';
export default defineComponent({
name: 'QuizCreate',
components: {
// Importação assíncrona de componentes
// permite um carregamente inicial mais rápido
ObjectiveBody: defineAsyncComponent(() => {
return import(
/* webpackPrefrech: true */ '../objectives/ObjectiveBody.vue'
);
}),
},
props: {
id: {
type: String,
},
},
setup(props) {
// Instâncias de variáveis
// com a utilização de TS e refs
const noDialog = ref(false);
const questionList = ref<Objective[]>([]);
const historyList = ref<ObjectiveHistoryWhere[]>([]);
// Lifecycle Hooks
onMounted(async () => {
// Uma variável ref dentro do composition api expõe seu
// conteúdo através do value
loading.value = true;
await fetchQuestions();
loading.value = false;
});
const fetchQuestions = async (): Promise<Question[]> => {
try {
const { data } = await Objectives.fetch({
where: {
qid: {
$in: quiz.value?.questions,
},
},
});
if (data?._items) {
questionList.value = response.data._items.sort(sortByQid);
}
} catch (error) {
// Utilização de uma classe própria para
// logs de erros, facilitando o controle de logs próprios
// para ambientes de desenvolvimento e produção
logger.log('error', Messages.QUIZ_FETCH_QUESTIONS_ERROR, error);
await fastExit(Messages.QUIZ_FETCH_QUESTIONS_ERROR);
}
};
return {
refClock,
setObjectiveAnswer,
saveHistoryDialog,
};
},
});
```
### 5. Classe de Log de Erros
Criar uma classe própria de log de erros aumenta o controle e rastreabilidade de falhas no código, além de possibilitar a integração, de forma mais prática de soluções externas, como o Sentry.
Exemplo de classe de log utilizando Sendgrid.
```typescript=
import * as Sentry from '@sentry/browser';
const LOGGER = process.env.LOGGER === 'true';
if (LOGGER) {
console.log('%c Logging error is enabled.', 'color: orange');
}
export type Level = 'error' | 'warn' | 'info' | 'debug' | 'trace';
export class Logger {
private module: string;
constructor(module: string) {
this.module = module;
}
public log(logLevel: Level, message: string, ...args: unknown[]): void {
const logColor = {
error: 'red',
warn: 'yellow',
info: 'green',
debug: 'cyan',
trace: 'gray',
};
if (LOGGER) {
console.log(
`%c[${this.module}] %c${message}`,
'color: orange',
`color: ${logColor[logLevel]}`
);
console[logLevel](`%c [${this.module}]`, 'color: orange', ...args);
}
Sentry.captureMessage(message);
}
public error(message: string, ...args: unknown[]): void {
this.log('error', message, ...args);
}
public warn(message: string, ...args: unknown[]): void {
this.log('warn', message, ...args);
}
public info(message: string, ...args: unknown[]): void {
this.log('info', message, ...args);
}
public debug(message: string, ...args: unknown[]): void {
this.log('debug', message, ...args);
}
public trace(message: string, ...args: unknown[]): void {
this.log('trace', message, ...args);
}
}
```
Além da classe, é importante alterar as configurações do Lint para alertar a respeito de toda instância do console nativo em arquivos que não sejam o da própria classe.
### 7. Variáveis de Ambiente
"Como definir Variáveis de ambiente" é sempre uma questão importante, pois este é o elo de configuração entre o host e a aplicação. É muito importante também manter tais dados fora do versionamento do projeto, por isso é fortemente recomendado a utilização de exemplos de arquivos .env (quando utilizados) com dados fake.
### 8. Pinia vs Vuex
Pinia é um gerenciador de máquina de estados feito para o VueJs, mais especificamente para a versão 3.
Criando a Store com Pinia
```typescript=
// stores/todo.js
import { defineStore } from 'pinia'
export const useTodoStore = defineStore({
id: 'todo',
state: () => ({ count: 0, title: "Cook noodles", done:false })
})
```
Usando Pinia em um componente
```typescript=
export default defineComponent({
setup() {
const todo = useTodoStore()
return {
// gives access only to specific state
state: computed(() => todo.title),
}
},
})
```