Ember.js: [META] Preparação para bordar

Criado em 18 ago. 2020  ·  17Comentários  ·  Fonte: emberjs/ember.js

Hoje, o Bordar em modo de compatibilidade pode ser usado em novos aplicativos e em muitos aplicativos existentes. É mais difícil usar o Bordado no modo staticComponents , que é necessário para obter os benefícios do modo splitAtRoutes .

A edição # 501 no repositório do Embroider rastreia os problemas restantes necessários para estabilizar o Embroider como parte de uma versão do Ember.js.

Este problema rastreia as etapas que precisamos seguir antes que as pessoas possam usar o Ember com Embroider como uma opção compatível com divisão de código baseada em rota ("Prontidão para bordar"). Embora existam muitas maneiras granulares de usar o Embroider (incluindo um modo de compatibilidade que oferece poucos benefícios concretos, mas é importante para migrar o Ember para o Embroider por padrão), este problema se concentra na capacidade de usar o Embroider com um aplicativo Ember normal e obter benefícios da divisão de código baseada em rota.

Requerimentos técnicos

Conforme descrito no README do splitAtRoutes ), um aplicativo deve ser capaz de habilitar estes sinalizadores:

  • [] staticAddonTestSupportTrees
  • [] staticAddonTrees
  • [] staticHelpers
  • [] staticComponents

Se um complemento ou aplicativo não funcionar na presença desses sinalizadores, eles estão usando "recursos dinâmicos clássicos".

MVP: descontinue e substitua (component dynamicString)

Para o primeiro objetivo de prontidão do Bordado ("MVP"), precisamos eliminar os obstáculos mais comuns que encontramos ao tentar habilitar os sinalizadores estáticos em aplicativos do mundo real.

Para o marco MVP, não é um objetivo que todos os complementos de ecossistema suportem esses sinalizadores.

Em vez disso, o objetivo é que os aplicativos tenham um caminho de transição razoável para splitAtRoutes e que seja possível construir aplicativos substanciais e não triviais nesse modo. Isso significa que todos os addons incluídos no blueprint padrão devem ter migrado dos recursos dinâmicos clássicos. Isso também significa que addons que são frequentemente usados ​​em aplicativos do mundo real, como a simultaneidade Ember, não devem usar recursos dinâmicos clássicos.

staticComponents

Este é o sinalizador estático mais importante e seu requisito representa o obstáculo mais substancial para o alvo MVP.

Para habilitar staticComponents , um aplicativo ( incluindo seus addons ) deve estar livre de todos os usos de (component dynamicString) .

É importante ressaltar que os aplicativos e seus complementos têm permissão para usar (component "static string") no modo staticComponents .

Na prática, isso significa que precisaremos migrar para longe de padrões como este :

