Desenvolvendo aplicativos JavaScript simples e modernos usando o Webpack e tecnologias avançadas da Web

Você já pensou em usar o conjunto de tecnologias existente mais simples ao desenvolver seu próximo projeto da web? Nesse caso, o material, cuja tradução publicamos hoje, foi escrito especificamente para você.

As estruturas JavaScript existem para nos ajudar a criar aplicativos com recursos semelhantes usando uma abordagem genérica. No entanto, muitos aplicativos não precisam de todo o poder que as estruturas fornecem. Usar uma estrutura em um projeto pequeno ou médio, com certos requisitos específicos, pode muito bem ser um desperdício desnecessário de tempo e energia.

imagem

Neste artigo, falaremos sobre o uso de tecnologias modernas no desenvolvimento de aplicativos da Web cujos recursos não são limitados pelos recursos das estruturas. A propósito, se você precisar, então, usando as tecnologias descritas aqui, poderá criar sua própria estrutura altamente especializada. O JavaScript puro e outras tecnologias básicas da Web oferecem aos desenvolvedores a capacidade de fazer o que precisam, sem se limitar ao escopo das ferramentas que usam.

Revisão


Antes de começarmos a trabalhar, vamos discutir as ferramentas de que precisamos.

▍ Arquitetura de aplicativos


Para garantir alta velocidade de carregamento e usabilidade de aplicativos, usaremos os seguintes padrões de design:

  • Arquitetura App Shell.
  • Padrão PRPL (push, renderização, pré-cache, carregamento lento).

▍ Sistema de criação do projeto


Em nosso projeto, precisamos de um sistema de montagem de alta qualidade personalizado para nossas necessidades. Aqui usaremos o Webpack, apresentando os seguintes requisitos para o sistema de criação do projeto:

  • Suporte para ES6 e recursos de importação de recursos dinâmicos.
  • Suporte para SASS e CSS.
  • Configuração separada dos modos de desenvolvimento e o trabalho real do aplicativo.
  • Capacidade de configurar automaticamente trabalhadores de serviço.

JavaScript Recursos avançados de JavaScript


Usaremos o conjunto mínimo de recursos modernos de JavaScript que nos permitem desenvolver o que precisamos. Aqui estão os recursos em questão:

  • Módulos
  • Diferentes maneiras de criar objetos (literais de objetos, classes).
  • Importação dinâmica de recursos.
  • Funções de seta.
  • Literais de modelo.

Agora que temos uma idéia geral do que precisamos, estamos prontos para começar a desenvolver nosso projeto.

Arquitetura de aplicativos


O advento do Progressive Web Application (PWA) contribuiu para a chegada de novas soluções arquitetônicas no desenvolvimento da web. Isso permitiu que os aplicativos da web fossem carregados e exibidos mais rapidamente. A combinação da arquitetura do App Shell e do padrão PRPL pode fazer com que o aplicativo da Web seja rápido e responsivo, semelhante a um aplicativo comum.

▍ O que é o App Shell e o PRPL?


O App Shell é um padrão de arquitetura usado para desenvolver o PWA, quando usado, uma quantidade mínima de recursos críticos para a operação do site é enviada ao navegador do usuário quando o site é carregado. A composição desses materiais geralmente inclui todos os recursos necessários para a primeira exibição do aplicativo. Esses recursos também podem ser armazenados em cache usando um trabalhador de serviço.

A abreviação PRPL é decifrada da seguinte maneira:

  • Envio por envio de recursos críticos para o cliente para a rota de origem (em particular, usando HTTP / 2).
  • Renderizar - exibe a rota original.
  • Pré-cache - armazenamento em cache das rotas ou recursos restantes com antecedência.
  • Carga preguiçosa - carrega "preguiçosamente" partes do aplicativo conforme elas se tornam necessárias (em particular, a pedido do usuário).

▍ Implementação do App Shell e PRPL no código


Os padrões de App Shepp e PRPL são compartilhados. Isso permite implementar abordagens avançadas para o desenvolvimento de projetos da web. Veja como o padrão do Shell de Aplicativo se parece no 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> 

