PROF. Alan Ferreira dos Santos

o nuxt.js

Framework vue

 

Nuxt.js é um framework de código aberto para aplicações web e é baseado em Vue.js, Node.js, Webpack e Babel.

Foi inspirado no Next.js, que tem estrutura e propósito semelhante, baseado em React.js.

vue

 

Vue.js é um framework JavaScript criado por um desenvolvedor da Google, que trabalhava no time de desenvolvimento do Angular;

Tem um curva de aprendizado pequena: HTML, CSS e JavaScript;

É versátil, pois você pode trabalhar com o simples e adicionar outras bibliotecas próprias ou de terceiros;

Por fim, é performático, rápido e leve (20k).

webpack

Empacotador

 

webpack é um empacotador de módulo JavaScript de código aberto.

O webpack pega módulos com dependências e gera conteúdos estáticos representando esses módulos.

Também permite dividir seu código em múltiplos módulos para serem lidos sob demanda.

babel

Transpilador js

 

O Babel é um transcompilador JavaScript de código aberto, usado principalmente para converter o código ECMAScript 2015+ em uma versão compatível com versões anteriores do JavaScript que pode ser executada por mecanismos JavaScript mais antigos.

criando o app

# usando yarn
yarn create nuxt-app notes-app

# usando npm
npm init nuxt-app notes-app
{
  "name": "notes-app",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    // inicia servidor de desenvolvimento
    "dev": "nuxt",
    // constroi a aplicação usando webpack
    "build": "nuxt build",
    // inicia o servidor de produção
    "start": "nuxt start",
    // gera um arquivo HTML para cada rota da aplicação
    "generate": "nuxt generate"
  },
}

package.json

export default {
  head: {
    title: 'notes-app',
    htmlAttrs: {
      lang: 'pt-br'
    },
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: '' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },
}

nuxt.config.js

páginas

diretório pages

 

O diretório pages contém a camada de visualização da aplicação. O Nuxt.js lê todos os arquivos .vue dentro desse diretório e automaticamente cria uma rota para ele dentro do servidor.

<template>
  <div class="container">
    <div class="row">
      <div class="col-md-3">
        <div class="card bg-warning my-3">
          <div class="card-body">
            <h5 class="card-title">Título da Nota</h5>
            <p class="card-text">
              Descrição da nota
            </p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

pages/index.vue

<style>
.card {
  min-height: 15rem;
}
</style>

pages/index.vue

listar notas

<script>
export default {
  data() {
    return {
      notas: [
        {
          id: 1,
          titulo: "Título da nota",
          descricao: "Descrição da nota"
        },
        {
          id: 1,
          titulo: "Título da nota 2",
          descricao: null
        }
      ]
    };
  }
};
</script>

pages/index.vue

<template>
  <div class="container">
    <div class="row">
      <div v-for="nota of notas" :key="nota.id" class="col-md-3">
        <div class="card bg-warning my-3">
          <div class="card-body">
            <h5 class="card-title">{{ nota.titulo }}</h5>
            <p v-if="nota.descricao" class="card-text">
              {{ nota.descricao }}
            </p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

pages/index.vue

buscando na api

hooks

 

O termo hooking descreve uma série de técnicas utilizadas para modificar ou melhorar o comportamento de um sistema operacional, aplicações ou outros componentes de software através da interceptação de chamadas de funções, mensagens ou eventos passados entre componentes de software.

fetch hook

 

Para carregar dados assíncronos, como dados vindos de uma API, utilizaremos o hook chamado fetch, que acontece após a criação da instância do componente.

Esse hook nos devolverá uma promise, por isso utilizaremos async/await.

export default {
  //...
  axios: {
    baseURL: "https://localhost:4443"
  },
};

nuxt.config.js

yarn add @nuxtjs/axios

//ou

npm install @nuxtjs/axios
<script>
export default {
  data() {
    return {
      notas: []
    };
  },
  async fetch() {
    this.notas = await this.$axios
      .get("nota/usuario/3")
      .then(res => res.json());
  }
};
</script>

pages/index.vue

$fetchState

 

O hook fetch também nos permite acessar algumas propriedades durante e após o processamento dos dados, como o estado do processo que está sendo realizado.

