Olá Habr!
Agora, cada navegador moderno permite que você trabalhe com os
Módulos ES6 .
À primeira vista, parece que isso é uma coisa completamente inútil - afinal, todos nós usamos colecionadores que substituem as importações por seus desafios internos. Mas se você se aprofundar nas especificações, acontece que, graças a elas, você pode fornecer um conjunto separado para navegadores modernos.
Sob o gato, há uma história de como consegui reduzir o tamanho do aplicativo em 11%, sem prejuízo dos navegadores antigos e dos meus nervos.
Recursos dos módulos ES6
O ES6 Modules é um sistema modular bem conhecido e amplamente utilizado para todos:
import { someFunc } from 'path/to/helpers.js'
export function someFunc() { }
Para usar esse sistema modular em navegadores, você precisa adicionar o tipo de módulo a cada tag de script. Os navegadores mais antigos verão que o tipo é diferente de texto / javascript e não executará o arquivo como JavaScript.
<script type="module" src="/path/to/someFile.js"></script>
A especificação também possui um atributo nomodule para tags de script. Os navegadores que suportam os
Módulos ES6 ignoram esse script e os navegadores mais antigos fazem o download e o executam.
<script nomodule src="/path/to/someFileFallback.js"></script>
Acontece que você pode simplesmente fazer dois assemblies: o primeiro com o tipo de módulo para navegadores modernos (Modern Build) e o outro com o nomodule para navegadores antigos (fallback build):
<script type="module" src="/path/to/someFile.js"></script> <script nomodule src="/path/to/someFileFallback.js"></script>
Por que isso é necessário?
Antes de enviar um projeto para produção, devemos:
- Adicione polyphiles.
- Transponha o código moderno para um mais antigo.
Nos meus projetos, tento oferecer suporte ao número máximo de navegadores, às vezes até o
IE 10 . Portanto, minha lista de polyfiles consiste em itens básicos como es6.promise, es6.object.values, etc. Mas os navegadores com
módulos ES6 são compatíveis com todos os métodos ES6 e não precisam de kilobytes extras de polyfills.
A transpilação também deixa uma marca notável no tamanho do arquivo: babel / preset-env usa 25 transformadores para cobrir a maioria dos navegadores, cada um dos quais aumenta o tamanho do código. Ao mesmo tempo, para navegadores com suporte aos
Módulos ES6 , o número de transformadores é reduzido para 9.
Portanto, na montagem de navegadores modernos, podemos remover polyfiles desnecessários e reduzir o número de transformadores, o que afetará bastante o tamanho dos arquivos resultantes!
Como adicionar polyfiles
Antes de preparar o Modern Build para navegadores modernos, vale a pena mencionar como adiciono polyfills ao projeto.
Normalmente, os projetos usam core-js para adicionar todos os polyfills possíveis.
Obviamente, você não deseja todos os 88 Kbytes de polyfiles dessa biblioteca, mas apenas os necessários para a sua lista de navegadores. Esse recurso está disponível usando babel / preset-env e sua opção useBuiltIns. Se você configurá-lo como entrada, a importação de core-js será substituída pela importação dos módulos individuais de que seu navegador precisa:
module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'entry', }] ], };
import 'core-js';
import "core-js/modules/es6.array.copy-within"; import "core-js/modules/es6.array.fill"; import "core-js/modules/es6.array.find";
Mas, com essa transformação, nos livramos apenas de uma parte dos polifilos desnecessários e muito antigos. Ainda temos polyphiles para TypedArray, WeakMap e outras coisas estranhas que nunca são usadas no projeto.
Para superar completamente esse problema, para a opção useBuiltIns, defino o valor como use. No estágio de compilação, o babel / preset-env analisará os arquivos para usar os recursos que não estão disponíveis nos navegadores selecionados e adicionará polyfiles a eles:
module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'usage', }] ], };
function sortStrings(strings) { return strings.sort(); } function createResolvedPromise() { return Promise.resolve(); }
import "core-js/modules/es6.array.sort"; import "core-js/modules/es6.promise"; function sortStrings(strings) { return strings.sort(); } function createResolvedPromise() { return Promise.resolve(); }
No exemplo acima, babel / preset-env adicionou um polyphile à função de classificação. Não é possível descobrir em JavaScript que tipo de objeto será passado para a função - será uma matriz ou um objeto de classe com a função de classificação, mas babel / preset-env seleciona o pior cenário para si e insere um polyfile.
Situações em que babel / preset-env está errado acontecem o tempo todo. Para remover polyphiles desnecessários, verifique periodicamente quais deles você importa e exclua os desnecessários usando a opção de exclusão:
module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'usage',
Não considero o módulo regenerator-runtime, pois uso o fast-async ( e aconselho a todos ).Criar versão moderna
Vamos configurar o Modern Build.
Verifique se temos um arquivo de lista de navegadores no projeto que descreve todos os navegadores necessários:
/* .browserslistrc */ > 0.5% IE 10
Adicione a variável de ambiente BROWSERS_ENV durante a construção, que pode assumir os valores fallback (para Fallback Build) e modern (para Modern Build):
/* package.json */ { "scripts": { /* ... */ "build": "NODE_ENV=production webpack /.../", "build:fallback": "BROWSERS_ENV=fallback npm run build", "build:modern": "BROWSERS_ENV=modern npm run build" }, /* ... */ }
Agora mude a configuração do babel / preset-env. Para especificar navegadores suportados na predefinição, há uma opção de destino. Ela tem uma abreviação especial - esmodules. Ao usá-lo, o babel / preset-env substituirá automaticamente os navegadores que suportam os
módulos ES6 .
const isModern = process.env.BROWSERS_ENV === 'modern'; module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'usage',
Babel / preset-env fará todo o trabalho para nós: selecionará apenas os polifiles e transformações necessários.
Agora podemos criar um projeto para navegadores modernos ou antigos apenas um comando do console!
Vincular Construção Moderna e Fallback
O último passo é combinar o Modern e o Fallback Builds em um.
Eu pretendo criar essa estrutura de projeto:
No index.html, haverá links para os arquivos javascript necessários de ambos os assemblies:
/* index.html */ <html> <head> </head> <body> <script type="module" src="/modern/js/app.540601d23b6d03413d5b.js"></script> <script nomodule src="/fallback/js/app.4d03e1af64f68111703e.js"></script> </body> </html>
Esta etapa pode ser dividida em três partes:
- Crie Modern e Fallback Build em diferentes diretórios.
- Obtendo informações sobre os caminhos para os arquivos javascript necessários.
- Criando index.html com links para todos os arquivos javascript.
Vamos começar!
Construa Modern e Fallback Build em diferentes diretórios
Para começar, vamos dar o passo mais fácil - coletaremos o Modern e o Fallback Build em diferentes diretórios dentro do diretório dist.
É simplesmente impossível especificar o diretório desejado para output.path, pois precisamos que o webpack possua caminhos para os arquivos relativos ao diretório dist (o index.html está nesse diretório e todas as outras dependências serão bombeadas em relação a ele).
Crie uma função especial para gerar caminhos de arquivo:
const path = require('path'); const isModern = process.env.BROWSERS_ENV === 'modern'; const prefix = isModern ? 'modern' : 'fallback'; module.exports = relativePath => ( path.join(prefix, relativePath) );
const getFilePath = require('path/to/getFilePath'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { mode: 'production', output: { path: 'dist', filename: getFilePath('js/[name].[contenthash].js'), }, plugins: [ new MiniCssExtractPlugin({ filename: getFilePath('css/[name].[contenthash].css'), }), ], }
O projeto começou a se reunir em diferentes diretórios do Modern e Fallback Build.
Obtendo informações sobre os caminhos para os arquivos javascript necessários
Para obter informações sobre os arquivos coletados, conecte o webpack-manifest-plugin. No final da montagem, ele adicionará um arquivo manifest.json com dados nos caminhos para os arquivos:
const getFilePath = require('path/to/getFilePath'); const WebpackManifestPlugin = require('webpack-manifest-plugin'); module.exports = { mode: 'production', plugins: [ new WebpackManifestPlugin({ fileName: getFilePath('manifest.json'), }), ], }
Agora temos informações sobre os arquivos coletados:
{ "app.js": "/fallback/js/app.4d03e1af64f68111703e.js", }
Criando index.html com links para todos os arquivos javascript
A única coisa que resta é adicionar index.html e inserir os caminhos para os arquivos necessários.
Para gerar o arquivo html, usarei o plugin html-webpack-plugin durante o Modern Build. O html-webpack-plugin inserirá os caminhos para os arquivos modernos em si, e eu pegarei os caminhos para os arquivos de fallback do arquivo criado na etapa anterior e os colarei no HTML usando um pequeno plugin de webpack:
const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModernBuildPlugin = require('path/to/ModernBuildPlugin'); module.exports = { mode: 'production', plugins: [ ...(isModern ? [
Atualize o package.json:
/* package.json */ { "scripts": { /* ... */ "build:full": "npm run build:fallback && npm run build:modern" }, /* ... */ }
Usando o comando npm run build: full, criaremos um arquivo html com o Modern e o Fallback Build. Qualquer navegador agora receberá o JavaScript que é capaz de executar.
Adicione a versão moderna ao seu aplicativo
Para testar minha solução em algo real, direcionei-a para um de meus projetos. A instalação da configuração levou menos de uma hora e o tamanho dos arquivos JavaScript diminuiu 11%. Ótimo resultado com uma implementação simples.
Obrigado por ler o artigo até o fim!
Materiais utilizados