Publique, envie e instale o JavaScript moderno para aplicativos mais rápidos

Melhore o desempenho ativando as dependências e saídas modernas de JavaScript.

Mais de 90% dos navegadores são capazes de executar JavaScript moderno, mas a A prevalência do JavaScript legado continua sendo uma grande fonte de problemas de desempenho na Web.

JavaScript moderno

O JavaScript moderno não é caracterizado como código escrito em um ECMAScript específico específica, mas sim na sintaxe com suporte em todos os modelos navegadores padrão. Navegadores da Web modernos, como Chrome, Edge, Firefox e Safari mais de 90% do mercado de navegadores, e navegadores diferentes que dependem dos mesmos mecanismos de renderização subjacentes formam uma mais 5%. Isso significa que 95% do tráfego global da web vem de navegadores com suporte aos recursos de linguagem JavaScript mais usados nos últimos 10 anos, incluindo:

  • Aulas (ES2015)
  • Funções de seta (ES2015)
  • Geradores (ES2015)
  • Escopo de bloco (ES2015)
  • Desestruturação (ES2015)
  • Parâmetros de descanso e propagação (ES2015)
  • Abreviação do objeto (ES2015)
  • Async/await (ES2017)

Os recursos em versões mais recentes da especificação de linguagem geralmente têm menos suporte consistente nos navegadores modernos. Por exemplo, muitos ES2020 e ES2021 têm suporte somente em 70% do mercado de navegadores, ainda que a maioria navegadores, mas não o suficiente para que seja seguro confiar nesses recursos diretamente. Isso significa que, embora "moderno" O JavaScript é um destino móvel, o ES2017 tem a a mais ampla variedade de compatibilidades incluindo a maioria dos recursos de sintaxe moderna usados com frequência. Em outras palavras, o ES2017 é o mais próximo da sintaxe moderna atual.

JavaScript legado

O JavaScript legado é o código que evita especificamente o uso de toda a linguagem acima atributos de machine learning. A maioria dos desenvolvedores escreve o código-fonte usando uma sintaxe moderna, mas compilar tudo na sintaxe legada para aumentar o suporte ao navegador. Compilação à sintaxe legada aumenta o suporte ao navegador, mas o efeito geralmente é menores do que nós imaginamos. Em muitos casos, o suporte aumenta de cerca de 95% para 98%, com um custo significativo:

  • Em geral, o JavaScript legado é cerca de 20% maior e mais lento códigos modernos equivalentes. Frequentemente, falhas de ferramentas e configurações incorretas ampliar ainda mais essa lacuna.

  • As bibliotecas instaladas representam até 90% da produção típica Código JavaScript. O código da biblioteca incorre em um JavaScript legado ainda mais sobrecarga devido ao polyfill e à duplicação auxiliar que poderiam ser evitadas com a publicação de códigos modernos.

JavaScript moderno no npm

Recentemente, o Node.js padronizou um campo "exports" para definir Pontos de entrada de um pacote:

{
  "exports": "./index.js"
}

Os módulos referenciados pelo campo "exports" implicam uma versão do nó de pelo menos 12.8, que oferece suporte a ES2019. Isso significa que qualquer módulo referenciado usando o O campo "exports" pode ser escrito em JavaScript moderno. Os consumidores do pacote devem presume que os módulos com um campo "exports" contêm código moderno e transcompilam se necessários.

Somente moderno

Se quiser publicar um pacote com código moderno e deixar com o ao consumidor lidar com a transcompilação quando a usar como dependência. Use apenas o "exports".

{
  "name": "foo",
  "exports": "./modern.js"
}

Moderno com substituto legado

Use o campo "exports" com "main" para publicar seu pacote usando código moderno, mas também incluir um substituto ES5 + CommonJS para navegadores padrão.

{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs"
}

Moderno com substituto legado e otimizações de bundler de ESM

Além de definir um ponto de entrada CommonJS substituto, o campo "module" pode ser usada para apontar para um pacote substituto legado semelhante, mas que usa A sintaxe do módulo JavaScript (import e export).

{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs",
  "module": "./module.js"
}

Muitos bundlers, como webpack e Rollup, dependem desse campo para aproveitar as vantagens de recursos do módulo e permitir tree shaking. Este ainda é um pacote legado que não contém nenhum código moderno além de import/export. Portanto, use essa abordagem para enviar códigos modernos com uma substituto legado que ainda está otimizado para agrupamento.

