Ember.js: [META] Préparation de la broderie

Créé le 18 août 2020  ·  17Commentaires  ·  Source: emberjs/ember.js

Aujourd'hui, Embroider en mode compatibilité peut être utilisé dans de nouvelles applications et dans de nombreuses applications existantes. Il est plus difficile d'utiliser Embroider en mode staticComponents , ce qui est nécessaire pour profiter du mode splitAtRoutes .

Le problème n ° 501 sur le référentiel Embroider suit les problèmes restants nécessaires pour stabiliser Embroider dans le cadre d'une version d'Embroider.js.

Ce problème suit les étapes à suivre avant que les utilisateurs puissent pratiquement utiliser Ember avec Embroider en tant qu'option prise en charge avec le fractionnement de code basé sur l'itinéraire («Préparation de broderie»). Bien qu'il existe de nombreuses façons granulaires d'utiliser Embroider (y compris un mode de compatibilité qui offre peu d'avantages concrets mais qui est important pour la migration d'Ember lui-même vers Embroider par défaut), ce problème est axé sur la possibilité d'utiliser Embroider avec une application Ember normale et d'obtenir des avantages à partir du fractionnement de code basé sur l'itinéraire.

Les pré-requis techniques

Comme décrit dans le fichier README de Embroider , pour activer le fractionnement de code basé sur l'itinéraire ( splitAtRoutes ), une application doit pouvoir activer ces indicateurs:

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

Si un addon ou une application ne peut pas fonctionner en présence de ces indicateurs, ils utilisent des "fonctionnalités dynamiques classiques".

MVP: observez et remplacez (component dynamicString)

Pour la première cible de préparation à Embroider ("MVP"), nous devons éliminer les obstacles les plus courants que nous avons trouvés en essayant d'activer les indicateurs statiques dans des applications du monde réel.

Pour le jalon MVP, ce n'est pas un objectif car tous les addons de l'écosystème prennent en charge ces indicateurs.

Au lieu de cela, l'objectif est que les applications aient un chemin de transition raisonnable vers splitAtRoutes , et qu'il soit possible de créer des applications substantielles et non triviales dans ce mode. Cela signifie que tous les modules complémentaires inclus dans le plan par défaut doivent avoir migré hors des fonctionnalités dynamiques classiques. Cela signifie également que les modules complémentaires qui sont fréquemment utilisés dans les applications du monde réel, telles que la concurrence Ember, ne doivent pas utiliser les fonctionnalités dynamiques classiques.

staticComponents

Il s'agit de l'indicateur statique le plus important , et son exigence pose l'obstacle le plus important à la cible MVP.

Pour activer staticComponents , une application ( y compris ses addons ) doit être libre de toute utilisation de (component dynamicString) .

Surtout, les applications et leurs addons sont autorisés à utiliser (component "static string") en mode staticComponents .

En pratique, cela signifie que nous devrons nous éloigner des modèles comme celui-ci :

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

Les addons qui prennent actuellement des chaînes dans le cadre de leur API publique devront à la place prendre des composants, ce qui signifierait que cet addon aurait besoin de migrer vers une approche qui obligeait leurs utilisateurs à fournir le composant à invoquer, plutôt qu'une chaîne.

C'est une situation particulièrement épineuse, puisque this.component est défini comme this.componentName = <code i="29">scaffolding/${dasherize(csId!)}/${dasherize(this.args.feature)}</code> . Des situations comme celle-ci sont précisément la raison pour laquelle nous devrons réfléchir et déployer soigneusement une stratégie de transition.

Au minimum, pour permettre aux addons de migrer de (component dynamicString) , nous devrons créer une nouvelle version du mot clé component qui ne permet pas l'invocation de composants dynamiques.

De plus, nous devons corriger un bogue qui autorise par inadvertance l'invocation de composant avec la syntaxe entre crochets angulaires à fonctionner de la même manière que le mot clé dynamique component . Cela doit être fait rapidement, car une fois que les gens commencent à essayer de migrer de (component dynamic) , il y a de fortes chances que les gens migrent accidentellement vers <dynamic> , constatent que cela fonctionne et passent à autre chose.

