Desenvolvendo para navegadores modernos e melhorando progressivamente como em 2003
Em março de 2003, Nick Finck e Steve Champeon surpreendeu o mundo do web design com o conceito de aprimoramento progressivo, uma estratégia de web design que enfatize primeiro o carregamento do conteúdo principal da página da web, Isso adiciona progressivamente mais nuances e tecnicamente rigorosas de apresentação e recursos sobre o conteúdo. Enquanto em 2003, o aprimoramento progressivo consistia em usar, na época, o uso de recursos recursos CSS, JavaScript discreto e até mesmo gráficos vetoriais escaláveis. O aprimoramento progressivo de 2020 em diante envolve o uso de recursos modernos do navegador.
JavaScript moderno
Falando em JavaScript, a situação de suporte do navegador para o JavaScript com núcleo mais recente do ES 2015
é ótimo.
O novo padrão inclui promessas, módulos, classes, literais de modelo, funções de seta, let
e const
.
parâmetros padrão, geradores, a atribuição de desestruturação, repouso e propagação, Map
/Set
WeakMap
/WeakSet
e muitos outros.
Todos são compatíveis.
Funções assíncronas, um recurso do ES 2017 e um dos meus favoritos,
pode ser usado
em todos os principais navegadores.
As palavras-chave async
e await
permitem um comportamento assíncrono e baseado em promessas
seja escrita em um estilo mais limpo, evitando a necessidade de configurar explicitamente as cadeias de promessas.
E até mesmo adições de idiomas recentes do ES 2020, como encadeamento opcional e uma fusão nula chegaram ao suporte rapidamente. Veja um exemplo de código abaixo. Quando se trata dos principais recursos do JavaScript, a grama não poderia ser muito mais ecológica do que é hoje.
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah',
},
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
O app de exemplo: Fugu Greetings
Para este artigo, trabalho com um PWA simples, chamado Saudações do fugu (GitHub). O nome do app é uma demonstração do Project Fugu 🐡, uma iniciativa para dar todo o trabalho na Web a capacidade dos aplicativos para Android/iOS/computador. Você pode ler mais sobre o projeto na página de destino.
O Fugu Greetings é um app de desenho que permite criar cartões comemorativos virtuais e enviar para seus entes queridos. Ele exemplifica Principais conceitos do PWA. Está confiável e totalmente off-line ativado, portanto, mesmo tiver uma rede, ainda poderá usá-la. Também é instalável. à tela inicial de um dispositivo e integra-se perfeitamente ao sistema operacional como um aplicativo independente.
Aprimoramento progressivo
Com isso resolvido, é hora de falar sobre o aprimoramento progressivo. O glossário do MDN Web Docs define o conceito da seguinte maneira:
O aprimoramento progressivo é uma filosofia de design que fornece uma linha de base conteúdo e funcionalidade essenciais para o maior número possível de usuários, enquanto oferecendo a melhor experiência possível somente para os usuários dos dispositivos que podem executar todos os códigos necessários.
Detecção de recursos geralmente é usado para determinar se os navegadores aceitam funcionalidades mais modernas, e os polyfills são frequentemente usadas para adicionar recursos ausentes com JavaScript.
[…]
O aprimoramento progressivo é uma técnica útil que permite que os desenvolvedores Web se concentrem desenvolver os melhores sites possíveis e, ao mesmo tempo, fazer com que eles funcionem em vários user agents desconhecidos. Degradação suave está relacionada, mas não é a mesma coisa e costuma ser vista como indo na direção oposta ao aprimoramento progressivo. Na realidade, as duas abordagens são válidas e muitas vezes podem se complementar.
Colaboradores do MDN
Começar cada cartão comemorativo do zero pode ser muito complicado.
Então, por que não ter um recurso que permita aos usuários importar uma imagem e começar daí?
Com uma abordagem tradicional, você teria usado
<input type=file>
para que isso aconteça.
Primeiro, você criaria o elemento, definiria a type
dele como 'file'
e adicionaria tipos MIME à propriedade accept
.
e depois "clicar" de forma programática e detectar alterações.
Quando você seleciona uma imagem, ela é importada diretamente para a tela.
const importImage = async () => {
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.addEventListener('change', () => {
resolve(input.files[0]);
});
input.click();
});
};
Quando há um recurso de importação, provavelmente é necessário ter um recurso de exportação.
para que os usuários possam salvar os cartões localmente.
A maneira tradicional de salvar arquivos é criar um link âncora
com um download
e com um URL de blob como href
.
Você também clicaria programaticamente que acione o download
e, para evitar vazamentos de memória, não se esqueça de revogar o URL do objeto blob.
const exportImage = async (blob) => {
const a = document.createElement('a');
a.download = 'fugu-greeting.png';
a.href = URL.createObjectURL(blob);
a.addEventListener('click', (e) => {
setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
});
a.click();
};
Mas espere um pouco. Mentalmente, você não fez o download um cartão comemorativo, você tem "salvo" reimplantá-lo. Em vez de exibir a opção "Salvar" que permite escolher onde colocar o arquivo, o navegador fez o download do cartão comemorativo diretamente, sem interação do usuário e o coloca diretamente na pasta "Downloads". Isso não é muito bom.
E se houvesse uma maneira melhor? E se você pudesse abrir um arquivo local, editá-lo e salvar as modificações, para um novo arquivo ou de volta para o arquivo original que você abriu inicialmente? Acontece que existe. A API File System Access permite abrir e criar arquivos e diretórios, bem como modificá-los e salvá-los .
Como posso detectar atributos em uma API?
A API File System Access expõe um novo método window.chooseFileSystemEntries()
.
Consequentemente, preciso carregar condicionalmente módulos de importação e exportação diferentes, dependendo da disponibilidade desse método. Mostrei como fazer isso abaixo.
const loadImportAndExport = () => {
if ('chooseFileSystemEntries' in window) {
Promise.all([
import('./import_image.mjs'),
import('./export_image.mjs'),
]);
} else {
Promise.all([
import('./import_image_legacy.mjs'),
import('./export_image_legacy.mjs'),
]);
}
};
Mas, antes de nos aprofundarmos nos detalhes da API File System Access, vou destacar rapidamente o padrão de aprimoramento progressivo. Em navegadores que atualmente não oferecem suporte à API File System Access, os scripts legados são carregados. Abaixo, é possível conferir as guias de rede do Firefox e do Safari.
No entanto, no Chrome, um navegador compatível com a API, apenas os novos scripts são carregados.
Isso é possível de forma elegante graças à
import()
dinâmico, que todos os navegadores modernos
do servidor.
Como eu disse antes, a grama está bem verde hoje em dia.
A API File System Access
Agora que abordei isso, é hora de analisar a implementação real com base na API File System Access.
Para importar uma imagem, chamo window.chooseFileSystemEntries()
e transmitir uma propriedade accepts
onde eu digo que quero arquivos de imagem.
Tanto as extensões de arquivo quanto os tipos MIME são compatíveis.
Isso resulta em um identificador de arquivo, do qual posso acessar o arquivo real chamando getFile()
.
const importImage = async () => {
try {
const handle = await window.chooseFileSystemEntries({
accepts: [
{
description: 'Image files',
mimeTypes: ['image/*'],
extensions: ['jpg', 'jpeg', 'png', 'webp', 'svg'],
},
],
});
return handle.getFile();
} catch (err) {
console.error(err.name, err.message);
}
};
Exportar uma imagem é quase o mesmo, mas desta vez
Preciso transmitir um parâmetro de tipo de 'save-file'
ao método chooseFileSystemEntries()
.
Depois, vejo uma caixa de diálogo para salvar arquivos.
Com o arquivo aberto, isso não era necessário porque 'open-file'
é o padrão.
Configurei o parâmetro accepts
de forma semelhante a antes, mas desta vez se limitei apenas a imagens PNG.
Mais uma vez, obtenho um identificador de arquivo, mas, em vez de receber o arquivo,
Desta vez, crie um stream gravável chamando createWritable()
.
Em seguida, crio o blob, que é a imagem do meu cartão comemorativo, no arquivo.
Por fim, fecho o stream gravável.
Tudo pode falhar: o disco pode estar sem espaço,
pode haver um erro de gravação ou leitura, ou talvez simplesmente o usuário cancele a caixa de diálogo do arquivo.
É por isso que sempre unifico as chamadas em uma instrução try...catch
.
const exportImage = async (blob) => {
try {
const handle = await window.chooseFileSystemEntries({
type: 'save-file',
accepts: [
{
description: 'Image file',
extensions: ['png'],
mimeTypes: ['image/png'],
},
],
});
const writable = await handle.createWritable();
await writable.write(blob);
await writable.close();
} catch (err) {
console.error(err.name, err.message);
}
};
Usar o aprimoramento progressivo com a API File System Access, posso abrir um arquivo como antes. O arquivo importado é desenhado diretamente na tela. Posso fazer minhas edições e, por fim, salvá-las em uma caixa de diálogo de salvamento real onde posso escolher o nome e o local de armazenamento do arquivo. Agora o arquivo está pronto para ser preservado para a eternidade.
APIs Web Share e Web Share Target
Além de guardar para sempre, talvez eu queira compartilhar meu cartão comemorativo. Isso é algo que a API Web Share e com a API Web Share Target. Para dispositivos móveis e, mais recentemente, os sistemas operacionais de desktop ganharam compartilhamento integrado de segurança. Por exemplo, abaixo está a página de compartilhamento do Safari para computador no macOS acionada de um artigo sobre meu blog. Ao clicar no botão Compartilhar artigo, você pode compartilhar um link para o artigo com um amigo, por exemplo, pelo app Mensagens do macOS.
O código para que isso aconteça é bem simples. Eu chamo navigator.share()
e
transmita a ele title
, text
e url
opcionais em um objeto.
E se eu quiser anexar uma imagem? O nível 1 da API Web Share ainda não é compatível com isso.
A boa notícia é que o Nível 2 de compartilhamento da Web adicionou recursos de compartilhamento de arquivos.
try {
await navigator.share({
title: 'Check out this article:',
text: `"${document.title}" by @tomayac:`,
url: document.querySelector('link[rel=canonical]').href,
});
} catch (err) {
console.warn(err.name, err.message);
}
Vou mostrar como fazer isso funcionar com o aplicativo de cartões comemorativos do Fugu.
Primeiro, preciso preparar um objeto data
com uma matriz files
composta por um blob e, em seguida,
um title
e um text
. Em seguida, como prática recomendada, eu uso o novo método navigator.canShare()
, que não
o que o nome sugere:
Ele me diz se o objeto data
que estou tentando compartilhar pode tecnicamente ser compartilhado pelo navegador.
Se o navigator.canShare()
disser que os dados podem ser compartilhados, estou pronto para
chame navigator.share()
como antes.
Como tudo pode falhar, estou novamente usando um bloco try...catch
.
const share = async (title, text, blob) => {
const data = {
files: [
new File([blob], 'fugu-greeting.png', {
type: blob.type,
}),
],
title: title,
text: text,
};
try {
if (!(navigator.canShare(data))) {
throw new Error("Can't share data.", data);
}
await navigator.share(data);
} catch (err) {
console.error(err.name, err.message);
}
};
Assim como antes, eu uso o aprimoramento progressivo.
Se 'share'
e 'canShare'
existirem no objeto navigator
, somente então
carregar share.mjs
usando import()
dinâmico.
Em navegadores como o Safari para dispositivos móveis, que só atendem a uma das duas condições, eu não carrego
a funcionalidade.
const loadShare = () => {
if ('share' in navigator && 'canShare' in navigator) {
import('./share.mjs');
}
};
No Fugu Saudações, se eu tocar no botão Compartilhar em um navegador compatível, como o Chrome no Android, a planilha de compartilhamento integrada será aberta. Posso, por exemplo, escolher o Gmail, e o widget de composição de e-mails será exibido com o em anexo.
API Contact Picker
Agora, eu quero falar sobre os contatos, ou seja, o catálogo de endereços de um dispositivo ou gerenciador de contatos. Nem sempre é fácil escrever corretamente um cartão comemorativo o nome de alguém. Por exemplo, tenho um amigo Sergey que prefere que seu nome seja escrito em letras cirílicas. estou usando um teclado QWERTZ alemão e não sabe como digitar seu nome. Esse problema pode ser resolvido pela API Contact Picker. Como meu amigo está armazenado no app Contatos do smartphone, por meio da API de seletor de contatos, posso acessar meus contatos na web.
Primeiro, preciso especificar a lista de propriedades que quero acessar.
Neste caso, só quero os nomes,
Mas para outros casos de uso, posso ter interesse em números de telefone, e-mails, avatar
ícones ou endereços físicos.
Em seguida, vou configurar um objeto options
e definir multiple
como true
, para poder selecionar mais
mais de uma entrada.
Por fim, posso chamar navigator.contacts.select()
, que retorna as propriedades desejadas
para os contatos selecionados pelo usuário.
const getContacts = async () => {
const properties = ['name'];
const options = { multiple: true };
try {
return await navigator.contacts.select(properties, options);
} catch (err) {
console.error(err.name, err.message);
}
};
Agora você provavelmente já aprendeu o padrão: Eu só carrego o arquivo quando há suporte para a API.
if ('contacts' in navigator) {
import('./contacts.mjs');
}
No Fugu Greeting, quando toco no botão Contacts e seleciono meus dois melhores amigos, Бергей гитайлови comunicações Брин e 劳伦斯·爱德华·"拉里"·佩奇, (em inglês) é possível conferir como o seletor de contatos exibe apenas os nomes, mas não o endereço de e-mail ou outras informações, como números de telefone. Os nomes deles são atraídos no meu cartão comemorativo.
A API Async Clipboard
O próximo passo é copiar e colar. Uma das nossas operações favoritas como desenvolvedores de software é copiar e colar. Como autor de cartões comemorativos, às vezes quero fazer o mesmo. vou colar uma imagem em um cartão comemorativo em que estou trabalhando, ou copiar meu cartão comemorativo para que eu possa continuar editando-o em outro lugar. A API Async Clipboard, oferece suporte a texto e imagens. Vamos conferir como adicionei o suporte ao recurso copiar e colar no Fugu Cumprimento.
Para copiar algo para a área de transferência do sistema, preciso gravar.
O método navigator.clipboard.write()
usa uma matriz de itens da área de transferência como
.
Cada item da área de transferência é essencialmente um objeto com um blob como valor e o tipo do blob
como a chave.
const copy = async (blob) => {
try {
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob,
}),
]);
} catch (err) {
console.error(err.name, err.message);
}
};
Para colar, preciso fazer um loop aos itens da área de transferência que obtive chamando
navigator.clipboard.read()
:
A razão para isso é que vários itens da área de transferência podem estar na área de transferência em
representações de vetor diferentes.
Cada item da área de transferência tem um campo types
que informa os tipos MIME dos
do Google Cloud.
Eu chamo o método getType()
do item da área de transferência, passando o
Tipo MIME que eu recebi antes.
const paste = async () => {
try {
const clipboardItems = await navigator.clipboard.read();
for (const clipboardItem of clipboardItems) {
try {
for (const type of clipboardItem.types) {
const blob = await clipboardItem.getType(type);
return blob;
}
} catch (err) {
console.error(err.name, err.message);
}
}
} catch (err) {
console.error(err.name, err.message);
}
};
E isso é quase desnecessário neste momento. Só faço isso em navegadores compatíveis.
if ('clipboard' in navigator && 'write' in navigator.clipboard) {
import('./clipboard.mjs');
}
Como isso funciona na prática? Tenho uma imagem aberta no app macOS Preview e copiar para a área de transferência. Quando clico em Colar, o app Fugu Greetings me pergunta se eu quero permitir que o aplicativo acesse textos e imagens na área de transferência.
Por fim, depois de aceitar a permissão, a imagem é colada no aplicativo. O contrário também funciona. Vou copiar um cartão para a área de transferência. Quando eu abro a visualização e clico em File e em New from Clipboard, o cartão comemorativo é colado em uma nova imagem sem título.
API Badging
Outra API útil é a API de selos.
Como um PWA instalável, o Fugu Greetings, é claro, tem um ícone do app.
que os usuários podem colocar no dock de apps ou na tela inicial.
Uma maneira fácil e divertida de demonstrar a API é (ab) usá-la no Fugu Greetings
como um contador de traços de caneta.
Adicionei um listener de eventos que incrementa o contador de traços da caneta sempre que ocorre o evento pointerdown
.
e define o selo de ícone atualizado.
Sempre que a tela é apagada, o contador é redefinido e o selo é removido.
let strokes = 0;
canvas.addEventListener('pointerdown', () => {
navigator.setAppBadge(++strokes);
});
clearButton.addEventListener('click', () => {
strokes = 0;
navigator.setAppBadge(strokes);
});
Esse recurso é um aprimoramento progressivo. Portanto, a lógica de carregamento está normalmente.
if ('setAppBadge' in navigator) {
import('./badge.mjs');
}
Neste exemplo, desenhei os números de um a sete usando um traço de caneta por número. O contador de selos do ícone agora está em sete.
API Periodic Background Sync
Quer começar o dia com algo novo? Um recurso legal do app Fugu Greetings é que ele pode inspirar você a cada manhã com uma nova imagem de plano de fundo para iniciar o cartão comemorativo. O app usa a API Periodic Background Sync para isso.
A primeira etapa é registrar um evento de sincronização periódico no registro do service worker.
Ele detecta uma tag de sincronização chamada 'image-of-the-day'
.
e tem um intervalo mínimo de um dia,
para que o usuário possa receber
uma nova imagem de plano de fundo a cada 24 horas.
const registerPeriodicBackgroundSync = async () => {
const registration = await navigator.serviceWorker.ready;
try {
registration.periodicSync.register('image-of-the-day-sync', {
// An interval of one day.
minInterval: 24 * 60 * 60 * 1000,
});
} catch (err) {
console.error(err.name, err.message);
}
};
A segunda etapa é detectar o evento periodicsync
no service worker.
Se a tag de evento for 'image-of-the-day'
, ou seja, a que foi registrada antes,
a imagem do dia é recuperada pela função getImageOfTheDay()
,
e o resultado foi propagado para todos os clientes, para que pudessem atualizar as telas e
armazenamento em cache.
self.addEventListener('periodicsync', (syncEvent) => {
if (syncEvent.tag === 'image-of-the-day-sync') {
syncEvent.waitUntil(
(async () => {
const blob = await getImageOfTheDay();
const clients = await self.clients.matchAll();
clients.forEach((client) => {
client.postMessage({
image: blob,
});
});
})()
);
}
});
Mais uma vez, este é realmente um aprimoramento progressivo, portanto, o código só é carregado quando o
O navegador oferece suporte à API.
Isso se aplica tanto ao código do cliente quanto ao código do service worker.
Em navegadores não compatíveis, nenhum deles é carregado.
Observe como no service worker, em vez de um import()
dinâmico
(incompatível com um contexto de service worker
ainda),
Eu uso a versão clássica
importScripts()
// In the client:
const registration = await navigator.serviceWorker.ready;
if (registration && 'periodicSync' in registration) {
import('./periodic_background_sync.mjs');
}
// In the service worker:
if ('periodicSync' in self.registration) {
importScripts('./image_of_the_day.mjs');
}
No Fugu Greetings, pressionar o botão Plano de fundo revela a imagem do cartão comemorativo do dia que é atualizada todos os dias pela API Periodic Background Sync.
API Notification Triggers
Às vezes, mesmo com muita inspiração, você precisa de um empurrãozinho para terminar uma saudação inicial carda. Esse é um recurso ativado pela API Notification Triggers. Como usuário, posso inserir um horário em que quero receber um lembrete para finalizar meu cartão comemorativo. Quando esse horário chegar, receberei uma notificação informando que meu cartão comemorativo está esperando.
Depois de solicitar o horário desejado,
o app programa a notificação com um showTrigger
.
Pode ser um TimestampTrigger
com a data do destino selecionada anteriormente.
A notificação de lembrete será acionada localmente. Não é necessário rede ou servidor.
const targetDate = promptTargetDate();
if (targetDate) {
const registration = await navigator.serviceWorker.ready;
registration.showNotification('Reminder', {
tag: 'reminder',
body: "It's time to finish your greeting card!",
showTrigger: new TimestampTrigger(targetDate),
});
}
Como tudo o que já mostrei até agora, este é um aprimoramento progressivo, Portanto, o código só é carregado condicionalmente.
if ('Notification' in window && 'showTrigger' in Notification.prototype) {
import('./notification_triggers.mjs');
}
Quando marco a caixa de seleção Lembrete em "Saudações do Fugu", aparece uma solicitação quando eu quiser ser lembrado de terminar meu cartão comemorativo.
Quando uma notificação programada é acionada no Fugu Greetings, ela é mostrada como qualquer outra notificação, mas, como escrevi antes, mas não exigia uma conexão de rede.
API Wake Lock
Também quero incluir a API Wake Lock. Às vezes, você só precisa olhar para a tela por tempo suficiente até obter inspiração beijos você. O pior que pode acontecer é a tela desligar. A API Wake Lock pode impedir que isso aconteça.
A primeira etapa é acessar um wake lock com o navigator.wakelock.request method()
.
Transmitirei a string 'screen'
para receber um wake lock de tela.
Em seguida, adiciono um listener de eventos para ser informado quando o wake lock for liberado.
Isso pode acontecer, por exemplo, quando a visibilidade da guia muda.
Se isso acontecer, quando a guia ficar visível novamente, posso acessar o wake lock de novo.
let wakeLock = null;
const requestWakeLock = async () => {
wakeLock = await navigator.wakeLock.request('screen');
wakeLock.addEventListener('release', () => {
console.log('Wake Lock was released');
});
console.log('Wake Lock is active');
};
const handleVisibilityChange = () => {
if (wakeLock !== null && document.visibilityState === 'visible') {
requestWakeLock();
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
document.addEventListener('fullscreenchange', handleVisibilityChange);
Sim, este é um aprimoramento progressivo, então só preciso carregá-lo quando o navegador oferece suporte à API.
if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
import('./wake_lock.mjs');
}
Em "Saudações do Fugu", há uma caixa de seleção Insônia que, quando marcada, mantém a tela ativada.
API Idle Detection
Às vezes, mesmo se você olhar para a tela por horas, Isso é inútil, e você não consegue ter a menor ideia do que fazer com um cartão comemorativo. A API Idle Detection permite que o app detecte o tempo de inatividade do usuário. Se o usuário ficar inativo por muito tempo, o app será redefinido para o estado inicial. e limpa a tela. No momento, essa API está protegida pela permissão de notificações, já que muitos casos de uso de produção de detecção de inatividade estão relacionados a notificações, Por exemplo, para enviar uma notificação somente a um dispositivo que o usuário esteja usando ativamente no momento.
Depois de ter certeza de que a permissão de notificações foi concedida, instalo a detector inativo. Eu registro um listener de eventos que detecta alterações ociosas, que incluem o usuário e o estado da tela. O usuário pode estar ativo ou inativo, e a tela pode ser desbloqueada ou bloqueada. Se o usuário estiver inativo, a tela será limpa. O detector inativo tem um limite de 60 segundos.
const idleDetector = new IdleDetector();
idleDetector.addEventListener('change', () => {
const userState = idleDetector.userState;
const screenState = idleDetector.screenState;
console.log(`Idle change: ${userState}, ${screenState}.`);
if (userState === 'idle') {
clearCanvas();
}
});
await idleDetector.start({
threshold: 60000,
signal,
});
E, como sempre, eu só carrego esse código quando o navegador é compatível.
if ('IdleDetector' in window) {
import('./idle_detection.mjs');
}
No app Fugu Greetings, a tela é apagada quando a caixa de seleção Ephemeral é está marcada e o usuário fica inativo por muito tempo.
Encerramento
Uau, que viagem. Muitas APIs em apenas um app de exemplo. E, lembre-se, eu nunca obrigamos o usuário a pagar o custo do download para um recurso incompatível com o navegador. Ao usar o aprimoramento progressivo, posso garantir que apenas o código relevante seja carregado. E, como as solicitações são baratas com o HTTP/2, esse padrão deve funcionar bem aplicativos, embora seja melhor considerar um bundler para apps muito grandes.
O aplicativo pode parecer um pouco diferente em cada navegador, já que nem todas as plataformas são compatíveis com todos os recursos, mas as funcionalidades principais estão sempre presentes, aprimoradas progressivamente de acordo com as capacidades de cada navegador. Esses recursos podem mudar até no mesmo navegador, dependendo se o app está sendo executado como instalado ou em uma guia do navegador.
Se você estiver interessado pelo app Fugu Greetings, encontre e bifurque no GitHub.
A equipe do Chromium está trabalhando duro para tornar a grama mais verde quando se trata de APIs avançadas do Fugu. Ao aplicar o aprimoramento progressivo no desenvolvimento do meu app, Eu garanto que todos tenham uma experiência de referência boa e sólida, mas que as pessoas que usam navegadores com suporte a mais APIs de plataforma da Web terão uma experiência ainda melhor. Mal posso esperar para ver o que você vai fazer com o aprimoramento progressivo nos seus apps.
Agradecimentos
Sou grato a Christian Liebel e
Hemanth HM, que contribuíram para o Fugu Greetings.
Este artigo foi revisado por Joe Medley e
basco kayce.
Jake Archibald me ajudou a descobrir a situação
com import()
dinâmico em um contexto de service worker.