Depois de estudar esse código, você pode entender que o modelo do App Shell fornece a criação de um "shell" de um aplicativo, que é seu "esqueleto" contendo um mínimo de marcação. Analisaremos esse código (a seguir, os fragmentos de código aos quais nos referiremos durante a análise são marcados com comentários, como <!-- №1 --> ).

  • Fragmento No. 1. Os estilos mais importantes são incorporados à marcação e não são apresentados como arquivos separados. Isso é feito para que o código CSS seja processado diretamente ao carregar a página HTML.
  • Fragmento No. 2. Aqui está o "shell" do aplicativo. Essas áreas serão posteriormente controladas pelo código JavaScript. Isso é especialmente verdadeiro para o que estará na tag main com o app identificador ( <main id="app"></main> ).
  • Fragmento No. 3. Aqui os scripts entram em jogo. O atributo async permite que você não bloqueie o analisador durante o carregamento do script.

O "esqueleto" acima do aplicativo implementa as etapas Push e Render do padrão PRPL. Isso acontece quando o navegador analisa o código HTML para formar uma representação visual da página. Ao mesmo tempo, o navegador encontra rapidamente os recursos críticos para a saída da página. Além disso, os scripts são apresentados aqui (fragmento nº 3), responsáveis ​​por exibir a rota original manipulando o DOM (na etapa Render).

No entanto, se não usarmos o trabalhador do serviço para armazenar em cache o "shell" do aplicativo, não obteremos um ganho de desempenho, por exemplo, ao recarregar a página.

O código abaixo mostra um trabalhador de serviço armazenando em cache o esqueleto e todos os recursos estáticos do aplicativo.

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

Vamos analisar esse código.

  • Fragmento No. 1. A manipulação do evento de install de um trabalhador de serviço ajuda a armazenar em cache os recursos estáticos. Aqui você pode armazenar em cache os recursos do "esqueleto" do aplicativo (CSS, JavaScript, imagens etc.) para a primeira rota (de acordo com o conteúdo do "esqueleto"). Além disso, você pode baixar outros recursos do aplicativo, fazendo com que funcione sem uma conexão com a Internet. O cache de recursos, além do cache de esqueleto, corresponde à etapa Pré-cache do padrão PRPL.
  • Fragmento No. 2. O processamento de um evento de activate caches não utilizados.
  • Fragmento No. 3. Essas linhas de código carregam recursos do cache, se estiverem lá. Caso contrário, as solicitações de rede são feitas. Além disso, se uma solicitação de rede for feita para receber um recurso, isso significa que esse recurso ainda não foi armazenado em cache. Esse recurso é colocado em um novo cache separado. Esse script ajuda a armazenar em cache os dados dinâmicos do aplicativo.

Até o momento, discutimos a maioria das soluções arquiteturais que serão usadas em nosso aplicativo. A única coisa sobre a qual ainda não falamos é a etapa de carregamento lento do padrão PRPL. Voltaremos a ele mais tarde, mas, por enquanto, trataremos do sistema de montagem do projeto.

Sistema de criação do projeto


Uma boa arquitetura sozinha, sem um sistema decente de construção de projeto, não é suficiente para criar um aplicativo de qualidade. É aqui que o Webpack é útil. Existem outras ferramentas para criar projetos (empacotadores), por exemplo - Parcel e Rollup. Mas o que implementaremos com base no Webpack também pode ser feito usando outros meios.

Aqui, falamos sobre como os recursos em que estamos interessados ​​estão relacionados aos plug-ins do Webpack. Isso permitirá que você compreenda rapidamente a essência do nosso sistema de compilação. A seleção de plug-ins para o bundler e sua configuração adequada é a etapa mais importante para um sistema de construção de projeto de alta qualidade. Depois de dominar esses princípios, você poderá usá-los no futuro ao trabalhar em seus próprios aplicativos.

Não é fácil ajustar ferramentas como o Webpack a partir do zero. Nesses casos, é útil ter uma boa ajuda em mãos. Este guia, com o qual a parte correspondente deste material foi escrita, foi este artigo. Se você tiver alguma dificuldade com o Webpack - entre em contato com ela. Agora, vamos relembrar e implementar os requisitos para o sistema de montagem do projeto sobre os quais falamos desde o início.

ESSuporte ES6 e recursos de importação de recursos dinâmicos


Para implementar esses recursos, precisamos do Babel, um transportador popular que permite converter código gravado usando os recursos do ES6 em código que pode ser executado nos ambientes do ES5. Para que o Babel trabalhe com o Webpack, podemos usar os seguintes pacotes:

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

Aqui está um exemplo de arquivo .babelrc para uso com o Webpack:

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

