Artigo original: How to Turn Your Website into a Mobile App with 7 Lines of JSON

por Ethan

Uma nova abordagem para transformar o mecanismo da web em aplicações para dispositivos móveis nativas

E se fosse dito a você que as 7 linhas de JSON acima, de cor laranja, seriam tudo que é preciso para transformar um site da web em uma aplicação para dispositivos móveis? Não há a necessidade de reescrever seu site da web usando alguma API de framework somente para fazê-lo se comportar como uma aplicação para dispositivos móveis. Usaremos seu site como ele está e o tornaremos uma aplicação para dispositivos móveis, apenas alterando o URL.

E se, apenas ajustando um pouco o  JSON, você pudesse acessar todas as APIs nativas, componentes de interface de usuário nativos e transições de visualização nativas prontas para uso?

Aqui está um exemplo inicial de como seria:

Observe como eu incorporei uma página da web do github.com, mas que, no resto do layout, são todos componentes nativos da interface de usuário, como o cabeçalho de navegação e a barra de guias inferior. A transição é automaticamente nativa sem que você precise reescrever o site usando alguma API.

Antes de eu explicar como isso funciona, você poderia se perguntar: "Isso é legal, mas você pode fazer algo significativo além de apenas exibir a página web em um quadro (frame) de aplicação nativa?"

Ótima pergunta, porque esse é o tema principal desta publicação. Tudo o que você precisa fazer é criar um canal de comunicação bidirecional contínuo entre a visualização web e a aplicação, de modo que a aplicação principal possa acionar qualquer função JavaScript dentro da visualização web e a visualização web possa buscar dados no exterior para chamar APIs nativas.

Aqui está um exemplo do quê estamos falando:

2-1-1
O gerador de QR code em ação

Observe que esta visualização contém:

  1. Cabeçalho de navegação nativo, completo com funcionalidade de transição integrada
  2. Uma visualização web, que incorpora uma aplicação da web geradora de código QR
  3. Um componente de entrada de bate-papo nativo na parte inferior

Tudo isso pode ser descrito apenas ajustando alguns dos atributos do JSON que vimos acima.

Finalmente, observe que o código QR muda à medida que você insere algo na caixa de texto do bate-papo. Essa caixa de texto aciona uma função do JavaScript dentro da aplicação para a web de código QR que gera novamente a imagem.

Nenhum framework tentou resolver fundamentalmente esse problema de "integração perfeita da visualização da Web em aplicações nativas" porque todos estão focados em escolher o lado 100% nativo ou 100% HTML5.

Sempre que você ouve alguém falar sobre o futuro das aplicações para dispositivos móveis, provavelmente ouve: "Será a abordagem do HTML5 que vence? Ou será a nativa?"

Nenhum deles vê nativo ou html como coisas que poderiam coexistir ou, até, criar sinergia e alcançar coisas que não seriam facilmente possíveis de outro modo.

Neste artigo será explicado:

  • Por que misturar o mecanismo da web com componentes nativos é normalmente uma boa ideia.
  • Por que uma integração perfeita de HTML e nativo não é fácil e como eu implementei uma.
  • Mais importante ainda, como VOCÊ pode usá-la para criar sua própria aplicação instantaneamente.

Por que você usaria HTML em uma aplicação nativa?

Antes de prosseguirmos, vamos primeiro discutir se isso é mesmo uma boa ideia e quando você pode querer adotar essa abordagem. Aqui estão alguns casos de uso em potencial:

1. Usar recursos nativos da web

Algumas partes da sua aplicação podem ser implementadas com mais qualidade usando o mecanismo da web. Por exemplo, o Websocket é um recurso nativo da web projetado para o ambiente da web. Nesse caso, faz sentido usar o mecanismo da web integrado (WKWebView para iOS e WebView para Android) em vez de instalar uma biblioteca de terceiros que essencialmente "emula" o Websocket.

Não há a necessidade de código adicional para fazer algo que podemos fazer livremente, o que nos leva ao próximo tópico.