Éléments d'action:

  • [] Concevoir et publier une nouvelle version de (component) qui ne prend en charge que les chaînes statiques (nécessite la RFC)
  • [] Correction du bogue qui permet à l'invocation des crochets angulaires de se comporter comme l'invocation de composants dynamiques dans Ember (correction de bogues, peut-être une API intime)

staticHelpers

L'indicateur staticHelpers ne réduit pas l'expressivité des modèles Ember, mais cela signifie que la liste complète des helpers d'une application ne sera pas disponible dans l'ensemble des modules du chargeur.

Nous supposons que cela aura des effets de rupture sur les applications Ember, mais beaucoup moins de problèmes que staticComponents . Nous ne prévoyons pas actuellement que staticHelpers affectera la cible MVP, mais cela peut changer lorsque nous tentons de mettre à niveau les applications vers splitAtRoutes . Si cela se produit, nous devrons évaluer les cas d'utilisation problématiques et envisager de nouvelles API pour faciliter la migration.

staticAddonTrees et staticAddonTestSupportTrees

L'hypothèse actuelle est que ces indicateurs sont compatibles avec la plupart des applications et addons Ember idiomatiques et ne posent donc aucun problème substantiel pour les exigences de la cible MVP.

Prochaines étapes

Ce problème est destiné à rester un problème de suivi une fois que nous avons atteint l'objectif MVP. Après la cible MVP, nous devrons faciliter une migration complète de l'écosystème et améliorer l'ergonomie des cas d'utilisation dynamiques (qui conservent la prise en charge du fractionnement de code). Les importations de modèles aideront probablement à atteindre ces objectifs.

Help Wanted

Commentaire le plus utile

@NullVoxPopuli @jherdman Génial!

En général, la façon de penser à ce que les gens devraient faire au lieu de (component dynamicString) est qu'ils ont besoin de import ou (component "staticString") pour aller quelque part .

Les options comprennent:

  • l'appelant du composant fera (component "staticString")
  • le code avec le dynamisme énumérera les possibilités, les mettra dans une carte et utilisera (get componentMap dynamicString) pour les sortir

Maintenant, vous vous demandez peut-être: comment (get components dynamicString) pourrait-il être meilleur que (component dynamicString) ? La réponse est que componentMap a dû être construit quelque part , et les règles s'appliquent de manière récursive.

Supposons que vous écriviez un composant qui vous permet d'écrire <InputField @type="text" /> ou <InputField @type="checkbox" /> (comme <input> en HTML).

Votre modèle pour input-field ressemblerait à ceci:

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

Lorsque vous écrivez le code de cette façon, Embroider peut voir qu'il ne doit inclure que deux composants dans le bundle. L'aviez-vous écrit de cette façon:

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

(oui, des choses comme ça sont étonnamment courantes)

alors Embroider ne peut pas facilement analyser le code et limiter les composants qui seront inclus dans le bundle. C'est encore pire si vous avez effectué le travail en JavaScript (ce qui est également très courant):

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

avec ce modèle:

{{component this.innerComponent}}

C'est un petit ajustement, mais cela permet à Embroider de déterminer quels composants sont réellement utilisés.

Tous les 17 commentaires

Une façon de réduire le coût de la transition serait de permettre aux addons d'indiquer statiquement lequel de leurs arguments correspond à une chaîne statique transmise par l'appelant du composant

En tant que spitball (où better-component est un espace réservé pour le nom du mot-clé du composant statique).

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

Cela permettrait aux addons d'indiquer qu'une "invocation dynamique" particulière ne fonctionne que si l' appelant fournit une chaîne statique.

Nous ne devrions le faire que si un grand nombre de cas de composants dynamiques sont, en pratique, causés par un "argument de chaîne passé à un addon".

Idk si c'est sur le sujet de ce problème, mais j'ai essayé périodiquement de comprendre la statique totale sur emberclear, et j'ai gardé une trace écrite des problèmes et de leurs solutions.

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

Donc, si les gens rencontrent des problèmes, peut-être que la documentation Abe peut vous aider? Idk

