esbuild - Desarrollo sin dolor.
Tags: webdev, javascript, tutorial, spanish
Published: 2022-06-08 19:53:11

## Herramientas:
- Node v16
- [Fastify](https://www.fastify.io/) v4.0 (Puedes usar el server que quieras [express, koa, nest])
- [esbuild](https://esbuild.github.io/) v0.14
- npm v8.5 / yarn v1.22

## Porque esbuild?
Nuestras actuales herramientas de compilación para la web son entre 10 y 100 veces más lentas de lo que podrían ser. El objetivo principal del proyecto esbuild bundler es traer una nueva era de rendimiento de la herramienta de construcción, y crear un bundler moderno y fácil de usar en el camino.

## Empecemos 🚀🚀

### Crear carpeta del proyecto
```sh
$ mkdir esbuild-test
$ cd esbuild-test

# npm
$ npm init -y

# yarn
$ yarn init -y
```

### Instalaciones
```sh
# npm
$ npm i esbuild fastify

# yarn
$ yarn add esbuild fastify
```

### Quickstart
```js
// index.js
import Fastify from 'fastify'

Fastify()
    .get('/', (_request, reply) => reply.send({ hello: 'world' }))
    .listen({ port: 3000 }, (_error, address) => console.log(`Listening on ${address}`))

```

### Creamos los scripts
Explicación de los flags:
- [minify](https://esbuild.github.io/api/#minify): El código minificado generalmente es equivalente al código de desarrollo pero es más pequeño, lo que significa que se descarga más rápido pero es más difícil de depurar. Minifica el código en producción pero no en desarrollo.

- [bundle](https://esbuild.github.io/api/#bundle): Agrupa cualquier dependencia importada en el archivo de entrada (en este caso index.js).
- [platform](https://esbuild.github.io/api/#platform): Por defecto, el bundler de esbuild está configurado para generar código destinado al navegador. Si su código empaquetado está destinado a ejecutarse en node en su lugar, debe establecer la plataforma a node.
- [external](https://esbuild.github.io/api/#external): Puede marcar un archivo o un paquete como externo para excluirlo de su construcción. En lugar de ser empaquetado, se conservará la importación (usando require para los formatos iife y cjs y usando import para el formato esm) y se evaluará en tiempo de ejecución en su lugar.
- [outfile](https://esbuild.github.io/api/#outfile): Esta opción establece el nombre del archivo de salida para la operación de construcción. Sólo es aplicable si hay un único punto de entrada. Si hay varios puntos de entrada, debe utilizar la opción outdir para especificar un directorio de salida.

```json
// package.json
{
  "name": "esbuild-test",
  "version": "1.0.0",
  "license": "MIT",
  "scripts": {
    "start": "node dist",
    "build": "esbuild index.js --minify --bundle --platform=node --target=node16 --external:./node_modules/* --outfile=dist/index.js"
  },
  "dependencies": {
    "esbuild": "^0.14.43",
    "fastify": "^4.0.0"
  }
}
```

### Ejecutamos los comandos
```sh
# npm
$ npm run build
$ npm start

# yarn
$ yarn build
$ yarn start
```
![terminal1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gyhew3fkyu4n9l8rygdt.png)

### Hot Reload
Ya creamos nuestro server, pero para ser un buen ambiente de desarrollo esto debería de soportar hot reload, sino que martirio tener que estar reiniciando el servidor manualmente.
Para esto esbuild ya cuenta con un [watch](https://esbuild.github.io/api/#watch) que re-construye el proyecto al guardar un archivo después de modificarlo.
Pero veámoslo en acción. si actualizamos nuestro script **dev** agregando este nuevo flag:
```json
// package.json
{
  "name": "esbuild-test",
  "version": "1.0.0",
  "license": "MIT",
  "scripts": {
    "start": "node dist",
    "dev": "npm run build -- --watch", // <- Justo aqui
    "build": "esbuild index.js --minify --bundle --platform=node --target=node16 --external:./node_modules/* --outfile=dist/index.js"
  },
  "dependencies": {
    "esbuild": "^0.14.43",
    "fastify": "^4.0.0"
  }
}
```
Ahora si lo corremos y modificamos nuestro index.js esbuild se encargara de re-transpilar nuestro codigo.
```sh
# npm
$ npm run dev

# yarn
$ yarn dev
```

![terminal2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zw8wplmh58luvrxdybwb.png)
 
Pero tenemos un detalle, este servidor no se levanta.
Esto funcionara si ejecutamos **start** justo después de correr **build** con watch? intentemos.
```json
// package.json
{
  "name": "esbuild-test",
  "version": "1.0.0",
  "license": "MIT",
  "scripts": {
    "start": "node dist",
    "dev": "npm run build -- --watch && npm start", // <- Justo aqui
    "build": "esbuild index.js --minify --bundle --platform=node --target=node16 --external:./node_modules/* --outfile=dist/index.js"
  },
  "dependencies": {
    "esbuild": "^0.14.43",
    "fastify": "^4.0.0"
  }
}
```
Corremos **dev**.
```sh
# npm
$ npm run dev

# yarn
$ yarn dev
```

![terminal3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sdbxt473q8ez718pg3q2.png)
 
Parece que nunca llegamos a **start**. Claro, por que watch deja colgado el evento y la terminal no avanza al siguiente comando. Es hora de usar la API de JavaScript en vez del CLI. creamos un **esbuild.dev.js** en la raíz de nuestro proyecto y activaremos las mismas flags que en el CLI.
Para esta parte el watch recibe un objeto con una función onRebuild, la cual se ejecutara cada vez que se actualicen los archivos.
```js
// esbuild.dev.js
require('esbuild').build({
    entryPoints: ['./index.js'],
    watch: {
        onRebuild: () => console.log('\nRebuilt...\n')
    },
    bundle: true,
    minify: true,
    platform: 'node',
    target: 'node16',
    external: ['/node_modules/*'],
    outfile: './dist/index.js',
})
    .then(() => console.log("\nDone 🚀\n"))
    .catch(() => process.exit(1))
```
### Lanzando procesos con node
Para este ultimo paso usaremos [child_process](https://nodejs.org/api/child_process.html) de Node.js, en pocas palabras esto nos permite lanzar procesos desde Node.js, así que actualicemos nuestro **esbuild.dev.js**.
```js
// esbuild.dev.js
const { spawn } = require('child_process')

let server;

require('esbuild').build({
    entryPoints: ['./index.js'],
    watch: {
        onRebuild: () => {
            console.log('\nRebuilt...\n')
            if (server) server.kill('SIGINT') // Si ya hay un servidor ejecutandose, lo matamos
            server = spawn('node', ['./dist'], { stdio: 'inherit' }) // Al terminar el re-build lanza el servidor
        }
    },
    bundle: true,
    minify: true,
    platform: 'node',
    target: 'node16',
    external: ['/node_modules/*'],
    outfile: './dist/index.js',
})
    .then(() => {
        console.log("\nDone 🚀\n")
        server = spawn('node', ['./dist'], { stdio: 'inherit' }) // Al terminar el build lanza el servidor
    })
    .catch(() => process.exit(1))
```
Probemoslo. Modifica el comando **dev**.
```json
// package.json
{
  "name": "esbuild-test",
  "version": "1.0.0",
  "license": "MIT",
  "scripts": {
    "start": "node dist",
    "dev": "node esbuild.dev.js", // <- Justo aqui
    "build": "esbuild index.js --minify --bundle --platform=node --target=node16 --external:./node_modules/* --outfile=dist/index.js"
  },
  "dependencies": {
    "esbuild": "^0.14.43",
    "fastify": "^4.0.0"
  }
}
```

![terminal4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/biazxmbnvor34chpr6ux.png)

## Listo 🚀
esbuild nos ah servido para remplazar:
- Webpack
- Nodemon
- tsc

Espera un segundo... TSC? Claro, esbuild también soporta Typescript ([más info](https://esbuild.github.io/content-types/#typescript)). desarrollo sin dolor = Typescript. Solo cambiemos la extensión de **index.js** -> **index.ts** y el punto de entrada en nuestro **esbuild.dev.js**:
```js
// esbuild.dev.js
/* ... code */
require('esbuild').build({
    entryPoints: ['./index.ts'], // <- Justo aqui
    watch: {
        onRebuild: () => {
            /* ... code */
        }
    },
    bundle: true,
    minify: true,
    platform: 'node',
    target: 'node16',
    external: ['/node_modules/*'],
    outfile: './dist/index.js',
})
/* ... code */
```
Y listo. Ejecutamos **dev**. Todo va sobre ruedas e igual de rápido.

Así debería quedar nuestro código al final.
![terminal5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/npvksuzzp2p2ubizcrci.png)

### Conclusión
Esta vez explicamos todo paso a paso pero siendo sinceros te tomaría un copiar y pegar para levantar esto en tu próximo proyecto.

Déjame en los comentarios que piensas

y Happy Hacking 👨‍💻🎉