2. Evitar o uso de arquivos binários muito grandes

Você pode querer incorporar rapidamente recursos que, de outro modo, exigiriam uma enorme biblioteca de terceiros.

Por exemplo, para incorporar um gerador de imagem de código QR nativamente, você precisará instalar alguma biblioteca de terceiros que aumentará o tamanho do arquivo binário. Se você, no entanto, usar o mecanismo de visualização da web e uma biblioteca JavaScript por meio de um simples <script src>, obterá tudo isso de graça e não precisará instalar nenhuma biblioteca de terceiros.

3. Falta de uma biblioteca móvel confiável

Para algumas tecnologias de ponta, ainda não existe uma implementação móvel confiável e estável.

Felizmente, a maioria dessas tecnologias possui implementações na web. Portanto, a maneira mais eficiente de integrá-las é usar uma biblioteca JavaScript.

4. Construa aplicações parcialmente nativas e parcialmente baseadas na web

Muitos novos desenvolvedores que desejam transformar seu site em uma aplicação para dispositivos móveis ficam desencorajados ou sobrecarregados quando descobrem que alguns dos recursos existentes em seu site são muito complexos de reescrever rapidamente do zero para uma plataforma móvel.

Por exemplo, você pode ter uma única página e ela, ainda assim, pode ser complexa demais para ser convertida imediatamente em uma aplicação para dispositivos móveis, mas o restante do seu site pode ser convertido facilmente.

Nesse caso, seria bom se houvesse uma maneira de construir a maior parte da aplicação nativamente, mas, para essa página da web complexa específica, de algum modo, integrá-la perfeitamente à aplicação como HTML.

Como Funciona?

A. Jasonette

O Jasonette é uma abordagem de código aberto baseada em marcação para criar aplicações nativas multiplataforma.

É como um navegador da web que, em vez de interpretar o HTML em páginas da web, interpreta o JSON em aplicações nativas no iOS e Android.

Assim como todos os navegadores da Web têm exatamente o mesmo código, mas podem fornecer todos os tipos de aplicações da Web diferentes interpretando vários HTMLs sob demanda, todas as aplicações do Jasonette têm exatamente o mesmo binário e interpretam vários JSONs sob demanda para criar a sua. Os desenvolvedores nunca precisam tocar no código. Em vez disso, você cria aplicações escrevendo um código que se traduz em uma aplicação nativa em tempo real.

Você pode descobrir mais sobre Jasonette aqui.

Enquanto o Jasonette, em sua essência, tem a ver com a construção de aplicações nativas, este artigo específico é sobre a integração do HTML no mecanismo nativo, então vamos começar.

B. Contêiner da web do Jasonette

Aplicações nativas são ótimas. Porém, algumas vezes, precisamos fazer uso das capacidades da web.

Integrar visualizações da web em aplicações móveis nativas, no entanto, é um trabalho complicado. Uma integração perfeita requer que:

  1. A visualização da web seja integrada como parte do layout nativo: a visualização da web deve se integrar à aplicação como parte do layout nativo e ser tratada como qualquer outro componente de interface do usuário. Caso contrário, parecerá desajeitada e exatamente como é - um site.
  2. A aplicação principal possa controlar o contêiner de visualização da web da aplicação subordinada: a aplicação principal deve poder controlar livremente a visualização da web da aplicação subordinada.
  3. O contêiner da web da aplicação subordinada possa acionar eventos nativos na aplicação principal: a aplicação subordinada deve ser capaz de acionar os eventos da aplicação principal para executar APIs nativas.

É bastante trabalho, então trabalhei apenas na primeira peça do quebra-cabeça - simplesmente incorporando um contêiner web no layout nativo - e o lancei como versão 1:

Contêiner web JSON  
O HTML dentro de JSON se transforma em componentes nativos jasonette.com

Isso já era bastante útil, mas ainda tinha a limitação de não ser interativo.

A aplicação principal não pode controlar o contêiner da web da aplicação subordinada e a aplicação subordinada não pode notificar a principal sobre nenhum evento, mantendo o contêiner da web completamente isolado do mundo exterior.