<template>
  <div class="container">
    <p v-if="$fetchState.pending">Carregando...</p>
    <p v-else-if="$fetchState.error">Ocorreu um erro :(</p>
    <div class="row">
      <div v-for="nota of notas" :key="nota.id" class="col-md-3">
        <div class="card bg-warning my-3">
          <div class="card-body">
            <h5 class="card-title">{{ nota.titulo }}</h5>
            <p v-if="nota.descricao" class="card-text">
              {{ nota.descricao }}
            </p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

pages/index.vue

autenticação

módulo auth

 

O Nuxt possui um módulo próprio que facilita a realização requisições de autenticação.

Com ele podemos configurar autenticações próprias ou ainda autenticações provenientes de serviços como Google, GitHub, entre outros.

Para isso basta instalar a biblioteca e configurar o projeto.

export default {
  //...
  auth: {
    strategies: {
      local: {
        endpoints: {
          login: { url: "login", method: "post" },
          user: { url: "usuario", method: "get", propertyName: false },
          logout: false
        }
      }
    }
  }
};

nuxt.config.js

yarn add @nuxtjs/auth

//ou

npm install @nuxtjs/auth
<script>
export default {
  data() {
    return {
      email: null,
      senha: null
    };
  },
  methods: {
    async login() {
      try {
        const response = await this.$auth.loginWith("local", {
          data: {
            email: this.email,
            senha: this.senha
          }
        });

        this.$router.push("/");
      } catch (e) {
        this.error = e.response.data.message;
      }
    }
  }
};
</script>

pages/login/index.vue

<div class="container-fluid">
    <div class="row">
      <div class="col-md-7 d-flex vh-100 justify-content-center">
        <div class="col-md-5 align-self-center">
          <h1 class="text-center mb-5">Notes App</h1>
          <p class="text-center">Informe os dados abaixo para acessar</p>

          <b-form @submit.prevent="login">
            <b-form-group>
              <b-form-input
                v-model="email"
                type="email"
                placeholder="E-mail"
                required
              ></b-form-input>
            </b-form-group>

            <b-form-group>
              <b-form-input
                v-model="senha"
                type="password"
                placeholder="Senha"
                required
              ></b-form-input>
            </b-form-group>

            <b-button block type="submit" variant="primary">Acessar</b-button>
          </b-form>
        </div>
      </div>
      <div class="col-md-5 vh-100 cover"></div>
    </div>
  </div>

pages/login/index.vue

<style>
.cover {
  background: url("https://url.gratis/wftZc") center center;
}
</style>

pages/login/index.vue

const jwt = require('jsonwebtoken');
const { secret } = require('../config/security');

module.exports = (req, res, next) => {
  const [type, token] = req.headers['authorization'].split(' ');
  //...
};

refatorando a API

router.get('/', async (req, res) => {
  const [type, token] = req.headers['authorization'].split(' ');

  const { id } = jwt.decode(token);
  //...
});

auth.js

routes/usuario.js

app.use(
  cors({
    origin: ['http://localhost:3000'],
  })
);

refatorando a API

app.js

em resumo

 

A biblioteca nuxtjs/auth se encarrega de obter os tokens de autenticação, assim como a interceptação das requisições enviando o token no cabeçalho.

LOGOUT

 

Assim como temos um método para login, a biblioteca nuxtjs/auth também fornece um método para realizar o logout do usuário.

Quando executado, o método remove do localStorage o token obtido no processo de autenticação.

export default {
  //...
  auth: {
    strategies: {
      local: {
        endpoints: {
          login: { url: "login", method: "post" },
          user: { url: "usuario", method: "get", propertyName: false },
          logout: false
        }
      }
    }
  }
};

nuxt.config.js

<template>
  <div>
    <b-navbar type="dark" variant="dark">
      <b-navbar-brand href="#">Notes App</b-navbar-brand>

      <b-navbar-toggle target="navbarNotes"></b-navbar-toggle>

      <b-collapse id="navbarNotes" is-nav>
        <b-navbar-nav class="ml-auto">
          <b-nav-item-dropdown text="User Name" right>
            <b-dropdown-item href="#" @click.prevent="logout"
              >Sair</b-dropdown-item
            >
          </b-nav-item-dropdown>
        </b-navbar-nav>
      </b-collapse>
    </b-navbar>
    
    <div class="container"> </div>
  </div>
</template>

index.vue

<script>
export default {
  methods: {
    async logout() {
      await this.$auth.logout();
      this.$router.push("/login");
    }
  }
};
</script>

index.vue

middlewares

diretório middleware

 

Middlewares permitem que possamos escrever funções customizadas que serão executadas antes da renderização de uma página.

Podem ser configurados globalmente ou direto na página onde será utilizado.

export default function({ store, redirect }) {
  if (store.state.auth.loggedIn) {
    return redirect("/");
  }
}

middleware/auth.js

<script>
export default {
  middleware: "auth",
  //...
};
</script>

index.vue

200+ recursos

para dev front-end

layouts

diretório layout

 

Layout é uma ferramenta ótima para alterar o visual de uma aplicação nuxt. Com ele você pode criar layouts distintos para cada tipo de página dentro de um projeto, como login, registro, início e perfil.

layout padrão

 

Por padrão aplicações vem com um layout padrão criado no diretório layouts. Se um layout não for especificado, a aplicação usará o layout default.vue para renderizar o conteúdo.

componente <nuxt/>

 

Para criar um novo layout, basta criar um arquivo .vue com o nome desejado no diretório layouts, utilizando a tag <nuxt/> onde será incorporado o conteúdo da página em que utilizaremos o layout.

<template>
  <div>
    <b-navbar type="dark" variant="dark">
      <b-navbar-brand href="/">Notes App</b-navbar-brand>
      <b-navbar-toggle target="notesBar"></b-navbar-toggle>

      <b-collapse id="notesBar" is-nav>
        <b-navbar-nav class="ml-auto">
          <b-nav-item-dropdown :text="primeiroNome" right>
            <b-dropdown-item href="perfil">Perfil</b-dropdown-item>
            <b-dropdown-item href="#" @click.prevent="logout"
              >Sair</b-dropdown-item
            >
          </b-nav-item-dropdown>
        </b-navbar-nav>
      </b-collapse>
    </b-navbar>

    <div class="container">
      <Nuxt />
    </div>
  </div>
</template>

layouts/home.vue

<script>
export default {
  computed: {
    usuario() {
      return this.$store.state.auth.user;
    },
    primeiroNome() {
      const [nome] = this.usuario.nome.split(" ");

      return nome;
    }
  },
  methods: {
    async logout() {
      await this.$auth.logout();
      this.$router.push("/login");
    }
  }
};
</script>

layouts/home.vue

<script>
export default {
  layout: "home",
  //...
}
</script>

pages/index.vue

alterando os dados do usuário

<template>
  <div>
    <div class="row">
      <div class="col-12">
        <h1 class="my-5">Meu Perfil</h1>

        <b-card class="p-5">
          <div class="row">
            <div class="col-md-3 text-center">
              
            </div>
            <div class="col-md-9">
              
            </div>
          </div>
        </b-card>
      </div>
    </div>
  </div>
</template>

pages/perfil/index.vue


<div class="row">
  <div class="col-md-3 text-center">
    <b-img
      center
      rounded="circle"
      src="https://picsum.photos/125/125/?image=1"
      alt="Center image" 
    />
    <h5 class="mt-3">Nome do caboclo</h5>
  </div>
  <div class="col-md-9">
    <!-- ... -->
  </div>
</div>

pages/perfil/index.vue


<div class="row">
  <div class="col-md-3 text-center">
    <!-- ... -->
  </div>
  <div class="col-md-9">
    <b-form @submit.prevent="save">
      <b-form-group label-align="right" label-cols="2" label="Nome">
        <b-form-input
          v-model="nome"
          type="text"
          required
        ></b-form-input>
      </b-form-group>

      <!-- ... -->

      <div class="row">
        <div class="col-md-10 offset-md-2">
          <b-button type="submit" variant="primary"
            >Salvar Perfil</b-button
          >
        </div>
      </div>
    </b-form>
  </div>
</div>

pages/perfil/index.vue

<script>
export default {
  layout: "home",
  computed: {
    usuario() {
      return this.$store.state.auth.user;
    }
  },
  methods: {
    async salvar() {
      try {
      	let data = {
          nome: this.nome,
          email: this.email
        };

        if (this.senha) {
          data = { ...data, senha: this.senha };
        }
      
        await this.$axios.put(`usuario`, data);
      } catch (e) {
        console.log(e);
      }
    }
  }
};
</script>

pages/perfil/index.vue

alterando o stado com auth

<script>
export default {
  methods: {
    async salvar() {
      try {
      	let data = {
          nome: this.nome,
          email: this.email
        };

        if (this.senha) {
          data = { ...data, senha: this.senha };
        }
      
        const savedUser = await this.$axios.put(`usuario`, data);
        
        await this.$auth.setUser(savedUser.data);
        
        this.$router.push("/");
      } catch (e) {
        console.log(e);
      }
    }
  }
};
</script>

pages/perfil/index.vue

melhoria de segurança

 

Para melhorar a segurança de nossa API, vamos extinguir o parâmetro id da rota PUT do recurso usuário. Agora iremos recuperar o id do usuário com base no token que está trafegando nas requisições.

refatorando a API

const jwt = require('jsonwebtoken');
const { secret } = require('../config/security');

module.exports = (req, res, next) => {
  if (!req.headers['authorization']) res.status(401).send({ error: 'Token não informado' });

  const [type, token] = req.headers['authorization'].split(' ');

  jwt.verify(token, secret, (error) => {
    if (error) return res.status(401).send({ error });

    req.token = token;

    next();
  });
};

middlewares/auth.js

refatorando a API

router.put('/', async (req, res) => {
  try {
    const { body, token } = req;

    const { id } = jwt.decode(token);

    const usuario = await controller.edit(Usuario, body, id);

    res.send(usuario);
  } catch (error) {
    res.status(500).send({ error });
  }
});

routes/usuario.js

refatorando a API

const bcrypt = require('bcrypt');
const { saltRounds } = require('../config/security');

module.exports = function (sequelize, DataTypes) {
  return sequelize.define(
    'usuario',
    {
      //...
    },
    {
      //...
      hooks: {
        beforeValidate: (usuario) => {
          if(usuario.senha) usuario.senha = bcrypt.hashSync(usuario.senha, saltRounds);
        },
      },
      //...
    }  
  );
};

models/usuario.js

Desafio

 

Construir uma página para registro dos novos usuários utilizando a funcionalidade layouts do nuxt, coletando Nome, E-mail e Senha.

Ao final, salvar no banco de dados os dados do novo usuário utilizando a API e redirecioná-lo para a página de login após ter finalizado o cadastro.

Para salvar, a rota POST do recurso usuários deverá ser reescrita para que possa ser carregada antes da validação de autenticação da API.

LOCALSTORAGE

para persistir o estado da aplicação

VUEX

 

É um padrão de gerenciamento de estado/biblioteca para aplicações Vue.js. Com é possível centralizar o armazenamento de todos os componentes da aplicação aplicando regras que garantem que o estado só pode ser mutado de uma forma previsível.

multiplas views dependem de um único estado

ações de diferentes views mudam um único estado

state

 

Chamada de arvore de estado, é um objeto único que armazena o estado de toda aplicação como uma única fonte da verdade, o que facilita a obtenção de dados instantaneamente e de maneira fácil.

export const state = () => ({
  list: []
});

store/nota.js

mutations

 

A única forma de mudar o estado da aplicação é utilizando uma mutação. São semelhantes a eventos, definidas por um nome e alguns parâmetros, sendo o primeiro referente ao estado, onde realizaremos a modificação e o segundo o valor ou objeto com os dados com as mudanças no estado.

//...

export const mutations = {
  SET(state, notas) {
    state.list = notas;
  }
};

store/nota.js

actions

 

As ações são responsáveis por solicitar mutações. Isso é feito utilizando a função commit, onde o primeiro parâmetro é o nome da mutação e o segundo o objeto ou valor a ser utilizado pela mutação.

Ações também podem realizar operações assíncronas, local ideal para realizar comunicações com o back-end.

//...

export const actions = {
  async list({ commit }, usuarioId) {
    try {
      const { data } = await this.$axios.get(`nota/usuario/${usuarioId}`);
      commit("SET", data);
      return data;
    } catch (e) {
      throw e;
    }
  }
};

store/nota.js

dispatch

 

Uma ação pode ser executada por meio do método dispatch por meio da biblioteca global this.$store.

Ele é responsável por receber como parâmetro o nome do módulo/ação a ser realizada, assim como o valor a ser alterado no estado da aplicação.

<script>
export default {
  layout: "home",
  middleware: "auth",
  async fetch() {
    await this.$store.dispatch("nota/list", this.usuario.id);
  },
  computed: {
    usuario() {
      return this.$store.state.auth.user;
    },
    notas() {
      return this.$store.state.nota.list;
    }
  }
};
</script>

index.vue

sistema de roteamento

rotas automáticas

 

Com base na estrutura adotada na pasta pages o Nuxt automaticamente configura as rotas da aplicação.

Algumas regras são importantes para entender a estrutura que será montada pelo framework.

pages/
--| perfil/
-----| index.vue
-----| historico.vue
--| index.vue

diretório pages

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'perfil',
      path: '/perfil',
      component: 'pages/perfil/index.vue'
    },
    {
      name: 'perfil-historico',
      path: '/perfil/historico',
      component: 'pages/perfil/historico.vue'
    }
  ]
}

