React: Formaliser les exportations ES de haut niveau

Créé le 9 nov. 2017  ·  104Commentaires  ·  Source: facebook/react

Actuellement, nous expédions uniquement les versions CommonJS de tous les packages. Cependant, nous pourrions vouloir les expédier en tant qu'ESM à l'avenir (https://github.com/facebook/react/issues/10021).

Nous ne pouvons pas le faire assez facilement car nous n'avons pas vraiment décidé à quoi ressembleraient les exportations ES de haut niveau à partir de chaque package. Par exemple, react a-t-il un tas d'exports nommés, mais aussi un export par défaut appelé React ? Devrions-nous encourager les gens à import * pour un meilleur secouage des arbres ? Qu'en est-il de react-test-renderer/shallow qui exporte actuellement une classe (et commencerait donc à échouer dans Node s'il était converti en exportation par défaut) ?

Build Infrastructure React Core Team Breaking Change Discussion

Commentaire le plus utile

C'est presque 2020 maintenant, j'aimerais savoir s'il y a des mises à jour de l'équipe officielle FB? Y aurait-il des changements liés à cela dans React v17 ?

Tous les 104 commentaires

Imho import * est une voie à suivre, je ne suis pas opposé à l'exportation par défaut non plus, mais il ne devrait pas être utilisé pour réexporter d'autres éléments comme dans cet exemple :

export const Component = ...
export default React
React.Component = Component

mais il ne devrait pas être utilisé pour réexporter d'autres éléments comme dans cet exemple :

