React: Formalizar exportações de ES de alto nível

Criado em 9 nov. 2017  ·  104Comentários  ·  Fonte: facebook/react

Atualmente, enviamos apenas versões CommonJS de todos os pacotes. No entanto, podemos querer enviá-los como ESM no futuro (https://github.com/facebook/react/issues/10021).

Não podemos fazer isso facilmente porque ainda não decidimos como as exportações de ES de nível superior ficariam em cada pacote. Por exemplo, react tem um monte de exportações nomeadas, mas também uma exportação padrão chamada React ? Devemos encorajar as pessoas a import * para melhor sacudir as árvores? E quanto a react-test-renderer/shallow que atualmente exporta uma classe (e, portanto, começaria a falhar no Node se fosse convertido em uma exportação padrão)?

Build Infrastructure React Core Team Breaking Change Discussion

Comentários muito úteis

Já estamos quase em 2020, gostaria de saber se há alguma atualização do time oficial do FB? Haveria alguma alteração relacionada a isso no React v17?

Todos 104 comentários

Imho import * é um caminho a percorrer. Não me oponho a ter uma exportação padrão também, mas não deve ser usada para reexportar outras coisas como neste exemplo:

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

mas não deve ser usado para reexportar outras coisas como neste exemplo:

Existe uma razão técnica para isso? (Além de haver duas maneiras de fazer a mesma coisa.)

Minha impressão é que as pessoas que importassem * (e não usassem o padrão) não teriam problemas para balançar a árvore, já que o padrão permaneceria sem uso. Mas talvez eu superestime Rollup etc.

Essas perguntas provavelmente podem ser melhor respondidas por @lukastaegert. Não tenho certeza se algo mudou desde https://github.com/facebook/react/issues/10021#issuecomment -335128611

Além disso, Rollup não é o único triturador de árvore por aí, e embora o algoritmo de trepidação de árvore do webpack seja pior do que o de rollup, seu uso é provavelmente muito maior do que o de rollup (ambas as ferramentas fazem excelentes trabalhos, ofc, não quero ofender ninguém , apenas declarando fatos) e se pudermos (como a comunidade) ajudar as duas ferramentas ao mesmo tempo, devemos fazê-lo sempre que pudermos.

o tremor de árvore _fazer_ qualquer coisa no caso do React, visto que tudo é pré-processado em um único pacote plano? Eu me pergunto qual é o estilo de importação principal do React, pessoalmente, tendo a tratá-lo como uma exportação padrão, por exemplo, React.Component , React.Children mas ocasionalmente faço a coisa nomeada com cloneElement

Como @gaearon já afirmou em outro lugar, espera-se que as melhorias de tamanho em caso de reação sejam mínimas. No entanto, existem vantagens:

  • React. Crianças provavelmente podem ser removidas em alguns casos (pelo que ouvi 😉)
  • O próprio React pode ser elevado ao escopo superior por agrupadores de módulos que oferecem suporte a isso. Isso poderia remover novamente alguns bytes e também conceder uma melhora de desempenho muito pequena. A principal melhoria estaria no fato de que não é necessário haver outra variável que faça referência a React.Component para cada módulo, mas apenas uma que seja compartilhada em todos os lugares (é assim que o rollup geralmente faz). Além disso, embora seja apenas eu adivinhando, isso pode reduzir a chance de o ModuleConcatenationPlugin do webpack escapar
  • A análise estática para reação é mais fácil não apenas para empacotadores de módulo, mas também para IDEs e outras ferramentas, por exemplo. Muitas dessas ferramentas já fazem um trabalho razoável para os módulos CJS, mas no final, há muitas suposições envolvidas. Com os módulos ES6, a análise é um acéfalo.

Quanto ao tipo de exportação, é claro que apenas a exportação nomeada realmente fornece o benefício de fácil agitação da árvore (a menos que você use o GCC, que pode ser capaz de fazer um pouco mais em seu movimento agressivo e talvez o último acúmulo se você tiver muita sorte) . A questão de fornecer uma exportação padrão também é mais difícil de decidir:

  • PRO: migração sem problemas para bases de código ES6 existentes (por exemplo, o que @jquense descreve)
  • CON: Uma vez que tudo está ligado a um objeto comum, uma vez que este objeto é incluído, todas as suas chaves são incluídas de uma vez, o que novamente derrota qualquer tentativa de sacudir a árvore. Até mesmo o GCC pode ter dificuldade aqui.

Como uma estratégia de migração de duas versões, você pode adicionar uma exportação padrão na próxima versão para fins de compatibilidade que é declarada obsoleta (pode até exibir um aviso por meio de um getter, etc.) e, em seguida, removê-la em uma versão posterior.

Este também é um caso interessante: https://github.com/facebook/react/issues/11526. Embora o monkeypatching para teste seja um pouco duvidoso, queremos estar atentos para não quebrar isso (ou ter uma solução alternativa para isso).

Vim aqui através desta conversa no Twitter . Para mim, há uma resposta clara e correta para esta pergunta: React e ReactDOM devem exportar apenas exportações nomeadas. Eles não são objetos que contêm estado ou aos quais outras bibliotecas podem sofrer mutação ou anexar propriedades (não obstante # 11526) - a única razão pela qual existem é como um lugar para 'colocar' Component , createElement e assim por diante. Em outras palavras, namespaces, que devem ser importados como tais.

(Também torna a vida mais fácil para os empacotadores, mas não é aqui nem ali.)

Claro, isso apresenta uma mudança significativa para as pessoas que atualmente usam uma importação e transpilação padrão. @lukastaegert provavelmente tem a idéia certa aqui, usando acessadores para imprimir avisos de depreciação. Eles poderiam ser removidos na versão 17, talvez?

No entanto, não tenho uma sugestão pronta para o # 11526. Talvez o ESM de envio tivesse que esperar pela v17 por esse motivo de qualquer maneira; nesse caso, não haveria necessidade de se preocupar com avisos de depreciação.

As pessoas realmente passaram a gostar

import React, { Component } from 'react'

então, convencê-los a desistir pode ser difícil.

Eu acho que isso não é tão ruim, mesmo que um pouco estranho:

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

Para esclarecer, precisamos que React esteja no escopo (neste caso, como um namespace) porque JSX transpila para React.createElement() . Poderíamos quebrar o JSX e dizer que ele depende da função global jsx() . Então as importações ficariam assim:

import {jsx, Component} from 'react';

o que talvez seja bom, mas uma grande mudança. Isso também significaria que as compilações UMD do React agora precisam definir window.jsx também.

Por que estou sugerindo jsx vez de createElement ? Bem, createElement já está sobrecarregado ( document.createElement ) e embora esteja tudo bem com o qualificador React. , sem ele reivindicá-lo no global é demais. Tbh, não estou muito empolgado com nenhuma dessas opções e acho que este seria provavelmente o melhor meio-termo:

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

e manter JSX transpilando para React.createElement por padrão.

Confissão: Eu sempre achei um pouco estranho que você tenha que importar explicitamente React para usar JSX, mesmo que você não esteja usando esse identificador em nenhum lugar. Talvez no futuro, os transpiladores possam inserir import * as React from 'react' (configurável por causa do Preact etc.) ao encontrar JSX, se ele ainda não existir? Dessa forma, você só precisa fazer isso ...

import { Component } from 'react';

... e a importação do namespace seria feita automaticamente.

Em um futuro distante, talvez. Por enquanto, precisamos ter certeza de que os transpiladores funcionam com outros sistemas de módulo (CommonJS ou globais). Tornar isso configurável também é um obstáculo e divide ainda mais a comunidade.

O que @ Rich-Harris sugeriu (inserir uma importação específica quando jsx é usado) é feito facilmente pelo plugin transpilers. A comunidade teria que atualizar seu babel-plugin-transform-react-jsx e pronto. E, é claro, mesmo as configurações existentes ainda funcionariam se apenas alguém adicionasse import * as React from 'react'; ao arquivo.

Claro que precisamos considerar outros sistemas de módulo, mas não parece um problema difícil de resolver. Existe alguma pegadinha específica em mente?

Claro que precisamos considerar outros sistemas de módulo, mas não parece um problema difícil de resolver. Existe alguma pegadinha específica em mente?

Não sei, qual é a sua sugestão específica sobre como lidar com isso? Qual seria o padrão para o plugin Babel JSX?

As pessoas realmente passaram a gostar

import React, { Component } from 'react'

Quais pessoas? Venha para que eu possa zombar de você.

Eu fazia muito isso 🙂 Tenho certeza de que já vi isso em outros lugares também.

No momento, o padrão é React.createElement e permaneceria praticamente o mesmo. O único problema é que ele assume um agora global (ou já disponível no escopo).

Eu acho que como os módulos es são basicamente a forma padrão (embora ainda não seja adotada por todos) de fazer módulos, é razoável supor que a maioria é (ou deveria) usá-lo. A grande maioria já usa várias ferramentas de etapa de compilação para criar seus pacotes - o que é ainda mais verdadeiro nesta discussão porque estamos falando sobre transpilar a sintaxe jsx . Mudar o comportamento padrão do plugin jsx para a inserção automática de React.createElement no escopo é algo razoável a se fazer. Estamos no momento perfeito para essa mudança com o babel @ 7 chegando em breve (-ish). Com a recente adição de babel-helper-module-imports , também é mais fácil do que nunca inserir o tipo certo de importação (es / cjs) no arquivo.

Ter isso configurável para resgatar o comportamento de hoje (supondo que esteja presente no escopo) parece realmente uma pequena mudança na configuração necessária para uma minoria de usuários e uma melhoria (claro, não grande - mas ainda assim) para a maioria.

Devemos encorajar as pessoas a importar * para melhor sacudir as árvores?

Graças a @alexlamsl uglify-es eliminou a penalidade de export default em cenários comuns:

$ 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);

uau, que ótima notícia 👏 uglify-es considerado estável agora? Lembro que você mencionou alguns meses atrás que ainda não estava lá, mas posso me lembrar disso incorretamente, então não tenho certeza.

Enfim - isso é tudo e muito bom em um mundo de rollup, mas considerando que React é empacotado principalmente em aplicativos e aqueles usam principalmente webpack que não faz levantamento de escopo por padrão, eu ainda diria que exportar um objeto como padrão deve ser evitado para ajudar outras ferramentas além de uglisy-es + rollup em seus esforços para produzir tamanhos de pacote menores. Além disso, para mim é semanticamente melhor evitar isso - o que as libs realmente fazem nesses casos é fornecer um namespace e é melhor representado ao usar import * as Namespace from 'namespace'

o uglify-es é considerado estável agora?

Tão estável quanto qualquer outra coisa no ecossistema JS. Mais de 500 mil downloads por semana.

isso é muito bom em um mundo de rollup, mas considerando que o React é empacotado principalmente em aplicativos e aqueles usam principalmente webpack, que não faz levantamento de escopo por padrão

Enfim, é uma opção. Os padrões do Webpack não são ideais - você deve usar ModuleConcatenationPlugin como sabe.

Adicionando alguns centavos aqui:

  • Eu concordo totalmente com @ Rich-Harris que semanticamente, as exportações nomeadas são a escolha certa
  • Eu realmente não gosto de import React from 'react' ou import * as React from 'react' apenas por poder usar a sintaxe JSX. A meu ver, este design está violando claramente o Princípio de Segregação de Interface, pois força os usuários a importar todo o React apenas para poder usar a parte createElement (embora reconhecidamente com uma exportação de namespace, um bundler como Rollup irá retire as exportações desnecessárias novamente)

Portanto, se estivermos em um ponto em que podemos tomar decisões de alteração significativa, aconselho alterar isso para que o JSX dependa de uma única função (global ou importada). Eu o teria chamado de createJSXElement() , que na minha opinião o descreve ainda melhor do que createElement() e não precisa mais do contexto React para fazer sentido. Mas em um mundo onde cada byte conta, jsx() provavelmente está ok também.

Isso também separaria, por fim, JSX de React de forma que outras bibliotecas possam escolher oferecer suporte a JSX usando a mesma transformação e fornecendo uma função jsx . Claro que você tem muita responsabilidade aqui, guiando inúmeros aplicativos estabelecidos por meio de tal transformação, mas do ponto de vista arquitetônico, é para onde eu acho que React e JSX deveriam estar indo. Usar Babel para fazer o trabalho pesado de tal transformação parece uma ótima ideia para mim!

Pessoalmente, não vejo muito ganho em migrar para jsx helper, pois o IMHO padrão para o plugin babel deveria importá-lo do pacote react , então o nome do helper real realmente não importa - o resto é apenas questão de configurá-lo.

Isso provavelmente é um pouco tangencial à discussão principal, mas estou curioso para saber como os módulos ES funcionam bem com a verificação de process.env.NODE_ENV para exportar condicionalmente pacotes dev / prod? Por exemplo,

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

Posso estar faltando algo óbvio aqui, mas estou lutando para ver como traduzir esse padrão em módulos ES?

@NMinhNguyen Exportações condicionais não são possíveis com módulos ES.

process.env.NODE_ENV cheques podem estar em um nível mais granular (código), porém, prontos para serem substituídos pelo empacotador com valores apropriados.

@Andarist @milesj Obrigado por confirmar minha suspeita :)

