Extensibilidade de modelos

A extensibilidade de modelos é um recurso introduzido no PWA Kit v3. O objetivo desse recurso é permitir que você personalize modelos para criar projetos do PWA Kit com mais facilidade. Você pode personalizar modelos-base já existentes, como o Retail React App, ou criar seu próprio. Nós sugerimos aos membros da comunidade PWA Kit que compartilhem seus modelos-base com todos.

A extensibilidade de modelos ajuda a modificar um modelo escolhido sem ter que duplicar cada arquivo dentro dele.

A extensibilidade de modelos é um recurso opcional. No entanto, os novos projetos gerados após 15 de junho de 2023 são configurados automaticamente para usar a extensibilidade de modelos.

No PWA Kit v3, um projeto pode ter um modelo-base e um diretório de substituições, sendo que os dois podem ser configurados no arquivo package.json do projeto. Para usar a extensibilidade de modelos, você precisa declarar um modelo-base com ccExtensibility.extends em package.json e um diretório de substituições com ccExtensibility.overridesDir. (Os detalhes da configuração são abordados na próxima seção).

Um modelo-base é um módulo npm que contém um projeto do PWA Kit totalmente operacional que foi pré-configurado de forma que outro projeto do PWA Kit possa substituir alguns de seus arquivos. Às vezes, um modelo-base também é chamado de “app extensível” ou “app expansível”."

O diretório de substituições é um diretório em um projeto onde você pode armazenar arquivos que substituem arquivos correspondentes em um modelo-base.

Suponha que você já tenha definido um diretório de substituições e estabeleceu o Retail React App como seu modelo-base. Digamos que você queira substituir o componente da página inicial do Retail React App. No Retail React App, o código do componente da página inicial está em app/pages/home/index.jsx no pacote @salesforce/retail-react-app. Para substituir esse arquivo, você precisa recriar o mesmo arquivo no diretório declarado via ccExtensibility.overridesDir em package.json. Portanto, o caminho para o novo arquivo é <ccExtensibility.overridesDir>/app/pages/home/index.jsx.

Agora, sempre que index.jsx for importado para qualquer arquivo no pacote @salesforce/retail-react-app, o arquivo no diretório de substituições vai ser carregado em vez do arquivo no pacote. Somente a base do nome do arquivo precisa ser a mesma para que a substituição ocorra. As extensões dos arquivos podem ser diferentes.

Durante a criação do seu app, você pode adicionar gradualmente arquivos do modelo-base ao seu diretório de substituições.

Quanto mais arquivos você substituir, mais esforço é preciso para acompanhar as mudanças no modelo-base.

Para conferir a extensibilidade de modelos em ação, gere um projeto novo do PWA Kit v3 executando npx @salesforce/pwa-kit-create-app@latest --outputDir <path/to/new/local/project>.

O projeto gerado usa o modelo-base do Retail React App, o que vai produzir um app bastante parecido com o encontrado em https://pwa-kit.mobify-storefront.com/.

As instruções nesta seção pressupõem que você quer habilitar a extensibilidade de modelos para um projeto existente que não foi gerado com o recurso já habilitado.

Para definir um modelo-base, adicione @salesforce/retail-react-app (ou um modelo diferente) como uma dependência npm no arquivo package.json do seu projeto.

Depois, adicione uma chave ccExtensibility a package.json com as seguintes chaves e valores:

Se você usar um modelo-base diferente do Retail React App, lembre-se de substituir @salesforce/retail-react-app pelo identificador de pacote do outro modelo.

Você pode substituir os valores de overridesDir por um nome de diretório personalizado, mas recomendamos usar overrides para manter a consistência entre projetos (e nosso código de exemplo).

A mesma dependência não pode estar presente no projeto principal e no modelo-base. Os dois aplicativos PWA Kit precisam ser diferentes para que @salesforce/pwa-kit-dev consiga diferenciar entre o projeto principal e o modelo-base. (O projeto principal precisa definir um modelo-base e não pode herdá-lo dele mesmo).

Seu projeto não pode ter conflitos de versão com as dependências npm do modelo-base listadas no arquivo package.json. Se o modelo subjacente usa @chakra-ui, seu projeto precisa também tê-lo como uma dependência e com a mesma versão. Essa precaução evita sobrecarga acidental no seu pacote (sem falar em falhas no funcionamento) devido a versões conflitantes do mesmo pacote. Há uma exceção a essa regra: Quando você adiciona substituições para todos os arquivos no modelo-base que importam uma determinada dependência. Nesse caso, a versão da dependência do modelo-base nunca é importada para seu projeto, porque você eliminou todos os arquivos que a importariam com as substituições.

Não há problema em incluir dependências npm adicionais que não são usadas no modelo subjacente. 👌

Todo projeto do PWA Kit implantado no Managed Runtime precisa ter os seguintes arquivos:

  • <overridesDir>/app/main.jsx
  • <overridesDir>/app/ssr.js
  • <overridesDir>/app/routes.jsx
  • <overridesDir>/app/request-processor.js
  • config/default.js (exceção: se você estiver usando a chave mobify em package.json para armazenar os dados de config do seu site)

