Saltar a contenido

Rsbuild

Rsbuild es un competidor justamente de Vite o Create-React-App, pero con la ventaja de que es mucho más flexible y configurable. Y cuenta con soporte de primera clase para Module Federation.

Una de las principales ventajas es que está diseñado para ser un cambio 1 a 1 a Webpack y que es muy fácil de configurar, sumado a que está hecho en Rust lo que lo hace más performante inclusive que vite, manteniendo la compatibilidad con desarrollos basados en Webpack. (Todos los CRA, Next.js, etc.)

Migración de CRA a Rsbuild

Hemos disponibilizado un comando en el CLI de @architecture-it/front-cli para facilitar la migración de proyectos de CRA a Rsbuild.

front-cli migrate

Este comando se encargará de realizar los cambios necesarios en el proyecto para que pueda ser ejecutado con Rsbuild.

Migración de CRA (simple) a Rsbuild

  • Se creará un archivo rsbuild.config.ts con la configuración básica.
Ejemplo de rsbuild.config.ts
rsbuild.config.ts
import { defaultConfig } from "@architecture-it/rsbuild";

export default defaultConfig({});
  • Se instalarán las dependencias necesarias para Rsbuild.
Se ejecutan los comandos
pnpm install @architecture-it/rsbuild @rsbuild/core @rsbuild/plugin-react @swc/core @swc/jest @testing-library/jest-dom @testing-library/react @types/jest jest jest-environment-jsdom cross-env identity-obj-proxy -D
  • Se modificará el archivo package.json para que se ejecute el comando de Rsbuild.
Se modifica el archivo package.json
{
  "scripts": {
    "prestart": "react-env --",
    "start": "rsbuild start",
    "build": "rsbuild build",
    "preview": "rsbuild preview",
    "test": "jest test",
    "posttest": "jest posttest",
    "test:coverage": "jest test:coverage",
    "test:coverage:ci": "jest test:coverage:ci",
    "test:watch": "jest test:watch",
    "test:specific": "jest test:specific",
    "test:specific-coverage": "jest test:specific-coverage",
    "test:clear-cache": "jest test:clear-cache"
  }
}
  • Se modificará el archivo tsconfig.json para que se pueda ejecutar con Rsbuild.
  • Se actualizará el archivo public/index.html para que se tome la carpeta pública.
Se reemplaza %PUBLIC_URL% por <%= assetPrefix %>
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="utf-8" />
    <link rel="icon" href="<%= assetPrefix %>/favicon.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
    name="description"
    content="Andreani App"
    />
    <link rel="manifest" href="<%= assetPrefix %>/manifest.json" />
    <title>Andreani App</title>
    <script src="<%= assetPrefix %>/__ENV.js"></script>
    <link href="https://fonts.googleapis.com" rel="preconnect" />
    <link crossOrigin="crossorigin" href="https://fonts.gstatic.com" rel="preconnect" />
    <link
    href="https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,700&display=swap"
    rel="stylesheet"
    />
</head>
<body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
</body>
</html>
  • Se actualizará el archivo Dockerfile para que se pueda ejecutar con Rsbuild. La carpeta de build será dist.
Dockerfile esperado
FROM ghcr.io/architecture-it/react:node-20 AS deps
WORKDIR /app

RUN apk add --no-cache libc6-compat

COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./

RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci --no-progress --silent --maxsockets 1; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
else \
    echo "ERROR: Falta archivo lockfile. Ver más en https://architecture-it.github.io/docs/Platform/Front/#manejo-de-dependencias"; \
    exit 1; \
fi

FROM ghcr.io/architecture-it/react:node-20 AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN yarn build

FROM ghcr.io/architecture-it/nginx:latest
COPY --from=builder /app/dist .
# WORKAROUND si se tiene problemas con permisos, (Evitar)
# RUN touch ./public/__ENV.js && chmod 777 ./public/__ENV.js

CMD ["/bin/sh", "-c", "react-env -d ./ -- && nginx -g \"daemon off;\""]
  • Se instalaran las dependencias para que Jest funcione, la diferencia es que se hará con swc y no con babel, para hacerlo más performante. Esto está dentro de la librería por lo que la configuración será transparente y customizable.

En el caso de los microfrontends se agregarán más archivos y configuraciones para que se pueda ejecutar con Rsbuild.

  • Se agregarán las dependencias necesarias para que se pueda ejecutar con Rsbuild.
Se ejecutan los comandos
pnpm install @module-federation/runtime @module-federation/enhanced
  • Se eliminára el archivo modulefederation.config.js y se agregará la confiuracion en el rsbuild.config.ts

  • Se inyectará la configuración necesaria para que se pueda ejecutar con Rsbuild. en el archivo src/bootstrap.tsx (Shell)

Ejemplo de src/bootstrap.tsx
src/bootstrap.tsx
import { createRoot } from "react-dom/client";
import { StyleSystemProvider } from "@architecture-it/stylesystem";
import { CssBaseline } from "@mui/material";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import { store } from "store/store";
import { initializeMicrofronts } from "@architecture-it/rsbuild/initializeMicrofronts";
import * as ReactRouterDom from "react-router-dom";
import { AuthProvider } from "@architecture-it/react-auth";

import pkg from "../package.json";

import rum from "./monitor";
import App from "./App";

const dependencies = pkg.dependencies;

