Javascript para Alojamiento Web (Hosting)

El concepto de alojamiento web (hosting) es el escenario esencial cuando se trata de publicar una página o un sitio web, y es distinto al de computación en la nube (dónde interviene infraestructura de computo o el procesamiento en una plataforma informática con una disposición más sofisticada). El alojamiento web básicamente ofrece el servicio de disponer de una carpeta con los archivos que conforman un sitio web para que sea visualizado cuando es visitado por medio de un navegador, agregando otras características interesantes (dominio, cuentas de correo electrónico, base de datos, entre otras) como parte de un paquete que ha tenido acogida masiva, de allí que suelen ser económicos respecto a servicios de otro nivel (como la computación en la nube). Veamos a continuación cómo diseñar una aplicación básica con Javascript sin requerir de servicios superiores al alojamiento web.

Firebase Hosting

Para lograr una prueba de concepto a través de Internet, se puede usar el servicio de alojamiento web de Google denominado Firebase Hosting que es gratuito bajo unos límites generosos y requiere una cuenta de Gmail (o abrir una). De este modo, así como se puede tener una cuenta de correo obtendrías servicio de hospedaje del sitio, adicionalmente ofrece su propuesta para los datos denominada Firestore. Si se adquiere un dominio y el paquete G Suite (que presentaría tu correo Gmail sin publicidad y con un dominio formal que aplica también al sitio) tendrías un servicio cercano al alojamiento web convencional en un precio interesante.

Aunque actualmente existe Google Cloud Platform con un escenario de servicios más completo y sofisticado, la plataforma Firebase es más sencilla para otro tipo de usuarios, sin embargo no aplicaría para amantes de WordPress (aplicación reconocida para facilitar diseño de sitios) dado que no opera para lenguaje PHP (requerido por WordPress) sino estático (HTML o archivos que se entregan directamente al navegador), que es la manera como se diseñan páginas web de modo directo (Adicionalmente, Firebase dispone del concepto de Functions usando Node.js).

Para acceder al servicio debes inscribir una cuenta Gmail en el sitio de Firebase. Una vez realizado el proceso de reportarte como usuario de Firebase (dando clic en el botón visitar consola o comenzar), en adelante se usa la misma página para ir a la consola con la que se gestiona el sitio.

El proceso de autenticación es simple y nos lleva a la consola Web (Firebase Console) en la que encuentras una bienvenida y puedes añadir tu proyecto (podría ser necesario configurar el método de inicio de sesión en el menú Authentication para habilitar el registro de usuarios con correo y contraseña).

En la opción de Configuración Web encontraremos la información necesaria para establecer nuestra página HTML.

Instalando la CLI de Firebase

Las herramientas para la CLI de Firebase (que evita el uso de otro tipo de herramientas como ftp) se instalan ejecutando desde una terminal:

npm install -g firebase-tools

Puedes usar el comando firebase list para mostrar la lista de proyectos remotos gestionados en Firebase Console. Si se requiere de conexión a la cuenta, se debe ejecutar primero: firebase login.

Para iniciar un proyecto debemos contar con una carpeta nueva en nuestro equipo asignada para este propósito, por ejemplo hosting, y desde la terminal debemos ubicarnos dentro de la carpeta y ejecutar:

firebase init

El comando firebase init crea archivos que contienen la información reportada para acceder al proyecto y la carpeta para nuestras páginas web. Podríamos encontrar una estructura semejante a la siguiente:

Para probar el proyecto de modo local se ejecuta:

firebase serve

Revisa abriendo un navegador y consultando la dirección respectiva, que generalmente es http://localhost:5000

Para publicar nuestra página o sitio web simplemente se ejecuta:

firebase deploy