C. Jasonette Web Container 2.0: tornando-o interativo

Após lançar a versão 1, foram feitas experiências com a segunda peça do quebra-cabeça - adicionar interatividade ao contêiner da web.

A próxima seção explica as soluções que foram adicionadas para transformar o contêiner da web anteriormente estático em interativo, tornando-o significativamente mais poderoso.

Implementação: contêiner da web interativo

1. Carregar pelo URL

Problema

Anteriormente, na versão 1, foi usado um contêiner da web como visualização em segundo plano do componente. Primeiramente, o $jason.body.background.type tinha que ser configurado para "html" e, então, era preciso escrever manualmente o HTML no atributo $jason.body.background.text, deste modo:

{  "$jason": {    "head": {      ...    },    "body": {      "background": {        "type": "html",        "text": "<html><body><h1>Hello World</h1></body></html>"      }    }  }}

‌Naturalmente, as pessoas queriam instanciar o contêiner usando simplesmente um URL em vez de ter que digitar todo código HTML em uma única linha.

Solução

O Web Container 2.0 adicionou o atributo url. Agora, um arquivo local file:// pode ser adicionado e será carregado dentro da aplicação.

{  "$jason": {    "head": {      ...    },    "body": {      "background": {        "type": "html",        "url": "file://index.html"      }    }  }}

A alternativa seria adicionar um URL http[s]:// remoto como este (carrega o HTML remotamente):

{  "$jason": {    "head": {      ...    },    "body": {      "background": {        "type": "html",        "url": "https://news.ycombinator.com"      }    }  }}

2. Comunicação entre a aplicação principal <=> Contêiner da web

Problema

Anteriormente, o contêiner da web apenas servia para mostrar conteúdo, mas não para interatividade. Isso significava que nenhuma das situações abaixo era possível:

  1. Jasonette => Contêiner da Web: chamar funções do JavaScript do Jasonette dentro do contêiner da web.
  2. Web Container => Jasonette: chamar APIs nativas dentro do código do contêiner da web.

Tudo que se podia fazer era mostrar o contêiner da web. Isso era similar a inserir um iframe em uma página da web. Porém, a página web não tinha acesso ao que havia no iframe.

Solução

A principal finalidade do Jasonette é criar um padrão de linguagem para descrever uma aplicação para dispositivos móveis multiplataforma. Neste caso, precisa-se de uma linguagem de marcação que descreva extensamente a comunicação entre a aplicação principal e o contêiner da web subordinado.

Para atingir esse objetivo, a solução foi uma comunicação baseada em JSON-RPC, um canal entre a aplicação principal e o contêiner da web subordinado. Como tudo em Jasonette é escrito em objetos JSON, fez muito sentido usar o padrão JSON-RPC como protocolo de comunicação.

‌                                              

image-2-1
Antes e depois do Web Container 2.0

Para chamar uma função do Javascript dentro do contêiner da web, temos que declarar uma ação chamada $agent.request:

{  "type": "$agent.request",  "options": {    "id": "$webcontainer",    "method": "login",    "params": ["username", "password"]  }}

$agent.request é uma API nativa que aciona uma requisição JSON-RPC dentro do contêiner da web. Para usá-la é preciso passar um objeto options como parâmetro.

O objeto options é, na verdade, a requisição JSON-RPC, que será enviada ao contêiner da web. Vamos dar uma olhada no que cada atributo significa:

  • id: O contêiner da web é construído sobre uma camada inferior chamada agent. Normalmente, pode-se ter múltiplos agents para uma única visualização e cada agent terá sua identificação única (ID). Porém, um contêiner da web é um tipo especial de agent que só pode ter o id $webcontainer. É por isso que essa ID foi usada.
  • method:  O nome da função Javascript que será chamada
  • params:  O array com parâmetros a serem passados para a função Javascript.

O código inteiro ficaria assim:

{  "$jason": {    "head": {      "actions": {        "$load": {          "type": "$agent.request",          "options": {            "id": "$webcontainer",            "method": "login",            "params": ["alice", "1234"]          }        }      }    },    "body": {      "header": {        "title": "Web Container 2.0"      },      "background": {        "type": "html",        "url": "file://index.html"      }    }  }}

Explicação:

Quando a visualização carregar ($jason.head.actions.$load), faça uma requisição JSON-RPC dentro do agent do contêiner da web ($agent.request), onde a requisição será detalhada em options.

O contêiner da web é definido em $jason.body.background, que, nesse caso, carrega um arquivo local chamado file://index.html.

A função de login será chamada e os dois argumentos em params ( "alice" e "1234") serão passados.

login("alice", "1234")

Foi explicado apenas como a aplicação principal pode acionar funções do Javascript na aplicação subordinada, mas também é possível fazer o oposto e permitir que o contêiner da web acione a API nativa da aplicação principal.

Para saber mais, verifique a documentação do agent.

Exemplo

Voltemos ao exemplo do QR Code disponibilizado anteriormente:

2-1-2
O gerador de QR code em ação
  1. O rodapé é 100% nativo.
  2. O QR Code é gerado pelo contêiner como uma aplicação da web.
  3. Quando o usuário digita alguma coisa e pressiona "Generate", ele chama $agent.request  dentro do agent do contêiner da web, chamando a função "qr" do JavaScript.

Você pode ver um exemplo aqui.

3. Injeção de script

Problema

Às vezes, é necessário injetar dinamicamente o código em Javascript no contêiner da web após o carregamento inicial do HTML.

Imagine que se queira construir um navegador da web personalizado. Poderia ser necessário injetar seu próprio código em Javascript na visualização para personalizar o comportamento da visualização, algo parecido com o modo como as extensões de navegador funcionam.

Ainda que não se esteja construindo um navegador da web, poderia ser necessário injetar um script a qualquer momento que um comportamento personalizado fosse desejado em um URL em que não se tivesse o controle do conteúdo. A única forma de comunicação entre a aplicação nativa e o contêiner da web é através da API do $agent. Se o conteúdo do HTML não puder ser modificado, no entanto, a única forma de adicionar o $agent ao contêiner da web é através da injeção dinâmica de script.

Solução

Como mencionado anteriormente, o contêiner da web $jason.body.background  é apenas outro agent. Isso significa que o método $agent.inject pode ser usado (ele fica disponível para todos os agents).

image-4-1
Imagem que mostra a aplicação de código em Javascript personalizado ao contêiner da web

4. Gerenciamento de cliques em URL

No passado, existia apenas duas formas pelas quais um contêiner da web poderia gerenciar cliques nos links:

  1. Somente leitura:  Tratava o contêiner da web como somente leitura e ignorava todos os eventos, como toques ou rolagem de página. Todos os contêineres da web são somente leitura, a menos que seja indicado para eles se comportarem como navegadores normais, como descrito adiante.
  2. Comportamento padrão do navegador: Permite que os usuários interajam com a página, se comportando como um navegador normal. Isso pode ser feito configurando "type": "$default" como atributo de action.

Problema

As duas soluções são do tipo "ou tudo ou nada".

  • No caso de "somente leitura", todas as interações são completamente ignoradas pelo contêiner da web.
  • No caso do comportamento padrão do navegador da web, o contêiner funciona literalmente como um navegador. Quando um link é clicado, o navegador vai até esse link e recarrega a página, assim como uma página web normal faria. Não havia meios de capturar o clique e chamar alguma API nativa.

Solução

Com o novo contêiner da web, agora é possível associar as ações ao action no contêiner $jason.body.background para gerenciar eventos de clique.

image-5-2
Imagem que mostra o gerenciamento de eventos de clique

Vamos ver um exemplo:

{  "$jason": {    "head": {      "actions": {        "displayBanner": {          "type": "$util.banner",          "options": {            "title": "Clicked",            "description": "Link {{$jason.url}} clicked!"          }        }      }    },    "body": {      "background": {        "type": "html",        "url": "file://index.html",        "action": {          "trigger": "displayBanner"        }      }    }  }}


