Olá. Meu nome é Alexander e sou desenvolvedor Vanilla ES5.1 em 2018.
Este artigo é uma resposta à resposta do artigo "Como procurar usuários no GitHub sem React + RxJS 6 + Recompose" , que nos mostrou como usar o SvelteJS.
Eu sugiro olhar para uma das opções de como isso pode ser implementado sem usar nenhuma dependência, exceto o navegador. Além disso, o próprio GitHub afirmou que eles estão desenvolvendo um frontend sem frameworks .
Faremos a mesma entrada, exibindo a placa do usuário do GitHub:

Isenção de responsabilidadeEste artigo ignora absolutamente todas as práticas possíveis de desenvolvimento moderno de javascript e web.
Preparação
Não precisamos configurar e escrever configurações, criar index.html com todo o layout necessário:
index.html<!doctype html> <html> <head> <meta charset='utf-8'> <title>GitHub users</title> <link rel='stylesheet' type='text/css' href='index.css'> </head> <body> <div id='root'></div> <div id='templates' style='display:none;'> <div data-template-id='username_input'> <input type='text' data-onedit='onNameEdit' placeholder='GitHub username'> </div> <div data-template-id='usercard' class='x-user-card'> <div class='background'></div> <div class='avatar-container'> <a class='avatar' data-href='userUrl'> <img data-src='avatarImageUrl'> </a> </div> <div class='name' data-text='userName'></div> <div class='content'> <a class='block' data-href='reposUrl'> <b data-text='reposCount'></b> <span>Repos</span> </a> <a class='block' data-href='gistsUrl'> <b data-text='gistsCount'></b> <span>Gists</span> </a> <a class='block' data-href='followersUrl'> <b data-text='followersCount'></b> <span>Followers</span> </a> </div> </div> <div data-template-id='error'><b data-text='status'></b>: <span data-text='text'></span></div> <div data-template-id='loading'>Loading...</div> </div> </body> </html>
Se alguém estiver interessado em CSS , você pode vê-lo no repositório .
Temos os estilos mais comuns, sem módulos CSS e outros escopos. Simplesmente marcamos os componentes com classes começando com x- e garantimos que não haverá mais no projeto. Nós escrevemos quaisquer seletores a respeito deles.
Campo de entrada
Tudo o que queremos do nosso campo de entrada são os eventos rejeitados de sua alteração, bem como o evento em que a entrada foi iniciada, para que ela mostre imediatamente a indicação de carga. Acontece assim:
in_package('GitHubUsers', function() { this.provide('UserNameInput', UserNameInput); function UserNameInput(options) { var onNameInput = options.onNameInput, onNameChange = options.onNameChange; var element = GitHubUsers.Dom.instantiateTemplate('username_input'); var debouncedChange = GitHubUsers.Util.delay(1000, function() { onNameChange(this.value); }); GitHubUsers.Dom.binding(element, { onNameEdit: function() { onNameInput(this.value); debouncedChange.apply(this, arguments); } }); this.getElement = function() { return element; }; } });
Aqui nós usamos algumas funções utilitárias, vamos passar por elas:
Como não temos webpack
, nem CommonJS
, nem RequireJS
, colocamos tudo em objetos usando a seguinte função:
packages.js window.in_package = function(path, fun) { path = path.split('.'); var obj = path.reduce(function(acc, p) { var o = acc[p]; if (!o) { o = {}; acc[p] = o; } return o; }, window); fun.call({ provide: function(name, value) { obj[name] = value; } }); };
A função instantiateTemplate()
fornece uma cópia profunda do elemento DOM, que será obtida pela função consumeTemplates()
do elemento #templates
em nosso index.html
.
templates.js in_package('GitHubUsers.Dom', function() { var templatesMap = new Map(); this.provide('consumeTemplates', function(containerEl) { var templates = containerEl.querySelectorAll('[data-template-id]'); for (var i = 0; i < templates.length; i++) { var templateEl = templates[i], templateId = templateEl.getAttribute('data-template-id'); templatesMap.set(templateId, templateEl); templateEl.parentNode.removeChild(templateEl); } if (containerEl.parentNode) containerEl.parentNode.removeChild(containerEl); }); this.provide('instantiateTemplate', function(templateId) { var templateEl = templatesMap.get(templateId); return templateEl.cloneNode(true); }); });
A função Dom.binding()
aceita um elemento, opções, procura atributos de dados específicos e executa as ações que precisamos com os elementos. Por exemplo, para o atributo do data-element
, ele adiciona um campo ao resultado com um link para o elemento marcado; para o atributo data-onedit
, os manipuladores de keyup
e change
são pendurados no elemento com o manipulador de opções.
binding.js in_package('GitHubUsers.Dom', function() { this.provide('binding', function(element, options) { options = options || {}; var binding = {}; handleAttribute('data-element', function(el, name) { binding[name] = el; }); handleAttribute('data-text', function(el, key) { var text = options[key]; if (typeof text !== 'string' && typeof text !== 'number') return; el.innerText = text; }); handleAttribute('data-src', function(el, key) { var src = options[key]; if (typeof src !== 'string') return; el.src = src; }); handleAttribute('data-href', function(el, key) { var href = options[key]; if (typeof href !== 'string') return; el.href = href; }); handleAttribute('data-onedit', function(el, key) { var handler = options[key]; if (typeof handler !== 'function') return; el.addEventListener('keyup', handler); el.addEventListener('change', handler); }); function handleAttribute(attribute, fun) { var elements = element.querySelectorAll('[' + attribute + ']'); for (var i = 0; i < elements.length; i++) { var el = elements[i], attributeValue = el.getAttribute(attribute); fun(el, attributeValue); } } return binding; }); });
Bem, o delay
lida com o tipo de devolução que precisamos:
debounce.js in_package('GitHubUsers.Util', function() { this.provide('delay', function(timeout, fun) { var timeoutId = 0; return function() { var that = this, args = arguments; if (timeoutId) clearTimeout(timeoutId); timeoutId = setTimeout(function() { timeoutId = 0; fun.apply(that, args); }, timeout); }; }); });
Cartão de usuário
Não possui lógica, apenas um modelo preenchido com dados:
in_package('GitHubUsers', function() { this.provide('UserCard', UserCard); function UserCard() { var element = GitHubUsers.Dom.instantiateTemplate('usercard'); this.getElement = function() { return element; }; this.setData = function(data) { GitHubUsers.Dom.binding(element, data); }; } });
Obviamente, fazer tantas querySelectorAll
toda vez que querySelectorAll
os dados não é muito bom, mas funciona e nós os querySelectorAll
. Se, de repente, tudo ficar mais lento por causa disso, gravaremos os dados no data-element
armazenado. Ou criaremos outra função de ligação, que salva os elementos e pode ler os novos dados. Ou apoiaremos a transferência de opções para o objeto não apenas de valores estáticos, um fluxo de suas alterações para que os ligantes possam segui-las.
Indicação de erros de carregamento / solicitação
Assumimos que essas representações também serão estáticas, serão usadas apenas em um local e as chances de que elas tenham sua própria lógica são extremamente pequenas (ao contrário do cartão de um usuário); portanto, não faremos componentes separados para eles. Eles serão apenas modelos para o componente de aplicativo.
Pedido de dados
Criaremos uma classe com um método de solicitação do usuário; nesse caso, podemos substituir facilmente sua instância por uma implementação moch / other:
in_package('GitHubUsers', function() { this.provide('GitHubApi', GitHubApi); function GitHubApi() { this.getUser = function(options, callback) { var url = 'https://api.github.com/users/' + options.userName; return GitHubUsers.Http.doRequest(url, function(error, data) { if (error) { if (error.type === 'not200') { if (error.status === 404) callback(null, null); else callback({ status: error.status, message: data && data.message }); } else { callback(error); } return; }
Obviamente, precisamos de um wrapper sobre XMLHttpRequest . Não usamos a fetch
porque ela não suporta interrupção de solicitações, nem queremos nos comunicar com promessas pelo mesmo motivo.
ajax.js in_package('GitHubUsers.Http', function() { this.provide('doRequest', function(options, callback) { var url; if (typeof options === "string") { url = options; options = {}; } else { if (!options) options = {}; url = options.url; } var method = options.method || "GET", headers = options.headers || [], body = options.body, dataType = options.dataType || "json", timeout = options.timeout || 10000; var old_callback = callback; callback = function() { callback = function(){};
Aplicação final
app.js in_package('GitHubUsers', function() { this.provide('App', App); function App(options) { var api = options.api; var element = document.createElement('div');
Temos bastante código, metade do qual está na inicialização de todos os componentes que precisamos, metade na lógica de enviar solicitações e exibir a carga / erro / resultado. Mas tudo é completamente transparente, óbvio e podemos mudar a lógica em qualquer lugar, se necessário.
Usamos o utilitário DisplayOneOf
, que mostra um dos elementos fornecidos, oculta o restante:
dom-util.js in_package('GitHubUsers.DomUtil', function() { this.provide('DisplayOneOf', function(options) { var items = options.items; var obj = {}; items.forEach(function(item) { obj[item.type] = item; }); var lastDisplayed = null; this.showByType = function(type) { if (lastDisplayed) { lastDisplayed.element.style.display = 'none'; } if (!type) { lastDisplayed = null; return; } lastDisplayed = obj[type]; lastDisplayed.element.style.display = ''; }; }); });
Para fazê-lo funcionar no final, precisamos inicializar os modelos e soltar a instância do App
na página:
function onReady() { GitHubUsers.Dom.consumeTemplates(document.getElementById('templates')); var rootEl = document.getElementById('root'); var app = new GitHubUsers.App({ api: new GitHubUsers.GitHubApi() }); rootEl.appendChild(app.getElement()); }
Resultado?
Como você pode ver por si mesmo, escrevemos muito código para um exemplo tão pequeno. Ninguém faz toda a mágica para nós, nós alcançamos tudo sozinhos. Nós mesmos criamos a mágica de que precisamos, se a queremos.
→ Demonstração → Código
Escreva um código idiota e eterno. Escreva sem estruturas, o que significa que você dorme melhor e não tem medo de que amanhã todo o seu código seja preterido ou não esteja na moda o suficiente.
O que vem a seguir?
Este exemplo é muito pequeno para escrevê-lo no VanillaJS em princípio. Acredito que escrever em baunilha só faz sentido se o seu projeto planeja viver muito mais do que qualquer uma das estruturas e você não tem recursos para reescrevê-lo na íntegra.
Mas se ele fosse maior de qualquer maneira, aqui está o que faríamos:
Modelos HTML que faríamos em relação aos módulos / componentes. Eles estariam nas pastas com os componentes e o instantiateTemplate
levaria o nome do módulo mais o nome do modelo, e não apenas o nome global.
No momento, todo o CSS que temos está no index.css
, obviamente, também precisamos colocá-lo ao lado dos componentes.
Não há montagem de pacotes suficiente, conectamos todos os arquivos com as mãos em index.html
, isso não é bom.
Não há problema em escrever um script que, através das listas de módulos que devem ser incluídos nos pacotes configuráveis, colete todos os js, html e css desses módulos e nos torne um js para cada pacote configurável. Será muito mais difícil e fácil do que configurar o webpack e, depois de um ano, descubra que já existe uma versão completamente diferente e você precisará reescrever a configuração e usar outros carregadores.
É recomendável ter algum tipo de sinalizador que suporte o esquema de conexão js / html / css com uma lista enorme em index.html
. Não haverá atrasos na montagem e, no Sources in chrome, você terá cada arquivo em uma guia separada e não serão necessários mapas de origem.
PS
Essa é apenas uma das opções de como tudo pode estar usando o VanillaJS . Nos comentários, seria interessante ouvir sobre outros casos de uso.
Obrigado pela atenção.