Revisa abriendo un navegador y consultado la dirección respectiva (por ejemplo: https://myproyect.birebaseapp.com). Si se requiere de conexión a la cuenta, se debe ejecutar primero: firebase login.

Ahora puedes modificar este proyecto para aplicar tu propio diseño (sobreescribiendo el archivo index.html). Si se requiere cambiar el nombre de la carpeta de contenido público por defecto (public), aún estas a tiempo de ejecutar de nuevo firebase init y reportar el nombre respectivo (por ejemplo, web) cuando se solicite.

Resumen de comandos de la CLI

Comando Descripción
firebase init Usado dentro de una carpeta de un proyecto para inicializar la configuración del proyecto asociándolo a la consola Web de Firebase
firebase serve Inicia el servicio localmente para revisión previa o pruebas.
firebase deploy Despliega la aplicación o página en Internet. Genera un control de versión y sobreescribe los archivos anteriores. En este sentido, se tiene una dinámica donde el proyecto local es el punto de partida, mientras la publicación remota queda sujeta al despliegue (publicación de cambios), de allí que Firebase proporciona un control de versiones pero debe tenerse presente que ocupan espacio.
firebase list Lista proyectos asociados a la cuenta que se administra con la consola Web de Firebase
firebase login Abre el navegador para conectarse a la cuenta en caso de requerirlo

Desarrollando una aplicación web estática

La siguiente información tiene como requisito una serie de documentos propuestos por OnMind, retomando el documento Javascript Fullstack. Si no se ha venido siguiendo ese temario has alcanzado la información necesaria y puedes omitir lo que resta.

Espero genere algún interés la idea de enfocarse en un desarrollo dónde sólo se usan recursos estáticos (como html, css, js, json) que funcionan en el navegador o en cualquier servidor web (como Apache, IIS, Nginx) o servicio de alojamiento web básico (web hosting), teniendo al alcance servicios como Firebase Hosting de Google (o similar en AWS que otorga un año sin costo). Esto brinda también la posibilidad de llegar a portar el software en una aplicación móvil (conocida como híbrida), además de evitar usar procesos de servidor si no se requieren. En cierto sentido es como volver a los orígenes de las páginas web, sin depender de lenguajes de programación que procesan datos en el lado del servidor (como PHP, Python, Java, etc.), cuando lo que se necesita es simplemente contenido o funciones que corren del lado del navegador y que eventualmente pueden consumir una API remota de llegar a requerirlo.

Aunque se han mencionado algunas ventajas también puede tener desventajas y no se puede comparar con algo que, por su naturaleza, puede corresponder al tema de computo. Sin embargo, sería útil si se piensa para ciertos escenarios simplificados.

Como una prueba de concepto, nos basaremos en el último código del documento titulado Javascript Fullstack (sólo gui, sin api), abriendo una nueva carpeta (sin relacionarse aún con Firebase) cuya estructura se verá como la siguiente:

Tomando o copiando src, package.json y webpack.config.js de nuestro proyecto gui para tenerlos en una carpeta nueva que llamaremos static, verificamos que el contenido corresponda al ilustrado. Se ha agregado la sub-carpeta web para usarla como nuestro nuevo destino de archivos distribuibles.

Incluimos en el archivo package.json la dependencia de lokijs, quedando así:

{
  "scripts": {
    "start": "webpack --mode development",
    "build": "webpack --mode production"
  },
  "dependencies": {
    "lokijs": "^1.5.7",
    "riot": "^4.6.5"
  },
  "devDependencies": {
    "@riotjs/compiler": "^4.5.1",
    "@riotjs/webpack-loader": "^4.0.1",
    "copy-webpack-plugin": "^5.0.4",
    "webpack": "^4.41.1",
    "webpack-cli": "^3.3.9"
  }
}

El archivo de configuración webpack.config.js tendrá el siguiente contenido:

const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
  entry: './src/gui.js',
  output: {
    path: path.join(__dirname, 'web'),
    filename: 'app.bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.riot$/,
        exclude: /node_modules/,
        use: [{
          loader: '@riotjs/webpack-loader',
          options: { hot: false }
        }]
      }
    ]
  },
  plugins: [
    new CopyWebpackPlugin([
        { from: './src/index.html' },
        { from: './node_modules/lokijs/build/lokijs.min.js' }
    ])
  ]
}

Nuestro archivo o plantilla index.html (tomada del proyecto api de un ejercicio anterior) incluirá dentro del body alguna línea nueva de “script” correspondiente a LokiJS, quedando de la siguiente manera:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Riot Component</title>

    <!-- Semantic-UI Styles -->
    <link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.5.0/semantic.min.css" rel="stylesheet" media="screen">
    <!-- HighlightsJS -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.9/styles/ocean.min.css">
  </head>
  <body>
    <div id="app"></div>

    <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.5.0/semantic.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.9/highlight.min.js"></script>
    <script src="lokijs.min.js"></script>
    <script defer src="app.bundle.js"></script>
  </body>
