Neste codelab, melhore o desempenho deste aplicativo simples que permite que os usuários classifiquem gatos aleatórios. Aprenda a otimizar o pacote JavaScript minimizando a quantidade de código transcompilado.
No app de exemplo, você pode selecionar uma palavra ou um 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 View App. Em seguida, pressione Fullscreen .
- 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 Command.Insira
Show Coverage
e pressioneEnter
para exibir a guia Cobertura.Na guia Cobertura, clique em Recarregar para recarregar o aplicativo durante a captura da cobertura.
Confira a quantidade de código usada em comparação com a quantidade de código carregada para o pacote principal:
Mais da metade do pacote (44 KB) nem é usada. Isso ocorre porque grande parte do código dele 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 ECMA-262 (link em inglês). Versões mais recentes da especificação são lançadas todos os anos e incluem novos recursos que foram aprovados no processo de proposta. Cada grande navegador 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 ES2017 também é usado:
Analise 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 usada para compilar códigos contendo sintaxe mais nova no código que os navegadores e ambientes mais antigos podem entender. Ele faz isso de duas maneiras:
- Os Polyfills (em inglês) 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 do método
Array.includes
. - Os plug-ins são usados para transformar o código ES2015 (ou posterior) em uma sintaxe ES5 mais antiga. Como essas mudanças estão relacionadas à sintaxe (como funções de seta), elas não podem ser emuladas com polyfills.
Consulte 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 principal compilador 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, olhe para webpack.config.js
para ver como babel-loader
está incluído como uma
regra:
module: { rules: [ //... { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } ] },
@babel/polyfill
fornece todos os polyfills necessários para qualquer recurso mais recente do ECMAScript para que eles funcionem em ambientes sem suporte a eles. Ele já foi importado na parte superior 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 do webpack. Saiba como 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. @babel/preset-env
é integrado a navegadoreslist, o que significa que é possível 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 cada navegador.
Depuração
Para ter uma visão completa de todos os destinos do Babel do navegador, bem como 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 navegadores não compatíveis não recebem recursos mais novos, e o Babel continua transcompilando sintaxes específicas para eles. Isso aumenta desnecessariamente o tamanho do pacote se os usuários não estiverem usando esse navegador para acessar seu site.
Ele 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 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"
}
]
]
}
Atualize o aplicativo. Agora é possível ver todos os polyfills específicos incluídos:
Embora apenas os polyfills necessários para "last 2 versions"
agora estejam 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 o necessário 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 é possível 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 serão incluídos.
O tamanho do pacote do aplicativo será reduzido significativamente.
Como restringir a lista de navegadores compatíveis
O número de destinos de navegador incluídos ainda é muito grande, e muitos usuários não utilizam navegadores descontinuados, como o Internet Explorer. Atualize as configurações para as seguintes:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"targets": [">0.25%", "not ie 11"],
"debug": true,
"useBuiltIns": "usage",
}
]
]
}
Dê uma olhada nos detalhes do pacote buscado.
Como o aplicativo é muito pequeno, não há muita diferença
dessas mudanças. No entanto, recomendamos usar uma porcentagem de participação de mercado de navegadores (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
"As últimas duas versões" consideradas prejudiciais
por James Kyle para saber mais sobre isso.
Usar <script type="module">
Ainda há espaço para melhorias. Embora vários polyfills não utilizados 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, a sintaxe mais recente pode ser escrita e enviada aos navegadores diretamente, sem o uso de polyfills desnecessários.
Os módulos JavaScript são um recurso relativamente novo suportado 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á são compatíveis com ambientes que aceitam 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 funciona em navegadores mais recentes com suporte a módulos e que inclui um módulo que não é transcompilado, mas tem um tamanho de arquivo menor
- Uma versão que inclui um script maior e transcompilado que funciona 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
. É possível adicionar as configurações do Babel à
configuração do webpack especificando dois formatos de compilação diferentes para cada
versão do aplicativo.
Comece adicionando uma configuração do script legado ao 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 são compatíveis com 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 exibidos no 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 do módulo abaixo, em que legacyConfig
está 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 é que a 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 este 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 com suporte a módulos.
No final do arquivo, exporte as duas configurações em uma única matriz.
module.exports = [
legacyConfig, moduleConfig
];
Agora, ele vai criar um módulo menor para navegadores com suporte a ele 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
desta forma:
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js"></script>
Navegadores compatíveis com módulos buscam e executam main.mjs
e ignoram
main.bundle.js.
. Os navegadores que não oferecem suporte a módulos fazem o contrário.
É 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, será necessário adicionar 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, e importar 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"
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 é a saída dos elementos de script legado e moderno para o arquivo HTML. Infelizmente, o plug-in que cria o arquivo HTML final, HTMLWebpackPlugin
, atualmente não oferece suporte à saída dos scripts de módulo e de nomodule. Embora existam soluções alternativas e plug-ins separados criados para resolver esse problema, como o BabelMultiTargetPlugin e o HTMLWebpackMultiBuildPlugin (links em inglês), uma abordagem mais simples de adicionar manualmente o elemento de script do módulo é usada para a finalidade 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 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 devido à transpilação em grande escala. O outro elemento de script é completamente ignorado pelo navegador.
Se você carregar o aplicativo em um navegador mais antigo, somente o script maior, 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 de destino. Você também sabe como os módulos JavaScript
podem melhorar ainda mais o desempenho ao enviar duas versões transcompiladas diferentes de um
aplicativo. Com um bom entendimento de como essas duas técnicas podem reduzir significativamente o tamanho do seu pacote, prossiga e otimize.