Aqui o "trigger": "displayBanner"  foi adicionado ao contêiner da web. Isso significa que, quando um usuário clicar em qualquer link no contêiner da web, será acionada a ação do displayBanner em vez de a visualização web ter que fazer esse gerenciamento.

Se a ação do displayBanner for observada mais de perto, será possível notar a variável $jason. Por exemplo, se o URL clicado for "https://google.com", então a variável $jason terá o seguinte conteúdo:

{  "url": "https://google.com"}


Isso significa que os acionadores podem ser usados de maneira seletiva dependendo do conteúdo de $jason.url .

Vejamos outro exemplo de implementação de um navegador da web personalizado:

{  "$jason": {    "head": {      "actions": {        "handleLink": [{          "{{#if $jason.url.indexOf('signin') !== -1 }}": {            "type": "$href",            "options": {              "url": "file://key.html"            }          }        }, {          "{{#else}}": {            "type": "$default"          }        }]      }    },    "body": {      "background": {        "type": "html",        "url": "file://index.html",        "action": {          "trigger": "handleLink"        }      }    }  }}

Primeiro, verificamos se o URL contém o texto signin. Depois, executamos duas ações diferentes, dependendo do resultado.

  1. Se contiver signin, uma nova visualização será aberta para fazer a gerência da autenticação nativamente.
  2. Se não contiver signin, apenas executará a ação "type": "$default" , fazendo-o se comportar como um navegador da web padrão.

Exemplo de uso

Construindo um navegador da web personalizado

Agora, nós podemos nos aproveitar do fato de os novos contêineres da web poderem:

  1. Pegar o atributo url e carregá-lo, funcionando como um navegador completo
  2. Seletivamente, gerenciar cliques em links dependendo do URL

Podemos ainda construir uma aplicação de navegador da web com apenas algumas linhas de JSON. Agora que podemos capturar cada clique, podemos usar o $jason.url e executar quaisquer ações que desejarmos, dependendo do URL.

Por exemplo, vamos analisar o exemplo seguinte:

3-1
GIF que mostra o funcionamento de um navegador da web personalizado
4-1
GIF que mostra o funcionamento de um navegador da web personalizado

Na primeira imagem, podemos observar que, clicando no link, o navegador se comporta como um navegador comum ("type": "$default").

Na segunda imagem, podemos observar que, clicando no link, o navegador se comporta como uma aplicação nativa e mostra uma visualização baseada em JASON.

Tudo isso é feito executando diferentes ações baseadas nos valores de $jason.url.

Passo 1. Insira uma ação visit no contêiner da web, desta forma:

{  ...  "body": {    "background": {      "type": "html",      "url": "https://news.ycombinator.com",      "action": {        "trigger": "visit"      }    }  }}


Passo 2. Execute ações relevantes dentro de visit, baseadas no conteúdo de $jason.url

No código a seguir, verifica-se se $jason.url contém newest, show, ask e outras coisas (elas estão no topo do menu de itens). Se elas estiverem presentes, então o contêiner da web se comporta como um navegador comum, configurando o  "type": "$default" .

Por outro lado, se não estiverem presentes, faremos uma transição para uma nova visualização nativa, usando o $href e passando o link clicado como parâmetro.

..."actions": {  "visit": [    {      "{{#if /\\/(newest|show|ask)$/.test($jason.url) }}": {        "type": "$default"      }    },    {      "{{#else}}": {        "type": "$href",        "options": {          "url": "https://jasonette.github.io/Jasonpedia/webcontainer/agent/hijack.json",          "preload": {            "background": "#ffffff"          },          "options": {            "url": "{{$jason.url}}"          }        }      }    }  ]},


Dê uma olhada no código JSON inteiro aqui (são apenas 48 linhas de código).

Uma aplicação "híbrida" instantaneamente

Normalmente, quando as pessoas falam sobre aplicações híbridas, elas querem dizer aplicações da web dentro de um frame nativo.

