Hapi.js + Vue.js ejemplo mínimo de frontend: formulario login

Hapi.js + Vue.js ejemplo mínimo de frontend: formulario login

Esta parte es solo frontend, aún no está configurado para que interactúe con el servidor.

Lo más destacado de este ejemplo es el uso de vue-router, paquete que permite la realización de SPA de forma sencilla. Como la aplicación será gestionada por un único fichero (index.html), es necesario configurar el servidor de webpack para que gestione las URLs que acceden a otras partes de la aplicación para que no devuelva un error 404.

Esto es fácil, tan solo hay que añadir historyApiFallback y ponerlo a true en la configuración del servidor.

devServer: {
	inline: true,
	hot: true,
	port: 9999,
	historyApiFallback: true,
},

Si usas Apache u otro servidor deberás usar una configuración distinta.

Lógicamente habrá que instalar el paquete vue-router.

Vale, ya está todo instalado, ahora solo hace falta configurar vue-router para que acepte distintas URLs y que muestre distintos controladores según sea el caso.

Para ello creamos un fichero router.js que posteriormente añadiremos a nuestra instancia de Vue:

Es fácil de entender, importamos los distintos controladores y configuramos las rutas (‘/‘ y ‘/login‘), a las que les asignaremos el controlador correspondiente.

Para indicar a Vue que vamos a usar vue-router, debemos importarlo en la instancia de la aplicación:

import App from './components/App.vue';
import router from './router';

Vue.use( VueRouter );

new Vue( {
	el: '#app',
	router,
	components: {
		App,
	},
	render: ( c ) => c( 'app' ),
} );

El siguiente paso es modificar el controlador principal de la aplicación (App.vue) para que muestre la cabecera (que tendrá su propio controlador) y la vista principal de vue-router (<router-view>):

<template>
	<div>
		<v-header />
		<router-view />
	</div>
</template>

<script>
import header from '@/js/components/layout/Header';

export default {
	name: 'App',
	components: {
		'v-header': header,
	},
};
</script>

Como no soy diseñador, pues usaré Buefy (basado en Bulma) y Material Design icons (no sé por qué le tengo algo de manía a FontAwesome).

Existe un paquete especial para usar Material Design en vue (vue-material-design-icons), que para funcionar con Buefy necesitará usar la fuente de letras de Material Design (@mdi/fonts). Instalamos todo y ya estará todo listo para empezar a diseñar nuestra página.

La cabecera (<v-header>) mostrará el logo, el menú principal y otro secundario para loguearse. No explicaré ni las clases Bulma (que yo casi ni conozco) y cómo se muestra el menú al clickar en el burger icon, ya que estos tutoriales son para llevar yo un diario de cómo desarrollar una app web con Hapi.js y Vue.js.

El controlador de la cabecera nos quedará así:

<template>
	<header>
		<nav
			class="navbar"
			role="navigation"
			aria-label="main navigation"
		>
			<div class="navbar-brand">
				<a
					class="navbar-item"
					href="https://sentidoweb.com"
				>
					<img src="/assets/images/logo.svg">
				</a>

				<a
					role="button"
					class="navbar-burger burger"
					aria-label="menu"
					aria-expanded="false"
					:class="{ 'is-active' : showNav }"
					@click="showNav = !showNav"
				>
					<span aria-hidden="true" />
					<span aria-hidden="true" />
					<span aria-hidden="true" />
				</a>
			</div>

			<div :class="[ { 'is-active' : showNav }, 'navbar-menu' ]">
				<div class="navbar-start">
					<router-link class="navbar-item" to='/'>Home</router-link>
				</div>

				<div class="navbar-end">
					<div class="navbar-item">
						<div class="buttons">
							<router-link class="button is-light" to='/login'>Log in</router-link>
						</div>
					</div>
				</div>
			</div>
		</nav>
	</header>
</template>

<script>
export default {
	data() {
		return {
			showNav: false,
		};
	},
};
</script>

Tan solo mencionar cómo vue-router gestiona la navegación, para ello hace uso de <router-link>:

<router-link class="navbar-item" to='/'>Home</router-link>

El resto del código es simplemente la página principal y el formulario “tonto”.

Hapi.js + Vue.js inicializar el frontend
|

Hapi.js + Vue.js inicializar el frontend

El backend ya está algo configurado, por lo que voy a empezar a configurar el frontend.