process.env.NODE_ENV cheques podem estar em um nível mais granular (código), porém, prontos para serem substituídos pelo empacotador com valores apropriados.

Na postagem do blog React 16, pensei que os cheques de process.env.NODE_ENV foram puxados para o topo de propósito (em vez de serem mais granulares, que é o que estão na fonte, se não me engano ), para ajudar no desempenho em Node.js?

Melhor renderização do lado do servidor

React 16 inclui um renderizador de servidor completamente reescrito. É muito rápido. Ele suporta streaming , para que você possa começar a enviar bytes para o cliente mais rápido. E graças a uma nova estratégia de empacotamento que compila process.env verificações (acredite ou não, ler process.env no Node é muito lento!), Você não precisa mais agrupar o React para obter um bom servidor- desempenho de renderização.

Tipo, não tenho certeza de como alguém poderia usar o campo module em package.json e diferenciar entre dev / prod para ESM, mantendo os pacotes ES planos e não afetando o desempenho do Node.js.

Tipo, eu não tenho certeza de como alguém poderia usar o campo do módulo em package.json e diferenciar entre dev / prod para ESM enquanto mantém pacotes ES planos e não afeta o desempenho do Node.js.

Isso com certeza é uma desvantagem, porque não existe uma maneira padrão no momento de fazer isso. OTOH é apenas uma questão de ferramentas, é possível (e é bastante fácil) compilar isso nas etapas de construção de seu aplicativo até hoje. Ofc seria mais fácil se o pacote pudesse expor as compilações dev / prod e o resolvedor soubesse apenas qual escolher, mas talvez isso seja apenas uma questão de passar essa ideia aos autores das ferramentas.

