Entrada
Webpack

Cargar archivos dinámicamente


Cargar archivos dinámicamente

En ocasiones, cuando necesitamos importar varios archivos de una carpeta (por ejemplo, imágenes, íconos o módulos), resulta poco práctico tener que importarlos uno por uno. Para resolverlo, Webpack nos ofrece la función require.context, una herramienta poderosa, ya que permite crear contexto dinámico de importación para todos los archivos dentro de una carpeta específica, sin necesidad de importarlos uno por uno.

¿Qué hace require.context?

En Webpack require.context es una función que permite crear un “contexto dinámico” para importar múltiples módulos a la vez desde de una carpeta o directorio específico (incluso dentro de subcarpetas). A diferencia de las importaciones estáticas de (ES6) (ES6) ES6, también conocido como ECMAScript 2015, es la sexta versión del estándar ECMAScript, en el cual se basa el lenguaje de programación JavaScript , que requiren que declares explícitamente cada archivo.

Sintaxis Básica:

1
2
3
4
5
require.context(
  directory,      // ¿Dónde buscar?
  recursive,      // ¿Buscar en subcarpetas?
  pattern         // ¿Qué archivos buscar?
);
  • directory: La ruta relativa a la carpeta donde Webpack debe buscar los archivos.
  • recursive: Un valor booleano (true o false) que indica si Webpack debe buscar en las subcarpetas dentro de directory.
  • pattern: Una expresión regular que define los archivos que deben ser incluidos (por ejemplo, \.js$ para todos los archivos JavaScript).

Iniciar con el starter template

Para explorar el uso de require.context, vamos a utilizar un template preconfigurado que incluye toda la configuración básica necesaria de webpack:

Usa este template

New starter template New starter template

Una vez creado el nuevo repositorio, clónalo en tu equipo local y luego instala las dependencias necesarias para el proyecto:

1
npm install

Finalizada la instalación, corremos el servidor de desarrollo:

1
npm run dev

A continuación, puedes reproducir el video y revisar cómo configurar el starter template:

Casos prácticos para require.context

1. Galería de imágenes

Lo primero es abrir el proyecto con VS Code, o con el editor de tu preferencia. Al hacerlo, notarás que dentro de la carpeta src existe una subcarpeta llamada assets. Ahí es donde puedes agregar las imágenes que desees utilizar para la galería.

En la carpeta config/ se encuentra el archivo webpack.common.js y verás que tenemos configurado el Asset Module tanto para el entorno de desarrollo como para producción. Esto es necesario para poder cargar imágenes correctamente en el proyecto. configuración de asset module configuración de asset module

Si quieres usar las mismas imágenes que se muestran en este ejemplo, puedes descargarlas directamente desde el siguiente enlace y colocarlas dentro de src/assets/tech-logos:

Descargar imágenes (.zip)

Una vez que tengas las imágenes en tu proyecto, asegúrate de que la estructura de carpetas quede así:

assets para la galería assets para la galería

Ahora que tenemos las imágenes de los logos en la carpeta src/assets/tech-logos-svg. El siguiente paso será crear una función que importe automáticamente esos archivos sin tener que escribir una importación por cada archivo. Dentro de la carpeta src, crea un nuevo archivo llamado gallery.js y escribe o copia el siguiente código

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
export function Gallery() {
  // Creamos el contenedor donde mostraremos las imágenes
  const galleryContainer = document.createElement('div');

  /**
   * require.context(directorio, incluirSubdirectorios, expresiónRegular)
   * 
   * - directorio: ruta relativa desde el archivo actual
   * - incluirSubdirectorios: si se deben explorar carpetas dentro de la ruta
   * - expresiónRegular: patrón para coincidir con los archivos deseados
   */
  const logosContext = require.context('./assets/tech-logos-svg', false, /\.svg$/);

  // Obtenemos un array con las rutas procesadas por Webpack
  const logos = logosContext.keys().map(logosContext);

  // Recorremos cada imagen y la insertamos en el contenedor
  logos.forEach(src => {
    const img = document.createElement('img');
    img.src = src;              // Ruta de la imagen procesada por Webpack
    img.alt = 'Tech logo';      // Texto alternativo para accesibilidad
    img.style.width = '100px';
    img.style.margin = '10px';
    img.style.objectFit = 'contain';
    img.style.transition = 'transform 0.2s ease';

    // Efecto visual simple al pasar el mouse
    img.addEventListener('mouseenter', () => img.style.transform = 'scale(1.1)');
    img.addEventListener('mouseleave', () => img.style.transform = 'scale(1)');

    galleryContainer.appendChild(img);
  });

  return galleryContainer;
}

El resultado puede variar según los estilos que tengas definidos, pero lo importante es que ya contamos con la funcionalidad completa: las imágenes se cargan automáticamente desde la carpeta assets/tech-logos-svg gracias a require.context, sin necesidad de escribir código adicional o modificar rutas manualmente cada vez que se agregan nuevos archivos.

assets para la galería

2. Generador de sitio estático

Supongamos que estás escribiendo un generador de sitios estáticos simple, donde el contenido se organiza en archivos Markdown. La idea es poder cargar automáticamente todos los archivos .md de una carpeta y convertirlos en secciones HTML, sin tener que importar cada archivo manualmente.

