Extensão Google Chrome com Angular 11
Veja nesse artigo como criar uma extensão para o Google Chrome utilizando o Angular
O que é uma extensão?
Antes de começarmos a desenvolver nosso projeto, primeiro devemos entender o que é e como funciona uma extensão.
Uma extensão nada mais é que um pequeno programa que adiciona um ou vários recursos a um navegador (que no nosso caso será o Google Chrome).
Se você sabe como criar páginas da Web, é tudo o que precisa saber para criar extensões para o Chrome, as extensões são implementadas usando as tecnologias da web padrão, como html
, css
, e java script
.
Uma extensão é na verdade uma pasta compactada, mais precisamente um arquivo zip com uma assinatura que contém vários arquivos.
A única coisa que diferencia uma extensão do Chrome de um projeto web com (html
, css
, java script
) é que obrigatoriamente uma extensão deve ter um arquivo chamadomanifest.json
.
Ele contém todas as informações sobre a extensão, como o número da versão, seu nome e pode apontar para outros componentes da extensão, componentes que, por sua vez, fornecem funcionalidade à extensão.
Por que alguém iria querer desenvolver uma extensão do Chrome?
Existem alguns motivos que lhe faria querer desenvolver uma extensão do Chrome. O primeiro (e o mais improvável) é ganhar dinheiro.
Sim, existem extensões do Chrome publicadas na Chrome Store e os desenvolvedores são pagos por isso. Então se você tem uma ideia para uma extensão e um modelo de negócio que seja lucrativo, é uma boa alternativa.
No entanto, acredito que o principal caso de uso para desenvolver uma extensão é atender às suas necessidades como desenvolvedor no quesito de automatização de tarefas.
Eu utilizo uma técnica chamada de mineração de sentenças junto com o Anki (Software de Repetição Espaçada) para aumentar o meu vocabulário em inglês, então costumo buscar minhas frases/palavras novas em uma série de URLs, porem eu geralmente preciso pesquisar nesse conjunto de URLs a mesma palavra/frase, como esse processo se tornou bastante repetitivo, fiz uma extensão bem simples para automatizar essa pesquisa (caso queira ver clique aqui).
Esse foi apenas um exemplo de algo que eu consegui automatizar utilizando uma extensão,tenho certeza de que você consegue pensar facilmente em pelo menos uma coisa que está fazendo repetidamente durante o ciclo de desenvolvimento ou no uso comum do seu computador.
Algo que pode ser facilmente automatizado com uma simples extensão do Chrome.
Antes de colocarmos a mão na massa é importante entendermos como funciona uma extensão do chrome.
Estrutura Anatômica de uma extensão do Chrome:
Background script:
O background script
é o manipulador de eventos da extensão, ele pode conter ouvintes para eventos do navegador que são importantes para a extensão. Ele permanece inativo até que um evento seja disparado e, em seguida, execute a lógica instruída. um background script
eficaz só é carregado quando necessário e descarregado quando fica ocioso.
Content script:
As extensões que realizam leitura ou gravação em páginas da web utilizam um content script
. O content script
contém o código JavaScript que pode ser injetado em uma página que foi carregada no navegador. O content script
lê e modifica o DOM da página da web que o navegador visita.
Popup (in-browser action):
Se uma ação do navegador tiver um pop-up, ele aparecerá quando o usuário clicar no ícone da extensão. O pop-up pode conter qualquer conteúdo HTML de sua preferência e é dimensionado automaticamente para caber em seu conteúdo.
Comunicação entre as páginas:
Diferentes componentes em uma extensão geralmente precisam se comunicar uns com os outros. Diferentes páginas HTML podem se encontrar usando os métodos da API chrome.extension, como getViews()
e getBackgroundPage()
.
Uma vez que uma página tenha uma referência a outras páginas da extensão, a primeira pode chamar funções nas outras páginas e manipular seus elementos DOM.
Além disso, todos os componentes da extensão podem acessar valores armazenados usando a storage API (que será a forma utilizada nesse artigo) e se comunicar utilizando message passing.
Criando o Projeto:
- Comece criando um novo projeto angular:
ng new angular-chrome-extension
- Navegue até a pasta do projeto
cd angular-chrome-extension
- Crie um arquivo chamado
manifest.json
dentro da pastasrc
.
manifest.json:
Esse arquivo não é nada mais que um JSON
, onde ficarão os metadados relacionados à sua extensão: nome da extensão, descrição, versão, permissões, etc. nesse link você encontra todos os metadados que podem ser incluídos no manifest.json
.
Inclua o código a seguir no arquivo:
Carregando a extensão no chrome:
Ao realizarmos o build
da nossa aplicação precisamos que o arquivo manifest.json
esteja lá também, pois é ele que diz ao chrome que o nosso app é uma extensão, para isso vamos incluí-lo no array da propriedade assets
do nosso angular.json
Execute o comando ng build
para certificar-se de que o manifest.json
está realmente presente no diretório dist
:
Para carregá-la no chrome siga os seguintes passos:
- Abra o Chrome
- Navegue para
chrome://extensions
ou
clique no menu de extensões e vá em gerenciar extensões
- Ative o modo de desenvolvedor clicando no botão toggle no canto superior direito.
- Clique no botão Carregar sem compactação e selecione o diretório da extensão que no nosso caso está em
dist/angular-chrome-extension
Agora você já deve ser capaz de ver a extensão instalada na lista de extensões e poderá fixá-la na barra de extensões.
No entanto, como você provavelmente notou, não há nada na extensão além do ícone padrão e o título que demos a ela no arquivomanifest.json
.
O motivo é que não definimos nenhuma ação. Vamos acrescentar isso ao nosso arquivo manifest.json
:
Utilize a propriedade browser_action
para definir as ações que o navegador irá executar, é possível utilizar as seguintes propriedades abaixo:
default_title
: define qual será o titulo a ser exibido como um tooltip quando o usuário passar o mouse sobre o icone, caso não seja informado será utilizado o nome da extensão definido na proprieade name.
default_icon
: utilizado para definir o ícone a ser exibido da extensão.
default_popup
: pagina html que será exibida quando o usuário clicar no ícone da extensão.
agora com o nosso index.html
definido para ser a nossa página de popup
vamos fazer ng build
da nossa aplicação novamente e clicar para recarregar a nossa extensão.
agora quando clicamos no ícone da extensão nossa aplicação angular será exibida.
Live reload e ng serve
O ideal seria se pudessemos utilizar o comandong serve
da mesma forma que usamos para nossas aplicações web e esperar que a extensão seja atualizada automaticamente sempre que houver uma alteração. Infelizmente esse não é o caso.
Não podemos usar o ng serve
aqui porque ele grava o pacote na memória do servidor de desenvolvimento, enquanto o Chrome precisa de uma pasta física real no disco para carregar a extensão.
Porém podemos utilizar o ng build --watch
.
Ele irá observar as alterações e reconstruíra os arquivos relevantes enquanto atualiza os pacotes no diretório dist
.
Ele se encarregará de atualizar a pasta dist
quando houver alterações nos arquivos ou em nosso manifest.json
.
A extensão ainda não será atualizada automaticamente, mas está mais fácil que antes. Agora, para aplicar as alterações, você só precisa de algumas coisas:
- Se você alterou qualquer arquivo de origem que afeta o
pop-up
da extensão, abra e feche opop-up
.
O Chrome carrega o pop-up diretamente da pastadist
sempre que você o abre, de forma que todas as alterações feitas nopop-up
são aplicadas automaticamente quando ele é reaberto. - Se você alterou o
manifest.json
, deverá recarregar a extensão na página de extensões (assim como fizemos antes).
Omanifest.json
é um conjunto de definições, regras e instruções para sua extensão, portanto, é aplicado quando a extensão é instalada. Ao pressionar o botão de recarregar, você está efetivamente reinstalando a extensão descompactada. - O
background script
também podem ser recarregado automaticamente, mas abordaremos isso mais tarde.
Adicionando Funcionalidades a Extensão
No momento, temos uma extensão que mostra apenas a página padrão do Angular como um pop-up
e, além disso, não faz absolutamente nada. Vamos mudar isso.
Primeiro, definiremos o que essa extensão fará:
- Ela estará ativa apenas em sites sob o domínio
google.com
. - Irá conter um
color-picker
. - Irá mudar o
background-color
de uma página para a cor selecionada ao pressionar um botão.
Ativar a extensão apenas em sites específicos
Até agora estávamos usando apenas o browser_action
sem o background script
ou o content script
, vamos relembrar o que é obrowser_action
.
API para colocar ícones na barra de ferramentas principal do Google Chrome, à direita da barra de endereço. As ações da página representam ações que podem ser realizadas na página atual, mas que não se aplicam a todas as páginas. As ações da página aparecem esmaecidas quando inativas.
Como queremos mostrar a extensão apenas em sites google.com
, usaremos a propriedade page_action
em seu lugar, que serve para:
API para colocar ícones na barra de ferramentas principal do Google Chrome, à direita da barra de endereço. As ações da página representam ações que podem ser realizadas na página atual, mas que não se aplicam a todas as páginas. As ações da página aparecem esmaecidas quando inativas.
Qual é a diferença?
Eles são basicamente os mesmos (exceto alguns pequenos recursos que apresentam apenas no browser_action
), mas o page_action
está desabilitado por padrão para todas as páginas, enquanto obrowser_action
é habilitado por padrão para todas as páginas.
Poderíamos continuar usando o browser_action
desde que o desabilitássemos para todos os sites que não correspondem a google.com
, mas a documentação deixa isso explícito:
Não use o
browser_action
para recursos que fazem sentido apenas para algumas páginas. Em vez disso,use opage_action
.
então vamos alterar o nosso manifest.json
e trocar para o page_action
Recarregue a extensão (pois alteramos o manifest.json
), e agora ela deve ser desabilitada para todos os sites. O ícone ficará esmaecido e quando você clicar nele, você tem apenas algumas opções que normalmente receberia ao clicar com o botão direito:
Agora é hora de habilitá-la para os sites que possuem google.com
no endereço.
Background Script
Para lembrá-lo, o background script é:
o manipulador de eventos da extensão; ele contém ouvintes para eventos do navegador que são importantes para a extensão.
No nosso caso, gostaríamos de ouvir o evento de navegação na web concluído, fazer a correspondência de URL e habilitar a extensão se houver uma correspondência.
- Instale as tipagens da API do Chrome:
npm i -D @types/chrome
- Adicione
chrome
aos tipos do array da propriedadetypes
que fica dentrocompilerOptions
notsconfig.app.json
- Crie um arquivo chamado
background.ts
dentro da pastasrc
com o seguinte conteúdo:
As APIs do Chrome quase sempre são assíncronas, então é o que acontece aqui:
- Nós adicionamos um ouvinte que, uma vez que a extensão for instalada irá …
- Adicionar um ouvinte que, assim que um usuário acessar qualquer URL que contenha
google.com
, irá … - … consultar as abas abertas para escolher uma ativa e assim que houver uma resposta…
- … Ativa o
page_action
na aba
Agora vamos executar o ng build
.
O nosso background script
foi compilado? Obviamente não. basicamente ele foi para limbo 😂 , pois como ele não é referenciado de nenhum lugar, não há como o Angular CLI realmente saber que esse script existe.
Precisamos adicionar o nosso background script
ao processo de build
.
Adicionando o background script ao processo de build
Uma vez que o background script
é executado em um contexto diferente do pop-up
da extensão, não podemos simplesmente adicioná-lo à seção de scripts
no angular.json
Tudo o que está na seção de scripts
é compilado pelo Angular CLI em blocos de scripts e carregado no index.html (no contexto do popup
em nosso caso).
Este também é o motivo pelo qual não podemos simplesmente importar o background script
no main.ts
ou em qualquer outro arquivo que é compilado como parte do popup
.
O que podemos fazer é criar outro entry point para o background script
no processo de build
do webpack, que é executado pelo angular CLI por debaixo dos panos, para isso, podemos usar o Custom Webpack Builder:
- Instale utilizando
npm i -D @angular-builders/custom-webpack
- Na raiz do projeto crie um arquivo chamado
custom-webpack.config.ts
com o seguinte conteúdo:
3. Atualize o angular.json
da seguintes forma:
dentro da propriedadearchitect
substitua a propriedade builder
que fica dentro da propriedade build
removendo o builder padrão do Angular pelo do custom-webpack
:
Na propriedade "options"
acrescente mais uma propriedade ao objeto onde irá informar o caminho para o nosso arquivo de configuração do custom-webpack
. conforme trecho destacado abaixo:
4. Inclua o arquivo background.ts
no array da propriedade files
do tsconfig.app.json
5. execute novamente o ng build
.
Agora você deve ser capaz vê-lo na saída do build (e também na pasta dist
):
Agora precisamos deixar o Chrome ciente desse background script
. Também precisamos conceder algumas permissões para nossa extensão. Atualize seu manifest.json
:
Se você recarregar a extensão agora e for para http://google.com, ainda verá que ela está inativa. Se você se aprofundar um pouco, verá que o background script
está sendo carregado, mas não está sendo executado. E há uma razão para isso.
Vamos dar uma olhada na versão compilada de nosso background.ts
:
Como você pode ver, nosso código está sendo inserido no array webpackJsonp
como parte de um dicionário que mapeia caminhos para o código real. Mas ninguém o invoca. Então, sim, o script está sendo carregado, mas nada acontece, porque ninguém realmente executa o código.
O Angular (Webpack) coloca o código responsável pela execução dos chunks em um chunk separado chamado runtime
. Esta é a parte que falta que precisamos adicionar ao manifest.json
:
Você pode ler mais sobre webpackJsonp
como Webpack runtime nesse artigo
Recarregue a extensão, vá para http://google.com e você estará de volta para ver esta bela imagem:
Live Reload para o Background Script
O background script
se comporta de forma muito semelhante ao manifest.json
ele é recarregado apenas quando você recarrega a extensão.
No entanto, ao contrário do manifest.json
(que raramente é atualizado após a fase inicial de desenvolvimento), ele contém uma parte significativa do nosso código e é atualizado com bastante frequência durante o desenvolvimento.
E acredite em mim, é muito chato ir à página de extensões e pressionar o botão recarregar toda vez que você altera o nome de uma variável no background script
.
Então, o que pode ser feito aqui? como já incorporamos o Custom Webpack Builder em nosso processo de build , podemos nos beneficiar um pouco mais.
Existe um plugin chamadowebpack-extension-reloader
que faz exatamente o que queremos. Ele acrescenta um código no background script
que ouve as alterações dos chunks e, quando há uma mudança no background script
ele informa ao Chrome para recarregar a extensão.
Queremos essa funcionalidade apenas durante o desenvolvimento, então vamos criar outra configuração do webpack que fará uso de nossa configuração inicial e irá adicionar o plug-in webpack-extension-reloader
apenas durante o desenvolvimento.
- Instale o reloader:
npm i -D webpack-extension-reloader
- Crie um arquivo chamado
custom-webpack-dev.config.ts
na raiz do projeto:
3. Na propriedade configurations
do angular.json
crie uma propriedade chamada dev
Vamos nos certificar de que tudo está funcionando:
- Execute o build utilizando está configuração:
ng build --watch --configuration=dev
. - Recarregue a extensão (o antigo
background script
ainda não possui o código para recargar em tempo real, então você tem que fazer isso pela última vez). - Navegue até
http://google.com
- Veja o
page action
habilitado. - Vá até o arquivo
background.ts
e altere a condição que verifica a URL parablahblah
- Volte para
ttp://google.com
- E veja o
page action
desabilitado.
Obs: poderíamos ter escrito o background script
em JavaScript em vez de TypeScript e adicioná-lo ao assets
assim como fizemos com o manifest.json
porem fazendo isso não seria possível realizar o live reload
, que seria um problema.
Obs 2: tudo o que fizemos aqui para o background script
(incluindo construí-lo com ng build
, live reload etc.) também se aplica ao content script
.
Adicionando o color picker
Isso será fácil. Usaremos o componente ngx-color-picker.
- Instale o
ngx-color-picker
:npm i ngx-color-picker
- Adicione o
ColorPickerModule
a propriedadeimports
noapp.module.ts
:
3. Adicione a propriedade color
no app.component.ts
:
4. Substitua o conteudo padrão do angular pelo color-picker
no app.component.html
:
Caso queira entender o funcionamento do color-picker
consulte aqui.
5. Se você não quiser a borda branca ao redor do color-picker
, remova as margens do body
em styles.scss:
6. Abra o popup
e veja:
Mas atualmente ele ainda não faz nada. O que nos leva ao próximo (e último) item.
Alterar o background color de Uma página
Agora que temos todas as ferramentas no lugar, queremos realmente aplicar a cor escolhida ao plano de fundo do site.
Vamos fazer isso:
- Adicione o método
colorize
aoapp.component.ts
:
Aqui estamos usando a API de tabs
de novo — selectionando uma aba ativa e injetando programaticamente o content script
na página.
2. Faça um Event binding da propriedade colorPickerSelect
ao método colorize
(que será chamado após clicar em Aplicar) no app.component.html
:
3. Abra o popup, selecione uma cor e clique em Aplicar para objeto o resultado abaixo:
Bonus: salvando a ultima cor selecionada
Para deixar as coisas mais produtivas, podemos salvar a última cor selecionada e carrega-la na próxima vez que abrirmos o pop-up.
Para isso, precisamos de algumas coisas:
- Adicionar a permissão de acesso ao
storage
nomanifest.json
e erecarregar a extensão:
2. Criar o método updateColor
no app.component.ts
e fazer um event bind dele a propriedade colorPickerChange
:
3. Definindo a cor inicial no storage
assim que o pop-up for criado:
4. Opcional: definindo uma cor padrão no background script
:
Conclusão
Embora a maioria das extensões não tenha uma interface do usuário complicada, algumas como por exemplo o Checker Plus ou 1PasswordX, têm uma interface do usuário bastante complicada.
Essas extensões seriam muito mais fáceis de construir e manter usando um dos frameworks populares da web, entre eles o Angular.
Espero que este artigo forneça todas as ferramentas e conhecimentos necessários para que você possa iniciar no desenvolvimento uma extensão do Chrome utilizando o Angular de maneira conveniente e eficiente.
O projeto utilizado está no GitHub e para acessá-lo clique aqui.
Esse artigo é uma adaptação/tradução e atualização que fiz do artigo original em Inglês do JeB Barabanov de 2019 que pode ser conferido aqui.
Espero que isso seja útil para você e até a próxima.