Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Webpack Encore: La Grandeza de Javascript

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Tip

Ahora la receta agrega estos dos archivos en un lugar ligeramente diferente:

  • assets/app.js
  • assets/styles/app.css

Pero el propósito de cada uno es exactamente el mismo.

Muy bien: Así es como funciona todo esto. La receta agregó una nuevo directorio assets/ con un par de archivos CSS y JS como ejemplo. El archivo app.js básicamente hace un console.log() de algo:

15 lines assets/js/app.js
/*
* Welcome to your app's main JavaScript file!
*
* We recommend including the built version of this JavaScript file
* (and its CSS file) in your base layout (base.html.twig).
*/
// any CSS you import will output into a single css file (app.css in this case)
import '../css/app.css';
// Need jQuery? Install it with "yarn add jquery", then uncomment to import it.
// import $ from 'jquery';
console.log('Hello Webpack Encore! Edit me in assets/js/app.js');

El app.css cambia el color del fondo a gris ligero:

body {
background-color: lightgray;
}

Webpack Encore está completamente configurado por un solo archivo: webpack.config.js.

var Encore = require('@symfony/webpack-encore');
// Manually configure the runtime environment if not already configured yet by the "encore" command.
// It's useful when you use tools that rely on webpack.config.js file.
if (!Encore.isRuntimeEnvironmentConfigured()) {
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}
Encore
// directory where compiled assets will be stored
.setOutputPath('public/build/')
// public path used by the web server to access the output path
.setPublicPath('/build')
// only needed for CDN's or sub-directory deploy
//.setManifestKeyPrefix('build/')
/*
* ENTRY CONFIG
*
* Add 1 entry for each "page" of your app
* (including one that's included on every page - e.g. "app")
*
* Each entry will result in one JavaScript file (e.g. app.js)
* and one CSS file (e.g. app.css) if your JavaScript imports CSS.
*/
.addEntry('app', './assets/js/app.js')
//.addEntry('page1', './assets/js/page1.js')
//.addEntry('page2', './assets/js/page2.js')
// When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
.splitEntryChunks()
// will require an extra script tag for runtime.js
// but, you probably want this, unless you're building a single-page app
.enableSingleRuntimeChunk()
/*
* FEATURE CONFIG
*
* Enable & configure other features below. For a full
* list of features, see:
* https://symfony.com/doc/current/frontend.html#adding-more-features
*/
.cleanupOutputBeforeBuild()
.enableBuildNotifications()
.enableSourceMaps(!Encore.isProduction())
// enables hashed filenames (e.g. app.abc123.css)
.enableVersioning(Encore.isProduction())
// enables @babel/preset-env polyfills
.configureBabelPresetEnv((config) => {
config.useBuiltIns = 'usage';
config.corejs = 3;
})
// enables Sass/SCSS support
//.enableSassLoader()
// uncomment if you use TypeScript
//.enableTypeScriptLoader()
// uncomment to get integrity="..." attributes on your script & link tags
// requires WebpackEncoreBundle 1.4 or higher
//.enableIntegrityHashes(Encore.isProduction())
// uncomment if you're having problems with a jQuery plugin
//.autoProvidejQuery()
// uncomment if you use API Platform Admin (composer req api-admin)
//.enableReactPreset()
//.addEntry('admin', './assets/js/admin.js')
;
module.exports = Encore.getWebpackConfig();

No hablaremos mucho sobre este archivo - lo vamos a guardar para el tutorial sobre Encore - pero ya está configurado para apuntar a los archivos app.js y app.css: Encore sabe que necesita procesarlos.

Corriendo Encore

Para ejecutar Encore, ve a tu terminal y corre:

yarn watch

Este es un atajo para correr yarn run encore dev --watch. ¿Qué hace esto? Lee esos dos archivos en assets/, hace algo de procesamiento, y emite una versión construida de cada uno dentro del nuevo directorio public/build/. Aquí está el archivo app.css ya construido... y el archivo app.js. Si corriéramos Encore en modo de producción - el cual es solamente otro comando - minificaría el contenido de cada archivo.