De plus, je suis super excité pour la broderie et j'ai déjà de grands projets une fois la statique totale atteinte
https://github.com/emberjs/rfcs/issues/611

Notre application est un système de diffusion de contenu dynamique. Les créateurs de contenu assemblent le contenu à partir d '«éléments d'activité», dont chacun a un composant Ember correspondant, dont le nom est défini sur un modèle de données Ember. Le cœur de ce système ressemble plus ou moins à ceci:

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

Ma lecture de ce qui précède suggère qu'il s'agit d'un scénario (component dynamicString) . Est-ce exact?

Ma lecture de ce qui précède suggère qu'il s'agit d'un scénario (component dynamicString). Est-ce exact?

Qu'est-ce que @activityElements et où est-il défini?

Ce serait un tableau d'instances de modèle de données Ember transmises à un composant contrôleur .

Nous avons également investi un peu lourdement dans les composants dynamiques, FYI. J'ai posé la question ici l'année dernière: https://discuss.emberjs.com/t/the-perils-of-dynamic-component-invocation/16784.

Nous aussi. Nous ne savons littéralement pas à l'avance les composants qui seront utilisés dans l'application. Ils se trouvent dans une base de données (ce qui les rend bien sûr modifiables), sont compilés sur le backend et sont envoyés à la demande au frontend. Ne pas pouvoir utiliser l'assistant dynamique component pour nous, c'est la même chose que de ne pas pouvoir utiliser pour chacun sur un tableau. J'espère vraiment qu'il y a une voie à suivre pour des cas d'utilisation comme celui-ci. Je ne me soucierais certainement pas de la division du code / de l'arborescence si mon application ne fonctionne pas parce que je n'ai pas le dynamisme dont j'ai besoin.

Une carte pourrait-elle être utilisée de tous les composants valides pour ce scénario?

Par exemple:

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

?

Par exemple, vous ne pouvez pas vraiment avoir de composants dynamiques complets, car vous ne pouvez rendre que ce qui se trouve dans votre application - la création d'une liste pour choisir ce que vous voulez rendre serait également utile pour le débogage. "Oh, cette valeur n'était pas l'un des composants valides"

Une carte pourrait-elle être utilisée de tous les composants valides pour ce scénario?

Cela couvrirait certainement notre cas d'utilisation.

@NullVoxPopuli @jherdman Génial!

En général, la façon de penser à ce que les gens devraient faire au lieu de (component dynamicString) est qu'ils ont besoin de import ou (component "staticString") pour aller quelque part .

Les options comprennent:

  • l'appelant du composant fera (component "staticString")
  • le code avec le dynamisme énumérera les possibilités, les mettra dans une carte et utilisera (get componentMap dynamicString) pour les sortir

Maintenant, vous vous demandez peut-être: comment (get components dynamicString) pourrait-il être meilleur que (component dynamicString) ? La réponse est que componentMap a dû être construit quelque part , et les règles s'appliquent de manière récursive.

Supposons que vous écriviez un composant qui vous permet d'écrire <InputField @type="text" /> ou <InputField @type="checkbox" /> (comme <input> en HTML).

Votre modèle pour input-field ressemblerait à ceci:

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

Lorsque vous écrivez le code de cette façon, Embroider peut voir qu'il ne doit inclure que deux composants dans le bundle. L'aviez-vous écrit de cette façon:

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

(oui, des choses comme ça sont étonnamment courantes)

alors Embroider ne peut pas facilement analyser le code et limiter les composants qui seront inclus dans le bundle. C'est encore pire si vous avez effectué le travail en JavaScript (ce qui est également très courant):

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

avec ce modèle:

{{component this.innerComponent}}

C'est un petit ajustement, mais cela permet à Embroider de déterminer quels composants sont réellement utilisés.

Je souhaite également définir plus précisément les connexions à deux autres fonctionnalités en vol.

  1. Importations de modèles
  2. Utilisation des classes de composants comme invocables

Si nous réécrivons l'exemple précédent en utilisant ces deux fonctionnalités, cela ressemble à:

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