Para classe:

import Component from 'react/Component'

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

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

Onde transform usará super.createElement () para transformar para jsx ou usará Component.createElement () estático.

Para componentes sem estado:

import jsx from 'react/jsx'

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

talvez seja possível usar literal de modelo com tag?

Esperamos que o Node aceite este PR https://github.com/nodejs/node/pull/18392

Concordamos aqui com @ Rich-Harris.

Apenas deixando um comentário neste tópico que não foi realmente mencionado especificamente.

Estou em uma situação em que não estou usando um bundler e apenas quero importar o react e vários componentes para usar nativamente através do navegador ( <script type="module" src="..."> ), ou seja,

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')
);

Pelo que posso dizer, isso não é possível hoje. Em vez disso, tenho que incluir a versão UMD de react por meio de uma tag <script> do CDN e, em seguida, assumir sua presença na janela em qualquer <script type="module"> módulo que eu escrever:

// 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 {...}

Ter uma importação de reação de um CDN seria fantástico. Isso tornaria a prototipagem no navegador muito rápida e fácil, ao mesmo tempo em que seria capaz de manter a separação dos arquivos. Uma coisa que sempre senti que estava sacrificando ao usar o React sem um bundler foi a capacidade de separar componentes (e outras funções de utilitário, etc.) por arquivo. Mas agora, com suporte de navegador para módulos ES nativos, posso escrever meus componentes React em arquivos separados e fazer com que o navegador os consuma à medida que são escritos. Concedido isso se eu não estiver usando JSX, mas mesmo se eu estiver usando JSX, eu poderia transpirar todos os arquivos no local por meio de uma etapa de compilação e todas as minhas importações ainda funcionariam no navegador.

// /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, ...)
}

Tenho certeza de que algumas pessoas diriam que digitar esse url CDN o tempo todo é um problema (um problema que algumas pessoas estão tentando corrigir), mas as compensações valem a pena para mim. Alterar / atualizar esse url é um processo simples de localizar / substituir. Para o meu caso de uso, isso supera o problema de configurar um bundler.

Se o React tivesse suporte para algo assim, não haveria necessidade de ferramentas. Estou apenas usando o navegador. Eu poderia enviar um código como este em alguns projetos pessoais que assumem navegadores modernos e reagem como um aprimoramento progressivo na página. O que torna isso fantástico é que quando eu volto à base de código em 12 meses, não preciso mudar um monte de APIs de ferramentas ou mesmo ter o NPM como um gerenciador de pacotes. Estou apenas usando APIs do navegador, nada mais.

FWIW: se / quando o React for fornecido com suporte como este, acho que seria muito valioso mostrar como você poderia usar o React assim nos documentos, ensinando que você pode aproveitar o React e seu modelo de componente, separando a lógica de cada componente por meio de seu seu próprio arquivo e você não precisa de um bundler para fazer isso, apenas use <script type="module"> nativo, importe o React de um CDN (ou sua própria cópia local) e pronto! ”

Agora, todos os navegadores modernos, incluindo versões móveis, oferecem suporte ao ESM. ESM não é mais um sistema de módulo futuro, mas um padrão de fato atual.

Esteja ciente de que não fornecer o módulo padronizado é um problema crítico, especialmente para uma biblioteca da Web padrão.

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

Este é o código típico para aplicar bibliotecas React, e o fato é que não existem bibliotecas que podem ser importadas, em vez disso, transpiladores e bundlers de terceiros emulam o processo de importação.

Foi ligeiramente justificado não fornecer o ESM real, já que os navegadores não tinham suporte para o ESM nativo de qualquer maneira, mas obviamente, o tempo acabou e agora é a hora de fornecer o ESM conforme especificado o código de amostra típico para import .

Comecei a trabalhar nisso aqui e aqui

@ TrySound Obrigado por sua contribuição.
Existe algum lugar para pegar e testar a compilação do ESM?

Ele está pronto apenas para o pacote react-is.

@ TrySound
Ok, encontrei seu branch https://github.com/ExperimenteSound/react/tree/react-is-esm , e o construí, e agora sei o que você quis dizer. Ansioso por react-dom também.

Acho que a comunidade React já discutiu esse assunto o suficiente por um tempo.
https://discuss.reactjs.org/t/es6-import-as-react-vs-import-react/360/

Decida a especificação oficial do módulo ES6 e publique logo.

@kenokabe Estamos a caminho. Não nos force, por favor. Não é tão fácil.

O plano atual é migrar todos os pacotes apenas com exportações nomeadas. Essa alteração não afetará o código das bibliotecas e não deve apresentar alterações importantes, pois o docs também usa exportações nomeadas.

Para outros pacotes, precisamos lidar com as exportações padrão e nomeadas, que funcionam de maneira diferente com várias ferramentas.

@ TrySound Minhas desculpas.
Eu não quis dizer com você, já que a menção principal deste tópico é

Não podemos fazer isso facilmente porque ainda não decidimos como as exportações de ES de nível superior ficariam em cada pacote. Por exemplo, o react tem um monte de exportações nomeadas, mas também uma exportação padrão chamada React? Devemos encorajar as pessoas a importar * para melhor sacudir as árvores?

e o dia mencionado foi há um tempo atrás, e eu apenas pensei que isso foi discutido na comunidade React, então gostaria de sugerir que a decisão seria clara. Obrigado!

Quer se atualizar sobre isso ...

Estou usando o webpack v4 para empacotar nosso aplicativo, enquanto meu IDE intellisense (WebStorm) sugere que eu use import * as React from 'react'; enquanto meu colega de trabalho me pede para alterar import React from 'react'; em uma revisão de código. Ambos funcionam bem, então eu pensei que ele estava dizendo alguma bobagem, mas para deixá-lo feliz, estou mudando de qualquer maneira. É também assim que encontro este tópico.

Por curiosidade, comparo as diferenças no tamanho final da compilação (com React 16.8.1):

Em import * as React from 'react'; : 6.618.723 bytes
Em import React from 'react'; : 6.619.077 bytes

Obviamente, havia algumas diferenças, embora marginais. (nota. Eu fiz o mesmo com propTypes )

Se meu entendimento neste tópico está correto, seria a favor de ter import * as React from 'react'; , certo ?! Porque (1) sim, economizou algum tamanho; (2) ESM é uma forma padronizada para que não haja mais sobras de CJS. Se for esse o caso, gostaria de mudar isso hoje e alinhar com meu IDE.

@leoyli Em longo prazo, sim. Mas primeiro haverá exportações nomeadas e padrão para não quebrar o código existente.

Eu resolvi resolver o problema aqui, um pouco como um experimento, já que não estou mais usando um bundler em meus projetos e ainda queria usar o react (direto de unpkg.com como você faz com outras bibliotecas como Vue, Hyperapp etc.) . Isso é o que eu descobri, nada sofisticado, apenas um umd editado à mão:

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

Um módulo ES6 expondo a versão mais recente de React e ReactDOM

