Category Archives: Uncategorized

Transformando seu código de negócio em biblioteca versionada

Um problema comum no Frontend é o gerenciamento e organização do código e as últimas tecnologias nos últimos anos nos ajudam a resolver.

Caso

Tenho um código que depende de mapas em várias telas do sistema e organizei um componente que pode ser importado em vários arquivos.

Como esse componente tem um ciclo de desenvolvimento diferente, organizei como submódulo no git.


Para facilitar, os devs importam o projeto principal com parâmetro pra já carregar os submódulos, mas no final de contas fica tudo num mesmo lugar.

git clone --recursive -j8 git@github.com:cmilfont/beerswarm.git

Problema

O código está blindado para importar as dependências sem se preocupar com estrutura e organização, já que nos foi dada com a nova abstração na linguagem — transpilada pelo Babeljs — para funcionar em todos os navegadores, algo como:

import { BMap, Toolbar, GoogleMutant, GoogleApiLoader } from '/components/maps';

O problema mesmo é no ciclo de versão desses componentes. Se um desenvolvedor move um método de lugar e o projeto não tem 100% de cobertura em testes (se tratando de legado é o normal e esperado) corre o risco provável de detectar apenas em produção.

Problema dobrado, uso o Leaflet como gerenciador de mapas, cada fornecedor representa uma camada (layer) a ser exibido, o default é o Open Street Maps.

Para usar o mapa do Google teria que implementar uma layer e carregar a API, causou o problema com outra tela que precisava além do Mapa também carregar a biblioteca de places que faz parte dos opcionais. Daí nasce o submódulo do submódulo pra gerenciar.

Problemas secundários que também tomam tempo da equipe: merges desmotivantes.

Solução

Transformar esses códigos genéricos em bibliotecas com seu próprio ciclo de versionamento usando o próprio gerenciador de pacotes do Nodejs, o npm, como fazem as bibliotecas OpenSource da linguagem.

Hospedando o projeto da lib

Começamos por isolar a Layer do Google a ser usada no Leaflet como uma lib.

Usando o create-react-app (troque pelo seu boilerplate preferido) eu criei um projeto novo https://github.com/produtoreativo/react-leaflet-googlemutant, adicionei repositorio no github.

Formato 1: Open Source

Se não tem diferencial pro meu negócio (como uma Layer para o mapa do Google) o melhor é deixar o mundo ajudar a manter, reservando um repositório no npmjs.com.

Formato 2: Privado no npmjs.com

O registro global de libs no nodejs tem planos pagos para libs privadas, tem também que colocar private: true no package.json do projeto. Conveniente por ser a mesma coisa do OpenSource, mas talvez um preço salgado dependendo do tamanho da sua companhia.

Formato 3: Privado e hospedado in-house.

Caso queira hospedar seu próprio repositório de bibliotecas, pode usar uma opção como o Nexus e apontar no package.json o caminho como a própria documentação da ferramenta ensina.

Formato 4: Privado sem gerenciador de repositórios de libs

Caso ainda não queira configurar um servidor privado ou pagar o registro público global, pode também indicar no package.json o caminho ao github diretamente, algo como:

{
"name": "beerswarm",
"version": "0.1.0",
"private": true,
"dependencies": {
/* libs ... */
"react-leaflet-googlemutant": "github:produtoreativo/react-leaflet-googlemutant",
},
"devDependencies": {
"react-scripts": "0.9.5",
},
"scripts": {
"start": "NODE_PATH=src react-scripts start",
"build": "NODE_PATH=src react-scripts build && sw-precache --config=sw-precache-config.js",
"test": "NODE_PATH=src react-scripts test --env=jsdom --coverage",
"eject": "react-scripts eject"
}
}

Os devs que tiveram em uma máquina com permissão para puxar desse repositório no seu fornecedor git bastarão executar o npm install (ou yarn install) para ter as libs no projeto principal como dependência.

Lembrando que esse último caso de apontar diretamente para o github (ou gitlab) só funciona — como demonstra a própria documentação — informando qual commit ou branch no final da url com o pattern #commit-ish.

