Feliz: guia de terceiros

Criado em 11 ago. 2019  ·  25Comentários  ·  Fonte: Zaid-Ajaj/Feliz

Isso parece fantástico IMHO.

Isso é simples de estender para coisas de terceiros, como Material-UI ?

Seria ótimo ter algum tipo de guia sobre como fazer isso. :)

Comentários muito úteis

Olá @cmeeren , acho que podemos considerar isso resolvido, certo? Vou encerrar a questão, por favor, reabra para uma discussão mais aprofundada, se necessário

Todos 25 comentários

Isso parece fantástico IMHO.

Obrigado! Que bom que gostou :smile:

Isso é simples de estender para coisas de terceiros, como Material-UI?

Claro, mas a abordagem é um pouco diferente porque não estamos usando sindicatos discriminados. Eu tento escrever algo quando o tempo permite, mas a ideia é que você tenha essencialmente duas opções:

  • 1) Estenda o tipo estático prop com mais funções
  • 2) Crie um tipo estático semelhante prop , específico para sua biblioteca (talvez Mui ) que tenha todas as propriedades que o Mui requer. Você pode até mesmo alias prop com Mui e estender Mui com propriedades e funções específicas de Mui (sobrecarregadas).

Estender é tão simples quanto:

type prop with
  static member hello (value: string) = Interop.mkAttr "hello" value 

Se esta é a "forma final" da biblioteca e a maneira de estendê-la, ainda está em consideração, pois primeiro quero experimentá-la construindo bibliotecas de terceiros e ver como funciona

Obrigado! Já estou bem adiantado testando algo para MUI, apenas para uma fração da API, para ver como funciona. Provavelmente compartilharei em algum momento durante a semana/fim, seria ótimo se você pudesse dar uma olhada só para ver se estou no caminho certo e seguindo o espírito Feliz. Parece sucesso até agora!

No entanto, não estendi o tipo prop existente. Eu defini um tipo prop separado em outro namespace ( Feliz.MaterialUI ). Funciona aparentemente muito bem; é claro que você terá acesso a todos os membros de todos os tipos correspondentes se abrir Feliz e Feliz.MaterialUI .

Eu tenho um tipo Mui que corresponde a Html e contém os componentes reais.