Incluyendo los Archivos CSS y JS Construidos

Ocurren muchas otras cosas interesantes, pero esta es la idea básica: ponemos el código en el directorio assets/, pero apuntamos a los archivos construidos en nuestros templates.

Por ejemplo, en base.html.twig, en vez de apuntar al viejo archivo app.css, queremos apuntar al que está en el directorio build/. Eso es muy simple, pero WebpackEncoreBundle tiene un atajo para hacerlo incluso más fácil: {{ encore_entry_link_tags() }} y pasa este app, porque ese es el nombre del archivo fuente - se le llama "entry" en el mundo de Webpack.

... line 1
<html>
<head>
... lines 4 - 5
{% block stylesheets %}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Spartan&display=swap">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css" integrity="sha256-mmgLkCYLUQbXn0B1SRqzHar6dCnv9oZFPEC1g1cwlkk=" crossorigin="anonymous" />
{{ encore_entry_link_tags('app') }}
{% endblock %}
</head>
... lines 13 - 33
</html>

Abajo, agrega la etiqueta script con {{ encore_entry_script_tags('app') }}.

... line 1
<html>
... lines 3 - 12
<body>
... lines 14 - 25
{% block javascripts %}
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
crossorigin="anonymous"></script>
{{ encore_entry_script_tags('app') }}
{% endblock %}
</body>
</html>

¡Vamos a probarlo! Ve al navegador y refresca. ¿Funcionó? ¡Lo hizo! El color de fondo es gris... y si abro la consola, ahí está el log:

Hello Webpack Encore!

Si miras la fuente HTML, ahí no está ocurriendo nada especial: tenemos una simple etiqueta link apuntando a /build/app.css.

Moviendo nuestro Código a Encore

Ahora que esto está funcionando, vamos a mover nuestro CSS hacia el nuevo sistema. Abre public/css/app.css, copia todo esto, luego haz click derecho y borrar el archivo. Ahora abre el nuevo app.css dentro de assets/ y pega.

127 lines assets/css/app.css
body {
font-family: spartan;
color: #444;
}
.jumbotron-img {
background: rgb(237,116,88);
background: linear-gradient(302deg, rgba(237,116,88,1) 16%, rgba(51,61,81,1) 97%);
color: #fff;
}
.q-container {
border-top-right-radius: .25rem;
border-top-left-radius: .25rem;
background-color: #efefee;
}
.q-container-show {
border-top-right-radius: .25rem;
border-top-left-radius: .25rem;
background-color: #ED7458 ;
}
.q-container img, .q-container-show img {
border: 2px solid #fff;
border-radius: 50%;
}
.q-display {
background: #fff;
border-radius: .25rem;
}
.q-title-show {
text-transform: uppercase;
font-size: 1.3rem;
color: #fff;
}
.q-title {
text-transform: uppercase;
color: #444;
}
.q-title:hover {
color: #2B2B2B;
}
.q-title h2 {
font-size: 1.3rem;
}
.q-display-response {
background: #333D51;
color: #fff;
}
.answer-link:hover .magic-wand {
transform: rotate(20deg);
}
.vote-arrows {
font-size: 1.5rem;
}
.vote-arrows span {
font-size: 1rem;
}
.vote-arrows a {
color: #444;
}
.vote-up:hover {
color: #3D9970;
}
.vote-down:hover {
color: #FF4136;
}
.btn-question {
color: #FFFFFF;
background-color: #ED7458;
border-color: #D45B3F;
}
.btn-question:hover,
.btn-question:focus,
.btn-question:active,
.btn-question.active,
.open .dropdown-toggle.btn-question {
color: #FFFFFF;
background-color: #D45B3F;
border-color: #D45B3F;
}
.btn-question:active,
.btn-question.active,
.open .dropdown-toggle.btn-question {
background-image: none;
}
.btn-question.disabled,
.btn-question[disabled],
fieldset[disabled] .btn-question,
.btn-question.disabled:hover,
.btn-question[disabled]:hover,
fieldset[disabled] .btn-question:hover,
.btn-question.disabled:focus,
.btn-question[disabled]:focus,
fieldset[disabled] .btn-question:focus,
.btn-question.disabled:active,
.btn-question[disabled]:active,
fieldset[disabled] .btn-question:active,
.btn-question.disabled.active,
.btn-question[disabled].active,
fieldset[disabled] .btn-question.active {
background-color: #ED7458;
border-color: #D45B3F;
}
.btn-question .badge {
color: #ED7458;
background-color: #FFFFFF;
}
footer {
background-color: #efefee;
}

