17. Exemplo [nuxt-20]: Portar o exemplo [vuejs-22]
17.1. Introdução
Aqui, propomos portar o exemplo [vuejs-22], que era uma aplicação do tipo SPA [vue.js], para um contexto SSR [nuxt]. [vuejs-22] era uma aplicação cliente para o servidor de cálculo de impostos que apresentava as seguintes vistas:
A primeira vista é a vista de autenticação:

A segunda vista é a vista de cálculo de impostos:

A terceira vista apresenta a lista de simulações realizadas pelo utilizador:

O ecrã acima mostra que a simulação n.º 1 pode ser eliminada. Isto resulta na seguinte vista:

Se agora eliminarmos a última simulação, obtemos a seguinte nova visualização:

Iremos migrar gradualmente a aplicação [vuejs-22] para a aplicação [nuxt-20]. Não iremos explicar novamente o código da [vuejs-22]. Recomendamos aos leitores que consultem o documento |Introdução ao Framework VUE.JS através de exemplos|. Os vários passos devem destacar as diferenças entre uma aplicação [vuejs] e uma aplicação [nuxt].
17.2. Passo 1
O projeto [nuxt-20] é inicialmente criado através da clonagem do projeto [nuxt-12]. Este é, de facto, um bom ponto de partida:
- consegue comunicar com o servidor de cálculo de impostos;
- lida corretamente com os erros que o servidor envia;
- o cliente e o servidor [Nuxt] podem comunicar através de uma sessão [Nuxt];
Temos, portanto, uma infraestrutura inicial sólida. A nossa principal tarefa deve ser modificar:
- as páginas. Vamos utilizar as do projeto [vuejs-22], que terão de ser adaptadas ao novo ambiente;
- gestão do armazenamento. Devem aparecer informações adicionais (lista de simulações), e outras informações podem tornar-se desnecessárias;
- gestão de encaminhamento [nuxt] no cliente e no servidor;
Então, primeiro, criamos o projeto [nuxt-20] clonando o projeto [nuxt-12]:

Em seguida, removemos as páginas e os componentes que já não são necessários [2]:
- o componente [components/navigation] desaparece;
- o layout [layout/default] desaparece;
- as páginas [index, authentication, get-admindata, end-session] são removidas;
Em seguida, integramos elementos do [vuejs-22] no [nuxt-20] [3]:
- as três páginas [Authentication, TaxCalculation, SimulationList] da aplicação [vuejs-22] vão para a pasta [pages];
- os componentes [FormCalculImpot, Menu, Layout] da aplicação [vuejs-22] vão para a pasta [components];
- a página [Main] do [vuejs-22], que serviu como [layout] para a aplicação [vuejs-22], vai para a pasta [layouts];
Renomeamos os elementos integrados [4]:

- em [layouts], [Main] passou a ser [default], uma vez que esse é o nome padrão para o layout de uma aplicação [nuxt];
- em [pages], a página [Authentication] passou a ser [index], uma vez que [Authentication] desempenhava esta função na aplicação [vuejs-22];
Nesta altura, podemos compilar o projeto para ver os primeiros erros. Modificamos o ficheiro [nuxt.config] do exemplo [nuxt-12] para agora executar [nuxt-20]:
export default {
mode: 'universal',
/*
** Headers of the page
*/
head: {
title: 'Introduction à [nuxt.js]',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{
hid: 'description',
name: 'description',
content: 'ssr routing loading asyncdata middleware plugins store'
}
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
},
/*
** Customize the progress-bar color
*/
loading: false,
/*
** Global CSS
*/
css: [],
/*
** Plugins to load before mounting the App
*/
plugins: [
{ src: '@/plugins/client/plgSession', mode: 'client' },
{ src: '@/plugins/server/plgSession', mode: 'server' },
{ src: '@/plugins/client/plgDao', mode: 'client' },
{ src: '@/plugins/server/plgDao', mode: 'server' },
{ src: '@/plugins/client/plgEventBus', mode: 'client' }
],
/*
** Nuxt.js dev-modules
*/
buildModules: [
// Doc: https://github.com/nuxt-community/eslint-module
'@nuxtjs/eslint-module'
],
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://bootstrap-vue.js.org
'bootstrap-vue/nuxt',
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
// https://www.npmjs.com/package/cookie-universal-nuxt
'cookie-universal-nuxt'
],
/*
** Axios module configuration
** See https://axios.nuxtjs.org/options
*/
axios: {},
/*
** Build configuration
*/
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {}
},
// source code directory
srcDir: 'nuxt-20',
// router
router: {
// application URL root
base: '/nuxt-20/',
// routing middleware
middleware: ['routing']
},
// server
server: {
// service port, default 3000
port: 81,
// network addresses listened to, default localhost: 127.0.0.1
// 0.0.0.0 = all the machine's network addresses
host: 'localhost'
},
// environment
env: {
// axios configuration
timeout: 2000,
withCredentials: true,
baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
// session cookie configuration [nuxt]
maxAge: 60 * 5
}
}
Em seguida, [compilamos] o projeto:

São apresentados os seguintes erros:
- O erro na linha 1 indica que está a ser feita referência a uma imagem inexistente. Iremos recuperá-la em [vuejs-22];
- O erro na linha 2 mostra que o componente [./FormCalculImpot] não existe. De facto, este componente encontra-se agora em [@/components/form-calcul-impot];
- Os erros nas linhas [3-5] mostram que o componente [./Layout] não existe. Na verdade, este componente encontra-se agora em [@/components/layout];
- os erros nas linhas [6-7] indicam que o componente [./Menu] não existe. De facto, agora chama-se [@/components/menu];
Adicionamos a imagem [assets/logo.jpg] ao projeto [nuxt-20]:

Além disso, vamos corrigir os caminhos dos componentes em todas as páginas. Vamos tomar a página [calcul-impot] como exemplo:
<!-- definition HTML of the view -->
<template>
<div>
<Layout :left="true" :right="true">
<!-- tax calculation form on the right -->
<FormCalculImpot slot="right" @resultatObtenu="handleResultatObtenu" />
<!-- left-hand navigation menu -->
<Menu slot="left" :options="options" />
</Layout>
<!-- display area for tax calculation results under the form -->
<b-row v-if="résultatObtenu" class="mt-3">
<!-- empty three-column zone -->
<b-col sm="3" />
<!-- nine-column zone -->
<b-col sm="9">
<b-alert show variant="success">
<span v-html="résultat"></span>
</b-alert>
</b-col>
</b-row>
</div>
</template>
<script>
// imports
import FormCalculImpot from './FormCalculImpot'
import Menu from './Menu'
import Layout from './Layout'
export default {
// composants utilisés
components: {
Layout,
FormCalculImpot,
Menu
},
As três instruções [import] nas linhas 26–28 passam a ser:
// imports
import FormCalculImpot from '@/components/form-calcul-impot'
import Menu from '@/components/menu'
import Layout from '@/components/layout'
Verifique e, se necessário, corrija as instruções [import] para todos os componentes, layouts e páginas. Depois de efetuar estas correções, pode tentar uma nova [compilação]. Normalmente, não deverá haver mais erros.
Pode então tentar executar a aplicação:

Aparecem erros:
Podemos ver que o comando [dev], combinado com o módulo [eslint], é mais rigoroso, do ponto de vista sintático, do que o comando [build]. Aqui, exige que o operador de comparação [!=] seja escrito como [!==], que é um operador mais rigoroso (também verifica o tipo dos operandos). Estes erros ocorrem na página [index.vue].
Corrigimos os erros acima e executamos novamente o projeto. Em seguida, recebemos um aviso do módulo [eslint]:

Corrigimos este erro utilizando a [Correção rápida] do módulo [eslint] [2].
Reiniciamos o projeto. Não há mais erros de compilação. Em seguida, acedemos à URL [http://localhost:81/nuxt-20/] num navegador. Recebemos um erro de tempo de execução:

O erro está em [index.vue] [2]. O erro [1] deve-se ao facto de, no [vuejs-22], a camada [dao] estar disponível em [this.$dao], enquanto no [nuxt-12], cuja infraestrutura adotámos, está disponível na função [this.$dao()].
O erro está na função [created] do ciclo de vida da página [index]:

Por enquanto, vamos simplesmente renomear [created] para [created2] para que a função do ciclo de vida [created] não seja executada [3].
Guardamos a alteração e recarregamos a página [index] no navegador. Desta vez, funciona:

17.3. Passo 2
As páginas do projeto [vuejs-22] utilizavam os seguintes elementos injetados:
- $dao: para a camada [dao] do cliente [vue.js];
- $session: para uma sessão armazenada no [localStorage] do navegador;
Estes elementos já não existem na infraestrutura do projeto [nuxt-12] que copiámos:
- existem agora duas camadas [dao], uma para o cliente [nuxt] e outra para o servidor [nuxt]. Ambas estão disponíveis através de uma função injetada chamada [$dao]. Isto significa que, nas páginas da aplicação, [this.$dao] deve ser substituído por [this.$dao()];
- a sessão [nuxt] gerida pela aplicação [nuxt-20] já não tem nada a ver com o objeto [$session] da aplicação [vuejs-22], onde não existia o conceito de cookies de sessão. No entanto, servem um propósito semelhante: armazenar informações persistentes à medida que o utilizador interage com a aplicação. A sessão [nuxt] armazena informações no store em vez de diretamente na sessão. Nas páginas da aplicação, [this.$session] deve ser substituído por [this.$store] ao armazenar informações na sessão, e por [this.$session()] ao manipular a própria sessão;
- para verificar o estado de uma propriedade P no store, deve escrever [this.$store.state.P];
- Para alterar a propriedade P do store, deve escrever [this.$store.commit('replace', {P:value}]
Fazemos estas alterações na página [index]:
<!-- définition HTML de la vue -->
<template>
<Layout :left="false" :right="true">
<template slot="right">
<!-- formulaire HTML - on poste ses valeurs avec l'action [authentifier-utilisateur] -->
<b-form @submit.prevent="login">
<!-- titre -->
<b-alert show variant="primary">
<h4>Bienvenue. Veuillez vous authentifier pour vous connecter</h4>
</b-alert>
<!-- 1ère ligne -->
<b-form-group label="Nom d'utilisateur" label-for="user" label-cols="3">
<!-- zone de saisie user -->
<b-col cols="6">
<b-form-input id="user" v-model="user" type="text" placeholder="Nom d'utilisateur" />
</b-col>
</b-form-group>
<!-- 2ième ligne -->
<b-form-group label="Mot de passe" label-for="password" label-cols="3">
<!-- zone de saisie password -->
<b-col cols="6">
<b-input id="password" v-model="password" type="password" placeholder="Mot de passe" />
</b-col>
</b-form-group>
<!-- 3ième ligne -->
<b-alert v-if="showError" show variant="danger" class="mt-3">L'erreur suivante s'est produite : {{ message }}</b-alert>
<!-- bouton de type [submit] sur une 3ième ligne -->
<b-row>
<b-col cols="2">
<b-button :disabled="!valid" variant="primary" type="submit">Valider</b-button>
</b-col>
</b-row>
</b-form>
</template>
</Layout>
</template>
<!-- dynamique de la vue -->
<script>
/* eslint-disable no-console */
import Layout from '@/components/layout'
export default {
// components used
components: {
Layout
},
// component status
data() {
return {
// user
user: '',
// password
password: '',
// controls the display of an error msg
showError: false,
// the error message
message: ''
}
},
// calculated properties
computed: {
// valid entries
valid() {
return this.user && this.password && this.$store.state.started
}
},
// life cycle: the component has just been created
mounted() {
// eslint-disable-next-line
console.log("Authentification mounted");
// can the user run simulations?
if (this.$store.state.started && this.$store.state.authenticated && this.$métier.taxAdminData) {
// then the user can run simulations
this.$router.push({ name: 'calculImpot' })
// return to event loop
return
}
// if the jSON session has already been started, it is not restarted again
if (!this.$store.state.started) {
// start waiting
this.$emit('loading', true)
// initialize the session with the server - asynchronous request
// we use the promise rendered by the [dao] layer methods
this.$dao()
// initialize a jSON session
.initSession()
// we got the answer
.then((response) => {
// end waiting
this.$emit('loading', false)
// response analysis
if (response.état !== 700) {
// error is displayed
this.message = response.réponse
this.showError = true
// return to event loop
return
}
// the session has started
this.$store.commit('replace', { started: true })
console.log('[authentification], session=', this.$session())
})
// in case of error
.catch((error) => {
// the error is traced back to the [Main] view
this.$emit('error', error)
})
// in all cases
.finally(() => {
// save the session
this.$session().save()
})
}
},
// event managers
methods: {
// ----------- authentication
async login() {
try {
// start waiting
this.$emit('loading', true)
// you are not yet authenticated
this.$store.commit('replace', { authenticated: false })
// blocking server authentication
const response = await this.$dao().authentifierUtilisateur(this.user, this.password)
// end of loading
this.$emit('loading', false)
// server response analysis
if (response.état !== 200) {
// error is displayed
this.message = response.réponse
this.showError = true
// return to event loop
return
}
// no error
this.showError = false
// you are authenticated
this.$store.commit('replace', { authenticated: true })
// --------- we now request data from the tax authorities
// initially, no data
this.$métier.setTaxAdminData(null)
// start waiting
this.$emit('loading', true)
// blocking request to the server
const response2 = await this.$dao().getAdminData()
// end of loading
this.$emit('loading', false)
// response analysis
if (response2.état !== 1000) {
// error is displayed
this.message = response2.réponse
this.showError = true
// return to event loop
return
}
// no error
this.showError = false
// the received data is stored in the [business] layer
this.$métier.setTaxAdminData(response2.réponse)
// we can move on to tax calculation
this.$router.push({ name: 'calculImpot' })
} catch (error) {
// the error is traced back to the main component
this.$emit('error', error)
} finally {
// maj store
this.$store.commit('replace', { métier: this.$métier })
// save the session
this.$session().save()
}
}
}
}
</script>
Tenha em atenção os seguintes pontos:
- linha 69: a função [created2] foi renomeada para [mounted] para que o servidor [nuxt] não a execute (ele não executa nem [beforeMount] nem [mounted]). Apenas o cliente [nuxt] irá executá-la, tal como aconteceu no exemplo [vuejs-22];
- linha 73: fazemos referência a [this.$business], que atualmente não existe;
- linha 75: nunca utilizámos este método numa aplicação [nuxt]. Teremos de verificar se funciona num contexto [nuxt];
- linhas 112, 172: no [vuejs-22], a sessão do projeto foi guardada desta forma. Com o projeto [nuxt-20], o método [save] deve receber o contexto atual. Sabemos que numa página [nuxt], o objeto [context] está disponível em [this.$nuxt.context];
As linhas 112 e 172 são, portanto, reescritas da seguinte forma:
this.$session().save(this.$nuxt.context)
Note que este código não está otimizado. Em vez de utilizar a função [this.$session()] várias vezes, seria melhor escrever:
const session=this.$session()
e, em seguida, utilizar a variável [session]. O mesmo raciocínio aplica-se à função [this.$dao()].
Com estas correções feitas, podemos recarregar a URL [http://localhost:81/nuxt-20/] num navegador. Continuamos a obter a mesma página de antes:

Vamos dar uma olhada nos registos do navegador:

O registo [1] é o último registo gerado pelo cliente [nuxt]. Em [2], vemos que a propriedade [started] está definida como [true], o que significa que a função [mounted] iniciou com sucesso uma sessão JSON com o servidor de cálculo de impostos. Vemos também que o store tem propriedades que terão de ser descartadas ou renomeadas. Lembre-se de que estamos a utilizar o store do exemplo [nuxt-12].
Agora, vamos solicitar a URL [http://localhost:81/nuxt-20/] novamente enquanto o servidor de cálculo de impostos não está em execução. Primeiro, certificamo-nos de eliminar o cookie de sessão [nuxt]:

A captura de ecrã acima é do Chrome. Depois de fazer isto, a URL [http://localhost:81/nuxt-20/] devolve o seguinte resultado:

O erro foi tratado corretamente pelo projeto [vuejs-22]. Continua a ser tratado corretamente pelo projeto [nuxt-20].
17.4. Passo 3
Agora que temos a página de autenticação, precisamos de analisar o código que é executado quando o utilizador clica no botão [Validate]:
// event managers
methods: {
// ----------- authentication
async login() {
try {
// start waiting
this.$emit('loading', true)
// you are not yet authenticated
this.$store.commit('replace', { authenticated: false })
// blocking server authentication
const response = await this.$dao().authentifierUtilisateur(this.user, this.password)
// end of loading
this.$emit('loading', false)
// server response analysis
if (response.état !== 200) {
// error is displayed
this.message = response.réponse
this.showError = true
// return to event loop
return
}
// no error
this.showError = false
// you are authenticated
this.$store.commit('replace', { authenticated: true })
// --------- we now request data from the tax authorities
// initially, no data
this.$métier.setTaxAdminData(null)
// start waiting
this.$emit('loading', true)
// blocking request to the server
const response2 = await this.$dao().getAdminData()
// end of loading
this.$emit('loading', false)
// response analysis
if (response2.état !== 1000) {
// error is displayed
this.message = response2.réponse
this.showError = true
// return to event loop
return
}
// no error
this.showError = false
// the received data is stored in the [business] layer
this.$métier.setTaxAdminData(response2.réponse)
// we can move on to tax calculation
this.$router.push({ name: 'calculImpot' })
} catch (error) {
// the error is traced back to the main component
this.$emit('error', error)
} finally {
// maj store
this.$store.commit('replace', { métier: this.$métier })
// save the session
this.$session().save(this.$nuxt.context)
}
}
}
O principal problema aqui parece ser a ausência dos dados [this.$métier]. Para resolver isto, vamos:
- incluir a classe [Métier] do exemplo [vuejs-22]. Vamos colocá-la na pasta [api];
- injetar uma função [$métier] no contexto do cliente [nuxt], o que dará acesso a esta classe;
Primeiro, copie a classe [Métier] para a pasta [api]:

Assim que a classe [Métier] estiver no projeto, vamos criar um novo plugin para o cliente [nuxt]. Este plugin, chamado [pluginMétier], irá injetar uma função [$métier] que fornece acesso à classe [Métier]:
/* eslint-disable no-console */
// on crée un point d'accès à la couche [métier]
import Métier from '@/api/client/Métier'
export default (context, inject) => {
// instanciation de la couche [métier]
const métier = new Métier()
// injection d'une fonction [$métier] dans le contexte
inject('métier', () => métier)
// log
console.log('[fonction client $métier créée]')
}
Agora que isto está feito, podemos atualizar a página [index]:
// life cycle: the component has just been created
mounted() {
// eslint-disable-next-line
console.log("Authentification mounted");
// can the user run simulations?
if (this.$store.state.started && this.$store.state.authenticated && this.$métier().taxAdminData) {
// then the user can run simulations
this.$router.push({ name: 'calcul-impot' })
// return to event loop
return
}
// if the jSON session has already been started, it is not restarted again
...
},
// event managers
methods: {
// ----------- authentication
async login() {
try {
// start waiting
this.$emit('loading', true)
// you are not yet authenticated
this.$store.commit('replace', { authenticated: false })
// blocking server authentication
const response = await this.$dao().authentifierUtilisateur(this.user, this.password)
// end of loading
this.$emit('loading', false)
// server response analysis
if (response.état !== 200) {
// error is displayed
this.message = response.réponse
this.showError = true
// return to event loop
return
}
// no error
this.showError = false
// you are authenticated
this.$store.commit('replace', { authenticated: true })
// --------- we now request data from the tax authorities
// initially, no data
this.$métier().setTaxAdminData(null)
// start waiting
this.$emit('loading', true)
// blocking request to the server
const response2 = await this.$dao().getAdminData()
// end of loading
this.$emit('loading', false)
// response analysis
if (response2.état !== 1000) {
// error is displayed
this.message = response2.réponse
this.showError = true
// return to event loop
return
}
// no error
this.showError = false
// the received data is stored in the [business] layer
this.$métier().setTaxAdminData(response2.réponse)
// we can move on to tax calculation
this.$router.push({ name: 'calcul-impot' })
} catch (error) {
// the error is traced back to the main component
this.$emit('error', error)
} finally {
// maj store
this.$store.commit('replace', { métier: this.$métier() })
// save the session
this.$session().save(this.$nuxt.context)
}
}
}
- linhas 43, 61, 69: [this.$métier] foi substituído por [this.$métier()];
- linhas 8, 63: o nome da página [CalculImpot] no projeto [vuejs-22] passou a ser a página [calcul-impot] no projeto [nuxt-20];
Com estas correções feitas, podemos tentar validar a página de autenticação:

A página resultante é a seguinte:

Chegámos com sucesso à página de cálculo de impostos. Agora, vamos ver os registos:

Em [2], vemos que o estado da autenticação foi corretamente guardado. Em [3-4], vemos que os dados [taxAdminData] foram recuperados, o que permite que o imposto seja calculado pela classe [Métier].
17.5. Passo 4
Vamos examinar a página [calcul-impot] que obtivemos:
<!-- definition HTML of the view -->
<template>
<div>
<Layout :left="true" :right="true">
<!-- tax calculation form on the right -->
<FormCalculImpot slot="right" @resultatObtenu="handleResultatObtenu" />
<!-- left-hand navigation menu -->
<Menu slot="left" :options="options" />
</Layout>
<!-- display area for tax calculation results under the form -->
<b-row v-if="résultatObtenu" class="mt-3">
<!-- empty three-column zone -->
<b-col sm="3" />
<!-- nine-column zone -->
<b-col sm="9">
<b-alert show variant="success">
<span v-html="résultat"></span>
</b-alert>
</b-col>
</b-row>
</div>
</template>
<script>
// imports
import FormCalculImpot from '@/components/form-calcul-impot'
import Menu from '@/components/menu'
import Layout from '@/components/layout'
export default {
// composants utilisés
components: {
Layout,
FormCalculImpot,
Menu
},
// état interne
data() {
return {
// options du menu
options: [
{
text: 'Liste des simulations',
path: '/liste-des-simulations'
},
{
text: 'Fin de session',
path: '/fin-session'
}
],
// résultat du calcul de l'impôt
résultat: '',
résultatObtenu: false
}
},
// cycle de vie
created() {
// eslint-disable-next-line
console.log("CalculImpot created");
},
// méthodes de gestion des évts
methods: {
// résultat du calcul de l'impôt
handleResultatObtenu(résultat) {
// on construit le résultat en chaîne HTML
const impôt = "Montant de l'impôt : " + résultat.impôt + ' euro(s)'
const décôte = 'Décôte : ' + résultat.décôte + ' euro(s)'
const réduction = 'Réduction : ' + résultat.réduction + ' euro(s)'
const surcôte = 'Surcôte : ' + résultat.surcôte + ' euro(s)'
const taux = "Taux d'imposition : " + résultat.taux
this.résultat = impôt + '<br/>' + décôte + '<br/>' + réduction + '<br/>' + surcôte + '<br/>' + taux
// affichage du résultat
this.résultatObtenu = true
// ---- maj du store [Vuex]
// une simulation de +
this.$store.commit('addSimulation', résultat)
// on sauvegarde la session
this.$session.save()
}
}
}
</script>
- linhas 44 e 48: os links do menu de navegação estão corretos. A página [/fin-session] não existe. O projeto [vuejs-22] resolveu este problema com o roteamento. Faremos o mesmo com o projeto [nuxt-20];
- linha 76: estamos a referenciar um método [addSimulation] que ainda não existe. Vamos criá-lo;
- linha 78: tal como na página [index], precisamos de escrever [this.$session().save(this.$nuxt.context)];
Vamos modificar o store [store/index]. Herdado do projeto [nuxt-12], atualmente tem este aspeto:
/* eslint-disable no-console */
// awning status
export const state = () => ({
// session jSON started
jsonSessionStarted: false,
// authenticated user
userAuthenticated: false,
// session cookie PHP
phpSessionCookie: '',
// adminData
adminData: ''
})
// changes in the awning
export const mutations = {
// state replacement
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
},
// awning reset
reset() {
this.commit('replace', { jsonSessionStarted: false, userAuthenticated: false, phpSessionCookie: '', adminData: '' })
}
}
// awning actions
export const actions = {
nuxtServerInit(store, context) {
// who executes this code?
console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
// init session
initStore(store, context)
}
}
function initStore(store, context) {
// store is the blind to be initialized
// retrieve the session
const session = context.app.$session()
// has the session already been initiated?
if (!session.value.initStoreDone) {
// start a new blind
console.log("nuxtServerInit, initialisation d'un nouveau store")
// put the blind in the session
session.value.store = store.state
// the blind is now initialized
session.value.initStoreDone = true
} else {
console.log("nuxtServerInit, reprise d'un store existant")
// update the store with the session store
store.commit('replace', session.value.store)
}
// save the session
session.save(context)
// log
console.log('initStore terminé, store=', store.state)
}
- linhas 3–27: vamos retomar o estado e as mutações da aplicação [vuejs-22] (ver documento [3]):
// awning status
export const state = () => ({
// session jSON started
started: false,
// authenticated user
authenticated: false,
// session cookie PHP
phpSessionCookie: '',
// list of simulations
simulations: [],
// last simulation number
idSimulation: 0,
// business] layer
métier: null
})
// changes in the awning
export const mutations = {
// state replacement
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
},
// awning reset
reset() {
this.commit('replace', { started: false, authenticated: false, phpSessionCookie: '', idSimulation: 0, simulations: [], métier: null })
},
// delete line n° index
deleteSimulation(state, index) {
// eslint-disable-next-line no-console
console.log('mutation deleteSimulation')
// delete line no. [index]
state.simulations.splice(index, 1)
console.log('store simulations', state.simulations)
},
// add a simulation
addSimulation(state, simulation) {
// eslint-disable-next-line no-console
console.log('mutation addSimulation')
// simulation no
state.idSimulation++
simulation.id = state.idSimulation
// add the simulation to the simulation table
state.simulations.push(simulation)
}
}
- linhas 4 e 6: apresentamos as propriedades já utilizadas;
- linha 8: armazenamos o cookie de sessão PHP. Isto é essencial para que o cliente e o servidor [nuxt] partilhem a mesma sessão PHP com o servidor de cálculo de impostos;
- linha 10: a lista de simulações realizadas pelo utilizador;
- linha 12: o número da última simulação realizada pelo utilizador;
- linha 14: a camada [business];
- linhas 30–47: as mutações presentes no store do projeto [vuejs-22] e referenciadas pelas páginas da aplicação. O projeto [vuejs-22] tinha uma mutação chamada [clear] que limpava a lista de simulações. Não a estamos a incluir porque a mutação [reset] já presente deve ser suficiente;
- linhas 26–28: a mutação [reset] é modificada para ter em conta o novo conteúdo do estado;
A página [calcul-impot] utiliza o seguinte componente [form-calcul-impot]:
<!-- définition HTML de la vue -->
<template>
<!-- formulaire HTML -->
<b-form @submit.prevent="calculerImpot" class="mb-3">
<!-- message sur 12 colonnes sur fond bleu -->
<b-row>
<b-col sm="12">
<b-alert show variant="primary">
<h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
</b-alert>
</b-col>
</b-row>
<!-- éléments du formulaire -->
<!-- première ligne -->
<b-form-group label="Etes-vous marié(e) ou pacsé(e) ?">
<!-- boutons radio sur 5 colonnes-->
<b-col sm="5">
<b-form-radio v-model="marié" value="oui">Oui</b-form-radio>
<b-form-radio v-model="marié" value="non">Non</b-form-radio>
</b-col>
</b-form-group>
<!-- deuxième ligne -->
<b-form-group label="Nombre d'enfants à charge" label-for="enfants">
<b-form-input id="enfants" v-model="enfants" :state="enfantsValide" type="text" placeholder="Indiquez votre nombre d'enfants"></b-form-input>
<!-- message d'erreur éventuel -->
<b-form-invalid-feedback :state="enfantsValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
</b-form-group>
<!-- troisème ligne -->
<b-form-group label="Salaire annuel net imposable" label-for="salaire" description="Arrondissez à l'euro inférieur">
<b-form-input id="salaire" v-model="salaire" :state="salaireValide" type="text" placeholder="Salaire annuel"></b-form-input>
<!-- message d'erreur éventuel -->
<b-form-invalid-feedback :state="salaireValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
</b-form-group>
<!-- quatrième ligne, bouton [submit] -->
<b-col sm="3">
<b-button :disabled="formInvalide" type="submit" variant="primary">Valider</b-button>
</b-col>
</b-form>
</template>
<!-- script -->
<script>
export default {
// inner state
data() {
return {
// married or not
marié: 'non',
// number of children
enfants: '',
// annual salary
salaire: ''
}
},
// calculated internal state
computed: {
// form validation
formInvalide() {
return (
// disabled salary
!this.salaire.match(/^\s*\d+\s*$/) ||
// or disabled children
!this.enfants.match(/^\s*\d+\s*$/) ||
// or tax data not obtained
!this.$métier.taxAdminData
)
},
// salary validation
salaireValide() {
// must be numeric >=0
return Boolean(this.salaire.match(/^\s*\d+\s*$/) || this.salaire.match(/^\s*$/))
},
// child validation
enfantsValide() {
// must be numeric >=0
return Boolean(this.enfants.match(/^\s*\d+\s*$/) || this.enfants.match(/^\s*$/))
}
},
// life cycle
created() {
// log
// eslint-disable-next-line
console.log("FormCalculImpot created");
},
// event manager
methods: {
calculerImpot() {
// tax is calculated using the [business] layer
const résultat = this.$métier.calculerImpot(this.marié, Number(this.enfants), Number(this.salaire))
// eslint-disable-next-line
console.log("résultat=", résultat);
// complete the result
résultat.marié = this.marié
résultat.enfants = this.enfants
résultat.salaire = this.salaire
// the [resultatObtenu] event is issued
this.$emit('resultatObtenu', résultat)
}
}
}
</script>
- linhas 65, 89: a referência [this.$métier] deve ser alterada para [this.$métier()];
Depois de feitas estas correções, podemos tentar executar uma simulação:

Recebemos a seguinte resposta:

Se analisarmos os registos:

- em [9-10], vemos que a primeira simulação está, de facto, na [loja];
- em [5], o número da última simulação foi efetivamente incrementado;
17.6. Passo 5
Agora que executámos uma simulação, vamos clicar na ligação [Lista de Simulações]. Obtemos a seguinte página:

O encaminhamento para o cliente [nuxt] foi bem-sucedido. Vamos ver o código da página [simulation-list]:
<!-- définition HTML de la vue -->
<template>
<div>
<!-- mise en page -->
<Layout :left="true" :right="true">
<!-- simulations dans colonne de droite -->
<template slot="right">
<template v-if="simulations.length == 0">
<!-- pas de simulations -->
<b-alert show variant="primary">
<h4>Votre liste de simulations est vide</h4>
</b-alert>
</template>
<template v-if="simulations.length != 0">
<!-- il y a des simulations -->
<b-alert show variant="primary">
<h4>Liste de vos simulations</h4>
</b-alert>
<!-- tableau des simulations -->
<b-table :items="simulations" :fields="fields" striped hover responsive>
<template v-slot:cell(action)="data">
<b-button @click="supprimerSimulation(data.index)" variant="link">Supprimer</b-button>
</template>
</b-table>
</template>
</template>
<!-- menu de navigation dans colonne de gauche -->
<Menu slot="left" :options="options" />
</Layout>
</div>
</template>
<script>
// imports
import Layout from '@/components/layout'
import Menu from '@/components/menu'
export default {
// composants
components: {
Layout,
Menu
},
// état interne
data() {
return {
// options du menu de navigation
options: [
{
text: "Calcul de l'impôt",
path: '/calcul-impot'
},
{
text: 'Fin de session',
path: '/fin-session'
}
],
// paramètres de la table HTML
fields: [
{ label: '#', key: 'id' },
{ label: 'Marié', key: 'marié' },
{ label: "Nombre d'enfants", key: 'enfants' },
{ label: 'Salaire', key: 'salaire' },
{ label: 'Impôt', key: 'impôt' },
{ label: 'Décôte', key: 'décôte' },
{ label: 'Réduction', key: 'réduction' },
{ label: 'Surcôte', key: 'surcôte' },
{ label: '', key: 'action' }
]
}
},
// état interne calculé
computed: {
// liste des simulations prise dans le store Vuex
simulations() {
return this.$store.state.simulations
}
},
// cycle de vie
created() {
// eslint-disable-next-line
console.log("ListeSimulations created");
},
// méthodes
methods: {
supprimerSimulation(index) {
// eslint-disable-next-line
console.log("supprimerSimulation", index);
// suppression de la simulation n° [index]
this.$store.commit('deleteSimulation', index)
// on sauvegarde la session
this.$session.save()
}
}
}
</script>
- linhas 47–56: os destinos do menu de navegação estão corretos;
- linha 75: a loja está corretamente referenciada;
- linha 89: estamos a utilizar uma mutação [deleteSimulation] que adicionámos no passo anterior;
- linha 91: esta linha deve ser reescrita como [this.$session().save(this.$nuxt.context)];
Fazemos as alterações necessárias e, em seguida, tentamos eliminar a simulação apresentada:

Em seguida, obtemos a seguinte página:

Vamos dar uma olhada nos registos:

- Em [6], vemos que a tabela de simulação está vazia;
Agora, voltemos ao formulário de cálculo de impostos:

Aparece a seguinte página:

Portanto, o encaminhamento funcionou.
17.7. Passo 6
Ainda precisamos de tratar da opção de navegação [Terminar sessão] no menu de navegação:
- na linha 9, a página [/end-session] não existe. O projeto [vuejs-22] tratou este caso com regras de roteamento num ficheiro [router.js]:
// imports
import Vue from 'vue'
import VueRouter from 'vue-router'
// les vues
import Authentification from './views/Authentification'
import CalculImpot from './views/CalculImpot'
import ListeSimulations from './views/ListeSimulations'
import NotFound from './views/NotFound'
// la session
import session from './session'
// plugin de routage
Vue.use(VueRouter)
// les routes de l'application
const routes = [
// authentification
{ path: '/', name: 'authentification', component: Authentification },
{ path: '/authentification', name: 'authentification2', component: Authentification },
// calcul de l'impôt
{
path: '/calcul-impot', name: 'calculImpot', component: CalculImpot,
meta: { authenticated: true }
},
// liste des simulations
{
path: '/liste-des-simulations', name: 'listeSimulations', component: ListeSimulations,
meta: { authenticated: true }
},
// fin de session
{
path: '/fin-session', name: 'finSession'
},
// page inconnue
{
path: '*', name: 'notFound', component: NotFound,
},
]
// le routeur
const router = new VueRouter({
// les routes
routes,
// le mode d'affichage des URL
mode: 'history',
// l'URL de base de l'application
base: '/client-vuejs-impot/'
})
// vérification des routes
router.beforeEach((to, from, next) => {
// eslint-disable-next-line no-console
console.log("router to=", to, "from=", from);
// route réservée aux utilisateurs authentifiés ?
if (to.meta.authenticated && !session.authenticated) {
next({
// on passe à l'authentification
name: 'authentification',
})
// retour à la boucle événementielle
return;
}
// cas particulier de la fin de session
if (to.name === "finSession") {
// on nettoie la session
session.clear();
// on va sur la vue [authentification]
next({
name: 'authentification',
})
// retour à la boucle événementielle
return;
}
// autres cas - vue suivante normale du routage
next();
})
// export du router
export default router
- As linhas 64–76 tratavam do caso especial do caminho para o caminho [/end-session];
- linha 66: limpa a sessão atual;
- linhas 68–70: exibem a vista [authentication];
Vamos tentar fazer algo semelhante no ficheiro de roteamento do cliente [nuxt]:

O script [client/routing.js] fica assim:
/* eslint-disable no-console */
export default function(context) {
// who executes this code?
console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
// management of the PHP session cookie in the browser
// the browser's PHP session cookie must be identical to the one found in the nuxt session
// the [end-session] action receives a new PHP cookie (server as nuxt client)
// if the server receives it, the client must pass it on to the browser
// for its own exchanges with the PHP server
// this is customer routing
// retrieve the session cookie PHP
const phpSessionCookie = context.store.state.phpSessionCookie
if (phpSessionCookie) {
// if it exists, we assign the PHP session cookie to the browser
document.cookie = phpSessionCookie
}
// where are we going?
const to = context.route.path
if (to === '/fin-session') {
// clean the session
const session = context.app.$session()
session.reset(context)
// redirects to the index page
context.redirect({ name: 'index' })
}
}
- Adicionámos as linhas [19-27] ao código existente;
- linha 20: recuperamos o [path] do destino da rota atual;
- linha 21: verificamos se é [/end-session]. Se for:
- linhas 23-24: a sessão é reiniciada;
- linha 26: redirecionamos o cliente [nuxt] para a página inicial;
O método [session.reset(context)] da sessão (linha 24) é o seguinte:
// reset de la session
reset(context) {
console.log('nuxt-session reset')
// reset du store
context.store.commit('reset')
// sauvegarde du nouveau store en session et sauvegarde de la session
this.save(context)
}
O método [context.store.commit('reset')] (linha 5) é o seguinte:
// reset du store
reset() {
this.commit('replace', { started: false, authenticated: false, phpSessionCookie: '', idSimulation: 0, simulations: [], métier: null })
}
Quando clicamos agora no link [Terminar sessão], a página inicial é apresentada com os seguintes registos:

- em [3], vemos que já não estamos autenticados;
- em [4], vemos que a sessão JSON foi iniciada;
- em [6], a camada [business] já não está presente na loja (continua presente nas páginas através de [this.$business()]);
- em [5, 7], já não há simulações;
É importante compreender o que acontece no final de uma sessão:
- a sessão [nuxt] é reiniciada: a propriedade [started] do store muda para [false];
- ocorre um redirecionamento para a página [index];
- o método [mounted] da página [index] é executado. Isto inicia uma nova sessão JSON com o servidor de cálculo de impostos. Se a operação for bem-sucedida, a propriedade [started] do store passa a [true];
17.8. Passo 7
Nesta altura, a aplicação [nuxt-20] possui todas as funcionalidades da aplicação [vuejs-22]. A migração parece estar concluída.
Vamos dar mais um passo no espírito do [nuxt]. O método [mounted] da página [index] coloca um problema. Ele inicia uma operação assíncrona que um motor de busca não vai esperar que termine. Sabemos que, neste caso, devemos colocar a operação assíncrona dentro de uma função [asyncData], pois assim o servidor [nuxt] que a executa irá esperar que ela termine antes de entregar a página ao motor de busca.
Aqui, usamos a função [asyncData] escrita na aplicação [nuxt-12] para a página [index]:
export default {
name: 'InitSession',
// components used
components: {
Layout,
Navigation
},
// asynchronous data
async asyncData(context) {
// log
console.log('[index asyncData started]')
// don't do things twice if the page has already been requested
if (process.server && context.store.state.jsonSessionStarted) {
console.log('[index asyncData canceled]')
return { result: '[succès]' }
}
try {
// start a jSON session
const dao = context.app.$dao()
const response = await dao.initSession()
// log
console.log('[index asyncData response=]', response)
// retrieve session cookie PHP for future requests
const phpSessionCookie = dao.getPhpSessionCookie()
// we store the PHP session cookie in the [nuxt] session
context.store.commit('replace', { phpSessionCookie })
// was there a mistake?
if (response.état !== 700) {
// the error is in response.réponse
throw new Error(response.réponse)
}
// note that the jSON session has started
context.store.commit('replace', { jsonSessionStarted: true })
// we return the result
return { result: '[succès]' }
} catch (e) {
// log
console.log('[index asyncData error=]', e)
// note that session jSON has not started
context.store.commit('replace', { jsonSessionStarted: false })
// we report the error
return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
} finally {
// save the blind
const session = context.app.$session()
session.save(context)
// log
console.log('[index asyncData finished]')
}
},
// life cycle
beforeCreate() {
console.log('[index beforeCreate]')
},
created() {
console.log('[index created]')
},
beforeMount() {
console.log('[index beforeMount]')
},
mounted() {
console.log('[index mounted]')
// customer only
if (this.showErrorLoading) {
console.log('[index mounted, showErrorLoading=true]')
this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
}
}
- linhas 13, 33, 40: altere a propriedade [jsonSessionStarted] para [started];
- linha 13: na aplicação [nuxt-12], apenas o servidor [nuxt] executou a página [index] e a sua função [asyncData]. O cliente [nuxt] só executou a página [index] depois de a receber do servidor [nuxt] e, por isso, não executou a função [asyncData]. No [nuxt-20], é diferente: o link [End Session] irá apresentar a página [index] no ambiente do cliente [nuxt]. A função [asyncData] será então executada. No entanto, ao chegar à página [index] desta forma, a sessão [nuxt] foi reiniciada entretanto, e a propriedade [started] do store é [false], pelo que a condição na linha 13 será necessariamente falsa. Podemos, portanto, omitir [process.server], e o cliente [nuxt] não realizará esta verificação;
- linhas 15, 35, 42: uma propriedade [result] é adicionada às propriedades [data] da página [index]. No [nuxt-20], esta propriedade não será utilizada, pelo que a removeremos do resultado devolvido pela função;
- Linhas 61–67: Este método [mounted] deve ser mantido, pois é ele que permite ao cliente [nuxt] exibir a mensagem de erro. No entanto, a forma como o erro é tratado será modificada;
Na página [index] atual, integramos a função [asyncData] acima no lugar da antiga função [mounted] e adicionamos uma nova função [mounted]. O código para a página [index] no exemplo [nuxt-20] passa então a ser o seguinte:
...
<!-- dynamique de la vue -->
<script>
/* eslint-disable no-console */
import Layout from '@/components/layout'
export default {
// components used
components: {
Layout
},
// component status
data() {
return {
// user
user: '',
// password
password: '',
// error display
showError: false
}
},
// calculated properties
computed: {
// valid entries
valid() {
return this.user && this.password && this.$store.state.started
}
},
// asynchronous data
async asyncData(context) {
// log
console.log('[index asyncData started]')
// don't do things twice if the page has already been requested
if (process.server && context.store.state.started) {
console.log('[index asyncData canceled]')
return
}
try {
// start a jSON session
const dao = context.app.$dao()
const response = await dao.initSession()
// log
console.log('[index asyncData response=]', response)
// retrieve session cookie PHP for future requests
const phpSessionCookie = dao.getPhpSessionCookie()
// we store the PHP session cookie in the [nuxt] session
context.store.commit('replace', { phpSessionCookie })
// was there a mistake?
if (response.état !== 700) {
// the error is in response.réponse
throw new Error(response.réponse)
}
// note that the jSON session has started
context.store.commit('replace', { started: true })
// no result
return
} catch (e) {
// log
console.log('[index asyncData error=]', e.message)
// note that session jSON has not started
context.store.commit('replace', { started: false })
// we report the error
return { showErrorLoading: true, errorLoadingMessage: e.message }
} finally {
// save the blind
const session = context.app.$session()
session.save(context)
// log
console.log('[index asyncData finished]')
}
},
// life cycle
beforeCreate() {
console.log('[index beforeCreate]')
},
created() {
console.log('[index created]')
},
beforeMount() {
// customer only
console.log('[index beforeMount]')
// error handling
if (this.showErrorLoading) {
// log
console.log('[index beforeMount, showErrorLoading=true]')
// the error is traced back to the main component [default]
this.$emit('error', new Error(this.errorLoadingMessage))
}
},
mounted() {
console.log('[index mounted]')
},
// event managers
methods: {
// ----------- authentication
async login() {
...
}
</script>
- linhas 58, 65: a função [asyncData] já não renderiza a propriedade [result] aqui;
- linha 81: o método [beforeMount] do cliente [nuxt]. Foi escolhido em vez do método [mounted] para lidar com quaisquer erros potenciais do [asyncData];
- linha 85: verificamos se a propriedade [errorLoading] foi definida. Ela só pode ser definida pela função [asyncData];
- linhas 85–90: se a função [asyncData] tiver reportado um erro, passamo-lo para a página [default] através do evento [error]. Era assim que a antiga função [created], que acabámos de substituir, tratava potenciais erros;
Vamos realizar alguns testes.
Primeiro, eliminamos tanto o cookie de sessão [nuxt] como o cookie de sessão PHP, caso existam. Em seguida, solicitamos a página [http://localhost:81/nuxt-20/] enquanto o servidor de cálculo de impostos não está a funcionar. Obtemos a seguinte página:

Recarregamos a mesma página após iniciar o servidor de cálculo de impostos:

Vamos ver os registos:

- Em [2-3], vemos que a sessão JSON foi iniciada;
- em [4], vemos o cookie de sessão PHP que o servidor [nuxt] recuperou durante a sua troca de dados com o servidor de cálculo de impostos. O cliente [nuxt] irá agora utilizá-lo;
Agora vamos iniciar sessão:

Aparece a seguinte página:

Em [1], recebemos uma mensagem de erro. Isto significa que o navegador não enviou o cookie de sessão correto da sessão PHP iniciada pelo servidor [nuxt] no passo anterior. Em [nuxt-12], o cookie de sessão PHP foi passado do servidor [nuxt] para o cliente [nuxt] no roteamento do cliente [nuxt] do script [middleware/client/routing]:
/* eslint-disable no-console */
export default function(context) { // who executes this code?
console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
// management of the PHP session cookie in the browser
// the browser's PHP session cookie must be identical to the one found in the nuxt session
// acion [fin-session] receives a new cookie PHP (server as nuxt client)
// if the server receives it, the client must pass it on to the browser
// for its own exchanges with the PHP server
// this is customer routing
// retrieve the session cookie PHP
const phpSessionCookie = context.store.state.phpSessionCookie
if (phpSessionCookie) {
// if it exists, we assign the PHP session cookie to the browser
document.cookie = phpSessionCookie
}
// where are we going?
const to = context.route.path
if (to === '/fin-session') {
// clean the session
const session = context.app.$session()
session.reset(context)
// redirects to the index page
context.redirect({ name: 'index' })
}
}
As linhas 13–17 permitem que o cliente [nuxt] recupere o cookie de sessão PHP do servidor [nuxt].
O problema aqui é que, quando clica no botão [Validate], não há encaminhamento a partir do cliente [nuxt]. A sua função de encaminhamento não é, portanto, chamada. Resolvemos o problema duplicando as linhas 12–17 no início do método de autenticação na página [index]:
// event managers
methods: {
// ----------- authentication
async login() {
// retrieve the PHP session cookie from the store
const phpSessionCookie = this.$store.state.phpSessionCookie
if (phpSessionCookie) {
// if it exists, we assign the PHP session cookie to the browser
document.cookie = phpSessionCookie
}
try {
// start waiting
this.$emit('loading', true)
// you are not yet authenticated
Linhas 5–10: Recuperamos o cookie de sessão PHP iniciado pelo servidor [nuxt] a partir do store. Assim que esta alteração é feita, a página de cálculo de impostos carrega com sucesso, indicando que a autenticação funcionou.
17.9. Passo 8
Temos uma aplicação funcional que funciona no espírito [nuxt]. Tal como fizemos para a aplicação [nuxt-13], vamos concentrar-nos na navegação no servidor [nuxt]. Conforme mencionado anteriormente, não se espera que o utilizador digite manualmente os URLs da aplicação. Espera-se que utilize os links que lhe são apresentados, os quais são executados pelo cliente [nuxt], que passa então a operar no modo SPA. No entanto, vamos garantir que a navegação no servidor [nuxt] mantenha sempre a aplicação num estado estável.
A partir do estudo realizado para o [nuxt-13] (ver parágrafo em link), sabemos que precisamos de:
- modificar o script [middleware/routing];
- Adicionar um script [middleware/server/routing];

O script [middleware/routing] é modificado da seguinte forma:
/* eslint-disable no-console */
// on importe les middleware du serveur et du client
import serverRouting from './server/routing'
import clientRouting from './client/routing'
export default function(context) {
// qui exécute ce code ?
console.log('[middleware], process.server', process.server, ', process.client=', process.client)
if (process.server) {
// routage serveur
serverRouting(context)
} else {
// routage client
clientRouting(context)
}
}
- linha 4: importamos o script de roteamento do servidor [nuxt];
- linhas 10–12: se o servidor [nuxt] estiver a executar o código, usamos a sua função de roteamento;
O script [middleware/server/routing] é o seguinte:
/* eslint-disable no-console */
export default function(context) {
// qui exécute ce code ?
console.log('[middleware server], process.server', process.server, ', process.client=', process.client)
// on récupère quelques informations ici et là
const store = context.store
// d'où vient-on ?
const from = store.state.from || 'nowhere'
// où va-t-on ?
let to = context.route.name
// cas particulier de /fin-session qui n'a pas d'attribut [name]
if (context.route.path === '/fin-session') {
to = 'fin-session'
}
// éventuelle redirection
let redirection = ''
// gestion du routage terminé
let done = false
// est-on déjà dans une redirection du serveur [nuxt]?
if (store.state.serverRedirection) {
// rien à faire
done = true
}
// s'agit-il d'un rechargement de page ?
if (!done && from === to) {
// rien à faire
done = true
}
// contrôle de la navigation du serveur [nuxt]
// on se calque sur le menu de navigation du client
// on traite d'abord le cas de fin-session
if (!done && store.state.started && store.state.authenticated && to === 'fin-session') {
// on nettoie la session
const session = context.app.$session()
session.reset(context)
// on redirige vers la page index
redirection = 'index'
// travail terminé
done = true
}
// cas où la session PHP n'a pas démarré
if (!done && !store.state.started && to !== 'index') {
// redirection vers [index]
redirection = 'index'
// travail terminé
done = true
}
// cas où l'utilisateur n'est pas authentifié
if (!done && store.state.started && !store.state.authenticated && to !== 'index') {
redirection = 'index'
// travail terminé
done = true
}
// cas où [adminData] n'a pas été obtenu
if (!done && store.state.started && store.state.authenticated && !store.state.métier.taxAdminData && to !== 'index') {
// redirection vers [index]
redirection = 'index'
// travail terminé
done = true
}
// cas où [adminData] a été obtenu
if (
!done &&
store.state.started &&
store.state.authenticated &&
store.state.métier.taxAdminData &&
to !== 'calcul-impot' &&
to !== 'liste-des-simulations'
) {
// on reste sur la même page
redirection = from
// travail terminé
done = true
}
// on a normalement fait tous les contrôles ---------------------
// redirection ?
if (redirection) {
// on note la redirection dans le store
store.commit('replace', { serverRedirection: true })
} else {
// pas de redirection
store.commit('replace', { serverRedirection: false, from: to })
}
// on sauvegarde le store dans la session [nuxt]
const session = context.app.$session()
session.value.store = store.state
session.save(context)
// on fait l'éventuelle redirection du serveur [nuxt]
if (redirection) {
context.redirect({ name: redirection })
}
}
- Neste script, reutilizamos os conceitos já desenvolvidos e utilizados no roteamento do servidor [nuxt] da aplicação [nuxt-13];
- Adicionamos duas propriedades ao armazenamento da aplicação:
- [from]: o nome da última página exibida. Sabemos que o cliente [nuxt] tem esta informação, mas o servidor [nuxt] não. Iremos adicionar esta informação, armazenando o nome da página a ser exibida no store cada vez que o servidor [nuxt] efetuar o roteamento. Faremos o mesmo para cada roteamento no cliente [nuxt]. Assim, no próximo roteamento pelo servidor [nuxt], este encontrará na loja o nome da última página exibida pela aplicação;
- [serverRedirection]: Quando um destino de encaminhamento é rejeitado pelo servidor [nuxt], este executará um redirecionamento. Indicará então no armazenamento que o próximo destino do servidor [nuxt] é uma página de redirecionamento. Este redirecionamento desencadeará uma nova execução do encaminhador do servidor [nuxt]. Se o encaminhador detetar que o destino atual é o resultado de um redirecionamento, permitirá que este prossiga;
- linhas 6–11: recuperamos as informações necessárias para o encaminhamento;
- linhas 13–16: o destino [/end-session] não está associado a uma página chamada [end-session]. Por isso, não tem nome. Atribuímos-lhe um;
- linha 19: o destino de um possível redirecionamento;
- linha 21: [done=true] quando os testes de encaminhamento estiverem concluídos;
- linhas 23–27: como mencionado, se o encaminhamento atual resultar de um redirecionamento, não há nada a fazer. De facto, durante o encaminhamento anterior, o router decidiu que o navegador do cliente deveria ser redirecionado. Não há necessidade de reconsiderar esta decisão;
- linhas 29–33: se for uma recarga da página, deixamos que isso aconteça. Esta não é uma regra universal para todas as aplicações [Nuxt]: deve examinar os efeitos de uma recarga para cada página. Aqui, verifica-se que recarregar as páginas [index, tax-calculation, simulation-list] não causa quaisquer efeitos indesejáveis;
- linhas 35–85: o encaminhamento do servidor [nuxt] espelha o encaminhamento do cliente [nuxt]. Quando numa página, o encaminhamento do servidor [nuxt] deve refletir o menu de navegação fornecido pelo cliente [nuxt] enquanto estiver nessa página;
- linhas 38–47: tratamos primeiro o caso em que o destino [end-session] não corresponde a uma página existente. Se as condições forem satisfeitas (sessão iniciada, utilizador autenticado), limpamos a sessão e redirecionamos o utilizador para a página [index];
- linhas 49–55: se a sessão JSON com o servidor de cálculo de impostos não tiver sido iniciada, então o único destino possível é a página [index];
- linhas 57–62: se a sessão JSON tiver sido iniciada e o utilizador não estiver autenticado e não tiver solicitado a página de autenticação, redirecionamos para a página de autenticação, que é a página [index];
- linhas 64–70: se o utilizador estiver autenticado, mas os [adminData] não tiverem sido obtidos, o utilizador é redirecionado para a página de autenticação. A autenticação faz duas coisas: autentica o utilizador e, se a autenticação for bem-sucedida, também solicita os dados [adminData]. Se estes dados não tiverem sido obtidos, a autenticação deve ser reiniciada;
- linhas 72–85: se os dados [adminData] tiverem sido obtidos, então os únicos destinos possíveis são [tax-calculation] e [simulation-list]. Se não for esse o caso, o encaminhamento é negado;
- linhas 88–95: o armazenamento é atualizado dependendo de haver ou não um redirecionamento;
- linha 94: não há redirecionamento. Portanto, o atual [to] torna-se o [from] para o próximo encaminhamento;
- linhas 96–99: as informações da loja são guardadas no cookie de sessão [nuxt];
- linhas 100–103: se for necessário um redirecionamento, este é executado;
Para executar os testes, certifique-se de começar do zero, eliminando o cookie de sessão [nuxt] e o cookie de sessão PHP com o servidor de cálculo de impostos:

Para testar o roteamento do servidor [nuxt], experimente todos os URLs possíveis [/, /tax-calculation, /simulation-list] em cada página. Em cada ocasião, a aplicação deve permanecer num estado consistente.
17.10. Passo 9
O Passo 9 envolve a implementação da aplicação [nuxt-20]. Isto requer um alojamento que forneça um ambiente [node.js] para executar o servidor [nuxt]. Eu não disponho disso. O leitor pode seguir os procedimentos descritos na secção indicada para implementar a aplicação [nuxt-20] na sua máquina de desenvolvimento e protegê-la com um protocolo HTTPS.
17.11. Conclusão
A portabilidade da aplicação [vuejs-22] para a aplicação [nuxt-20] está agora concluída. Vamos rever alguns pontos-chave desta portabilidade:
- as páginas [vuejs-22] foram mantidas;
- as operações assíncronas que existiam nas páginas [vuejs-22] foram migradas para uma função [asyncData];
- no [nuxt-20], tivemos de gerir duas entidades: o cliente [nuxt] e o servidor [nuxt]. A última entidade não existia no [vuejs-22]. Para manter a consistência entre as duas entidades, precisámos de uma sessão [nuxt];
- tínhamos de gerir o encaminhamento do servidor [nuxt];
Na prática, é provavelmente preferível começar diretamente com uma arquitetura [nuxt] em vez de construir uma arquitetura [vue.js] e depois portá-la para um ambiente [nuxt].