Isso, porém, não é o que eu quero dizer. Quando digo "híbrida", quero dizer híbrida de verdade, como uma aplicação que pode ter múltiplas visualizações nativas e múltiplas visualizações da web simultaneamente. A aplicação também pode ter múltiplas interfaces de usuário e o contêiner da web pode renderizar isso tudo no mesmo layout nativo.

A fronteira entre a visualização da aplicação da web e a visualização para dispositivos móveis é tão imperceptível, que fica difícil dizer onde uma começa e a outra termina.

5-1
GIF mostrando as capacidades do Jasonbase

Nesse exemplo, eu criei uma aplicação que mostra jasonbase.com em um contêiner da web como página principal.

Jasonbase é um serviço gratuito de hospedagem de JSON que eu construí para hospedar facilmente arquivos JSON para aplicações do Jasonette.

Naturalmente, é apenas um site, mas eu o incorporei no Jasonette para que, quando você clicar no link, em vez de abrir a página da web, faça uma transição $href nativa para uma visualização do JASON nativa.

Não precisei mexer em nenhum código do Jasonbase.com para criar essa aplicação.

Eu simplesmente incorporei o site no Jasonette como um contêiner da web e capturei os cliques do link para tratá-los nativamente, para que ele possa fazer todas as coisas nativas, como acionar APIs nativas e fazer transições nativas.

Você pode conferir o código aqui.

Conclusão

Na minha opinião, o que faz tudo isso funcionar fabulosamente é que tudo é cuidado no nível do framework. Todo o trabalho duro é feito nos bastidores.

Em vez de colocar a carga sobre os desenvolvedores de aplicações de implementar tudo do zero:

  • Incorporar uma visualização da web no layout nativo
  • Criar uma ponte de JavaScript para que a aplicação possa fazer chamadas de função na visualização da web
  • Criar uma arquitetura nativa de manipulação de eventos para que a visualização da web possa acionar eventos nativos na aplicação principal

A solução foi criar uma abstração composta por:

  1. Linguagem de marcação declarativa: para descrever como incorporar uma visualização da web em uma aplicação nativa
  2. Protocolo de comunicação (JSON-RPC): para permitir interações simples entre a aplicação e suas visualizações da web subordinadas.

Não afirmo que essa abordagem seja a solução definitiva para resolver tudo, mas fico feliz em dizer que essa foi uma ótima solução para meu próprio caso de uso.

Eu tentava construir uma aplicação baseada em uma tecnologia de ponta que não possui implementações móveis estáveis ​​e confiáveis ​​(e não está claro se haverá uma implementação móvel devido à natureza do protocolo). Felizmente, havia implementações de JavaScript que podiam ser integradas facilmente à aplicação sem problemas.

No geral, foi ótimo e estou satisfeito com o resultado. A documentação está atualizada para refletir todos os novos recursos. Portanto, sinta-se à vontade para pesquisar e brincar.

Aviso: com grandes poderes vêm grandes responsabilidades

Eu gostaria de terminar com um aviso: por maior que seja esse poder recém-descoberto, acho que você precisa manter um equilíbrio para criar uma aplicação com uma ótima experiência do usuário.

Alguns podem pegar isso e criar uma aplicação inteira usando apenas visualizações da web, mas você acabará com uma aplicação que é basicamente apenas um site, o que anula o objetivo de criar uma aplicação dedicada.

Enfatizo que não estou dizendo que você deve sempre construir aplicações com HTML e nativa. Estou dizendo que isso pode ser muito útil para muitas pessoas em diferentes situações. Só não exagere.

Para saber mais:

Existem muitas configurações diferentes nas quais o núcleo nativo do Jasonette e seu contêiner da web subordinado podem se comunicar para fazer as coisas de maneiras criativas e poderosas. Esta publicação é apenas o início.

No futuro, o autor planeja compartilhar mais desses casos de uso e tutoriais. Se estiver interessado, siga-o no Medium ou no Twitter.