A versão informada no package.json fica só simbólica nesse caso e não define que versão real foi puxada.

Estrutura da Lib


Com o seu boilerplate preferido — no meu caso o create-react-app — crie a estrutura do seu projeto e reserve uma pasta (sugestão, chamei de lib) para o resultado final que será empacotado.

Basicamente precisamos configurar adequadamente o package.json e usar um Module Bundler (construtor que vai transformar seu código em um projeto executável no navegador) como Webpack.

Quero transformar o import em algo como:

import { GoogleMutant, GoogleApiLoader } from 'react-leaflet-googlemutant';

Configurando o package.json

Duas coisas importantes a configurar logo de início, o número de versão da lib e de forma recomendável o script que vai ser carregado quando essa lib for importada.

{
"name": "react-leaflet-googlemutant",
"description": "React leaflet wrapper to GoogleMutant plugin",
"version": "0.1.6",
"main": "./lib/react-leaflet-googlemutant.js",
"private": false
}

Ou seja, quando o usuário faz um import from ‘lib’. Essa Lib vai ser carregada no que você informar em main.

Utilizei a lib standard-version para gerenciar as mudanças do número de versão de forma automática.

Outro fator importante de configuração é definir o que vai ficar em dependencies, devDependencies ou peerDependencies. Lembrando que para o arquivo final que será importando num projeto Frontend você pode fazer o ajuste tranquilamente no Webpack.

Configurando o Webpack

Além de instalar com yarn add -D webpack, criei um arquivo webpack.conf.js na raiz da pasta para iniciar a configuração.

Para importar as classes que estão no caminho src/components/ eu criei um index.js que exporta todas as interfaces públicas dessa lib.

export { default as GoogleApiLoader} from 'components/googleapiloader.js';
export { default as GoogleMutant } from 'components/googlemutant.js';

Ou seja, você pode importar a Lib inteira como

import ReactLeafletGoogleMutant from 'react-leaflet-googlemutant';

ou as duas classes exportadas como no código mostrado anteriormente:

import { GoogleMutant, GoogleApiLoader } from 'react-leaflet-googlemutant';

Esse arquivo de export vai ser nosso Entry Point. A ponte de entrada para todos os componentes da lib.

var path = require('path');
var webpack = require('webpack');

module.exports = {
entry: "./src/react-leaflet-googlemutant/index.js"
};

Definimos as referências dinâmicas para os locais que o código deve encontrar as fontes já que estamos usando caminhos absolutos a partir de um source (src).

var path = require('path');
var webpack = require('webpack');

module.exports = {
entry: "./src/react-leaflet-googlemutant/index.js",
resolve: {
modules: [path.resolve(__dirname, "src"), "node_modules"]
}

};

Em seguida definimos qual vai ser aquela saída que os projetos importarão:

var path = require('path');
var webpack = require('webpack');

module.exports = {
entry: "./src/react-leaflet-googlemutant/index.js",
resolve: {
modules: [path.resolve(__dirname, "src"), "node_modules"]
},
output: {
path: path.join(__dirname, 'lib'),
filename: "react-leaflet-googlemutant.js",
library: "ReactLeafletGoogleMutant",
libraryTarget: "amd"
}

};

Escolhi o formato amd como padrão da biblioteca gerada por ser um dos mais comuns que funciona para todos os ambientes, seja web ou node, o que facilita testes e integrações.

Um fator importante é definir as dependências e não levar junto no build gerado, exemplo, esse projeto de lib assume que o React e o Leaflet vai existir para ele funcionar, não faz sentido empacotar já que os projetos já o farão, você pode definir o que é externo:

var path = require('path');
var webpack = require('webpack');

module.exports = {
entry: "./src/react-leaflet-googlemutant/index.js",
resolve: {
modules: [path.resolve(__dirname, "src"), "node_modules"]
},
output: {
path: path.join(__dirname, 'lib'),
filename: "react-leaflet-googlemutant.js",
library: "ReactLeafletGoogleMutant",
libraryTarget: "amd"
},
externals: {
react: {
root: 'React',
amd: 'react'
},
'react-dom': {
root: 'ReactDOM',
amd: 'react-dom'
},
'prop-types': {
root: 'PropTypes',
amd: 'prop-types'
},
'react-leaflet': {
amd: 'react-leaflet'
},
'leaflet': {
amd: 'leaflet'
}
}

};