estrutura gerada

rota dinâmicas

undescored

 

Chamados de dinâmicas as rotas que podem receber parâmetros. Esses parâmetros são identificados por um undescorded no início do aquivo/diretório. 

pages/
--| nota
-----| index.vue
-----| _id.vue
--| index.vue

diretório pages

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'nota',
      path: '/nota',
      component: 'pages/nota/index.vue'
    },
    {
      name: 'nota',
      path: '/nota/:id?',
      component: 'pages/nota/_id.vue'
    },
  ]
}

estrutura gerada

pages/
--| nota
-----| new.vue
-----| edit/
--------| _id/
-----------| index.vue
--| index.vue

undescored

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'nota-new',
      path: '/nota/new',
      component: 'pages/nota/new.vue'
    },
    {
      name: 'nota-edit',
      path: '/nota/edit/:id',
      component: 'pages/nota/edit/_id/index.vue'
    },
  ]
}

estrutura gerada

roteamento manual

 

Caso prefira o roteamento tradicional, é possível fazer isso configurando a chave router nas configurações da aplicação.

export default {
  //...
  router: {
    routes: [
      {
        name: 'index',
        path: '/',
        component: 'pages/index.vue'
      },
      {
        name: 'nota-new',
        path: '/nota/new',
        component: 'pages/nota/new.vue'
      },
      {
        name: 'nota-edit',
        path: '/nota/edit/:id',
        component: 'pages/nota/edit.vue'
      },
    ]
  }
}

