domingo, 16 de agosto de 2020

React Rutas con wouter

Cuando se tiene una aplicación spa, es muy util tener un mecanismo que nos ayude a trabajar con las rutas de nuestra url, cosas como que dependiendo de la ruta mueste un determinado componente o tal vez que dependiento de cierta logica de negocio nos redirija a una determinada ruta y justo esto y mucho mas wouter nos ayuda a conseguirlo, aqui ire mostrando las cosas que voy aprendiendo
Documentación
https://github.com/molefrog/wouter

Intalación con npm

npm install wouter --save-dev
Primer reto encontrado
Estoy trabajando con el servidor de paginas webpack-dev-server del cual ya les hable aquí y me encontre con que al utilizar wouter salia pagina no encontrada cuando intentaba navegar a otra ruta, leyendo, leyendo encontre que webpack-dev-server por defecto solo sirve la ruta raíz y si se desea navegar a otra ruta, es necesario indicarselo en el archivo de configuración de webpack webpack.config.js de la siguiente forma
devServer: {
   historyApiFallback: true,
},
Route
Con Route puedes hacer que se renderice un componente especifico de acuerdo a la url, en el ejemplo a continuación renderizaremos el componente Login si estamos en la ruta raíz o el componente Home si estamos en la ruta Home

 <route component="{Login}" path="/">
 <route component="{Home}" path="/Home">
Link
Con Link puedes hacer lo mismo que haces con la etiqueta de html a, solo que Link no hace postback o refresco de toda la pagina

<Link to="/Home">Ir a Home</Link>
El hook location con este hook lo que puedes hacer es redirigir programaticamente al usuario a una determinada ruta, en el ejemplo a continuación enviamos al usuario a la ruta search, tambien sirve para saber en que ruta nos encontramos actualmente.

//Importamos el hook
import {useLocation} from "wouter"
//Destructuramos la llamada a useLocation
const [path,pushLocation] = useLocation()
//Enviamos al usuario a la ruta search
pushLocation('/search')

Webpack

Iniciar un proyecto nuevo con npm

npm init -y

Instalar webpack

npm install webpack webpack-cli -D

Indicarle a npm que debe compilar usando webpack

En el archivo package.json en la propiedad scripts se debe indicar a npm que debe usar webpack cuando complie

"scripts": {
    "build":"webpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  }

Webpack y sus valores de compilación por defecto

Cuando ejecutamos npm run build webpack trae preconfigurado que debe buscar en la carpeta src y compilar el archivo index.js como punto de entrada, asi mismo tambien trae preconfigurado que el resultado de la compilación debe dejarlo en la carpeta dist, si no existe el la crea, tambien trae preconfigurado el modo de compilación production, tiene dos production o development y tambien trae preconfigurado que el archivo resultado de la compilación se llamara main.js; todos estos valores se pueden cambiar

Conceptos clave de Webpack que se iran revisando en detalle mas adelante

Entrypoint:
Es el punto de entrada de la aplicación
Se le debe indicar a webpack cual es el punto de entrada de la aplicación, el por defecto tiene configurado index.js

Output
Por defecto Webpack crea una carpeta dist y dentro un archivo main.js que contiene el codigo fuente de todas las dependencias usadas armando un grafo desde index.js

Loaders
Son como tuneles o transformadores o transpiladores, que transforman sintaxis a otra sintaxis, i.e:
- El loader de babel para transformar jsx a javascript
- El loader de sass para transformar de sass a css
- Cada vez que hacemos un import pasa por un loader

Loaders y presets
Los presest son reglas preestablecidas para que se pueda hacer una configuración u otra, al final son plugins, ej: el loader de babel con el preset de react una de las transformaciones que trae es la del jsx

Plugins
Ejecuta código en diferentes puntos de ejecución de Webpack

Indicarle a Webpack que compile en modo desarrollo o producción

En el nodo scripts del archivo package.json puedes definir varios modos de empaquetar la aplicación

"scripts": {
    "build": "webpack --mode production",
    "dev": "webpack --mode development",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Para compilar en modo desarrollo o producción se usa la siguiente sintaxis de npm

npm run dev
npm run build

Y lo anterior para que sirve, bueno para indicarle a Webpack si debe o no optimizar el paquete que va a generar, con optimizar me refiero a si lo minifica entre otras técnicas de optimización que seguro tiene y no conozco por ahora, con dev Webpack no optimiza el paquete generado con build si

Hacer que Webpack compile cuando detecta un cambio

En el archivo package.json, si le agregas --watch a build o dev puedes conseguir esto

"scripts": {
    "build": "webpack --mode production --watch",
    "dev": "webpack --mode development --watch",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Pero no es muy optimo

Antes de ver loaders y plugins crea este archivo

Para configurar los loader y plugins debes crear primero un archivo con el nombre webpack.config.js al mismo nivel de package.json

Configurar un loader, por ejemplo el de babel para React

Debes instalar previamente el core de babel, el loader y el preset, la -D indica que son dependencias de desarrollo

npm install @babel/core babel-loader @babel/preset-react -D

En el archivo webpack.config.js configurarlo de la siguiente manera:

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-react']
                    }
                }
            }
        ]
    }
}

