|
|
|
# WP React
|
|
|
|
|
|
|
|
_Solució per injectar components de React al DOM generat per WordPress sense necessitat d'incrustació via `<iframe />`_.
|
|
|
|
|
|
|
|
Amb aquesta funcionalitat es vol aconseguir un mètode per reutilitzar parts de la lògica de la aplicació frontend fora del seu context. En concret, **ha de permetre reutilitzar els components de React encarregats dels fluxes de contractació i d'alta de sòcia sense necessitat de carregar tota l'aplicació a través d'un iframe**. Amb aquesta metodologia aconseguim una optimització dels temps de càrrega de la pàgina web de Som Connexió alhora que simplifiquem la gestió de maquetació de les pàgines d'alta i de contractació, que deixaran d'estar segmentades en dos contextos independents (dom i iframe) amb totes les dificultats de sincronització que això comporta, a més de reduir la complexitat dels sistemes d'analítiques per fer seguiment dels _leads_ ja que tot el procés es donarà sota el domini píublic de la pàgina web [somconnexio.coop](https://somconnexio.coop).
|
|
|
|
|
|
|
|
## React
|
|
|
|
|
|
|
|
**React es una llibreria de JavaScript per construir interfícies d'usuari**. React no és un _framework_ –ni tan sols és una llibreria acotada a la web. S'utilitza de la ma d'altres llibreries encarregades de renderitzar els seus components en funció de l'entorn. Per exemple, [React Native](https://reactnative.dev/) pot ser utilitzat per desenvolupar aplicacions mòbils.
|
|
|
|
|
|
|
|
**Quan desenvolupem aplicacions amb React per a l'entorn web fem ús de [ReactDOM](https://react.dev/reference/react#react-dom)**. Sobint, ens referim a React i ReactDOM com a _frameworks_, ja que resolen els mateixos problemes que altres _frameworks_, però el fet que React per si sol no sigui una solució completa per al desenvolupament web i necessiti treballar orquestrat amb altres llibreries és el que ens impedeix considerar-lo com a tal.
|
|
|
|
|
|
|
|
L'objectiu principal de React és el de minimitzar els errors que succeeixen durant el desenvolupament de UIs. Això ho acompleix gràcies a l'ús de components —blocs lògics de codi autoncontinguts que descriuen procions de la interfície d'usuari. Aquests components poden ser conjugats per crear una UI completa abstraient-se del treball de renderització, permetent a la persona desenvolupadora contentrar-se en el disseny de la UI.
|
|
|
|
|
|
|
|
## React DOM
|
|
|
|
|
|
|
|
**El paquet ReactDOM és una llibreria encarregada de gestionar la renderització de components de React en entorns web** representant-los com a elements d'un [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction) o com a HTML.
|
|
|
|
|
|
|
|
La llibrerira ens exposa dos punts d'entrada:
|
|
|
|
|
|
|
|
1. [react-dom/client](https://react.dev/reference/react-dom/client): Aquestes APIs et permet renderitzar components de React a la banda del client (el navegador). Aquestes APIs son utilitzades tipicament a l'arrel del teu arbre de components per iniciar el teu arbre de components i injectar-lo al DOM del navegador.
|
|
|
|
2. [react-dom/server](https://react.dev/reference/react-dom/server): Aquestes APIs et permeten renderitzar components de React com a text HTML a la banda del servidor. Aquestes APIs només s'utilitzen a l'arrel de les aplicacions per transformar l'arbre de components en la seva representacio HTML.
|
|
|
|
|
|
|
|
WP React farà ús de les APIs `react-dom/client` per tal de renderitzar els components de la nostra oficina virtual dins el DOM generat per WordPress.
|
|
|
|
|
|
|
|
### createRoot
|
|
|
|
|
|
|
|
La funció `createRoot` del mòdul `react-dom/client` ens permet crear un node arrel d'on fer penjar els teus components de React dins d'un node DOM. React prendrà el control del DOM descendent del node arrel. Sovint, quan treballem amb React, treballem fent aplicacions gestionades totalment per la llibreria vinculades a un arxiu HTML amb un sol node que ens servira com a arrel per al nostre arbre de components de React, però també és possible delegar en React parts parcials del nostre DOM i fer ús de la funció `createRoot` múltiples cops en una mateixa pàgina per mostrar components de react.
|
|
|
|
|
|
|
|
A continuació es mostra un exemple de com podem utilitzar React per gestionar dos parts diferents del DOM, la nevagació i els comentaris, sense que React prengui el control de tot el DOM de la pàgina.
|
|
|
|
|
|
|
|
**index.html**
|
|
|
|
```html
|
|
|
|
<!DOCTYPE html>
|
|
|
|
<html>
|
|
|
|
<head><title>My app</title></head>
|
|
|
|
<body>
|
|
|
|
<nav id="navigation"></nav>
|
|
|
|
<main>
|
|
|
|
<p>This paragraph is not rendered by React (open index.html to verify).</p>
|
|
|
|
<section id="comments"></section>
|
|
|
|
</main>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
```
|
|
|
|
|
|
|
|
**index.js**
|
|
|
|
|
|
|
|
```jsx
|
|
|
|
import './styles.css';
|
|
|
|
import { createRoot } from 'react-dom/client';
|
|
|
|
import { Comments, Navigation } from './Components.js';
|
|
|
|
|
|
|
|
const navDomNode = document.getElementById('navigation');
|
|
|
|
const navRoot = createRoot(navDomNode);
|
|
|
|
navRoot.render(<Navigation />);
|
|
|
|
|
|
|
|
const commentDomNode = document.getElementById('comments');
|
|
|
|
const commentRoot = createRoot(commentDomNode);
|
|
|
|
commentRoot.render(<Comments />);
|
|
|
|
```
|
|
|
|
|
|
|
|
**Components.js**
|
|
|
|
```jsx
|
|
|
|
export function Navigation() {
|
|
|
|
return (
|
|
|
|
<ul>
|
|
|
|
<NavLink href="/">Home</NavLink>
|
|
|
|
<NavLink href="/about">About</NavLink>
|
|
|
|
</ul>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function NavLink({ href, children }) {
|
|
|
|
return (
|
|
|
|
<li>
|
|
|
|
<a href={href}>{children}</a>
|
|
|
|
</li>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function Comments() {
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<h2>Comments</h2>
|
|
|
|
<Comment text="Hello!" author="Sophie" />
|
|
|
|
<Comment text="How are you?" author="Sunil" />
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function Comment({ text, author }) {
|
|
|
|
return (
|
|
|
|
<p>{text} — <i>{author}</i></p>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## Implementació
|
|
|
|
|
|
|
|
Fent ús de les APIs de ReactDOM podem crear nodes arrel per a una aplicació de React i injectar-los dins un DOM preexistent perquè prenguin el control d'una de les seves branques. Com hem vist, això és una solució estandard que utilitza les APIs nadives de React i ReactDOM. El repte és el següent: **Configurar múltiples rutines de transpilació del codi de frontend per tal d'aconseguir encapsular en mòduls distribuibles diferents versions del nostre arbre de components de React amb l'objectiu de reutilitzar la lògica de l'aplicació fora del seu context**. Un cop disposem d'aquests mòduls, haurem de fer que aquests estiguin disponbiles a l'entorn de WordPress perquè aquest pugui carregar-los amb el seu sistema de càrrega de _scripts_ i instanciar-los a les seves pàgines.
|
|
|
|
|
|
|
|
### Transpilació i empaquetament
|
|
|
|
|
|
|
|
El frontend de l'oficina virtual de Som Connexió està creat amb l'utilitat [`create-react-app`](https://create-react-app.dev). Aquesta utilitat ens permet crear entorns de desenvolupament autoconfigurats per a aplicacions de React de tipus _single page applications_. Els entorns de desenvolupament generats per aquesta útilitat gestionen la configuració com un paquet extern actualitzable amb el sistema de dependències de npm. D'aquesta forma, els equips de desenvolupament externalitzen la necessitat de gestió i manteniment de la configuració i s'adequen als estandards definits per la utilitat. El resultat és la possibilitat de disposar d'un entorn de desenvolupament llest per ser productiu en questió de segons que implementa els millors estandards a nivell de solucions tecnològiques i sense necessitat de dedicacions destinades al seu manteniment. Com a contrapartida, disposarem d'un entorn de treball que amaga els arxius de configuració i que gestiona de forma externalitzada i no personalitzable les eines de desenvolupament disponibles. Això pot ser problemàtic si no esperes que la teva aplicació de React funcioni com una _single page application_. Aquestes limitacions són les que han portat a la exposició a la web pública dels fluxes de contractació i alta a través de la incrustació via iframe de tota l'aplicació de la oficina virtual sota el subdomini [oficinavirtual.somconnexio.coop](https://oficinavirtual.somconnexio.coop). Amb l'ajuda d'un sistema de rutes vinculades a un sistema d'autenticació dels usuaris, l'oficina virtual disposa de rutes públiques i privades. Les públiques són accessibles sense necessitat d'autenticació, les privades no. Les rutes públiques, que funcionen sense necessitat de sessió d'usuari, són les que es mostren al _iframe_ incrustat a WordPress, les rutes privades són les que donen accés als usuaris a la seva oficina virtual.
|
|
|
|
|
|
|
|
Al abordar la necessitat de diferents rutines de transpilació i empaquetament del nostre codi per tal de generar diferents distribuibles de complexitat variable ens topem amb la impossibilitat de solucionar el problema amb configuracions personalitzades de les eines de compilació. Com s'ha comentat, l'entorn de desenvolupament empreat no permet aquest tipus d'aproximació. La solució ha de donar-se, no a nivell de configuració del sistema de transpilació i empaquetament –[Webpack](https://webpack.js.org/)–, sinó a nivell de codi, on si que tenim autonomia, i des d'allà aconseguir modificar el comportament del transpilador. L'estrategia seguida és la de configurar, a nivell de codi, importacions de mòduls condicionals en base al valor de variables d'entorn.
|
|
|
|
|
|
|
|
> **Variables d'entorn**<br>Una variable d'entorn és un valor definit per l'usuari que pot afectar el funcionament d'un procés dins d'una computadora. Les variables són part de l'entorn en el qual el procés corre. [Wikipedia](https://en.wikipedia.org/wiki/Environment_variable)
|
|
|
|
|
|
|
|
|
|
|
|
#### Exemple d'importació de mòduls condicional en base a variables d'entorn
|
|
|
|
|
|
|
|
```jsx
|
|
|
|
import React from "react";
|
|
|
|
import ReactDOM from "react-dom/client";
|
|
|
|
|
|
|
|
if (process.env.FOO === "bar") {
|
|
|
|
const App = require("./App.js");
|
|
|
|
} else {
|
|
|
|
const App = require("./Forms.js");
|
|
|
|
}
|
|
|
|
|
|
|
|
const root = ReactDOM.createRoot(document.querySelector("#root"));
|
|
|
|
root.render(<App />);
|
|
|
|
```
|
|
|
|
|
|
|
|
Seguint aquesta estratègia i via variables d'entorn podem generar variants de l'arbre de components de React que ens permetin disposar de múltiples paquets distribuibles. Per a resoldre de forma òptima la importació condicional de mòduls fem ús de les APIs de CJS, menys restrictives i que ens permet fer importacions sense la necessitat de ser declarades com a constants a l'inici del nostre codi.
|
|
|
|
|
|
|
|
> **CJS vs ESM**<br/>[CJS](https://nodejs.org/api/modules.html#modules-commonjs-modules), o CommonJS, és el sistema de mòduls propi de node.js. [ESM](https://tc39.es/ecma262/#sec-modules), o ECMAScript Module, és el sistema de mòduls definit per l'especificiació oficial de JavaScript. CJS és anterior a ESM i apareix en el context de node.js per resoldre la necessitat de treballar en un entorn de terminal amb javascript. ESM neix amb una evolució de les especificacions d'ECMAScript per resoldre la necessitat d'incloure un sistema de mòduls definit de forma estàndard per a tots els entorns de JavaScript.
|
|
|
|
|
|
|
|
Un cop resolta la necessitat d'importacions condicionals de mòduls, i per tant, de definició de variants de l'arbre de components de React de la nostra aplicació, s'haurà de definir diferents mòduls, o components, d'applicació. Quan parlem de mòduls d'aplicació fem referència al nivell més alt del nostre arbre de components, el nivell global, o _root_. Des d'aquí es configuren les variables i els estats que han de ser visibles des d'arreu de l'aplicació i els sistemes de rutes que gestionen la visibilitat dels diferents components. En una primera iteració es crearan dos mòduls d'aplicació: `App.js` i `Forms.js`. El mòdul `App.js` contindrà tot l'arbre de components de l'aplicació de l'oficina virtual. El mòdul `Forms.js` importarà només els components necessàris per a resoldre els fluxes de contractació. Cadascuna d'aquestes sub-aplicacions configurarà el seu propi _router_ amb les rutes i vistes així com els diferents mòduls de la _Store_ que necessiti.
|
|
|
|
|
|
|
|
### React Router
|
|
|
|
|
|
|
|
**El frontend de l'oficina virtual de Som Connexió fa ús del paquet [React Router](https://reactrouter.com/en/main) per gestionar l'estat de visibilitat dels seus components.**
|
|
|
|
|
|
|
|
Aquest paquet permet l'enrutament del costat del navegador.
|
|
|
|
|
|
|
|
A les pàgines webs tradicionals, el navegador demana un document al servidor, el descarrega i n'evalua el codi de presentació (CSS) i el d'aplicació (JavaScript), i finalment pinta el contingut (HTML) rebut. Quan l'usuari clica un enllaç, torna a començar tot el procés de nou per a una nova pàgina.
|
|
|
|
|
|
|
|
L'enrutament a la banda del navegador permet a la nostra aplicació actualitzar la URL després que l'usuari faci clic sobre un enllaç sense haver de tornar a fer una petició al servidor en busca d'un nou document. En canvi, l'aplicació pot mostrar de forma immediata un nou contingut i fer una petició de dades al servidor per a actualitzar de forma dinàmica el contingut.
|
|
|
|
|
|
|
|
Gràcies a aquesta funcionalitat, es millora l'experiència d'usuari reduint el temps de resposta del navegador estalviant la necessitat de carregar tot un nou document i la seva conseguent evaluació a cada navegació.
|
|
|
|
|
|
|
|
**Aquest és un aspecte a controlar quan executem l'aplicació en l'entorn de WordPress ja que la gestio de URLs de WordPress colisiona amb la gestió que fa React**. Per una banda, WordPress utilitza el sistema tradicional d'enrutament gestionat desde la banda del servidor: Cada URL representa un recurs únic que el servidor resol i representa en forma d'HTML abans d'enviar-lo de tornada al navegador. React amb sistema d'enrutament, per la seva banda, pren forma d'aplicació JavaScript que es carrega un sol cop, i un cop actiu, pren el control de la URL, intercepta les navegacions, i interpeta els seus canvis com a canvis en el seu estat intern amb el que gestiona la visibilitat dels diferents components.
|
|
|
|
|
|
|
|
Per tal de resoldre això, les versions empaquetades per a ser executades en l'entorn WP hauran de fer ús de la variant [HashRouter](https://reactrouter.com/en/main/router-components/hash-router). Aquesta modalitat de router treballa prenen el control sobre el compononent _hash_ de la URL. Una URL es pot dividir en 5 components:
|
|
|
|
|
|
|
|
# https://somconnexio.coop/tarifes-internet?lng=ca#join
|
|
|
|
# [protocol]://[origin]/[pathname]?[search]#[hash]
|
|
|
|
# ┌---------------------------------------┐┌---------┐
|
|
|
|
# WordPress React
|
|
|
|
|
|
|
|
El sistema d'enrutament de WordPress treballa sobre els components _pathname_ i _search_, però no utilitza el _hash_. Per tal d'aconseguir que els sistema d'enrutament de WordPress i de React convisquin en un mateix context, s'ha de configurar React per treballar sobre el _hash_. Els canvis en el _hash_ de la URL només seran detectats pel router de React i dispararan una navegació en la banda del navegador, els canvis a la resta de la URL dispararan una navegació clàssica que serà gestionada per WordPress.
|
|
|
|
|
|
|
|
El resultat final serà que de les dues versions distribuibles, la pròpia de la oficina virtual configurarà el seu _router_ com un router de URL amb la totalitat de les rutes disponbiles a l'oficina virtual activades, i la versió per a incrustar a WP configurarà el _router_ com un ruter de hash sense rutes privades ni sistema d'autenticació.
|
|
|
|
|
|
|
|
### Configuració HTTP CORS de la API
|
|
|
|
|
|
|
|
L'oficina virtual utilitza les comunicacións [ajax](https://developer.mozilla.org/en-US/docs/Glossary/Ajax) per alimentar de dades el _frontend_. Aquesta comunicació apunta a la REST API de l'oficina virtual implementada amb [Django](https://www.djangoproject.com/), un framework de python per a desenvolupar servidors web. La API està exposada sota el domini [oficinavirtual.somconnexio.coop](https://oficinavirtual.somconnexio.coop), el mateix entorn on està exposat el codi _frontend_ de la oficina virtual. Quan l'usuari consulta la seva oficina virtual accedeix des del navegador al domini, carrega el codi JavaScript i aquest fa peticions de dades a la API que està sota el mateix domini.
|
|
|
|
|
|
|
|
Quan la nostra aplicació s'executi en el context de WP el nostre codi de React s'estarà executant sota el domini de la pàgina web de SomConnexio, [somconnexio.coop](https://somconnexio.coop). Aquest fenòmen no es dona si l'aplicació s'incrusta a la web pública a través d'un _iframe_, ja que aquest, tot i que visualment es mostra integrat a la pàgina, internament funciona com un context web diferents, com si el codi que s'executa dins l'_iframe_ estigués en una pestanya independent en un domini diferent. D'aquest fet se'n deriven dos problemàtiques a tenir en conta:
|
|
|
|
|
|
|
|
1. Configuració de React: En el procés de compilació de React s'injecten, via variables d'entorn, els valors de les URL de les que ha de disposar l'aplicació per accedir a les diferents APIs via connexions http així com gestionar redireccions entre pàgines. Per evitar problemes de links trencats quan l'aplicació s'executi fora del seu domini natural, s'ha de controlar que aquestes variables d'entorn estiguin definides ben definides i permetin a React gestionar bé l'enrutament i les peticions de dades entre els dos dominis.
|
|
|
|
2. Configuració de Django: Django disposa d'un arxiu de configuració on s'indica el domini al qual la nostra aplicació a de servir. Per defecte, qualsevol petició que no s'origini des d'aquest domini i arribi al procés de python és descartada. Aquesta és la configuració per defecte i està dissenyada com a mesura de seguretat per evitar atacs des d'origens desconeguts. Haurem de fer alguns canvis en la configuració de Django perquè l'aplicació reconegui el domini [somconnexio.coop](https://somconnexio.coop) com un domini vàlid i permeti a React accedir a la REST API des d'aquest domini sense generar problemes permisos CORS (Cros Origin Resource Sharing).
|
|
|
|
|
|
|
|
Per resoldre el primer dels punts, haurem de crear una nova variable d'entorn: `REACT_APP_API_URL`. Aquesta variable serà l'encarregada d'informar a l'aplicació de React quin és el domini base al qual haurà d'apuntar per fer les peticions de dades corresponents. Fins ara, el domini sota el que corria l'aplicació de React i el domini sota el qual s'exposava la API eren sempre el mateix i aquest valor es podia inferir per context. En el nou escenari, això s'haura d'informar de forma explícita per permetre el sistema CORS.
|
|
|
|
|
|
|
|
Per tal de modificar la configuració de Django i permetre múltiples dominis com a origens vàlids, farem ús de la llibreria [django-cors-headers](https://pypi.org/project/django-cors-headers/). Aquesta és un paquet de python que permet a Django incloure capçaleres CORS a les seves respostes. Un cop instal·lada i integrada a l'aplicació de Django, haurem de configurar-la perquè reconegui el domini de somconnexio.coop com a vàlid.
|
|
|
|
|
|
|
|
```python
|
|
|
|
# file: somoffice/settings.py
|
|
|
|
CORS_ORIGIN_WHITELIST = [
|
|
|
|
"https://somconnexio.coop",
|
|
|
|
"https://somosconexion.coop"
|
|
|
|
]
|
|
|
|
```
|
|
|
|
|
|
|
|
### WordPress
|
|
|
|
|
|
|
|
Un cop configurada l'aplicació de React per poder generar diferents paquests distribuibles, **haurem d'implementar al WordPress de Som Connexió la lògica que permeti carregar els scrits generats i instanciar l'aplicació a les seves pàgines**.
|
|
|
|
|
|
|
|
Per tal d'acomplir això s'ha generat un petit plugin amb PHP. El codi del plugin es pot trobar en aquest [repositori de gitlab](https://git.coopdevs.org/coopdevs/som-connexio/oficina-virtual/wp-react/). Aquest plugin implementa, a l'entorn de WordPress, les següents funcionalitats:
|
|
|
|
|
|
|
|
1. **Un endpoint per desplegar els scripts transpilats de React i allotjar-los al sistema d'arxius de la pàgina web**. Aquest endpoint ens permet automatitzar les rutines de publicació de canvis a l'aplicació de l'oficina virtual a l'entorn de WordPress. WordPress espera una connexió de tipus POST amb un contingut codificat com a `multipart/form-data`. Aquest tipus de codificació ens permet barrejar al cos de la petició camps de tipus text (les credencials) i camps de tipus binari (els arxius). Dins el cos de la petició hauran de venir definits dos camps per a les credencials, l'usuari i la password, i un nombre variable d'arxius javascript. L'endpoint està protegit amb una capa d'autenticació que treball sobre el sitema d'usuaris intern de WordPress i utilitzarà el xifrat SSL de les connexions HTTPS per securitzar les credencials que s'enviin. Un cop validades les credencials contra el sistema d'usuaris de WordPress, el sistema netejarà la ruta on emmagatzema els scripts de React i els substituirà pels arxius adjuntants al cos de la connexió.
|
|
|
|
|
|
|
|
```sh
|
|
|
|
curl \
|
|
|
|
-F username=wpuser \
|
|
|
|
-F password=wppass \
|
|
|
|
-F app=@main.179b493c.chunk.js \
|
|
|
|
-F runtime=@runtime-main.cc85cbej.js \
|
|
|
|
-F chunk=@2.608ccbe5.js \
|
|
|
|
https://somconnexio.coop?wp_react_upload=1
|
|
|
|
```
|
|
|
|
|
|
|
|
2. **Un [shortcode](https://developer.wordpress.org/plugins/shortcodes/) per a la injecció del node arrel de l'aplicació així com la càrrega dinàmica de scripts**. A l'entorn WordPress, un _shortcode_ és una macro que s'utilitza per generar interaccions dinàmiques amb el contingut. p.e. creant galeries d'imàtges vinculades a un post o renderitzant un video. En el nostre cas, el _shortcode_ permetrà a l'usuari fer-ne ús des de l'editor de WordPress per que en el moment de renderitzat de les pàgines, WordPress substitueixi la macro del _shortcode_ per l'element HTML que farà d'arrel de la nostra aplicació alhora que s'informa a WordPress de la necessitat de carregar el _script_ amb el codi de React transpilat i el script de muntatge.
|
|
|
|
|
|
|
|
3. **Un script de javascript encarregat de muntar l'aplicació al DOM en temps d'execució**. Aquest script exposa a, a l'entorn javascript vinculat a la pàgina de WordPress en que s'estigui fent ús de l'aplicació de React, un objecte global anomenat **wpReact**. Aquest objecte té la següent interfície `wpReact = { startApp: ({ el: HTMLNode, route: String }) => {} }`. El mètode `startApp` de l'objecte ha de ser sobreescrit per React en temps d'execució i definir-lo com a funció de muntatge de l'aplicació. El script s'encarregarà de fer de proxy, a través d'aquest mètode, per tal de fer arribar a React el node arrel i la ruta del router a mostrar així com d'actualitzar el component _hash_ de la url per preconfigurar-lo abans d'inicialitzar el ruter de React.
|
|
|
|
|
|
|
|
```jsx
|
|
|
|
window.wpReact.startApp = ({ el }) => {
|
|
|
|
ReactDOM.render(
|
|
|
|
<HashRouter>
|
|
|
|
<App />
|
|
|
|
</HashRouter>,
|
|
|
|
el
|
|
|
|
);
|
|
|
|
};
|
|
|
|
```
|
|
|
|
|
|
|
|
## Automatització i integració contínua
|
|
|
|
|
|
|
|
Per a la transpilació automatitzada i el desplegament dels diferents paquets distribuibles farem ús del [sistema d'integració continua de Gitlab](https://about.gitlab.com/topics/ci-cd/). El repositori de l'aplicació ja disposa d'aquest sistema configurat per a l'automatització dels tests funcionals. La nova configuració implementa una nova _pipeline_ que automatiza tot el cicle de publicació de les versions de codi. **Aquesta pipeline anirà vinculada a la creació de nous _tags_**, el sistema de versionat del repositori git, de forma que s'activi cada cop que una nova versió de codi es publiqui. Per a cada _tag_ es farà disponible una _pipeline_ encarregada d'executar, sobre la versió de codi concreta vinculada al _tag_, el procés de test, transpilació, publicació com a paquet i, de forma opcional, desplegament automàtic a l'entorn de WordPress.
|
|
|
|
|
|
|
|
Els runners de gitlab disposaran de configuració variable en funció de l'entorn per al que s'estigui executant el procés: _staging_ o _productiu_. Per a aquesta configuració variable es defineixen, dins el sistema de CI/CD de Gitlab, els mateixos entorns, _staging_ i _production_, i es fa ús de [les variables d'entorn](https://docs.gitlab.com/ee/ci/variables/) vinculades a un o altre. Aquestes variables d'entorn s'encarregan de modificar el comportament de la _pipeline_ per ajustar-lo als requeriments variables entre entorns. En panell de configuració de variables d'entorn d'un repositori gitlab el podem trobar a [settings > CI/CD > Variables](https://git.coopdevs.org/coopdevs/som-connexio/oficina-virtual/somoffice/-/settings/ci_cd).
|
|
|
|
|
|
|
|
Per últim, s'utilita un sistema de reconeixement de patrons sobre el text del _tag_ per apuntar a un o altre entorn. Els _tags_ del projecte segueixen el patró `v?[0-9]+[.][0-9]+([.][0-9]+)?([-a-z])*`. **Els _tags_ sense sufix s'entenen com a marcas de versió productives, els _tags_ amb el sufix `-staging` s'entenen com a marques de versions per a l'entorn de proves**. La creació de nous _tags_ dispararà l'activació de la _pipeline_ vinculada a un o altre entorn.
|
|
|
|
|
|
|
|
La nova pipeline estarà estructurada en una cadena de _jobs_ que segueix el següent fluxe: `test > build > package > upload > release > deploy`. L'últim _job_ de la _pipeline_ és el de desplegament. Aprofitant el sistema de _runners_, s'ha definit una rutina que permet automatizar el desplegament del codi des de Gitlab. Aquest requereix que el procés d'execució conegui la URL on fer la petició i les credencials d'un usuari al sistema de WordPress. Amb aquesta informació accessible a través de les variables d'entorn, **el procés es capaç de connectar-se a WordPress a través d'una connexio HTTP i desplegar l'última versió de codi transpilat**.
|
|
|
|
|
|
|
|
## Conflictes d'estils entre WP i MUI
|
|
|
|
|
|
|
|
En el nou escenari, React pren el control d'una part parcial del DOM generat per WordPress i renderitza els seus components en ell. Aquesta nova estratègia d'integració entre la web pública i els formularis de la oficina virtual genera un efecte col·lateral que s'ha de controlar a nivell d'estils CSS: **Col·lisions entre els estisl CSS de WordPress i de React**.
|
|
|
|
|
|
|
|
WordPress disposa del seus fulls d'estils que defineixen el comportament visual dels diferents elements HTML de la pàgina. React utilitza una llibreria de components UI –[MUI](https://mui.com/)– que ofereix components amb estils CSS preconfigurats per tal de facilitar i estandarditzar les tàsques de maquetació de l'equip de desenvolupament. Quan fem conviure en un mateix DOM els dos sistemes d'estils CSS ens trobem amb alguns conflictes respecte a com una autoritat i l'altre imposen la representació visual dels elements HTML. Una solució basada en _iframes_ evita aquesta col·lisió al presentar visualment un contingut unificat gestionat internament com dos DOMs independents. Un cop descartada aquesta solució, fa falta resoldre manualment aquests conflictes.
|
|
|
|
|
|
|
|
La solució a implementar és la de extendre els fulls d'estils de WordPress per incloure-hi excepcions a les seves directives que resolguin els conflictes de maquetació que s'identifiquin. Per permetre la definició d'aquestes excepcions de forma senzilla, el plugin PHP desenvolupat identifica el node arrel de la aplicació React amb l'ID `#wp-react-root`. Sabem que tots els components de React injectats a través del nou sistema viuran sempre sota un element HTML amb aquest ID. Sabem també que els estils CSS funcionen en cascada jerarquitzada en funció de la [especificitat](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity) de les diferents directrius d'estil, i que una directriu basada en un ID sempre té més especificitat que la resta. Així, fent servir l'ID `#wp-react-root` com a component dels selectors CSS ens és fàcil definir directrius d'estils que sobreescriguin el comportament per defecte de les fulles d'estil de WordPress alhora que presentin un domini acotat a la branca del DOM gestionada per React.
|
|
|
|
|
|
|
|
# WP React
|
|
|
|
|
|
|
|
_Solució per injectar components de React al DOM generat per WordPress sense necessitat d'incrustació via `<iframe />`_.
|
|
|
|
|
|
|
|
Amb aquesta funcionalitat es vol aconseguir un mètode per reutilitzar parts de la lògica de la aplicació frontend fora del seu context. En concret, **ha de permetre reutilitzar els components de React encarregats dels fluxes de contractació i d'alta de sòcia sense necessitat de carregar tota l'aplicació a través d'un iframe**. Amb aquesta metodologia aconseguim una optimització dels temps de càrrega de la pàgina web de Som Connexió alhora que simplifiquem la gestió de maquetació de les pàgines d'alta i de contractació, que deixaran d'estar segmentades en dos contextos independents (dom i iframe) amb totes les dificultats de sincronització que això comporta, a més de reduir la complexitat dels sistemes d'analítiques per fer seguiment dels _leads_ ja que tot el procés es donarà sota el domini píublic de la pàgina web [somconnexio.coop](https://somconnexio.coop).
|
|
|
|
|
|
|
|
## React
|
|
|
|
|
|
|
|
**React es una llibreria de JavaScript per construir interfícies d'usuari**. React no és un _framework_ –ni tan sols és una llibreria acotada a la web. S'utilitza de la ma d'altres llibreries encarregades de renderitzar els seus components en funció de l'entorn. Per exemple, [React Native](https://reactnative.dev/) pot ser utilitzat per desenvolupar aplicacions mòbils.
|
|
|
|
|
|
|
|
**Quan desenvolupem aplicacions amb React per a l'entorn web fem ús de [ReactDOM](https://react.dev/reference/react#react-dom)**. Sobint, ens referim a React i ReactDOM com a _frameworks_, ja que resolen els mateixos problemes que altres _frameworks_, però el fet que React per si sol no sigui una solució completa per al desenvolupament web i necessiti treballar orquestrat amb altres llibreries és el que ens impedeix considerar-lo com a tal.
|
|
|
|
|
|
|
|
L'objectiu principal de React és el de minimitzar els errors que succeeixen durant el desenvolupament de UIs. Això ho acompleix gràcies a l'ús de components —blocs lògics de codi autoncontinguts que descriuen procions de la interfície d'usuari. Aquests components poden ser conjugats per crear una UI completa abstraient-se del treball de renderització, permetent a la persona desenvolupadora contentrar-se en el disseny de la UI.
|
|
|
|
|
|
|
|
## React DOM
|
|
|
|
|
|
|
|
**El paquet ReactDOM és una llibreria encarregada de gestionar la renderització de components de React en entorns web** representant-los com a elements d'un [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction) o com a HTML.
|
|
|
|
|
|
|
|
La llibrerira ens exposa dos punts d'entrada:
|
|
|
|
|
|
|
|
1. [react-dom/client](https://react.dev/reference/react-dom/client): Aquestes APIs et permet renderitzar components de React a la banda del client (el navegador). Aquestes APIs son utilitzades tipicament a l'arrel del teu arbre de components per iniciar el teu arbre de components i injectar-lo al DOM del navegador.
|
|
|
|
2. [react-dom/server](https://react.dev/reference/react-dom/server): Aquestes APIs et permeten renderitzar components de React com a text HTML a la banda del servidor. Aquestes APIs només s'utilitzen a l'arrel de les aplicacions per transformar l'arbre de components en la seva representacio HTML.
|
|
|
|
|
|
|
|
WP React farà ús de les APIs `react-dom/client` per tal de renderitzar els components de la nostra oficina virtual dins el DOM generat per WordPress.
|
|
|
|
|
|
|
|
### createRoot
|
|
|
|
|
|
|
|
La funció `createRoot` del mòdul `react-dom/client` ens permet crear un node arrel d'on fer penjar els teus components de React dins d'un node DOM. React prendrà el control del DOM descendent del node arrel. Sovint, quan treballem amb React, treballem fent aplicacions gestionades totalment per la llibreria vinculades a un arxiu HTML amb un sol node que ens servira com a arrel per al nostre arbre de components de React, però també és possible delegar en React parts parcials del nostre DOM i fer ús de la funció `createRoot` múltiples cops en una mateixa pàgina per mostrar components de react.
|
|
|
|
|
|
|
|
A continuació es mostra un exemple de com podem utilitzar React per gestionar dos parts diferents del DOM, la nevagació i els comentaris, sense que React prengui el control de tot el DOM de la pàgina.
|
|
|
|
|
|
|
|
**index.html**
|
|
|
|
```html
|
|
|
|
<!DOCTYPE html>
|
|
|
|
<html>
|
|
|
|
<head><title>My app</title></head>
|
|
|
|
<body>
|
|
|
|
<nav id="navigation"></nav>
|
|
|
|
<main>
|
|
|
|
<p>This paragraph is not rendered by React (open index.html to verify).</p>
|
|
|
|
<section id="comments"></section>
|
|
|
|
</main>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
```
|
|
|
|
|
|
|
|
**index.js**
|
|
|
|
|
|
|
|
```jsx
|
|
|
|
import './styles.css';
|
|
|
|
import { createRoot } from 'react-dom/client';
|
|
|
|
import { Comments, Navigation } from './Components.js';
|
|
|
|
|
|
|
|
const navDomNode = document.getElementById('navigation');
|
|
|
|
const navRoot = createRoot(navDomNode);
|
|
|
|
navRoot.render(<Navigation />);
|
|
|
|
|
|
|
|
const commentDomNode = document.getElementById('comments');
|
|
|
|
const commentRoot = createRoot(commentDomNode);
|
|
|
|
commentRoot.render(<Comments />);
|
|
|
|
```
|
|
|
|
|
|
|
|
**Components.js**
|
|
|
|
```jsx
|
|
|
|
export function Navigation() {
|
|
|
|
return (
|
|
|
|
<ul>
|
|
|
|
<NavLink href="/">Home</NavLink>
|
|
|
|
<NavLink href="/about">About</NavLink>
|
|
|
|
</ul>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function NavLink({ href, children }) {
|
|
|
|
return (
|
|
|
|
<li>
|
|
|
|
<a href={href}>{children}</a>
|
|
|
|
</li>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function Comments() {
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<h2>Comments</h2>
|
|
|
|
<Comment text="Hello!" author="Sophie" />
|
|
|
|
<Comment text="How are you?" author="Sunil" />
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function Comment({ text, author }) {
|
|
|
|
return (
|
|
|
|
<p>{text} — <i>{author}</i></p>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## Implementació
|
|
|
|
|
|
|
|
Fent ús de les APIs de ReactDOM podem crear nodes arrel per a una aplicació de React i injectar-los dins un DOM preexistent perquè prenguin el control d'una de les seves branques. Com hem vist, això és una solució estandard que utilitza les APIs nadives de React i ReactDOM. El repte és el següent: **Configurar múltiples rutines de transpilació del codi de frontend per tal d'aconseguir encapsular en mòduls distribuibles diferents versions del nostre arbre de components de React amb l'objectiu de reutilitzar la lògica de l'aplicació fora del seu context**. Un cop disposem d'aquests mòduls, haurem de fer que aquests estiguin disponbiles a l'entorn de WordPress perquè aquest pugui carregar-los amb el seu sistema de càrrega de _scripts_ i instanciar-los a les seves pàgines.
|
|
|
|
|
|
|
|
### Transpilació i empaquetament
|
|
|
|
|
|
|
|
El frontend de l'oficina virtual de Som Connexió està creat amb l'utilitat [`create-react-app`](https://create-react-app.dev). Aquesta utilitat ens permet crear entorns de desenvolupament autoconfigurats per a aplicacions de React de tipus _single page applications_. Els entorns de desenvolupament generats per aquesta útilitat gestionen la configuració com un paquet extern actualitzable amb el sistema de dependències de npm. D'aquesta forma, els equips de desenvolupament externalitzen la necessitat de gestió i manteniment de la configuració i s'adequen als estandards definits per la utilitat. El resultat és la possibilitat de disposar d'un entorn de desenvolupament llest per ser productiu en questió de segons que implementa els millors estandards a nivell de solucions tecnològiques i sense necessitat de dedicacions destinades al seu manteniment. Com a contrapartida, disposarem d'un entorn de treball que amaga els arxius de configuració i que gestiona de forma externalitzada i no personalitzable les eines de desenvolupament disponibles. Això pot ser problemàtic si no esperes que la teva aplicació de React funcioni com una _single page application_. Aquestes limitacions són les que han portat a la exposició a la web pública dels fluxes de contractació i alta a través de la incrustació via iframe de tota l'aplicació de la oficina virtual sota el subdomini [oficinavirtual.somconnexio.coop](https://oficinavirtual.somconnexio.coop). Amb l'ajuda d'un sistema de rutes vinculades a un sistema d'autenticació dels usuaris, l'oficina virtual disposa de rutes públiques i privades. Les públiques són accessibles sense necessitat d'autenticació, les privades no. Les rutes públiques, que funcionen sense necessitat de sessió d'usuari, són les que es mostren al _iframe_ incrustat a WordPress, les rutes privades són les que donen accés als usuaris a la seva oficina virtual.
|
|
|
|
|
|
|
|
Al abordar la necessitat de diferents rutines de transpilació i empaquetament del nostre codi per tal de generar diferents distribuibles de complexitat variable ens topem amb la impossibilitat de solucionar el problema amb configuracions personalitzades de les eines de compilació. Com s'ha comentat, l'entorn de desenvolupament empreat no permet aquest tipus d'aproximació. La solució ha de donar-se, no a nivell de configuració del sistema de transpilació i empaquetament –[Webpack](https://webpack.js.org/)–, sinó a nivell de codi, on si que tenim autonomia, i des d'allà aconseguir modificar el comportament del transpilador. L'estrategia seguida és la de configurar, a nivell de codi, importacions de mòduls condicionals en base al valor de variables d'entorn.
|
|
|
|
|
|
|
|
> **Variables d'entorn**<br>Una variable d'entorn és un valor definit per l'usuari que pot afectar el funcionament d'un procés dins d'una computadora. Les variables són part de l'entorn en el qual el procés corre. [Wikipedia](https://en.wikipedia.org/wiki/Environment_variable)
|
|
|
|
|
|
|
|
|
|
|
|
#### Exemple d'importació de mòduls condicional en base a variables d'entorn
|
|
|
|
|
|
|
|
```jsx
|
|
|
|
import React from "react";
|
|
|
|
import ReactDOM from "react-dom/client";
|
|
|
|
|
|
|
|
if (process.env.FOO === "bar") {
|
|
|
|
const App = require("./App.js");
|
|
|
|
} else {
|
|
|
|
const App = require("./Forms.js");
|
|
|
|
}
|
|
|
|
|
|
|
|
const root = ReactDOM.createRoot(document.querySelector("#root"));
|
|
|
|
root.render(<App />);
|
|
|
|
```
|
|
|
|
|
|
|
|
Seguint aquesta estratègia i via variables d'entorn podem generar variants de l'arbre de components de React que ens permetin disposar de múltiples paquets distribuibles. Per a resoldre de forma òptima la importació condicional de mòduls fem ús de les APIs de CJS, menys restrictives i que ens permet fer importacions sense la necessitat de ser declarades com a constants a l'inici del nostre codi.
|
|
|
|
|
|
|
|
> **CJS vs ESM**<br/>[CJS](https://nodejs.org/api/modules.html#modules-commonjs-modules), o CommonJS, és el sistema de mòduls propi de node.js. [ESM](https://tc39.es/ecma262/#sec-modules), o ECMAScript Module, és el sistema de mòduls definit per l'especificiació oficial de JavaScript. CJS és anterior a ESM i apareix en el context de node.js per resoldre la necessitat de treballar en un entorn de terminal amb javascript. ESM neix amb una evolució de les especificacions d'ECMAScript per resoldre la necessitat d'incloure un sistema de mòduls definit de forma estàndard per a tots els entorns de JavaScript.
|
|
|
|
|
|
|
|
Un cop resolta la necessitat d'importacions condicionals de mòduls, i per tant, de definició de variants de l'arbre de components de React de la nostra aplicació, s'haurà de definir diferents mòduls, o components, d'applicació. Quan parlem de mòduls d'aplicació fem referència al nivell més alt del nostre arbre de components, el nivell global, o _root_. Des d'aquí es configuren les variables i els estats que han de ser visibles des d'arreu de l'aplicació i els sistemes de rutes que gestionen la visibilitat dels diferents components. En una primera iteració es crearan dos mòduls d'aplicació: `App.js` i `Forms.js`. El mòdul `App.js` contindrà tot l'arbre de components de l'aplicació de l'oficina virtual. El mòdul `Forms.js` importarà només els components necessàris per a resoldre els fluxes de contractació. Cadascuna d'aquestes sub-aplicacions configurarà el seu propi _router_ amb les rutes i vistes així com els diferents mòduls de la _Store_ que necessiti.
|
|
|
|
|
|
|
|
### React Router
|
|
|
|
|
|
|
|
**El frontend de l'oficina virtual de Som Connexió fa ús del paquet [React Router](https://reactrouter.com/en/main) per gestionar l'estat de visibilitat dels seus components.**
|
|
|
|
|
|
|
|
Aquest paquet permet l'enrutament del costat del navegador.
|
|
|
|
|
|
|
|
A les pàgines webs tradicionals, el navegador demana un document al servidor, el descarrega i n'evalua el codi de presentació (CSS) i el d'aplicació (JavaScript), i finalment pinta el contingut (HTML) rebut. Quan l'usuari clica un enllaç, torna a començar tot el procés de nou per a una nova pàgina.
|
|
|
|
|
|
|
|
L'enrutament a la banda del navegador permet a la nostra aplicació actualitzar la URL després que l'usuari faci clic sobre un enllaç sense haver de tornar a fer una petició al servidor en busca d'un nou document. En canvi, l'aplicació pot mostrar de forma immediata un nou contingut i fer una petició de dades al servidor per a actualitzar de forma dinàmica el contingut.
|
|
|
|
|
|
|
|
Gràcies a aquesta funcionalitat, es millora l'experiència d'usuari reduint el temps de resposta del navegador estalviant la necessitat de carregar tot un nou document i la seva conseguent evaluació a cada navegació.
|
|
|
|
|
|
|
|
**Aquest és un aspecte a controlar quan executem l'aplicació en l'entorn de WordPress ja que la gestio de URLs de WordPress colisiona amb la gestió que fa React**. Per una banda, WordPress utilitza el sistema tradicional d'enrutament gestionat desde la banda del servidor: Cada URL representa un recurs únic que el servidor resol i representa en forma d'HTML abans d'enviar-lo de tornada al navegador. React amb sistema d'enrutament, per la seva banda, pren forma d'aplicació JavaScript que es carrega un sol cop, i un cop actiu, pren el control de la URL, intercepta les navegacions, i interpeta els seus canvis com a canvis en el seu estat intern amb el que gestiona la visibilitat dels diferents components.
|
|
|
|
|
|
|
|
Per tal de resoldre això, les versions empaquetades per a ser executades en l'entorn WP hauran de fer ús de la variant [HashRouter](https://reactrouter.com/en/main/router-components/hash-router). Aquesta modalitat de router treballa prenen el control sobre el compononent _hash_ de la URL. Una URL es pot dividir en 5 components:
|
|
|
|
|
|
|
|
# https://somconnexio.coop/tarifes-internet?lng=ca#join
|
|
|
|
# [protocol]://[origin]/[pathname]?[search]#[hash]
|
|
|
|
# ┌---------------------------------------┐┌---------┐
|
|
|
|
# WordPress React
|
|
|
|
|
|
|
|
El sistema d'enrutament de WordPress treballa sobre els components _pathname_ i _search_, però no utilitza el _hash_. Per tal d'aconseguir que els sistema d'enrutament de WordPress i de React convisquin en un mateix context, s'ha de configurar React per treballar sobre el _hash_. Els canvis en el _hash_ de la URL només seran detectats pel router de React i dispararan una navegació en la banda del navegador, els canvis a la resta de la URL dispararan una navegació clàssica que serà gestionada per WordPress.
|
|
|
|
|
|
|
|
El resultat final serà que de les dues versions distribuibles, la pròpia de la oficina virtual configurarà el seu _router_ com un router de URL amb la totalitat de les rutes disponbiles a l'oficina virtual activades, i la versió per a incrustar a WP configurarà el _router_ com un ruter de hash sense rutes privades ni sistema d'autenticació.
|
|
|
|
|
|
|
|
### Configuració HTTP CORS de la API
|
|
|
|
|
|
|
|
L'oficina virtual utilitza les comunicacións [ajax](https://developer.mozilla.org/en-US/docs/Glossary/Ajax) per alimentar de dades el _frontend_. Aquesta comunicació apunta a la REST API de l'oficina virtual implementada amb [Django](https://www.djangoproject.com/), un framework de python per a desenvolupar servidors web. La API està exposada sota el domini [oficinavirtual.somconnexio.coop](https://oficinavirtual.somconnexio.coop), el mateix entorn on està exposat el codi _frontend_ de la oficina virtual. Quan l'usuari consulta la seva oficina virtual accedeix des del navegador al domini, carrega el codi JavaScript i aquest fa peticions de dades a la API que està sota el mateix domini.
|
|
|
|
|
|
|
|
Quan la nostra aplicació s'executi en el context de WP el nostre codi de React s'estarà executant sota el domini de la pàgina web de SomConnexio, [somconnexio.coop](https://somconnexio.coop). Aquest fenòmen no es dona si l'aplicació s'incrusta a la web pública a través d'un _iframe_, ja que aquest, tot i que visualment es mostra integrat a la pàgina, internament funciona com un context web diferents, com si el codi que s'executa dins l'_iframe_ estigués en una pestanya independent en un domini diferent. D'aquest fet se'n deriven dos problemàtiques a tenir en conta:
|
|
|
|
|
|
|
|
1. Configuració de React: En el procés de compilació de React s'injecten, via variables d'entorn, els valors de les URL de les que ha de disposar l'aplicació per accedir a les diferents APIs via connexions http així com gestionar redireccions entre pàgines. Per evitar problemes de links trencats quan l'aplicació s'executi fora del seu domini natural, s'ha de controlar que aquestes variables d'entorn estiguin definides ben definides i permetin a React gestionar bé l'enrutament i les peticions de dades entre els dos dominis.
|
|
|
|
2. Configuració de Django: Django disposa d'un arxiu de configuració on s'indica el domini al qual la nostra aplicació a de servir. Per defecte, qualsevol petició que no s'origini des d'aquest domini i arribi al procés de python és descartada. Aquesta és la configuració per defecte i està dissenyada com a mesura de seguretat per evitar atacs des d'origens desconeguts. Haurem de fer alguns canvis en la configuració de Django perquè l'aplicació reconegui el domini [somconnexio.coop](https://somconnexio.coop) com un domini vàlid i permeti a React accedir a la REST API des d'aquest domini sense generar problemes permisos CORS (Cros Origin Resource Sharing).
|
|
|
|
|
|
|
|
Per resoldre el primer dels punts, haurem de crear una nova variable d'entorn: `REACT_APP_API_URL`. Aquesta variable serà l'encarregada d'informar a l'aplicació de React quin és el domini base al qual haurà d'apuntar per fer les peticions de dades corresponents. Fins ara, el domini sota el que corria l'aplicació de React i el domini sota el qual s'exposava la API eren sempre el mateix i aquest valor es podia inferir per context. En el nou escenari, això s'haura d'informar de forma explícita per permetre el sistema CORS.
|
|
|
|
|
|
|
|
Per tal de modificar la configuració de Django i permetre múltiples dominis com a origens vàlids, farem ús de la llibreria [django-cors-headers](https://pypi.org/project/django-cors-headers/). Aquesta és un paquet de python que permet a Django incloure capçaleres CORS a les seves respostes. Un cop instal·lada i integrada a l'aplicació de Django, haurem de configurar-la perquè reconegui el domini de somconnexio.coop com a vàlid.
|
|
|
|
|
|
|
|
```python
|
|
|
|
# file: somoffice/settings.py
|
|
|
|
CORS_ORIGIN_WHITELIST = [
|
|
|
|
"https://somconnexio.coop",
|
|
|
|
"https://somosconexion.coop"
|
|
|
|
]
|
|
|
|
```
|
|
|
|
|
|
|
|
### WordPress
|
|
|
|
|
|
|
|
Un cop configurada l'aplicació de React per poder generar diferents paquests distribuibles, **haurem d'implementar al WordPress de Som Connexió la lògica que permeti carregar els scrits generats i instanciar l'aplicació a les seves pàgines**.
|
|
|
|
|
|
|
|
Per tal d'acomplir això s'ha generat un petit plugin amb PHP. El codi del plugin es pot trobar en aquest [repositori de gitlab](https://git.coopdevs.org/coopdevs/som-connexio/oficina-virtual/wp-react/). Aquest plugin implementa, a l'entorn de WordPress, les següents funcionalitats:
|
|
|
|
|
|
|
|
1. **Un endpoint per desplegar els scripts transpilats de React i allotjar-los al sistema d'arxius de la pàgina web**. Aquest endpoint ens permet automatitzar les rutines de publicació de canvis a l'aplicació de l'oficina virtual a l'entorn de WordPress. WordPress espera una connexió de tipus POST amb un contingut codificat com a `multipart/form-data`. Aquest tipus de codificació ens permet barrejar al cos de la petició camps de tipus text (les credencials) i camps de tipus binari (els arxius). Dins el cos de la petició hauran de venir definits dos camps per a les credencials, l'usuari i la password, i un nombre variable d'arxius javascript. L'endpoint està protegit amb una capa d'autenticació que treball sobre el sitema d'usuaris intern de WordPress i utilitzarà el xifrat SSL de les connexions HTTPS per securitzar les credencials que s'enviin. Un cop validades les credencials contra el sistema d'usuaris de WordPress, el sistema netejarà la ruta on emmagatzema els scripts de React i els substituirà pels arxius adjuntants al cos de la connexió.
|
|
|
|
|
|
|
|
```sh
|
|
|
|
curl \
|
|
|
|
-F username=wpuser \
|
|
|
|
-F password=wppass \
|
|
|
|
-F app=@main.179b493c.chunk.js \
|
|
|
|
-F runtime=@runtime-main.cc85cbej.js \
|
|
|
|
-F chunk=@2.608ccbe5.js \
|
|
|
|
https://somconnexio.coop?wp_react_upload=1
|
|
|
|
```
|
|
|
|
|
|
|
|
2. **Un [shortcode](https://developer.wordpress.org/plugins/shortcodes/) per a la injecció del node arrel de l'aplicació així com la càrrega dinàmica de scripts**. A l'entorn WordPress, un _shortcode_ és una macro que s'utilitza per generar interaccions dinàmiques amb el contingut. p.e. creant galeries d'imàtges vinculades a un post o renderitzant un video. En el nostre cas, el _shortcode_ permetrà a l'usuari fer-ne ús des de l'editor de WordPress per que en el moment de renderitzat de les pàgines, WordPress substitueixi la macro del _shortcode_ per l'element HTML que farà d'arrel de la nostra aplicació alhora que s'informa a WordPress de la necessitat de carregar el _script_ amb el codi de React transpilat i el script de muntatge.
|
|
|
|
|
|
|
|
3. **Un script de javascript encarregat de muntar l'aplicació al DOM en temps d'execució**. Aquest script exposa a, a l'entorn javascript vinculat a la pàgina de WordPress en que s'estigui fent ús de l'aplicació de React, un objecte global anomenat **wpReact**. Aquest objecte té la següent interfície `wpReact = { startApp: ({ el: HTMLNode, route: String }) => {} }`. El mètode `startApp` de l'objecte ha de ser sobreescrit per React en temps d'execució i definir-lo com a funció de muntatge de l'aplicació. El script s'encarregarà de fer de proxy, a través d'aquest mètode, per tal de fer arribar a React el node arrel i la ruta del router a mostrar així com d'actualitzar el component _hash_ de la url per preconfigurar-lo abans d'inicialitzar el ruter de React.
|
|
|
|
|
|
|
|
```jsx
|
|
|
|
window.wpReact.startApp = ({ el }) => {
|
|
|
|
ReactDOM.render(
|
|
|
|
<HashRouter>
|
|
|
|
<App />
|
|
|
|
</HashRouter>,
|
|
|
|
el
|
|
|
|
);
|
|
|
|
};
|
|
|
|
```
|
|
|
|
|
|
|
|
## Automatització i integració contínua
|
|
|
|
|
|
|
|
Per a la transpilació automatitzada i el desplegament dels diferents paquets distribuibles farem ús del [sistema d'integració continua de Gitlab](https://about.gitlab.com/topics/ci-cd/). El repositori de l'aplicació ja disposa d'aquest sistema configurat per a l'automatització dels tests funcionals. La nova configuració implementa una nova _pipeline_ que automatiza tot el cicle de publicació de les versions de codi. **Aquesta pipeline anirà vinculada a la creació de nous _tags_**, el sistema de versionat del repositori git, de forma que s'activi cada cop que una nova versió de codi es publiqui. Per a cada _tag_ es farà disponible una _pipeline_ encarregada d'executar, sobre la versió de codi concreta vinculada al _tag_, el procés de test, transpilació, publicació com a paquet i, de forma opcional, desplegament automàtic a l'entorn de WordPress.
|
|
|
|
|
|
|
|
Els runners de gitlab disposaran de configuració variable en funció de l'entorn per al que s'estigui executant el procés: _staging_ o _productiu_. Per a aquesta configuració variable es defineixen, dins el sistema de CI/CD de Gitlab, els mateixos entorns, _staging_ i _production_, i es fa ús de [les variables d'entorn](https://docs.gitlab.com/ee/ci/variables/) vinculades a un o altre. Aquestes variables d'entorn s'encarregan de modificar el comportament de la _pipeline_ per ajustar-lo als requeriments variables entre entorns. En panell de configuració de variables d'entorn d'un repositori gitlab el podem trobar a [settings > CI/CD > Variables](https://git.coopdevs.org/coopdevs/som-connexio/oficina-virtual/somoffice/-/settings/ci_cd).
|
|
|
|
|
|
|
|
Per últim, s'utilita un sistema de reconeixement de patrons sobre _tags_ i branques per apuntar a un o altre entorn. Els _tags_ del projecte segueixen el patró `v?[0-9]+[.][0-9]+([.][0-9]+)?([-a-z])*`, basat en l'estàndad [semver](https://semver.org/). Els _tags_ que s'ajustin a aquest patró s'identifiquen com a marques de versionat i habiliten la _pipeline_ de l'entorn productiu; les branques que s'ajustin al patró `testing/*` s'identifiquen com versions de codi en fase de validació i habiliten la _pipeline_ de l'entorn de _staging_. La creació de nous _tags_, o la publicació de nous commits a una branca `testing/*`, dispararà l'activació de la _pipeline_ vinculada a un o altre entorn.
|
|
|
|
|
|
|
|
La nova pipeline estarà estructurada en una cadena de _jobs_ que segueix el següent fluxe: `test > build > package > upload > release > deploy`. L'últim _job_ de la _pipeline_ és el de desplegament. Aprofitant el sistema de _runners_, s'ha definit una rutina que permet automatizar el desplegament del codi des de Gitlab. Aquest requereix que el procés d'execució conegui la URL on fer la petició i les credencials d'un usuari al sistema de WordPress. Amb aquesta informació accessible a través de les variables d'entorn, **el procés es capaç de connectar-se a WordPress a través d'una connexio HTTP i desplegar l'última versió de codi transpilat**.
|
|
|
|
|
|
|
|
## Conflictes d'estils entre WP i MUI
|
|
|
|
|
|
|
|
En el nou escenari, React pren el control d'una part parcial del DOM generat per WordPress i renderitza els seus components en ell. Aquesta nova estratègia d'integració entre la web pública i els formularis de la oficina virtual genera un efecte col·lateral que s'ha de controlar a nivell d'estils CSS: **Col·lisions entre els estisl CSS de WordPress i de React**.
|
|
|
|
|
|
|
|
WordPress disposa del seus fulls d'estils que defineixen el comportament visual dels diferents elements HTML de la pàgina. React utilitza una llibreria de components UI –[MUI](https://mui.com/)– que ofereix components amb estils CSS preconfigurats per tal de facilitar i estandarditzar les tàsques de maquetació de l'equip de desenvolupament. Quan fem conviure en un mateix DOM els dos sistemes d'estils CSS ens trobem amb alguns conflictes respecte a com una autoritat i l'altre imposen la representació visual dels elements HTML. Una solució basada en _iframes_ evita aquesta col·lisió al presentar visualment un contingut unificat gestionat internament com dos DOMs independents. Un cop descartada aquesta solució, fa falta resoldre manualment aquests conflictes.
|
|
|
|
|
|
|
|
La solució a implementar és la de extendre els fulls d'estils de WordPress per incloure-hi excepcions a les seves directives que resolguin els conflictes de maquetació que s'identifiquin. Per permetre la definició d'aquestes excepcions de forma senzilla, el plugin PHP desenvolupat identifica el node arrel de la aplicació React amb l'ID `#wp-react-root`. Sabem que tots els components de React injectats a través del nou sistema viuran sempre sota un element HTML amb aquest ID. Sabem també que els estils CSS funcionen en cascada jerarquitzada en funció de la [especificitat](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity) de les diferents directrius d'estil, i que una directriu basada en un ID sempre té més especificitat que la resta. Així, fent servir l'ID `#wp-react-root` com a component dels selectors CSS ens és fàcil definir directrius d'estils que sobreescriguin el comportament per defecte de les fulles d'estil de WordPress alhora que presentin un domini acotat a la branca del DOM gestionada per React.
|
|
|
|
|
|
|
|
En una primera iteració, i degut a que la titularitat del codi de WordPress no pertany a l'equip de desenvolupament que està treballant en aquesta solució, aquesta extensió dels estils de WordPress s'ha fet a través del [panell de personalització de temes](https://somconnexio.hiruu.com/wp-admin/customize.php?return=%2Fwp-admin%2F) de l'administrador de la pàgina. Aquesta solució presenta la desaventatge de guardar el codi CSS a base de dades i ens impedeix fer un control de versions del mateix o utilitzar sistemes automatitzats de linting o de deployment, com sí que es fa amb la resta del codi. En una pròxima iteració, es farà necessari introduir aquests canvis al codi font de la pàgina web. |
|
|
\ No newline at end of file |