Développement d'applications JavaScript simples et modernes à l'aide de Webpack et de technologies Web avancées

Avez-vous pensé à utiliser l'ensemble de technologies existant le plus simple lors du développement de votre prochain projet Web? Si c'est le cas, alors le matériel, dont nous publions la traduction aujourd'hui, est écrit spécialement pour vous.

Des frameworks JavaScript existent pour nous aider Ă  crĂ©er des applications avec des capacitĂ©s similaires en utilisant une approche gĂ©nĂ©rique. Cependant, de nombreuses applications n'ont pas besoin de toute la puissance fournie par les frameworks. L'utilisation d'un cadre dans un projet de petite ou moyenne taille, qui a certaines exigences spĂ©cifiques, pourrait bien ĂȘtre une perte inutile de temps et d'Ă©nergie.

image

Dans cet article, nous parlerons de l'utilisation des technologies modernes dans le développement d'applications Web dont les capacités ne sont pas limitées par les capacités des frameworks. Soit dit en passant, si vous en avez besoin, puis en utilisant les technologies décrites ici, vous pouvez créer votre propre cadre hautement spécialisé. Le JavaScript pur et d'autres technologies Web de base permettent aux développeurs de faire ce dont ils ont besoin sans se limiter à la portée des outils qu'ils utilisent.

Revue


Avant de passer aux choses sérieuses, discutons des outils dont nous avons besoin.

▍ Architecture d'application


Afin d'assurer une vitesse élevée de chargement et de convivialité des applications, nous utiliserons les modÚles de conception suivants:

  • Architecture App Shell.
  • ModĂšle PRPL (Push, Render, Pre-cache, chargement paresseux).

▍ Systùme de construction de projet


Dans notre projet, nous avons besoin d'un systÚme d'assemblage de haute qualité adapté à nos besoins. Ici, nous utiliserons Webpack, présentant les exigences suivantes pour le systÚme de construction de projet:

  • Prise en charge d'ES6 et des capacitĂ©s d'importation de ressources dynamiques.
  • Prise en charge de SASS et CSS.
  • Configuration sĂ©parĂ©e des modes de dĂ©veloppement et du vrai travail de l'application.
  • PossibilitĂ© de configurer automatiquement les employĂ©s de service.

▍ FonctionnalitĂ©s JavaScript avancĂ©es