En este objeto le estamos diciento a webpack lo siguiente:
-  module: Carga este modulo
-  rules: Este modulo que estas cargando tiene las siguientes reglas
-  test: /\.js$/ Debes ejecutarlo en todos los archivos .js
-  excludes: /node_modules/ Excluye la carpeta node_modules
-  use: Lo que usaran para ejecutarlo
-  loader: 'babel-loader' Usaras el loader de babel
-  options: Con las siguientes opciones
-  presets: ['@babel/preset-react'] Usaras la configuración preestablecida de react, esta configuración     dentro de muchas cosas que tiene, tiene la configuración para el transpilador de JSX a Javascript

En otras palabras Webpack le enviara todos los archivos .js al transformador babel-loader, exluyendo todo lo que encuentre en la carpeta node_modules, lo que se espera es que babel-loader con el preset de react, identifique y transforme la sintaxis JSX de cada archivo en sintaxis javascript que el navegador pueda interpretar

Cambiando el nombre al archivo output y colocandole un hash al nombre

En ocasiones queremos cambiar el nombre main.js que genera por defecto webpack en el output y también a veces queremos que al nombre le agregue una especie de hash, esto con el fin de que cuando publiquemos, evitar la cache del navegador y que nuestros usuarios tengan la versión mas actualizada de nuestra aplicación

En el archivo webpack.config.js debemos agregar output y adicionando [contentHash], WebPack cuando compila allí le agrega un hash.

module.exports = {
    output: {
        filename: 'app[contentHash].js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-react']
                    }
                }
            }
        ]
    }
}
Al cambiar el nombre y adicionar un hash debemos ajustar la referencia en el archivo index.html, a continuación veremos como hacer para que webpack haga el ajuste automáticamente por medio del plugin HTMLWebpackPlugin

Utilizando el plugin HTML Webpack Plugin

Vamos a usar este plugin para que cuando cambiemos el nombre del archivo output, se ajusten las referencias en el archivo index.html automáticamente

Instalar el plugin

npm i --save-dev html-webpack-plugin

En el archivo webpack.config.js se debe agregar una constante del HTML Webpack Plugin y luego adicionarlo a la propiedad plugins del modulo

const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    output: {
        filename: 'app[contentHash].js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-react']
                    }
                }
            }
        ]
    },
    plugins:[
        new HtmlWebpackPlugin()
    ]
}

Cuando corres npm run dev, notamos que en la carpeta dist, webpack genero el archivo index.html y tambien el app[hashContent], pero notamos que el archivo index.html no es el nuestro el que teniamos en desarrollo y esto es porque el plugin usa una plantilla por defecto si no se le indica alguna y como no la hemos indicado por eso genero la plantilla que tiene por defecto; a continuación vamos a configurar la nuestra.

Configurando la plantilla que usara HtmlWebpackPlugin

En el archivo webpack.config.js en la sección plugins se indica en titulo y el template html que usara el plugin

const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    output: {
        filename: 'app[contentHash].js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-react']
                    }
                }
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin(
            {
                title: "Webpack paso a paso",
                template: "src/index.html"
            }
        )
    ]
}


Ya con esto, cada vez que se compile, Webpack generara el archivo index.html de acuerdo a la plantilla, generara el archivo .js indicando el hash y inyectara la ruta correcta automáticamente

Referencia:
Todo esto lo aprendí de Midudev
https://www.youtube.com/watch?v=ansUGkcrhwY&t=3004s

Publicar aplicación React en IIS

Es necesario instalar Url rewite Aquí en IIS, esto es por el tema de que las rutas en React, bien sea con wouter o react router, son administradas en el cliente, pero si el usuario llega a entrar a una ruta tipando directamente en el navegador, se debe indicar a IIS como debe tratarla, para esto se instala Url rewriter y se configura el archivo web.config con lo siguiente, esta configuración lo que le dice a IIS es que todas las peticiones las debe enviar al archivos index.html

<rewrite>
 <rules>
     <rule name="Rewrite Text Requests" stopProcessing="true">
         <match url=".*" />
             <conditions>
                        <add input="{HTTP_METHOD}" pattern="^GET$" />
                        <add input="{HTTP_ACCEPT}" pattern="^text/html" />
                        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
             </conditions>
             <action type="Rewrite" url="index.html" />
     </rule>
</rules>
 </rewrite>

En esta parte <action type="Rewrite" url="index.html"/> debes prestar atención, generalmente las rutas por lo menos con wouter estan en la raíz del sitio, por lo que debes hacer la referencia al archivos index.html correcta

Algo que me ocurrio es que tenia publicada una API de NetCore dentro del directorio virtual donde estaba publicada la aplicación de React y Url rewrite comenzo a redirigir las peticiones al index.html y hacia que fallara la api, asi es que tuve que adicionar otra regla dentro de conditions que omitiera la carpeta de la API, de esta manera

	 <add input="{REQUEST_URI}" pattern="^/Api/" negate="true"/>