Tan pronto como hago eso, cuando refresco... ¡Funciona! ¡Nuestro CSS está de vuelta! La razón es que - si revisas tu terminal - yarn watch está observando a nuestros archivos por cambios. Tan pronto modificamos el archivo app.css, esto vuelve a leer el archivo y arroja una nueva versión dentro del directorio public/build. Esa es la razón por la cual corremos esto en segundo plano.

Hagamos lo mismo para nuestro JavaScript particular. Abre question_show.js y, en vez de tener un archivo JavaScript específico por página - donde solo incluimos esto en nuestra página "show" - para mantener las cosas simples, voy a poner esto dentro del nuevo app.js, el cual es cargado en cada página.

29 lines assets/js/app.js
... lines 1 - 13
/**
* Simple (ugly) code to handle the comment vote up/down
*/
var $container = $('.js-vote-arrows');
$container.find('a').on('click', function(e) {
e.preventDefault();
var $link = $(e.currentTarget);
$.ajax({
url: '/comments/10/vote/'+$link.data('direction'),
method: 'POST'
}).then(function(data) {
$container.find('.js-vote-total').text(data.votes);
});
});

Luego ve a borrar el directorio public/js/ completamente... y public/css/. También abre templates/question/show.html.twig y, al final, remueve la vieja etiqueta script.

{% extends 'base.html.twig' %}
{% block title %}Question: {{ question }}{% endblock %}
{% block body %}
... lines 6 - 57
{% endblock %}

Con algo de suerte, Encore ya reconstruyó mi app.js. Asi que si damos click para ver una pregunta - Voy a refrescar solo para estar seguros - y... da click en los íconos para votar. ¡Si! Todavía funciona.

Instalando e Importando Librerías Externas (jQuery)

Ya que estamos usando Encore, existen algunas cosas muy interesantes que podemos hacer. Esta es una: en vez de enlazar a una CDN o descargar jQuery directamente en nuestro proyecto y agregarlo al commit, podemos importar jQuery e instalarlo en nuestro directorio node_modules/... lo cual es exactamente como lo haríamos en PHP: Instalamos una librería pública dentro de vendor/ en vez de descargarla manualmente.

Para hacer eso, abre una nueva terminal y corre:

yarn add jquery --dev

Esto es lo equivalente a correr el comando composer require: Agrega jquery al archivo package.json y lo descarga dentro de node_modules/. La parte --dev no es importante.

Después, dentro de base.html.twig, remueve por completo jQuery del layout.

... line 1
<html>
... lines 3 - 12
<body>
... lines 14 - 25
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
</body>
</html>

Si regresas a tu navegador y refrescas la página ahora... Está completamente roto:

$ is not defined

...viniendo de app.js. Eso tiene sentido: Solamente descargamos jQuery en nuestro directorio node_modules/ - aquí puedes encontrar un directorio llamado jquery - pero aún no lo estamos usando.

¿Cómo lo utilizamos? Dentro de app.js, descomentariza la línea del import: import $ from 'jquery'.

29 lines assets/js/app.js
... lines 1 - 9
// Need jQuery? Install it with "yarn add jquery", then uncomment to import it.
import $ from 'jquery';
... lines 14 - 29

Esto "carga" el paquete jquery que instalamos y lo asigna a la variable $. Todas esas variables $ de más abajo están haciendo referencia al valor que importamos.