Nous utiliserons l'ensemble minimal de fonctionnalités JavaScript modernes qui nous permet de développer ce dont nous avons besoin. Voici les fonctionnalités en question:

  • Modules
  • DiffĂ©rentes façons de crĂ©er des objets (littĂ©raux d'objet, classes).
  • Importation dynamique de ressources.
  • Fonctions flĂ©chĂ©es.
  • LittĂ©raux de modĂšle.

Maintenant que nous avons une idĂ©e gĂ©nĂ©rale de ce dont nous avons besoin, nous sommes prĂȘts Ă  commencer Ă  dĂ©velopper notre projet.

Architecture d'application


L'avÚnement de Progressive Web Application (PWA) a contribué à l'arrivée de nouvelles solutions architecturales dans le développement Web. Cela a permis aux applications Web de se charger et de s'afficher plus rapidement. La combinaison de l'architecture App Shell et du modÚle PRPL peut rendre l'application Web rapide et réactive, semblable à une application standard.

▍ Qu'est-ce que App Shell et PRPL?


App Shell est un modĂšle architectural utilisĂ© pour dĂ©velopper PWA, lorsqu'il est utilisĂ©, une quantitĂ© minimale de ressources essentielles au fonctionnement du site est envoyĂ©e au navigateur de l'utilisateur lors du chargement du site. La composition de ces matĂ©riaux comprend gĂ©nĂ©ralement toutes les ressources nĂ©cessaires au premier affichage de l'application. Ces ressources peuvent Ă©galement ĂȘtre mises en cache Ă  l'aide d'un technicien de service.

L'abréviation PRPL est déchiffrée comme suit:

  • Push - envoi de ressources critiques au client pour la route source (en particulier, en utilisant HTTP / 2).
  • Rendu - affiche l'itinĂ©raire d'origine.
  • PrĂ©-cache - mise en cache des routes ou ressources restantes Ă  l'avance.
  • Chargement paresseux - chargement «paresseux» de parties de l'application Ă  mesure qu'elles deviennent nĂ©cessaires (en particulier, Ă  la demande de l'utilisateur).

▍ ImplĂ©mentation d'App Shell et de PRPL dans le code


Les modĂšles App Shepp et PRPL sont partagĂ©s. Cela vous permet de mettre en Ɠuvre des approches avancĂ©es pour le dĂ©veloppement de projets Web. Voici Ă  quoi ressemble le modĂšle App Shell dans le code:

<!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> 

AprĂšs avoir Ă©tudiĂ© ce code, vous pouvez comprendre que le modĂšle App Shell prĂ©voit la crĂ©ation d'un «shell» d'une application, qui est son «squelette» contenant un minimum de balisage. Analysons ce code (ci-aprĂšs, les fragments de code auxquels nous nous rĂ©fĂ©rerons lors de l'analyse sont marquĂ©s de commentaires, comme <!-- №1 --> ).

  • Fragment n ° 1. Les styles les plus importants sont intĂ©grĂ©s au balisage et ne sont pas prĂ©sentĂ©s comme des fichiers sĂ©parĂ©s. Ceci est fait pour que le code CSS soit traitĂ© directement lors du chargement de la page HTML.
  • Fragment n ° 2. Voici le "shell" de l'application. Ces zones seront ultĂ©rieurement contrĂŽlĂ©es par du code JavaScript. Cela est particuliĂšrement vrai pour ce qui sera dans la balise main avec l'identifiant app ( <main id="app"></main> ).
  • Fragment n ° 3. Ici, les scripts entrent en jeu. L'attribut async vous permet de ne pas bloquer l'analyseur pendant le chargement du script.

Le squelette d'application prĂ©sentĂ© ci-dessus implĂ©mente les Ă©tapes Push et Render du modĂšle PRPL. Cela se produit lorsque le navigateur analyse le code HTML pour former une reprĂ©sentation visuelle de la page. Dans le mĂȘme temps, le navigateur trouve rapidement les ressources essentielles Ă  la sortie de la page. De plus, des scripts sont prĂ©sentĂ©s ici (fragment n ° 3), qui sont chargĂ©s d'afficher la route d'origine en manipulant le DOM (Ă  l'Ă©tape Render).

Cependant, si nous n'utilisons pas le service worker pour mettre en cache le «shell» de l'application, nous n'obtiendrons pas de gain de performances, par exemple, lors du rechargement de la page.

Le code ci-dessous montre un technicien de service mettant en cache le squelette et toutes les ressources statiques de l'application.

 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);     }); } 

Analysons ce code.

  • Fragment n ° 1. La gestion de l'Ă©vĂ©nement d' install d'un technicien de service permet de mettre en cache les ressources statiques. Ici, vous pouvez mettre en cache les ressources du "squelette" de l'application (CSS, JavaScript, images, etc.) pour la premiĂšre route (conformĂ©ment au contenu du "squelette"). De plus, vous pouvez tĂ©lĂ©charger d'autres ressources d'application, de maniĂšre Ă  ce qu'elles puissent fonctionner sans connexion Internet. La mise en cache des ressources, en plus de la mise en cache squelette, correspond Ă  l'Ă©tape de prĂ©-mise en cache du modĂšle PRPL.
  • Fragment n ° 2. Le traitement d'un Ă©vĂ©nement d' activate caches inutilisĂ©s.
  • Fragment n ° 3. Ces lignes de code chargent les ressources du cache si elles s'y trouvent. Sinon, des requĂȘtes rĂ©seau sont effectuĂ©es. De plus, si une demande rĂ©seau est faite pour recevoir une ressource, cela signifie que cette ressource n'a pas encore Ă©tĂ© mise en cache. Une telle ressource est placĂ©e dans un nouveau cache sĂ©parĂ©. Ce script permet de mettre en cache les donnĂ©es d'application dynamiques.

À ce jour, nous avons discutĂ© de la plupart des solutions architecturales qui seront utilisĂ©es dans notre application. La seule chose dont nous n'avons pas encore parlĂ© est l'Ă©tape de chargement paresseux du modĂšle PRPL. Nous y reviendrons plus tard, mais pour l'instant nous traiterons du systĂšme d'assemblage du projet.

SystĂšme de construction de projet


Une bonne architecture seule, sans systĂšme de construction de projet dĂ©cent, ne suffit pas pour crĂ©er une application de qualitĂ©. C'est lĂ  que Webpack est utile. Il existe d'autres outils pour la construction de projets (bundlers), par exemple - Parcel and Rollup. Mais ce que nous allons implĂ©menter basĂ© sur Webpack peut Ă©galement ĂȘtre fait en utilisant d'autres moyens.

