Code splitting en React JS.
Tags: javascript, react, spanish, performance
Published: 2023-03-03 14:25:39

Cuando tus aplicaciones crecen, seria considerable mejorar la velocidad de carga inicial para hacerla mas pequeña y así evitar que el usuario se vaya de nuestra app por tener que estar esperando a que cargue.

Y para eso, podemos usar **code splitting**, lo cual nos ayudara bastante en nuestras aplicaciones.

> Nota: Para entender este post, tienes que tener bases de react router dom, ya que us una librería que vamos a usar para explicar el code splitting.

&nbsp;

## Tabla de contenido.
> 📌 [Tecnologías a utilizar.](#1)  
> 📌 [¿Qué es code splitting?](#2)  
> 📌 [Creando el proyecto.](#3)  
> 📌 [Primeros pasos.](#4)  
> 📌 [Aplicando code splitting.](#5)  
> 📌 [Conclusión.](#6)  

&nbsp;


## 💢 Tecnologías a utilizar. <a id="1"></a>

- React JS v18.2.0
- React router dom v6.7.0
- Vite JS v4.0.0
- TypeScript v4.9.3


## 💢 ¿Qué es code splitting? <a id="2"></a>

Primero hay que entender como funcionan la mayoría de los frameworks.
Ya que muchos agrupan todas las dependencias en un gran archivo, lo cual facilita la adición de JavaScript a una página HTML. 
**En teoría, agrupar JavaScript de este modo debería acelerar la carga de las páginas y reducir la cantidad de tráfico que éstas deben gestionar.**

Pero a medida que una aplicación crece, el tamaño de sus paquetes (bundles) también crece y, en algún momento, sus paquetes serán tan grandes que tardarán mucho en cargarse.

Aquí es donde entra la técnica de code splitting. **Code splitting** o división de código traducido a español, consiste en separar el código en varios paquetes o componentes que pueden cargarse bajo demanda o en paralelo. Esto significa que no se cargan hasta que son necesarias.

*La página sigue cargando la misma cantidad de código, pero la diferencia se debe a que la página puede no ejecutar todo el código que carga*

**Los beneficios del code splitting son:**

- La velocidad a la que un sitio web carga y muestra el contenido se vuelve más rápida.
- El tiempo de interacción, mejoran
- Disminuye el porcentaje de usuarios que abandonan la página web sin interactuar con ella.


## 💢 Creando el proyecto. <a id="3"></a>

Al proyecto le colocaremos el nombre de: **`code-splitting-react`** (opcional, tu le puedes poner el nombre que gustes).
```bash
npm create vite@latest
```
Creamos el proyecto con Vite JS y seleccionamos React con TypeScript.  
Luego ejecutamos el siguiente comando para navegar al directorio que se acaba de crear.

```bash
cd code-splitting-react
```
Luego instalamos las dependencias.
```bash
npm install
```
Después abrimos el proyecto en un editor de código (en mi caso VS code).
```bash
code .
```

## 💢 Primeros pasos. <a id="4"></a>
Primero vamos crear unas cuantas paginas. Creamos la carpeta **src/pages** y dentro creamos 6 archivos. Las cuales serán muy sencillas con muy poco contenido.

Observa que lo único que coloco es un div con el nombre correspondiente a la pagina.

1- Profile.tsx
```tsx
export const Profile = () => {
    return (
        <div>Profile</div>
    )
}
```

Bueno hacemos lo mismo con los siguientes 4 paginas

2- About.tsx
3- Contact.tsx
4- FAQs.tsx
5- Login.tsx

En este archivo solo exportaremos cada pagina, o sea lo usaremos como un archivo barril

6- index.ts
```tsx
export * from './About';
export * from './Contact';
export * from './FAQs';
export * from './Login';
export * from './Profile';
```


**Ahora si vamos a crear un ejemplo sobre como normalmente crearíamos un app con rutas sin aplicar code splitting.**

Instalamos react router dom
```
npm install react-router-dom
```

Creamos una carpeta **src/components** y creamos un archivo **NavBar.tsx**
```tsx
import { Link } from 'react-router-dom';

export const NavBar = () => {
    return (
        <nav>
            <Link to='/home'>Home</Link>
            <Link to='/about'>About</Link>
            <Link to='/contact'>Contact</Link>
            <Link to='/faqs'>FAQs</Link>
        </nav>
    )
}
```

Y ahora dentro del archivo **src/App.tsx** borramos todo su contenido y creamos un nuevo componente.

```tsx
import { BrowserRouter, Navigate } from 'react-router-dom';
import { NavBar } from './components/NavBar';
import { About, Contact, FAQs, Profile, Login } from './pages'

const App = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path='profile' element={ <Profile /> } />
        <Route path='contact' element={ <Contact /> } />
        <Route path='about' element={ <About /> } />
        <Route path='faqs' element={ <FAQs /> } />
        <Route path='login' element={ <Login /> } />

        <Route path='/*' element={<Navigate to='/login' replace />} />
      </Routes>
    </BrowserRouter>
  )
}
export default App
```

Ahora vamos a modificar un poco, para simular las rutas privadas. Así que en el mismo archivo creamos unos 2 nuevos componentes.


En este componente vamos a tener estas rutas, que serán las privadas, o sea que el usuario las vera hasta que sea autenticado.
También nota que aquí vamos a mostrar el **<NavBar/>**

```tsx
export const PrivateRoutes = () => {
  return (
    <>
      <NavBar />
      <Routes>
        <Route path='profile' element={ <Profile /> } />
        <Route path='about'   element={ <About />   } />
        <Route path='contact' element={ <Contact /> } />
        <Route path='faqs'    element={ <FAQs />    } />

        <Route path='/*'      element={<Navigate to='/profile' replace />} />
      </Routes>
    </>
  )
}
```

Luego tenemos las rutas publicas.

```tsx

export const PublicRoutes = () => {
  return (
    <Routes>
      <Route path='login' element={ <Login /> } />

      <Route path='/*' element={<Navigate to='/login' replace />} />
    </Routes>
  )
}
```

Por ultimo modificamos el componente **App**.
Creamos una constante para simular la autenticación.
Y Dependiendo de esa constante se crearan una rutas u otras.

```tsx
const isAuthenticated = false

const App = () => {
  return (
    <BrowserRouter>
      <Routes>
        {
          (isAuthenticated)
            ? <Route path="/*" element={<PrivateRoutes />} />
            : <Route path="/*" element={<PublicRoutes />} />
        }
      </Routes>
    </BrowserRouter>
  )
}
export default App
```
Tal vez creas que por que solo renderizamos unas rutas y otras no, las rutas que no se renderizan no se van a cargar. Pero la verdad es que aunque no se renderize, ya sea la ruta publica o privada en pantalla, siempre se van a cargar todos los componentes, todas las paginas y su css y demás paquetes. 


Y puedes comprobarlo si ejecutas la app, y vas a inspeccionar la pagina en la pestaña de **Network** y lo filtras por archivos JS, veras como se cargan todas las paginas, Login.tsx, Profile.tsx, About.tsx, etc.

Tal vez ahorita no notes algún problema en la velocidad de carga, pues esta app, no tiene casi nada de contenido. Pero si imaginas que tengas mas componentes o mas paquetes instalados, tu aplicación cargaría todo incluso si no lo estas usando. 

Asi que vamos a tratar de resolver este problema con **code splitting**.

## 💢 Aplicando code splitting. <a id="5"></a>

En este momento tenemos una "autenticación" que nos permitirá o no ver las paginas privadas.
Pero anteriormente vimos que aunque estés autenticado o no, siempre se cargaran las paginas.

Así que, que pasa si el usuario, solo quiere ingresar al Login y ya, entonces no hay necesidad de cargar las otras paginas que son privadas. O que tal si el usuario ya esta autenticado, no hay necesidad de cargar el Login, sino hasta que el usuario decida cerrar sesión. 

Bueno para aplicar code splitting, vamos a usar:

**React.lazy**. Característica proporcionada directamente por React, permite la carga perezosa de importaciones. Se trata de una función de componente, que toma como parámetro otra y finalmente devuelve una promesa  como resultado que se espera que se resuelva un componente de React.

```tsx
const Login = lazy(() => import('./pages/Login'));
```

Pero, tal vez te salga un error, esto debido a que la función de lazy espera que el componente que quieres que devuelva sea una exportación por defecto.

Asi que solamente vamos a cada pagina, y agregamos su exportación por defecto

Toma de ejemplo **pages/Login.tsx**. Haremos lo mismo con todas las paginas.

```tsx
const Login = () => {
    return (
        <div>Login</div>
    )
}
export default Login
```

Y luego creamos las demás paginas para agregarles la función **lazy**
```tsx
const Profile = lazy(() => import('./pages/Profile'));
const About   = lazy(() => import('./pages/About'  ));
const Contact = lazy(() => import('./pages/Contact'));
const FAQs    = lazy(() => import('./pages/FAQs'   ));
const Login   = lazy(() => import('./pages/Login'  ));
```

Ahora, vamos a comentar las importaciones para que no hagan conflicto con los nuevos componentes que creamos

```tsx
// import { About, Contact, FAQs, Profile, Login } from './pages'
```

Pero aun falta el paso final, ya que si vemos nuestra app, nos dará un error, y eso es porque necesitamos un componente que suspenda el renderizado del componente hasta que todas sus dependencias estén cargada.

Para ello usamos el componente **<Suspense/>** que nos proporciona React.

```tsx
import { lazy, Suspense } from 'react';

<Route 
    path='login' 
    element={
        <Suspense> 
            <Login />
        </Suspense>
    } 
/>
```
Suspense, ademas nos sirve como interfaz de usuario, ya que contiene una prop **fallback** que debe recibir un componente de React, el cual se mostrara hasta que termine de cargarse el componente al que le aplicamos **lazy**. O sea que aquí es un buen lugar para poner un componente loading.

```tsx
import { lazy, Suspense } from 'react';

<Route 
    path='login' 
    element={
        <Suspense fallback={<>Loading app...</>}> 
            <Login />
        </Suspense>
    } 
/>
```

Tendrás que agregar el Suspense a cada pagina que le has aplicado la función lazy. Y el archivo quedaría de esta manera.

```tsx
import { lazy, Suspense } from 'react';
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import { NavBar } from './components/NavBar';
// import { About, Contact, FAQs, Profile, Login } from './pages'

const Profile = lazy(() => import('./pages/Profile'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
const FAQs = lazy(() => import('./pages/FAQs'));
const Login = lazy(() => import('./pages/Login'));

const isAuthenticated = false

const App = () => {
  return (
    <BrowserRouter>
      <Routes>
        {
          (isAuthenticated)
            ? <Route path="/*" element={<PrivateRoutes />} />
            : <Route path="/*" element={<PublicRoutes />} />
        }
      </Routes>
    </BrowserRouter>
  )
}
export default App

export const PublicRoutes = () => {
  return (
    <Routes>
      <Route path='login' element={<Suspense fallback={<>...</>}> <Login /></Suspense>} />
      <Route path='/*' element={<Navigate to='/login' replace />} />
    </Routes>
  )
}

export const PrivateRoutes = () => {
  return (
    <>
      <NavBar />
      <Routes>
        <Route path='profile' element={<Suspense fallback={<>...</>}> <Profile /></Suspense>} />
        <Route path='about' element={<Suspense fallback={<>...</>}> <About /></Suspense>} />
        <Route path='contact' element={<Suspense fallback={<>...</>}> <Contact /></Suspense>} />
        <Route path='faqs' element={<Suspense fallback={<>...</>}> <FAQs /></Suspense>} />
        <Route path='/*' element={<Navigate to='/profile' replace />} />
      </Routes>
    </>
  )
}
```

Incluso puedes crear una lista con las rutas y recorrerla con un **.map** para evitar tener que colocar el Suspense demasiado.

Pero si ahora vamos y observamos nuevamente la pestaña **Network**. La verdad no observaremos una gran  diferencia en la velocidad de carga, porque es una app muy pequeña.

Pero ahora, cambia la autenticación a true, para que se renderize la parte privada de las rutas.
```tsx
const isAuthenticated = true
```

Pon atención a los archivos de JS, veras que no todos se cargan solamente el de Profile.tsx porque es la pagina que estas viendo actualmente.

Si te empiezas a mover entre paginas con el Navbar, veras como se van cargando cada pagina que visitas, o sea que solo se cargan hasta que tu las necesitas.

**Asi es como aplicaríamos code splitting para mejorar el rendimiento de nuestras aplicaciones en React.**

> Tampoco te digo que uses lazy en todos los componentes, porque puede provocar tiempos de cargas mas largos. **Intenta cargar sólo componentes que no sean visibles en el renderizado inicial.**

## 💢 Conclusión. <a id="6"></a>

El **code splitting** es una práctica común en las grandes aplicaciones React, y el aumento de velocidad que proporciona puede determinar si un usuario continúa usando una aplicación web o la abandona, por lo que recortar incluso fracciones de segundo podría ser significativo.

Espero haberte ayudado a entender un poco mas este tema. Si conoces alguna otra forma distinta o mejor de realizar esta aplicación con gusto puedes comentarla. 🙌

> **Te invito a que revises mi portafolio en caso de que estés interesado en contactarme para algún proyecto! [Franklin Martinez Lucas](https://franklin-dev.netlify.app/)**

> 🔵 No olvides seguirme también en twitter: [**@Frankomtz361**](https://twitter.com/Frankomtz361)