Conforme descrito no README, trata-se principalmente de um POC, mas para pessoas que não podem esperar que esta construção chegue, é uma construção 16.8.3 que inclui ganchos, suspense, preguiça etc. e funciona conforme o esperado ao fazer:

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

Talvez alguns de vocês neste tópico achem útil .. pessoalmente, tenho usado essa abordagem para criar um projeto inicial de reação livre de etapa de compilação . Também é um trabalho em andamento.

@lukejacksonn Temos usado essa solução também na produção, embora nossa abordagem seja diferente no sentido de que é mais um script de transformador para a versão UMD do React e do ReactDOM em seu projeto atual. E que ele produz esses arquivos separadamente, portanto, para a maioria dos códigos existentes, deve haver uma queda na substituição. Se você estiver interessado https://github.com/wearespindle/react-ecmascript e também pode carregá-lo em unpkg https://unpkg.com/react-ecmascript/

@ PM5544 Uau .. esta é uma solução muito mais abrangente do que a minha! Bom trabalho 💯

Coisas incríveis @ PM5544. Adoraria ouvir mais sobre isso algum dia. Talvez uma aparição especial na Xebia?
Recentemente, adotei o pacote para agrupar meus pacotes de código aberto, que oferece suporte a UNPKG.
Alguém conhece um bom artigo sobre como carregar dependências diretamente do UNPKG em vez de usar um bundler?

No momento, estou escrevendo um e vou apresentá-lo como uma palestra no React Norway em junho também!

@ TrySound Houve alguma atualização sobre isso desde fevereiro? O que resta para resolver esse problema, e posso participar para resolvê-lo por meio de algum trabalho de codificação? Já assinei o CA, e hoje tenho tempo disponível para trabalhar nele.

Isso precisa ser mesclado primeiro https://github.com/facebook/react/pull/15037

@ TrySound Ok, obrigado, encaminhei minha oferta de assistência para esse tópico.

Ao usar uma exportação React padrão, você pode usar esta abordagem:

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

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

Isso torna estaticamente analisável que a exportação padrão é um objeto de namespace que permite a agitação de árvore para essas construções no webpack 5 e rollup:

import React from "react";

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

Estou no chat gitter rollup há 1,5 anos e esse tipo de problema surge a cada 2 semanas ou mais ...

A propósito , excelente trabalho na oficial do es- react , e pode altamente recomendá-lo.

A propósito , excelente trabalho na oficial do es- react , e pode altamente recomendá-lo.

Eu me pergunto por que o time oficial do FB não faz nada contra isso.

Eu também esperava que isso gerasse alguma pressão para fazer as coisas avançarem, e acho que o próprio @lukejacksonn também. Pelo que entendi, no entanto, ele realmente recebeu algum apoio da equipe do React para construir seu fork a partir das fontes originais, então parece haver pelo menos algum interesse nisso.

Eu esperava isso também. Na verdade, não recebi quase nenhum apoio da equipe de reação na criação do pacote (além de algumas palavras gentis de encorajamento de @threepointone). Recentemente, um colega aqui na Formidable me ajudou a construir o pacote programaticamente, o que é uma melhoria em relação a fazê-lo manualmente toda vez que uma nova versão do react é lançada, mas resulta em muito menos saída limpa na guia de rede, então não tenho certeza se isso vai fique assim ainda. Veremos!

Já estamos quase em 2020, gostaria de saber se há alguma atualização do time oficial do FB? Haveria alguma alteração relacionada a isso no React v17?

Para aqueles que precisam de um módulo ES atualizado React NOW, tente @pica/react , que já está na v16.13.x
https://www.npmjs.com/package/@pika/react
https://www.npmjs.com/package/@pika/react -dom
https://github.com/pikapkg/react

Em um futuro distante, talvez.

Ok, já no futuro. É em um futuro próximo agora?

@gaearon qual é o bloqueador para decidir como estruturar as exportações (exportação padrão vs exportações nomeadas)? Há algo que a comunidade possa ajudá-lo a tomar essa decisão?

Aparentemente, essa decisão já foi tomada há algum tempo: # 18102. Este problema pode ser encerrado agora.

Vou dar uma pequena atualização sobre isso.

Sem exportação padrão

Nosso plano final é nos afastarmos totalmente das exportações padrão:

import { useState } from 'react';

Nesse mundo, isso não funcionaria:

import React from 'react'; // no

Isso funcionaria, embora seja um pouco barulhento:

import * as React from 'react';

Mas aqui está o chute. Na verdade, não haveria muitos motivos para importar React .

Importação automática JSX

O motivo pelo qual as pessoas importam React hoje é principalmente devido ao JSX. Mas @lunaruan está terminando o trabalho na nova transformação JSX e codemods relacionados, que eliminam a necessidade dela.

Então você partiria deste:

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

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

para isso:

import { useState } from 'react';

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

JSX insere a importação correta automaticamente sob o capô, portanto, não há necessidade de React no escopo.
Isso é o que torna tolerável a mudança para remover as exportações padrão. Você simplesmente não precisa tanto deles.

Módulos ES

Implantar o ESM além de uma pequena parcela geral de entusiastas é um desafio. O amplo ecossistema não está realmente pronto e há várias maneiras de as coisas darem errado com diferentes combinações de ferramentas. A forma como o CJS e o ESM interagem é muito complexa e essa interoperabilidade (e como ela falha) é a fonte da maioria desses problemas.

Portanto, nosso pensamento atual é que, quando adotamos o ESM, podemos querer experimentar o ESM até o fim. Nenhum CJS - ou separado em um pacote de legado compatível. Isso não acontecerá no React 17 e é improvável no 18, mas é plausível tentar no React 19.

Para quem procura uma alternativa para a construção de ESM de @pika/react , confira https://github.com/esm-bundle/react e https://github.com/esm-bundle/react-dom. A diferença é que eles podem ser usados ​​em navegadores sem um polyfill de mapa de importação - o import React from 'react'; dentro do código-fonte do react-dom é alterado para importar o React de um url CDN completo. Outra diferença é que as novas versões são publicadas automaticamente sempre que uma nova versão do react é publicada, sem nenhuma etapa manual.

Demonstração de sandbox de código: https://codesandbox.io/s/gifted-roentgen-qcqoj?file=/index.html

Algumas pessoas desafiaram a noção de que o ecossistema não está pronto. Não tenho um contexto completo sobre isso, mas se você acha que é um bom momento para começar a fazer alterações, agradeceria se pudesse dar uma olhada em https://github.com/reactjs/rfcs/pull/38 e expressar qualquer preocupações sobre isso. É isso que você tinha em mente ou prefere uma abordagem diferente?

Mas aqui está o chute. Na verdade, não haveria muitos motivos para importar o React.

Sim, se você estiver usando o TypeScript, e continuará assim no futuro próximo. Até que o TS aprenda sobre esse novo comportamento mágico do babel, os desenvolvedores terão que continuar importando explicitamente o React, e eles precisam saber qual é a instrução de importação correta.

JSX insere a importação correta automaticamente sob o capô, então não há necessidade de React no escopo.

s / JSX / O novo plugin de transformação babel react jsx /. JSX é uma extensão de sintaxe para JavaScript, que por si só não faz nada.

Sim, se você estiver usando o TypeScript, e continuará assim no futuro próximo. Até que o TS aprenda sobre esse novo comportamento mágico do babel, os desenvolvedores terão que continuar importando explicitamente o React, e eles precisam saber qual é a instrução de importação correta.