(Atualmente, coloquei props específicas de componentes em submódulos separados de prop , conforme mencionado em #13.)

Um possível ponto de melhoria em Feliz é ter um reactElement e createElement que aceite não string , mas ReactElementType (eu acho). para que possamos chamar createElement (importDefault "@material-ui/core/Button") . Eu mesmo criei esses dois ajudantes atualmente.

A propósito, todos os membros devem ser inline ? Quais são os prós/contras? Percebi que você não usou inline acima, mas tudo em Feliz é inline .

Obrigado! Já estou bem adiantado testando algo para MUI, apenas para uma fração da API, para ver como funciona. Provavelmente compartilharei em algum momento durante a semana/fim, seria ótimo se você pudesse dar uma olhada só para ver se estou no caminho certo e seguindo o espírito Feliz. Parece sucesso até agora!

Fantástico! Eu definitivamente ficaria feliz em dar uma olhada se você

No entanto, não estendi o tipo de prop existente. Eu defini um tipo de prop separado em outro namespace (Feliz.MaterialUI). Funciona aparentemente muito bem; é claro que você terá acesso a todos os membros de todos os tipos correspondentes se abrir Feliz e Feliz.MaterialUI.

Eu tenho um tipo Mui que corresponde a Html e contém os componentes reais.

Isso é o que eu faria por Mui

(Atualmente, coloquei props específicas de componentes em submódulos separados de prop, conforme mencionado em #13.)

Faz sentido para Mui por causa de quantas opções você tem

Um possível ponto de melhoria no Feliz é ter um reactElement e createElement que não aceite string, mas ReactElementType (eu acho). para que possamos chamar createElement (importDefault "@material-ui/core/Button"). Eu mesmo criei esses dois ajudantes atualmente.

Estou revisando os membros que estou usando no módulo Interop , acabei de expor o que estava usando na biblioteca, será reconsiderado para a versão estável

A propósito, todos os membros devem estar em linha? Quais são os prós/contras? Percebi que você não usou inline acima, mas tudo em Feliz é inline.

Eu deveria ter alinhado o membro estendido acima!

A regra geral é: se o valor da propriedade é primitivo como uma string/int/bool/enum então inline a propriedade, mas se sua propriedade calcula um valor com base na entrada, então é melhor não inline porque toda vez que o usuário chama a função embutida, todo o corpo da função é embutido nesse local de invocação, portanto, se um usuário usar a mesma função 10 vezes em todo o aplicativo, o corpo da função será compilado em linha 10 vezes em vez de ser definido uma vez e referenciado 10 vezes

A regra geral é: se o valor da propriedade é primitivo como uma string/int/bool/enum então inline a propriedade, mas se sua propriedade calcula um valor com base na entrada, então é melhor não inline porque toda vez que o usuário chama a função embutida, todo o corpo da função é embutido nesse local de invocação, portanto, se um usuário usar a mesma função 10 vezes em todo o aplicativo, o corpo da função será compilado em linha 10 vezes em vez de ser definido uma vez e referenciado 10 vezes

Bom saber! Mas (no contexto de Fable) por que inline em primeiro lugar, então? Qual é o benefício para os corpos de função/método "simples"?

  • Tamanho do pacote: se essas funções não forem usadas (e houver centenas delas), elas ainda serão compiladas de qualquer maneira, inflando o tamanho do pacote AFAIK (o impacto, é claro, ainda é relativo ao tamanho total do aplicativo)
  • Argumentos de tipo genérico: irrelevante para Feliz, mas no contexto de Fable, uma função genérica com argumentos de tipo genérico não compilará se o local de invocação não estiver embutido (todos os argumentos de tipo genérico devem ser resolvidos estaticamente em tempo de compilação), exceto em casos especiais onde você pode usar [<Inject>] ITypeResolver<'t> como um argumento opcional de uma classe estática (somente bibliotecas altamente especializadas usam esse recurso, veja Fable.SimpleJson/Thoth.Json)

Eu acho que o babel faz o tree-shake e remove as funções não utilizadas quando você faz o pacote de produção. Inlining iria derrotar isso.

@Luiz-Monad Você está dizendo que, idealmente, nada em Feliz deve ser embutido? Esse inlining por motivos de tamanho de pacote é contraproducente?

@Luiz-Monad O que você está dizendo seria incrível! Pelo menos se a compilação funcionasse dessa maneira. Aqui está um exemplo que você pode tentar com o REPL:

module App

type prop = 
  // does useless stuff
  static member f() = 
    [ 1 .. 100 ]
    |> List.map (fun x -> x * 20)
    |> List.collect (fun n -> [n; n])
    |> List.fold (+) 0

  // does useless stuff
  static member inline k() = 
    [ 1 .. 100 ]
    |> List.map (fun x -> x * 20)
    |> List.collect (fun n -> [n; n])
    |> List.fold (+) 0

  static member g() = 1

let value = prop.g()

printfn "%d" value

Onde prop contém:

  • f() contém um corpo de função -> não embutido e também não usado
  • k() contém um corpo de função -> embutido, mas não usado
  • g() contém uma função -> não inline e usada

Você pensaria que f() e g() não serão compilados, mas esse não é o caso, f() (não embutido e não usado) é compilado de qualquer maneira, mas k() (inline, não usado) não faz parte do pacote compilado

import { fold, collect, map, ofSeq, ofArray } from "fable-library/List.js";
import { type } from "fable-library/Reflection.js";
import { rangeNumber } from "fable-library/Seq.js";
import { toConsole, printf } from "fable-library/String.js";
import { declare } from "fable-library/Types.js";
export const prop = declare(function App_prop() {});
export function prop$reflection() {
  return type("App.prop");
}
export function prop$$$f() {
  return fold(function folder(x$$1, y) {
    return x$$1 + y;
  }, 0, collect(function mapping$$1(n) {
    return ofArray([n, n]);
  }, map(function mapping(x) {
    return x * 20;
  }, ofSeq(rangeNumber(1, 1, 100)))));
}
export function prop$$$g() {
  return 1;
}
export const value = prop$$$g();
toConsole(printf("%d"))(value);

Ele realmente funciona, mas você precisa configurar webpack para fazer isso, porque o que faz o tree-shake não é a fábula em si, então não funcionará no REPL.

Antes de

/// Library.fs
module Library

type prop = 
    // does useless stuff
    static member f() = 
      [ 1 .. 100 ]
      |> List.map (fun x -> x * 20)
      |> List.collect (fun n -> [n; n])
      |> List.fold (+) 0

    // does useless stuff
    static member inline k() = 
      [ 1 .. 100 ]
      |> List.map (fun x -> x * 20)
      |> List.collect (fun n -> [n; n])
      |> List.fold (+) 0


type AppMain =
    static member g() = 1

//// App.fs
module App

let value = Library.AppMain.g ()

printfn "%d" value

Depois de

  declare(function Library_prop() { 
     // see its empty, this weren't removed too because of `keep_classnames: true, keep_fnames: true ` in the terser plugin
  });
  declare(function Library_AppMain() {
  });
  !function toConsole(arg) {
    return arg.cont(function (x) {
      console.log(x)
    })
  }(printf('%d')) (1),
  __webpack_require__.d(__webpack_exports__, 'value', function () {
    return 1
  })

Além disso, há uma ressalva para o seu teste, o arquivo de entrada é meio que especial, pois as funções dele não são removidas. (Eu acho que há algo a ver com a inicialização de classes estáticas, algo tem que chamar o construtor de inicializador estático para fazer efeitos colaterais nos módulos)

Veja este repositório que fiz para este teste https://github.com/Luiz-Monad/test-tree-shaking

Muito obrigado pelo repositório de exemplo! Eu definitivamente investigarei isso mais a fundo para ver se o inlining está realmente fazendo algo útil no contexto de Feliz

Eu definitivamente investigarei isso mais a fundo para ver se o inlining está realmente fazendo algo útil no contexto de Feliz

Legal, ansioso para ouvir o que você encontra :)

Fantástico! Eu definitivamente ficaria feliz em dar uma olhada se você

Por favor, confira a filial feliz em cmeeren/fable-elmish-electron-material-ui-demo .

A maior parte do código é gerada automaticamente com base nos documentos da API (HTML). Há um projeto gerador (feio e hacky, mas faz o trabalho) e um projeto para as ligações reais. No projeto de renderização, apenas App.fs foi convertido para usar as novas ligações no estilo Feliz.

Por favor, dê uma olhada quando você quiser, e deixe-me saber o que você pensa e se você tiver alguma dúvida.

@cmeeren Parece muito bom e muito melhor do que a API IMHO atual, mas ainda é um pouco difícil de ler, mas isso é mais a natureza da própria biblioteca, tem muitas partes muito específicas com as quais você deve se familiarizar. Acho que há partes que poderiam ser melhoradas, veja este exemplo:

Mui.appBar [
  prop.className c.appBar
  prop.appBar.position.fixed'
  prop.children [
    Mui.toolbar [
      prop.children [
        Mui.typography [
          prop.typography.variant.h6
          prop.typography.color.inherit'
          prop.text (pageTitle model.Page)
        ]
      ]
    ]
  ]
]

Minha versão pessoal perfeita deste trecho seria transformá-lo no seguinte:

Mui.appBar [
  AppBar.className c.appBar
  AppBar.position.fixed'
  AppBar.children [
    Mui.toolbar [
      Toolbar.children [
        Mui.typography [
          Typography.variant.h6
          Typography.color.inherit'
          Typygraphy.text (pageTitle model.Page)
        ]
      ]
    ]
  ]
]

Dessa forma, é fácil encontrar elementos Mui porque você pode apenas "pontilhar" Mui e uma vez que você encontrou seu elemento ( appBar ), você pode "pontilhar" o nome do módulo ( AppBar ) para definir as propriedades e tal

Talvez mantenha AppBar em minúsculas também

Acho que você entendeu, mas para ser mais exato, a sintaxe geral dessa API é a seguinte, onde {Element} é um elemento react:

Mui.{element} [
  {Element}.{propName}.{propValue}
  {Element}.children [
    Mui.{otherElem} [
      {OtherElem}.{propName}.{propValue}
      // etc.
    ]
  ]
]

O que você pensa sobre isso? Esta API também inspirou a biblioteca as ideias de elementos fabulosos-simples se você quiser ver uma implementação concreta

Eu acho que é perfeito, e exatamente o tipo de coisa que eu queria saber a sua opinião. Eu inicialmente escolhi ter tudo abaixo de prop já que era assim que a biblioteca principal funcionava, mas é claro que você realmente não tem nenhum props específico de componente, enquanto o MUI tem mais mais menos apenas props específicos de componente.

Acho que ficar com nomes de módulos em letras minúsculas pode parecer melhor (e economizar um pressionamento de tecla extra), mas estou aberto a contra-argumentos.

Ainda bem que eu gerei essas coisas automaticamente, isso facilita a mudança.

Vou atualizar e te aviso.

Há uma coisa em particular que eu gostaria de obter opiniões, no entanto. É o material ClassName . O gancho makeStyles retorna um objeto com os mesmos adereços daquele que foi passado, mas onde cada elemento (JSS) foi substituído por uma string, que é o nome da classe a ser usada (e pode ser passado para, por exemplo, prop.className ).

Agora, não há como digitar isso em F#, então tenho que trabalhar com o que tenho. Normalmente todos os adereços do objeto de estilo são IStyleAttribute list . Isso significa que posso adicionar uma sobrecarga para prop.className que aceita IStyleAttribute list , o que obviamente é uma mentira, pois em tempo de execução é uma string. Se você realmente passasse IStyleAttribute list , falharia. Além de prop.className , isso também vale para todos os classes.<element>.<classesName> (usados ​​em <element>.classes [ ... ] ). Eles também aceitam um nome de classe (string).

O que eu fiz, como você pode ver, é "exigir" que todas as propriedades IStyleAttribute list do objeto de estilo sejam envolvidas em asClassName , que basicamente apenas descompacta para IClassName (um proxy para string , se preferir). E então eu adicionei uma sobrecarga a prop.className aceitando IClassName , e fiz todos os adereços classes aceitarem IClassName . Gosto que seja mais fortemente tipado, mas não gosto que exija digitação extra ( asClassName para cada regra CSS de nível superior). O compilador reclamará se você errar, mas não lhe dirá o que fazer, e ainda é um ruído extra.

Você tem alguma entrada sobre isso?

Além disso, notei isso:

f# listItem.divider ((page = Home))

Aqui, parênteses duplos são necessários, pois, caso contrário, o F# o interpreta como tentando chamar listItem.divider com o parâmetro $# page 2$#$ (inexistente) definido como Home (em vez do value parâmetro page = Home ). Você vê uma maneira de evitar isso?

Olá @cmeeren , antes de tudo, eu adoro essa sintaxe:

Mui.appBar [
  prop.className c.appBar
  appBar.position.fixed'
  appBar.children [
    Mui.toolbar [
      toolbar.children [
        Mui.typography [
          typography.variant.h6
          typography.color.inherit'
          prop.text (pageTitle model.Page)
        ]
      ]
    ]
  ]
]

Parece tão limpo e tão simples! No entanto, se eu fosse você, provavelmente teria duplicado algumas das funções genéricas prop em adereços específicos de componentes, como appBar.className em vez de (ou ao lado) prop.className para que todos eles parecem simétricos, mas o mais importante é fornecer a sobrecarga IClassName ao componente específico do Mui em vez do prop.className genérico que recebe uma string porque makeStyles também é específico do Mui construir e faz sentido que ele se aplique apenas aos componentes Mui.

Acho que você abordou o makeStyles da melhor maneira possível, pelo menos agora não consigo pensar em uma maneira melhor (embora eu não seja muito fã de asClassName , talvez Styles.createClass em vez disso? cabe a você).

Quanto a listItem.divider ((page = Home)) , é complicado, você pode adicionar uma função fictícia let when (x: bool) = x mas isso é apenas ruído. Eu acho que é melhor arquivá-lo como um bug do compilador porque não consigo pensar no motivo pelo qual o compilador F # não pode resolver a sobrecarga adequada da função, eu não tentei, mas vou investigar quando o tempo permitir

Por fim, não sou tão receptivo como de costume esta semana porque estou de férias agora, então talvez não consiga ler/verificar tudo etc.

No entanto, se eu fosse você, provavelmente teria duplicado algumas das funções genéricas prop em adereços específicos de componentes, como appBar.className em vez de (ou ao lado) prop.className para que todos eles parecem simétricos, mas o mais importante é fornecer a sobrecarga IClassName ao componente específico do Mui em vez do prop.className genérico que recebe uma string porque makeStyles também é específico do Mui construir e faz sentido que ele se aplique apenas aos componentes Mui.

Confira agora :) Fiz melhorias e expansões drásticas nos últimos dias, apenas empurrei (não concluído, estou passando por todos os componentes MUI para verificar e melhorar os adereços gerados).

