Neste codelab, melhore o desempenho desse aplicativo simples que permite que os usuários classifiquem gatos aleatórios. Saiba como otimizar o pacote do JavaScript minimizando a quantidade de código transcompilada.
No app de exemplo, você pode 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 atual do gato.
Medida
É sempre bom começar inspecionando um site antes de adicionar otimizações:
- Para visualizar o site, pressione Ver app. Em seguida, pressione Tela cheia .
- Pressione "Control + Shift + J" (ou "Command + Option + J" no Mac) para abrir o DevTools.
- Clique na guia Rede.
- Marque a caixa de seleção Desativar cache.
- Atualize o app.
Mais de 80 KB são usados neste 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 Comando.Insira
Show Coverage
e clique emEnter
para exibir a guia Cobertura.Na guia Cobertura, clique em Recarregar para recarregar o aplicativo durante a captura da cobertura.
Observe quanto código foi usado em comparação com quanto foi carregado para o pacote principal:
Mais da metade do pacote (44 KB) não foi 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 segue um padrão conhecido como ECMAScript ou ECMA-262. Versões mais recentes da especificação são lançadas todos os anos e incluem novos recursos que passaram no processo de proposta. A compatibilidade de cada navegador principal com esses recursos é diferente.
Os seguintes recursos do ES2015 são usados no aplicativo:
O seguinte recurso ES2017 também é usado:
- Funções assíncronas (link em inglês)
Fique à vontade para analisar o código-fonte em src/index.js
para ver como tudo isso é usado.
Todos esses recursos são compatíveis com a versão mais recente do Chrome, mas e os outros navegadores que não são compatíveis com eles? O Babel, incluído no aplicativo, é a biblioteca mais conhecida para compilar códigos com sintaxes mais recentes que podem ser entendidas por navegadores e ambientes mais antigos. Ele faz isso de duas maneiras:
- Os Polyfills estão incluídos para emular funções mais recentes do ES2015+ para que as APIs possam ser usadas mesmo que não sejam compatíveis com o navegador. Veja um exemplo de polyfill (em inglês) do método
Array.includes
. - Os plug-ins são usados para transformar o código ES2015 (ou posterior) em 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.
Observe package.json
para ver 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.- O
babel-loader
inclui o Babel no processo de build do webpack.
Agora, confira webpack.config.js
para saber como babel-loader
é incluído como
regra:
module: { rules: [ //... { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } ] },
@babel/polyfill
fornece todos os polyfills necessários para os recursos mais recentes do ECMAScript para que eles possam funcionar em ambientes não compatíveis. Ela já foi importada no topo desrc/index.js.
import "./style.css";
import "@babel/polyfill";
@babel/preset-env
identifica quais transformações e polyfills são necessários para qualquer navegador ou ambiente escolhido como destino.
Confira o arquivo de configurações do Babel, .babelrc
, para ver como ele está
incluído:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions"
}
]
]
}
Esta é uma configuração do Babel e webpack. Aprenda a incluir o Babel no seu aplicativo se você usar um bundler de módulo diferente do webpack.
O atributo targets
em .babelrc
identifica quais navegadores estão sendo segmentados. O @babel/preset-env
é integrado à lista de navegadores, o que significa que você pode encontrar uma lista completa de consultas
compatíveis que podem ser usadas nesse campo na
documentação da lista de navegadores.
O valor "last 2 versions"
transcompila o código no aplicativo para as
duas últimas versões de todos os navegadores.
Depuração
Para ter uma visão completa de todos os alvos do Babel do navegador, bem como 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 navegadores descontinuados, como o Internet Explorer, estão incluídos nessa lista. Isso é um problema porque os navegadores sem suporte não terão recursos mais recentes adicionados, e o Babel continua transcompilando sintaxe específica para eles. Isso aumenta desnecessariamente o tamanho do pacote se os usuários não estiverem usando esse navegador para acessar o site.
O Babel também registra uma lista de plug-ins de transformação usados:
É uma lista bem longa! Esses são todos os plug-ins que o Babel precisa usar para transformar qualquer sintaxe ES2015+ em sintaxe mais antiga para todos os navegadores de destino.
No entanto, o Babel não mostra nenhum polyfill específico usado:
Isso ocorre porque todo o @babel/polyfill
está sendo importado diretamente.
Carregar polyfills individualmente
Por padrão, o Babel inclui todo polyfill necessário 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"
}
]
]
}
Atualize o aplicativo. Agora é possível ver todos os polyfills específicos incluídos:
Embora os polyfills necessários apenas para "last 2 versions"
agora sejam incluídos, essa ainda é uma lista muito longa. Isso ocorre porque os polyfills necessários para os navegadores de destino para todos os recursos mais recentes ainda estã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"
}
]
]
}
Assim, os polyfills são incluídos automaticamente quando necessário.
Isso significa que é possível remover a importação de @babel/polyfill
no 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 navegadores incluídos ainda é muito 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, realmente não há muita diferença com
essas mudanças. No entanto, é recomendável usar uma porcentagem de participação de mercado do navegador (como
">0.25%"
) e excluir navegadores específicos que você tem certeza de que seus
usuários não estão usando. Confira o artigo
"Últimas duas versões" consideradas prejudiciais (link em inglês)
de James Kyle para saber mais sobre isso.
Usar <script type="module">
Ainda há espaço para melhorias. Embora diversos polyfills não usados tenham sido removidos, muitos deles estão sendo enviados e não são necessários para alguns navegadores. Com o uso de módulos, sintaxes mais recentes podem ser escritas e enviadas aos navegadores diretamente, sem o uso de polyfills desnecessários.
Os módulos JavaScript são um recurso relativamente novo compatível com 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á são compatíveis com ambientes compatíveis com 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 seu aplicativo ao navegador:
- Uma versão que funcionaria em navegadores mais recentes compatíveis com módulos e que inclui um módulo que é amplamente não transcompilado, mas tem um tamanho de arquivo menor.
- Versão que inclui um script maior e transcompilado que funcionaria em qualquer navegador legado.
Como usar módulos ES com o Babel
Para ter configurações @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 legado em 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
. Eles são compartilhados entre o módulo e os scripts legados veiculados 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"})
];
Agora, da mesma forma, crie um objeto de configuração para o script de módulo abaixo, em que legacyConfig
esteja 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 verdadeiro aqui, o que significa que o código
gerado para esse 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 de navegadores com 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 transcompilado maior para navegadores mais antigos.
Os navegadores compatíveis com 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, bem como um substituto compilado. O ideal é que as duas versões do aplicativo estejam em index.html
assim:
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js"></script>
Navegadores compatíveis com módulos que 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
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 você precisa fazer é 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 este 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"
para todos os elementos de script .mjs
,
bem como 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 é enviar os elementos de script legados e modernos para o arquivo HTML. O plug-in que cria o arquivo HTML final, HTMLWebpackPlugin
, atualmente não oferece suporte à saída dos scripts módulo e nomodule. Embora existam soluções alternativas e plug-ins separados para resolver esse problema, como o BabelMultiTargetPlugin e o HTMLWebpackMultiBuildPlugin, uma abordagem mais simples de adicionar manualmente o elemento de script do módulo é usada para os fins deste tutorial.
Adicione o código abaixo 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 com suporte a módulos, como a versão mais recente do Chrome.
Somente o módulo é buscado, com um tamanho de pacote muito menor por ser em grande parte não transcompilado. O outro elemento de script é completamente ignorado pelo navegador.
Se você carregar o aplicativo em um navegador mais antigo, apenas o script maior e transcompilado com todos os polyfills e transformações necessários será buscado. Esta é 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ê sabe como usar @babel/preset-env
para fornecer apenas os polyfills necessários para os navegadores segmentados. Você também sabe como os módulos JavaScript
podem melhorar ainda mais o desempenho enviando duas versões diferentes transcompiladas de um
aplicativo. Depois de entender como essas duas técnicas podem reduzir significativamente
o tamanho do pacote, vá em frente e otimize.