JavaScript moderno em aplicativos

As dependências de terceiros compõem a grande maioria dos recursos o código JavaScript em aplicativos da Web. Embora as dependências de npm publicado como sintaxe ES5 legada, essa não é mais uma suposição segura, e riscos de atualizações de dependência que corrompem o suporte ao navegador em seu aplicativo.

Com um número cada vez maior de pacotes npm migrando para o JavaScript moderno, é é importante garantir que as ferramentas de criação estejam configuradas para lidar com eles. Há um uma grande chance de alguns dos pacotes npm de que você depende já usarem recursos de linguagem natural. Há várias opções disponíveis para usar códigos modernos do npm sem corromper seu aplicativo em navegadores mais antigos, mas a ideia é que o sistema de build transcompile as dependências para a mesma sintaxe como seu código-fonte.

webpack

A partir do webpack 5, é possível configurar qual sintaxe o webpack usará ao gerar códigos para pacotes e módulos. Isso não transcompila seu o código ou as dependências, isso só afeta o "cola" gerado pelo webpack. Para especificar o destino do suporte ao navegador, adicione um configuração da Browserslist ao seu projeto ou faça isso diretamente na configuração do webpack:

module.exports = {
  target: ['web', 'es2017'],
};

Também é possível configurar o webpack para gerar pacotes otimizados que omitir funções de wrapper desnecessárias ao direcionar para módulos ES modernos. de nuvem. Isso também configura o webpack para carregar pacotes divididos com base no código usando <script type="module">:

module.exports = {
  target: ['web', 'es2017'],
  output: {
    module: true,
  },
  experiments: {
    outputModule: true,
  },
};

Existem vários plug-ins de webpack disponíveis que possibilitam compilar e entregar JavaScript moderno, sem deixar de oferecer suporte a navegadores legados, como o do Optimize e o BabelEsmPlugin.

Plug-in do Optimize

O plug-in do Optimize é um webpack. plug-in que transforma o código empacotado final de JavaScript moderno em legado. em vez de cada arquivo de origem. É uma configuração independente que permite sua configuração do webpack para que tudo seja JavaScript moderno, sem ramificação especial para múltiplas saídas ou sintaxes.

Como o plug-in do Optimize opera em pacotes, e não em módulos individuais, ele processa igualmente o código do aplicativo e as dependências; Assim, seguro usar dependências modernas de JavaScript no npm, porque o código delas será empacotadas e transcompiladas para a sintaxe correta. Também pode ser mais rápido do que soluções tradicionais que envolvem duas etapas de compilação e ainda geram pacotes separados para navegadores modernos e legados. Os dois conjuntos de pacotes são projetada para ser carregada usando o padrão módulo/nomódulo.

// webpack.config.js
const OptimizePlugin = require('optimize-plugin');

module.exports = {
  // ...
  plugins: [new OptimizePlugin()],
};

O Optimize Plugin pode ser mais rápido e eficiente do que o webpack personalizado que geralmente agrupam códigos modernos e legados separadamente. Ela também cuida da execução do Babel para você e reduz pacotes usando Terser com configurações ideais separadas para as saídas modernas e legadas. Por fim, os polyfills necessários para os pacotes legados são extraídos para um script dedicado para que nunca duplicados ou carregados desnecessariamente em navegadores mais recentes.

Comparação: transcompilação de módulos de origem duas vezes versus transcompilação de pacotes gerados.

BabelEsmPlugin

O BabelEsmPlugin é um webpack. plug-in que funciona junto com @babel/preset-env para gerar versões modernas de pacotes existentes e enviar códigos menos transcompilados para navegadores mais recentes. É a solução pronta para uso mais conhecida module/nomodule, usado pelo Next.js e CLI Preact:

// webpack.config.js
const BabelEsmPlugin = require('babel-esm-plugin');