Ici, nous parlons de la façon dont les fonctionnalités qui nous intéressent sont liées aux plugins pour Webpack. Cela vous permettra de saisir rapidement l'essence de notre systÚme de construction. La sélection de plug-ins pour le bundler et sa configuration appropriée est l'étape la plus importante vers un systÚme de construction de projet de haute qualité. AprÚs avoir maßtrisé ces principes, vous pourrez les utiliser à l'avenir lorsque vous travaillez sur vos propres applications.

Il n'est pas facile de régler des outils comme Webpack à partir de zéro. Dans de tels cas, il est utile d'avoir une bonne aide à portée de main. Ce guide, avec lequel la partie correspondante de ce matériel a été écrite, était cet article. Si vous rencontrez des difficultés avec Webpack, contactez-la. Rappelons maintenant et implémentons les exigences du systÚme d'assemblage de projet dont nous avons parlé au tout début.

▍Support ES6 et capacitĂ©s d'importation de ressources dynamiques


Pour implĂ©menter ces fonctionnalitĂ©s, nous avons besoin de Babel, un transporteur populaire qui vous permet de convertir du code Ă©crit Ă  l'aide des fonctionnalitĂ©s ES6 en code pouvant ĂȘtre exĂ©cutĂ© dans des environnements ES5. Afin de faire fonctionner Babel avec Webpack, nous pouvons utiliser les packages suivants:

  • @babel/core
  • @babel/plugin-syntax-dynamic-import
  • @babel/preset-env
  • babel-core
  • babel-loader
  • babel-preset-env

Voici un exemple de fichier .babelrc Ă  utiliser avec Webpack:

 {   "presets": ["@babel/preset-env"],   "plugins": ["@babel/plugin-syntax-dynamic-import"] } 

Lors de la configuration de Babel, la ligne de presets de ce fichier est utilisĂ©e pour configurer Babel pour compiler ES6 Ă  ES5, et la ligne de plugins pour que l'importation dynamique puisse ĂȘtre utilisĂ©e dans Webpack.

Voici comment Babel est utilisé avec Webpack (voici un extrait du fichier de paramÚtres webpack.config.js - webpack.config.js ):

 module.exports = {   entry: {       //     },   output: {       //     },   module: {       rules: [           {               test: /\.js$/,               exclude: /node_modules/,               use: {                   loader: 'babel-loader'               }           }       ]   },   plugins: [       //    ] }; 

La section des rules de ce fichier décrit comment utiliser le babel-loader pour personnaliser le processus de transpilation. Le reste de ce fichier est omis par souci de concision.

Prise en charge de SASS et CSS


Pour prendre en charge notre systĂšme d'assemblage de projets SASS et CSS, nous avons besoin des plugins suivants:

  • sass-loader
  • css-loader
  • style-loader
  • MiniCssExtractPlugin

Voici à quoi ressemble le fichier de paramÚtres Webpack dans lequel les données sur ces plugins sont entrées:

 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'       }),   ] }; 

Les chargeurs sont enregistrés dans la section des rules . Puisque nous utilisons le plugin pour extraire les styles CSS, l'entrée correspondante est entrée dans la section plugins .

▍ RĂ©glage sĂ©parĂ© des modes de dĂ©veloppement et travail rĂ©el de l'application


Il s'agit d'une partie extrĂȘmement importante du processus de crĂ©ation d'application. Tout le monde sait que lors de la crĂ©ation d'une application, certains paramĂštres sont utilisĂ©s pour crĂ©er la version utilisĂ©e pendant le dĂ©veloppement, tandis que d'autres sont utilisĂ©s pour sa version de production. Voici une liste de packages utiles ici:

  • clean-webpack-plugin : pour nettoyer le contenu du dossier dist .
  • compression-webpack-plugin : pour compresser le contenu du dossier dist .
  • copy-webpack-plugin : pour copier des ressources statiques, par exemple des fichiers, Ă  partir de dossiers contenant les donnĂ©es source de l'application vers le dossier dist .
  • html-webpack-plugin : pour crĂ©er le fichier index.html dans le dossier dist .
  • webpack-md5-hash : pour hacher les fichiers d'application dans le dossier dist .
  • webpack-dev-server : pour dĂ©marrer le serveur local utilisĂ© pendant le dĂ©veloppement.

Voici à quoi ressemble le fichier webpack.config.js résultant:

 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   } }); 