Ao configurar o Babel, a linha de presets desse arquivo é usada para configurar o Babel para compilar o ES6 para o ES5 e a linha de plugins - plugins para que a importação dinâmica possa ser usada no Webpack.

É assim que o Babel é usado com o Webpack (aqui está um trecho do arquivo de configurações do Webpack - webpack.config.js ):

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

A seção de rules deste arquivo descreve como usar o babel-loader para personalizar o processo de transpilação. O restante deste arquivo é omitido por questões de brevidade.

▍SASS e suporte CSS


Para fornecer suporte ao nosso sistema de montagem de projetos SASS e CSS, precisamos dos seguintes plugins:

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

Veja como é o arquivo de configurações do Webpack em que os dados sobre esses plug-ins são inseridos:

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

Carregadores são registrados na seção de rules . Como usamos o plug-in para extrair estilos CSS, a entrada correspondente é inserida na seção de plugins - plugins .

Setting Configuração separada dos modos de desenvolvimento e trabalho real do aplicativo


Essa é uma parte extremamente importante do processo de criação do aplicativo. Todo mundo sabe que, ao criar um aplicativo, algumas configurações são usadas para criar a versão usada durante o desenvolvimento, enquanto outras são usadas para sua versão de produção. Aqui está uma lista de pacotes úteis aqui:

  • clean-webpack-plugin : para limpar o conteúdo da pasta dist .
  • compression-webpack-plugin : para compactar o conteúdo da pasta dist .
  • copy-webpack-plugin : para copiar recursos estáticos, por exemplo, arquivos, de pastas com os dados de origem do aplicativo para a pasta dist .
  • html-webpack-plugin : para criar o arquivo index.html na pasta dist .
  • webpack-md5-hash : para fazer o hash de arquivos de aplicativos na pasta dist .
  • webpack-dev-server : para iniciar o servidor local usado durante o desenvolvimento.

Veja como é o arquivo 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 a configuração do Webpack é apresentada como uma função que recebe dois argumentos. O argumento argv é usado aqui, que representa os argumentos passados ​​para esta função quando os webpack ou webpack-dev-server são webpack . Veja como a descrição desses comandos é package.json arquivo de projeto package.json :

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

Como resultado, se executarmos o npm run build , a versão de produção do aplicativo será criada. Se você executar o comando npm run serve , o servidor de desenvolvimento será iniciado, suportando o processo de trabalho no aplicativo.

As devServer plugins e devServer arquivo acima mostram como configurar plugins e o servidor de desenvolvimento.

Na seção que começa com o new CopyWebpackPlugin , você especifica os recursos que deseja copiar dos materiais de origem do aplicativo.

▍Configurando um trabalhador de serviço


Todos sabemos que compilar manualmente listas de arquivos, por exemplo, destinadas ao cache, é uma tarefa bastante entediante. Portanto, aqui usaremos um script de montagem de trabalhador de serviço especial que localiza os arquivos na pasta dist e os adiciona como conteúdo de cache no modelo de trabalhador de serviço. Depois disso, o arquivo do operador de serviço será gravado na pasta dist . Os conceitos sobre os quais falamos ao aplicar aos trabalhadores de serviço não mudam. Aqui está o código do 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 

Vamos analisar esse código.

  • Fragmento No. 1. Aqui, a lista de arquivos da pasta dist é colocada na matriz staticAssetsCacheFiles .
  • Fragmento No. 2. Este é o modelo do trabalhador de serviço sobre o qual falamos. Ao gerar o código finalizado, as variáveis ​​são usadas. Isso torna o modelo universal, permitindo que você o use no futuro, durante o desenvolvimento do projeto. Também precisamos de um modelo, porque adicionamos informações sobre o conteúdo da pasta dist , que podem mudar com o tempo. Para isso, a constante stringFileCachesArray .
  • Fragmento No. 3. Aqui, o código do operador de serviço recém-gerado armazenado na constante serviceWorkerScript é gravado no arquivo localizado em dist/sw.js

Para executar este script, use o comando node build-sw do node build-sw . Ele precisa ser executado após a webpack --mode production comando webpack --mode production .

O script para a construção de um trabalhador de serviço apresentado aqui simplifica bastante a tarefa de organizar o cache de arquivos. Note-se que este script já encontrou aplicação em um projeto real.