Y a-t-il une raison technique? (En plus d'avoir deux façons de faire la même chose.)

Mon impression est que les personnes qui importeraient * (et n'utiliseraient pas la valeur par défaut) n'auraient pas de problèmes de tremblement d'arbre car la valeur par défaut resterait inutilisée. Mais peut-être que je surestime Rollup, etc.

Ces questions peuvent probablement être mieux répondues par @lukastaegert. Je ne sais pas si quelque chose a changé depuis https://github.com/facebook/react/issues/10021#issuecomment -335128611

De plus, Rollup n'est pas le seul agitateur d'arbres, et bien que l'algorithme d'agitation d'arbres de webpack soit pire que celui de rollup, son utilisation est probablement bien supérieure à celle de rollup (les deux outils font d'excellents travaux, je ne veux offenser personne , en énonçant simplement des faits) et si nous pouvons (en tant que communauté) aider les deux outils à la fois, nous devrions le faire chaque fois que nous le pouvons.

est-ce que l'arborescence va _faire_ quelque chose dans le cas de React, étant donné que tout est prétraité en un seul paquet plat ? Je me demande quel est le style d'importation principal pour React, personnellement j'ai tendance à le traiter comme une exportation par défaut, par exemple React.Component , React.Children mais parfois faire la chose nommée avec cloneElement

Comme @gaearon l'a déjà déclaré ailleurs, les améliorations de taille en cas de réaction devraient être minimes. Néanmoins, il y a des avantages :

  • React.Children pourrait probablement être supprimé dans certains cas (donc j'ai entendu )
  • React lui-même peut être hissé dans la portée supérieure par les bundlers de modules qui le prennent en charge. Cela pourrait à nouveau supprimer pas mal d'octets et pourrait également apporter une très légère amélioration des performances. La principale amélioration résiderait dans le fait qu'il n'y a pas besoin d'avoir une autre variable qui référence React.Component pour chaque module mais juste une qui est partagée partout (c'est ainsi que le rollup le fait habituellement). De plus, bien que ce ne soit que deviner, cela pourrait réduire les chances de renflouer le ModuleConcatenationPlugin de webpack
  • L'analyse statique pour réagir est plus facile non seulement pour les bundlers de modules, mais aussi pour, par exemple, les IDE et autres outils. Beaucoup de ces outils font déjà un travail raisonnable pour les modules CJS, mais à la fin, il y a beaucoup de devinettes de leur côté. Avec les modules ES6, l'analyse est une évidence.

En ce qui concerne le type d'exportation, bien sûr, seule l'exportation nommée offre vraiment l'avantage d'une arborescence facile (à moins que vous n'utilisiez GCC qui pourrait peut-être faire un peu plus dans son mouvement agressif et peut-être le dernier cumul si vous êtes vraiment chanceux) . La question de savoir si vous fournissez également une exportation par défaut est plus difficile à trancher :

  • PRO : migration sans douleur pour les bases de code ES6 existantes (par exemple, ce que @jquense décrit)
  • CONTRE : Puisque tout est attaché à un objet commun, une fois que cet objet est inclus, toutes ses clés sont incluses à la fois, ce qui déjoue à nouveau toute tentative de secouer l'arbre. Même GCC pourrait avoir du mal ici.

En tant que stratégie de migration à deux versions, vous pouvez ajouter une exportation par défaut dans la prochaine version à des fins de compatibilité qui est déclarée obsolète (elle peut même afficher un avertissement via un getter, etc.), puis la supprimer dans une version ultérieure.

C'est aussi un cas intéressant : https://github.com/facebook/react/issues/11526. Bien que le patch de singe pour les tests soit un peu louche, nous voudrons être conscients de la possibilité de casser cela (ou d'avoir une solution de contournement pour cela).

Je suis venu ici via cette conversation sur Twitter . Pour moi, il existe une réponse claire et correcte à cette question : React et ReactDOM ne devraient exporter que des exportations nommées. Ce ne sont pas des objets qui contiennent un état ou auxquels d'autres bibliothèques peuvent muter ou attacher des propriétés (nonobstant le numéro 11526) - la seule raison pour laquelle ils existent est comme un endroit pour « mettre » Component , createElement et ainsi de suite. En d'autres termes, des espaces de noms, qui doivent être importés en tant que tels.

(Cela facilite également la vie des bundlers, mais ce n'est ni ici ni là.)

Bien sûr, cela présente un changement décisif pour les personnes utilisant actuellement une importation et une transpilation par défaut. @lukastaegert a probablement la bonne idée ici, en utilisant des accesseurs pour imprimer des avertissements de dépréciation. Ceux-ci pourraient être supprimés dans la version 17, peut-être ?

Cependant, je n'ai pas de suggestion toute faite pour le #11526. Peut-être que l'expédition d'ESM aurait dû attendre la v17 pour cette raison de toute façon, auquel cas il n'y aurait pas lieu de s'inquiéter des avertissements de dépréciation.

Les gens en sont vraiment venus à aimer

import React, { Component } from 'react'

il peut donc être difficile de les convaincre d'abandonner.

Je suppose que ce n'est pas trop mal, même si un peu étrange :

import * as React from 'react';
import { Component } from 'react';

Pour clarifier, nous avons besoin que React soit dans la portée (dans ce cas, en tant qu'espace de noms) car JSX se transpile en React.createElement() . Nous pourrions casser JSX et dire que cela dépend de la fonction globale jsx() place. Ensuite, les importations ressembleraient à :

import {jsx, Component} from 'react';

ce qui est peut-être bien mais un énorme changement. Cela signifierait également que les versions de React UMD doivent désormais définir également window.jsx .

Pourquoi est-ce que je suggère jsx au lieu de createElement ? Eh bien, createElement est déjà surchargé ( document.createElement ) et bien qu'il soit d'accord avec le qualificateur React. , sans qu'il le réclame sur le global, c'est tout simplement trop. Tbh, je ne suis pas très enthousiasmé par l'une ou l'autre de ces options, et je pense que ce serait probablement le meilleur compromis :

import * as React from 'react';
import { Component } from 'react';

et gardez JSX transpilé à React.createElement par défaut.

Confession : J'ai toujours trouvé un peu étrange que vous deviez importer explicitement React pour utiliser JSX, même si vous n'utilisez cet identifiant nulle part. Peut-être qu'à l'avenir, les transpileurs pourraient insérer import * as React from 'react' (configurable dans l'intérêt de Preact, etc.) lors de la rencontre avec JSX, s'il n'existe pas déjà ? Comme ça, tu n'aurais qu'à faire ça...

import { Component } from 'react';

... et l'importation de l'espace de noms serait prise en charge automatiquement.

Dans un futur lointain, peut-être. Pour l'instant, nous devons nous assurer que les transpileurs fonctionnent avec d'autres systèmes de modules (CommonJS ou globals). Rendre cela configurable est également un obstacle et divise davantage la communauté.

Ce que @Rich-Harris a suggéré (insérer une importation spécifique lorsque jsx est utilisé) est facilement réalisé par le plugin transpilers. La communauté devrait mettre à niveau ses babel-plugin-transform-react-jsx et c'est tout. Et bien sûr, même les configurations existantes fonctionneraient toujours si une seule ajoutait import * as React from 'react'; au fichier.

Bien sûr, nous devons considérer d'autres systèmes de modules, mais cela ne semble pas être un problème difficile à résoudre. Y a-t-il des pièges spécifiques en tête ?

Bien sûr, nous devons considérer d'autres systèmes de modules, mais cela ne semble pas être un problème difficile à résoudre. Y a-t-il des pièges spécifiques en tête ?

Je ne sais pas, quelle est votre suggestion spécifique quant à la façon de le gérer? Quelle serait la valeur par défaut du plugin Babel JSX ?

Les gens en sont vraiment venus à aimer

import React, { Component } from 'react'

Quelles personnes ? sors pour que je me moque de toi.

Je l'ai souvent fait 🙂 Je suis sûr d'avoir vu ça dans d'autres endroits aussi.

La valeur par défaut est pour le moment React.createElement et elle resterait à peu près la même. Le seul problème est qu'il suppose un maintenant global (ou déjà disponible dans la portée).

Je pense que les modules es sont fondamentalement la manière standard (bien que pas encore adoptée par tous) de créer des modules, il est raisonnable de supposer que la majorité l'utilise (ou devrait) l'utiliser. La grande majorité utilise déjà divers outils d'étape de construction pour créer leurs bundles - ce qui est encore plus vrai dans cette discussion car nous parlons de transpiler la syntaxe jsx . Changer le comportement par défaut du plugin jsx en insertion automatique de React.createElement dans la portée est à mon humble avis une chose raisonnable à faire. Nous sommes au bon moment pour ce changement avec babel@7 à venir (-ish). Avec l'ajout récent de babel-helper-module-imports il est également plus facile que jamais d'insérer le bon type d'importation (es/cjs) dans le fichier.

Avoir ceci configurable pour renflouer le comportement d'aujourd'hui (en supposant qu'il soit présent dans la portée) semble vraiment être un changement mineur de configuration nécessaire pour une minorité d'utilisateurs et une amélioration (bien sûr, pas importante - mais quand même) pour la majorité.

Devrions-nous encourager les gens à importer * pour un meilleur secouage des arbres ?

Grâce à @alexlamsl, uglify-es a éliminé la pénalité de export default dans les scénarios courants :

$ cat mod.js 
export default {
    foo: 1,
    bar: 2,
    square: (x) => x * x,
    cube: (x) => x * x * x,
};
$ cat main.js 
import mod from './mod.js'
console.log(mod.foo, mod.cube(mod.bar));



md5-d6d4ede42fc8d7f66e23b62d7795acb9



$ uglifyjs -V
uglify-es 3.2.1

```js
$ cat bundle.js | uglifyjs --toplevel -bc
var mod_foo = 1, mod_bar = 2, mod_cube = x => x * x * x;

console.log(mod_foo, mod_cube(mod_bar));
$ cat bundle.js | uglifyjs --toplevel -mc passes=3
console.log(1,8);

wow, c'est super nouveau 👏 est-ce que uglify-es considéré comme stable maintenant ? Je me souviens que vous aviez mentionné il y a quelques mois que ce n'était pas encore tout à fait là, mais je m'en souviens mal, donc je ne suis pas sûr.

Quoi qu'il en soit - c'est tout et bien dans un monde de cumul, mais étant donné que React est principalement fourni dans des applications et que celles-ci utilisent principalement webpack qui ne fait pas de levage de portée par défaut, je dirais quand même que l'exportation d'un objet par défaut doit être évitée afin d'aider d'autres outils que uglisy-es + rollup dans leurs efforts pour produire des bundles de plus petite taille. Aussi pour moi, il est sémantiquement préférable d'éviter cela - ce que les bibliothèques font réellement dans de tels cas, c'est de fournir un espace de noms et il est mieux représenté lors de l'utilisation de import * as Namespace from 'namespace'

uglify-es est-il considéré comme stable maintenant ?

Aussi stable que n'importe quoi d'autre dans l'écosystème JS. Plus de 500 000 téléchargements par semaine.

c'est tout et bien dans un monde de rollup, mais étant donné que React est principalement fourni dans des applications et que celles-ci utilisent principalement des webpacks qui ne font pas de levage de portée par défaut

Quoi qu'il en soit, c'est une option. Les valeurs par défaut de Webpack ne sont pas idéales de toute façon - vous devez utiliser ModuleConcatenationPlugin comme vous le savez.

Ajoutons quelques centimes ici :

  • Je suis tout à fait d'accord avec @Rich-Harris que sémantiquement, les exportations nommées sont le bon choix
  • Je n'aime vraiment pas import React from 'react' ou import * as React from 'react' juste pour pouvoir utiliser la syntaxe JSX. À mes yeux, cette conception viole clairement le principe de ségrégation d'interface en ce sens qu'elle oblige les utilisateurs à importer tout React juste pour pouvoir utiliser la partie createElement (bien qu'avec une exportation d'espace de noms, un bundle comme Rollup supprimer à nouveau les exportations inutiles)

Donc, si nous sommes à un point où nous pourrions prendre des décisions de changement radical, je conseillerais de changer cela afin que JSX dépende d'une seule fonction (globale ou importée). Je l'aurais appelé createJSXElement() , ce qui, à mon avis, le décrit encore mieux que createElement() et n'a plus besoin du contexte React pour avoir un sens. Mais dans un monde où chaque octet compte, jsx() est probablement bien aussi.

Cela découplerait également enfin JSX de React de manière à ce que d'autres bibliothèques puissent choisir de prendre en charge JSX en utilisant la même transformation et en fournissant une fonction jsx différente. Bien sûr, vous avez beaucoup de responsabilités ici en guidant d'innombrables applications établies à travers une telle transformation, mais d'un point de vue architectural, c'est là où je pense que React et JSX devraient se diriger. Utiliser Babel pour faire le gros du travail d'une telle transformation me semble être une excellente idée !

Personnellement, je ne vois pas beaucoup d'avantages à migrer vers jsx helper car l'IMHO par défaut pour le plugin babel devrait l'importer à partir du package react , donc le nom de l'assistant réel n'est pas vraiment importe - le reste est juste une question de configuration.

C'est probablement légèrement tangentiel à la discussion principale, mais je suis curieux de savoir à quel point les modules ES fonctionnent bien avec la vérification de process.env.NODE_ENV pour exporter conditionnellement les bundles dev/prod ? Par exemple,

https://github.com/facebook/react/blob/d9c1dbd61772f8f8ab0cdf389e70463d704c480b/packages/react/npm/index.js#L3 -L7

Il me manque peut-être quelque chose d'évident ici, mais j'ai du mal à voir comment traduire ce modèle en modules ES ?

@NMinhNguyen Les exportations conditionnelles ne sont pas possibles avec les modules ES.

process.env.NODE_ENV contrôles

@Andarist @milesj Merci d'avoir confirmé mes soupçons :)

process.env.NODE_ENV contrôles

D'après le billet de blog de React 16, je pensais que les chèques process.env.NODE_ENV avaient été volontairement retirés tout en haut (au lieu d'être plus granulaires, ce qu'ils sont dans la source, si je ne me trompe pas ), pour améliorer les performances dans Node.js ?

Meilleur rendu côté serveur

React 16 inclut un moteur de rendu de serveur complètement réécrit. C'est vraiment rapide. Il prend en charge le nouvelle stratégie de packaging qui compile les process.env checks (croyez-le ou non, la lecture de process.env dans Node est vraiment lente !), vous n'avez plus besoin de regrouper React pour obtenir un bon serveur. performances de rendu.

Comme, je ne sais pas comment on pourrait utiliser le champ module dans package.json et différencier entre dev/prod pour ESM tout en gardant les bundles ES à plat et n'affectant pas les performances de Node.js

Comme, je ne sais pas comment on pourrait utiliser le champ module dans package.json et différencier entre dev/prod pour ESM tout en gardant les bundles ES à plat et n'affectant pas les performances de Node.js

C'est certainement un inconvénient, car il n'y a pas de moyen standard pour le moment de le faire. OTOH c'est juste une question d'outillage, il est possible (et c'est plutôt facile) de compiler cela dans les étapes de build de votre application même aujourd'hui. Ofc, ce serait plus facile si le package pouvait exposer les builds dev/prod et le résolveur saurait simplement lequel choisir, mais peut-être que c'est juste une question de pousser cette idée aux auteurs d'outils.

Pour la classe:

import Component from 'react/Component'

class MyButton extends Component{
  constructor(){
    this.state = {}
  }

  render() {
    return <button> Button <Button>
  }
}

Où transform utilisera super.createElement() pour se transformer en jsx ou utilisera Component.createElement() statique.

Pour les composants sans état :

import jsx from 'react/jsx'

const MyButton = () => jsx`<button> Button <Button>`;

il est peut-être possible d'utiliser un modèle littéral balisé ?

Nous espérons que Node acceptera ce PR https://github.com/nodejs/node/pull/18392

Nous sommes d'accord ici avec @Rich-Harris.

Je viens de laisser un commentaire sur ce fil qui n'a pas vraiment été mentionné spécifiquement.

Je suis dans une situation où je n'utilise pas du tout de bundler et je veux juste importer des réactifs et divers composants à utiliser nativement via le navigateur ( <script type="module" src="..."> ), c'est-à-dire

import React from “https://unpkg.com/[email protected]/umd/react.development.js”;
import ReactDOM from “https://unpkg.com/[email protected]/umd/react-dom.development.js”;
ReactDOM.render(
  React.createElement(...),
  document.getElementById('root')
);

D'après ce que je peux dire, ce n'est pas possible aujourd'hui. Au lieu de cela, je dois inclure la version UMD de réagir via une balise <script> du CDN, puis supposer qu'elle est présente sur la fenêtre dans n'importe quel module <script type="module"> que j'écris :

// myPage.html
<div id="myComponentRoot"></div>
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script type="module" src="/assets/scripts/components/MyComponent.js"></script>

// MyComponent.js
import AnotherComponent from "/assets/scripts/components/AnotherComponent.js";
window.ReactDOM.render(
  window.React.createElement(AnotherComponent),
  document.getElementById('root')
);

// AnotherComponent.js
export default class AnotherComponent extends window.React.Component {...}

Avoir une importation réactive depuis un CDN serait fantastique. Cela rendrait le prototypage dans le navigateur très rapide et facile tout en conservant la séparation des fichiers. Une chose que j'ai toujours eu l'impression de sacrifier lors de l'utilisation de React sans bundler était la possibilité de séparer les composants (et autres fonctions utilitaires, etc.) par fichier. Mais maintenant, avec la prise en charge du navigateur pour les modules ES natifs, je peux écrire mes composants React dans des fichiers séparés et faire en sorte que le navigateur les utilise simplement au fur et à mesure qu'ils sont écrits. Certes, c'est si je n'utilise pas JSX, mais même si j'utilisais JSX, je pourrais transpirer tous les fichiers en place via une étape de construction et toutes mes importations fonctionneraient toujours dans le navigateur.

// /assets/scripts/entry.js
import React from “https://unpkg.com/[email protected]/umd/react.development.js”;
import React from “https://unpkg.com/[email protected]/umd/react-dom.development.js”;
import RelatedPosts from "/assets/scripts/components/RelatedPosts.js";
ReactDOM.render(
  React.createElement(RelatedPosts),
  document.getElementById('root')
);

// /assets/scripts/components/RelatedPosts.js
import React from “https://unpkg.com/[email protected]/umd/react.development.js”;
import ListItem from "/assets/scripts/components/ListItem.js"
export default class MyComponent extends React.Component {
  componentDidMount() { /* fetch some data */ }
  render() { 
    return React.createElement(
      'ul',
      {},
      this.state.items.map(item => React.createElement(ListItem, { item: item })
    )
  }
}

// /assets/scripts/components/ListItem.js
import React from “https://unpkg.com/[email protected]/umd/react.development.js”;
export default function ListItem(props) {
  return React.createElement('li', null, ...)
}

Je suis sûr que certaines personnes diraient que taper cette URL CDN tout le temps est un problème (un problème que certaines personnes essaient de résoudre), mais les compromis en valent la peine pour moi. Changer/mettre à jour cette URL est une simple recherche/remplacement. Pour mon cas d'utilisation, cela l'emporte sur la difficulté de configurer un bundler.

Si React avait un support pour quelque chose comme ça, il n'y aurait pas besoin d'outillage. J'utilise juste le navigateur. Je pourrais envoyer du code comme celui-ci dans quelques projets personnels qui supposent des navigateurs modernes et utilisent React comme une amélioration progressive de la page. Ce qui rend cela fantastique, c'est que lorsque je reviens à la base de code dans 12 mois, je n'ai pas besoin de changer un tas d'API d'outils ou même d'avoir NPM comme gestionnaire de packages. J'utilise juste les API du navigateur, rien d'autre.

FWIW : si/quand React est livré avec un support comme celui-ci, je pense qu'il pourrait être très utile de montrer comment vous pouvez utiliser React comme ça dans la documentation, en enseignant que vous pouvez tirer parti de React et de son modèle de composant en séparant chaque logique de composants via son propre fichier et vous n'avez pas besoin d'un bundler pour le faire, utilisez simplement <script type="module"> natif, importez React depuis un CDN (ou votre propre copie locale), et c'est parti ! »

Désormais, tous les navigateurs modernes, y compris les versions mobiles, prennent en charge ESM. L'ESM n'est plus un futur système de modules mais une norme de facto actuelle.

Veuillez noter que le fait de ne pas fournir le module standardisé est un problème critique, en particulier pour une bibliothèque Web standard de facto.

import * as React from 'react';
import * as ReactDOM from 'react-dom';

C'est le code typique pour appliquer les bibliothèques React, et le fait est qu'il n'y a pas réellement de bibliothèques qui peuvent être importées, à la place, des transpileurs et des bundlers tiers émulent le processus d'importation.

Il a été légèrement justifié de ne pas fournir le vrai ESM puisque les navigateurs n'avaient pas pris en charge l'ESM natif de toute façon, mais évidemment, le temps est écoulé, et il est maintenant temps de fournir ESM comme spécifié l'exemple de code typique à import .

J'ai commencé à travailler dessus ici et ici

@TrySound Merci pour votre contribution.
Y a-t-il un endroit pour saisir et tester la version ESM ?

Il n'est prêt que pour le package React-is.

@TrySound
Ok, j'ai trouvé votre branche https://github.com/TrySound/react/tree/react-is-esm , et j'ai construit, et maintenant je sais ce que vous vouliez dire. J'attends avec impatience react-dom aussi.

Je pense que la communauté React a suffisamment discuté de ce problème pendant un certain temps.
https://discuss.reactjs.org/t/es6-import-as-react-vs-import-react/360/

Veuillez décider de la spécification officielle du module ES6 et publier bientôt.

@kenokabe Nous sommes en route. Ne nous forcez pas s'il vous plaît. Ce n'est pas si facile.

Le plan actuel migre tous les packages avec uniquement des exportations nommées. Cette modification n'affectera pas le code des bibliothèques et ne devrait pas introduire de modifications majeures, car la documentation utilise également des exportations nommées.

Pour d'autres packages, nous devons gérer à la fois les exportations par défaut et nommées qui fonctionnent différemment avec divers outils.

@TrySound Mes excuses.
Je ne voulais pas dire pour vous, puisque la mention principale de ce sujet est

Nous ne pouvons pas le faire assez facilement car nous n'avons pas vraiment décidé à quoi ressembleraient les exportations ES de haut niveau à partir de chaque package. Par exemple, React a-t-il un tas d'exports nommés, mais aussi un export par défaut appelé React ? Devrions-nous encourager les gens à importer * pour un meilleur secouage des arbres ?

et, le jour mentionné est il y a quelque temps, et je pensais juste que cela avait été discuté dans la communauté React, donc je voulais suggérer que la décision serait claire. Merci!

Vous voulez avoir une mise à jour à ce sujet...

J'utilise webpack v4 pour regrouper notre application, tandis que mon IDE intellisense (WebStorm) me suggère d'utiliser import * as React from 'react'; tandis que mon collègue me demande de changer import React from 'react'; dans une revue de code. Les deux fonctionnent bien, alors j'ai pensé qu'il disait des bêtises, mais pour le rendre heureux, je le change quand même. C'est aussi comme ça que je trouve ce fil.

Bien que par curiosité, je compare les différences de taille de construction finale entre elles (avec React 16.8.1):

En import * as React from 'react'; : 6 618 723 octets
En import React from 'react'; : 6 619 077 octets

Alors évidemment, il y avait quelques différences, marginales cependant. (note. J'ai fait la même chose avec propTypes )

Si ma compréhension de ce fil est correcte, ce serait en faveur d'avoir import * as React from 'react'; , n'est-ce pas ?! Parce que (1) oui, cela a permis d'économiser de la taille ; (2) L'ESM est un moyen standardisé, donc plus de restes de CJS. Si tel est le cas, j'aimerais changer cela aujourd'hui et m'aligner sur mon IDE.

@leoyli A long terme oui. Mais d'abord, il y aura à la fois des exportations nommées et par défaut pour ne pas casser le code existant.

J'ai pris les choses en main ici, un peu à titre d'expérience car je n'utilise plus de bundler dans mes projets et je voulais toujours utiliser react (directement depuis unpkg.com comme vous pouvez le faire avec d'autres bibliothèques telles que Vue, Hyperapp, etc.) . C'est ce que j'ai trouvé, rien d'extraordinaire, juste un umd édité à la main :

https://github.com/lukejacksonn/es-react

Un module ES6 exposant la dernière version de React et ReactDOM

Comme décrit dans le README, il s'agit principalement d'un POC, mais pour les personnes qui ne peuvent pas attendre que cette version atterrisse, il s'agit d'une version 16.8.3 qui inclut des crochets, du suspense, des paresseux, etc. et fonctionne comme prévu en faisant :

import { React, ReactDOM } from 'https://unpkg.com/es-react'

Peut-être que certains d'entre vous dans ce fil le trouveront utile .. personnellement, j'ai utilisé cette approche pour créer un projet de démarrage de réaction sans étape de construction . C'est aussi encore un travail en cours.

@lukejacksonn Nous avons également utilisé une telle solution en production, alors que notre approche était différente dans le sens où il s'agissait davantage d'un script de transformateur pour la version UMD de React et ReactDOM dans votre projet actuel. Et qu'il génère ces fichiers séparément, donc pour la plupart du code, il devrait être remplacé par une goutte. Si vous êtes intéressé https://github.com/wearespindle/react-ecmascript et vous pouvez également le charger depuis unpkg https://unpkg.com/react-ecmascript/

@ PM5544 Oh wow.. c'est une solution beaucoup plus complète que la mienne ! Super boulot

Des trucs géniaux @ PM5544. J'aimerais en savoir plus un jour. Peut-être une apparition en tant qu'invité chez Xebia ?
J'ai récemment adopté le pack pour regrouper mes packages open source, qui prend en charge UNPKG.
Quelqu'un connaît-il un bon article sur le chargement direct des dépendances à partir d'UNPKG plutôt que d'utiliser un bundler ?

J'en écris actuellement un et je le ferai également lors d'une conférence à React Norway en juin !

@TrySound Y a- t-il eu une mise à jour à ce sujet depuis février ? Que reste-t-il pour faire avancer ce problème et puis-je participer à la résolution de ce problème par un travail de codage ? J'ai déjà signé le CA, et j'ai du temps disponible aujourd'hui pour y travailler.

Cela doit d'abord être fusionné https://github.com/facebook/react/pull/15037

@TrySound D'accord, merci, j'ai transmis mon offre d'assistance à ce fil.

Lorsque vous optez pour une exportation React par défaut, vous pouvez utiliser cette approche :

// react/index.js
import * as React from "./react";
export { React as default }
export * from "./react";

// react/react.js
export function createElement() {}
...

Cela permet d'analyser statiquement le fait que l'exportation par défaut est un objet d'espace de noms qui permet l'arborescence pour ces constructions dans webpack 5 et rollup :

import React from "react";

React.createElement(); // <- only `createElement` export is used

Je suis dans le chat rollup gitter depuis un an et demi et ce genre de problème survient toutes les 2 semaines environ...

BTW, @lukejacksonn a fait un travail formidable sur le fork officieux es- react ,

BTW, @lukejacksonn a fait un travail formidable sur le fork officieux es- react ,

Je me demande pourquoi l'équipe officielle de FB ne fait rien à cela.

J'espérais également que cela générerait une certaine pression pour faire avancer les choses, et je suppose que @lukejacksonn lui-même l'était

J'espérais cela aussi en effet. En fait, je n'ai reçu pratiquement aucun soutien de la part de l'équipe de réaction dans la création du package (à part quelques mots d'encouragement de @threepointone). Récemment, un collègue de Formidable m'a aidé à créer le package par programme, ce qui est une amélioration par rapport au fait de le faire à la main chaque fois qu'une nouvelle version de React est publiée, mais entraîne une sortie beaucoup moins propre dans l'onglet réseau, donc je ne suis pas sûr que ce soit le cas. reste comme ça encore. Nous verrons!

C'est presque 2020 maintenant, j'aimerais savoir s'il y a des mises à jour de l'équipe officielle FB? Y aurait-il des changements liés à cela dans React v17 ?

Pour ceux qui ont besoin d'un module ES mis à jour React NOW, essayez @pica/react , qui est déjà sur v16.13.x
https://www.npmjs.com/package/@pika/react
https://www.npmjs.com/package/@pika/react-dom
https://github.com/pikapkg/react

Dans un futur lointain, peut-être.

Ok, déjà dans le futur. Est-ce dans un futur proche maintenant ?

@gaearon quel est le bloqueur pour décider comment structurer les exportations (exportation par défaut vs exportations nommées) ? La communauté peut-elle vous aider à prendre cette décision ?

Apparemment, cette décision a déjà été prise il y a quelque temps : #18102. Ce problème peut être clos maintenant.

Je vais donner une petite mise à jour à ce sujet.

Aucune exportation par défaut

Notre plan final est de nous éloigner complètement des exportations par défaut :

import { useState } from 'react';

Dans ce monde, cela ne fonctionnerait

import React from 'react'; // no

Cela fonctionnerait bien que ce soit un peu bruyant:

import * as React from 'react';

Mais voici le kick. Il n'y aurait en fait pas beaucoup de raisons d'importer React .

Importation automatique JSX

La raison pour laquelle les gens importent React aujourd'hui est principalement due à JSX. Mais @lunaruan termine le travail sur la nouvelle transformation JSX et les codemods associés, ce qui en supprime le besoin.

Donc tu partirais de ça :

import React from 'react';
import { useState } from 'react';

function Button() {
  const [pressed, setPressed] = useState(false)
  return <button />
}

pour ça:

import { useState } from 'react';

function Button() {
  const [pressed, setPressed] = useState(false)
  return <button />
}

JSX insère automatiquement l'importation correcte sous le capot, donc pas besoin de React dans la portée.
C'est ce qui rend tolérable la suppression des exportations par défaut. Vous n'en avez tout simplement pas autant besoin.

Modules ES

Le déploiement d'ESM au-delà d'une petite tranche globale de passionnés est un défi. Le vaste écosystème n'est pas vraiment prêt et il y a une tonne de façons dont les choses tournent mal avec différentes combinaisons d'outils. La façon dont CJS et ESM interagissent est très complexe, et cette interopérabilité (et comment elle échoue) est la source de la plupart de ces problèmes.

Notre réflexion actuelle est donc que lorsque nous optons pour l'ESM, nous pourrions vouloir essayer l'ESM jusqu'au bout. Pas de CJS du tout - ou séparé dans un package hérité compatible. Cela ne se produira pas dans React 17 et est peu probable dans 18, mais il est plausible d'essayer dans React 19.

Pour tous ceux qui recherchent une alternative à la version ESM de @pika/react , consultez https://github.com/esm-bundle/react et https://github.com/esm-bundle/react-dom. La différence est que ceux-ci sont utilisables dans les navigateurs sans polyfill de carte d'importation - le import React from 'react'; intérieur du code source de react-dom est modifié pour importer React à partir d'une URL CDN complète. Une autre différence est que les nouvelles versions sont publiées automatiquement chaque fois qu'une nouvelle version de réaction est publiée, sans aucune étape manuelle.

Démonstration de code Sandbox : https://codesandbox.io/s/gifted-roentgen-qcqoj?file=/index.html

Quelques personnes ont contesté l'idée que l'écosystème n'est pas prêt. Je n'ai pas de contexte complet à ce sujet, mais si vous pensez que c'est le bon moment pour commencer à apporter des modifications, j'apprécierais si vous pouviez consulter https://github.com/reactjs/rfcs/pull/38 et exprimer tout inquiétudes à ce sujet. Est-ce à peu près cela que vous aviez en tête, ou préféreriez-vous une approche différente?

Mais voici le kick. Il n'y aurait en fait pas beaucoup de raisons d'importer React.

C'est le cas si vous utilisez TypeScript et continuerez ainsi dans un avenir prévisible. Jusqu'à ce que TS découvre ce nouveau comportement magique de babel, les développeurs devront continuer à importer explicitement React, et ils doivent savoir quelle est la déclaration d'importation correcte.

JSX insère automatiquement l'importation correcte sous le capot, donc pas besoin de React dans la portée.

s/JSX/Le nouveau plugin de transformation babel react jsx/. JSX est une extension de syntaxe à JavaScript, elle ne fait rien en elle-même.

C'est le cas si vous utilisez TypeScript et continuerez ainsi dans un avenir prévisible. Jusqu'à ce que TS découvre ce nouveau comportement magique de babel, les développeurs devront continuer à importer explicitement React, et ils doivent savoir quelle est la déclaration d'importation correcte.

L'équipe TypeScript est au courant de ce changement et il est suivi sur https://github.com/microsoft/TypeScript/issues/34547. Nous sommes en contact étroit avec eux, alors soyez assurés que ce n'est pas un citoyen de seconde classe pour nous.

s/JSX/Le nouveau plugin de transformation babel react jsx/

Oui, c'est ce que je voulais dire !

J'apprécierais si vous pouviez consulter reactjs/rfcs#38 et exprimer vos préoccupations à ce sujet. Est-ce à peu près cela que vous aviez en tête, ou préféreriez-vous une approche différente?

Une partie du texte original de la RFC est obsolète. NodeJS permet à ESM de s'exécuter dans des fichiers .js au lieu de .mjs maintenant, lorsque vous spécifiez un "type" dans votre package.json. ( doc ).

Plus précisément, le texte original de la RFC dit ce qui n'est pas vrai :

Le code ESM doit être à l'intérieur d'un fichier .mjs

Après avoir discuté avec @frehner , voici notre proposition sur la façon dont React pourrait être progressivement converti en ESM. Notez que nous ne couplons pas le problème des exportations nommées/par défaut avec la publication d'une version ESM de React. @gaearon a précisé que l'exportation par défaut finira par disparaître, mais cela n'est pas répertorié dans notre proposition avant la phase 4.

| | Phase 1 | Phase 2 | Phase 3 | Phase 4 |
| - | -------- | -------- | -------- | ------- |
| ESM publié ? | ✔️ | ✔️ | ✔️ | ✔️ |
| package.json "module" | | ✔️ | ✔️ | ✔️ |
| webpack/rollup utiliser esm 1 , 2 | | ✔️ | ✔️ | ✔️ |
| package.json "exports" | | | ✔️ | ✔️ |
| package.json "type" | | | ✔️ | ✔️ |
| NodeJS utilise esm | | | ✔️ | ✔️ |
| Changement de rupture ? | | | | ✔️ |
| Exportation par défaut disparue ? | | | | ✔️ |
| Extensions de fichiers requises dans les importations | | | | |
| extensions de fichier mjs | | | | |

Je pense qu'il y a un argument valable pour combiner la Phase 1 et la Phase 2 ensemble, puisque la Phase 1 ne cible vraiment que les passionnés. Cependant, je les ai divisés parce que je pense que séparer les phases donne une chance de déployer très lentement ESM d'une manière qui ne brisera pas immédiatement CRA et l'ensemble de l'écosystème sans d'abord donner aux premiers utilisateurs une chance de signaler les problèmes et de trouver des correctifs .

@joeldenning quel serait le timing approximatif des phases ? est-ce uniquement basé sur le temps ou sont-ils liés à certains points de contrôle temporels dans l'écosystème ? require('react') fonctionnerait-il toujours dans la phase 3 ?

quel serait le timing approximatif des phases ? est-ce uniquement basé sur le temps ou sont-ils liés à certains points de contrôle temporels dans l'écosystème ?

Le calendrier de toutes les phases n'est limité que par le travail à faire et le calendrier de sortie de React. Je crois comprendre que toutes les choses proposées sont prises en charge par les bundlers et les nodejs, avec des solutions de repli appropriées lors de l'utilisation d'anciennes versions qui ne les prennent pas en charge.

Est-ce que require('react') fonctionnerait toujours dans la phase 3 ?

Je pense que oui. Voir https://nodejs.org/api/esm.html#esm_dual_commonjs_es_module_packages et https://nodejs.org/api/esm.html#esm_package_entry_points. Il est bien sûr tout à fait possible que je ne connaisse pas tous les cas de figure pour tout, mais je crois comprendre que la voie de transition que j'ai proposée "fonctionnera partout". Ce sont peut-être des derniers mots célèbres 😄

Une précision supplémentaire, voici à quoi ressemblerait l'archive publiée pour React :

node_modules/react/
  cjs/
    react.development.js
    react.production.min.js
    react.profiling.min.js
  umd/
    react.development.js
    react.production.min.js
    react.profiling.min.js
  esm/
    react.development.js
    react.production.min.js
    react.profiling.min.js
  index.js

Et voici une approximation de ce que serait le package.json à la fin :

{
  "type": "module",
  "main": "index.js",
  "module": "esm/react.development.js",
  "exports": {
    "import": "./esm/react.development.js",
    "require": "./cjs/react.development.js"
  }
}

^ Ce n'est pas complètement réfléchi et perfectionné, mais je partage pour donner un contexte concret à la proposition.

Donc - React souffre du risque de double package car il est avec état (hooks), donc cet état devrait être soigneusement isolé. Soit il devrait vivre dans un petit fichier CJS qui pourrait être importé à la fois par les entrées CJS et ESM, soit il existe un moyen de charger .json et de le muter avec cet état à partir des deux entrées (je ne suis pas sûr à 100% de la deuxième approche).

Je pense également qu'il est important de lister dans votre tableau l'ajout d'exportations nommées, ce qui se produirait déjà dans la phase 1.

Je ne suis toujours pas sûr à 100% si je n'ai pas raté quelques cas de coin, ce sujet est très complexe. Dans le même temps, le calendrier doit être lié aux points de contrôle de l'écosystème - la phase 3 ne peut être expédiée qu'une fois que exports dispose d'un support suffisant dans les bundlers, sinon je pense que cela pourrait entraîner des problèmes potentiels.

React souffre du risque de double package car il est avec état (hooks), donc cet état devrait être soigneusement isolé.

Bon point, je n'y avais pas pensé ( plus d'infos ). Dans ce cas, peut-être que votre suggestion d'un fichier partagé entre les builds CJS / ESM serait utile. Ou peut-être que la version ESM n'est pas une copie complète de react, mais simplement l'interface publique ESM qui appelle la version CJS.

Je pense également qu'il est important de lister dans votre tableau l'ajout d'exportations nommées, ce qui se produirait déjà dans la phase 1.

Il semble que ce soit déjà le cas, cependant, dans le code source ? D'après ce que je peux dire, le code source exporte déjà des choses en tant qu'exportations nommées.

https://github.com/facebook/react/blob/ef22aecfc52cdf0d7cedc723c590719c009c2a64/packages/react/index.js#L39

https://github.com/facebook/react/blob/ef22aecfc52cdf0d7cedc723c590719c009c2a64/packages/react/index.js#L39

Je ne suis toujours pas sûr à 100% si je n'ai pas raté quelques cas de coin, ce sujet est très complexe. Dans le même temps, le calendrier doit être lié aux points de contrôle de l'écosystème - la phase 3 ne peut être expédiée qu'une fois que les exportations ont un support suffisant dans les bundlers, sinon je pense que cela pourrait entraîner des problèmes potentiels.

D'accord sur la phase 3 - c'est pourquoi j'ai mis un point d'interrogation pour savoir s'il s'agissait d'un changement décisif. Je sais que l'ajout d'exports package.json a souvent été un changement décisif pour les autres packages de l'écosystème. Et le sujet est décidément complexe. Une chose à noter est que l'ordre des phases 3 et 4 peut être inversé, si vous le souhaitez. Je pense que ceux qui implémentent chaque phase devraient faire des tests très très très approfondis de nombreuses versions de webpack, rollup, nodejs, etc. Je ne dis pas que le travail à faire est trivial - je dis juste que je pense qu'il y a est probablement une voie de transition ici :)

Il semble que ce soit déjà le cas, cependant, dans le code source ? D'après ce que je peux dire, le code source exporte déjà des choses en tant qu'exportations nommées.

Ah - d'accord, dans ce cas, il devrait y avoir une ligne de tableau pour ajouter export default 😂 car cela fonctionne actuellement dans la plupart des bundlers et est populaire dans la nature, mais ajouter une véritable entrée ESM sans fournir default serait casser ces usages.

dans ce cas, il devrait y avoir une ligne de tableau pour ajouter l'exportation par défaut

Oui, bonne remarque. Je vais ajouter ça. Je pense qu'un RP faisant cela "semblerait mal", mais je le considère comme acceptant simplement l'interface publique actuelle pour ce qu'elle est, avec des plans pour l'améliorer à l'avenir.

Il faut noter que, dès que une version est publiée, il n'y a pas de retour et d' aller tout changement sémantique à ce enfreindrait. Étant donné que nous ne publions pas de majors très souvent, nous devons réfléchir à la manière de regrouper les modifications afin de réduire au minimum le taux de désabonnement.

Il y a aussi la question de savoir comment la séparation des builds développement/production serait gérée. Et en effet, la nature avec état de la version React est assez importante à préserver.

Il convient de noter que dès qu'une version est publiée, il n'y a pas de retour en arrière et tout changement sémantique serait rompu. Étant donné que nous ne publions pas de majors très souvent, nous devons réfléchir à la manière de regrouper les modifications afin de réduire au minimum le taux de désabonnement.

Bons points. Mon idée est d'ajouter un export default React explicite à une version ESM publiée dans React 16. Je pense que le PR pourrait faire sourciller et pourrait être controversé, car ce n'est pas la destination qui a été décidée. Cependant, je pense que nous pouvons avoir une version ESM dans React 16, puis supprimer le export default dans une future version majeure. Pour moi, l'utilisation de React via import React from 'react'; est tellement courante que l'exportation par défaut dans la version ESM consiste simplement à "accepter où nous sommes". Une future version majeure le supprimerait.

Également lié à la minimisation des changements de rupture - les phases 3 et 4 pourraient faire partie de la même version majeure. Il est possible que la phase 3 soit réalisée de manière entièrement rétrocompatible, auquel cas elle pourrait également se faufiler dans une version 16. Mais celui-là est plus délicat et je n'en connais pas assez pour en être sûr.

Il y a aussi la question de savoir comment la séparation des builds développement/production serait gérée.

C'est quelque chose que j'ai négligé. Je ne sais pas comment faire cela avec ESM dans les deux bundlers et dans NodeJS, mais je ferai des recherches pour voir ce qui est possible. J'ai trouvé cette proposition morte , mais je vais chercher des

Et en effet, la nature avec état de la version React est assez importante à préserver.

D'accord. Une chose à noter est que la nature avec état de la version React ne doit être résolue que dans la phase 3, pas dans les phases 1 et 2. Les options que @Andarist et moi avons suggérées fonctionneraient pour le résoudre.

L'approche la plus simple et la plus rétrocompatible pour le moment consiste à ajouter un simple wrapper ESM pour permettre les importations nommées.

Je serais heureux de faire un PR pour ajouter ceci à React, en m'assurant que l'ajout du champ "exports" n'est pas un changement radical (j'ai écrit un outil, npx ls-exports , ce qui facilite la détermination). Cela n'aidera pas les gens qui essaient d'utiliser ESM sans processus de génération, mais c'est un problème que les packages individuels ne sont pas capables de résoudre de toute façon.

@gaearon serait-ce utile ? Il pourrait atterrir dans React 16 en tant que semver-mineur.

La version CJS continuerait à utiliser la détection d'environnement, comme elle le fait maintenant, de sorte que ce problème (difficile, non résolu) dans ESM n'a pas encore à être résolu.

Cependant, je pense que nous pouvons avoir une version ESM dans React 16, puis supprimer la valeur par défaut d'exportation dans une future version majeure.

Une autre préoccupation est que l'augmentation du nombre de configurations met l'écosystème à rude épreuve. Par exemple, je pense que si nous publions une version ESM, elle ne devrait pas inclure quelque chose que nous allons définitivement supprimer dans la toute prochaine version, comme une exportation par défaut. En d'autres termes, je ne pense pas que cela ait beaucoup de sens d'introduire quelque chose qui va disparaître (exportations par défaut) dans quelque chose qui vient d'être ajouté (construction ESM).

Cependant, je pense que nous pouvons avoir une version ESM dans React 16, puis supprimer la valeur par défaut d'exportation dans une future version majeure.

Une autre préoccupation est que l'augmentation du nombre de configurations met l'écosystème à rude épreuve. Par exemple, je pense que si nous publions une version ESM, elle ne devrait pas inclure quelque chose que nous allons définitivement supprimer dans la toute prochaine version, comme une exportation par défaut. En d'autres termes, je ne pense pas que cela ait beaucoup de sens d'introduire quelque chose qui va disparaître (exportations par défaut) dans quelque chose qui vient d'être ajouté (construction ESM).

Je pense que cela dépend de la durée de la prochaine version. Si nous parlons d'une semaine de différence, cela semble raisonnable. Cependant, si nous parlons de mois/années, alors je ne vois pas pourquoi ce serait mal de le sortir pendant autant de temps

Le problème dans ce cas est que plus il existe longtemps, plus les gens en dépendent sous sa forme actuelle. Et puis, introduire un changement décisif là-bas rendrait plus difficile la mise à niveau de React. Maintenant, il n'y a pas qu'une seule migration ESM, mais plusieurs, et certaines personnes seront laissées pour compte parce qu'elles ont sauté trop tôt et n'ont plus les ressources pour une autre migration. Ce qui, dans le cas de l'ESM, se répercutera sur l'écosystème.

Je pense que si nous publions une version ESM, elle ne devrait pas inclure quelque chose que nous allons certainement supprimer dans la toute prochaine version, comme une exportation par défaut. En d'autres termes, je ne pense pas que cela ait beaucoup de sens d'introduire quelque chose qui va disparaître (exportations par défaut) dans quelque chose qui vient d'être ajouté (construction ESM).

Je ne considère pas import React from 'react' comme quelque chose de nouvellement ajouté, mais plutôt comme une acceptation de la réalité actuelle. Même si cela n'était peut-être pas intentionnel et n'était qu'un effet secondaire de l'interopérabilité ESM/CJS désormais obsolète, il y a encore des milliers (millions ?) de lignes de code qui le font. L'alternative (exporter uniquement les exportations nommées) dit aux utilisateurs "nous avons publié une version ESM dans une version mineure, mais vous ne pouvez pas l'utiliser sans changer tout votre code", ce qui pour moi est plus déroutant pour les utilisateurs que de voir "export par défaut supprimé" dans les notes de version d'une version majeure.

Je suis curieux - comment ESM avec une exportation par défaut peut-il être ajouté de manière rétrocompatible ? Cela est déjà arrivé (par exemple https://github.com/facebook/react/pull/18187 qui renvoie également à des problèmes connexes). Le problème est avec l'interopérabilité webpack CJS <-> ESM où si vous avez du code CJS faisant require('react') quel webpack retournera en présence d'un ESM react avec une exportation par défaut, est un objet avec default propriété (ce qui signifie qu'elle requiert maintenant require('react').default ) quel que soit le CJS react . Mais peut-être que si vous exportez également nommément, ce ne sera pas un problème ? Je pense que @TrySound a déjà rencontré de tels problèmes dans d'autres packages.

Mais peut-être que si vous exportez également nommément, ce ne sera pas un problème ?

Oui, c'est à cette approche que je pense. Voir les 40 dernières lignes de https://unpkg.com/browse/@esm-bundle/react @16.13.1/esm/react.development.js - c'est une version non officielle de React qui fait exactement cela et est utilisée par plusieurs organisations en remplacement du React officiel. Aucun changement de rupture.

Mais peut-être que si vous exportez également nommément, ce ne sera pas un problème ?

Oui, c'est à cette approche que je pense. Voir les 40 dernières lignes de https://unpkg.com/browse/@esm-bundle/react @16.13.1/esm/react.development.js - c'est une version non officielle de React qui fait exactement cela et est utilisée par plusieurs organisations en remplacement du React officiel. Aucun changement de rupture.

Mais le consommez-vous à partir du code CJS ou ESM ? Parce que ce sont les problèmes d'interopérabilité CJS <-> ESM qui peuvent être très surprenants.

@gaearon pour être clair ; il est logique de n'avoir aucune exportation par défaut dans le wrapper ESM que je propose ; toute personne faisant de l'ESM natif ferait import * as React from 'react' pour le contourner. Cependant, il est juste que quiconque faisant cela maintenant considérerait cela comme un changement décisif de ne pas avoir soudainement l'exportation par défaut, il faudrait donc attendre la v17 si vous ne vouliez pas ajouter la valeur par défaut maintenant.

Mais le consommez-vous à partir du code CJS ou ESM ? Parce que ce sont les problèmes d'interopérabilité CJS <-> ESM qui peuvent être très surprenants.

Je l'ai importé avec succès dans webpack à partir des fichiers CJS et ESM.

toute personne faisant de l'ESM natif importerait * en tant que React à partir de 'react' pour le contourner. Cependant, il est juste que quiconque faisant cela maintenant considérerait cela comme un changement décisif de ne pas avoir soudainement l'exportation par défaut, il faudrait donc attendre la v17 si vous ne vouliez pas ajouter la valeur par défaut maintenant.

D'accord. Si vous partez de zéro, il n'est pas nécessaire d'avoir l'exportation par défaut. Cependant, sans ajouter d'export par défaut, il n'est pas possible d'implémenter la Phase 2 sans que ce soit un changement décisif. Personnellement , accord pour faire la Phase 1 dans React 16 et les Phases 2-4 dans React 17+, bien que ma préférence soit de faire la Phase 1, 2 et peut-être même 3 (avec l'aide de l'outil de vérification des exportations de

Suite de cette proposition . Cela semble être une proposition de passage à un double paquet avec une source CJS et ESM complète traitant le danger du double paquet en isolant l'état ailleurs ( approche 2 ). Par opposition à l'utilisation d'un wrapper ESM comme ma précédente RFC ( approche 1 ).

En supposant que, j'ai quelques notes de choses qui n'ont pas encore été mentionnées.

  • Vous ne pouvez pas avoir un package Node.js où les fichiers ESM et CommonJS utilisent .js . Si vous définissez "type": "module" alors tous les fichiers CommonJS doivent utiliser l'extension de fichier .cjs la place.
  • Le risque d'état et d'égalité du double paquet n'est pas le seul problème lié au fait d'avoir à la fois CJS et ESM. Le fait d'avoir potentiellement 2 versions du même package chargées augmente également l'empreinte mémoire de React dans ces cas, et React n'est pas une petite bibliothèque. Ce n'est pas un compromis, mais cela vaut la peine de le garder à l'esprit.
  • Je vois un potentiel de danger de double paquet en plus de l'état interne de React. Par exemple, si l'implémentation de la classe Component ne fait pas partie du code CJS où nous partageons l'état et fait plutôt partie des bundles CJS/ESM, il existe un risque de vérifications instanceof Component dans diverses bibliothèques rupture.

Vous ne pouvez pas avoir un package Node.js où les fichiers ESM et CommonJS utilisent .js. Si vous définissez "type": "module", alors tous les fichiers CommonJS doivent utiliser l'extension de fichier .cjs à la place.

C'est vrai pour NodeJS (mais pas pour les bundlers), donc les fichiers du répertoire cjs devraient se terminer par .cjs .

Le fait d'avoir potentiellement 2 versions du même package chargées augmente également l'empreinte mémoire de React dans ces cas

Je vois comment cela augmente la taille de l'archive publiée en npm, et donc la taille globale sur le disque. Mais je ne vois pas comment cela affecte la mémoire. Pour autant que je sache, les bundlers et NodeJS n'apportent pas de code en mémoire qui n'a pas été chargé via import / require() . Pourriez-vous préciser comment l'empreinte mémoire changerait ?

Par exemple, si l'implémentation de la classe Component ne fait pas partie du code CJS où nous partageons l'état et fait plutôt partie des bundles CJS/ESM, il existe un risque d'instanceof Component checks dans diverses bibliothèques.

Une solution proposée ( 1 , 2 ) consiste à faire en sorte que l'implémentation esm de NodeJS soit simplement une interface ESM qui appelle le code CJS. De cette façon, il n'y a qu'une seule définition de Component jamais utilisée dans Node. Les phases 1 et 2 ne changeraient pas ce qui s'exécute dans NodeJS, donc cela ne s'appliquerait qu'à la phase 3.

Comme nous (webpack) avons récemment ajouté le support de terrain exports au webpack 5, je veux donner mes 2 centimes à ce sujet :

  • Ce document de travail en cours contient de nombreuses informations sur le champ exports concernant le webpack mais aussi Node.js et en général : https://gist.github.com/sokra/e032a0f17c1721c71cfced6f14516c62
  • Voici les points clés par rapport à Node.js :

    • webpack 5 ajoute également les conditions development et production , qui sont très utiles pour réagir. ( process.env.NODE_ENV , bien qu'il soit toujours pris en charge, doit être évité pour le code frontal en général, il est spécifique à Node.js)

    • webpack (et d'autres bundlers) prend en charge require("esm") , ce qui permet d'éviter le problème de double état en utilisant toujours ESM (même pour require() ). webpack a introduit une condition spéciale pour cela : module . Pour cela, les versions CommonJs et ESM doivent exporter la même interface. Actuellement, il n'y a aucune autre chose qui a le problème de double état que Node.js. Je ne pense pas que nous verrons quelque chose à l'avenir, car il s'agit principalement d'un problème de compatibilité descendante.

      Pour une compatibilité maximale, je recommanderais ce qui suit :

package.json

{
    "type": "commonjs",
    "main": "index.js",
    "module": "esm/wrapper.js",
    "exports": {
        ".": {
            "node": {
                "development": {
                    "module": "./esm/index.development.js",
                    "import": "./esm/wrapper.development.js",
                    "require": "./cjs/index.development.js"
                },
                "production": {
                    "module": "./esm/index.production.min.js",
                    "import": "./esm/wrapper.production.min.js",
                    "require": "./cjs/index.production.min.js"
                },
                "import": "./esm/wrapper.js",
                "default": "./cjs/index.js"
            },
            "development": "./esm/index.development.js",
            "production": "./esm/index.production.min.js",
            "default": "./esm/index.production.min.js"
        },
        "./index": "./index.js",
        "./index.js": "./index.js",
        "./umd/react.development": "./umd/react.development.js",
        "./umd/react.development.js": "./umd/react.development.js",
        "./umd/react.production.min": "./umd/react.production.min.js",
        "./umd/react.production.min.js": "./umd/react.production.min.js",
        "./umd/react.profiling.min": "./umd/react.profiling.min.js",
        "./umd/react.profiling.min.js": "./umd/react.profiling.min.js",
        "./package.json": "./package.json"
    }
}

esm/package.json

Permet d'utiliser .js comme extension dans ces répertoires. Alternativement, .mjs pourrait être utilisé, mais cela pourrait avoir des effets secondaires potentiels lorsque les outils vérifient l'extension. Donc .js pour être en sécurité.

{
    "type": "module"
}

esm/wrapper.js

Ce wrapper est nécessaire pour Node.js afin d'éviter le problème de double état.

import React from "../cjs/index.js";
export const {
    Children,
    Component,
    ...,
    useState,
    version
} = React;
export { React as default };

cjs/index.js

Ceci est utilisé par Node.js lorsque les conditions development et production ne sont pas prises en charge.

'use strict';

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}

esm/wrapper.development.js (similaire à esm/wrapper.production.min.js)

Ces wrappers sont nécessaires pour Node.js afin d'éviter le problème de double état.
Ils ne sont utilisés qu'une fois que Node.js ajoute les conditions development et production .

import React from "../cjs/index.development.js";
export const {
    Children,
    Component,
    ...,
    useState,
    version
} = React;
export { React as default };

index.js

Pour rétrocompatibilité.

module.exports = require('./cjs/index.js');

esm/index.development.js, esm/index.production.min.js

Ceci est utilisé par les outils qui prennent en charge le champ exports , la condition module et les conditions production / development .

/* React in ESM format */

// compat for the default exports with support for tree-shaking
import * as self from "./esm/index.development.js";
export { self as default }

Résultats

  • Webpack 5 : ./esm/index.development.js ou ./esm/index.production.min.js
  • navigateur : ./cjs/index.js
  • Webpack 4 à partir de .mjs : ./cjs/index.js
  • autres bundles : ./esm/wrapper.js
  • Node.js (ESM) : ./cjs/index.js (requis) ou ./esm/wrapper.js (importation)
  • Node.js (ancien) : ./cjs/index.js
  • Node.js (ESM + dev/prod) : ./esm/wrapper.development.js ou ./esm/wrapper.production.min.js pour l'import, ./cjs/index.development.js ou ./cjs/index.production.min.js pour require

Remarques

Il n'y a pas de esm/index.js car le choix conditionnel d'une version n'est pas possible dans ESM sans compromis majeurs.
Les outils ne peuvent bénéficier pleinement de React ESM que lorsqu'ils prennent en charge le champ exports , la condition module (à cause du problème de double état) et production / development conditions (à cause du problème d'import conditionnel).

Les outils peuvent partiellement bénéficier de React ESM lorsqu'ils prennent en charge le champ module ou le champ exports .

import { useState } from "react" ou import * as React from "react" sont techniquement illégaux tant que long react est un module CommonJs.
La plupart des outils le prennent toujours en charge pour la rétrocompatibilité, mais certains ne le font pas, par exemple Node.js
Donc actuellement, la seule façon d'utiliser react qui est valide partout est : import React from "react" .
Cette méthode devrait rester prise en charge, sinon il y aurait des cas (par exemple Node.js 14) où il n'y aurait pas de syntaxe valide pour réagir maintenant et réagir après l'ajout d'ESM.

Node.js a rejeté l'ajout d'une condition development / production pour exports pour le moment.
C'est triste, et j'espère toujours qu'ils finiront par ajouter cela.
C'est pourquoi le support pour cela est préparé dans le champ exports ci-dessus.

@sokra super ventilation, très utile, merci!

Une petite question :

Node.js a rejeté l'ajout d'une condition de développement/production pour les exportations pour le moment.

si je comprends bien, c'est toujours en cours d'élaboration ? https://github.com/nodejs/node/pull/33171 mais peut-être que je comprends mal ce PR

[modifier] le PR ci-dessus auquel j'ai lié a été remplacé par https://github.com/nodejs/node/pull/34637

[edit2] et a maintenant été fusionné dans nodejs

Merci @sokra , ce sont des suggestions très utiles.

Voici les options que je vois. Il semble que tous soient techniquement possibles, et que la décision relève davantage de la stratégie que de la mise en œuvre technique :

Option 1

Ajoutez export default React à une version ESM de React 17 et supprimez-la une fois que la prise en charge de CJS pour import React from 'react' est supprimée (peut-être dans React 18 ?).

Option 2

N'ajoutez pas export default React et créez un build React 17 ESM avec des exportations nommées uniquement.

Option 3

Ne publiez pas de build React 17 ESM. (😢) Attendez que le support import React from 'react'; soit abandonné avant de créer une version ESM.

Comparaison

| | Option 1 | Option 2 | Option 3 |
| - | -------- | -------- | -------- |
| Version ESM non référencée | v17 | v17 | v18+ |
| package.json "module" (tree secouant par défaut) | v17 | v18+ | v18+ |
| package.json "type" / "exports" (NodeJS utilise ESM) | v18+ 1 | v18+ | v18+ |

  1. Il pourrait être possible d'implémenter le type/exports package.json d'une manière entièrement rétrocompatible, auquel cas il pourrait faire partie de React 17 si l'option 1 est choisie.

Ma préférence va à l'option 1, comme je l'ai expliqué ci-dessus. Cependant, l'option 2 est également assez excitante pour moi. L'option 3 est bien sûr moins excitante. D'après ce que j'ai rassemblé dans ce numéro de github, nous avons l'expertise technique pour faire en sorte que tout cela se produise (et probablement même le travail !).

La première réaction à ce problème était, pourquoi ce problème est-il ouvert même après 3 ans ? Après avoir lu une partie de celui-ci, il est logique de comprendre pourquoi cela prend si longtemps. Maintenir une bibliothèque comme React est une tâche énorme. Alors

Compte tenu des récentes nouvelles concernant React 17, j'ai mis à jour mon commentaire précédent pour faire référence à React 17 au lieu de 16 pour tout projet futur.

J'apprécierais les commentaires des personnes sur laquelle des trois options ci-dessus est préférée.

Je pense que nous pouvons ajouter le champ exports dans package.json dans React 17, nous pourrions probablement aussi le rétroporter vers les versions précédentes :

{
  "exports": {
    ".": {
      "development": "./esm/react.development.mjs",
      "production": "./esm/react.production.mjs",
      "node": {
        "import": "./esm/react.node.mjs",
        "require": "./index.js"
      },
      "default": "./index.js"
    },
    "./jsx-dev-runtime": {
      "development": "./esm/react-jsx-dev-runtime.development.mjs",
      "production": "./esm/react-jsx-dev-runtime.production.mjs",
      "node": {
        "import": "./esm/react-jsx-dev-runtime.node.mjs",
        "require": "./jsx-dev-runtime.js"
      },
      "default": "./jsx-dev-runtime.js"
    },
    "./jsx-runtime": {
      "development": "./esm/react-jsx-runtime.development.mjs",
      "production": "./esm/react-jsx-runtime.production.mjs",
      "node": {
        "import": "./esm/react-jsx-runtime.node.mjs",
        "require": "./jsx-runtime.js"
      },
      "default": "./jsx-runtime.js"
    },
    "./": "./"
  },
}

Nous aurions besoin de nouveaux bundles esm, bien que cela ne devrait pas être trop difficile à ajouter avec le rollup.

  • Les forfaits ./esm/react.development.mjs et ./esm/react.production.mjs devraient être exempts de chèques process.env.NODE_ENV :

    • la condition est résolue au moment de l'import/du bundle via le champ exports .

    • process est une API de nœud, cela n'a pas de sens dans un environnement de navigateur et n'est pas pris en charge par _default_ par webpack 5 par exemple.

  • Le ./esm/react.node.mjs garderait les chèques process.env.NODE_ENV .
  • AFAIK uniquement le webpack 5 et le nœud prennent en charge le champ exports le moment.

Je pense que c'est plutôt sûr à ajouter, WDYT ?

https://webpack.js.org/guides/package-exports/
https://nodejs.org/dist/latest-v15.x/docs/api/packages.html

Le "./": "./" rend sûr, oui, mais empêche également toute encapsulation, vous voudriez donc le supprimer dès que vous avez un semver-major.

FWIW babel génère la nouvelle importation d'exécution jsx en tant que

import { jsxs, jsx, Fragment } from 'react/jsx-runtime';

mais si vous chargez un module comme celui-ci dans Node, il se plaindra du manque d'extension de fichier :

> node .\node.mjs
node:internal/process/esm_loader:74
    internalBinding('errors').triggerUncaughtException(
                              ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'test\node_modules\react\jsx-runtime' imported from test\node_modules\react-data-grid\lib\bundle.js
Did you mean to import react/jsx-runtime.js?
    at new NodeError (node:internal/errors:259:15)
    at finalizeResolution (node:internal/modules/esm/resolve:307:11)
    at moduleResolve (node:internal/modules/esm/resolve:742:10)
    at Loader.defaultResolve [as _resolve] (node:internal/modules/esm/resolve:853:11)
    at Loader.resolve (node:internal/modules/esm/loader:85:40)
    at Loader.getModuleJob (node:internal/modules/esm/loader:229:28)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:51:40)
    at link (node:internal/modules/esm/module_job:50:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}

L'ajout de exports devrait résoudre le problème 🤔

@nstepien fournir une exports comme vous l'avez montré dans votre message précédent n'est pas une option d'après ce que je crois. Ce que le nœud implémente concernant l'interopérabilité cjs et tout ne fonctionne pas vraiment bien avec l'écosystème existant. Le risque de double package est réel, en particulier pour les packages comme Reac qui en nécessitent une seule copie.

Une carte exports avec des fichiers commonjs uniquement pourrait potentiellement être ajoutée sans rien casser, mais devrait également être faite avec beaucoup de soin et avec des tests e2e appropriés pour cela (étant donné la complexité des choses à faire correctement)

@Andarist cela fonctionne bien, et n'est pas différent dans le cas de

Si toutes les dépendances, transitives ou non, de React sont sous le contrôle de React (ce qui est le cas dans ce cas) et que les points d'entrée ESM de chacun d'entre eux ne font que réexporter le contenu CJS, alors vous - c'est peut-être réalisable dans ce cas particulier.

Il reste cependant tout le drame de la forme réelle de l'entrée ESM (nommée, par défaut, les deux) :

  • nommé uniquement : pas vraiment rétrocompatible car une grande partie du code utilise import React from 'react' , qui est également le seul moyen d'importer réellement React dans le nœud en ce moment lors de l'utilisation d'ESM
  • par défaut uniquement : pas vraiment rétrocompatible car une grande partie du code que nous utilisons utilise import * as React from 'react' , cela a souvent été promu par les vérificateurs de type et d'autres outils
  • les deux : le seul moyen de le rendre entièrement rétrocompatible, afin qu'il puisse fonctionner avec tous les styles de chargement actuels et lors du mélange des modules ESM et CJS à travers l'arbre de dépendance

J'oublie constamment la possibilité des wrappers ESM car ils ressemblent à une triche mais aussi parce que cette technique ne fonctionne que si vous contrôlez toutes vos dépendances et ne peut pas vraiment être utilisée comme une stratégie universelle qui "fonctionnerait simplement" 😢

Je crains que la seule stratégie universelle pour fournir les deux soit, en fait, d'avoir tout votre code réel dans CJS et d'écrire des wrappers ESM autour de celui-ci pour fournir des exportations nommées. Le code qui n'est pas avec état ni ne repose sur l'identité peut être écrit nativement dans les deux, mais c'est un sous-ensemble, une mise en garde.

jsx-runtime n'est pas avec état, n'est-ce pas ? Devrait être sûr d'expédier les deux esm/cjs sans emballage pour cela.

Dois-je enregistrer un problème distinct pour l'importation de react/jsx-runtime dans le nœud esm ?

Pour les packages front-end, un défi supplémentaire rejoint le risque à double état : appelons-le le risque de double paquet.

Lors de l'exportation des versions ESM et CJS et que le package est utilisé via require et import , les deux versions sont regroupées et doublent la taille effective du package pour le package.

@sokra est-ce possible en pratique ? Par exemple, avec le rollup, je supposerais que puisque les modules cjs sont transformés en modules esm, il préférerait alors importer les modules esm chaque fois qu'ils sont disponibles.

C'est possible dans la pratique pour les bundlers suivant la sémantique des nœuds - comme le webpack 5. Il est important de faire correspondre la sémantique car sinon vous obtiendriez potentiellement des résultats différents lors de l'exécution dans un nœud et lors de l'exécution d'un code de bundles.

Cependant, webpack 5 gère une condition spéciale "module" qui est utilisée pour dédupliquer esm/cjs (ceux provenant de la carte d'exportation).

Si nous considérons la proposition de @ljharb, ce n'est pas vraiment pertinent car il propose que le fichier esm ne soit que quelques lignes de code wrapper qui réexporteraient simplement le fichier cjs.

Avec un wrapper ESM mince, il n'y a aucun risque supplémentaire, avec ou sans bundlers - seulement le risque normal que les services homologues évitent à propos des dupes dans le graphique.

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