Ember.js: [META] Bordar preparación

Creado en 18 ago. 2020  ·  17Comentarios  ·  Fuente: emberjs/ember.js

Hoy en día, Embroider en modo de compatibilidad se puede utilizar en nuevas aplicaciones y en muchas aplicaciones existentes. Es más difícil usar Bordado en el modo staticComponents , que es necesario para obtener el beneficio del modo splitAtRoutes .

El número 501 en el repositorio de Embroider rastrea los problemas restantes necesarios para estabilizar Embroider como parte de una versión de Ember.js.

Este problema hace un seguimiento de los pasos que debemos seguir antes de que la gente pueda utilizar prácticamente Ember with Embroider como una opción compatible con la división de código basada en rutas ("Preparación para bordar"). Si bien hay muchas formas detalladas de usar Embroider (incluido un modo de compatibilidad que ofrece pocos beneficios concretos, pero es importante para migrar Ember a Embroider de forma predeterminada), este problema se centra en la capacidad de usar Embroider con una aplicación Ember normal y obtener beneficios de la división de código basada en rutas.

Requerimientos técnicos

Como se describe en Embroider README , para habilitar la división de código basada en rutas ( splitAtRoutes ), una aplicación debe poder habilitar estos indicadores:

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

Si un complemento o aplicación no puede funcionar en presencia de estos indicadores, están usando "características dinámicas clásicas".

MVP: desaprobar y reemplazar (component dynamicString)

Para el primer objetivo de preparación de Bordado ("MVP"), necesitamos eliminar los obstáculos más comunes que hemos encontrado al intentar habilitar las banderas estáticas en aplicaciones del mundo real.

Para el hito de MVP, no es un objetivo para todos los complementos del ecosistema que admiten estas banderas.

En cambio, el objetivo es que las aplicaciones tengan una ruta de transición razonable a splitAtRoutes , y que sea posible crear aplicaciones sustanciales y no triviales en ese modo. Eso significa que todos los complementos incluidos en el plan predeterminado deben haberse migrado de las características dinámicas clásicas. También significa que los complementos que se usan con frecuencia en aplicaciones del mundo real, como la concurrencia de Ember, no deben usar características dinámicas clásicas.

staticComponents

Esta es la bandera estática más importante y su requisito representa el obstáculo más importante para el objetivo de MVP.

Para habilitar staticComponents , una aplicación ( incluidos sus complementos ) debe estar libre de todos los usos de (component dynamicString) .

Es importante destacar que las aplicaciones y sus complementos pueden usar (component "static string") en el modo staticComponents .

En la práctica, esto significa que necesitaremos migrar lejos de patrones como este :

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

Los complementos que actualmente toman cadenas como parte de su API pública necesitarán, en su lugar, tomar componentes, lo que significaría que este complemento tendría que migrar a un enfoque que requiriera que sus usuarios suministraran el componente a invocar, en lugar de una cadena.

Esta es una situación particularmente espinosa, ya que this.component se define como this.componentName = <code i="29">scaffolding/${dasherize(csId!)}/${dasherize(this.args.feature)}</code> . Situaciones como esta son precisamente la razón por la que tendremos que pensar e implementar cuidadosamente una estrategia de transición.

Como mínimo, para permitir que los complementos migren de (component dynamicString) , necesitaremos crear una nueva versión de la palabra clave component que no permita la invocación de componentes dinámicos.

Además, necesitamos corregir un error que inadvertidamente permite que la invocación de componentes con sintaxis de paréntesis angulares funcione de la misma manera que la palabra clave dinámica component . Esto debe hacerse pronto, porque una vez que las personas comienzan a intentar migrar desde (component dynamic) , existe una alta probabilidad de que las personas migren accidentalmente a <dynamic> , observen que funciona y continúen.

Elementos de acción:

  • [] Diseña y lanza una nueva versión de (component) que solo admita cadenas estáticas (requiere RFC)
  • [] Se corrigió el error que permite que la invocación de corchetes angulares se comporte como la invocación de componentes dinámicos en Ember (corrección de errores, quizás una API íntima)

staticHelpers

La bandera staticHelpers no reduce la expresividad de las plantillas de Ember, pero significa que la lista completa de ayudantes de una aplicación no estará disponible en el conjunto de módulos en el cargador.

Suponemos que esto tendrá efectos de ruptura en las aplicaciones de Ember, pero muchos menos problemas que staticComponents . Actualmente no esperamos que staticHelpers afecte al objetivo de MVP, pero eso puede cambiar a medida que intentemos actualizar las aplicaciones a splitAtRoutes . Si eso sucede, tendremos que evaluar los casos de uso problemáticos y considerar nuevas API para facilitar la migración.

staticAddonTrees y staticAddonTestSupportTrees

La suposición actual es que estos indicadores son compatibles con la mayoría de las aplicaciones y complementos idiomáticos de Ember y, por lo tanto, no plantean ningún problema sustancial para los requisitos del objetivo MVP.

Próximos pasos