Instalaré varias librerías:

  • Vue.js
  • Webpack: configurado para que funcione con HMR
  • Eslint: para que no haya errores Javascript
  • Stylelint: lo mismo para CSS
  • Buefy: una librería que combina Bulma y Vue
  • Sass loader

En vez de ir instalando una a una, usando el siguiente package.json y ejecutando npm i, lo tendremos todo instalado.

{
  "name": "hapi-frontend",
  "version": "1.0.0",
  "description": "Hapi.js frontend",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack-dev-server --mode development --config webpack.config.dev.js",
    "build": "webpack --mode production --config webpack.config.production.js"
  },
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.5.4",
    "babel-eslint": "^10.0.2",
    "babel-loader": "^8.0.6",
    "css-loader": "^3.0.0",
    "eslint": "^6.0.1",
    "eslint-plugin-html": "^6.0.0",
    "eslint-plugin-vue": "^5.2.3",
    "mini-css-extract-plugin": "^0.7.0",
    "node-sass": "^4.12.0",
    "sass-loader": "^7.1.0",
    "stylelint": "^10.1.0",
    "stylelint-config-standard": "^18.3.0",
    "stylelint-webpack-plugin": "^0.10.5",
    "vue-hot-reload-api": "^2.3.3",
    "vue-html-loader": "^1.2.4",
    "vue-loader": "^15.7.0",
    "vue-style-loader": "^4.1.2",
    "vue-template-compiler": "^2.6.10",
    "webpack": "^4.35.3",
    "webpack-cli": "^3.3.5",
    "webpack-dev-server": "^3.7.2",
    "webpack-merge": "^4.2.1"
  },
  "dependencies": {
    "buefy": "^0.7.10",
    "vue": "^2.6.10"
  }
}

Como se puede ver, existen dos scripts dentro de npm: build que compila el js y extrae los CSS, y dev, que arranca el servidor de webpack habilitando HMR (🎶 ¡ya no puedo vivir sin él! 🎶).

Ambas configuraciones de webpack usan un script en común (webpack.config.common.js):

const webpack = require( 'webpack' );
const path = require( 'path' );
// Carga los ficheros .vue
const VueLoaderPlugin = require( 'vue-loader/lib/plugin' );
// Configura stylelint
const StyleLintPlugin = require( 'stylelint-webpack-plugin' );

// Para obtener un path para los alias
function resolve( dir ) {
	return path.join( __dirname, '.', dir );
}

module.exports = {
	mode: 'production',
	// Fichero inicial del proyecto
	entry: './js/main.js',
	// Fichero final para incluir
	output: {
		filename: 'js/main.js',
		publicPath: '/dist/',
	},
	module: {
		// Reglas para los ficheros
		rules: [
			{
				test: /\.js$/,
				exclude: /node_modules/,
				loader: 'babel-loader',
			},
			{
				test: /\.vue$/,
				loader: 'vue-loader',
			},
			{
				test: /\.css$/,
				use: [
					'css-loader',
					'sass-loader',
				],
			},
		],
	},
	plugins: [
		new webpack.HotModuleReplacementPlugin(),
		new VueLoaderPlugin(),
		new StyleLintPlugin( {
			files: [ '**/*.{vue,htm,html,css,sss,less,scss,sass}' ],
		} ),
	],
	resolve: {
		extensions: [ '.js', '.vue', '.json' ],
		alias: {
			'@': resolve( '' ),
		},
	},
};

El frontend se gestiona desde el fichero main.js, que inicializará Vue y añadirá el componente principal:

import Vue from 'vue';
import Buefy from 'buefy';
import 'buefy/dist/buefy.css';

import App from './components/App.vue';

import '@/assets/scss/main.scss';

Vue.use( Buefy );

new Vue( {
	el: '#app',
	components: {
		App,
	},
	render: ( c ) => c( 'app' ),
} );

// accept replacement modules
if ( module.hot ) {
	module.hot.accept();
}

Y ya por último el componente App.vue, que muestra simplemente un poco de HTML

<template>
	<header class="hero">
		<div class="hero-head">
			<h1>{{ title }}</h1>
		</div>
	</header>
</template>

<script>
export default {
	data() {
		return {
			title: "Demo site",
		};
	},
};
</script>

<style lang="scss" scoped>
	div {

		h1 {
			color: #fff;
		}
	}
</style>

Bueno, ha sido un resumen rápido, pero bajándote el código seguro que lo entiendes fácil