¿Has pensado en utilizar el conjunto de tecnología existente más simple al desarrollar tu próximo proyecto web? Si es así, entonces el material, cuya traducción publicamos hoy, está escrito específicamente para usted.
Existen marcos de JavaScript para ayudarnos a crear aplicaciones con capacidades similares utilizando un enfoque genérico. Sin embargo, muchas aplicaciones no necesitan toda la potencia que proporcionan los marcos. El uso de un marco en un proyecto pequeño o mediano, que tiene ciertos requisitos específicos, puede ser una pérdida innecesaria de tiempo y energía.

En este artículo, hablaremos sobre el uso de tecnologías modernas en el desarrollo de aplicaciones web cuyas capacidades no están limitadas por las capacidades de los marcos. Por cierto, si lo necesita, usted, utilizando las tecnologías descritas aquí, puede crear su propio marco altamente especializado. JavaScript puro y otras tecnologías web básicas brindan a los desarrolladores la capacidad de hacer lo que necesitan sin limitarse al alcance de las herramientas que utilizan.
Revisar
Antes de comenzar a trabajar, analicemos las herramientas que necesitamos.
▍ Arquitectura de aplicación
Para garantizar una alta velocidad de carga de aplicaciones y usabilidad, utilizaremos los siguientes patrones de diseño:
- Aplicación de Arquitectura Shell.
- Patrón PRPL (Push, Render, Pre-cache, Carga diferida).
▍ Sistema de construcción del proyecto
En nuestro proyecto, necesitamos un sistema de ensamblaje de alta calidad personalizado a nuestras necesidades. Aquí usaremos Webpack, presentando los siguientes requisitos para el sistema de construcción del proyecto:
- Soporte para ES6 y capacidades dinámicas de importación de recursos.
- Soporte para SASS y CSS.
- Configuración separada de modos de desarrollo y el trabajo real de la aplicación.
- Posibilidad de configurar automáticamente los trabajadores del servicio.
▍ Funciones avanzadas de JavaScript
Utilizaremos el conjunto mínimo de características modernas de JavaScript que nos permite desarrollar lo que necesitamos. Aquí están las características en cuestión:
- Módulos
- Diferentes formas de crear objetos (literales de objetos, clases).
- Importación dinámica de recursos.
- Funciones de flecha.
- Literales de plantilla.
Ahora que tenemos una idea general de lo que necesitamos, estamos listos para comenzar a desarrollar nuestro proyecto.
Arquitectura de la aplicación
El advenimiento de Progressive Web Application (PWA) ha contribuido a la llegada de nuevas soluciones arquitectónicas en el desarrollo web. Esto permitió que las aplicaciones web se cargaran y mostraran más rápido. La combinación de la arquitectura de App Shell y el patrón PRPL puede hacer que la aplicación web sea rápida y receptiva, similar a una aplicación normal.
▍ ¿Qué es App Shell y PRPL?
App Shell es un patrón arquitectónico utilizado para desarrollar PWA; cuando se usa, se envía una cantidad mínima de recursos críticos para el funcionamiento del sitio al navegador del usuario cuando se carga el sitio. La composición de estos materiales generalmente incluye todos los recursos necesarios para la primera visualización de la aplicación. Dichos recursos también se pueden almacenar en caché utilizando un trabajador de servicio.
La abreviatura PRPL se descifra de la siguiente manera:
- Push: envío de recursos críticos al cliente para la ruta de origen (en particular, usando HTTP / 2).
- Renderizar: muestra la ruta original.
- Pre-caché: almacenamiento en caché de las rutas o recursos restantes por adelantado.
- Carga diferida: partes de carga de la aplicación “diferidas” a medida que se hacen necesarias (en particular, a solicitud del usuario).
▍ Implementación de App Shell y PRPL en código
Se comparten los patrones de App Shepp y PRPL. Esto le permite implementar enfoques avanzados para el desarrollo de proyectos web. Así es como se ve el patrón de App Shell en el código:
<!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" /> <style> html { box-sizing: border-box; } *, *:after, *:before { box-sizing: inherit; } body { margin: 0; padding: 0; font: 18px 'Oxygen', Helvetica; background: #ececec; } header { height: 60px; background: #512DA8; color: #fff; display: flex; align-items: center; padding: 0 40px; box-shadow: 1px 2px 6px 0px #777; } h1 { margin: 0; } .banner { text-decoration: none; color: #fff; cursor: pointer; } main { display: flex; justify-content: center; height: calc(100vh - 140px); padding: 20px 40px; overflow-y: auto; } button { background: #512DA8; border: 2px solid #512DA8; cursor: pointer; box-shadow: 1px 1px 3px 0px #777; color: #fff; padding: 10px 15px; border-radius: 20px; } .button { display: flex; justify-content: center; } button:hover { box-shadow: none; } footer { height: 40px; background: #2d3850; color: #fff; display: flex; align-items: center; padding: 40px; } </style> <title>Vanilla Todos PWA</title> </head> <body> <body> <header> <h3><font color="#3AC1EF">▍<a class="banner"> Vanilla Todos PWA </a></font></h3> </header> <main id="app"></main> <footer> <span>© 2019 Anurag Majumdar - Vanilla Todos SPA</span> </footer> <script async src="<%= htmlWebpackPlugin.files.chunks.main.entry %>"></script> <noscript> This site uses JavaScript. Please enable JavaScript in your browser. </noscript> </body> </body> </html>
Después de estudiar este código, puede comprender que la plantilla de Shell de aplicación proporciona la creación de un "shell" de una aplicación, que es su "esqueleto" que contiene un mínimo de marcado. Analizaremos este código (en adelante, los fragmentos de código a los que nos referiremos durante el análisis están marcados con comentarios, como
<!-- №1 -->
).
- Fragmento No. 1. Los estilos más importantes están integrados en el marcado y no se presentan como archivos separados. Esto se hace para que el código CSS se procese directamente al cargar la página HTML.
- Fragmento No. 2. Aquí está el "shell" de la aplicación. Estas áreas luego serán controladas por el código JavaScript. Esto es especialmente cierto para lo que estará en la etiqueta
main
con la app
identificación ( <main id="app"></main>
). - Fragmento No. 3. Aquí los guiones entran en juego. El atributo
async
permite no bloquear el analizador durante la carga del script.
El "esqueleto" anterior de la aplicación implementa los pasos Push y Render del patrón PRPL. Esto sucede cuando el navegador analiza el código HTML para formar una representación visual de la página. Al mismo tiempo, el navegador encuentra rápidamente los recursos que son críticos para la salida de la página. Además, los scripts se presentan aquí (fragmento No. 3), que son responsables de mostrar la ruta original al manipular el DOM (en el paso Renderizar).
Sin embargo, si no utilizamos el trabajador de servicio para almacenar en caché el "shell" de la aplicación, no obtendremos un aumento de rendimiento, por ejemplo, al volver a cargar la página.
El siguiente código muestra a un trabajador de servicio que almacena en caché el esqueleto y todos los recursos estáticos de la aplicación.
var staticAssetsCacheName = 'todo-assets-v3'; var dynamicCacheName = 'todo-dynamic-v3';
Analicemos este código.
- Fragmento No. 1. El manejo del evento de
install
de un trabajador de servicio ayuda a almacenar en caché los recursos estáticos. Aquí puede almacenar en caché los recursos del "esqueleto" de la aplicación (CSS, JavaScript, imágenes, etc.) para la primera ruta (de acuerdo con el contenido del "esqueleto"). Además, puede descargar otros recursos de la aplicación para que funcione sin una conexión a Internet. El almacenamiento en caché de recursos, además del almacenamiento en caché de esqueleto, corresponde al paso Pre-caché del patrón PRPL. - Fragmento No. 2. El procesamiento de un evento de
activate
cachés no utilizados. - Fragmento No. 3. Estas líneas de código cargan recursos del caché si están allí. De lo contrario, se realizan solicitudes de red. Además, si se realiza una solicitud de red para recibir un recurso, esto significa que este recurso aún no se ha almacenado en caché. Dicho recurso se coloca en una nueva caché separada. Este script ayuda a almacenar en caché los datos dinámicos de la aplicación.
Hasta la fecha, hemos discutido la mayoría de las soluciones arquitectónicas que se utilizarán en nuestra aplicación. Lo único de lo que no hemos hablado todavía es el paso de carga diferida del patrón PRPL. Volveremos a ello más tarde, pero por ahora nos ocuparemos del sistema de montaje del proyecto.
Sistema de construcción del proyecto
Una buena arquitectura sola, sin un sistema de construcción de proyecto decente, no es suficiente para crear una aplicación de calidad. Aquí es donde Webpack es útil. Existen otras herramientas para crear proyectos (paquetes), por ejemplo: parcela y paquete acumulativo. Pero lo que implementaremos en base a Webpack también se puede hacer usando otros medios.
Aquí hablamos sobre cómo las características que nos interesan están relacionadas con los complementos para Webpack. Esto le permitirá comprender rápidamente la esencia de nuestro sistema de construcción. La selección de complementos para el paquete y su configuración adecuada es el paso más importante hacia un sistema de construcción de proyectos de alta calidad. Una vez que domine estos principios, podrá usarlos en el futuro cuando trabaje en sus propias aplicaciones.
No es fácil ajustar herramientas como Webpack desde cero. En tales casos, es útil tener una buena ayuda a mano. Esta guía, con la que se escribió la parte correspondiente de este material, fue
este artículo. Si tiene alguna dificultad con Webpack, comuníquese con ella. Ahora recordemos e implementemos los requisitos para el sistema de ensamblaje del proyecto del que hablamos al principio.
▍Support ES6 y capacidades dinámicas de importación de recursos
Para implementar estas características, necesitamos Babel, un transportador popular que le permite convertir el código escrito con las funciones de ES6 en código que se puede ejecutar en entornos ES5. Para que Babel trabaje con Webpack, podemos usar los siguientes paquetes:
@babel/core
@babel/plugin-syntax-dynamic-import
@babel/preset-env
babel-core
babel-loader
babel-preset-env
Aquí hay un ejemplo de archivo
.babelrc
para usar con Webpack:
{ "presets": ["@babel/preset-env"], "plugins": ["@babel/plugin-syntax-dynamic-import"] }
Al configurar Babel, la línea de
presets
de este archivo se usa para configurar Babel para compilar ES6 a ES5, y la línea de
plugins
para que la importación dinámica se pueda usar en Webpack.
Así es como se usa Babel con Webpack (aquí hay un fragmento del archivo de configuración de
webpack.config.js
:
webpack.config.js
):
module.exports = { entry: {
La sección de
rules
de este archivo describe cómo usar el
babel-loader
para personalizar el proceso de transpilación. El resto de este archivo se omite por razones de brevedad.
SupportSASS y soporte CSS
Para brindar soporte a nuestro sistema de ensamblaje de proyectos SASS y CSS, necesitamos los siguientes complementos:
sass-loader
css-loader
style-loader
MiniCssExtractPlugin
Así es como se ve el archivo de configuración de Webpack en el que se ingresan los datos sobre estos complementos:
module.exports = { entry: {
Los cargadores están registrados en la sección de
rules
. Como utilizamos el complemento para extraer estilos CSS, la entrada correspondiente se ingresa en la sección de
plugins
.
▍ Configuración separada de modos de desarrollo y trabajo real de la aplicación
Esta es una parte extremadamente importante del proceso de compilación de la aplicación. Todos saben que al crear una aplicación, algunas configuraciones se usan para construir la versión que se usa durante el desarrollo, mientras que otras se usan para su versión de producción. Aquí hay una lista de paquetes que son útiles aquí:
clean-webpack-plugin
: para limpiar el contenido de la carpeta dist
.compression-webpack-plugin
: para comprimir el contenido de la carpeta dist
.copy-webpack-plugin
: para copiar recursos estáticos, por ejemplo, archivos, desde carpetas con los datos de origen de la aplicación a la carpeta dist
.html-webpack-plugin
: para crear el archivo index.html
en la carpeta dist
.webpack-md5-hash
: para el hash de archivos de aplicación en la carpeta dist
.webpack-dev-server
: para iniciar el servidor local utilizado durante el desarrollo.
webpack.config.js
es como se ve el archivo
webpack.config.js
resultante:
const path = require('path'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const WebpackMd5Hash = require('webpack-md5-hash'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const CompressionPlugin = require('compression-webpack-plugin'); module.exports = (env, argv) => ({ entry: { main: './src/main.js' }, devtool: argv.mode === 'production' ? false : 'source-map', output: { path: path.resolve(__dirname, 'dist'), chunkFilename: argv.mode === 'production' ? 'chunks/[name].[chunkhash].js' : 'chunks/[name].js', filename: argv.mode === 'production' ? '[name].[chunkhash].js' : '[name].js' }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader' } }, { test: /\.scss$/, use: [ 'style-loader', MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader' ] } ] }, plugins: [ new CleanWebpackPlugin('dist', {}), new MiniCssExtractPlugin({ filename: argv.mode === 'production' ? '[name].[contenthash].css' : '[name].css' }), new HtmlWebpackPlugin({ inject: false, hash: true, template: './index.html', filename: 'index.html' }), new WebpackMd5Hash(), new CopyWebpackPlugin([
Toda la configuración de Webpack se presenta como una función que toma dos argumentos. Aquí se usa el argumento
argv
, que representa los argumentos pasados a esta función cuando se
webpack
los
webpack
o
webpack-dev-server
. Así es como se ve la descripción de estos comandos en el archivo de proyecto
package.json
:
"scripts": { "build": "webpack --mode production && node build-sw", "serve": "webpack-dev-server --mode=development --hot", },
Como resultado, si ejecutamos el
npm run build
, se
npm run build
la versión de producción de la aplicación. Si ejecuta el
npm run serve
, el servidor de desarrollo se iniciará y admitirá el proceso de trabajo en la aplicación.
Las
devServer
plugins
y
devServer
archivo anterior muestran cómo configurar los complementos y el servidor de desarrollo.
En la sección que comienza con el
new CopyWebpackPlugin
, especifique los recursos que desea copiar de los materiales de origen de la aplicación.
▍Configurar un trabajador de servicio
Todos sabemos que compilar manualmente listas de archivos, por ejemplo, destinados al almacenamiento en caché, es una tarea bastante aburrida. Por lo tanto, aquí usaremos un script de ensamblaje de trabajador de servicio especial que encuentra los archivos en la carpeta
dist
y los agrega como contenido de caché en la plantilla de trabajador de servicio. Después de eso, el archivo del trabajador de servicio se escribirá en la carpeta
dist
. Esos conceptos de los que hablamos cuando aplicamos a los trabajadores de servicios no cambian. Aquí está el código de script
build-sw.js
:
const glob = require('glob'); const fs = require('fs'); const dest = 'dist/sw.js'; const staticAssetsCacheName = 'todo-assets-v1'; const dynamicCacheName = 'todo-dynamic-v1';
Analicemos este código.
- Fragmento No. 1. Aquí la lista de archivos de la carpeta
dist
se coloca en la matriz staticAssetsCacheFiles
. - Fragmento No. 2. Esta es la plantilla de trabajador de servicio de la que hablamos. Al generar el código terminado, se utilizan variables. Esto hace que la plantilla sea universal, permitiéndole usarla en el futuro, durante el desarrollo del proyecto. También necesitamos una plantilla porque le agregamos información sobre el contenido de la carpeta
dist
, que puede cambiar con el tiempo. Para esto, stringFileCachesArray
la constante stringFileCachesArray
. - Fragmento No. 3. Aquí, el código de trabajador de servicio recién generado almacenado en la constante
serviceWorkerScript
se escribe en el archivo ubicado en dist/sw.js
Para ejecutar este script, use el comando
node build-sw
. Debe ejecutarse después de que se
webpack --mode production
.
El script para construir un trabajador de servicio presentado aquí simplifica enormemente la tarea de organizar el almacenamiento en caché de archivos. Cabe señalar que este script ya ha encontrado aplicación en un proyecto real.
Si desea utilizar una biblioteca especial diseñada para resolver el problema de trabajar aplicaciones web progresivas sin conexión, eche un vistazo a
Workbox . Tiene características personalizables muy interesantes.
▍ Descripción general de los paquetes utilizados en el proyecto
Aquí está el archivo
package.json
de nuestro proyecto, donde puede encontrar información sobre los paquetes utilizados en este proyecto:
{ "name": "vanilla-todos-pwa", "version": "1.0.0", "description": "A simple todo application using ES6 and Webpack", "main": "src/main.js", "scripts": { "build": "webpack --mode production && node build-sw", "serve": "webpack-dev-server --mode=development --hot" }, "keywords": [], "author": "Anurag Majumdar", "license": "MIT", "devDependencies": { "@babel/core": "^7.2.2", "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/preset-env": "^7.2.3", "autoprefixer": "^9.4.5", "babel-core": "^6.26.3", "babel-loader": "^8.0.4", "babel-preset-env": "^1.7.0", "clean-webpack-plugin": "^1.0.0", "compression-webpack-plugin": "^2.0.0", "copy-webpack-plugin": "^4.6.0", "css-loader": "^2.1.0", "html-webpack-plugin": "^3.2.0", "mini-css-extract-plugin": "^0.5.0", "node-sass": "^4.11.0", "sass-loader": "^7.1.0", "style-loader": "^0.23.1", "terser": "^3.14.1", "webpack": "^4.28.4", "webpack-cli": "^3.2.1", "webpack-dev-server": "^3.1.14", "webpack-md5-hash": "0.0.6" } }
Si hablamos del apoyo de tales proyectos, debe tenerse en cuenta que las herramientas en el ecosistema de Webpack se actualizan con bastante frecuencia. A menudo sucede que los complementos existentes se reemplazan por otros nuevos. Por lo tanto, es importante, al decidir si usar los más nuevos en lugar de algunos paquetes, centrarse no en los paquetes en sí, sino en las características que deberían implementar. Hablando estrictamente, esta es precisamente la razón por la que hablamos anteriormente sobre el papel que desempeña este o aquel paquete.
Funciones modernas de JavaScript
Durante el desarrollo de una aplicación web, el programador tiene la opción: escribir sus propias implementaciones de características tales como detección de cambios, enrutamiento, almacenamiento de datos o usar paquetes existentes.
Ahora hablaremos sobre el conjunto mínimo de tecnologías necesarias para garantizar el funcionamiento de nuestro proyecto. Si es necesario, este conjunto de tecnologías se puede ampliar utilizando los marcos o paquetes existentes.
▍ Módulos
Utilizaremos las capacidades de ES6 para importar y exportar módulos, considerando cada archivo como un módulo ES6. Esta característica a menudo se encuentra en marcos populares como Angular y React, es muy conveniente usarla. Gracias a la configuración de Webpack que tenemos, podemos usar la expresión de importación y exportación de recursos. Así es como se ve en el archivo
app.js
:
import { appTemplate } from './app.template'; import { AppModel } from './app.model'; export const AppComponent = {
▍ Varias formas de crear objetos
La creación de componentes es una parte importante de nuestro proceso de desarrollo de aplicaciones. Aquí es bastante posible usar alguna herramienta moderna, como componentes web, pero para no complicar el proyecto, usaremos objetos JavaScript ordinarios, que pueden crearse utilizando literales de objeto o la sintaxis de clase que apareció en el estándar ES6 .
La peculiaridad de usar clases para crear objetos es que después de que se describe la clase, debe crear una instancia del objeto sobre la base y luego exportar este objeto. Para simplificar las cosas aún más fuertemente, usaremos aquí objetos ordinarios creados usando literales de objeto. Aquí está el código para el archivo
app.js
donde puede ver su aplicación.
import { appTemplate } from './app.template'; import { AppModel } from './app.model'; export const AppComponent = { init() { this.appElement = document.querySelector('#app'); this.initEvents(); this.render(); }, initEvents() { this.appElement.addEventListener('click', event => { if (event.target.className === 'btn-todo') { import( './todo/todo.module') .then(lazyModule => { lazyModule.TodoModule.init(); }) .catch(error => 'An error occurred while loading Module'); } }); document.querySelector('.banner').addEventListener('click', event => { event.preventDefault(); this.render(); }); }, render() { this.appElement.innerHTML = appTemplate(AppModel); } };
Aquí formamos y exportamos el componente
AppComponent
, que puede usar inmediatamente en otras partes de la aplicación.
Puede usar muy bien las clases de ES6 o los componentes web en tales situaciones, desarrollando un proyecto en un estilo más cercano al declarativo que el que se usa aquí. Aquí, para no complicar el proyecto de capacitación, se utiliza un enfoque imperativo.
▍ Importación dinámica de recursos
¿Recuerda que, hablando del patrón PRPL, todavía no hemos descubierto la parte del mismo que está representada por la letra L (carga diferida)? La importación dinámica de recursos es lo que nos ayuda a organizar la carga diferida de componentes o módulos. Dado que utilizamos la arquitectura de App Shell y el patrón PRPL para almacenar en caché el "esqueleto" de la aplicación y sus recursos, el proceso de importación dinámica carga recursos del caché, no de la red.
Tenga en cuenta que si utilizamos solo la arquitectura de App Shell, los recursos restantes de la aplicación, es decir, el contenido de la carpeta de
chunks
, no se almacenarán en caché.
Se puede ver un ejemplo de importación dinámica de recursos en el fragmento de código anterior del componente
AppComponent
, en particular, donde se configura el evento de clic del botón (estamos hablando del método del objeto
initEvents()
). Es decir, si el usuario de la aplicación hace clic en el botón
btn-todo
, se
btn-todo
módulo
btn-todo
. Este módulo es un archivo JavaScript normal que contiene un conjunto de componentes representados como objetos.
▍ Funciones de flecha y literales de plantilla
Las funciones de flecha son especialmente útiles cuando necesita la
this
en dichas funciones para indicar el contexto en el que se declara la función. Además, las funciones de flecha le permiten escribir código que es más compacto que el uso de funciones convencionales. Aquí hay un ejemplo de tal función:
export const appTemplate = model => ` <section class="app"> <h3><font color="#3AC1EF">▍ ${model.title} </font></h3> <section class="button"> <button class="btn-todo"> Todo Module </button> </section> </section> `;
La función de
appTemplate
toma un modelo (parámetro de
model
) y devuelve una cadena HTML que contiene datos tomados del modelo. . , - .
, , .
reduce()
HTML-:
const WorkModel = [ { id: 1, src: '', alt: '', designation: '', period: '', description: '' }, { id: 2, src: '', alt: '', designation: '', period: '', description: '' },
, . . , , .
:
model
. — , reduce()
, .model.reduce
, HTML-, , . , , , — .
. , , — .
Todo-, . .
.
. , , , .
-
-, , -, . , , . , JavaScript -, .
, .
-. Lighthouse.
Resumen
, , JavaScript, . , , , .
Estimados lectores! -, ?