nuxt.config.js

Componentes

Reutilizáveis

 

Componentes são instâncias reutilizáveis do Vue com um nome. Como toda instância Vue, eles também tem acesso a recursos como data, computed, watch, methods e gatilhos de ciclo de vida.

<template>
  
</template>

<script>
export default {
  name: "meu-componente",
  props: {
  
  }
};
</script>

<style>

</style>

Single file component

props

propriedades

 

Props são atributos personalizáveis que você pode registrar em um componente. Quando um valor é passado para um atributo prop, ele torna-se uma propriedade daquela instância de componente.

<template>
  <div class="card bg-warning my-5">
    
  </div>
</template>

<script>
export default {
  name: "n-nota",
  props: {
    id: {
      type: [String, Number],
      required: true
    }
  },
};
</script>

components/NNota.vue

v-model em compoentes

mão dupla

 

Você pode usar a diretiva v-model para criar interligações de mão dupla (two-way binding) entre os dados e elementos input, textarea e select de formulários. A diretiva automaticamente busca a maneira correta de atualizar o elemento com base no tipo de entrada.

Isso também é possível em componentes, utilizando a propriedade value no componente.

<template>
  <b-input-group class="mb-2">
    <b-input-group-prepend is-text>
      <!-- -->
    </b-input-group-prepend>
    <b-form-input
      class="bg-warning border-0 text-dark"
      :class="{ concluida: value.concluida == 1 }"
      placeholder="Novo Item"
      v-model="value.descricao"
    />
  </b-input-group>