Se você deseja usar uma biblioteca especial projetada para resolver o problema de trabalhar com aplicativos da Web progressivos offline, consulte a Caixa de Trabalho. Possui recursos personalizáveis ​​muito interessantes.

▍ Visão geral dos pacotes usados ​​no projeto


Aqui está o arquivo package.json do nosso projeto, onde você pode encontrar informações sobre os pacotes usados ​​neste projeto:

 { "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" } } 

Se falarmos sobre o apoio a esses projetos, deve-se notar que as ferramentas no ecossistema Webpack são atualizadas com bastante frequência. Muitas vezes acontece que os plug-ins existentes são substituídos por novos. Portanto, é importante, ao decidir se deseja usar os mais novos, em vez de alguns pacotes, para se concentrar não nos próprios pacotes, mas nos recursos que eles devem implementar. A rigor, é exatamente por isso que falamos acima sobre o papel que esse ou aquele pacote desempenha.

Recursos modernos do JavaScript


Durante o desenvolvimento de um aplicativo da Web, o programador tem a opção de escrever suas próprias implementações de recursos como detecção de alterações, roteamento, armazenamento de dados ou usar pacotes existentes.

Agora falaremos sobre o conjunto mínimo de tecnologias necessárias para garantir a operação do nosso projeto. Se necessário, esse conjunto de tecnologias pode ser expandido usando estruturas ou pacotes existentes.

▍ Módulos


Usaremos os recursos do ES6 para importar e exportar módulos, considerando cada arquivo como um módulo ES6. Esse recurso é frequentemente encontrado em estruturas populares como Angular e React, é muito conveniente usá-lo. Graças à configuração do Webpack que temos, podemos usar a expressão de recursos de importação e exportação. Aqui está o que parece no arquivo app.js :

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

WaysVárias maneiras de criar objetos


A criação de componentes é uma parte importante do nosso processo de desenvolvimento de aplicativos. Aqui é bem possível usar alguma ferramenta moderna, como componentes da Web, mas para não complicar o projeto, usaremos objetos JavaScript comuns, que podem ser criados usando literais de objeto ou usando a sintaxe de classe que apareceu no padrão ES6 .

A peculiaridade de usar classes para criar objetos é que, após a descrição da classe, você precisa criar uma instância do objeto com base e depois exportar esse objeto. Para simplificar ainda mais as coisas, usaremos aqui objetos comuns criados usando literais de objetos. Aqui está o código para o arquivo app.js qual você pode ver o aplicativo.

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

Aqui, formamos e exportamos o componente AppComponent , que você pode usar imediatamente em outras partes do aplicativo.

É possível usar muito bem classes ES6 ou componentes da Web nessas situações, desenvolvendo um projeto em um estilo mais próximo do declarativo do que o usado aqui. Aqui, para não complicar o projeto de treinamento, é utilizada uma abordagem imperativa.

ImportImportação dinâmica de recursos


Lembre-se de que, falando do padrão PRPL, ainda não descobrimos a parte dele representada pela letra L (carregamento lento)? A importação dinâmica de recursos é o que nos ajuda a organizar o carregamento lento de componentes ou módulos. Como usamos a arquitetura do Shell de Aplicativo e o padrão PRPL para armazenar em cache o "esqueleto" do aplicativo e seus recursos, durante a importação dinâmica, os recursos são baixados do cache e não da rede.

Observe que, se usamos apenas a arquitetura do Shell de Aplicativo, os recursos restantes do aplicativo, ou seja, o conteúdo da pasta de chunks , não serão armazenados em cache.

Um exemplo de importação dinâmica de recursos pode ser visto no fragmento de código acima do componente AppComponent , em particular onde o evento click do botão está configurado (estamos falando sobre o método do objeto initEvents() ). Ou seja, se o usuário do aplicativo clicar no botão btn-todo , o módulo btn-todo será carregado. Este módulo é um arquivo JavaScript comum que contém um conjunto de componentes representados como objetos.

▍ Funções de seta e literais de modelo


As funções de seta são especialmente úteis quando você precisa da this em tais funções para indicar o contexto em que a função é declarada. Além disso, as funções de seta permitem escrever um código mais compacto do que o uso de funções convencionais. Aqui está um exemplo dessa função:

 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> `; 

A função de appTemplate pega um modelo (parâmetro de model ) e retorna uma sequência HTML que contém dados extraídos do 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.




Sumário


, , JavaScript, . , , , .

Caros leitores! -, ?

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


All Articles