Webpack 4 Tips

September 23, 2019 • ☕️☕️ 11 minutos de lectura

¿Que es Webpack?

Esquema Webpack”

Webpack es un bundler, nos ayudará a empaquetar y optimizar todos los paquetes con dependencias y crear un archivo con todo eso. Webpack no se limita a js, también puede cargar todo tipo de archivos. imágenes, css, saas, txt…

Lo primero que haremos será instalar las dependencias como desarrollo.

npm i webpack webpack-cli --save--dev

Conceptos básicos

  • Entry: Aquí indicaremos cual es nuestro entry point, de forma predeterminada su valor es ./src/index.js. Webpack descubrirá de qué otros módulos y bibliotecas depende ese entry point (directa e indirectamente).
module.exports = {
  entry: "./src/index.js",
}

Podemos pasarle varios entry points, de esta manera optimizaremos el tamaño de nuestros ficheros.

module.exports = {
  entry: {
    home: "./src/index.js",
    contact: "src/contact.js",
  },
}
  • Output: output indica a webpack dónde se crearán los paquetes y como se nombrarán a estos archivos. Su valor predeterminado es ./dist/main.js.
  • Loaders: Los Loaders permiten que webpack procese otros tipos de archivos y los convierta en módulos válidos que su aplicación pueda consumirlos sin problemas en nuestra aplicación, ya se cargar JavaScript moderno con babel-loader, CSS con css-loader. Reciben dos propiedades. _ test: Es una expresión regular identifica los archivos que vamos a transformar. En nuestro caso archivos todos los archivos .txt _ use: Indica qué loader se encargará de hacer la transformación. Además puede recibir opciones. _ exclude Expresión regular que indica a Webpack qué ruta debe ignorarse, muy comun evitar node_modules. _ options Este campo varía dependiendo del loader. _ mode Podemos establecerlo en development, productiono bien none, puede habilitar las optimizaciones integradas de webpack que corresponden a cada entorno. Por defecto usará production.

webpack.config.js

const path = require("path")

module.exports = {
  output: {
    filename: "app.bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.txt$/,
        use: "raw-loader",
      },
    ],
  },
}
  • Plugins: Los plugins se encargan de realizar tareas como la optimización de paquetes, la gestión de activos y la inyección de variables de entorno.

Para usar los plugins debemos usar require() para importar el plugin y luego debemos llamarlo con el operador new.

webpack.config.js

const HtmlWebpackPlugin = require("html-webpack-plugin") //installed via npm
const webpack = require("webpack") //to access built-in plugins

module.exports = {
  module: {
    rules: [{ test: /\.txt$/, use: "raw-loader" }],
  },
  plugins: [new HtmlWebpackPlugin({ template: "./src/index.html" })],
}

Webpack sin archivo de configuración

A partir de Webpack 4, podemos configurar nuestro proyecto sin crear ningún fichero de configuraci on para webpack.config.js, gracias a webpack-cli podemos usar la linea de comandos para ejecutar nuestra configuración. Personalemente me gusta más crear mi archivo de configuración.

webpack --entry ./index.js --output ./bundle.js --mode development

Webpack con archivo de Configuracion

const path = require("path")

module.exports = {
  entry: "./index.js",
  mode: "development",
  output: {
    path: path.resolve(__dirname),
    filename: "bundle.js",
  },
}

Además deberemos crear nuestro index.html, que será el encargado de cargar nuestro bundle.js

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Mi App</title>
  </head>
  <body>
    <script scr="bundle.js"></script>
  </body>
</html>

Para ejecutarlo basta podemos hacerlo de varias formas.

npx webpack
npx webpack --mode development

O bien añadirlo a nuestro package.json en scripts.

"scripts": {
    //...
    "build": "webpack"
  },
npm run build

Por defecto webpack ejecuta webpack.config.js si queremos usar otro fichero, debemos usar la flag --config ./webpack.dev.config. Puedes nombrar el fichero con el nombre que quieras.

"scripts": {
    "build:dev": "webpack --config ./webpack.dev.config.js"
  },