A equipe do TypeScript está ciente dessa mudança e ela está sendo controlada em https://github.com/microsoft/TypeScript/issues/34547. Estamos em contato próximo com eles, portanto, tenha certeza de que não é um cidadão de segunda classe para nós.

s / JSX / O novo plugin de transformação babel react jsx /

Sim, é isso que eu quis dizer!

Eu apreciaria se você pudesse ler reactjs / rfcs # 38 e expressar qualquer preocupação sobre isso. É isso que você tinha em mente ou prefere uma abordagem diferente?

Parte do texto original da RFC está desatualizado. O NodeJS permite que o ESM seja executado em .js arquivos em vez de .mjs agora, quando você especifica um "type" em seu package.json. ( docs ).

Especificamente, o texto original da RFC diz o seguinte, o que não é verdade:

O código ESM deve estar dentro de um arquivo .mjs

Depois de conversar com @frehner , aqui está nossa proposta de como o React pode ser convertido incrementalmente para ESM. Observe que não associamos o problema das exportações nomeadas / padrão com a publicação de uma versão ESM do React. @gaearon esclareceu que a exportação padrão acabará, mas isso não está listado em nossa proposta até a Fase 4.

| | Fase 1 | Fase 2 | Fase 3 | Fase 4 |
| - | -------- | -------- | -------- | ------- |
| ESM publicado? | ✔️ | ✔️ | ✔️ | ✔️ |
| package.json "module" | ❌ | ✔️ | ✔️ | ✔️ |
| webpack / rollup use esm 1 , 2 | ❌ | ✔️ | ✔️ | ✔️ |
| package.json "exports" | ❌ | ❌ | ✔️ | ✔️ |
| package.json "type" | ❌ | ❌ | ✔️ | ✔️ |
| NodeJS usa esm | ❌ | ❌ | ✔️ | ✔️ |
| Quebrando a mudança? | ❌ | ❌ | ❓ | ✔️ |
| A exportação padrão foi eliminada? | ❌ | ❌ | ❌ | ✔️ |
| Extensões de arquivo exigidas nas importações | ❌ | ❌ | ❌ | ❌ |
| extensões de arquivo mjs | ❌ | ❌ | ❌ | ❌ |

Acho que há um argumento válido para combinar a Fase 1 e a Fase 2 juntas, uma vez que a Fase 1 realmente visa apenas os entusiastas. No entanto, eu os divido porque acho que separar as fases dá a chance de implantar o ESM muito lentamente de uma forma que não interrompa imediatamente o CRA e todo o ecossistema, sem primeiro dar aos primeiros usuários a chance de relatar problemas e encontrar soluções .

@joeldenning qual seria o momento difícil das fases? é apenas baseado no tempo ou está relacionado a alguns pontos de verificação de tempo no ecossistema? Será que require('react') ainda funcionaria na Fase 3?

qual seria o momento difícil das fases? é apenas baseado no tempo ou está relacionado a alguns pontos de verificação de tempo no ecossistema?

O tempo para todas as fases é limitado apenas pelo trabalho a ser feito e pela programação de lançamento do React. Meu entendimento é que todas as coisas propostas são suportadas por bundlers e nodejs, com fallbacks apropriados ao usar versões mais antigas que não os suportam.

Requereria ('reagiria') ainda funcionaria na Fase 3?

Eu acho que sim. Consulte https://nodejs.org/api/esm.html#esm_dual_commonjs_es_module_packages e https://nodejs.org/api/esm.html#esm_package_entry_points. Claro, é bem possível que eu não conheça todos os casos esquivos de tudo, mas meu entendimento é que o caminho de transição que propus "funcionará em qualquer lugar". Talvez essas sejam últimas palavras famosas 😄

Um esclarecimento adicional, aqui está como estamos propondo a aparência do tarball publicado para o 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

E aqui está uma aproximação de como seria o package.json no final:

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

^ Isso não foi totalmente pensado e aperfeiçoado, mas estou compartilhando para dar um contexto concreto à proposta.

Portanto, o React sofre do perigo de pacote duplo porque tem estado (ganchos), portanto, esse estado teria que ser isolado com cuidado. Ele deve estar em um pequeno arquivo CJS que pode ser importado por ambas as entradas CJS e ESM ou talvez haja uma maneira de carregar .json e alterá-lo com esse estado de ambas as entradas (não tenho 100% de certeza sobre a segunda abordagem).

Também acho importante listar em sua tabela adicionando exportações nomeadas, o que já aconteceria na Fase 1.

Ainda não tenho 100% de certeza se não perdi alguns casos esquivos, este tópico é muito complexo. Ao mesmo tempo, a programação deve ser vinculada aos pontos de verificação do ecossistema - a Fase 3 só pode ser enviada quando exports tiver suporte suficiente em empacotadores, caso contrário, acho que isso pode levar a possíveis problemas.

O React sofre do perigo do pacote duplo porque tem estado (ganchos), então esse estado teria que ser isolado com cuidado.

Bom ponto, eu não tinha considerado isso ( mais informações ). Nesse caso, talvez sua sugestão de um arquivo compartilhado entre as compilações CJS / ESM seja útil. Ou talvez a versão do ESM não seja uma cópia completa do react, mas apenas a interface pública do ESM que chama o build CJS.

Também acho importante listar em sua tabela adicionando exportações nomeadas, o que já aconteceria na Fase 1.

Parece que já é o caso, porém, no código-fonte? Pelo que posso dizer, o código-fonte já exporta coisas como exportações nomeadas.

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

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

Ainda não tenho 100% de certeza se não perdi alguns casos esquivos, este tópico é muito complexo. Ao mesmo tempo, o cronograma deve ser vinculado aos pontos de verificação do ecossistema - a Fase 3 só pode ser enviada quando as exportações têm suporte suficiente nos empacotadores, caso contrário, acho que isso poderia levar a possíveis problemas.

Concordo sobre a Fase 3 - é por isso que coloquei um ponto de interrogação para saber se foi uma alteração significativa. Eu sei que adicionar exportações de package.json geralmente é uma mudança significativa para outros pacotes no ecossistema. E o assunto é definitivamente complexo. Uma coisa a notar é que a ordem da Fase 3 e 4 pode ser trocada, se desejado. Eu acho que aqueles que implementam cada fase teriam que fazer testes muito, muito detalhados de muitas versões de webpack, rollup, nodejs, etc. Não estou dizendo que o trabalho a ser feito é trivial - apenas estou dizendo que acho que há é provavelmente um caminho de transição aqui :)

Parece que já é o caso, porém, no código-fonte? Pelo que posso dizer, o código-fonte já exporta coisas como exportações nomeadas.

Ah - certo, neste caso deve haver uma linha de tabela para adicionar export default 😂, pois atualmente funciona na maioria dos empacotadores e é popular, mas adicionar uma entrada ESM verdadeira sem fornecer default seria quebrar esses usos.

neste caso, deve haver uma linha da tabela para adicionar o padrão de exportação

Sim, bom ponto. Eu vou adicionar isso. Eu acho que um PR fazendo isso "pareceria ruim", mas eu vejo isso apenas como uma aceitação da interface pública atual pelo que ela é, com planos de melhorar no futuro.