Existem várias preferências sobre qual formato de source map (aqui definido pela propriedade devtool), adotei qualquer uma porque essa lib é intermediária e não componente final a ser aplicado em projeto:

var path = require('path');
var webpack = require('webpack');

module.exports = {
entry: "./src/react-leaflet-googlemutant/index.js",
resolve: {
modules: [path.resolve(__dirname, "src"), "node_modules"]
},
output: {
path: path.join(__dirname, 'lib'),
filename: "react-leaflet-googlemutant.js",
library: "ReactLeafletGoogleMutant",
libraryTarget: "amd"
},
externals: {
react: {
root: 'React',
amd: 'react'
},
'react-dom': {
root: 'ReactDOM',
amd: 'react-dom'
},
'prop-types': {
root: 'PropTypes',
amd: 'prop-types'
},
'react-leaflet': {
amd: 'react-leaflet'
},
'leaflet': {
amd: 'leaflet'
}
},
devtool: 'sourcemap'
};

Por fim configurei um plugin para aplicar variáveis de ambiente (vou colocar o Version Number da lib dentro do código) e o babel configurado para stage 2 e praticamente traduzindo apenas ES7, já que espero não usar diretamente no navegador e sim já incluído na tradução de outra lib/projeto. Ler mais sobre configuração do Babel nesse artigo.

var path = require('path');
var webpack = require('webpack');

module.exports = {
entry: "./src/react-leaflet-googlemutant/index.js",
resolve: {
modules: [path.resolve(__dirname, "src"), "node_modules"]
},
output: {
path: path.join(__dirname, 'lib'),
filename: "react-leaflet-googlemutant.js",
library: "ReactLeafletGoogleMutant",
libraryTarget: "amd"
},
externals: {
react: {
root: 'React',
amd: 'react'
},
'react-dom': {
root: 'ReactDOM',
amd: 'react-dom'
},
'prop-types': {
root: 'PropTypes',
amd: 'prop-types'
},
'react-leaflet': {
amd: 'react-leaflet'
},
'leaflet': {
amd: 'leaflet'
}
},
devtool: 'sourcemap',
plugins: [
new webpack.EnvironmentPlugin(['__VERSION__'])
],
module: {
rules: [
{
test: /.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ["react", "stage-2"],
plugins: [
['transform-runtime', {
"helpers": false,
"polyfill": false,
"regenerator": false,
"moduleName": "babel-runtime"
}]
]
}
}
}
]
}

};

Criei uma task script no package.json chamada transpile com o comando webpack e o resultado final fica extremamente enxuto (já que não transpila classes e outras features do es6 também).


Criei um bash script pra rodar todas as tasks necessárias pra gerar uma versão, chamado publish.sh

#!/bin/sh -x

yarn release #incrementa a versão, consultar a lib standard-version
VERSION=`node -e "console.log(require('./package.json').version);"`
NODE_ENV=production __VERSION__=$VERSION yarn transpile
git commit -a -m $VERSION
git push --follow-tags origin master
npm publish

Debugger e atualizações

Uma realidade é que precisamos muitas vezes debugar o código dessa sublib enquanto detectamos alguma evidência de problema (atire o primeiro unit test que nunca fez isso), além de atualizar a versão conforme novas necessidades no projeto principal.

Estamos trabalhando no BeerSwarm 1.0 que precisará da versão v0.1.9 do react-leaflet-googlemutant que ainda se encontra na v0.1.8.

Para tornar isso possível o npm fornece um mecanismo simples para linkar um package local e fazer referência direta.

Na raiz do projeto, execute o comando npm link.

[cmilfont@MacBook-Pro-de-Christiano:/Users/cmilfont/projetos/react-leaflet-googlemutant:master:a635622:]
$ npm link
/Users/cmilfont/.nvm/versions/node/v8.0.0/lib/node_modules/react-leaflet-googlemutant -> /Users/cmilfont/projetos/react-leaflet-googlemutant