</html>

Nuestro “script” principal gui.js tendrá el siguiente contenido:

import { component } from 'riot'
import Tag from './my-data.riot'

var db = new loki('database.json', {
        autoload: true,
        autoloadCallback : databaseInitialize,
        autosave: true, 
        autosaveInterval: 4000
    })
  
function databaseInitialize() {
    if (!db.getCollection('mydata'))
      db.addCollection('mydata')
}

component(Tag)(document.getElementById('app'), { 'db': db })

Nótese que se destina un archivo para la base de datos, se inicializa y se pasa como propiedad al componente web.

Ahora cambiamos en el archivo my-data.riot el bloque de código correspondiente al “script”, es decir, el contenido interno de la etiqueta script quedaría así:

        import MyForm from './my-form.riot'
        import MyTable from './my-table.riot'
        export default {
            components: { MyForm, MyTable },
            state: {
                status: 0,  // 0 => initial, 1 => ok, -1 => error
                name: 'john',
                color: 'dark',
                data: [],
                baseURI: 'http://localhost:3000/mydata/'
            },
            onMounted() {  // When component is mounted or created
                this.update()
            },
            onSend(e) {
                e.preventDefault()
                this.update({ status: 1, code: '...' })  // update or render again HTML (DOM)
                let form = document.forms[0]  // get the first form or unique
                let name = form['name'].value
                let lastname = form['lastname'].value

                if (!name) {
                    this.update({ code: 'Name wrong!' })
                    return
                }

                let data = {
                    "name": name,
                    "lastname": lastname
                }

                try {
                    let mydata = this.props.db.getCollection('mydata')
                    mydata.on('insert', (row) => { row.id = row.$loki; });  // set auto id
                    let result = mydata.insert(data)
                    
                    form['name'].value = null
                    form['lastname'].value = null
                    this.onList()
                }
                catch (err) {
                    this.update({ status: -1, code: err.message })
                    hljs.initHighlighting()
                }
            },
            onList(e) {
                if (e) e.preventDefault()  // this "if" is because can be called whithout "e"
                this.update({ status: 1, code: '...' })  // update or render again HTML (DOM)

                try {
                    let mydata = this.props.db.getCollection('mydata')
                    this.state.data = mydata.data
                    this.update()
                }
                catch (err) {
                    this.update({ status: -1, code: err.message })
                    hljs.initHighlighting()
                }
            },
            onRemove(e) {
                e.preventDefault()
                let target = e.target
                if (target.tagName == 'I')
                    target = e.target.parentNode
                
                // Get the "id" from element or target attribute
                let id = target.getAttribute('data-id')

                try {
                    let db = this.props.db
                    let data = db.getCollection('mydata')
                    let result = data.where((row) => { return row.id == id })
                    data.remove(result)
                    this.onList()
                }
                catch (err) {
                    this.update({ status: -1, code: err.message })
                    hljs.initHighlighting()
                }
            },
            onCheck(e) {
                if (this.state.color == 'clear')
                    this.state.color = 'dark'
                else
                    this.state.color = 'clear'
                this.update()
            }
        }

Recuerda que deben haberse instalado los módulos con npm install, luego podremos armar nuestro paquete distribuible con npm start y, como tiene un diseño estático, hasta podemos abrir el archivo web/index.html directamente con un navegador (por ejemplo, dando clic derecho sobre este archivo y eligiendo la aplicación).

Publicando nuestra aplicación con Firebase

La manera en que se ha planteado la aplicación simplemente es con el objetivo de independizar la versión del proyecto para reutilizarla sin asociarse aún a Firebase (por supuesto que podría haberse hecho un solo proyecto). Por tanto, es momento de retomar el proyecto de Firebase que habíamos iniciado anteriormente. Se deben copiar los archivos distribuibles de nuestra aplicación (index.html, app.bundle.js, lokijs.min.js) en la carpeta definida para el contenido (por ejemplo: public o web según se haya indicado) . Es decir, el proyecto de Firebase se podría ver de la siguiente manera:

RECUERDA: Para publicar el proyecto en Internet ejecutas el comando firebase deploy (que además genera versión en el control de versiones de la plataforma). Si se requiere de conexión a la cuenta, se debe ejecutar primero: firebase login. Para probar el proyecto localmente ejecutas el comando firebase serve.


© 2019 by César Arcila