Podrías modelar el contenido de tu sitio dentro de una estructura de directorios, creando un directorio .pages/ que contenga los archivos Markdown.

1
2
3
4
5
6
7
8
src/
 ├─ assets/
 ├─ pages/
 │   ├─ intro.md
 │   ├─ webpack-tips.md
 │   └─ about.md
 ├─ index.js
 └─ markdownLoader.js

Antes de continuar, es necesario que instalemos y configuremos otros 2 loaders:

1
npm install --save-dev html-loader markdown-loader
  • markdown-loader: convierte Markdown en HTML.
  • html-loader: permite que Webpack importe HTML como módulos JS.

Y ahora añadimos la siguiente regla:

Siguiendo con la estructura del starter template, añadelo en config/webpack.common.js que corresponde a las configuraciones que comparten ambos entornos.

1
2
3
4
5
6
7
{
  test: /\.md$/,
  use: [
    { loader: 'html-loader' },
    { loader: 'markdown-loader' }
  ]
}

Cada uno de estos archivos tendría un (front matter) (front matter) Sección de metadatos que aparece al principio de un archivo para sus metadatos. La URL de cada página podría determinarse a partir del nombre del archivo y mapearse. Para modelar la idea usando require.context, se podría considerar la siguiente estructura propuesta en el previsualizador de archivos:

FOLDERS
    Sin archivo x
              
            
              # Introducción  
    Bienvenido a este sitio generado dinámicamente con Webpack y require.context.
    
            
          
              
            
              # Acerca de  
    Este es un ejemplo educativo para entender cómo cargar archivos Markdown automáticamente.
    
            
          
              
            
              .markdown-list {
      padding: 20px;
      max-width: 700px;
      margin: auto;
    }
    
    .markdown-list h1 {
      color: #003547;
      margin-bottom: 10px;
    }
    
    .markdown-list section {
      border-bottom: 1px solid #ddd;
      margin-bottom: 20px;
      padding-bottom: 10px;
    }
    
            
          
              
            
              // Carga todos los archivos Markdown desde /content
    const markdownFiles = require.context('./content', false, /\.md$/);
    
    export default function MarkdownLoader() {
      const container = document.createElement('div');
      container.classList.add('markdown-list');
    
      markdownFiles.keys().forEach((path) => {
        const section = document.createElement('section');
        const content = markdownFiles(path);
        section.innerHTML = content.default || content;
        container.appendChild(section);
      });
    
      return container;
    }
    
            
          
              
            
              import './styles/markdown.css';
    import MarkdownLoader from './markdownLoader.js';
    
    const root = document.querySelector('#root');
    root.appendChild(MarkdownLoader());
    
            
          
              
            
              const { merge } = require('webpack-merge');
    const common = require('./webpack.common');
    
    /** @type {import('webpack').Configuration} */
    const devConfig = {
      mode: 'development',
      devtool: 'eval-source-map',
      module: {
        rules: [
          {
            test: /\.css$/,
            use: ['style-loader', 'css-loader']
          },
        ],
      },
      devServer: {
        hot: true,
        open: true,
        port: 4000,
      }
    };
    
    module.exports = merge(common, devConfig);
    
            
          
              
            
              const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    /** @type {import('webpack').Configuration} */
    module.exports = {
      entry: './src/index.js',
      output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, '../dist'),
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html',
        }),
      ],
      module: {
        rules: [
          {
            test: /\.js$/,
            exclude: /node_modules/,
            use: 'babel-loader',
          },
          {
            test: /\.(png|jpe?g|gif|svg)$/,
            type: 'asset/resource',
          },
          {
            test: /\.md$/,
            use: [
              { loader: 'html-loader' },
              { loader: 'markdown-loader' }
            ]
          }
        ]
      },
      resolve: {
        extensions: ['.js', '.json'],
      }
    }
    
            
          
              
            
              const { merge } = require('webpack-merge');
    const common = require('./webpack.common');
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    
    /** @type {import('webpack').Configuration} */
    const prodConfig = {
      mode: 'production',
      devtool: 'source-map',
      plugins: [
        new MiniCssExtractPlugin({
          filename: '[name].[contenthash].css',
        }),
      ],
      module: {
        rules: [
          {
            test: /\.css$/i,
            use: [MiniCssExtractPlugin.loader, "css-loader"],
          },
        ],
      },
    };
    
    module.exports = merge(common, prodConfig);
    
            
          
              
            
              <!doctype html>
    <html lang="es">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Generador de sitio estático</title>
      </head>
      <body>
        <div id="root"></div>
      </body>
    </html>
            
          

    Selecciona un archivo para ver su contenido

    En resumen:

    • require.context es una característica avanzada que a menudo está oculta tras bastidores. Úsala si necesitas realizar búsquedas entre un gran número de archivos.
    • Una importación dinámica escrita de cierta forma genera una llamada a require.context. En este caso, el código se lee un poco mejor.
    • Estas técnicas funcionan únicamente sobre el sistema de archivos. Si necesitas operar sobre URLs, deberías considerar soluciones del lado del cliente.
    Esta entrada está licenciada bajo CC BY 4.0 por el autor.