Cargando varios entry points

Si queremos usar varios entry points en nuestra aplicación, podemos hacer con unos cambios muy sutiles, gracias a webpack.

Ahora entry llamará a un objeto en vez de una string.

const path = require("path")

module.exports = {
  entry: {
    home: path.resolve(__dirname, "src/index.js"),
    contact: path.resolve(__dirname, "src/contact.js"),
  },
  mode: "development",
  output: {
    path: path.resolve(__dirname),
    filename: "js/[name].js",
  },
}

Recuerda que debemos crear nuestros archivos html y añadirle a cada uno su etiqueta <script>.

Loaders

Los loaders, nos ayudan a que javascript interprete nuestros tipos de archivos.

Por ejemplo css-loader nos permitirá llamar a los css dentro de javascript con ìmport './css/style.css. Antes que anda, debemos instalar el loader con npm.

⚠️Esto no hará que puedas usar css, necesitas style-loader

npm install --save-dev --save-exact css-loader
const path = require("path")

module.exports = {
  entry: {
    home: path.resolve(__dirname, "src/index.js"),
    contact: path.resolve(__dirname, "src/contact.js"),
  },
  mode: "development",
  output: {
    path: path.resolve(__dirname),
    filename: "js/[name].js",
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: "css-loader",
      },
    ],
  },
}

Gracias a css-loader, nuestra app puede leer css, pero aun no podemos usarlo, para ello debemos instalar style-loader, lograremos esto.

npm install style-loader --save-exact --save--dev
const path = require("path")

module.exports = {
  entry: {
    home: path.resolve(__dirname, "src/index.js"),
    contact: path.resolve(__dirname, "src/contact.js"),
  },
  mode: "development",
  output: {
    path: path.resolve(__dirname),
    filename: "dist/js/[name].js",
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
}

Plugins

Extienden las habiliades de los loaders…

npm install mini-css-extract-plugin --save-exact --save-dev
const path = require("path")
const MiniCSSExtractPlugin = require("mini-css-extract-plugin")

module.exports = {
  entry: {
    home: path.resolve(__dirname, "src/index.js"),
    contact: path.resolve(__dirname, "src/contact.js"),
  },
  mode: "development",
  output: {
    path: path.resolve(__dirname),
    filename: "dist/js/[name].js",
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          {
            loader: MiniCSSExtractPlugin.loader,
          },
          "css-loader",
        ],
      },
    ],
  },
  plugins: [
    new MiniCSSExtractPlugin({
      filename: "dist/css/[name].css",
    }),
  ],
}

Existe otro plugin llamamo html-webpack-plugin que nos genera los archivos html necesarios y les inyecta todo lo necesario.

npm install html-webpack-plugin --save-exact --save-dev
const path = require("path")
const MiniCSSExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")

module.exports = {
  entry: {
    home: path.resolve(__dirname, "src/index.js"),
  },
  mode: "development",
  output: {
    path: path.resolve(__dirname),
    filename: "dist/js/[name].js",
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          {
            loader: MiniCSSExtractPlugin.loader,
          },
          "css-loader",
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: path.resolve(__dirname) + "/dist/index.html",
      title: "Hola Mundo webpack",
    }),
    new MiniCSSExtractPlugin({
      filename: "dist/css/[name].css",
    }),
  ],
}

Si quisieramos hacerlo con varios entrie points, sería de la siguiente forma.

🤔Estoy seguro que tiene que existir una forma más “pro” de hacerlo… si la sabes, escríbeme un mensaje @enmaska.

const path = require("path")
const MiniCSSExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")

module.exports = {
  entry: {
    home: path.resolve(__dirname, "src/index.js"),
  },
  mode: "development",
  output: {
    path: path.resolve(__dirname),
    filename: "dist/js/[name].js",
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          {
            loader: MiniCSSExtractPlugin.loader,
          },
          "css-loader",
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: path.resolve(__dirname) + "/dist/index.html",
      title: "Hola Mundo webpack",
    }),
    new MiniCSSExtractPlugin({
      filename: "dist/css/[name].css",
    }),
  ],
}