Acho que você abordou o makeStyles da melhor maneira possível, pelo menos agora não consigo pensar em uma maneira melhor (embora eu não seja muito fã de asClassName , talvez Styles.createClass em vez disso? cabe a você).

Eu gostaria de mantê-lo o mais curto possível, pois será muito usado, mas estou aberto para outros nomes. Embora eu tenha metade da mente para apenas ter uma sobrecarga IStyleAttribute list . Ele removerá potencialmente um pouco de ruído e duvido que seja muito perigoso, mesmo que tecnicamente possa ser usado incorretamente.

Quanto a listItem.divider ((page = Home)) , é complicado, você pode adicionar uma função fictícia let when (x: bool) = x mas isso é apenas ruído. Eu acho que é melhor arquivá-lo como um bug do compilador porque não consigo pensar no motivo pelo qual o compilador F # não pode resolver a sobrecarga adequada da função, eu não tentei, mas vou investigar quando o tempo permitir

Obrigado, eu registrei um problema agora: https://github.com/dotnet/fsharp/issues/7423

Por fim, não sou tão receptivo como de costume esta semana porque estou de férias agora, então talvez não consiga ler/verificar tudo etc.

Entendi, sem problemas. Continuarei postando problemas se me deparar com coisas, e você apenas responde no seu próprio tempo.