Para personalizar comportamentos no projeto, é normal substituir esses arquivos em @salesforce/retail-react-app:

  • app/pages/home/index.jsx: A remoção do conteúdo-padrão de marketing da Salesforce da página inicial é a primeira etapa em quase todos os projetos.
  • app/static/*: Esses arquivos-padrão disponibilizam os ícones da marca usados no seu site por navegadores em dispositivos móveis e desktops. Atualize-os para que correspondam à marca.
  • app/constants.js
  • app/assets/svg/brand-logo.svg: Esse arquivo é substituído no projeto gerador. Siga esse exemplo para substituir outros ícones em seu projeto.

O propósito dessa configuração “sistema de arquivos como uma API” é ajudar você a encontrar os arquivos subjacentes, copiá-los e, então, personalizar o comportamento e a lógica. A divisão do código em arquivos menores que orquestram vários subcomponentes permite que você se concentre apenas no comportamento que quer substituir.

Dito isso, às vezes você vai ter que copiar um arquivo para suas substituições a fim de mudar apenas uma parte do comportamento daquele arquivo. Sempre que possível, é melhor importar as exportações do modelo subjacente e exportá-las novamente, para que as mudanças na versão do modelo não exijam atualizações manuais de código no seu projeto expandido.

Por exemplo, no arquivo routes.jsx em um projeto gerado, observe que as rotas-padrão são importadas porimport {routes as _routes} from '@salesforce/retail-react-app/app/routes' com _routes concebidas como uma convenção para indicar que essas rotas-padrão são gerenciadas por uma dependência externa. Como o arquivo @salesforce/retail-react-app/app/routes.jsx, nossa implementação personalizada routes.jsx exporta rotas. Ela adiciona uma rota / (Início/Home). A nova rota tem prioridade sobre a rota-padrão, porque o React Router localiza o primeiro item de um determinado nome de caminho no array de rotas.

As importações relativas são a melhor abordagem para a maioria dos projetos que usam a capacidade de extensão de modelos. O comportamento de importação para os arquivos em seu modelo-base (por exemplo, @salesforce/retail-react-app) apresenta uma lógica especial que dá prioridade ao arquivo em seu diretório de substituições. Essa lógica de importação não é válida para os arquivos em seu diretório de substituições. Sendo assim, recomendamos o uso de importações relativas para a maioria das implementações.

Um problema com a extensibilidade de modelos que deve ser observado é a importação acidental de dois arquivos conflitantes diferentes. Um projeto do PWA Kit é criado por meio de @salesforce/pwa-kit-dev, que usa o webpack internamente. Há um plug-in em @salesforce/pwa-kit-dev que habilita a funcionalidade “substituir” da extensibilidade de modelos. Esse plug-in inspeciona todas as solicitações de arquivos webpack e faz duas perguntas:

  1. Essa solicitação de arquivo tem origem no modelo-base?
  2. Esse arquivo existe no diretório de substituições?

Se a resposta para ambas as perguntas for “sim”, a solicitação de arquivo é regravada no momento da criação para direcionar ao arquivo no diretório de substituições.

Essa lógica não é ativada para as solicitações de arquivo webpack que se originam no diretório de substituições. Sendo assim, é possível importar o mesmo arquivo do diretório de substituições e do modelo-base. Se essa situação ocorrer com, por exemplo, a importação de @saleforce/retail-react-app/my-file e <overrides directory>/my-file no mesmo pacote, o webpack emite um erro confuso parecido com can’t find <export name> from <filename> O webpack emite esse erro porque o pacote não sabe qual importação é o destino esperado para inclusão no pacote.

É importante evitar a importação de arquivos do modelo-base quando um arquivo equivalente existe nas substituições, com uma exceção relevante. Como explicado anteriormente no exemplo de código na seção sobre Arquivos especiais e demonstrado com constants.js, é melhor importar o modelo subjacente e exportar novamente todas as exportações dele, substituindo ou adicionando o mínimo de exportações necessárias.

Quando você precisa substituir o componente app/components/_app/index.jsx, há muitos componentes globais, como Header, Footer e DrawerMenu. Depois que a substituição de _app/index.jsx tiver sido feita, toda tentativa de importação desse componente pelo modelo-base vai redirecionar a solicitação de arquivo para <ccExtensibility.overridesDir>/app/components/_app/index.jsx. Só que nem tudo é tão simples. Como Header e Footer são importados por app/components/_app/index.jsx, a importação também precisa ser atualizada em _app/index.jsx! Caso contrário, _app/index.jsx vai importar Header e Footer do modelo-base.

As importações do ECMAScript com origem em ccExtensibility.extends são “mágicas”, pois @salesforce/pwa-kit-dev busca em vários lugares por um arquivo com aquele nome, com ccExtensibility.overridesDir tendo prioridade.

Por outro lado, as importações com origem em <ccExtensibility.overridesDir>/* não apresentam esse comportamento mágico. Sendo assim, você precisa especificar os arquivos que deseja, e <ccExtensibility.overridesDir>/app/components/header/index.jsx precisa ser importado de forma explícita para<ccExtensibility.overridesDir>/app/components/_app/index.jsx da seguinte forma:

Não importe do pacote dessa maneira, porque isso vai ignorar o cabeçalho em seu diretório de substituições:

É importante destacar que routes.jsx é um arquivo especial. Ele forma o “entryPoint”, na terminologia webpack, para o aplicativo todo. Se você tiver alguma configuração incorreta no routes.jsx, o aplicativo inteiro vai apresentar falha na fase de compilação, pois o chunking com base nas rotas em pwa-kit-dev não vai ser resolvido adequadamente. Por isso, incluímos um exemplo de como ampliar as rotas adequadamente combinando importações do @salesforce/retail-react-app/app/routes.jsx padrão e um routes.jsx local no overridesDir.

Como as importações estão na parte superior do caminho, se você substituir esses arquivos e, então, substituir footer.jsx, vai ser preciso retornar a _app/index.jsx e modificar essa importação para que ela aponte para a sua importação relativa do modelo.

Uma limitação conhecida do sistema de extensibilidade de modelos é que, devido à extração mágica (por exemplo, retail-react-app/constants), sempre que o modelo-base (@salesforce/retail-react-app) importa esse arquivo, ele espera que os mesmos valores sejam exportados.

Um arquivo nas substituições que não exporta as mesmas exportações do ECMAScript como seu equivalente no modelo-base pode causar erros inesperados, como o mostrado abaixo.

Incluir um arquivo constants.js ao seu diretório de substituições dessa maneira gera um erro:

Exemplo de mensagem de erro:

O motivo desse erro é que CAT_MENU_DEFAULT_ROOT_CATEGORY é uma exportação esperada em @salesforce/retail-react-app/components/_app/index.jsx e, durante o processo de substituição dele com a única exportação CUSTOM_MESSAGE acima, sua substituição quebra o “contrato com a API” subjacente (a capacidade de arquivos dependerem de um determinado valor, nesse caso CAT_MENU_DEFAULT_ROOT_CATEGORY) ao ser exportada por constants.js

A abordagem correta seria a mostrada a seguir. Essa abordagem evita a omissão de CAT_MENU_DEFAULT_ROOT_CATEGORY como uma exportação esperada e obrigatória. Dessa maneira, mantemos a consistência da API exportada.

Essa abordagem só funciona para alterações aditivas. No exemplo a seguir, adicionamos uma exportação CUSTOM_MESSAGE que não está nas exportações do arquivo retail-react-app/constants.js subjacente.

Essa abordagem funciona para o caso a seguir, em que DEFAULT_LOCALE é exportado por retail-react-app/constants e nós estamos alterando esse valor:

Os ganchos de modelo são uma nova maneira de interagir com o modelo do Retail React App historicamente disponível pelo comando npx pwa-kit-create-app (agora renomeado para npx @salesforce/pwa-kit-create-app).

Como parte do recurso de extensibilidade de modelos, os novos “ganchos de modelo” foram adicionados ao @salesforce/retail-react-app que permite a inclusão global de um pequeno subconjunto de componentes, enumerados na lista a seguir. Por padrão, cada um desses componentes retorna null e fica intencionalmente em branco para evitar sobrecarga desnecessária a um projeto de implementação do PWA Kit concluído. O @salesforce/retail-react-app base nunca adiciona funcionalidades a esses componentes. Os ganchos de modelo sempre retornam null para garantir que as implementações do cliente possam sempre se “enganchar” no modelo nesses locais para personalizar o projeto sem ter que substituir mais arquivos do que o necessário.

A partir de @salesforce/retail-react-app@1.0.0, os seguintes ganchos de modelo estão disponíveis:

  • app/components/_app/partials/above-header.jsx
  • app/pages/product-list/partials/above-page-header.jsx

Quando do lançamento do PWA Kit v3, só haverá um modelo-base extensível disponível publicamente: @salesforce/retail-react-app (outros vão chegar em breve).

Para ser usado como um modelo-base, um projeto do PWA Kit precisa ser publicado no npm, e todas as suas importações do ECMAScript precisam ser prefixadas com um valor que corresponda ao nome do pacote. Por exemplo, @salesforce/retail-react-app é publicado no npm, e suas importações são prefixadas com @salesforce/retail-react-app. Esse prefixo é uma referência ao diretório raiz do pacote, o que é necessário para que ele funcione corretamente como um modelo-base.

Para usar um projeto do PWA Kit como um modelo-base extensível, ele precisa usar <npm package name> em todas as importações. Por exemplo, @salesforce/retail-react-app adiciona ccExtensibility a package.json, o que assegura que todas as referências do IDE (por exemplo, @salesforce/retail-react-app/app/components/_app) resolvam adequadamente.