Recargar en caliente

npm install webpack-dev-server --save-dev
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const webpack = require("webpack")

module.exports = {
  devServer: {
    hot: true,
    open: true,
    contentBase: path.join(__dirname, "dist"),
    compress: true,
    port: 9001,
  },
  entry: {
    home: path.resolve(__dirname, "src/index.js"),
  },
  mode: "development",
  output: {
    path: path.resolve(__dirname),
    filename: "dist/js/[name].js",
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      filename: path.resolve(__dirname) + "/dist/index.html",
      title: "Hola Mundo webpack",
    }),
  ],
}

Babel

Si queremos que nuestra App pueda leer código moderno, deberemos instalar babel, un loader que convierte el codigo moderno de js a common js, para que todos los navegadores puedan ejecutar el código.

npm install --save-dev --save-exact @babel/core babel-loader @babel/preset-env
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const webpack = require("webpack")

module.exports = {
  devServer: {
    hot: true,
    open: true,
    contentBase: path.join(__dirname, "dist"),
    compress: true,
    port: 9001,
  },
  entry: {
    home: path.resolve(__dirname, "src/index.js"),
  },
  mode: "development",
  output: {
    path: path.resolve(__dirname),
    filename: "dist/js/[name].js",
  },
  module: {
    rules: [
      {
        test: /\.js$/i,
        use: ["babel-loader"],
        exclude: "/node_modules/",
      },
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      filename: path.resolve(__dirname) + "/dist/index.html",
      title: "Hola Mundo webpack",
    }),
  ],
}

Además deberemos crear un archivo llamado .babelrc en el cual indicaremos las especificaciones de babel que queramos usar.

{
  "presets": ["preset-env"]
}

Añadiendo soporte a React

Para esto debemos instalar un nuevo preset de babel, en este caso @babel/preset-react

npm install --save-dev @babel/preset-react
{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

Ahora podemos crear un archivo para index.js con react y podrá leerlo sin problemas.

npm install react react-dom --save
import React from "react"
import ReactDOM from "react-dom"

const App = () => {
  return <div>Hola Mundo React,Webpack 4 & Babel!</div>
}

ReactDOM.render(<App />, document.querySelector("#app"))

También modificaremos el webpack.config.js para añadir a HotModuleReplacementPlugin la propiedad template, que se encargará de decirle que use la plantilla que le indiquemos en la ruta, como base para generar el html final.

const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const webpack = require("webpack")

module.exports = {
  devServer: {
    hot: true,
    open: true,
    contentBase: path.join(__dirname, "dist"),
    port: 9001,
  },
  entry: {
    home: path.resolve(__dirname, "src/index.js"),
  },
  mode: "development",
  output: {
    path: path.resolve(__dirname),
    filename: "dist/js/[name].js",
  },
  module: {
    rules: [
      {
        test: /\.js$/i,
        use: ["babel-loader"],
        exclude: "/node_modules/",
      },
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname) + "/src/index.html",
      filename: path.resolve(__dirname) + "/dist/index.html",
      title: "Hola Mundo webpack con React",
    }),
  ],
}

Nuestra plantilla la guardaremos en src/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Mi App con React</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

Soporte a imágenes y otros ficheros

Para añadir soporte a imágenes y otros ficheros, necesitamos url-loader que se encargará de convertir los ficheros que le indiquemos en las opciones a base64 que si lo lee javascript.

npm install --save-dev url-loader

Como siempre, volvemos a modificar el webpack.config.js para añadir las nuevas reglas.

const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const webpack = require("webpack")

module.exports = {
  devServer: {
    hot: true,
    open: true,
    contentBase: path.join(__dirname, "dist"),
    port: 9001,
  },
  entry: {
    home: path.resolve(__dirname, "src/index.js"),
  },
  mode: "development",
  output: {
    path: path.resolve(__dirname),
    filename: "dist/js/[name].js",
  },
  module: {
    rules: [
      {
        test: /\.js$/i,
        use: ["babel-loader"],
        exclude: "/node_modules/",
      },
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.jpg|png|gif|svg|mp4|woff|eot|tttf$/i,
        use: {
          loader: "url-loader",
          options: {
            limit: 90000,
          },
        },
      },
    ],
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname) + "/src/index.html",
      filename: path.resolve(__dirname) + "/dist/index.html",
      title: "Hola Mundo webpack con react",
    }),
  ],
}