La configuration Webpack entiÚre est présentée comme une fonction qui prend deux arguments. L'argument argv est utilisé ici, qui représente les arguments passés à cette fonction lorsque les commandes webpack ou webpack-dev-server sont webpack . Voici à quoi ressemble la description de ces commandes dans le fichier de projet package.json :

 "scripts": {   "build": "webpack --mode production && node build-sw",   "serve": "webpack-dev-server --mode=development --hot", }, 

Par conséquent, si nous npm run build , la version de production de l'application sera créée. Si vous exécutez la commande npm run serve , le serveur de développement démarre, prenant en charge le travail sur l'application.

Les devServer plugins et devServer fichier ci devServer dessus montrent comment configurer les plugins et le serveur de développement.

Dans la section qui commence par le new CopyWebpackPlugin , vous spécifiez les ressources que vous souhaitez copier à partir des matériaux source de l'application.

▍Installation d'un travailleur de service


Nous savons tous que la compilation manuelle de listes de fichiers, par exemple, destinés à la mise en cache, est une tùche plutÎt ennuyeuse. Par conséquent, ici, nous allons utiliser un script d'assemblage de service worker spécial qui trouve les fichiers dans le dossier dist et les ajoute en tant que contenu de cache dans le modÚle de service worker. AprÚs cela, le fichier de service worker sera écrit dans le dossier dist . Ces concepts dont nous avons parlé lorsqu'ils s'appliquent aux travailleurs des services ne changent pas. Voici le code du 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 

Analysons ce code.

  • Fragment n ° 1. Ici, la liste des fichiers du dossier dist est placĂ©e dans le tableau staticAssetsCacheFiles .
  • Fragment n ° 2. Il s'agit du modĂšle de travailleur de service dont nous avons parlĂ©. Lors de la gĂ©nĂ©ration du code fini, des variables sont utilisĂ©es. Cela rend le modĂšle universel, vous permettant de l'utiliser Ă  l'avenir pendant le dĂ©veloppement du projet. Nous avons Ă©galement besoin d'un modĂšle car nous y ajoutons des informations sur le contenu du dossier dist , qui peuvent changer avec le temps. Pour cela, la constante stringFileCachesArray .
  • Fragment n ° 3. Ici, le code de service worker nouvellement gĂ©nĂ©rĂ© stockĂ© dans la constante serviceWorkerScript est Ă©crit dans le fichier situĂ© Ă  dist/sw.js

Pour exĂ©cuter ce script, utilisez la commande node build-sw . Il doit ĂȘtre exĂ©cutĂ© une fois la commande webpack --mode production .

Le script de construction d'un service worker présenté ici simplifie considérablement la tùche d'organisation de la mise en cache des fichiers. Il est à noter que ce script a déjà trouvé application dans un vrai projet.

Si vous souhaitez utiliser une bibliothĂšque spĂ©ciale conçue pour rĂ©soudre le problĂšme du travail hors ligne d'applications Web progressives, jetez un Ɠil Ă  Workbox . Il a des fonctionnalitĂ©s personnalisables trĂšs intĂ©ressantes.

▍ Aperçu des packages utilisĂ©s dans le projet


Voici le fichier package.json de notre projet, oĂč vous pouvez trouver des informations sur les packages utilisĂ©s dans ce projet:

 { "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 nous parlons du support de tels projets, il faut noter que les outils de l'Ă©cosystĂšme Webpack sont mis Ă  jour assez souvent. Il arrive souvent que les plugins existants soient remplacĂ©s par de nouveaux. Par consĂ©quent, il est important, lors de la dĂ©cision d'utiliser ou non des packages plus rĂ©cents, de se concentrer non pas sur les packages eux-mĂȘmes, mais sur les fonctionnalitĂ©s qu'ils doivent implĂ©menter. En fait, c'est prĂ©cisĂ©ment la raison pour laquelle nous avons parlĂ© plus haut du rĂŽle que joue tel ou tel paquet.

Fonctionnalités modernes JavaScript


Pendant le développement d'une application Web, le programmeur a le choix - d'écrire ses propres implémentations de fonctionnalités telles que la détection des modifications, le routage, le stockage des données ou d'utiliser des packages existants.

Nous allons maintenant parler de l'ensemble minimal de technologies nĂ©cessaires pour assurer le fonctionnement de notre projet. Si nĂ©cessaire, cet ensemble de technologies peut ĂȘtre Ă©tendu Ă  l'aide de cadres ou de packages existants.

▍ Modules


Nous utiliserons les capacités d'ES6 pour importer et exporter des modules, en considérant chaque fichier comme un module ES6. Cette fonctionnalité se retrouve souvent dans les frameworks populaires tels que Angular et React, elle est trÚs pratique à utiliser. Grùce à la configuration de Webpack que nous avons, nous pouvons utiliser l'expression de ressources d'importation et d'exportation. Voici à quoi cela ressemble dans le fichier app.js :

 import { appTemplate } from './app.template'; import { AppModel } from './app.model'; export const AppComponent = { //   App... }; 

▍Diverses façons de crĂ©er des objets


La crĂ©ation de composants est une partie importante de notre processus de dĂ©veloppement d'applications. Ici, il est tout Ă  fait possible d'utiliser un outil moderne, comme des composants Web, mais afin de ne pas compliquer le projet, nous utiliserons des objets JavaScript ordinaires, qui peuvent ĂȘtre créés Ă  l'aide de littĂ©raux d'objets ou Ă  l'aide de la syntaxe de classe apparue dans la norme ES6. .

La particularitĂ© de l'utilisation de classes pour crĂ©er des objets est qu'aprĂšs la description de la classe, vous devez crĂ©er une instance de l'objet sur sa base, puis exporter cet objet. Afin de simplifier encore plus les choses, nous utiliserons ici des objets ordinaires créés Ă  l'aide de littĂ©raux d'objets. Voici le code du fichier app.js oĂč vous pouvez voir leur application.

 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);   } }; 