Devemos observar que assim que alguma versão for publicada, não há como voltar atrás e qualquer mudança semântica seria interrompida. Visto que não lançamos majors com muita frequência, devemos considerar como agrupar quaisquer alterações para que haja menos rotatividade.

Também há uma questão de como a divisão de desenvolvimento / produção seria tratada. E, de fato, a natureza stateful da construção do React é muito importante para preservar.

Devemos observar que assim que alguma versão for publicada, não há como voltar atrás e qualquer mudança semântica seria interrompida. Visto que não lançamos majors com muita frequência, devemos considerar como agrupar quaisquer alterações para que haja menos rotatividade.

Bons pontos. Minha ideia é adicionar um export default React explícito a uma compilação de ESM publicada no React 16. Acho que o PR pode levantar algumas sobrancelhas e pode ser controverso, já que esse não é o destino que foi decidido. No entanto, minha opinião é que podemos ter um ESM compilado no React 16 e, em seguida, remover export default em uma versão principal futura. Para mim, usar o react via import React from 'react'; é tão comum que exportar o default na construção do ESM é simplesmente "aceitar onde estamos". Uma versão principal futura iria removê-lo.

Também relacionado à minimização de alterações significativas - as fases 3 e 4 podem fazer parte da mesma versão principal. É possível que a fase 3 possa ser feita de uma maneira totalmente compatível com versões anteriores, caso em que também pode entrar furtivamente em uma versão 16. Mas esse é mais complicado e não sei o suficiente sobre ele para ter certeza disso.

Também há uma questão de como a divisão de desenvolvimento / produção seria tratada.

Isso é algo que eu esqueci. Não sei como fazer isso com ESM em ambos os bundlers e em NodeJS, mas vou fazer algumas pesquisas para ver o que é possível. Eu encontrei esta proposta morta , mas vou procurar nas vivas :)

E, de fato, a natureza stateful da construção do React é muito importante para preservar.

Concordou. Uma coisa a notar é que a natureza stateful da construção do React só precisa ser resolvida na Fase 3, não nas Fases 1 e 2. As opções que @Andarist e eu sugerimos funcionariam para resolvê-lo.

A abordagem mais fácil e compatível com versões anteriores por enquanto é adicionar um empacotador ESM simples para permitir importações nomeadas.

Eu ficaria feliz em fazer um PR para adicionar isso ao React, garantindo que a adição do campo "exportações" não seja uma alteração significativa (eu escrevi uma ferramenta, npx ls-exports , que torna essa determinação fácil). Não vai ajudar as pessoas que tentam usar o ESM sem um processo de construção, mas esse é um problema que os pacotes individuais não são capazes de resolver de qualquer maneira.

@gaearon isso seria útil? Ele poderia pousar no React 16 como um semver-menor.

A compilação do CJS continuaria a usar detecção de ambiente, como faz agora, de forma que o problema (difícil, não resolvido) no ESM não precise ser resolvido ainda.

No entanto, minha opinião é que podemos ter um ESM compilado no React 16 e, em seguida, remover o padrão de exportação em uma versão principal futura.

Outra preocupação é que aumentar o número de configurações sobrecarrega o ecossistema. Por exemplo, acho que se lançarmos uma compilação de ESM, ela não deve incluir algo que definitivamente iremos remover na próxima versão, como uma exportação padrão. Em outras palavras, não acho que faça muito sentido introduzir algo que está desaparecendo (exportações padrão) em algo que está sendo adicionado (construção ESM).

No entanto, minha opinião é que podemos ter um ESM compilado no React 16 e, em seguida, remover o padrão de exportação em uma versão principal futura.

Outra preocupação é que aumentar o número de configurações sobrecarrega o ecossistema. Por exemplo, acho que se lançarmos uma compilação de ESM, ela não deve incluir algo que definitivamente iremos remover na próxima versão, como uma exportação padrão. Em outras palavras, não acho que faça muito sentido introduzir algo que está desaparecendo (exportações padrão) em algo que está sendo adicionado (construção ESM).

Eu acho que depende de quanto tempo será o próximo lançamento. Se estamos falando de uma diferença de uma semana, isso parece razoável. No entanto, se estamos falando de meses / anos, então não vejo por que seria ruim divulgá-lo por tanto tempo

A preocupação nesse caso é que quanto mais tempo existe, mais pessoas dependem dele na forma atual. E, então, a introdução de uma alteração significativa tornaria mais difícil para eles atualizarem o React. Agora não há apenas uma migração ESM, mas várias, e algumas pessoas serão deixadas para trás porque saltaram muito cedo e depois não têm os recursos para outra migração. O que, no caso do ESM, se propagará por todo o ecossistema.

Acho que se lançarmos uma compilação ESM, ela não deve incluir algo que definitivamente iremos remover na próxima versão, como uma exportação padrão. Em outras palavras, não acho que faça muito sentido introduzir algo que está desaparecendo (exportações padrão) em algo que está sendo adicionado (construção ESM).

Não vejo import React from 'react' como algo recém-adicionado, mas sim uma aceitação da realidade atual. Mesmo que tenha sido não intencional e apenas um efeito colateral da interoperabilidade ESM / CJS agora obsoleta, ainda existem milhares (milhões?) De linhas de código que fazem isso. A alternativa (apenas exportar exportações nomeadas) diz aos usuários "publicamos uma compilação de ESM em uma versão secundária, mas você não pode usá-la sem alterar todo o seu código", o que para mim é mais confuso para os usuários do que ver "exportação padrão removida" nas notas de lançamento de uma versão principal.