module.exports = {
  //...
  module: {
    rules: [
      // your existing babel-loader configuration:
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
  plugins: [new BabelEsmPlugin()],
};

BabelEsmPlugin oferece suporte a uma ampla variedade de configurações de webpack, porque executa dois builds em grande parte separados do seu aplicativo. A compilação duplicada pode demorar um pouco mais de tempo para aplicativos grandes. No entanto, essa técnica permite BabelEsmPlugin para se integrar perfeitamente às configurações atuais do webpack. sendo uma das opções mais convenientes.

Configurar o babel-loader para transpilar node_modules

Se você estiver usando babel-loader sem um dos dois plug-ins anteriores, há uma etapa importante necessária para consumir o npm moderno em JavaScript módulos. A definição de duas configurações de babel-loader separadas possibilita para compilar automaticamente os recursos de linguagem modernos encontrados em node_modules para ES2017 sem deixar de transpilar seu próprio código próprio com o Babel plug-ins e predefinições definidas na configuração do projeto. Isso não geram pacotes modernos e legados para uma configuração de módulo/nenhum módulo, mas possibilitam a instalação e o uso de pacotes npm que contêm JavaScript moderno sem corromper navegadores antigos.

webpack-plugin-modern-npm usa essa técnica para compilar dependências de npm que têm um campo "exports" no package.json, já que eles podem conter sintaxe moderna:

// webpack.config.js
const ModernNpmPlugin = require('webpack-plugin-modern-npm');

module.exports = {
  plugins: [
    // auto-transpile modern stuff found in node_modules
    new ModernNpmPlugin(),
  ],
};

Como alternativa, é possível implementar a técnica manualmente no seu webpack da configuração, verificando um campo "exports" em package.json do módulos assim que forem resolvidos. Omitindo o armazenamento em cache para agilizar, um padrão pode ser assim:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      // Transpile for your own first-party code:
      {
        test: /\.js$/i,
        loader: 'babel-loader',
        exclude: /node_modules/,
      },
      // Transpile modern dependencies:
      {
        test: /\.js$/i,
        include(file) {
          let dir = file.match(/^.*[/\\]node_modules[/\\](@.*?[/\\])?.*?[/\\]/);
          try {
            return dir && !!require(dir[0] + 'package.json').exports;
          } catch (e) {}
        },
        use: {
          loader: 'babel-loader',
          options: {
            babelrc: false,
            configFile: false,
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
};

Ao usar essa abordagem, você precisa garantir que a sintaxe moderna seja compatível com o minificador. Ambos Terser e uglify-es a opção de especificar {ecma: 2017} para preservar e, em alguns casos, gerar a sintaxe ES2017 durante a compactação e formatação.

Consolidação

A Rollup tem suporte integrado para gerar vários conjuntos de pacotes como parte do um único build e gera código moderno por padrão. Como resultado, a consolidação pode ser configurados para gerar pacotes modernos e legados com os plug-ins oficiais; que você provavelmente já usa.

@rollup/plugin-babel

Se você usar o Rollup, o Método getBabelOutputPlugin() (fornecidos pelo plug-in oficial do Babel) transforma o código em pacotes gerados em vez de módulos de origem individuais. A Rollup tem suporte integrado para gerar vários conjuntos de pacotes como parte do um único build, cada um com plug-ins próprios. Você pode usar isso para produzir pacotes diferentes para as versões moderna e legada, transmitindo cada um por uma Configuração do plug-in de saída do Babel:

// rollup.config.js
import {getBabelOutputPlugin} from '@rollup/plugin-babel';

export default {
  input: 'src/index.js',
  output: [
    // modern bundles:
    {
      format: 'es',
      plugins: [
        getBabelOutputPlugin({
          presets: [
            [
              '@babel/preset-env',
              {
                targets: {esmodules: true},
                bugfixes: true,
                loose: true,
              },
            ],
          ],
        }),
      ],
    },
    // legacy (ES5) bundles:
    {
      format: 'amd',
      entryFileNames: '[name].legacy.js',
      chunkFileNames: '[name]-[hash].legacy.js',
      plugins: [
        getBabelOutputPlugin({
          presets: ['@babel/preset-env'],
        }),
      ],
    },
  ],
};

Outras ferramentas de build

O Rollup e o webpack são altamente configuráveis, o que geralmente significa que cada projeto precisa atualizar a configuração para ativar a sintaxe moderna do JavaScript nas dependências. Há também ferramentas de build de nível superior que favorecem a convenção e os padrões, configuração, como Parcel, Snowpack, Vite e WMR. A maioria dessas ferramentas presumem que as dependências npm podem conter sintaxe moderna e as transpilam para os níveis de sintaxe apropriados ao criar para produção.

Além dos plug-ins dedicados para webpack e Rollup, JavaScript moderno pacotes com substitutos legados podem ser adicionados a qualquer projeto usando devolution (link em inglês). A evolução é uma ferramenta autônoma que transforma a saída de um sistema de build para produzir versões legadas Variantes JavaScript, permitindo que empacotamento e transformações assumam uma destino de saída.