Desarrollar aplicaciones JavaScript simples y modernas utilizando Webpack y tecnologías web avanzadas

¿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.

imagen

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" />    <!-- Critical Styles -->    <!--   №1 -->    <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>    <!--   №1 -->    <title>Vanilla Todos PWA</title> </head> <body>    <body>        <!-- Main Application Section -->        <!--   №2 -->        <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>        <!--   №2 -->             <!-- Critical Scripts -->        <!--   №3 -->        <script async src="<%= htmlWebpackPlugin.files.chunks.main.entry %>"></script>        <!--   №3 -->        <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'; //   №1 self.addEventListener('install', function (event) {   self.skipWaiting();   event.waitUntil(     caches.open(staticAssetsCacheName).then(function (cache) {       cache.addAll([           '/',           "chunks/todo.d41d8cd98f00b204e980.js","index.html","main.d41d8cd98f00b204e980.js"       ]       );     }).catch((error) => {       console.log('Error caching static assets:', error);     })   ); }); //   №1 //   №2 self.addEventListener('activate', function (event) {   if (self.clients && clients.claim) {     clients.claim();   }   event.waitUntil(     caches.keys().then(function (cacheNames) {       return Promise.all(         cacheNames.filter(function (cacheName) {           return (cacheName.startsWith('todo-')) && cacheName !== staticAssetsCacheName;         })         .map(function (cacheName) {           return caches.delete(cacheName);         })       ).catch((error) => {           console.log('Some error occurred while removing existing cache:', error);       });     }).catch((error) => {       console.log('Some error occurred while removing existing cache:', error);   })); }); //   №2 //   №3 self.addEventListener('fetch', (event) => {   event.respondWith(     caches.match(event.request).then((response) => {       return response || fetch(event.request)         .then((fetchResponse) => {             return cacheDynamicRequestData(dynamicCacheName, event.request.url, fetchResponse.clone());         }).catch((error) => {           console.log(error);         });     }).catch((error) => {       console.log(error);     })   ); }); //   №3 function cacheDynamicRequestData(dynamicCacheName, url, fetchResponse) {   return caches.open(dynamicCacheName)     .then((cache) => {       cache.put(url, fetchResponse.clone());       return fetchResponse;     }).catch((error) => {       console.log(error);     }); } 

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: {       //     },   output: {       //     },   module: {       rules: [           {               test: /\.js$/,               exclude: /node_modules/,               use: {                   loader: 'babel-loader'               }           }       ]   },   plugins: [       //    ] }; 

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: {       //     },   output: {       //     },   module: {       rules: [           {               test: /\.js$/,               exclude: /node_modules/,               use: {                   loader: 'babel-loader'               }           },           {               test: /\.scss$/,               use: [                   'style-loader',                   MiniCssExtractPlugin.loader,                   'css-loader',                   'sass-loader'               ]           }       ]   },   plugins: [       new MiniCssExtractPlugin({           filename: '[name].css'       }),   ] }; 

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([           // {           // from: './src/assets',           // to: './assets'           // },           // {           // from: 'manifest.json',           // to: 'manifest.json'           // }       ]),       new CompressionPlugin({           algorithm: 'gzip'       })   ],   devServer: {       contentBase: 'dist',       watchContentBase: true,       port: 1000   } }); 

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'; //   №1 let staticAssetsCacheFiles = glob   .sync('dist/**/*')   .map((path) => {       return path.slice(5);   })   .filter((file) => {       if (/\.gz$/.test(file)) return false;       if (/sw\.js$/.test(file)) return false;       if (!/\.+/.test(file)) return false;       return true;   }); //   №1 const stringFileCachesArray = JSON.stringify(staticAssetsCacheFiles); //   №2 const serviceWorkerScript = `var staticAssetsCacheName = '${staticAssetsCacheName}'; var dynamicCacheName = '${dynamicCacheName}'; self.addEventListener('install', function (event) {   self.skipWaiting();   event.waitUntil(     caches.open(staticAssetsCacheName).then(function (cache) {       cache.addAll([           '/',           ${stringFileCachesArray.slice(1, stringFileCachesArray.length - 1)}       ]       );     }).catch((error) => {       console.log('Error caching static assets:', error);     })   ); }); self.addEventListener('activate', function (event) {   if (self.clients && clients.claim) {     clients.claim();   }   event.waitUntil(     caches.keys().then(function (cacheNames) {       return Promise.all(         cacheNames.filter(function (cacheName) {           return (cacheName.startsWith('todo-')) && cacheName !== staticAssetsCacheName;         })         .map(function (cacheName) {           return caches.delete(cacheName);         })       ).catch((error) => {           console.log('Some error occurred while removing existing cache:', error);       });     }).catch((error) => {       console.log('Some error occurred while removing existing cache:', error);   })); }); self.addEventListener('fetch', (event) => {   event.respondWith(     caches.match(event.request).then((response) => {       return response || fetch(event.request)         .then((fetchResponse) => {             return cacheDynamicRequestData(dynamicCacheName, event.request.url, fetchResponse.clone());         }).catch((error) => {           console.log(error);         });     }).catch((error) => {       console.log(error);     })   ); }); function cacheDynamicRequestData(dynamicCacheName, url, fetchResponse) {   return caches.open(dynamicCacheName)     .then((cache) => {       cache.put(url, fetchResponse.clone());       return fetchResponse;     }).catch((error) => {       console.log(error);     }); } `; //   №2 //   №3 fs.writeFile(dest, serviceWorkerScript, function(error) {   if (error) return;   console.log('Service Worker Write success'); }); //   №3 

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 = { //   App... }; 

▍ 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( /* webpackChunkName: "todo" */ './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: ''   },   //... ]; const workCardTemplate = (cardModel) => ` <section id="${cardModel.id}" class="work-card">   <section class="work__image">       <img class="work__image-content" type="image/svg+xml" src="${           cardModel.src       }" alt="${cardModel.alt}" />   </section>   <section class="work__designation">${cardModel.designation}</section>   <section class="work__period">${cardModel.period}</section>   <section class="work__content">       <section class="work__content-text">           ${cardModel.description}       </section>   </section> </section> `; export const workTemplate = (model) => ` <section class="work__section">   <section class="work-text">       <header class="header-text">           <span class="work-text__header"> Work </span>       </header>       <section class="work-text__content content-text">           <p class="work-text__content-para">               This area signifies work experience           </p>       </section>   </section>   <section class="work-cards">       ${model.reduce((html, card) => html + workCardTemplate(card), '')}   </section> </section> `; 

, . . , , .

:

  • model . — , reduce() , .
  • model.reduce , HTML-, , . , , , — .

. , , — .


Todo-, . .




. . , , , .

-


-, , -, . , , . , JavaScript -, .

, .


-

. Lighthouse.




Resumen


, , JavaScript, . , , , .

Estimados lectores! -, ?

Source: https://habr.com/ru/post/444342/


All Articles