Se pretende que este problema siga siendo un problema de seguimiento después de que logremos el objetivo de MVP. Después del objetivo de MVP, necesitaremos facilitar una migración completa del ecosistema y mejorar la ergonomía de los casos de uso dinámicos (que conservan el soporte para la división de código). Es probable que las importaciones de plantillas ayuden a lograr estos objetivos.

Help Wanted

Comentario más útil

@NullVoxPopuli @jherdman ¡Impresionante!

En general, la forma de pensar sobre lo que la gente debería hacer en lugar de (component dynamicString) es que necesitan import o (component "staticString") para ir a algún lado .

Las opciones incluyen:

  • el llamador del componente hará (component "staticString")
  • el código con el dinamismo enumerará las posibilidades, las pondrá en un mapa y usará (get componentMap dynamicString) para sacarlas

Ahora podría estar pensando: ¿cómo podría (get components dynamicString) ser mejor que (component dynamicString) ? La respuesta es que componentMap tuvo que construirse en algún lugar , y las reglas se aplican de forma recursiva.

Entonces, digamos que está escribiendo un componente que le permite escribir <InputField @type="text" /> o <InputField @type="checkbox" /> (como <input> en HTML).

Su plantilla para input-field se vería así:

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

Cuando escribe el código de esta manera, Embroider puede ver que solo necesita incluir dos componentes en el paquete. Lo habías escrito de esta manera:

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

(sí, cosas así son sorprendentemente comunes)

entonces Embroider no puede analizar fácilmente el código y limitar los componentes que se incluirán en el paquete. Esto es aún peor si hizo el trabajo en JavaScript (que también es muy común):

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

con esta plantilla:

{{component this.innerComponent}}

Es un pequeño ajuste, pero hace posible que Embroider determine qué componentes se utilizan realmente.

Todos 17 comentarios

Una forma de aliviar el costo de la transición sería permitir que los complementos indiquen estáticamente cuál de sus argumentos corresponde a una cadena estática pasada por el llamador del componente.

Como spitball (donde better-component es un marcador de posición para el nombre de la palabra clave del componente estático).

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

Esto permitiría que los complementos indiquen que una "invocación dinámica" en particular solo funciona si la persona que

Solo deberíamos hacer esto si muchos casos de componentes dinámicos son, en la práctica, causados ​​por un simple "argumento de cadena pasado a un complemento".

No sé si esto está en el tema de este problema, pero periódicamente he estado tratando de averiguar la estática total en emberclear y he estado manteniendo un registro en papel de los problemas y sus soluciones.

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

Entonces, si la gente tiene problemas, ¿tal vez la documentación de Abe pueda ayudar? No sé

Además, estoy muy emocionado por bordar y ya tengo grandes planes una vez que se logre la estática total.
https://github.com/emberjs/rfcs/issues/611

Nuestra aplicación es un sistema de entrega de contenido dinámico. Los creadores de contenido ensamblan contenido a partir de "Elementos de actividad", cada uno de los cuales tiene un Componente Ember correspondiente, cuyo nombre se define en un modelo de Datos Ember. El corazón de este sistema se ve más o menos así:

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

Mi lectura de lo anterior sugiere que este es un escenario de (component dynamicString) . ¿Es eso exacto?

Mi lectura de lo anterior sugiere que este es un escenario (componente dynamicString). ¿Es eso exacto?

¿Qué es @activityElements y dónde se define?

Esta sería una matriz de instancias del modelo de datos Ember que se pasa a un componente del controlador .

También invertimos un poco en componentes dinámicos, FYI. Pregunté al respecto aquí el año pasado: https://discuss.emberjs.com/t/the-perils-of-dynamic-component-invocation/16784.

Nosotros también. Literalmente, no conocemos los componentes que se utilizarán en la aplicación de antemano. Se encuentran en una base de datos (lo que, por supuesto, los hace modificables), se compilan en el backend y se envían a pedido al frontend. No poder usar el ayudante dinámico component para nosotros es lo mismo que no poder usar para cada uno en una matriz. Realmente espero que haya alguna forma de avanzar para casos de uso como este. Definitivamente no me importaría dividir el código / sacudir los árboles si mi aplicación no funciona porque no tengo el dinamismo que necesito.

¿Podría usarse un mapa de todos los componentes válidos para ese escenario?

Por ejemplo:

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

?

Por ejemplo, realmente no puede tener componentes dinámicos completos, porque solo puede renderizar lo que hay en su aplicación; crear una lista para elegir qué renderizar también ayudaría en la depuración. "Oh, este valor no era uno de los componentes válidos"

¿Podría usarse un mapa de todos los componentes válidos para ese escenario?

Esto definitivamente cubriría nuestro caso de uso.

@NullVoxPopuli @jherdman ¡Impresionante!

En general, la forma de pensar sobre lo que la gente debería hacer en lugar de (component dynamicString) es que necesitan import o (component "staticString") para ir a algún lado .

Las opciones incluyen:

  • el llamador del componente hará (component "staticString")
  • el código con el dinamismo enumerará las posibilidades, las pondrá en un mapa y usará (get componentMap dynamicString) para sacarlas