Estou curioso - como o ESM com uma exportação padrão pode ser adicionado de forma compatível com versões anteriores? Isso já aconteceu antes (por exemplo, https://github.com/facebook/react/pull/18187 que também contém links para questões relacionadas). O problema é com webpack CJS <-> ESM interop, onde se você tiver o código CJS fazendo require('react') o webpack retornará na presença de um ESM react com uma exportação padrão, é um objeto com default propriedade (o que significa que agora requer require('react').default ) independentemente do CJS react . Mas talvez se você também exportar o nome, não seja um problema? Acho que @ TrySound já teve problemas semelhantes em outros pacotes antes.

Mas talvez se você também exportar o nome, não seja um problema?

Sim, é nessa abordagem que estou pensando. Veja as últimas 40 linhas de https://unpkg.com/browse/@esm-bundle/react @ 16.13.1 / esm / react.development.js - é uma versão não oficial do React que faz exatamente isso e é usada por vários organizações como um substituto imediato para o React oficial. Sem alterações significativas.

Mas talvez se você também exportar o nome, não seja um problema?

Sim, é nessa abordagem que estou pensando. Veja as últimas 40 linhas de https://unpkg.com/browse/@esm-bundle/react @ 16.13.1 / esm / react.development.js - é uma versão não oficial do React que faz exatamente isso e é usada por vários organizações como um substituto imediato para o React oficial. Sem alterações significativas.

Mas você está consumindo do código CJS ou ESM? Porque são os problemas de interoperabilidade do CJS <-> ESM que podem ser muito surpreendentes.

@gaearon para ser claro; faz sentido não ter exportação padrão no wrapper ESM que estou propondo; qualquer um que fizer ESM nativo faria import * as React from 'react' para contornar isso. No entanto, é justo que qualquer pessoa que fizer isso agora considere uma alteração significativa não ter a exportação padrão de repente, então teria que esperar até a v17 se você não quisesse adicionar o padrão agora.

Mas você está consumindo do código CJS ou ESM? Porque são os problemas de interoperabilidade do CJS <-> ESM que podem ser muito surpreendentes.

Importei-o com sucesso no webpack dos arquivos CJS e ESM.

qualquer um que fizer ESM nativo importaria * como React de 'react' para contornar isso. No entanto, é justo que qualquer pessoa que fizer isso agora considere uma alteração significativa não ter a exportação padrão de repente, então teria que esperar até a v17 se você não quisesse adicionar o padrão agora.

Concordou. Se estiver começando do zero, não haverá necessidade de ter a exportação padrão. No entanto, sem adicionar uma exportação padrão, não é possível implementar a Fase 2 sem que seja uma alteração significativa. Eu pessoalmente ficaria bem em fazer a Fase 1 no react 16 e as Fases 2-4 no React 17+, embora minha preferência seja fazer a Fase 1, 2 e talvez até 3 (com a ajuda da ferramenta de verificação de exportação do @ljharb ) no React 16 sem alterar as alterações. A razão é que a fase 2 é aquela em que a maioria dos usuários começa a usar o pacote ESM, enquanto a fase 1 é principalmente para os primeiros usuários / entusiastas.

Continuando a partir desta proposta . Esta parece ser uma proposta para uma mudança para um pacote duplo com CJS completo e fonte ESM lidando com o perigo de pacote duplo, isolando o estado em outro lugar ( abordagem 2 ). Ao contrário de usar um invólucro ESM como meu RFC anterior ( abordagem 1 ).

Supondo isso, tenho algumas notas de coisas que ainda não foram mencionadas.

  • Você não pode ter um pacote Node.js em que os arquivos ESM e CommonJS usem .js . Se você definir "type": "module" , todos os arquivos CommonJS precisarão usar a extensão de arquivo .cjs .
  • O perigo de pacote duplo de estado e igualdade não é o único problema com o CJS e o ESM. Ter potencialmente 2 versões do mesmo pacote carregadas também aumenta a área de cobertura da memória do React nesses casos, e o React não é uma biblioteca pequena. Isso não é um obstáculo, mas vale a pena ter em mente.
  • Eu vejo potencial para um perigo de pacote duplo além do estado interno do React. Por exemplo, se a implementação da classe Component não faz parte do código CJS onde compartilhamos o estado e, em vez disso, faz parte dos pacotes CJS / ESM, então há o risco de instanceof Component verificações em várias bibliotecas quebra.

Você não pode ter um pacote Node.js onde os arquivos ESM e CommonJS usam .js. Se você definir "type": "module", todos os arquivos CommonJS precisarão usar a extensão de arquivo .cjs.

Isso é verdade para NodeJS (mas não para bundlers), portanto, os arquivos no diretório cjs teriam que terminar com .cjs .

Ter potencialmente 2 versões do mesmo pacote carregadas também aumenta a pegada de memória do React nesses casos

Vejo como isso aumenta o tamanho do tarball publicado em npm e, portanto, o tamanho geral no disco. Mas não vejo como isso afeta a memória. Pelo que eu sei, bundlers e NodeJS não trazem código para a memória que não foi carregado por meio de import / require() . Você poderia esclarecer como a pegada de memória mudaria?

Por exemplo, se a implementação da classe Component não fizer parte do código CJS onde compartilhamos o estado e, em vez disso, fizer parte dos pacotes CJS / ESM, haverá risco de quebra de instância de verificações de componente em várias bibliotecas.

Uma solução proposta ( 1 , 2 ) é fazer com que a implementação NodeJS esm seja simplesmente uma interface ESM que chama o código CJS. Dessa forma, há apenas uma definição de Component que é usada no Node. A Fase 1 e a Fase 2 não mudariam o que é executado no NodeJS, portanto, isso se aplicaria apenas à Fase 3.

Como nós (webpack) adicionamos recentemente suporte de campo exports ao webpack 5, quero dar meus 2 centavos a este tópico:

  • Este documento work-in-process tem muitas informações sobre o campo exports relação ao webpack, mas também Node.js e em geral: https://gist.github.com/sokra/e032a0f17c1721c71cfced6f14516c62
  • Estes são os pontos-chave em comparação com Node.js:

    • webpack 5 também adiciona as condições development e production , que são muito úteis para reagir. ( process.env.NODE_ENV , embora ainda seja compatível, deve ser evitado para código de front-end em geral, é específico do Node.js)

    • webpack (e outros bundlers) suporta require("esm") , o que permite evitar o problema de estado duplo sempre usando ESM (mesmo para require() ). webpack introduziu uma condição especial para isso: module . Para esta versão CommonJs e ESM deve exportar a mesma interface. Atualmente, não há outra coisa que tenha o problema de estado duplo do que o Node.js. Não espero que veremos algo no futuro, já que este é principalmente um problema de compatibilidade retroativa.

      Para compatibilidade máxima, eu recomendaria o seguinte:

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

Permite usar .js como extensão neste diretório. Alternativamente, .mjs pode ser usado, mas isso pode ter efeitos colaterais potenciais quando as ferramentas verificam a extensão. Portanto, .js para estar seguro.

{
    "type": "module"
}

esm / wrapper.js

Este wrapper é necessário para Node.js para evitar o problema de estado duplo.

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

cjs / index.js

Isso é usado pelo Node.js quando as condições development e production não são suportadas.

'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 (esm / wrapper.production.min.js semelhante)

Esses wrappers são necessários para Node.js para evitar o problema de estado duplo.
Eles são usados ​​apenas quando o Node.js adiciona as condições development e production .

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

index.js

Para compatibilidade com versões anteriores.

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

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

Isto é usado por ferramentas que suporta o exports campo, module condição e production / development condições.

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

Resultados

  • webpack 5: ./esm/index.development.js ou ./esm/index.production.min.js
  • browserify: ./cjs/index.js
  • webpack 4 de .mjs: ./cjs/index.js
  • outros empacotadores: ./esm/wrapper.js
  • Node.js (ESM): ./cjs/index.js (exigir) ou ./esm/wrapper.js (importar)
  • Node.js (antigo): ./cjs/index.js
  • Node.js (ESM + dev / prod): ./esm/wrapper.development.js ou ./esm/wrapper.production.min.js para importar, ./cjs/index.development.js ou ./cjs/index.production.min.js para exigir

Notas

Não há esm/index.js pois a escolha condicional de uma versão não é possível no ESM sem grandes compensações.
As ferramentas só podem se beneficiar totalmente do ESM de reação quando suportam o campo exports , a condição module (por causa do problema de estado duplo) e production / development conditions (por causa do problema de importação condicional).

As ferramentas podem se beneficiar parcialmente do ESM de reação quando suportam o campo module ou o campo exports .

import { useState } from "react" ou import * as React from "react" são tecnicamente ilegais desde que long react seja um módulo CommonJs.
A maioria das ferramentas ainda oferece suporte para compatibilidade com versões anteriores, mas algumas não, por exemplo, Node.js
Portanto, atualmente, a única maneira de usar o react que é válido em qualquer lugar é: import React from "react" .
Essa forma deve permanecer suportada, caso contrário, haveria casos (por exemplo, Node.js 14) em que não há sintaxe válida para reagir agora e reagir após a adição do ESM.

O Node.js rejeitou a adição de uma condição development / production para exports por enquanto.
Isso é triste, e ainda espero o melhor que eles eventualmente adicionem isso.
É por isso que o suporte para isso é preparado no campo exports acima.

@sokra grande repartição, muito útil, obrigado!

Uma pequena pergunta:

Node.js rejeitou a adição de uma condição de desenvolvimento / produção para exportações por enquanto.

meu entendimento é que ainda está sendo trabalhado? https://github.com/nodejs/node/pull/33171 mas talvez eu esteja entendendo mal que PR

[editar] o PR acima ao qual vinculei foi substituído por https://github.com/nodejs/node/pull/34637

[edit2] e agora foi fundido em nodejs

Obrigado @sokra , são sugestões muito úteis.

Aqui estão as opções que vejo. Parece que todos eles são tecnicamente possíveis, e que a decisão é mais uma estratégia do que uma implementação técnica:

Opção 1

Adicione export default React a uma compilação do React 17 ESM e remova-o assim que o suporte CJS para import React from 'react' for cancelado (talvez no React 18?).

opção 2

Não adicione export default React e crie uma construção do React 17 ESM apenas com exportações nomeadas.

Opção 3

Não publique uma construção do React 17 ESM. (😢) Espere até que o suporte import React from 'react'; seja cancelado antes de criar uma compilação de ESM.

Comparação

| | Opção 1 | Opção 2 | Opção 3 |
| - | -------- | -------- | -------- |
| Compilação ESM não referenciada | v17 | v17 | v18 + |
| package.json "módulo" (árvore balançando por padrão) | v17 | v18 + | v18 + |
| package.json "tipo" / "exportações" (NodeJS usa ESM) | v18 + 1 | v18 + | v18 + |

  1. Pode ser possível implementar o tipo / exportações de package.json de uma forma totalmente compatível com versões anteriores, caso em que poderia fazer parte do React 17 se a Opção 1 for escolhida.

Minha preferência é pela Opção 1, conforme expliquei acima. No entanto, a Opção 2 também é muito emocionante para mim. A opção 3 é, obviamente, menos empolgante. Pelo que reuni nesta edição do github, temos a experiência técnica para fazer qualquer um desses acontecer (e provavelmente até mesmo o trabalho!).

A reação inicial a esse problema foi: por que esse problema está aberto mesmo depois de 3 anos ? Depois de ler parte dele, faz sentido porque está demorando tanto. Manter uma biblioteca como o React é uma tarefa enorme. Então 🙇🏻

Dadas as notícias recentes com o React 17, atualizei meu comentário anterior para fazer referência ao React 17 em vez de 16 para quaisquer planos futuros.

Agradeço o feedback das pessoas sobre qual das três opções acima é a preferida.

Acho que podemos adicionar o campo exports em package.json no React 17, provavelmente poderíamos transportá-lo para versões anteriores também:

{
  "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"
    },
    "./": "./"
  },
}