</template>

<script>
export default {
  name: "n-checklist-item",
  props: {
    value: Object
  }
};
</script>

COMPONENTS/NCHECLISTITEM.VUE

<template>
<input
  :value="value"
  @input="$emit('input', $event.target.value)"
/>
</template>

<script>
export default {
  name: "n-checklist-item",
  props: {
    value: Object
  }
};
</script>

Elementos nativos

eventos

emissão de valores

 

Às vezes é útil emitir um valor específico com um evento. Por exemplo, quando nosso usuário pressionar a tecla enter, iremos emitir um evento para confirmar a inclusão do item ao checklist. 

<template>
  <b-input-group class="mb-2">
    <b-input-group-prepend is-text>
      <!-- -->
    </b-input-group-prepend>
    <b-form-input
      class="bg-warning border-0 text-dark"
      :class="{ concluida: value.concluida == 1 }"
      placeholder="Novo Item"
      v-model="value.descricao"
      @keyup.enter.prevent="change"
    />
  </b-input-group>
</template>

<script>
export default {
  name: "n-checklist-item",
  props: {
    value: Object
  },
  methods: {
    async change() {
      this.$emit("changeItem", this.value);
    }
  }
};
</script>

COMPONENTS/NCHECLISTITEM.VUE

<template>
  <div>
    <ul class="list-unstyled">
      <li v-for="(item, index) of value" :key="index">
        <n-checklist-item v-model="value[index]"></n-checklist-item>
      </li>
      <li>
        <n-checklist-item
          v-model="checklist"
          @changeItem="adicionar()"
        ></n-checklist-item>
      </li>
    </ul>
  </div>