Agora no projeto da lib você vai executar a task transpile com o parâmetro -w (de watch) pra ficar observando todas as alterações:


Toda alteração no source muda automaticamente no Webpack que produz aquela saída que planejamos.

No projeto que importa essa lib rode o mesmo comando, mas indicando qual lib foi linkada npm link react-leaflet-googlemutant.

E rode o seu toolset de desenvolvimento hot-loader, no meu caso o yarn start para o create-react-app, todas as modificações na lib original causarão um “touch” no projeto que linkou causando a imediata recompilação.

Ao final de desenvolvimento execute o script bash de geração da nova versão, coloque o número correto no package.json que vai receber (um yarn upgrade nome-da-lib resolve) e evite quebrar o código de outros projetos que continuarão com a versão anterior.

Espero que esse pequeno tutorial tenha sido útil como foi pra mim, quaisquer dúvidas comenta ou manda issues no github 🙂

Analise o comportamento do seu usuário com LogRocket

A stack React e Redux está fazendo tanto sucesso que começaram a aparecer serviços oferecendo coletar os dados do estado para análise.

Há muito tempo um serviço como o LogRocket não me impressionava tanto.

Em seguido você pode assistir um vídeo curto que gravei da Dashboard do LogRocket. É o detalhe de uma sessão de usuário apresentado em vídeo com o log da navegação durante as transações de cada atualização do estado.

Essas sessões podem ser identificadas ou não, basta incluir uma linha como no exemplo abaixo:

https://gist.github.com/cmilfont/22d0520e4fc5bf8a7f733fe5d77de141

Além de integração com vários serviços que você já deve usar como Sentry ou Heap.


Esse post não é patrocinado (infelizmente) e o autor declara que não há conflito de interesses além de ter ficado realmente impressionado.

Transpilando com Babel

Disclaimer: use o create-react-app se for aprender com React ou aguarde enquanto publico sobre o Webpack para os demais projetos.

Esse tutorial eu utilizarei o Browserify para explicar como funciona o Babel, a ferramenta que tornou possível o futuro hoje.

O que é o Babel?

É uma ferramenta que transforma código Javascript (e até de outras linguagens) para uma versão específica que você queira executar, como ES5 que é de 2009 e praticamente funciona em todos os navegadores que existam.

Vamos criar um Hello World em React usando uma extensão da linguagem que ele trás, o JSX, para um código que os navegadores entendam. Vou usar uma versão mais nova do Javascript, o ES6 que roda nativamente no Chrome, Firefox, Edge e demais navegadores modernos.

Transpilar

Para “transpilar” o código antes já que a sintaxe JSX não é reconhecida, usará o Babel que sabe como portar. Transpilação é um neologismo das palavras compilação e transformação.

Crie uma arquivo chamado package.json com comando npm init.

https://gist.github.com/d25d214894ec4de895d104ca06670cb3

Ele fará algums perguntas para definir os parametros básicos do package. Se preferir pode deixar tudo em branco e vai só apertando Enter.

Usando o gerenciador de pacotes do nodejs, o npm, execute em um terminal a instalação do Babel e suas dependências na raiz do projeto.

https://gist.github.com/93124ac30791fabf6a96c82b322b4599

Presets e plugins

O Babel por si só é uma plataforma de “transpilação”, você precisa de presets que são um conjunto de plugins com as instruções específicas de uma linguagem que deseja portar pra javascript e plugins que são extensões das linguagens, geralmente uma funcionalidade especifica que deve ser transpilada. Utilizará o preset babel-preset-react para o projeto.

Para este tutorial você utilizará a escrita de código com a sintaxe do Javascript 6 que apesar de algumas versões de navegadores não darem suporte pleno pode ser traduzido para versões anteriores. Execute agora a instalação desses presets.

https://gist.github.com/7f439f2e780c0a078c1bec6041bd0765

O preset de stage é o nível que será transpilado em relação a especificação, sendo o zero a proposta inicial que pode até a não ser especificada e o nível 3 a indicação que já tem um candidato maduro para a próxima versão.

