React : Babel + Webpack + Sass aplicación básica
Introducción
En este tutorial veremos como construir una aplicación React con Webpack, Babel y Sass.
El principal requisito para este tutorial es disponer de una instalación para tu plataforma/sistema operativo de node con npm.
El tutorial está estructurado en varias secciones:
- Crear un directorio con un proyecto inicial en blanco
- Instalar Webpack
- Instalar Babel
- React
- Añadir soporte para estilos con Sass
Proyecto Inicial
El primer paso es crear un proyecto inicial en blanco. Para ello crearemos un directorio para el proyecto y ejecutaremos el siguiente comando dentro del mismo:
1npm init -y
Este comando creará un nuevo fichero package.json
con los metadatos por defecto para un nuevo proyecto.
Las partes relevantes del fichero package.json
generado deberían de actualizarse con los valores que consideremos para el nuevo proyecto. Este es el contenido de nuestro fichero tras las modificaciones iniciales:
1{
2 "name": "react-webpack-babel-sass-boilerplate",
3 "version": "2.0.0",
4 "description": "Boilerplate React application with Webpack, Babel and Sass.",
5 "main": "index.js",
6 "scripts": {
7 "test": "echo \"Error: no test specified\" && exit 1"
8 },
9 "repository": {
10 "type": "git",
11 "url": "git+https://github.com/marcnuri-demo/react-webpack-babel-sass-boilerplate.git"
12 },
13 "keywords": [],
14 "author": {
15 "name": "Marc Nuri",
16 "url": "https://blog.marcnuri.com"
17 },
18 "license": "Apache-2.0",
19 "licenses": [
20 {
21 "type": "Apache-2.0",
22 "url": "http://www.apache.org/licenses/LICENSE-2.0"
23 }
24 ],
25 "bugs": {
26 "url": "https://github.com/marcnuri-demo/react-webpack-babel-sass-boilerplate/issues"
27 },
28 "homepage": "https://github.com/marcnuri-demo/react-webpack-babel-sass-boilerplate#readme"
29}
Webpack: añadir soporte para Webpack 4
Instalar Webpack
El siguiente paso es añadir soporte para Webpack.
1npm install --save-dev webpack webpack-dev-server webpack-cli html-webpack-plugin html-loader
El comando anterior añadirá Webpack a las dependencias de desarrollo de nuestro fichero package.json
. Las tres primeras están estrictamente relacionadas con Webpack, si has trabajado con Webpack en otros proyectos, probablemente las tengas instaladas como dependencias globales. Para nuestro propósito y con motivo de soportar diferentes versiones de Webpack, las añadimos como dependencias locales.
Las otras dos (html-webpack-plugin, html-loader) se emplearán para minimizar el código html generado y para poder hacer referencia al Javascript empaquetado de nuestra aplicación.
Tras la ejecución del comando, en nuestro fichero package.json
se habrá creado una nueva sección "devDependencies"
.
Configuración de Webpack
El primer paso es crear contenido/código fuente para que Webpack pueda procesarlo. Creamos un nuevo directorio para alojar el código fuente y un fichero index.html
dentro de este directorio con el siguiente contenido:
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8" />
5 <title>react-webpack-babel-sass-boilerplate</title>
6</head>
7<body>
8 <div id="root"></div>
9</body>
10</html>
Para que Webpack (4) funcione necesitaremos un fichero de configuración para webpack. Para ello creamos un fichero webpack.config.js
vacío en el directorio raíz del proyecto y le insertamos este contenido:
1const webpack = require('webpack');
2const HtmlWebpackPlugin = require('html-webpack-plugin');
3
4const SRC_DIR = __dirname + '/src';
5const DIST_DIR = __dirname + '/dist';
6
7module.exports = {
8 entry: [
9 SRC_DIR + '/index.html',
10 ],
11 output: {
12 path: DIST_DIR,
13 publicPath: '/',
14 filename: 'bundle.js'
15 },
16 module: {
17 rules: [
18 {
19 test: /\.(html)$/,
20 exclude: /node_modules/,
21 use: {
22 loader: 'html-loader',
23 options: {minimize: true}
24 }
25 }
26 ]
27 },
28 resolve: {
29 extensions: ['*', '.js', '.jsx']
30 },
31 plugins: [
32 new webpack.HotModuleReplacementPlugin(),
33 new HtmlWebpackPlugin({
34 template: SRC_DIR + '/index.html',
35 filename: './index.html'
36 })
37 ],
38 devServer: {
39 contentBase: DIST_DIR,
40 hot: true,
41 port: 9000
42 }
43};
En la primera sección añadimos los require del script y declaramos dos constantes (SRC_DIR
, DIST_DIR
) para hacer referencia a los directorios con las fuentes (src) y la aplicación construida (dist).
A continuación declaramos un nuevo módulo empleando como punto de entrada el fichero index.html
creado en el paso anterior. Los ficheros de salida del módulo (output) se generarán en un directorio con el nombre ./dist
. Cuando el proyecto se construya una versión minimizada del fichero index.html
y otro con el nombre bundle.js
se generarán dentro de este directorio.
En la sección de reglas (rules) añadiremos el primer loader. En este caso vamos a usar html-loader
para preprocesar todos los ficheros html
ubicados en el directorio src
.
Queda por rellenar la configuración para resolve extensions. Esta configuración nos permitirá hacer referencia en los scripts a otros ficheros/dependencias indicando únicamente el nombre de fichero sin la extensión. En este caso, cuando en nuestro código indiquemos un import import Component from ./feature/Component'
Webpack buscará el código fuente del componente en ./feature/Component
, ./feature/Component.js
and ./feature/Component.jsx
.
En la sección de plugins vamos a incluir el plugin de Webpack HotModuleReplacementPlugin
que se encarga de a cambiar, añadir o quitar módulos mientras la aplicación se está ejecutando sin necesidad de hacer una recarga completa cuando se realice cualquier modificación en el código fuente (hot reloading).
También añadiremos el plugin HtmlWebpackPlugin
que se encargará de hacer referencia al fichero con el paquete de javascript (bundle) en el fichero index.html
creado anteriormente.
Por último definiremos un servidor de desarrollo (devServer) para servir nuestro módulo con hot reloading en el puerto 9000
.
package.json webpack scripts
El último paso para poner en marcha Webpack es definir algunos scripts en el fichero package.json
para que la aplicación se pueda construir y servir:
1"scripts": {
2 "build": "./node_modules/.bin/webpack -p --mode production",
3 "start": "./node_modules/.bin/webpack-dev-server --config ./webpack.config.js --mode development",
4 "test": "echo \"Error: no test specified\" && exit 1"
5 },
El primero de ellos (npm run build
) ejecutará el proceso de construcción de la aplicación depositando dos ficheros en el directorio dist
: index.html
y bundle.js
. Estos ficheros ya podrían desplegarse en un servidor web de estáticos sin que fuese necesaria ninguna otra configuración.
El segundo de los scripts (npm start
) lanzará un servidor de desarrollo con el módulo compilado en memoria y reaccionando a posibles cambios en el código. Una vez invocado con éxito, podemos dirigir nuestro navegador a la URL http://localhost:9000 dónde la aplicación ya debería de estar disponible. Cualquier cambio en el código fuente debería de desencadenar una recarga del navegador mostrando los nuevos cambios.
Es conveniente notar como hemos definido los scripts haciendo referencia a la versión local de webpack client (./node_modules/.bin/webpack
) en lugar de a la a versión global (webpack
) que probablemente también esté disponible en el sistema. Esto nos va a permitir controlar y ejecutar una versión específica de Webpack en este proyecto, ejecutando aquella versión que se haya definido en la sección devDependencies
del fichero de configuración package.json
.
Babel: añadir soporte para Babel 7
Instalar Babel
Una vez disponemos de un proyecto en el que hemos configurado Webpack el siguiente paso es añadir soporte para Babel. Babel se encargará de transpilar JSX y Javascript en Javascript compatible con ES5. React recomienda desarrollar el código empleando la sintaxis JSX ya que permite mezclar código HTML y Javascript de forma sencilla.
1npm install --save-dev @babel/core babel-loader @babel/preset-env @babel/preset-react
El comando anterior añadirá Babel a las dependencias de desarrollo (devDependencies
) del fichero package.json
. El paquete @babel/core
añadirá el soporte básico para Babel. babel-loader
habilitará la ejecución de Babel desde Webpack. @babel/preset-env
añadirá soporte para transpilar ficheros Javascript > ES5 en ficheros compatibles con ES5 Javascript . @babel/preset-react
añadirá soporte para transpilar ficheros con sintaxis JSX.
También vamos a añadir algunos plugins que nos facilitarán la vida durante el desarrollo, añadiendo soporte para nuevas funcionalidades Javascript. Estos plugins también sirven de ejemplo para ver cómo hay que configurar Babel y Webpack para habilitarlos.
1npm install --save-dev @babel/plugin-proposal-object-rest-spread @babel/plugin-syntax-dynamic-import @babel/plugin-transform-runtime @babel/runtime
Configuración de Babel
En versiones anteriores de Babel, las configuraciones se definían en el fichero .babelrc
. Desde la versión 7 de Babel, el procedimiento recomendado es definir la configuración en el fichero babel.config.js
que afectará al proyecto completo.
El fichero de configuración servirá para indicar a Babel y Webpack que debe de cargar los presets y plugins que hemos añadido a la sección devDependencies de nuestro package.json
en el paso anterior.
1module.exports = {
2 presets: ['@babel/preset-env', '@babel/preset-react'],
3 plugins: [
4 '@babel/plugin-transform-runtime',
5 '@babel/plugin-proposal-object-rest-spread',
6 '@babel/plugin-syntax-dynamic-import'
7 ]
8};
A continuación debemos de configurar Webpack para que utilice el loader de Babel para que preprocese los ficheros jsx
y js
antes de empaquetarlos. Para ello insertaremos la siguiente regla en la sección module rules de nuestro fichero webpack.config.js
:
1{
2 test: /\.(js|jsx)$/,
3 exclude: /node_modules/,
4 use: {
5 loader: 'babel-loader'
6 }
7},
A partir de este momento cuando cualquiera de los scripts de Webpack que hemos definido previamente se ejecute, Babel se arrancará y transpilará los ficheros js
y jsx
empleando las configuraciones definidas en el fichero babel.config.js
.
React
Instalar React
Para poder utilizar React en nuestros proyectos debemos de añadir dos nuevos paquetes:
1npm install --save react react-dom
El comando anterior añadirá React a las dependencias de nuestro fichero package.json
.
Punto de entrada (Entrypoint) de la aplicación
Una vez tenemos disponibles las dependencias de React, vamos a crear el punto de entrada de nuestra aplicación React. Para ello crearemos un nuevo fichero index.jsx
dentro del directorio src
con el siguiente contenido:
1import React from 'react';
2import ReactDOM from 'react-dom';
3
4ReactDOM.render(
5 <p>Hello world</p>,
6 document.getElementById('root')
7);
8
9if (module.hot) {
10 module.hot.accept();
11}
Este fichero renderizará <p>Hello world</p>
dentro del tag <div id="root"...
en el fichero index.html
que hemos creado en el paso anterior.
Desde este momento, ya podemos lanzar el script empleando el comando npm start
y acceder a nuestra aplicación apuntando el navegador a http://localhost:9000/.
Hot Module Replacement
Nuestra aplicación incluye el plugin de Webpack Hot Module Replacement (HMR). Este plugin modifica, añade o quita módulos de una aplicación mientras se está ejecutando sin necesidad de lanzar una recarga completa. Este mejora mucho el rendimiento y la productividad ya que los cambios en el código fuente se muestran casi al instante en el navegador sin necesidad de redesplegar la aplicación.
Cualquier cambio en el código de un fichero lanzará una recarga automática en el navegador y los cambios se mostrarán enseguida.
El único inconveniente de HMR es que no preserva el estado de la aplicación. En otro post analizaremos cómo podemos emplear React Hot Loader para incrementar aun más la productividad en el desarrollo de la aplicación al preservar el estado de la aplicación cuando se hace una recarga en caliente.
React App Component
Con el fin de mantener el código estructurado vamos a añadir el primer componente a la aplicación y a anidarlo bajo el fichero principal index.jsx
. Para ello crearemos un fichero app.jsx
con el siguiente contenido;
1import React, {Component} from 'react';
2
3const content = 'Hello world!';
4
5class App extends Component {
6 render() {
7 return (
8 <p>{content}</p>
9 );
10 }
11}
12
13export default App;
Ahora incluiremos el import del nuevo componente en el fichero index.jsx
(import App from "./app";
) y añadiremos el componente:
1/* ... */
2ReactDOM.render(
3 <App/>,
4 document.getElementById('root')
5);
6/* ... */
SCSS: añadir soporte para Sass
A pesar de que React ofrece diferentes formas para dar estilo a los componentes y de que existen infinidad de librerías con estilos para componentes, personalmente encuentro muy práctico el uso de CSS clásico o Sass para dar estilo a nuestra aplicación. Uno de los principales motivos es que Sass me permite compartir variables, media queries, partials, mixins…. de forma eficiente en todos los componentes de la misma aplicación e incluso entre diferentes repositorios con frameworks distintos (Puedo compartir la misma librería SCSS entre distintas aplicaciones construidas con diferentes frameworks: React, Angular, Hugo…).
Configuración de SCSS
Para poder preprocesar los ficheros SCSS tendremos que añadir algunos loaders más a Webpack, además de los bindings para node del preprocesador de Sass LibSass (node-sass
):
1npm install --save-dev mini-css-extract-plugin css-loader sass-loader node-sass
mini-css-extract-plugin
se encargará de extraer todo el código CSS generado en un único fichero. css-loader
se responsabilizará de los módulos CSS (explicado más adelante) y de resolver los estilos dentro de nuestros componentes React. sass-loader
and node-sass
harán el preprocesado de los ficheros Sass compilando/convertiendo el código SCSS en CSS.
Una vez instaladas las dependencias de desarrollo, añadimos una nueva regla a la sección module rules de nuestro fichero webpack.config.js
para invocar los loaders que acabamos de añadir.
1{
2 test: /\.(scss|sass|css)$/,
3 exclude: /node_modules/,
4 loaders: [
5 MiniCssExtractPlugin.loader,
6 {
7 loader: 'css-loader',
8 options: {
9 modules: true,
10 sourceMap: true,
11 importLoaders: 1,
12 localIdentName: '[local]___[hash:base64:5]'
13 }
14 },
15 'sass-loader',
16 ]
17},
En la nueva regla hacemos referencia a los 3 loaders que hemos instalado en el paso anterior. Para el caso de css-loader
añadimos una configuración adicional para dar soporte a los módulos CSS (explicado más adelante).
También debemos de añadir una referencia y configuración a MiniCssExtractPlugin
en la sección de plugins (revisa el fichero webpack.config.js
completo para ver los imports y las declaraciones de constantes);
1new MiniCssExtractPlugin({
2 filename: devMode ? '[name].css' : '[name].[hash].css',
3 chunkFilename: devMode ? '[id].css' : '[id].[hash].css',
4})
En las líneas anteriores se define la configuración del plugin MiniCssExtractPlugin
y como debe de generarse el fichero con el paquete CSS.
SCSS global
Para organizar los estilos globales de la aplicación crearemos la siguiente estructura de directorios:
1src
2└─┬ styles
3 ├─┬ partials
4 | ├── _base.scss
5 | ├── _mixins.scss
6 | └── _variables.scss
7 └── main.scss
El fichero main.scss
contiene los imports para los ficheros con los partials. A modo de ejemplo hemos incluido 3 ficheros con Sass partials: _variables.scss
para declarar las variables con los colores, fuentes, etc.; _mixins.scss
que incluye un mixin de muestra para hacer una caja redimensionable; y _base.scss
que define los estilos básicos para la aplicación.
Finalmente, para habilitar los estilos globales en la aplicación tendremos que añadir un import al fichero de estilos principal main.scss
dentro de nuestro componente app.jsx
. Esto será tan sencillo como añadir la siguiente línea en la sección de imports del componente: import styles from './styles/main.scss';
. Webpack automáticamente reconocerá el import durante la fase de transpilación resolviendo la ubicación del fichero empaquetado con los estilos completos empleando la regla para el módulo que hemos definido en el paso anterior.
CSS Modules: Módulos CSS para dar estilo a cada componente
En la sección anterior hemos visto como declarar estilos globales para nuestra aplicación, en ésta veremos como utilizar los módulos CSS para permitir dar estilos SCSS independientes a cada componente React.
En el fichero webpack.config.js
ya hemos declarado una regla para el preprocesado de estilos SCSS y la configuración de css-loader
. Para permitir los módulos CSS además hemos añadido configuración adicional: modules: true
and localIdentName
que definen la forma en la que el nombre de las clases CSS se extenderán. En definitiva, con el enfoque de los módulos CSS, lo que ocurrirá cuando se genere el código de la aplicación es que se añadirá un hash a los nombres de las clases que definamos en el código.
Para demostrar como funciona esto vamos a crear dos componentes (Button and FancyButton), ambos con el mismo código y nombre de clases CSS, pero definidos en ficheros diferentes. Gracias al enfoque modular, cada uno de ellos se mostrará con su diseño particular.
1src
2├─┬ button
3| ├── button.jsx
4| └── button.scss
5└─┬ fancy-button
6 ├── fancy-button.jsx
7 └── fancy-button.scss
Para cada componente vamos a añadir un fichero jsx
con su definición:
1import React, {Component} from 'react';
2import styles from './button.scss'
3
4class Button extends Component {
5
6 render() {
7 return (
8 <button className={styles.button}>{this.props.label}</button>
9 );
10 }
11}
12
13export default Button;
Ambos botones tendrán el mismo código, siendo la única diferencia el nombre de la clase Javascript. Para poder hacer referencia al nombre de la clase definida en el código SCSS de cada componente, utilizaremos {styles.button}
, donde styles
es la referencia al fichero SCSS definida en la sección de imports y button
es el nombre de la clase CSS definida dentro de este fichero (button para ambos).
También añadiremos un fichero SCSS con los estilos para cada uno de los componentes:
1@import '../styles/partials/variables';
2
3.button {
4 /* ... */
5}
De nuevo, para ambos componentes, tanto la estructura como el nombre de la clase CSS serán iguales para ambos botones. La única diferencia serán los estilos particulares para cada uno de ellos.
Por último añadimos los dos botones al componente App:
1import React, {Component, Fragment} from 'react';
2import Button from "./button/button";
3import styles from './styles/main.scss';
4import FancyButton from "./fancy-button/fancy-button";
5
6class App extends Component {
7 render() {
8 return (
9 <Fragment>
10 <Button label="Regular Button"/>
11 <FancyButton label="Fancy Button"/>
12 </Fragment>
13 );
14 }
15}
16
17export default App;
Cuando la página se muestre en el navegador, aunque ambos componentes comparten el mismo nombre de clase CSS, cada uno de ellos se renderiza con los estilos que tiene definido en su propio fichero SCSS.
Si inspeccionamos el nombre de las clases de cada uno de los botones, veremos que ambos tienen el mismo nombre (button) per con un sufijo diferente consistente en un hash añadido por css-loader
:
Conclusión
Esta publicación es un tutorial para crear una aplicación React básica con soporte para estilos Sass, Babel y Webpack 4. El tutorial muestra como instalar todas las dependencias y como configurar Webpack y Babel para transpilar los fichero React jsx en código Javascript estándar. En la última sección se incluyen instrucciones para añadir estilos Sass tanto a nivel global de aplicación como a nivel particular de componente empleando CSS modules. La intención de esta publicación es disponer de una pequeña guía para crear la estructura básica de una aplicación cada vez que se inicie un nuevo proyecto React.
El código fuente completo de este artículo puede encontrarse en Github.
Comentarios en "React : Babel + Webpack + Sass aplicación básica"