</template>

components/nchecklist.vue

<script>
export default {
  name: "n-checklist",
  props: {
    value: Array
  },
  data() {
    return {
      checklist: {
        descricao: null,
        concluida: 0
      }
    };
  },
  methods: {
    adicionar() {
      this.value.push(this.checklist);
      
      this.checklist = {
        descricao: null,
        concluida: 0
      };
    }
  }
};
</script>

components/nchecklist.vue

computed

dados computados

 

Expressões dentro de templates são muito convenientes, mas são destinadas a operações simples. Colocar muita lógica neles pode fazer com que fiquem inchados e que a sua manutenção fique mais complicada.

Para realizar lógicas complexas utilizamos dados computados, que se parecem muito com propriedades definidas no data.

<script>
export default {
  name: "n-nota",
  data() {
    return {
      nota: {
        id: null,
        titulo: null,
        descricao: null,
        criadoEm: null,
        atualizadoEm: null,
        checklists: [],
        tags: []
      }
    };
  },
  computed: {
    numeroItensChecklist() {
      return this.nota.checklists.length;
    }
  },
};
</script>

components/nNOTA.vue

watch

oBSERVADORES

 

Enquanto dados computados são mais adequados na maioria dos casos, há momentos em que um observador personalizado é necessário. Por isso o Vue fornece uma maneira mais genérica para reagir a alterações de dados, o watch. Isto é particularmente útil quando se precisa executar operações assíncronas ou operações complexas antes de responder a uma alteração de dados.

<script>
export default {
  data() {
    return {
      awaitingChange: false,
      nota: { }
    }
  },
  watch: {
    nota: {
      handler(value, oldValue) {
        if (!this.awaitingChange) {
          setTimeout(() => {
            adicionar()
            this.awaitingChange = false;
          }, 1000);
        }
        this.awaitingChange = true;
      },
      deep: true
    },
  }
};
</script>

COMPONENTS/NNOTA.VUE

Desafio

 

Construir um componente personalizado onde seja possível categorizar notas utilizando tags.

 

O componente deverá estar interligado com a propriedade tags do componente NNota.vue, e o controle dessas tags deverá estar dentro do componente construído por você.

Configurando nossa PWA

Progressive Web Apps

 

PWA é uma metodologia de desenvolvimento considerada uma evolução híbrida entre aplicações web tradicionais e aplicativos móveis, combinando recursos modernos disponíveis nos navegadores e as vantagens de utilizar o celular.

módulo nuxt/pwa

 

O Nuxt possui um módulo para configurar uma PWA do zero. Com ela é possível:

 

  • Registrar Services Workers;
  • Gerar automaticamento o arquivo manifest.json;
  • Adiciona automaticamente os metadados amigáveis do SEO com a integração manifesto;
  • Gera automaticamente ícones de aplicativos com diferentes tamanhos;
  • Permite utilizar notificações push com o OneSignal.
yarn add --dev @nuxtjs/pwa

Instalação

{
  buildModules: [
    '@nuxtjs/pwa',
  ]
}

nuxt.config.js

sw.*

.gitignore

Módulo de ícone

{
  pwa: {
    icon: {
      source: "static/icon.png"
    },
  }
}

nuxt.config.js

módulo meta

{
  pwa: {
    //...
    meta: {
      theme_color: "#fff"
    },
  }
}

nuxt.config.js

módulo manifest

{
  pwa: {
    //...
    manifest: {
      // Presente em telas de diálogo de instalação, WebStores
      name: 'Notes App TADS',
      // Tela iniciais do dispositivo, Novas abas 
      short_name: "Notes App"
      // Recomendado pela Google
      description: 'Aplicativo de Notas da Aula de Programação Web'
      lang: 'pt-br',
      // Equivalente a meta tag theme-color 
      theme_color: "#fff"
    }
  }
}