Por fim instale as bibliotecas do React.

https://gist.github.com/ec2fd4016dbb987c8f80071c5b915db8

O package.json deve ficar com a seguinte estrutura graças a instrução save passada que indica ao npm que deve salvar uma entrada no arquivo com a versão instalada de cada biblioteca.

Além disso uma pasta node_modules foi criada e será aonde as bibliotecas de dependência do projeto ficarão. As versões podem ter alguma diferença para o seu arquivo, essas versões são as existentes durante a escrita deste tutorial.

https://gist.github.com/052ee9675f07edf7ec36ff65b4c7183f

Hello World

Crie uma pasta chamada app.

No seu editor, escreva a function seguinte para imprimir o clássico Hello World na tela e salve no arquivo app/app.jsx.

https://gist.github.com/5a9815bed3b9f9d052edfab788a58aa4

ReactDOM faz a renderização da interface, que é a biblioteca responsável em “colar” a árvore de componentes chamados Virtual DOM (aguarde post sobre Virtual-DOM) na página.

https://gist.github.com/8dfedb6fe669ca1b40caaca4cfb97812

O código completo para gerar o Hello World.

https://gist.github.com/a085fde1a8e7aa6832fc7cf0b2232144

Crie uma pasta chamada public aonde armazenará todos os arquivos estáticos como o javascript gerado que vai pra produção.

Salve um arquivo nessa pasta chamado index.html para testar o código com o seguinte HTML.

https://gist.github.com/d4966178a18d54c48ca7e41e7440c4d0

Crie uma pasta assets/js dentro da public que será o local do arquivo final transpilado.

Agora no mesmo terminal, executa a instrução com a biblioteca babel-cli que é o executável do Babel, informando qual arquivo deve transpilar e a opção -o indicado que o arquivo bundle.js será gerado. Além disso a opção — presets para indicar que linguagens corretas serão utilizadas.

https://gist.github.com/d5a47230755e8de24a9d8880668baf07

Se voce abrir o arquivo gerado, verá a instrução de return da função com JSX em app.jsx, que era:

https://gist.github.com/69e8c165b20a24e1173948ad7a4c4f05

Traduzida do JSX para o Javascript que os navegadores entende:

https://gist.github.com/65e18708caeb21b38cfa8d29c69f3894

Altere a sintaxe usando uma abordagem em ES6, não se preocupe em entender ainda. Escreva a function Root como no código seguinte:

https://gist.github.com/fd9897153a553d2fe4a525dfc941220a

Antes de executar a tradução, aproveite esse momento pra utilizar um recurso que o nodejs disponibiliza para execução desses scripts. Crie uma entrada no package.json chamada scripts para colocar a tarefa de tradução. O arquivo ficará como:

https://gist.github.com/561c0c008bee825bf8c1e6e777e0f0ee

Não precisa mais invocar o comando a partir do caminho completo da biblioteca babel-cli porque o nodejs já entende que em suas tarefas ele pode encontrar esse comando dentro da pasta node_modules.

Agora basta executar a instrução:

https://gist.github.com/d2cffdf2a4f8fbf44ac024413d8d7f0e

Ainda é necessário incluir o React no html para o exemplo funcionar, enquanto não monta o ambiente ideal, acrescente o react e o react-dom diretamente de uma CDN disponível.

https://gist.github.com/a73d142dbf30523b4f84ab564c1e7c9f

Agora abra o index.html direto no seu navegador (open public/index.html no terminal deve funcionar) e deverá ver a mensagem Hello World na tela

Preparando um ambiente ideal

Uma grande conquista da comunidade em torno do Javascript foi a maturidade de uma biblioteca chamada browserify que transporta o código executado em um terminal no seu sistema operacional para um código reconhecido pelos navegadores. Hoje temos uma evolução com o Webpack, mas fica pra um próximo artigo.

Portanto em vez de utilizarmos as dependências a partir de um CDN, podemos utilizar o NodeJS para importar as bibliotecas e incluir no arquivo final transpilado junto com seu código.

No terminal, instale o browserify:

https://gist.github.com/7691e3c2933a9329987e987d54365657

Para não termos necessidade de passar parâmetros para o babel, podemos criar um arquivo chamado .babelrc na raiz do projeto com o seguinte conteúdo:

https://gist.github.com/b618b0f076993ba415e02be461b7693e

Instale também um pacote chamado babelify que é quem faz a ligação entre o babel e o browserify:

https://gist.github.com/9fdf718cc1f461d71fd35972cac34711

Esse pacote é um transformer, um mecanismo que o browserify utiliza semelhante ao princípio do preset do babel, ele executará o transformer babelify para processar por meio do babel e transpilar o código no mesmo processo de portar para o navegador.

No seu arquivo package.json, edite a task pra ficar assim:

https://gist.github.com/6ba62086c51994c18447cb17eae4fb05

No seu arquivo app.jsx agora pode colocar as importações das dependências:

https://gist.github.com/6ca124e401f29e79b1d457b8dd132fba

Retire as dependências do index.html voltando ao código inicial:

https://gist.github.com/fa971c3c10e64444b71ce9f5d61a5c3b

Se estivesse utlizando o preset es2015 o código seria totalmente portado para o ES5 que todos os navegadores existentes conhecem.

Esse preset vem com um conjunto de plugins que traduzem tudo, já que escolhemos manter a sintaxe do ES6 e privilegiar apenas os navegadores modernos, precisamos instalar um plugin que resolve o import/export do Javascript novo.

Se você tentar executar o build novamente vai se deparar com o seguinte erro:

https://gist.github.com/f3459b0b45aecf2860d86446e5e4ee82

Então instale um plugin (estaria no preset es2015) que resolve isso.

https://gist.github.com/474e88caf8c74c7d2ec6b0cd87bb23a1

Inclua o plugin no seu arquivo .babelrc:

https://gist.github.com/e028e15fd6dbf09dd82cff2432cf018e

Executando novamente o comando para gerar o arquivo bundle.js transpilado:

https://gist.github.com/0db7541f80c3876a5f8553dab5338c40

O seu arquivo bundle.js agora tem todas as dependências necessárias para utilizar esse código no navegador.

Gerando o bundle.js automaticamente a cada mudança

Esse processo de transpilar é contínuo, portanto não é produtivo ficar executando a task de build a cada modificação.

Instale uma biblioteca chamada watchify que fica observando as mudanças e executando o browserify pra você:

https://gist.github.com/732bbdfafd3f0f8ddbf0d8b0a6b41b2e

Agora inclua no package.json a configuração para o babelify conseguir executar o browserify:

https://gist.github.com/fd2588ce28c1879c074e44ed348fa90c

Edite o seu arquivo package.json para executar com o watchify criando mais uma task com nome de watch:js:

https://gist.github.com/65a90cebe0afed53817bec6cf2139cfc

O parâmetro -m gerará um mapa entre o código original e o transpilado dentro do bundle.js num formato que os navegadores entendem para facilitar o debugging.

O parâmetro -d informa que o processo de watch deve ficar executando e aguardando as modificações.

O parâmetro -v significa que o modo é “verboso”, todas as informações de erro e tradução devem ser lançadas no terminal.

O parâmetro -o significa o arquivo de saída que será gerado como no babel e no browserify.

Agora voce executar a task no terminal e deixar traduzindo:

https://gist.github.com/72734f9f5e799d3407f7a2c1274a3108

Realize qualquer mudança no arquivo e veja no terminal o reflexo da sua alteração, como por exemplo:

https://gist.github.com/8345893d699a72975f0616a0cb32c426

A saída do terminal ficará com algo assim:

https://gist.github.com/974f19b1035c4bb27ff3373df3b5dc59

Considerações finais

O Browserify já sente o peso da idade e é aconselhado usar o Webpack, mas é excelente para demonstrar o Babel porque as outras ferramentas escondem os detalhes, o que não é errado.

Espero que esse artigo/tutorial ajude principalmente a desenvolvedores Backend que querem conhecer o fantástico e admirável mundo novo.


Originally published at gist.github.com.