Precisaríamos de novos pacotes ESM, embora isso não seja muito difícil de adicionar com o rollup.

  • Os pacotes de ./esm/react.development.mjs e ./esm/react.production.mjs devem conter process.env.NODE_ENV cheques:

    • a condição é resolvida no momento da importação / pacote por meio do campo exports .

    • process é uma API de nó, não faz sentido em um ambiente de navegador e não é compatível com _default_ por webpack 5, por exemplo.

  • O ./esm/react.node.mjs manteria os cheques de process.env.NODE_ENV .
  • AFAIK apenas o webpack 5 e o nó suportam o campo exports agora.

Eu acho que isso é bastante seguro para adicionar, WDYT?

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

O "./": "./" torna seguro, sim, mas também evita qualquer encapsulamento, então você deseja removê-lo assim que tiver um semver-major.

FWIW babel mostra a nova importação de tempo de execução jsx como

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

mas se você carregar um módulo como esse no Node, ele reclamará da falta de extensão do arquivo:

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

Adicionar exports deve consertar 🤔

@nstepien fornecer um mapa exports completo como você mostrou em seu post anterior não é uma opção do que eu acredito. O que o nó implementa em relação à interoperabilidade cjs e outras coisas não funciona bem com o ecossistema existente. O risco de pacote duplo é real - especialmente para pacotes como o Reac, que requer uma única cópia deles.

Um mapa exports com arquivos somente commonjs poderia potencialmente ser adicionado sem quebrar nada, mas também teria que ser feito com muito cuidado e com testes e2e apropriados para isso (dada a complexidade das coisas para acertar)

@Andarist funciona bem, e não é diferente no caso do

Se todas as dependências, transitivas ou não, do React estão sob o controle do React (que neste caso estão) e os pontos de entrada do ESM de todos eles estão apenas reexportando o conteúdo CJS, então você - talvez isso seja possível neste caso específico.

Ainda há todo o drama de como deve ser a forma real da entrada do ESM (nomeada, padrão, ambas):

  • apenas nomeado: não é realmente compatível com versões anteriores porque muito do código lá fora está usando import React from 'react' , que também é a única maneira de realmente importar React no nó agora ao usar ESM
  • somente padrão: não é realmente compatível com versões anteriores porque muito do código que existe usa import * as React from 'react' , isso muitas vezes é promovido por verificadores de tipo e outras ferramentas
  • ambos: a única maneira de torná-lo totalmente compatível com versões anteriores, para que possa funcionar com todos os estilos de carregamento atuais e ao misturar módulos ESM e CJS na árvore de dependência

Eu sempre me esqueço da possibilidade de invólucros ESM, pois eles parecem um trapaceiro, mas também porque essa técnica funciona se você controlar todas as suas dependências e não pode realmente ser usada como uma estratégia universal que "simplesmente funcionaria" 😢

Receio que a única estratégia universal para fornecer ambos é, na verdade, ter todo o seu código real em CJS e escrever wrappers ESM em torno dele para fornecer exportações nomeadas. O código que não tem estado nem depende da identidade pode ser escrito nativamente em ambos, mas isso é um subconjunto, uma advertência.

jsx-runtime não tem estado, certo? Deve ser seguro enviar ambos os esm / cjs sem embalagem para ele.

Devo registrar um problema separado para importar react/jsx-runtime no nó esm?

Para pacotes de front-end, um desafio extra se junta ao risco de estado duplo: vamos chamá-lo de risco de pacote duplo.

Ao exportar versões ESM e CJS e o pacote é usado por meio de require e import , ambas as versões seriam agrupadas e dobrariam o tamanho efetivo do pacote para o pacote.

@sokra isso é possível na prática? Por exemplo, com rollup, eu presumiria que, como os módulos cjs são transformados em módulos esm, ele preferiria importar os módulos esm sempre que disponíveis.

É possível na prática para bundlers seguindo a semântica de nó - como o webpack 5. É importante combinar a semântica porque, de outra forma, você teria resultados diferentes ao executar no nó e ao executar um código de bundles.

No entanto, o webpack 5 lida com uma condição "module" que é usada para desduplicar esm / cjs (aqueles provenientes do mapa de exportações).

Se considerarmos a proposta de @ljharb, isso não é realmente relevante porque ele propõe que o arquivo esm poderia ser apenas algumas linhas de código de wrapper que apenas reexportariam o arquivo cjs.

Com um invólucro ESM fino, não há risco adicional, com ou sem bundlers - apenas o normal que os departamentos de pares evitam sobre enganos no gráfico.

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