Neste codelab, melhore o desempenho deste aplicativo simples que permite que os usuários avaliem gatos aleatórios. Aprenda a otimizar o pacote JavaScript minimizando a quantidade de código transcompilado.
No app de exemplo, é possível selecionar uma palavra ou emoji para transmitir o quanto você gosta de cada gato. Quando você clica em um botão, o app mostra o valor do botão abaixo da imagem do gato atual.
Medir
É sempre recomendável começar inspecionando um site antes de adicionar otimizações:
- Para visualizar o site, pressione View App. Em seguida, pressione Fullscreen .
- Pressione "Control+Shift+J" (ou "Command+Option+J" no Mac) para abrir as Ferramentas do desenvolvedor.
- Clique na guia Rede.
- Marque a caixa de seleção Desativar cache.
- Atualize o app.
Mais de 80 KB são usados para este aplicativo. É hora de descobrir se partes do pacote não estão sendo usadas:
Pressione
Control+Shift+P
(ouCommand+Shift+P
no Mac) para abrir o menu Command.Digite
Show Coverage
e pressioneEnter
para mostrar a guia Cobertura.Na guia Cobertura, clique em Recarregar para recarregar o aplicativo enquanto captura a cobertura.
Confira quanto código foi usado em comparação com a quantidade carregada para o pacote principal:
Mais da metade do pacote (44 KB) nem é utilizada. Isso ocorre porque grande parte do código consiste em polyfills para garantir que o aplicativo funcione em navegadores mais antigos.
Usar @babel/preset-env
A sintaxe da linguagem JavaScript está em conformidade com um padrão conhecido como ECMAScript ou ECMA-262. Novas versões da especificação são lançadas todos os anos e incluem novos recursos que passaram pelo processo de proposta. Cada navegador principal está sempre em um estágio diferente de suporte a esses recursos.
Os seguintes recursos do ES2015 são usados no aplicativo:
O seguinte recurso do ES2017 também é usado:
Confira o código-fonte em src/index.js
para saber como tudo isso
é usado.
Todos esses recursos são compatíveis com a versão mais recente do Chrome, mas e outros navegadores que não são? O Babel, que é incluído no aplicativo, é a biblioteca mais usada para compilar códigos que contêm sintaxe mais recente em código que navegadores e ambientes mais antigos podem entender. Isso acontece de duas maneiras:
- Os polyfills são incluídos para emular funções ES2015+ mais recentes, de modo que as APIs
possam ser usadas mesmo que não tenham suporte do navegador. Confira um exemplo de
polyfill
do método
Array.includes
. - Os plug-ins são usados para transformar o código ES2015 (ou mais recente) em uma sintaxe ES5 mais antiga. Como essas são mudanças relacionadas à sintaxe (como funções de seta), elas não podem ser emuladas com polyfills.
Consulte package.json
para saber quais bibliotecas do Babel estão incluídas:
"dependencies": {
"@babel/polyfill": "^7.0.0"
},
"devDependencies": {
//...
"babel-loader": "^8.0.2",
"@babel/core": "^7.1.0",
"@babel/preset-env": "^7.1.0",
//...
}
@babel/core
é o compilador principal do Babel. Com isso, todas as configurações do Babel são definidas em um.babelrc
na raiz do projeto.babel-loader
inclui o Babel no processo de build do webpack.
Agora, observe webpack.config.js
para ver como babel-loader
é incluído como uma
regra:
module: { rules: [ //... { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } ] },
- O
@babel/polyfill
fornece todos os polyfills necessários para recursos ECMAScript mais recentes para que eles possam funcionar em ambientes que não oferecem suporte a eles. Ele já foi importado na parte de cima desrc/index.js.
import "./style.css";
import "@babel/polyfill";
@babel/preset-env
identifica quais transformações e polyfills são necessários para todos os navegadores ou ambientes escolhidos como destinos.
Confira o arquivo de configurações do Babel, .babelrc
, para saber como ele é
incluído:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions"
}
]
]
}
Esta é uma configuração do Babel e do webpack. Saiba como incluir o Babel no seu aplicativo se você usa um bundler de módulos diferente do webpack.
O atributo targets
em .babelrc
identifica quais navegadores estão sendo segmentados. @babel/preset-env
se integra ao browserslist, o que significa que você pode encontrar uma lista completa de consultas
compatíveis que podem ser usadas neste campo na
documentação do browserslist.
O valor "last 2 versions"
transpila o código no aplicativo para as
duas últimas versões de cada navegador.
Depuração
Para ter uma visão completa de todos os destinos do Babel do navegador e de todas as
transformações e polyfills incluídos, adicione um campo debug
a .babelrc:
.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
}
]
]
}
- Clique em Ferramentas.
- Clique em Registros.
Atualize o aplicativo e confira os registros de status do Glitch na parte de baixo do editor.
Navegadores segmentados
O Babel registra vários detalhes no console sobre o processo de compilação, incluindo todos os ambientes de destino para os quais o código foi compilado.
Observe como os navegadores descontinuados, como o Internet Explorer, estão incluídos nesta lista. Isso é um problema porque os navegadores sem suporte não terão recursos mais recentes adicionados, e o Babel continuará transpilando sintaxe específica para eles. Isso aumenta desnecessariamente o tamanho do pacote se os usuários não estiverem usando esse navegador para acessar seu site.
O Babel também registra uma lista de plug-ins de transformação usados:
Essa lista é bem longa. Esses são todos os plug-ins que o Babel precisa usar para transformar qualquer sintaxe ES2015+ em uma sintaxe mais antiga para todos os navegadores de destino.
No entanto, o Babel não mostra nenhum polyfill específico que é usado:
Isso acontece porque o @babel/polyfill
inteiro está sendo importado diretamente.
Carregar polyfills individualmente
Por padrão, o Babel inclui todos os polyfills necessários para um ambiente ES2015+ completo quando
@babel/polyfill
é importado para um arquivo. Para importar polyfills específicos necessários para
os navegadores de destino, adicione um useBuiltIns: 'entry'
à configuração.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
"useBuiltIns": "entry"
}
]
]
}
Recarregue o aplicativo. Agora você pode conferir todos os polyfills específicos incluídos:
Embora apenas os polyfills necessários para "last 2 versions"
sejam incluídos agora, a lista ainda é muito longa. Isso acontece porque
os polyfills necessários para os navegadores de destino para cada recurso mais recente ainda são incluídos. Mude o valor do atributo para usage
para incluir apenas os necessários para os recursos que estão sendo usados no código.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true,
"useBuiltIns": "entry"
"useBuiltIns": "usage"
}
]
]
}
Com isso, os polyfills são incluídos automaticamente quando necessário.
Isso significa que você pode remover a importação @babel/polyfill
em src/index.js.
import "./style.css";
import "@babel/polyfill";
Agora, apenas os polyfills necessários para o aplicativo são incluídos.
O tamanho do pacote de aplicativos é reduzido significativamente.
Como restringir a lista de navegadores compatíveis
O número de destinos de navegador incluídos ainda é bastante grande, e poucos usuários usam navegadores descontinuados, como o Internet Explorer. Atualize as configurações para o seguinte:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"targets": [">0.25%", "not ie 11"],
"debug": true,
"useBuiltIns": "usage",
}
]
]
}
Confira os detalhes do pacote buscado.
Como o aplicativo é muito pequeno, não há muita diferença com
essas mudanças. No entanto, a abordagem recomendada é usar uma porcentagem de participação de mercado do navegador (como
">0.25%"
) e excluir navegadores específicos que você tem certeza de que os
usuários não estão usando. Leia o artigo
"Last 2 versions" considered harmful
de James Kyle para saber mais sobre o assunto.
Use <script type="module">
Ainda há espaço para melhorar. Embora um número de polyfills não usados tenha sido removido, muitos estão sendo enviados e não são necessários para alguns navegadores. Ao usar módulos, a sintaxe mais recente pode ser escrita e enviada diretamente aos navegadores sem o uso de polyfills desnecessários.
Os módulos JavaScript são um recurso relativamente novo com suporte em todos os principais navegadores.
Os módulos podem ser criados usando um atributo type="module"
para definir scripts que importam e exportam de outros
módulos. Exemplo:
// math.mjs
export const add = (x, y) => x + y;
<!-- index.html -->
<script type="module">
import { add } from './math.mjs';
add(5, 2); // 7
</script>
Muitos recursos mais recentes do ECMAScript já têm suporte em ambientes que oferecem suporte a módulos JavaScript (em vez de precisar do Babel). Isso significa que a configuração do Babel pode ser modificada para enviar duas versões diferentes do aplicativo ao navegador:
- Uma versão que funcione em navegadores mais recentes com suporte a módulos e que inclua um módulo que não é totalmente transpilado, mas tem um tamanho de arquivo menor
- Uma versão que inclui um script maior e transpilado que funciona em qualquer navegador legado
Como usar módulos ES com o Babel
Para ter configurações de @babel/preset-env
separadas para as duas versões
do aplicativo, remova o arquivo .babelrc
. As configurações do Babel podem ser adicionadas à
configuração do webpack especificando dois formatos de compilação diferentes para cada
versão do aplicativo.
Comece adicionando uma configuração para o script legada a webpack.config.js
:
const legacyConfig = {
entry,
output: {
path: path.resolve(__dirname, "public"),
filename: "[name].bundle.js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
targets: {
esmodules: false
}
}]
]
}
},
cssRule
]
},
plugins
}
Em vez de usar o valor targets
para "@babel/preset-env"
,
esmodules
com um valor de false
é usado. Isso significa que o Babel
inclui todas as transformações e polyfills necessários para segmentar todos os navegadores que ainda
não oferecem suporte a módulos ES.
Adicione os objetos entry
, cssRule
e corePlugins
ao início do
arquivo webpack.config.js
. Todos eles são compartilhados entre o módulo e
os scripts legados enviados ao navegador.
const entry = {
main: "./src"
};
const cssRule = {
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
};
const plugins = [
new ExtractTextPlugin({filename: "[name].css", allChunks: true}),
new HtmlWebpackPlugin({template: "./src/index.html"})
];
Da mesma forma, crie um objeto de configuração para o script do módulo abaixo, em que legacyConfig
é definido:
const moduleConfig = {
entry,
output: {
path: path.resolve(__dirname, "public"),
filename: "[name].mjs"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
targets: {
esmodules: true
}
}]
]
}
},
cssRule
]
},
plugins
}
A principal diferença aqui é que uma extensão de arquivo .mjs
é usada para o nome do
arquivo de saída. O valor esmodules
é definido como "true" aqui, o que significa que o código
que é gerado neste módulo é um script menor e menos compilado que não
passa por nenhuma transformação neste exemplo, já que todos os recursos usados
já têm suporte em navegadores que oferecem suporte a módulos.
No final do arquivo, exporte as duas configurações em uma única matriz.
module.exports = [
legacyConfig, moduleConfig
];
Agora, isso cria um módulo menor para navegadores compatíveis e um script transpilado maior para navegadores mais antigos.
Os navegadores que oferecem suporte a módulos ignoram scripts com um atributo nomodule
.
Por outro lado, os navegadores que não oferecem suporte a módulos ignoram elementos de script com
type="module"
. Isso significa que você pode incluir um módulo e um substituto
compilado. O ideal é que as duas versões do aplicativo estejam em index.html
como esta:
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js"></script>
Os navegadores que oferecem suporte a módulos buscam e executam main.mjs
e ignoram
main.bundle.js.
. Os navegadores que não oferecem suporte a módulos fazem o oposto.
É importante observar que, ao contrário dos scripts normais, os scripts de módulo são sempre adiados por padrão.
Se você quiser que o script nomodule
equivalente também seja adiado e executado somente após
a análise, adicione o atributo defer
:
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js" defer></script>
A última coisa que precisa ser feita aqui é adicionar os atributos module
e nomodule
ao módulo e ao script legado, respectivamente. Importe o
ScriptExtHtmlWebpackPlugin
na parte de cima de webpack.config.js
:
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
Agora atualize a matriz plugins
nas configurações para incluir esse plug-in:
const plugins = [ new ExtractTextPlugin({filename: "[name].css", allChunks: true}), new HtmlWebpackPlugin({template: "./src/index.html"}), new ScriptExtHtmlWebpackPlugin({ module: /\.mjs$/, custom: [ { test: /\.js$/, attribute: 'nomodule', value: '' }, ] }) ];
Essas configurações do plug-in adicionam um atributo type="module"
a todos os elementos de script
.mjs
, além de um atributo nomodule
para todos os módulos de script .js
.
Como exibir módulos no documento HTML
A última coisa que precisa ser feita é gerar os elementos de script legados e modernos no arquivo HTML. Infelizmente, o plug-in que cria o arquivo HTML final, HTMLWebpackPlugin
, não oferece suporte à saída dos scripts do módulo e do nomodule. Embora existam soluções alternativas e plug-ins separados criados para resolver esse problema, como BabelMultiTargetPlugin e HTMLWebpackMultiBuildPlugin, uma abordagem mais simples de adicionar o elemento de script do módulo manualmente é usada para fins deste tutorial.
Adicione o seguinte a src/index.js
no final do arquivo:
...
</form>
<script type="module" src="main.mjs"></script>
</body>
</html>
Agora, carregue o aplicativo em um navegador que ofereça suporte a módulos, como a versão mais recente do Chrome.
Somente o módulo é buscado, com um tamanho de pacote muito menor, já que ele não é muito transpilado. O outro elemento de script é completamente ignorado pelo navegador.
Se você carregar o aplicativo em um navegador mais antigo, apenas o script maior e transpilado com todos os polyfills e transformações necessários serão buscados. Confira uma captura de tela de todas as solicitações feitas em uma versão mais antiga do Chrome (versão 38).
Conclusão
Agora você entende como usar @babel/preset-env
para fornecer apenas os polyfills
necessários para os navegadores de destino. Você também sabe como os módulos JavaScript
podem melhorar ainda mais o desempenho enviando duas versões transpiladas diferentes de um
aplicativo. Com um bom entendimento de como essas duas técnicas podem reduzir
significativamente o tamanho do pacote, vá em frente e otimize!