Esta es la parte realmente interesante: sin hacer ningún otro cambio, cuando refrescamos, ¡Funciona! Webpack se dio cuenta que estamos importando jquery y automáticamente lo empaquetó dentro del archivo app.js final. Importamos las cosas que necesitamos, y Webpack se encarga de... empaquetar todo.

Tip

De hecho, Webpack los separa en multiples archivos por cuestión de eficiencia. En realidad, jQuery vive dentro de un archivo diferente en public/build/ ¡Pero eso no es importante!

Importando el CSS de Bootstrap

Podemos hacer lo mismo para el CSS de Boostrap. En base.html.twig, arriba, elimina la etiqueta que enlaza a Bootstrap.

... line 1
<html>
<head>
... lines 4 - 5
{% block stylesheets %}
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Spartan&display=swap">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css" integrity="sha256-mmgLkCYLUQbXn0B1SRqzHar6dCnv9oZFPEC1g1cwlkk=" crossorigin="anonymous" />
{{ encore_entry_link_tags('app') }}
{% endblock %}
</head>
... lines 12 - 28
</html>

Nada nuevo, cuando refrescamos, nuestro sitio se ve terrible.

Para arreglarlo, encuentra tu terminal y corre:

yarn add bootstrap --dev

Esto descarga el paquete de bootstrap dentro de node_modules/. Este paquete contiene ambos JavaScript y CSS. Queremos activar el CSS.

Para hacerlo, abre app.css y, en la parte de arriba, utiliza la vieja y confiable sintaxis @import. Dentro de las comillas, escribe ~bootstrap:

129 lines assets/css/app.css
@import "~bootstrap";
... lines 3 - 129

En CSS, la ~ es una forma especial de decir que quieres cargar el CSS del paquete de bootstrap dentro de node_modules/.

Ve al navegador, refresca y... estamos de vuelta! Webpack vio el import, tomó el CSS del paquete de bootstrap, y lo incluyó en el archivo app.css final. ¿Qué tan bueno es eso?

¿Qué Otras Cosas Puede Hacer Encore?

Esto es solo el comienzo de lo que Webpack Encore puede hacer. También puede minificar tus archivos para producción, puede compilar código Sass o LESS, viene con soporte para React y Vue.js, maneja versiones para los archivos y más. Para aprender más, mira nuestro tutorial gratuito sobre Webpack Encore.

Y... ¡Eso es todo para este tutorial! ¡Felicitaciones por llegar al final junto conmigo! Ahora ya entiendes las partes más importantes de Symfony. En el siguiente tutorial, vamos a hacer crecer incluso aún más tu potencial de Symfony al revelar el secreto de los servicios. Serás imparable.

Como siempre, si tienes preguntas, problemas o tienes una historia divertida - especialmente si involucra a tu gato - nos encantaría escuchar sobre ti en los comentarios.

Muy bien amigos - ¡Nos vemos la próxima vez!

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.3.0 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "easycorp/easy-log-handler": "^1.0.7", // v1.0.9
        "sensio/framework-extra-bundle": "^6.0", // v6.2.1
        "symfony/asset": "5.0.*", // v5.0.11
        "symfony/console": "5.0.*", // v5.0.11
        "symfony/debug-bundle": "5.0.*", // v5.0.11
        "symfony/dotenv": "5.0.*", // v5.0.11
        "symfony/flex": "^1.3.1", // v1.17.5
        "symfony/framework-bundle": "5.0.*", // v5.0.11
        "symfony/monolog-bundle": "^3.0", // v3.5.0
        "symfony/profiler-pack": "*", // v1.0.5
        "symfony/routing": "5.1.*", // v5.1.11
        "symfony/twig-pack": "^1.0", // v1.0.1
        "symfony/var-dumper": "5.0.*", // v5.0.11
        "symfony/webpack-encore-bundle": "^1.7", // v1.8.0
        "symfony/yaml": "5.0.*" // v5.0.11
    },
    "require-dev": {
        "symfony/profiler-pack": "^1.0" // v1.0.5
    }
}