En esta publicación final de nuestra serie sobre módulos CSS, echaré un vistazo a cómo hacer un sitio React estático con el agradecimiento de Webpack. Este sitio estático tendrá dos plantillas: una página de inicio y una página de información con un par de componentes de React para explicar cómo funciona en la práctica.
Serie de artículos:
- ¿Qué son los módulos CSS y por qué los necesitamos?
- Introducción a los módulos CSS
- React + CSS Modules = 😍 (¡Estás aquí!)
En la publicación anterior, configuramos un proyecto rápido con Webpack que mostró cómo se pueden importar las dependencias a un archivo y cómo se puede usar un proceso de compilación para crear un nombre de clase único que se genera tanto en CSS como en HTML. El siguiente ejemplo se basa en gran medida en ese tutorial, por lo que definitivamente vale la pena trabajar primero con esos ejemplos anteriores. Además, esta publicación asume que está familiarizado con los conceptos básicos de React.
En la demostración anterior, hubo problemas con el código base cuando concluimos. Dependíamos de JavaScript para representar nuestro marcado y no estaba del todo claro cómo deberíamos estructurar un proyecto. En esta publicación, veremos un ejemplo más realista en el que intentamos hacer algunos componentes con nuestro nuevo conocimiento de Webpack.
Para ponerse al día, puede consultar el repositorio css-modules-react que hice, que es solo un proyecto de demostración que nos lleva hasta donde quedó la última demostración. Desde allí, puede continuar con el tutorial a continuación.
Generador de sitios estáticos de Webpack
Para generar marcado estático, necesitaremos instalar un complemento para Webpack que nos ayude a generar marcado estático:
npm i -D static-site-generator-webpack-plugin
Ahora necesitamos agregar nuestro complemento en `webpack.config.js` y agregar nuestras rutas. Las rutas serían como /
para la página de inicio o /about
para la página acerca de. Las rutas le dicen al complemento qué archivos estáticos crear.
var StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin');
var locals = {
routes: [
"https://css-tricks.com/",
]
};
Dado que queremos ofrecer marcado estático, y preferimos evitar el código del lado del servidor en este punto, podemos usar nuestro StaticSiteGeneratorPlugin. Como mencionan los documentos de este complemento, proporciona:
una serie de rutas para renderizar, y un conjunto coincidente de archivos index.html se renderizará en su directorio de salida mediante la ejecución de su propia función de renderización personalizada compilada con el paquete web.
Si eso suena espeluznante, ¡no se preocupe! Aún en nuestro `webpack.config.js`, ahora podemos actualizar nuestro module.exports
objeto:
module.exports = {
entry: {
'main': './src/',
},
output: {
path: 'build',
filename: 'bundle.js',
libraryTarget: 'umd' // this is super important
},
...
}
Establecemos el libraryTarget
porque ese es un requisito para que nodejs y el complemento del sitio estático funcionen correctamente. También agregamos una ruta para que todo se genere en nuestro directorio `/ build`.
Aún dentro de nuestro archivo `webpack.config.js` necesitamos agregar el StaticSiteGeneratorPlugin
en la parte inferior, así, pasando las rutas que queremos generar:
plugins: [
new ExtractTextPlugin('styles.css'),
new StaticSiteGeneratorPlugin('main', locals.routes),
]
Nuestro `webpack.config.js` completo debería verse así:
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin')
var locals = {
routes: [
"https://css-tricks.com/",
]
}
module.exports = {
entry: './src',
output: {
path: 'build',
filename: 'bundle.js',
libraryTarget: 'umd' // this is super important
},
module: {
loaders: [
{
test: /.js$/,
loader: 'babel',
include: __dirname + '/src',
},
{
test: /.css$/,
loader: ExtractTextPlugin.extract('css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'),
include: __dirname + '/src'
}
],
},
plugins: [
new StaticSiteGeneratorPlugin('main', locals.routes),
new ExtractTextPlugin("styles.css"),
]
};
En nuestro archivo vacío `src / index.js` podemos agregar lo siguiente:
// Exported static site renderer:
module.exports = function render(locals, callback) {
callback(null, '<html>Hello!</html>');
};
Por ahora solo queremos imprimir Hello!
en la página de inicio de nuestro sitio. Eventualmente lo convertiremos en un sitio más realista.
En nuestro `package.json`, que discutimos en el tutorial anterior, ya tenemos el comando básico, webpack
, que podemos ejecutar con:
npm start
Y si echamos un vistazo a nuestro build
directorio, entonces deberíamos encontrar un index.html
archivo con nuestro contenido. ¡Dulce! Podemos confirmar que el complemento Static Site está funcionando. Ahora, para probar que todo esto funciona, podemos regresar a nuestro webpack.config.js
y actualizar nuestras rutas:
var locals = {
routes: [
"https://css-tricks.com/",
"https://css-tricks.com/about"
]
};
Al volver a ejecutar nuestro npm start
comando, hemos creado un nuevo archivo: `build / about / index.html`. Sin embargo, esto tendrá “¡Hola!” al igual que `build / index.html` porque estamos enviando el mismo contenido a ambos archivos. Para solucionarlo, necesitaremos usar un enrutador, pero primero, necesitaremos configurar React.
Antes de hacer eso, debemos mover nuestras rutas a un archivo separado solo para mantener las cosas bien y ordenadas. Entonces en `. / Data.js` podemos escribir:
module.exports = {
routes: [
"https://css-tricks.com/",
"https://css-tricks.com/about"
]
}
Luego, necesitaremos esos datos en `webpack.config.js` y eliminaremos nuestro locals
variable:
var data = require('./data.js');
Más abajo en ese archivo actualizaremos nuestro StaticSiteGeneratorPlugin
:
plugins: [
new ExtractTextPlugin('styles.css'),
new StaticSiteGeneratorPlugin('main', data.routes, data),
]
Instalación de React
Queremos hacer muchos paquetes pequeños de HTML y CSS que luego podamos agrupar en una plantilla (como Acerca de o Página de inicio). Esto se puede hacer con react
, y react-dom
, que necesitaremos instalar:
npm i -D react react-dom babel-preset-react
Entonces necesitaremos actualizar nuestro archivo `.babelrc`:
{
"presets": ["es2016", "react"]
}
Ahora, en una nueva carpeta, `/ src / templates`, necesitaremos crear un archivo` Main.js`. Aquí será donde residen todas nuestras marcas y será donde vivirán todos los activos compartidos para nuestras plantillas (como todo en el <head>
y nuestro sitio <footer>
:
import React from 'react'
import Head from '../components/Head'
export default class Main extends React.Component {
render() {
return (
<html>
<Head title="React and CSS Modules" />
<body>
{/* This is where our content for various pages will go */}
</body>
</html>
)
}
}
Hay dos cosas a tener en cuenta aquí: Primero, si no está familiarizado con la sintaxis JSX que usa React, entonces es útil saber que el texto dentro del body
elemento es un comentario. También es posible que haya notado ese extraño <Head />
elemento, que no es un elemento HTML estándar, es un componente de React y lo que estamos haciendo aquí es pasarle datos a través de su title
atributo. Aunque no es un atributo, es lo que se conoce en el mundo de React como accesorios.
Ahora también necesitamos crear un archivo `src / components / Head.js`:
import React from 'react'
export default class Head extends React.Component {
render() {
return (
<head>
<title>{this.props.title}</title>
</head>
)
}
}
Podríamos poner todo ese código de `Head.js` en` Main.js`, pero es útil dividir nuestro código en partes más pequeñas: si queremos un pie de página, entonces crearíamos un nuevo componente con `src / components / Footer .js` y luego importarlo a nuestro archivo `Main.js`.
Ahora, en `src / index.js`, podemos reemplazar todo con nuestro nuevo código React:
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import Main from './templates/Main.js'
module.exports = function render(locals, callback) {
var html = ReactDOMServer.renderToStaticMarkup(React.createElement(Main, locals))
callback(null, '<!DOCTYPE html>' + html)
}
Lo que esto hace es importar todo nuestro marcado de `Main.js` (que posteriormente importará el Head
React componente) y luego renderizará todo esto con React DOM. Si corremos npm start
una vez más y revise `build / index.html` en esta etapa, luego encontraremos que React ha agregado nuestro componente React` Main.js`, junto con el componente Head, y luego lo convierte todo en marcado estático.
Pero ese contenido todavía se está generando tanto para nuestra página Acerca de como para nuestra Página de inicio. Traigamos nuestro enrutador para arreglar esto.
Configuración de nuestro enrutador
Necesitamos entregar ciertos fragmentos de código a ciertas rutas: en la página Acerca de necesitamos contenido para la página Acerca de, y también en una Página de inicio, Blog o cualquier otra página que queramos tener. En otras palabras, necesitamos un poco de software para controlar el contenido: un enrutador. Y por esto podemos dejar react-router
haz todo el trabajo pesado por nosotros.
Antes de comenzar, vale la pena señalar que en este tutorial usaremos la versión 2.0 de React Router y hay una gran cantidad de cambios desde la versión anterior.
Primero necesitamos instalarlo, porque React Router no viene incluido con React por defecto, así que tendremos que saltar a la línea de comando:
npm i -D react-router
En el directorio `/ src` podemos crear un archivo` routes.js` y agregar lo siguiente:
import React from 'react'
import {Route, Redirect} from 'react-router'
import Main from './templates/Main.js'
import Home from './templates/Home.js'
import About from './templates/About.js'
module.exports = (
// Router code will go here
)
Queremos varias páginas: una para la página de inicio y otra para la página Acerca de, de modo que podamos seguir adelante rápidamente y crear un archivo `src / templates / About.js`:
import React from 'react'
export default class About extends React.Component {
render() {
return (
<div>
<h1>About page</h1>
<p>This is an about page</p>
</div>
)
}
}
Y un archivo `src / templates / Home.js`:
import React from 'react'
export default class Home extends React.Component {
render() {
return (
<div>
<h1>Home page</h1>
<p>This is a home page</p>
</div>
)
}
}
Ahora podemos volver a `route.js` y dentro module.exports
:
<Route component={Main}>
<Route path="https://css-tricks.com/" component={Home}/>
<Route path="https://css-tricks.com/about" component={About}/>
</Route>
Nuestro archivo `src / templates / Main.js` contiene todo el marcado circundante (como el <head>
). Los componentes React `Home.js` y` About.js` se pueden colocar dentro del <body>
elemento de `Main.js`.
A continuación, necesitamos un archivo `src / router.js`. Esto reemplazará efectivamente `src / index.js` para que pueda continuar y eliminar ese archivo y escribir lo siguiente en router.js
:
import React from 'react'
import ReactDOM from 'react-dom'
import ReactDOMServer from 'react-dom/server'
import {Router, RouterContext, match, createMemoryHistory} from 'react-router'
import Routes from './routes'
import Main from './templates/Main'
module.exports = function(locals, callback){
const history = createMemoryHistory();
const location = history.createLocation(locals.path);
return match({
routes: Routes,
location: location
}, function(error, redirectLocation, renderProps) {
var html = ReactDOMServer.renderToStaticMarkup(
<RouterContext {...renderProps} />
);
return callback(null, html);
})
}
Si no está familiarizado con lo que está sucediendo aquí, lo mejor es echar un vistazo a la introducción de Brad Westfall a React Router.
Debido a que hemos eliminado nuestro archivo `index.js` y lo hemos reemplazado con nuestro enrutador, necesitamos volver a nuestro webpack.config.js
y fijar el valor de la entry
llave:
module.exports = {
entry: './src/router',
// other stuff...
}
Y finalmente solo tenemos que ir a `src / templates / Main.js`:
export default class Main extends React.Component {
render() {
return (
<html>
<Head title="React and CSS Modules" />
<body>
{this.props.children}
</body>
</html>
)
}
}
{this.props.children}
es donde se colocará todo nuestro código de las otras plantillas. Entonces ahora podemos npm start
una vez más y deberíamos ver dos archivos generados: `build / index.html` y` build / about / index.html`, cada uno con su propio contenido respectivo.
Reimplementación de módulos CSS
Desde el <button>
es el hola mundo de CSS, vamos a crear un módulo de botones. Y aunque me quedaré con el cargador de CSS de Webpack y lo que usé en el tutorial anterior, hay alternativas.
Este es el tipo de estructura de archivos que nos gustaría en este proyecto:
/components
/Button
Button.js
styles.css
Luego, importaremos este componente React personalizado a una de nuestras plantillas. Para hacer eso, podemos seguir adelante y crear un nuevo archivo: `src / components / Button / Button.js`:
import React from 'react'
import btn from './styles.css'
export default class CoolButton extends React.Component {
render() {
return (
<button className={btn.red}>{this.props.text}</button>
)
}
}
Como aprendimos en el tutorial anterior, el {btn.red}
className se está sumergiendo en el CSS desde styles.css
y encontrar el .red
class, Webpack generará nuestro nombre de clase de módulos CSS gobbledygook.
Ahora podemos hacer algunos estilos simples en `src / components / Button / styles.css`:
.red {
font-size: 25px;
background-color: red;
color: white;
}
Y finalmente podemos agregar ese componente Button a una página de plantilla, como `src / templates / Home.js`:
import React from 'react'
import CoolButton from '../components/Button/Button'
export default class Home extends React.Component {
render() {
return (
<div>
<h1>Home page</h1>
<p>This is a home page</p>
<CoolButton text="A super cool button" />
</div>
)
}
}
Uno mas npm start
¡y ahí lo tenemos! Un sitio React estático donde podemos agregar rápidamente nuevas plantillas, componentes y tenemos el beneficio adicional de los módulos CSS para que nuestras clases ahora se vean así:
Puede encontrar una versión completa de la demostración anterior en el repositorio de módulos React y CSS. Si observa algún error en el código anterior, asegúrese de presentar un problema.
Ciertamente, hay formas en las que podríamos mejorar este proyecto, por ejemplo, podríamos agregar Browsersync a nuestro flujo de trabajo de Webpack para que no tengamos que seguir npm install
ing todo el tiempo. También podríamos agregar Sass, PostCSS y una serie de cargadores y complementos para ayudar, pero en aras de la brevedad, he decidido dejarlos fuera del proyecto por ahora.
Terminando
¿Qué hemos logrado aquí? Bueno, aunque esto parece una gran cantidad de trabajo, ahora tenemos un entorno modular para escribir código. Podríamos agregar tantos componentes como queramos:
/components
Head.js
/Button
Button.js
styles.css
/Input
Input.js
style.css
/Title
Title.js
style.css
En consecuencia, si tenemos un .large
dentro de los estilos de nuestro componente Heading, entonces no entrará en conflicto con el .large
estilos de nuestro componente Botón. Además, aún podemos usar estilos globales importando un archivo como `src / globals.css` en cada componente, o simplemente agregando un archivo CSS separado en el <head>
.
Al hacer un sitio estático con React, hemos perdido una gran cantidad de las propiedades mágicas que React nos brinda de inmediato, incluida la administración del estado, pero aún es posible servir dos tipos de sitios web con este sistema: puede crear un sitio estático sitio como te he mostrado arriba y luego mejora progresivamente todo con los superpoderes de React después del hecho.
Este flujo de trabajo es limpio y ordenado, pero hay muchos casos en los que esta combinación de módulos CSS, React y Webpack sería completamente exagerada. Dependiendo del tamaño y alcance del proyecto web, sería casi una locura dedicar el tiempo a implementar esta solución, si fuera solo una página web, por ejemplo.
Sin embargo, si hay muchas personas que contribuyen con CSS a la base de código todos los días, entonces podría ser extraordinariamente útil que los módulos CSS evitaran cualquier error que se produzca gracias a la cascada. Pero esto podría llevar a que los diseñadores tengan menos acceso al código base porque ahora también deben aprender a escribir JavaScript. También hay muchas dependencias que deben ser compatibles para que este método funcione correctamente.
¿Significa esto que todos usaremos módulos CSS en un futuro próximo? No lo creo porque, como ocurre con todas las técnicas de interfaz, la solución depende del problema y no todos los problemas son iguales.
Serie de artículos:
- ¿Qué son los módulos CSS y por qué los necesitamos?
- Introducción a los módulos CSS
- React + CSS Modules = 😍 (¡Estás aquí!)