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.

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" /> <style> html { box-sizing: border-box; } *, *:after, *:before { box-sizing: inherit; } body { margin: 0; padding: 0; font: 18px 'Oxygen', Helvetica; background: #ececec; } header { height: 60px; background: #512DA8; color: #fff; display: flex; align-items: center; padding: 0 40px; box-shadow: 1px 2px 6px 0px #777; } h1 { margin: 0; } .banner { text-decoration: none; color: #fff; cursor: pointer; } main { display: flex; justify-content: center; height: calc(100vh - 140px); padding: 20px 40px; overflow-y: auto; } button { background: #512DA8; border: 2px solid #512DA8; cursor: pointer; box-shadow: 1px 1px 3px 0px #777; color: #fff; padding: 10px 15px; border-radius: 20px; } .button { display: flex; justify-content: center; } button:hover { box-shadow: none; } footer { height: 40px; background: #2d3850; color: #fff; display: flex; align-items: center; padding: 40px; } </style> <title>Vanilla Todos PWA</title> </head> <body> <body> <header> <h3><font color="#3AC1EF">▍<a class="banner"> Vanilla Todos PWA </a></font></h3> </header> <main id="app"></main> <footer> <span>© 2019 Anurag Majumdar - Vanilla Todos SPA</span> </footer> <script async src="<%= htmlWebpackPlugin.files.chunks.main.entry %>"></script> <noscript> This site uses JavaScript. Please enable JavaScript in your browser. </noscript> </body> </body> </html>
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';
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: {
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: {
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([
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';
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 = {
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( './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: '' },
, . . , , .
:
model
. — , reduce()
, .model.reduce
, HTML-, , . , , , — .
. , , — .
Todo-, . .
.
. , , , .
-
-, , -, . , , . , JavaScript -, .
, .
-. Lighthouse.
Sumário
, , JavaScript, . , , , .
Caros leitores! -, ?