Ici, nous formons et exportons le composant AppComponent , que vous pouvez immédiatement utiliser dans d'autres parties de l'application.

Vous pouvez trÚs bien utiliser des classes ES6 ou des composants Web dans de telles situations, en développant un projet dans un style plus proche de la déclaration que celui utilisé ici. Ici, afin de ne pas compliquer le projet de formation, une approche impérative est utilisée.

▍Importation dynamique de ressources


Rappelez-vous que, en parlant du modÚle PRPL, nous n'avons pas encore compris la partie de celui-ci qui est représentée par la lettre L (chargement paresseux)? L'importation dynamique de ressources est ce qui nous aide à organiser le chargement paresseux de composants ou de modules. Comme nous utilisons l'architecture App Shell et le modÚle PRPL pour mettre en cache le «squelette» de l'application et de ses ressources, lors de l'importation dynamique, les ressources sont téléchargées à partir du cache et non à partir du réseau.

Veuillez noter que si nous utilisons uniquement l'architecture App Shell, les ressources d'application restantes, c'est-Ă -dire le contenu du dossier des chunks , ne seront pas mises en cache.

Un exemple d'importation dynamique de ressources peut ĂȘtre vu dans le fragment de code ci-dessus du composant AppComponent , en particulier, oĂč l'Ă©vĂ©nement click du bouton est configurĂ© (nous parlons de la mĂ©thode de l'objet initEvents() ). A savoir, si l'utilisateur de l'application clique sur le bouton btn-todo , le module btn-todo sera chargĂ©. Ce module est un fichier JavaScript standard qui contient un ensemble de composants reprĂ©sentĂ©s comme des objets.

▍ Fonctions flĂ©chĂ©es et littĂ©raux de modĂšle


Les fonctions fléchées sont particuliÚrement utiles lorsque vous avez besoin du this dans de telles fonctions pour indiquer le contexte dans lequel la fonction est déclarée. De plus, les fonctions fléchées vous permettent d'écrire du code plus compact que l'utilisation des fonctions conventionnelles. Voici un exemple d'une telle fonction:

 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 fonction de appTemplate prend un modÚle (paramÚtre de model ) et renvoie une chaßne HTML contenant des données extraites du modÚle.La formation des chaßnes est effectuée à l'aide de la technologie littérale de modÚle. Il est pratique de les utiliser pour représenter des structures multilignes dans lesquelles vous devez ajouter des données.

Voici un petit conseil sur la façon de normaliser les composants et de créer des composants réutilisables. Il consiste à utiliser une méthode tableau reduce()pour assembler des chaßnes 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> `; 

En fait, une quantitĂ© importante de travail est effectuĂ©e dans ce morceau de code. En mĂȘme temps, il est conçu de maniĂšre simple et intuitive. Il convient de noter que les mĂȘmes techniques sont utilisĂ©es ici, qui sont caractĂ©ristiques des cadres modernes.

Analysons-le:

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

. , , — .


Todo-, . .




. . , , , .

-


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

, .


-

. Lighthouse.




Résumé


, , JavaScript, . , , , .

Chers lecteurs! -, ?

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


All Articles