nuxt.config.js

módulo workbox

Bibliotecas JS para PWA

 

É uma coleção de bibliotecas Java Script para PWA's. Esse módulo adiciona suporte completo para aplicações offline.

 

Permite configurar por exemplo como sua aplicação irá realizar o cache das informações, rotas, etc.

 

O Nuxt já realiza algumas configurações padrões que podem ser sobrescritas.

helpers

ajudantes

 

Helpers são ajudantes presentes no Nuxt. O Ajudante $nuxt está presente no Nuxt para melhorar a experiência dos usuários.

<template>
  <div>
    <!-- 
    ...
    -->

    <div class="container">
      <b-alert class="mt-5" variant="dark" show v-if="$nuxt.isOffline"
        >Você está offline</b-alert
      >

      <Nuxt />
    </div>
  </div>
</template>

layouts/home.vue

// Verifica se está desconectado
this.$nuxt.isOffline
window.$nuxt.isOffline

// Verifica se está conectado
this.$nuxt.isOnline
window.$nuxt.isOnline

módulo One Signal

envio de mensagens

 

O OneSignal é uma ferramenta para envio de mensagens de diversos tipos, como Mobile Push e Web Push. 

 

Configurar o módulo OneSignal para PWAs Nuxt, permite integração rápida com essa ferramenta.

yarn add @nuxtjs/onesignal

instalação

{
  //...
  modules: [
    //...
    '@nuxtjs/onesignal'
  ]
}

NUXT.CONFIG.JS

{
  //...
  oneSignal: {
    init: {
      appId: 'YOUR_APP_ID',
      allowLocalhostAsSecureOrigin: true,
      cdn: true,
      welcomeNotification: {
        disable: true
      }
    }
  }
}

NUXT.CONFIG.JS

meta tags e seo

seo para SPA's

 

O Nuxt oferece recursos que facilitam a otimização de páginas para motores de busca. As tags meta podem ser definidas de duas formas.

global

 

‎Muito útil para adicionar um título padrão e tag de descrição para fins de SEO ou para definir o viewport ou adicionar o favicon‎.

{
  head: {
    title: "Notes App",
    htmlAttrs: {
      lang: "pt-br"
    },
    bodyAttrs: {
      class: "bg-light"
    },
    meta: [
      { charset: "utf-8" },
      { name: "viewport", content: "width=device-width, initial-scale=1" },
      {
        hid: "description",
        name: "description",
        content: "Sem lojas e instalações. Crie suas anotações mesmo offline"
      }
    ],
    link: [{ rel: "icon", type: "image/png", href: "/icon.png" }]
  },
}

nuxt.config.js

local

 

Também é possível adicionar títulos e meta para cada página usando o método dentro de sua tag de script em cada página‎ utilizando o head.

<script>
export default {
  head: {
    title: 'Login',
    meta: [
      {
        hid: 'description',
        name: 'description',
        content: 'Acesse o Notes App'
      }
    ],
  }
}
</script>

pages/login.vue

hora do código

 

Configurar o head para todas as páginas do projeto:

 

  • Registro - Notes App;
  • Home - Notes App;
  • Perfil - Notes App;
  • Nova Nota - Notes App;
  • Alterar Nota - Notes App.

desafio 07/06

 

Implementar as seguintes adequações na edição de Notas para quando a aplicação estiver offline

  • Carregar o conteúdo da Nota do estado ao carregar o modo de edição;
  • Na ação de atualização, utilizar somente o estado para guardar as alterações.

transições

transições

 

Transições permitem aplicar regras de css como animações e transições.

classes de transição

 

v-enter: Inicia o estado de entrada. Aplicada antes do elemento ser inserido, removida depois de um frame.

v-enter-active: Ativa e termina o estado de entrada. Aplicada antes do elemento ser inserido, removida quando a transição/animação termina.

v-leave-active: Estado ativo de saída. Aplicada durante toda a fase de saída. Adicionada imediatamente quando a transição de saída é disparada, removida quando a transição/animação termina. Esta classe pode ser usada para definir a duração, atraso e a curva da transição de saída.

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-active {
  opacity: 0;
}

assets/css/main.css

<script>
export default { 
  transition: "fade"
}
</script>

pages/login.vue

{
  //...
  pageTransition: "fade", 
}

nuxt.config.js

Nuxt.js

By Alan Ferreira dos Santos