Añadiendo manifest

npm install --save-dev webpack-pwa-manifest
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const webpack = require("webpack")
var WebpackPwaManifest = require("webpack-pwa-manifest")

module.exports = {
  devServer: {
    hot: true,
    open: true,
    contentBase: path.join(__dirname, "dist"),
    port: 9001,
  },
  entry: {
    home: path.resolve(__dirname, "src/index.js"),
  },
  mode: "development",
  output: {
    path: path.resolve(__dirname),
    filename: "dist/js/[name].js",
  },
  module: {
    rules: [
      {
        test: /\.js$/i,
        use: ["babel-loader"],
        exclude: "/node_modules/",
      },
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.jpg|png|gif|svg|mp4|woff|eot|tttf$/i,
        use: {
          loader: "url-loader",
          options: {
            limit: 90000,
          },
        },
      },
    ],
  },
  plugins: [
    new WebpackPwaManifest({
      name: "Mi webpack App con React y Manifest",
      shortname: "Webpack App",
      description: "Web con webpack que soporta react y tiene manifest",
      background_color: "#fff",
      theme_color: "#000",
      icons: [
        {
          src: path.resolve("src/assets/icon.png"),
          sizes: [96, 128, 192, 256, 384, 512],
        },
      ],
    }),
    new webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname) + "/src/index.html",
      filename: path.resolve(__dirname) + "/dist/index.html",
      title: "Hola Mundo webpack con react",
    }),
  ],
}

Añadiendo service worker

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Mi App con React</title>
  </head>
  <body>
    <div id="app"></div>

    <noscript>
      <h3>Esta app necesita Javascript para funcionar</h3>
    </noscript>
    <script>
      if ("serviceWorker" in navigator) {
        window.addEventListener("load", function() {
          navigator.serviceWorker
            .register("/service-worker.js")
            .then(registration => {
              console.log("SW registrado")
            })
            .catch(registrationError => {
              console.log("SW error", registrationError)
            })
        })
      }
    </script>
  </body>
</html>

Soportar asyn/await

No podemos usar async/await de manera nativa, así que tendremos que añadir babel-polyfill o regenerator-runtime. Opté por usar regenerator-runtime, ya que a partir de Babel 7.4.0 babel-polyfill está en desuso.

En esta ocasión no modificaremos webpack, símplemente lo importaremos en nuestro archivo raiz, en este caso index.js

npm i regenerator-runtime
// CommonJS
require("regenerator-runtime/runtime")

// ECMAScript 2015
import "regenerator-runtime/runtime"

Configuración optima para React.

Si queremos crear una aplicación con React sin usar create-react-app, podemos hacerlo con webpack. Esto es lo recomendable si quieres tener todo el control sobre nuestro sitio, además prodemos optimizarlo usando solo lo que necesitamos.

Por ejemplo este archivo de configuración no carga loaders de css, ya que si voy a usar styled-components esto no será necesario. Si en algún punto de nuestra aplicación queremos añadirlo podremos hacerlo fácilmente.

npm i webpack webpack-cli --save--dev
npm install html-webpack-plugin --save-exact --save-dev
npm install --save-dev --save-exact @babel/core babel-loader @babel/preset-env @babel/preset-react
npm install --save-dev @babel/plugin-syntax-dynamic-import

webpack.config.dev

const HtmlWebpackPlugin = require("html-webpack-plugin")

const path = require("path")

module.exports = {
  output: {
    filename: "app.bundle.js",
    publicPath: "/",
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html",
    }),
  ],
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            plugins: ["@babel/plugin-syntax-dynamic-import"],
            presets: ["@babel/preset-env", "@babel/preset-react"],
          },
        },
      },
    ],
  },
}