Análise de Componentes Lightning Web (LWC) do Salesforce
Construa uma configuração de componente passo a passo, usando Salesforce Lightning Web Components como exemplo prático
Os modelos de Componentes Lightning Web (LWC) do Salesforce são arquivos HTML construídos a partir de elementos personalizados: componentes base do Salesforce, como <lightning-button> e <lightning-icon>, e os componentes que você mesmo cria no namespace c-, como <c-my-image>. O Axe DevTools Linter verifica os elementos personalizados assim que você informa o que esses elementos renderizam, usando a opção de configuração global-components.
Este artigo constrói uma configuração LWC do zero. O LWC é um bom exemplo prático porque explora todas as partes da sintaxe de mapeamento, incluindo componentes que não rendem nenhum elemento próprio. O método se aplica a qualquer biblioteca de componentes: descreva o que cada componente renderiza, verifique essa descrição com um arquivo de teste e corrija o mapeamento sempre que houver divergências.
Para uma introdução ao mapeamento de componentes personalizados, veja Analisando Componentes Personalizados. Para a referência completa da sintaxe, veja Configurando o Axe DevTools Linter.
Antes de Começar
Adicione um arquivo axe-linter.yml na raiz do seu projeto. Todos os exemplos abaixo vão nesse arquivo. Se você usar o endpoint REST em vez de uma extensão de editor, a mesma configuração vai no objeto config da requisição, como mostrado em Analisando Componentes Personalizados com o Endpoint REST do Axe DevTools Linter.
Um Mapeamento Descreve o Que o Navegador Recebe
Esta é a ideia que torna todas as outras decisões diretas: um mapeamento descreve o elemento que seu componente renderiza, não a tag que você escreve no modelo.
<lightning-button label="Save"> renderiza um button cujo conteúdo de texto é "Salvar". Escrito como um mapeamento, isso é:
global-components:
lightning-button:
element: button
attributes:
- label: <text>O valor especial <text> indica que o atributo label se torna o conteúdo de texto do elemento, o que dá a um botão seu nome acessível. Com esse mapeamento no lugar, o Axe DevTools Linter relata uma violação button-name para <lightning-button> usado sem label, exatamente como faria para um <button> vazio.
Etapa 1: Mapear os Componentes que Rendam um Elemento
Comece com componentes que mapeiem claramente para um único elemento nativo, e mapeie apenas os atributos que contenham informações de acessibilidade.
global-components:
# Salesforce base components
lightning-button:
element: button
attributes:
- label: <text>
lightning-icon:
element: img
attributes:
- alternative-text: alt
# Components you author yourself
c-my-image:
element: img
attributes:
- alternative-text: altOs nomes dos componentes são sensíveis a maiúsculas e minúsculas e correspondem à tag como você a escreve no modelo, então os nomes em kebab-case do LWC são usados tal como estão.
Para os outros valores especiais (aria-* para passar atributos ARIA, <element> para permitir que um atributo escolha o elemento emitido, e default para declarar um valor que o componente sempre renderiza) veja o passo a passo em Analisando Componentes Personalizados com a Extensão Axe Accessibility Linter para VS Code ou o Plugin JetBrains.
Etapa 2: Declarar os Envolventes que Não Rendam Nada
O LWC usa <template> para um elemento raiz de componente e novamente para iterações e condicionais:
<template>
<ul>
<template for:each={items} for:item="item">
<li key={item.id}>{item.name}</li>
</template>
</ul>
</template>Em HTML, o conteúdo de um elemento <template> é inerte: o navegador não o rende, e a tecnologia assistiva não pode alcançá-lo. O Axe DevTools Linter avalia a marcação que um usuário realmente recebe, então não relata sobre o conteúdo dentro de um <template>. O LWC usa o mesmo nome de tag para uma instrução de tempo de compilação que não produz nenhum elemento, então o linter precisa ser informado sobre no que esses envolventes se transformam. A marcação acima chega ao navegador assim:
<ul>
<li>…</li>
<li>…</li>
</ul>Declarar template na sua configuração é o que permite ao linter ver através desses envolventes até a marcação interna. Como um mapeamento precisa nomear algum elemento, e um mapeamento se aplica em todo lugar que a tag aparece, o elemento que você escolhe importa. Trabalhar com duas escolhas com a lista acima mostra por que.
Tentando um Contêiner Genérico
Um div é a primeira suposição natural, já que um envolvente soa como um contêiner genérico:
global-components:
template: divO Axe DevTools Linter agora avalia a lista como se você tivesse escrito o <template> como um div:
<ul>
<div>
<li>…</li>
</div>
</ul>E relata uma violação na linha <ul>:
list: <ul> and <ol> must only directly contain <li>, <script> or <template> elementsO relatório está correto sobre a marcação que foi dada, mas essa marcação não é o que o navegador recebe. O LWC remove o envolvente, então a lista renderizada contém itens de lista diretamente e é perfeitamente válida. A violação vem do elemento substituto, não do seu modelo.
Correspondendo à Relação Renderizada
Agora mapeie template para o elemento que o navegador realmente encontra dentro da lista:
global-components:
template: liO linter avalia a mesma lista assim:
<ul>
<li>
<li>…</li>
</li>
</ul>Nenhuma violação list é relatada, porque o <ul> contém diretamente um li, que é um dos elementos que a mensagem de regra acima permite, e é também a relação que o navegador obtém. O elemento li duplicado existe apenas na visão que o linter tem do seu arquivo: ele representa um envolvente que não rende nada, e você nunca o escreve em um modelo.
Deixar template fora da sua configuração também evita a violação, mas assim nada dentro dos seus modelos é avaliado, o que é o problema que a Etapa 2 pretendia resolver.
Ambas as Escolhas Verificam o Conteúdo Interno
O elemento substituto afeta apenas como o envolvente em si aparece para o linter. A marcação interna é avaliada de qualquer maneira. Adicione uma imagem sem texto alternativo à mesma lista:
<template>
<ul>
<template for:each={items} for:item="item">
<li key={item.id}><img src={item.url}></li>
</template>
</ul>
</template>Ambos template: div e template: li relatam a falta de texto alternativo na imagem:
image-alt: Images must have alternative textCom template: div você obtém esse resultado mais a violação espúria list, que é a diferença prática entre os dois. Como iterar itens de lista é o uso mais comum de <template>, mapeá-lo para li corresponde à saída renderizada mais frequentemente, e é o ponto de partida recomendado.
Três regras verificam se um contêiner contém o tipo certo de filho direto: list para <ul> e <ol>, definition-list para <dl>, e summary-name para <details>. Onde um <template> aparece diretamente dentro de um desses quatro contêineres, seu elemento substituto toma seu lugar, então essas três regras podem não relatar o que fariam para o HTML simples equivalente. Nenhuma outra regra é afetada, e o conteúdo dentro do modelo ainda é verificado por completo.
Etapa 3: Verifique Sua Configuração
Não presuma que um mapeamento se comporta da maneira que você pretendia. A verificação mais confiável é escrever a mesma marcação duas vezes, uma com seus componentes e outra com o HTML simples que você espera que eles renderizem, e confirmar que ambos produzem os mesmos resultados.
Crie um pequeno arquivo de teste, scratch.html, em qualquer lugar do projeto que o Axe DevTools Linter verifica:
<template>
<c-my-image src="cat.jpg"></c-my-image>
<lightning-button></lightning-button>
</template>Depois crie seu gêmeo em HTML simples, scratch-expected.html, contendo a marcação que você espera que esses componentes renderizem:
<img src="cat.jpg">
<button></button>Com os mapeamentos das Etapas 1 e 2 em vigor, ambos os arquivos relatam as mesmas duas violações: image-alt para a imagem sem texto alternativo, e button-name para o botão sem nome acessível. Resultados correspondentes significam que os mapeamentos descrevem seus componentes corretamente. Uma violação em um arquivo mas não no outro aponta para a configuração em vez de para sua marcação.
Este par também mostra por que a verificação vale a pena. Sem o mapeamento template da Etapa 2, scratch.html não relata nada enquanto scratch-expected.html relata ambas as violações, e esse descompasso é o sinal de que algo na configuração está faltando.
Existem três maneiras convenientes de ver os resultados:
- No VS Code ou em um IDE JetBrains, abra o arquivo e leia os erros destacados, como descrito em Analisando Componentes Personalizados com a Extensão Axe Accessibility Linter para VS Code ou o Plugin JetBrains.
- Com o Axe DevTools Linter Connector, execute-o contra ambos os arquivos em um único comando.
- Com o endpoint REST, publique o conteúdo de cada arquivo com sua configuração no objeto
config. Veja Analisando Componentes Personalizados com o Endpoint REST do Axe DevTools Linter.
Adicionar o nome do componente a cada resultado torna configurações maiores muito mais fáceis de depurar, porque mostra qual mapeamento produziu uma violação. Veja Analisando Violações de Componentes Personalizados.
Decidindo o Que Um Mapeamento Deve Requerer
Alguns componentes renderizam diferentes marcações dependendo de como são usados, e mapeá-los é uma decisão de política em vez de uma tradução simples. <lightning-icon> é um bom exemplo: um ícone sem alternative-text é decorativo, enquanto um ícone com alternative-text transmite significado.
Mapear <lightning-icon> para img significa que o linter solicita que cada ícone declare seu texto alternativo. Um valor explicitamente vazio satisfaz essa solicitação:
<!-- Reported: an image with no alternative text -->
<lightning-icon icon-name="utility:check"></lightning-icon>
<!-- Not reported: explicitly decorative -->
<lightning-icon icon-name="utility:check" alternative-text=""></lightning-icon>Essa é uma convenção útil, porque torna visível a intenção de cada ícone no template em vez de ser implícita por omissão. Se preferir não adotá-la, deixe <lightning-icon> fora da sua configuração e o linter não o avaliará.
Quando um componente sempre renderiza o mesmo valor para um atributo, como um role fixo, use default para registrar esse valor. Um default entra em vigor somente quando o valor não está vazio, e um default pode definir um atributo, mas não pode fornecer conteúdo <text>. Veja Atributos Padrão.
Mapeie apenas os atributos que transportam informações de acessibilidade e confirme o que cada componente renderiza antes de mapeá-lo. Os exemplos neste artigo seguem a marcação que os componentes base do Salesforce produzem, mas seus próprios componentes, e qualquer componente que você envolver, precisam da mesma verificação.
Aplicando Isso aos Seus Próprios Componentes
As etapas acima se generalizam para qualquer biblioteca de componentes:
- Liste os componentes que renderizam um elemento interativo ou significativo, como botões, links, imagens, controles de formulário e cabeçalhos. Eles lhe dão o máximo de valor com o mínimo de configuração.
- Para cada um, observe o que ele renderiza e quais atributos carregam informações de acessibilidade, depois mapeie apenas esses atributos.
- Declare wrappers que não renderizam nada por conta própria, escolhendo um elemento substituto que preserve o relacionamento que o navegador vê.
- Verifique cada mapeamento em relação ao seu equivalente em HTML puro antes de depender dele.
- Revise a configuração quando seus componentes mudarem. Um mapeamento reflete apenas como um componente se comportou no dia em que o mapeamento foi escrito.
Se sua biblioteca for uma das bibliotecas que o Axe DevTools Linter já conhece, você pode pular a maior parte deste trabalho. Veja Bibliotecas de Componentes Pré-configuradas.
Veja Também
- Linting de Componentes Personalizados
- Linting de Componentes Personalizados com a Extensão Axe Accessibility Linter para VS Code ou o Plugin JetBrains
- Linting de Componentes Personalizados com o Endpoint REST do Axe DevTools Linter
- Configurando o Axe DevTools Linter
- Bibliotecas de Componentes Pré-configuradas