Cela a la belle propriété d'éliminer une règle spéciale que Embroider devrait comprendre, et de rendre le modèle utilisateur pour la division du code encore plus sur les modules.

Utilisation des classes de composants comme invocables

Depuis la RFC # 481 , une classe de composant est une unité entièrement autonome qui contient toutes les informations nécessaires à la machine virtuelle Glimmer pour l'invoquer en tant que composant.

Remarque: ce n'est pas seulement vrai pour les classes qui héritent de @ember/component ou @glimmer/component . Depuis la RFC 481, la définition d'Ember de «composant» est «un objet qui est associé à un modèle et un gestionnaire de composants», qui comprend des composants personnalisés tels que des composants accrochés .

Depuis RFC # 432 (helpers contextuels et modificateurs) et RFC # 496 (handlebars strict mode), la conception pour l'avenir de la syntaxe du modèle Ember est la suivante: les expressions contenant des helpers, des composants ou des modificateurs peuvent être invoquées comme helpers, composants ou modifiers.

L'implication de la collection actuelle de RFC approuvées est que <SomeComponentClass /> (ou <this.componentClass>this.componentClass résout en une classe de composant) "fonctionnerait simplement". C'est aussi le plan d'enregistrement pour le travail de mise en œuvre.

Cela dit, pour plus de clarté, nous devrions créer une nouvelle RFC qui spécifie explicitement ce comportement.

Spécifier une liste de composants valides dans HBS quelque part est faisable pour nous, mais il y a quelques mises en garde à signaler:

  1. Actuellement, nous utilisons la solution JS indiquée par @wycats . Faire la même chose dans un fournisseur HBS est faisable, mais comme nous le savons tous, la logique dans HBS est un peu plus ardue. Il est également beaucoup plus difficile de tester unitaire qu'un composant fournisseur donne le bon composant qu'une fonction util renvoyant les bonnes chaînes.
  2. Cela n'existe pas dans JS (avec l'API publique de toute façon) afaik, mais utiliser la structure de répertoires à notre avantage serait très bien ici. Par exemple:

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

    Ce genre de chose peut finir par être plus facile à maintenir avec le temps si vous avez des types polymorphes connus regroupés par structure de répertoires.

Dans tous les cas, j'ai l'impression que les composants dynamiques ont besoin de leur propre problème pour discuter plutôt que de prendre en charge celui-ci 🙈 😄

Dans tous les cas, j'ai l'impression que les composants dynamiques ont besoin de leur propre problème pour discuter plutôt que de prendre en charge celui-ci 🙈 😄

J'accepte, et j'en ouvrirai un sous peu dans le référentiel RFC, et posterai un lien ici.

@mehulkar @wycats @jherdman qu'en est-il de permettre aux utilisateurs d'importer simplement leur liste de composants invocables dynamiquement? Cela semble beaucoup plus facile à suivre qu'un assistant qui comprend un niveau supplémentaire d'indirection.

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

Ensuite, le modèle peut simplement faire: {{#let (component this.innerComponent) as |DynamicComponent|}} ou quelque chose de similaire.

Je préférerais de loin quelque chose comme celui-ci qui facilite la recherche / lecture / analyse et détecte facilement où ces composants sont utilisés. Pensées?

@Samsinite ouais je pense que c'est l'idée. L'assistant dans HBS serait principalement du sucre syntaxique et utile pour les composants de modèle uniquement.

@Samsinite c'est essentiellement ce que j'ai proposé comme solution à court terme (avant les importations de modèles).

Cela nous obligerait à autoriser les classes de composants à être invocables, ce dont je parlais dans ce commentaire .

Allons-y!

Ah, cela a du sens pour les composants de modèle uniquement, désolé pour une mauvaise compréhension :). J'aime aussi la syntaxe des composants de modèle uniquement de:

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

De plus, en fait, je l'utiliserais probablement uniquement pour les modèles et les composants soutenus par js, en fonction de ce que les autres membres de l'équipe trouvent plus facile à lire.

Cette page vous a été utile?
0 / 5 - 0 notes