Code splitting en React JS.
Franklin Martinez
Posted on March 3, 2023
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.
Tabla de contenido.
📌 Tecnologías a utilizar.
📌 ¿Qué es code splitting?
📌 Creando el proyecto.
📌 Primeros pasos.
📌 Aplicando code splitting.
📌 Conclusión.
💢 Tecnologías a utilizar.
- React JS v18.2.0
- React router dom v6.7.0
- Vite JS v4.0.0
- TypeScript v4.9.3
💢 ¿Qué es code splitting?
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.
Al proyecto le colocaremos el nombre de: code-splitting-react
(opcional, tu le puedes poner el nombre que gustes).
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.
cd code-splitting-react
Luego instalamos las dependencias.
npm install
Después abrimos el proyecto en un editor de código (en mi caso VS code).
code .
💢 Primeros pasos.
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
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
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
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.
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
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.
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.
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.
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.
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.
const Login = () => {
return (
<div>Login</div>
)
}
export default Login
Y luego creamos las demás paginas para agregarles la función lazy
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
// 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 que nos proporciona React.
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.
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.
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.
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.
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
🔵 No olvides seguirme también en twitter: @Frankomtz361
Posted on March 3, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.