initializeMicrofronts("shell", {
dependencies,
shared: {
    "react-router-dom": {
    version: dependencies["react-router-dom"],
    scope: "default",
    // por default toma esta instancia de la librería
    lib: () => ReactRouterDom,
    shareConfig: {
        singleton: true,
        requiredVersion: dependencies["react-router-dom"],
    },
    },
    "react-redux": {
    scope: "default",
    shareConfig: {
        singleton: true,
        requiredVersion: false,
    },
    },
},
});

rum.setInitialPageLoadName("Home");

const container = document.getElementById("root") as HTMLElement;
const root = createRoot(container);

root.render(
<BrowserRouter>
    <Provider store={store}>
    <AuthProvider>
        <StyleSystemProvider>
        <CssBaseline />
        <App />
        </StyleSystemProvider>
    </AuthProvider>
    </Provider>
</BrowserRouter>
);

Migración de Vite a Rsbuild

  • Se instalarán las dependencias necesarias para Rsbuild.
  • Se eliminaran las dependencias de vite así como los archivos de configuración.
  • Se cambiarán los nombres de las variables de los archivos de environments .env .devops/values-qa.yml ...
  • Se creará un archivo rsbuild.config.ts con la configuración básica.
  • Se modificará el archivo package.json para que se ejecute el comando de Rsbuild. Así como el tipo de module se eliminara.
  • Se modificará el archivo tsconfig.json para que se pueda ejecutar con Rsbuild.
  • Se actualizará el archivo public/index.html para que se tome la carpeta pública.
  • Se actualizará el archivo Dockerfile para que se pueda ejecutar con Rsbuild. La carpeta de build será dist. Y no necesitará el prefijo VITE
  • Se instalaran las dependencias para que Jest funcione, la diferencia es que se hará con swc y no con babel, para hacerlo más performante. Esto está dentro de la librería por lo que la configuración será transparente y customizable.

En el caso de los microfrontends se agregarán más archivos y configuraciones para que se pueda ejecutar con Rsbuild.

  • Se agregarán las dependencias necesarias para que se pueda ejecutar con Rsbuild.
  • Se agreará la confiuracion básica en el rsbuild.config.ts. Necesita configuración manual
  • Se inyectará la configuración necesaria para que se pueda ejecutar con Rsbuild. en el archivo src/App.tsx (Shell)
  • Se renombrará el arcivo src/main.tsx a src/bootstrap.tsx y en src/index.tsx se dejará el entrypoint de la aplicación, asincrono como lo require modue federation.

Si se agrega el flag de migrateToJest (confirmación), se ejecutaran automatizaciones sobre la carpeta src/__test__ para que se pueda ejecutar con Jest, así como el linter y el formatter de conseguirlo.

Se automatizaron los siguientes cambios:

  • Se eliminan todos los import de vitest
  • Se eliminan los llamados de vi por jest
  • Se renombran las funciones importActual por requireActual. (Necesita manualmente quitarse el await y en async de estos mocks)

FAQ

Hice la migración pero los test me muestran vitest
  1. Eliminar la carpeta node_modules y el archivo pnpm-lock.yml
  2. Instalar las dependencias nuevamente con pnpm i
  3. Ejecutar los test nuevamente con pnpm test
Hice la migración pero tengo errores con los svg
  1. Agregar la dependencia de pnpm add @rsbuild/plugin-svgr
  2. Agregar el plugin a la config de rsbuild
        import { defaultConfig } from "@architecture-it/rsbuild";
        import { pluginSvgr } from "@rsbuild/plugin-svgr";
        export default defaultConfig({
        plugins: [pluginSvgr()],
        });
    
  3. Ejemplo de implementación en un componente
        import AndreaniLongTruck from "@/assets/svg/andreani_long_truck.svg";
        <>
            <Chip
                  label={<AndreaniLongTruck height="100%" width="100%" />}
                  sx={{
                    color: "#5B5B5B",
                    backgroundColor: "#EEEEEE",
                    fontSize: 16,
                  }}
                />
        <>
    
Hice la migración pero tengo errores con paquetes que no tengo instalados

Desde cierta version de nodejs y webpack ya no se agregan ciertos paquetes a los proyectos de frontend, por lo que debes instalar un plugin para poder hacer funcionar tu aplicación como costumbre.

Por lo general si usabas react-app-rewired se ve de esta manera o similar:

// ...codigo ignorado por brevedad
    const fallback = config.resolve.fallback || {};

Object.assign(fallback, {
    "assert": require.resolve("assert"),
    "zlib": require.resolve("browserify-zlib"),
    "process/browser": require.resolve("process/browser"),
    "stream": require.resolve("stream-browserify"),
    "util": require.resolve("util"),
    "buffer": require.resolve("buffer"),
});
config.resolve.fallback = fallback;
config.plugins = (config.plugins || []).concat([
    new webpack.ProvidePlugin({
        process: "process/browser",
        Buffer: ["buffer", "Buffer"]
    })
]);
// ...codigo ignorado por brevedad
  1. Agregar la dependencia de pnpm add @rsbuild/plugin-node-polyfill

  2. Agregar el plugin a la config de rsbuild

        import { defaultConfig } from "@architecture-it/rsbuild";
        import { pluginNodePolyfill } from "@rsbuild/plugin-node-polyfill";
    
        export default defaultConfig({
            plugins: [pluginNodePolyfill()],
        });
    

Tengo el siguiente error Module not found: Can't resolve 'mui/system'

En ciertos proyectos que usan las primeras versiones de material teniamos esta dependencia, lo ideal sería evitarla, pero si no podemos, se soluciona simplemente agregandola.

  1. Agregar la dependencia de pnpm add @mui/system