Hoy vamos a cambiar de aires dejando por un momento de lado Vue.js para montar el esqueleto de una aplicación con otro framework que ha gozado de una gran popularidad en los últimos años: angularjs. Y aprovechando el reciente lanzamiento de webpack 4, vamos a preparar un proyecto en el que utilizaremos este empaquetador para generar nuestro paquete final, y el servidor de desarrollo del mismo para ejecutar nuestra aplicación mientras trabajamos. Y todo ello partiendo de cero, ¡manos a la obra!
Instalando y configurando webpack 4
Empezamos por crear, en un directorio vacío, el fichero package.json con npm:
npm init -y
Vamos a modificar el paquete para hacerlo privado, no vaya a ser que lo publiquemos por error 😉 . Incluimos la linea
"private": true,
npm i --save-dev webpack webpack-cli webpack-dev-server
Modificamos la sección scripts de package.json para incluir los comandos que utilizaremos durante el desarrollo para ejecutar la aplicación y posteriormente para crear el paquete distribuible:
"scripts": { "dev": "webpack-dev-server --open --hot --mode development", "build": "webpack --progress --hide-modules --mode production" },
El comando dev ejecuta webpack-dev-server indicándole que debe ejecutarse con actualizaciones en caliente y que nos debe abrir el navegador al arrancar. Además el pasamos el parametro --mode development
para webpack, que se utiliza para usar la configuración predeterminada de desarrollo.
En el caso del script build, ejecutamos directamente webpack en modo producción para generar el paquete final con las opciones por defecto para esta configuración.
Y es que la última versión de webpack presume de ser «sin configuración», es decir, de no necesitar de un fichero como las versiones anteriores para funcionar. Y esto es así pero solo para las configuraciones más básicas; como veremos mas adelante necesitaremos el fichero webpack.config.js para varias cosas, por lo que vamos a crearlo de entrada con un contenido muy básico, indicando el punto de entrada y el fichero empaquetado de salida y una pequeña configuración para el servidor de desarrollo:
const path = require('path'); module.exports = { entry: './src/app/app.module.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, devServer: { contentBase: path.join(__dirname, "src"), compress: true, port: 9000, publicPath: "/dist/" } }
Si no estás acostumbrado a los ficheros de configuracion de webpack, esto es lo que estamos haciendo:
- entry: indicamos el punto de entrada que debe utilizar el empaquetador para recorrer nuestro proyecto y decidir qué es lo tiene que incluir en el paquete final. En nuestro caso sera el script ./src/app/app.module.js (más adelante veremos la estructura del proyecto)
- output: salida del empaquetador: queremos obtener un paquete llamado bundle.js en la carpeta ./dist. Este fichero se generará cuando utilicemos webpack en modo production, ya que en modo development los paquetes de sirven desde memoria.
- devServer: configuracion del servidor de desarrollo
- port: no necesita explicación
- compress: esta opción tampoco, ¿verdad?
- contentBase: carpeta desde la que servir el contenido estático. En nuestro caso, y para esta demostracion, utiliaremos un fichero index.html en ./src
- publicPath: ubicación desde la que servir los paquetes generados. Como en output hemos indicado la carpeta dist, aquí también 🙂
css-loader
nos permitirá incluir las hojas de estilos en el paquete final como cadenas de texto, mientras que style-loader
tomará las cadenas generados por el anterior y las pondrá dentro de tags <style> allá donde hagan falta. Los descargamos con npm:npm i --save-dev css-loader style-loader
Hay que indicarle a webpack que utilice estos cargadores para los ficheros css. Esto se hace mediante la sección rules
en la propiedad module
del fichero webpack.config.js:
rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] },
Esta configuración indica a webpack que para los ficheros .css debe usar en primer lugar el cargador css-loader
y luego style-loader
. Atención porque el orden en el que webpack utiliza los cargadores en los recursos correspondientes es el inverso al indicado en el array, del último al primero.
Vamos a añadir también un cargador para los fiheros html. raw-loader nos permite importar en el paquete final ficheros como cadenas:
npm i --save-dev raw-loader
Y añadimos la configuración a la seccion rules
:
{ test: /\.html$/, use: 'raw-loader' }
Ya tenemos todos los elementos listos, vamos con la aplicación.
Creando la aplicación angular 1.6
Lo primero de todo, necesitamos instalar el paquete de angular. Además para el layout voy a utilizar bootstrap , vpor lo que descargamos e instalamos ambos paquetes con npm:
npm i --save angular bootstrap-css-only
Vamos a generar un esqueleto muy simple de una aplicacion angular y para ello vamos a seguir el ejemplo de estructura propuesto por angular-1.5-cli.
Colocaremos nuestro código fuente en una carpeta src, con el archivo principal (index.html) en esta y los archivos de la aplicación dentro de una subcarpeta app.
Puesto que vamos a utilizar webpack para generar el paquete final, y este minifica el contenido de los ficheros javascript , es muy importante que tengamos en cuenta que angular 1.x utiliza inyección de dependencias. La minificación cambiará el nombre de los parámetros de las funciones por versiones mínimas de los mismos, y esto hará que nuestra aplicación no funcione correctamente. Para asegurarnos de que esto no ocurre, hay que indicar el parámetro ng-stricit-di
en la definición de la aplicación en index.html:
<body ng-app="app" ng-strict-di ng-cloak>
Con este parámetro especificado, la aplicación no será capaz de invocar funciones que no usen anotación explicita de sus parámetros y por lo tanto no sean candidatas a ser minificadas. Puedes encontrar más información de este tema en la referencia de ngApp y en la guia de inyeccion de dependencias de angularjs. En caso de tratar de utilizar una función que no este anotada en modo de inyección estricto nos encontraremos con un error como este en la consola:
Uncaught Error: [$injector:strictdi] function($window) is not using explicit annotation and cannot be invoked in strict mode
Nuestro fichero html queda así:
<body ng-app="app" ng-strict-di ng-cloak> <app> Cargando... </app> <script src="../dist/bundle.js"></script> </body>
El único paquete que debemos cargar es el resultante del empaquetado, dist/bundle.js.
¿Recuerdas la configuracion de webpack que especificamos al principio? El fichero de entrada a nuestra aplicación va ser /src/app/app.module.js:
import 'bootstrap-css-only'; import angular from 'angular'; import appComponent from './app.component'; import ComponentsModule from './components/components'; angular.module('app', [ ComponentsModule.name ]).component('app', appComponent);
En él importamos los componentes que necesitamos, tanto de código como de css o plantillas html (¿recuerdas css-loader
y html-loader
?)
import template from './app.component.html'; import './app.component.css'; const AppComponent = { template }; export default AppComponent;
<div class="container-fluid"> <div class="jumbotron"> <h1> Oddbytes</h1> <h2>Cliente angular 1.6 con webpack 4</h2> </div> </div>
import angular from 'angular'; const ComponentsModule = angular.module('app.components', [ ]); export default ComponentsModule;
El fichero app.component.css contendrá la hoja de estilo de nuestro primer componente, pero por el momento podemos dejarla vacía.
Ejecutando la aplicación
Ya tenemos todo lo necesario para desarrollar nuestra aplicación. Podemos poner en marcha el servidor de desarrollos utilizando el script que introdujimos al principio en package.json:
npm run dev
webpack recopilará y generara en memoria nuestro paquete, a la vez que el servidor de desarrollos arranca y nos sirve la aplicación en el puerto 9000. Cualquier cambio en los ficheros js, html o css provocará que se recompile de nuevo el paquete en caliente y se recargue automaticamente el navegador para mostrar los cambios.
Una vez que nuestra aplicación este lista para ser desplegada, tan solo tenemos que general el empaquetado en modo producción ejecutando
npm run build
Lo que nos generará el paquete bundle.js en la carpeta /dist.