Ahora podría estar pensando: ¿cómo podría (get components dynamicString) ser mejor que (component dynamicString) ? La respuesta es que componentMap tuvo que construirse en algún lugar , y las reglas se aplican de forma recursiva.

Entonces, digamos que está escribiendo un componente que le permite escribir <InputField @type="text" /> o <InputField @type="checkbox" /> (como <input> en HTML).

Su plantilla para input-field se vería así:

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

Cuando escribe el código de esta manera, Embroider puede ver que solo necesita incluir dos componentes en el paquete. Lo habías escrito de esta manera:

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

(sí, cosas así son sorprendentemente comunes)

entonces Embroider no puede analizar fácilmente el código y limitar los componentes que se incluirán en el paquete. Esto es aún peor si hizo el trabajo en JavaScript (que también es muy común):

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

con esta plantilla:

{{component this.innerComponent}}

Es un pequeño ajuste, pero hace posible que Embroider determine qué componentes se utilizan realmente.

También quiero explicar con más detalle las conexiones con otras dos funciones en vuelo.

  1. Importaciones de plantillas
  2. Usar clases de componentes como invokables

Si reescribimos el ejemplo anterior usando esas dos características, se verá así:

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

Esto tiene la buena propiedad de eliminar una regla especial que Embroider tendría que entender y hacer que el modelo de usuario para dividir el código sea aún más sobre los módulos.

Usar clases de componentes como invokables

Desde RFC # 481 , una clase de componente ha sido una unidad totalmente autónoma que tiene toda la información que se necesita para que la VM Glimmer la invoque como un componente.

Nota: Esto no es solo cierto para las clases que heredan de @ember/component o @glimmer/component . Desde RFC 481, la definición de "componente" de Ember es "un objeto que está asociado con una plantilla y un administrador de componentes", que incluye componentes personalizados como componentes enganchados .

Desde RFC # 432 (modificadores y ayudantes contextuales) y RFC # 496 (modo estricto del manillar), el diseño para el futuro de la sintaxis de la plantilla de Ember es: las expresiones que contienen ayudantes, componentes o modificadores pueden invocarse como ayudantes, componentes o modificadores.

La implicación de la colección actual de RFC aprobados es que <SomeComponentClass /> (o <this.componentClass> donde this.componentClass resuelve en una clase de componente) "simplemente funcionaría". También es el plan de registro para el trabajo de implementación.

Dicho esto, para mayor claridad, deberíamos crear un nuevo RFC que especifique explícitamente este comportamiento.

Especificar una lista de componentes válidos en HBS en algún lugar es factible para nosotros, pero hay un par de advertencias que vale la pena mencionar:

  1. Actualmente, usamos la solución JS que @wycats señaló. Hacer lo mismo en un proveedor de HBS es factible, pero como todos sabemos, la lógica en HBS es un poco más ardua. También es _mucho_ más difícil probar unitariamente que un componente de proveedor produce el componente correcto que una función útil que devuelve las cadenas correctas.
  2. Esto no existe en JS (con API pública de todos modos) afaik, pero usar la estructura de directorios para nuestra ventaja sería muy bueno aquí. P.ej:

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

    Este tipo de cosas puede resultar más fácil de mantener a lo largo del tiempo si tiene tipos polimórficos conocidos agrupados por estructura de directorios.

En cualquier caso, siento que los componentes dinámicos necesitan su propio tema para discutir en lugar de hacerse cargo de este 🙈 😄

En cualquier caso, siento que los componentes dinámicos necesitan su propio tema para discutir en lugar de hacerse cargo de este 🙈 😄

Estoy de acuerdo, abriré uno en breve en el repositorio de RFC y publicaré un enlace aquí.

@mehulkar @wycats @jherdman ¿qué hay de permitir a los usuarios simplemente importar su lista de componentes invocables dinámicamente? Parece mucho más fácil de seguir que un ayudante que incluye un nivel adicional de indirección.

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
    }
  }
}

Entonces la plantilla puede hacer: {{#let (component this.innerComponent) as |DynamicComponent|}} o algo similar.

Preferiría algo como esto que sea más fácil de buscar / leer / analizar y detectar fácilmente dónde se están utilizando estos componentes. Pensamientos

@Samsinite, sí, creo que esa es la idea. El ayudante en HBS sería principalmente azúcar sintáctico y útil para componentes solo de plantilla.

@Samsinite eso es básicamente lo que propuse como una solución a corto plazo (antes de las importaciones de plantillas).

Requeriría que permitiéramos que las clases de componentes fueran invocables, que es de lo que estaba hablando en este comentario .

¡Vamos a hacerlo!

Ah, eso tiene sentido para los componentes solo de plantilla, perdón por malentendidos :). También me gusta la sintaxis de los componentes de plantilla de:

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

Además, de hecho, probablemente lo usaría solo para plantillas y componentes respaldados por js, dependiendo de lo que otros miembros del equipo encuentren más fácil de leer.

¿Fue útil esta página
0 / 5 - 0 calificaciones