# 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), } }, }) ```