{{#let (component this.componentName) as | Component |}}

Add-ons que atualmente usam strings como parte de sua API pública precisarão, em vez disso, usar componentes, o que significaria que esse addon precisaria migrar para uma abordagem que exigisse que seus usuários fornecessem o componente a ser invocado, em vez de uma string.

Esta é uma situação particularmente espinhosa, uma vez que this.component é definido como this.componentName = <code i="29">scaffolding/${dasherize(csId!)}/${dasherize(this.args.feature)}</code> . Situações como essa são exatamente a razão pela qual precisaremos pensar e implantar cuidadosamente uma estratégia de transição.

No mínimo, para permitir que os addons migrem para fora de (component dynamicString) , precisaremos criar uma nova versão da palavra-chave component que não permite a invocação de componentes dinâmicos.

Além disso, precisamos corrigir um bug que permite inadvertidamente a invocação do componente com a sintaxe de colchetes angulares para funcionar da mesma maneira que a palavra-chave dinâmica component . Isso precisa ser feito em breve, porque, uma vez que as pessoas começam a tentar migrar de (component dynamic) , há uma grande probabilidade de que as pessoas migrem acidentalmente para <dynamic> , observe que funciona e siga em frente.

Itens de ação:

  • [] Projete e lance uma nova versão de (component) que suporta apenas strings estáticas (requer RFC)
  • [] Corrigir o bug que permite a invocação de colchetes angulares para se comportar como invocação de componente dinâmico em Ember (correção de bug, talvez uma API íntima)

staticHelpers

A flag staticHelpers não reduz a expressividade dos templates Ember, mas significa que toda a lista de helpers de uma aplicação não estará disponível no conjunto de módulos do loader.

Presumimos que isso terá efeitos de interrupção nos aplicativos Ember, mas muito menos problemas do que staticComponents . Atualmente, não esperamos que staticHelpers afete o alvo do MVP, mas isso pode mudar conforme tentamos atualizar os aplicativos para splitAtRoutes . Se isso acontecer, precisaremos avaliar os casos de uso problemáticos e considerar novas APIs para facilitar a migração.

staticAddonTrees e staticAddonTestSupportTrees

A suposição atual é que esses sinalizadores são compatíveis com a maioria dos aplicativos e complementos idiomáticos do Ember e, portanto, não apresentam problemas substanciais para os requisitos do alvo MVP.

Próximos passos

Esse problema deve permanecer como um problema de rastreamento após atingirmos a meta do MVP. Após a meta do MVP, precisaremos facilitar uma migração completa do ecossistema e melhorar a ergonomia dos casos de uso dinâmicos (que retêm o suporte para divisão de código). É provável que a importação de modelos ajude a atingir esses objetivos.

Help Wanted

Comentários muito úteis

@NullVoxPopuli @jherdman Incrível!

Em geral, a maneira de pensar sobre o que as pessoas precisariam fazer em vez de (component dynamicString) é que elas precisam de import ou (component "staticString") para ir a algum lugar .

As opções incluem:

  • o chamador do componente fará (component "staticString")
  • o código com o dinamismo irá enumerar as possibilidades, colocá-las em um mapa e usar (get componentMap dynamicString) para tirá-las

Agora você deve estar pensando: como (get components dynamicString) ser melhor do que (component dynamicString) ? A resposta é que componentMap teve que ser construído em algum lugar , e as regras se aplicam recursivamente.

Então, digamos que você esteja escrevendo um componente que permite escrever <InputField @type="text" /> ou <InputField @type="checkbox" /> (como <input> em HTML).

Seu modelo para input-field seria parecido com este:

{{#let (hash text=(component "inputs/text-field") checkbox=(component "inputs/checkbox")) as |components|}}
  {{component (get components @type)}}
{{/let}}

Quando você escreve o código dessa forma, o Embroider pode ver que ele só precisa incluir dois componentes no pacote. Você escreveu desta forma:

{{component (concat "inputs/" <strong i="33">@type</strong> "-field")}}

(sim, coisas assim são surpreendentemente comuns)

então o Bordar não pode analisar facilmente o código e limitar os componentes que serão incluídos no pacote. Isso é ainda pior se você fez o trabalho em JavaScript (o que também é muito comum):

export default class extends Component {
  get innerComponent() {
    return `inputs/${this.args.type}-field`;
  }
}

com este modelo:

{{component this.innerComponent}}

É um pequeno ajuste, mas torna possível para o Embroider determinar quais componentes são realmente usados.

Todos 17 comentários

Uma maneira de diminuir o custo da transição seria permitir que os addons indiquem estaticamente qual dos seus argumentos corresponde a uma string estática passada pelo chamador do componente

Como uma cuspideira (onde better-component é um espaço reservado para o nome da palavra-chave do componente estático).

{{better-component <strong i="8">@arg</strong> staticString=true}}

Isso permitiria que addons indiquem que uma determinada "invocação dinâmica" funciona apenas se o chamador fornecer uma string estática.

Devemos fazer isso apenas se muitos casos de componentes dinâmicos forem, na prática, causados ​​por um "argumento de string passado para um addon" direto.

Não sei se esse é o tópico deste problema, mas tenho tentado periodicamente descobrir a estática total no emberclear e tenho mantido um registro em papel dos problemas e suas soluções.

https://github.com/NullVoxPopuli/emberclear/pull/784

Então, se as pessoas tiverem problemas, talvez a documentação do Abe possa ajudar? Sei lá

Além disso, estou super animado para bordar, e já tenho grandes planos quando a estática total for alcançada
https://github.com/emberjs/rfcs/issues/611

Nosso aplicativo é um sistema de entrega de conteúdo dinâmico. Os criadores de conteúdo reúnem o conteúdo a partir de "Elementos de atividade", cada um dos quais possui um componente Ember correspondente, cujo nome é definido em um modelo de dados Ember. O coração deste sistema é mais ou menos assim:

// example model definition
export default class TextElement extends Model {
  _componentName = 'text-element';
}
  {{#each (sort-by "position" @activityElements) as |activityElement|}}
    {{component (get activityElement "_componentName")}}

Minha leitura do acima sugere que este é um cenário (component dynamicString) . Isso é correto?

Minha leitura do acima sugere que este é um cenário (dynamicString de componente). Isso é correto?

O que é @activityElements e onde está definido?

Isso seria uma matriz de instâncias do Ember Data Model passadas para um componente controlador .

Também investimos um pouco pesadamente em componentes dinâmicos, FYI. Eu perguntei sobre isso aqui no ano passado: https://discuss.emberjs.com/t/the-perils-of-dynamic-component-invocation/16784.

Nós também. Literalmente, não sabemos com antecedência os componentes que serão usados ​​no aplicativo. Eles ficam em um banco de dados (o que, é claro, os torna mutáveis), são compilados no back-end e enviados sob demanda para o front-end. Não ser capaz de usar o helper component dinâmico para nós é o mesmo que não poder usar o for-each em um array. Eu realmente espero que haja algum caminho a seguir para casos de uso como este. Eu definitivamente não me importaria com divisão de código / agitação de árvore se meu aplicativo não funcionasse porque eu não tenho o dinamismo de que preciso.

Poderia ser usado um mapa de todos os componentes válidos para esse cenário?

Por exemplo:

{{#let (hash
   Foo=(import 'path/to/foo')
   Etc=...
) as |validComponents|
}}
  {{component (get validComponents @someDynamicValue)}}
{{/let}}

?

Tipo, você não pode realmente ter componentes dinâmicos completos, porque você só pode renderizar o que está em seu aplicativo - criar uma lista para escolher o que renderizar também ajudaria na depuração também. "Ah, esse valor não era um dos componentes válidos"

Poderia ser usado um mapa de todos os componentes válidos para esse cenário?

Isso definitivamente cobriria nosso caso de uso.

@NullVoxPopuli @jherdman Incrível!

Em geral, a maneira de pensar sobre o que as pessoas precisariam fazer em vez de (component dynamicString) é que elas precisam de import ou (component "staticString") para ir a algum lugar .

As opções incluem:

  • o chamador do componente fará (component "staticString")
  • o código com o dinamismo irá enumerar as possibilidades, colocá-las em um mapa e usar (get componentMap dynamicString) para tirá-las

Agora você deve estar pensando: como (get components dynamicString) ser melhor do que (component dynamicString) ? A resposta é que componentMap teve que ser construído em algum lugar , e as regras se aplicam recursivamente.

Então, digamos que você esteja escrevendo um componente que permite escrever <InputField @type="text" /> ou <InputField @type="checkbox" /> (como <input> em HTML).

Seu modelo para input-field seria parecido com este:

{{#let (hash text=(component "inputs/text-field") checkbox=(component "inputs/checkbox")) as |components|}}
  {{component (get components @type)}}
{{/let}}

Quando você escreve o código dessa forma, o Embroider pode ver que ele só precisa incluir dois componentes no pacote. Você escreveu desta forma:

{{component (concat "inputs/" <strong i="33">@type</strong> "-field")}}

(sim, coisas assim são surpreendentemente comuns)

então o Bordar não pode analisar facilmente o código e limitar os componentes que serão incluídos no pacote. Isso é ainda pior se você fez o trabalho em JavaScript (o que também é muito comum):

export default class extends Component {
  get innerComponent() {
    return `inputs/${this.args.type}-field`;
  }
}

com este modelo:

{{component this.innerComponent}}

É um pequeno ajuste, mas torna possível para o Embroider determinar quais componentes são realmente usados.

Também quero esclarecer as conexões com dois outros recursos de vôo com mais cuidado.

  1. Importações de modelos
  2. Usando classes de componentes como invokables

Se reescrevermos o exemplo anterior usando esses dois recursos, terá a seguinte aparência:

---
import TextField from "./text-field";
import Checkbox from "./checkbox";
---
{{#let (hash text=TextField checkbox=Checkbox) as |components|}}
  {{component (get components @type)}}
{{/let}}

Isso tem a boa propriedade de eliminar uma regra especial que o Embroider deveria entender e tornar o modelo do usuário para divisão de código ainda mais sobre módulos.

Usando classes de componentes como invokables

Desde a RFC # 481 , uma classe de componente tem sido uma unidade totalmente independente que possui todas as informações necessárias para que o Glimmer VM a invoque como um componente.

Nota: Isso não é verdade apenas sobre classes que herdam de @ember/component ou @glimmer/component . Desde a RFC 481, a definição de "componente" do Ember é "um objeto que está associado a um modelo e gerenciador de componentes", que inclui componentes personalizados, como componentes conectados .

Desde RFC # 432 (auxiliares contextuais e modificadores) e RFC # 496 (modo estrito de guiador), o design para o futuro da sintaxe do modelo Ember é: expressões contendo auxiliares, componentes ou modificadores podem ser invocados como auxiliares, componentes ou modificadores.

A implicação da coleção atual de RFCs aprovados é que <SomeComponentClass /> (ou <this.componentClass> onde this.componentClass resolve para uma classe de componente) "simplesmente funcionaria". É também o plano de registro para o trabalho de implementação.

Dito isso, para maior clareza, devemos criar um novo RFC que especifique explicitamente esse comportamento.

Especificar uma lista de componentes válidos no HBS em algum lugar é viável para nós, mas há algumas advertências que vale a pena mencionar:

  1. Atualmente, usamos a solução JS que @wycats apontou. Fazer o mesmo em um provedor HBS é possível, mas como todos sabemos, a lógica em HBS é um pouco mais árdua. Também é _muito_ mais difícil testar a unidade de que um componente de provedor produz o componente certo do que uma função util que retorna as strings corretas.
  2. Isso não existe no JS (com API pública de qualquer maneira) afaik, mas usar a estrutura de diretórios em nossa vantagem seria muito bom aqui. Por exemplo:

    {{#let (lookup-directory "components/inputs/") as |components|}}
    {{/let}}
    

    Esse tipo de coisa pode acabar sendo mais fácil de manter ao longo do tempo se você conhecer tipos polimórficos agrupados por estrutura de diretório.

Em qualquer caso, sinto que os componentes dinâmicos precisam de seu próprio problema para discutir, em vez de assumir este aqui 🙈 😄

Em qualquer caso, sinto que os componentes dinâmicos precisam de seu próprio problema para discutir, em vez de assumir este aqui 🙈 😄

Eu concordo e irei abrir um em breve no repositório RFC e postar um link aqui.

@mehulkar @wycats @jherdman que tal permitir que os usuários apenas importem sua lista de componentes invocáveis ​​dinamicamente? Parece muito mais fácil de seguir do que um ajudante que inclui um nível extra de indireção.

import Component1 from './dynamic/component-1';
import Component2 from './dynamic/component-2';
import Component3 from './dynamic/component-3';

export default class extends Component {
  get innerComponent() {
    switch(this.args.type) {
      case 'one':
        return Component1;
      case 'two':
        return Component2;
      case 'three':
        return Component3;
      default:
        // handle invalid type
    }
  }
}

Então, o modelo pode apenas fazer: {{#let (component this.innerComponent) as |DynamicComponent|}} ou algo semelhante.

Eu prefiro algo assim que seja mais fácil de pesquisar / ler / analisar e detectar facilmente onde esses componentes estão sendo usados. Pensamentos?

@Samsinite sim, acho que essa é a ideia. O auxiliar no HBS seria principalmente açúcar sintático e útil para componentes apenas de modelo.

@Samsinite é basicamente o que propus como uma correção de curto prazo (antes das importações de modelos).

Isso exigiria que permitíssemos que classes de componentes pudessem ser invocadas, que é o que eu estava falando neste comentário .

Vamos fazer isso!

Ah, isso faz sentido apenas para componentes de modelo, desculpe pelo mal-entendido :). Também gosto da sintaxe para componentes somente de modelo de:

---
import TextField from "./text-field";
import Checkbox from "./checkbox";
---
{{#let (hash text=TextField checkbox=Checkbox) as |components|}}
  {{component (get components @type)}}
{{/let}}

também, na verdade, provavelmente o usaria apenas para componentes de template e js, dependendo do que outros na equipe acharem mais fácil de ler.

Esta página foi útil?
0 / 5 - 0 avaliações