Confira agora :) Fiz melhorias e expansões drásticas nos últimos dias, apenas empurrei (não concluído, estou passando por todos os componentes MUI para verificar e melhorar os adereços gerados).

Parece muito bom, com os documentos gerados também 😍 talvez seja hora de colocar seu próprio repositório?

Eu gostaria de mantê-lo o mais curto possível, pois será muito usado, mas estou aberto para outros nomes. Embora eu tenha metade da mente para ter apenas uma sobrecarga de lista IStyleAttribute. Ele removerá potencialmente um pouco de ruído e duvido que seja muito perigoso, mesmo que tecnicamente possa ser usado incorretamente.

Isso também funcionaria, as bibliotecas Fable enganam o sistema de tipos o tempo todo;)

Obrigado, registrei um problema agora: dotnet/fsharp#7423

Impressionante! muito obrigado

Parece muito bom, com os documentos gerados também 😍 talvez seja hora de colocar seu próprio repositório?

Eu tenho pensado nisso, mas ainda existem alguns bugs importantes (por exemplo, #27) eu prefiro descobrir com a conveniência de ter tudo no mesmo lugar, então acho que vou mantê-lo lá até que esteja pronto para um pré-lançamento no nuget (espero que não seja muito longo).

@Zaid-Ajaj Estou quase terminando o Feliz.MaterialUI. Publicará em um repositório separado em breve. Seria ótimo que você revisasse, em primeiro lugar para verificar algumas decisões de design e garantir consistência com Feliz, mas também para verificar algumas coisas de implementação (por exemplo, estou usando coisas que você fará internamente ou há coisas que eu não estou usando do Feliz, mas deveria estar usando).

Quando eu crio o novo repositório, tudo bem se eu criar problemas explicando o que eu gostaria que fosse revisado e marcar você?

Agora carreguei um rascunho de Feliz.MaterialUI para cmeeren/Feliz.MaterialUI . Eu criei vários problemas com coisas que eu gostaria que fossem revisadas.

Eu ficaria muito grato se você pudesse encontrar tempo para dar uma olhada neles!

Não há necessidade de gastar muito tempo em cada questão; Eu realmente só quero uma segunda opinião pelas razões mencionadas no meu comentário anterior. Fico feliz em ir para as ervas daninhas, se você quiser, mas até mesmo "isso parece bom" seria ótimo.

Não há pressa, claro. :)

Trabalho incrível @cmereren! À primeira vista, as ligações parecem muito limpas, vou dar uma olhada em cada edição nos próximos dias, prometo :smile:

Ei! Alguma chance de continuar analisando os problemas? Novamente, sem pressa, apenas um lembrete amigável, já que não tenho notícias suas há algumas semanas 😃

Eu realmente olhei para os problemas, mas como eu disse antes, se uma API é boa ou não vem de casos de uso, é por isso que eu pedi que você publicasse uma versão alfa para que eu pudesse testá-la, mas não tive o tempo para fazê-lo ainda (foi na minha mente esta semana :smile:)

Olá @cmeeren , acho que podemos considerar isso resolvido, certo? Vou encerrar a questão, por favor, reabra para uma discussão mais aprofundada, se necessário

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

Questões relacionadas

Dzoukr picture Dzoukr  ·  9Comentários

l3m picture l3m  ·  7Comentários

Dzoukr picture Dzoukr  ·  6Comentários

alfonsogarciacaro picture alfonsogarciacaro  ·  6Comentários

mastoj picture mastoj  ·  3Comentários