Предоставляйте современный код современным браузерам для более быстрой загрузки страниц.

В этой лаборатории кода вы улучшите производительность простого приложения, которое позволяет пользователям оценивать случайных кошек. Узнайте, как оптимизировать пакет JavaScript, минимизировав объем передаваемого кода.

Скриншот приложения

В примере приложения вы можете выбрать слово или смайлик, чтобы передать, насколько вам нравится каждый кот. Когда вы нажимаете кнопку, приложение отображает значение кнопки под текущим изображением кошки.

Мера

Всегда полезно начать с проверки веб-сайта, прежде чем добавлять какие-либо оптимизации:

  1. Чтобы просмотреть сайт, нажмите «Просмотреть приложение» . Затем нажмите Полноэкранный режим полноэкранный .
  2. Нажмите «Control+Shift+J» (или «Command+Option+J» на Mac), чтобы открыть DevTools.
  3. Откройте вкладку «Сеть» .
  4. Установите флажок Отключить кеш .
  5. Перезагрузите приложение.

Запрос исходного размера пакета

Для этого приложения используется более 80 КБ! Пришло время узнать, не используются ли части пакета:

  1. Нажмите Control+Shift+P (или Command+Shift+P на Mac), чтобы открыть меню команд . Командное меню

  2. Введите Show Coverage и нажмите Enter , чтобы отобразить вкладку «Покрытие» .

  3. На вкладке «Покрытие» нажмите «Обновить» , чтобы перезагрузить приложение во время сбора данных о покрытии.

    Перезагрузите приложение с покрытием кода

  4. Посмотрите, сколько кода было использовано по сравнению с тем, сколько было загружено для основного пакета:

    Покрытие кода пакета

Более половины пакета (44 КБ) даже не используется. Это связано с тем, что большая часть кода состоит из полифилов, обеспечивающих работу приложения в старых браузерах.

Используйте @babel/preset-env

Синтаксис языка JavaScript соответствует стандарту, известному как ECMAScript или ECMA-262 . Новые версии спецификации выпускаются каждый год и включают новые функции, прошедшие процедуру предложения. Каждый основной браузер всегда находится на разном этапе поддержки этих функций.

В приложении используются следующие возможности ES2015:

Также используется следующая функция ES2017:

Не стесняйтесь погрузиться в исходный код в src/index.js чтобы увидеть, как все это используется.

Все эти функции поддерживаются в последней версии Chrome, но как насчет других браузеров, которые их не поддерживают? Babel , включенная в приложение, — это самая популярная библиотека, используемая для компиляции кода, содержащего новый синтаксис, в код, понятный старым браузерам и средам. Это делается двумя способами:

  • Полифилы включены для эмуляции новых функций ES2015+, чтобы их API можно было использовать, даже если они не поддерживаются браузером. Вот пример полифилла метода Array.includes .
  • Плагины используются для преобразования кода ES2015 (или более поздней версии) в более старый синтаксис ES5. Поскольку это изменения, связанные с синтаксисом (например, функции стрелок), их нельзя эмулировать с помощью полифилов.

Посмотрите package.json , чтобы узнать, какие библиотеки Babel включены:

"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 — основной компилятор Babel. При этом все конфигурации Babel определяются в файле .babelrc в корне проекта.
  • babel-loader включает Babel в процесс сборки веб-пакета.

Теперь посмотрите на webpack.config.js чтобы увидеть, как обычно включается babel-loader :

module: {
  rules: [
    //...
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: "babel-loader"
    }
  ]
},
  • @babel/polyfill предоставляет все необходимые полифилы для любых новых функций ECMAScript, чтобы они могли работать в средах, которые их не поддерживают. Он уже импортирован в самом верху src/index.js.
import "./style.css";
import "@babel/polyfill";
  • @babel/preset-env определяет, какие преобразования и полифилы необходимы для любых браузеров или сред, выбранных в качестве целевых.

Взгляните на файл конфигурации Babel, .babelrc , чтобы узнать, как он включен:

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions"
     
}
   
]
 
]
}

Это установка Babel и веб-пакета. Узнайте, как включить Babel в свое приложение, если вы используете другой упаковщик модулей, чем веб-пакет.

Атрибут targets в .babelrc определяет, какие браузеры являются мишенью. @babel/preset-env интегрируется со списком браузеров, а это значит, что вы можете найти полный список совместимых запросов, которые можно использовать в этом поле, в документации по списку браузеров .

Значение "last 2 versions" транспилирует код в приложении для двух последних версий каждого браузера.

Отладка

Чтобы получить полное представление обо всех целях Babel браузера, а также обо всех включенных преобразованиях и полифилах, добавьте поле debug в .babelrc:

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"debug": true
     
}
   
]
 
]
}
  • Нажмите «Инструменты» .
  • Нажмите Журналы .

Перезагрузите приложение и просмотрите журналы состояния сбоев в нижней части редактора.

Целевые браузеры

Babel записывает в консоль ряд подробностей о процессе компиляции, включая все целевые среды, для которых был скомпилирован код.

Целевые браузеры

Обратите внимание, что в этот список включены браузеры, выпуск которых прекращен, например Internet Explorer. Это проблема, поскольку в неподдерживаемые браузеры не будут добавлены новые функции, и Babel продолжает переносить для них определенный синтаксис. Это неоправданно увеличивает размер вашего пакета, если пользователи не используют этот браузер для доступа к вашему сайту.

Babel также записывает список используемых плагинов преобразования:

Список используемых плагинов

Это довольно длинный список! Это все плагины, которые необходимо использовать Babel для преобразования любого синтаксиса ES2015+ в более старый синтаксис для всех целевых браузеров.

Однако Babel не показывает какие-либо конкретные используемые полифилы:

Полифиллы не добавлены

Это связано с тем, что весь @babel/polyfill импортируется напрямую.

Загружать полифилы по отдельности

По умолчанию Babel включает все полифилы, необходимые для полной среды ES2015+, когда @babel/polyfill импортируется в файл. Чтобы импортировать определенные полифиллы, необходимые для целевых браузеров, добавьте в конфигурацию запись useBuiltIns: 'entry' .

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"debug": true
       
"useBuiltIns": "entry"
     
}
   
]
 
]
}

Перезагрузите приложение. Теперь вы можете увидеть все включенные в него полифилы:

Список импортированных полифилов

Хотя теперь включены только необходимые полифилы для "last 2 versions" , это по-прежнему очень длинный список! Это связано с тем, что полифилы, необходимые целевым браузерам для каждой новой функции, по-прежнему включены. Измените значение атрибута на usage , чтобы включать только те функции, которые используются в коде.

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"debug": true,
       
"useBuiltIns": "entry"
       
"useBuiltIns": "usage"
     
}
   
]
 
]
}

При этом полифилы автоматически включаются там, где это необходимо. Это означает, что вы можете удалить импорт @babel/polyfill в src/index.js.

import "./style.css";
import "@babel/polyfill";

Теперь включены только те полифилы, которые необходимы приложению.

Список полифилов, включенных автоматически

Размер пакета приложений значительно уменьшен.

Размер пакета уменьшен до 30,1 КБ.

Сужение списка поддерживаемых браузеров

Число включенных целевых браузеров по-прежнему довольно велико, и немногие пользователи используют устаревшие браузеры, такие как Internet Explorer. Обновите конфигурации до следующего:

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"targets": [">0.25%", "not ie 11"],
       
"debug": true,
       
"useBuiltIns": "usage",
     
}
   
]
 
]
}

Взгляните на детали полученного комплекта.

Размер пакета 30,0 КБ.

Поскольку приложение настолько маленькое, в этих изменениях нет особой разницы. Однако рекомендуемым подходом является использование процентной доли рынка браузеров (например ">0.25%" » и исключение конкретных браузеров, которые, как вы уверены, не используют ваши пользователи. Чтобы узнать больше об этом, прочтите статью Джеймса Кайла «Последние 2 версии», которую считают вредной .

Используйте <script type="module">

Есть еще много возможностей для улучшения. Хотя ряд неиспользуемых полифилов был удален, многие из них уже поставляются и не нужны для некоторых браузеров. Используя модули, можно написать новый синтаксис и отправить его в браузеры напрямую без использования ненужных полифилов.

Модули JavaScript — относительно новая функция, поддерживаемая во всех основных браузерах . Модули можно создавать с использованием атрибута type="module" для определения сценариев, которые импортируют и экспортируют из других модулей. Например:

// math.mjs
export const add = (x, y) => x + y;

<!-- index.html -->
<script type="module">
 
import { add } from './math.mjs';

  add
(5, 2); // 7
</script>

Многие новые функции ECMAScript уже поддерживаются в средах, поддерживающих модули JavaScript (вместо Babel). Это означает, что конфигурацию Babel можно изменить для отправки в браузер двух разных версий вашего приложения:

  • Версия, которая будет работать в новых браузерах, поддерживающих модули и включающая модуль, который практически не транспилируется, но имеет меньший размер файла.
  • Версия, включающая более крупный транспилированный скрипт, который будет работать в любом устаревшем браузере.

Использование модулей ES с Babel

Чтобы иметь отдельные настройки @babel/preset-env для двух версий приложения, удалите файл .babelrc . Настройки Babel можно добавить в конфигурацию веб-пакета, указав два разных формата компиляции для каждой версии приложения.

Начните с добавления конфигурации устаревшего скрипта в 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
}

Обратите внимание, что вместо использования targets значения для "@babel/preset-env" вместо этого используются esmodules со значением false . Это означает, что Babel включает в себя все необходимые преобразования и полифилы для каждого браузера, который еще не поддерживает модули ES.

Добавьте объекты entry , cssRule и corePlugins в начало файла webpack.config.js . Все они используются как модулем, так и устаревшими сценариями, передаваемыми в браузер.

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"})
];

Теперь аналогичным образом создайте объект конфигурации для сценария модуля ниже, где определен legacyConfig :

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
}

Основное отличие здесь заключается в том, что для имени выходного файла используется расширение .mjs . Здесь для значения esmodules установлено значение true, что означает, что код, выводимый в этот модуль, представляет собой меньший по размеру и менее компилируемый сценарий, который не подвергается каким-либо преобразованиям в этом примере, поскольку все используемые функции уже поддерживаются в браузерах, поддерживающих модули.

В самом конце файла экспортируйте обе конфигурации в один массив.

module.exports = [
  legacyConfig
, moduleConfig
];

Теперь создается как меньший модуль для браузеров, которые его поддерживают, так и более крупный транспилированный скрипт для старых браузеров.

Браузеры, поддерживающие модули, игнорируют скрипты с атрибутом nomodule . И наоборот, браузеры, которые не поддерживают модули, игнорируют элементы сценария с type="module" . Это означает, что вы можете включить как модуль, так и скомпилированный резервный вариант. В идеале две версии приложения должны находиться в index.html следующим образом:

<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js"></script>

Браузеры, поддерживающие модули, извлекают и выполняют main.mjs и игнорируют main.bundle.js. Браузеры, не поддерживающие модули, делают наоборот.

Важно отметить, что в отличие от обычных скриптов, скрипты модулей по умолчанию всегда откладываются. Если вы хотите, чтобы эквивалентный скрипт nomodule также откладывался и выполнялся только после синтаксического анализа, вам необходимо добавить атрибут defer :

<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js" defer></script>

Последнее, что здесь нужно сделать, — это добавить атрибуты module и nomodule к модулю и устаревшему скрипту соответственно. Импортируйте ScriptExtHtmlWebpackPlugin в самый верх 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");

Теперь обновите массив plugins в конфигурациях, чтобы включить этот плагин:

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: ''
    },
    ]
  })
];

Эти настройки плагина добавляют атрибут type="module" для всех элементов сценария .mjs , а также атрибут nomodule для всех модулей сценария .js .

Обслуживание модулей в HTML-документе

Последнее, что нужно сделать, — это вывести как устаревшие, так и современные элементы сценария в HTML-файл. К сожалению, плагин, создающий окончательный HTML-файл, HTMLWebpackPlugin , в настоящее время не поддерживает вывод скриптов модуля и nomodule. Хотя для решения этой проблемы существуют обходные пути и отдельные плагины, такие как BabelMultiTargetPlugin и HTMLWebpackMultiBuildPlugin , для целей данного руководства используется более простой подход — добавление элемента сценария модуля вручную.

Добавьте следующее в src/index.js в конце файла:

    ...
   
</form>
    <script type="module" src="main.mjs"></
script>
 
</body>
</
html>

Теперь загрузите приложение в браузере, поддерживающем модули, например в последней версии Chrome.

Модуль размером 5,2 КБ, полученный по сети для новых браузеров

Извлекается только модуль, но с гораздо меньшим размером пакета, поскольку он практически не транспилируется! Другой элемент сценария полностью игнорируется браузером.

Если вы загружаете приложение в более старом браузере, будет загружен только более крупный транспилированный скрипт со всеми необходимыми полифилами и преобразованиями. Вот скриншот всех запросов, сделанных в более старой версии Chrome (версия 38).

Скрипт размером 30 КБ получен для старых браузеров.

Заключение

Теперь вы понимаете, как использовать @babel/preset-env для предоставления только необходимых полифилов, необходимых для целевых браузеров. Вы также знаете, как модули JavaScript могут еще больше повысить производительность, предоставляя две разные транспилированные версии приложения. Имея хорошее представление о том, как оба эти метода могут значительно сократить размер вашего пакета, приступайте к оптимизации!

,

В этой лаборатории кода вы улучшите производительность простого приложения, которое позволяет пользователям оценивать случайных кошек. Узнайте, как оптимизировать пакет JavaScript, минимизировав объем передаваемого кода.

Скриншот приложения

В примере приложения вы можете выбрать слово или смайлик, чтобы передать, насколько вам нравится каждый кот. Когда вы нажимаете кнопку, приложение отображает значение кнопки под текущим изображением кошки.

Мера

Всегда полезно начать с проверки веб-сайта, прежде чем добавлять какие-либо оптимизации:

  1. Чтобы просмотреть сайт, нажмите «Просмотреть приложение» . Затем нажмите Полноэкранный режим полноэкранный .
  2. Нажмите «Control+Shift+J» (или «Command+Option+J» на Mac), чтобы открыть DevTools.
  3. Откройте вкладку «Сеть» .
  4. Установите флажок Отключить кеш .
  5. Перезагрузите приложение.

Запрос исходного размера пакета

Для этого приложения используется более 80 КБ! Пришло время узнать, не используются ли части пакета:

  1. Нажмите Control+Shift+P (или Command+Shift+P на Mac), чтобы открыть меню команд . Командное меню

  2. Введите Show Coverage и нажмите Enter , чтобы отобразить вкладку «Покрытие» .

  3. На вкладке «Покрытие» нажмите «Обновить» , чтобы перезагрузить приложение во время сбора данных о покрытии.

    Перезагрузите приложение с покрытием кода

  4. Посмотрите, сколько кода было использовано по сравнению с тем, сколько было загружено для основного пакета:

    Покрытие кода пакета

Более половины пакета (44 КБ) даже не используется. Это связано с тем, что большая часть кода состоит из полифилов, обеспечивающих работу приложения в старых браузерах.

Используйте @babel/preset-env

Синтаксис языка JavaScript соответствует стандарту, известному как ECMAScript или ECMA-262 . Новые версии спецификации выпускаются каждый год и включают новые функции, прошедшие процедуру предложения. Каждый основной браузер всегда находится на разном этапе поддержки этих функций.

В приложении используются следующие возможности ES2015:

Также используется следующая функция ES2017:

Не стесняйтесь погрузиться в исходный код в src/index.js чтобы увидеть, как все это используется.

Все эти функции поддерживаются в последней версии Chrome, но как насчет других браузеров, которые их не поддерживают? Babel , включенная в приложение, — это самая популярная библиотека, используемая для компиляции кода, содержащего новый синтаксис, в код, понятный старым браузерам и средам. Это делается двумя способами:

  • Полифилы включены для эмуляции новых функций ES2015+, чтобы их API можно было использовать, даже если они не поддерживаются браузером. Вот пример полифилла метода Array.includes .
  • Плагины используются для преобразования кода ES2015 (или более поздней версии) в более старый синтаксис ES5. Поскольку это изменения, связанные с синтаксисом (например, функции стрелок), их нельзя эмулировать с помощью полифилов.

Посмотрите package.json , чтобы узнать, какие библиотеки Babel включены:

"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 — основной компилятор Babel. При этом все конфигурации Babel определяются в файле .babelrc в корне проекта.
  • babel-loader включает Babel в процесс сборки веб-пакета.

Теперь посмотрите на webpack.config.js чтобы увидеть, как обычно включается babel-loader :

module: {
  rules: [
    //...
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: "babel-loader"
    }
  ]
},
  • @babel/polyfill предоставляет все необходимые полифилы для любых новых функций ECMAScript, чтобы они могли работать в средах, которые их не поддерживают. Он уже импортирован в самом верху src/index.js.
import "./style.css";
import "@babel/polyfill";
  • @babel/preset-env определяет, какие преобразования и полифилы необходимы для любых браузеров или сред, выбранных в качестве целевых.

Взгляните на файл конфигурации Babel, .babelrc , чтобы узнать, как он включен:

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions"
     
}
   
]
 
]
}

Это установка Babel и веб-пакета. Узнайте, как включить Babel в свое приложение, если вы используете другой упаковщик модулей, чем веб-пакет.

Атрибут targets в .babelrc определяет, какие браузеры являются мишенью. @babel/preset-env интегрируется со списком браузеров, а это значит, что вы можете найти полный список совместимых запросов, которые можно использовать в этом поле, в документации по списку браузеров .

Значение "last 2 versions" транспилирует код в приложении для двух последних версий каждого браузера.

Отладка

Чтобы получить полное представление обо всех целях Babel браузера, а также обо всех включенных преобразованиях и полифилах, добавьте поле debug в .babelrc:

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"debug": true
     
}
   
]
 
]
}
  • Нажмите «Инструменты» .
  • Нажмите Журналы .

Перезагрузите приложение и просмотрите журналы состояния сбоев в нижней части редактора.

Целевые браузеры

Babel записывает в консоль ряд подробностей о процессе компиляции, включая все целевые среды, для которых был скомпилирован код.

Целевые браузеры

Обратите внимание, что в этот список включены браузеры, выпуск которых прекращен, например Internet Explorer. Это проблема, поскольку в неподдерживаемые браузеры не будут добавлены новые функции, и Babel продолжает переносить для них определенный синтаксис. Это неоправданно увеличивает размер вашего пакета, если пользователи не используют этот браузер для доступа к вашему сайту.

Babel также записывает список используемых плагинов преобразования:

Список используемых плагинов

Это довольно длинный список! Это все плагины, которые необходимо использовать Babel для преобразования любого синтаксиса ES2015+ в более старый синтаксис для всех целевых браузеров.

Однако Babel не показывает какие-либо конкретные используемые полифилы:

Полифиллы не добавлены

Это связано с тем, что весь @babel/polyfill импортируется напрямую.

Загружать полифилы по отдельности

По умолчанию Babel включает все полифилы, необходимые для полной среды ES2015+, когда @babel/polyfill импортируется в файл. Чтобы импортировать определенные полифиллы, необходимые для целевых браузеров, добавьте в конфигурацию запись useBuiltIns: 'entry' .

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"debug": true
       
"useBuiltIns": "entry"
     
}
   
]
 
]
}

Перезагрузите приложение. Теперь вы можете увидеть все включенные в него полифилы:

Список импортированных полифилов

Хотя теперь включены только необходимые полифилы для "last 2 versions" , это по-прежнему очень длинный список! Это связано с тем, что полифилы, необходимые целевым браузерам для каждой новой функции, по-прежнему включены. Измените значение атрибута на usage , чтобы включать только те функции, которые используются в коде.

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"debug": true,
       
"useBuiltIns": "entry"
       
"useBuiltIns": "usage"
     
}
   
]
 
]
}

При этом полифилы автоматически включаются там, где это необходимо. Это означает, что вы можете удалить импорт @babel/polyfill в src/index.js.

import "./style.css";
import "@babel/polyfill";

Теперь включены только те полифилы, которые необходимы приложению.

Список полифилов, включенных автоматически

Размер пакета приложений значительно уменьшен.

Размер пакета уменьшен до 30,1 КБ.

Сужение списка поддерживаемых браузеров

Число включенных целевых браузеров по-прежнему довольно велико, и немногие пользователи используют устаревшие браузеры, такие как Internet Explorer. Обновите конфигурации до следующего:

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"targets": [">0.25%", "not ie 11"],
       
"debug": true,
       
"useBuiltIns": "usage",
     
}
   
]
 
]
}

Взгляните на детали полученного комплекта.

Размер пакета 30,0 КБ.

Поскольку приложение настолько маленькое, в этих изменениях нет особой разницы. Однако рекомендуемым подходом является использование процентной доли рынка браузеров (например ">0.25%" » и исключение конкретных браузеров, которые, как вы уверены, не используют ваши пользователи. Чтобы узнать больше об этом, прочтите статью Джеймса Кайла «Последние 2 версии», которую считают вредной .

Используйте <script type="module">

Есть еще много возможностей для улучшения. Хотя ряд неиспользуемых полифилов был удален, многие из них уже поставляются и не нужны для некоторых браузеров. Используя модули, можно написать новый синтаксис и отправить его в браузеры напрямую без использования ненужных полифилов.

Модули JavaScript — относительно новая функция, поддерживаемая во всех основных браузерах . Модули можно создавать с использованием атрибута type="module" для определения сценариев, которые импортируют и экспортируют из других модулей. Например:

// math.mjs
export const add = (x, y) => x + y;

<!-- index.html -->
<script type="module">
 
import { add } from './math.mjs';

  add
(5, 2); // 7
</script>

Многие новые функции ECMAScript уже поддерживаются в средах, поддерживающих модули JavaScript (вместо Babel). Это означает, что конфигурацию Babel можно изменить для отправки в браузер двух разных версий вашего приложения:

  • Версия, которая будет работать в новых браузерах, поддерживающих модули и включающая модуль, который практически не транспилируется, но имеет меньший размер файла.
  • Версия, включающая более крупный транспилированный скрипт, который будет работать в любом устаревшем браузере.

Использование модулей ES с Babel

Чтобы иметь отдельные настройки @babel/preset-env для двух версий приложения, удалите файл .babelrc . Настройки Babel можно добавить в конфигурацию веб-пакета, указав два разных формата компиляции для каждой версии приложения.

Начните с добавления конфигурации устаревшего скрипта в 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
}

Обратите внимание, что вместо использования targets значения для "@babel/preset-env" вместо этого используются esmodules со значением false . Это означает, что Babel включает в себя все необходимые преобразования и полифилы для каждого браузера, который еще не поддерживает модули ES.

Добавьте объекты entry , cssRule и corePlugins в начало файла webpack.config.js . Все они используются как модулем, так и устаревшими сценариями, передаваемыми в браузер.

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"})
];

Теперь аналогичным образом создайте объект конфигурации для сценария модуля ниже, где определен legacyConfig :

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
}

Основное отличие здесь заключается в том, что для имени выходного файла используется расширение .mjs . Здесь для значения esmodules установлено значение true, что означает, что код, выводимый в этот модуль, представляет собой меньший по размеру и менее компилируемый сценарий, который не подвергается каким-либо преобразованиям в этом примере, поскольку все используемые функции уже поддерживаются в браузерах, поддерживающих модули.

В самом конце файла экспортируйте обе конфигурации в один массив.

module.exports = [
  legacyConfig
, moduleConfig
];

Теперь создается как меньший модуль для браузеров, которые его поддерживают, так и более крупный транспилированный скрипт для старых браузеров.

Браузеры, поддерживающие модули, игнорируют скрипты с атрибутом nomodule . И наоборот, браузеры, которые не поддерживают модули, игнорируют элементы сценария с type="module" . Это означает, что вы можете включить как модуль, так и скомпилированный резервный вариант. В идеале две версии приложения должны находиться в index.html следующим образом:

<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js"></script>

Браузеры, поддерживающие модули, извлекают и выполняют main.mjs и игнорируют main.bundle.js. Браузеры, не поддерживающие модули, делают наоборот.

Важно отметить, что в отличие от обычных скриптов, скрипты модулей по умолчанию всегда откладываются. Если вы хотите, чтобы эквивалентный скрипт nomodule также откладывался и выполнялся только после синтаксического анализа, вам необходимо добавить атрибут defer :

<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js" defer></script>

Последнее, что здесь нужно сделать, — это добавить атрибуты module и nomodule к модулю и устаревшему скрипту соответственно. Импортируйте ScriptExtHtmlWebpackPlugin в самый верх 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");

Теперь обновите массив plugins в конфигурациях, чтобы включить этот плагин:

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: ''
    },
    ]
  })
];

Эти настройки плагина добавляют атрибут type="module" для всех элементов сценария .mjs , а также атрибут nomodule для всех модулей сценария .js .

Обслуживание модулей в HTML-документе

Последнее, что необходимо сделать, — это вывести как устаревшие, так и современные элементы сценария в HTML-файл. К сожалению, плагин, создающий окончательный HTML-файл, HTMLWebpackPlugin , в настоящее время не поддерживает вывод скриптов модуля и nomodule. Хотя для решения этой проблемы существуют обходные пути и отдельные плагины, такие как BabelMultiTargetPlugin и HTMLWebpackMultiBuildPlugin , для целей данного руководства используется более простой подход — добавление элемента сценария модуля вручную.

Добавьте следующее в src/index.js в конце файла:

    ...
   
</form>
    <script type="module" src="main.mjs"></
script>
 
</body>
</
html>

Теперь загрузите приложение в браузере, поддерживающем модули, например в последней версии Chrome.

Модуль размером 5,2 КБ, полученный по сети для новых браузеров

Извлекается только модуль, но с гораздо меньшим размером пакета, поскольку он практически не транспилируется! Другой элемент сценария полностью игнорируется браузером.

Если вы загружаете приложение в более старом браузере, будет загружен только более крупный транспилированный скрипт со всеми необходимыми полифилами и преобразованиями. Вот скриншот всех запросов, сделанных в более старой версии Chrome (версия 38).

Скрипт размером 30 КБ получен для старых браузеров.

Заключение

Теперь вы понимаете, как использовать @babel/preset-env для предоставления только необходимых полифилов, необходимых для целевых браузеров. Вы также знаете, как модули JavaScript могут еще больше повысить производительность, предоставляя две разные транспилированные версии приложения. Имея хорошее представление о том, как оба этих метода могут значительно сократить размер вашего пакета, приступайте к оптимизации!

,

В этой лаборатории кода вы улучшите производительность простого приложения, которое позволяет пользователям оценивать случайных кошек. Узнайте, как оптимизировать пакет JavaScript, минимизировав объем передаваемого кода.

Скриншот приложения

В примере приложения вы можете выбрать слово или смайлик, чтобы передать, насколько вам нравится каждый кот. Когда вы нажимаете кнопку, приложение отображает значение кнопки под текущим изображением кошки.

Мера

Всегда полезно начать с проверки веб-сайта, прежде чем добавлять какие-либо оптимизации:

  1. Чтобы просмотреть сайт, нажмите «Просмотреть приложение» . Затем нажмите Полноэкранный режим полноэкранный .
  2. Нажмите «Control+Shift+J» (или «Command+Option+J» на Mac), чтобы открыть DevTools.
  3. Откройте вкладку «Сеть» .
  4. Установите флажок Отключить кеш .
  5. Перезагрузите приложение.

Запрос исходного размера пакета

Для этого приложения используется более 80 КБ! Пришло время узнать, не используются ли части пакета:

  1. Нажмите Control+Shift+P (или Command+Shift+P на Mac), чтобы открыть меню команд . Командное меню

  2. Введите Show Coverage и нажмите Enter , чтобы отобразить вкладку «Покрытие» .

  3. На вкладке «Покрытие» нажмите «Обновить» , чтобы перезагрузить приложение во время сбора данных о покрытии.

    Перезагрузите приложение с покрытием кода

  4. Посмотрите, сколько кода было использовано по сравнению с тем, сколько было загружено для основного пакета:

    Покрытие кода пакета

Более половины пакета (44 КБ) даже не используется. Это связано с тем, что большая часть кода состоит из полифилов, обеспечивающих работу приложения в старых браузерах.

Используйте @babel/preset-env

Синтаксис языка JavaScript соответствует стандарту, известному как ECMAScript или ECMA-262 . Новые версии спецификации выпускаются каждый год и включают новые функции, прошедшие процедуру предложения. Каждый основной браузер всегда находится на разном этапе поддержки этих функций.

В приложении используются следующие возможности ES2015:

Также используется следующая функция ES2017:

Не стесняйтесь погрузиться в исходный код в src/index.js чтобы увидеть, как все это используется.

Все эти функции поддерживаются в последней версии Chrome, но как насчет других браузеров, которые их не поддерживают? Babel , включенная в приложение, — это самая популярная библиотека, используемая для компиляции кода, содержащего новый синтаксис, в код, понятный старым браузерам и средам. Это делается двумя способами:

  • Полифилы включены для эмуляции новых функций ES2015+, чтобы их API можно было использовать, даже если они не поддерживаются браузером. Вот пример полифилла метода Array.includes .
  • Плагины используются для преобразования кода ES2015 (или более поздней версии) в более старый синтаксис ES5. Поскольку это изменения, связанные с синтаксисом (например, функции стрелок), их нельзя эмулировать с помощью полифилов.

Посмотрите package.json , чтобы узнать, какие библиотеки Babel включены:

"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 — основной компилятор Babel. При этом все конфигурации Babel определяются в файле .babelrc в корне проекта.
  • babel-loader включает Babel в процесс сборки веб-пакета.

Теперь посмотрите на webpack.config.js чтобы увидеть, как обычно включается babel-loader :

module: {
  rules: [
    //...
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: "babel-loader"
    }
  ]
},
  • @babel/polyfill предоставляет все необходимые полифилы для любых новых функций ECMAScript, чтобы они могли работать в средах, которые их не поддерживают. Он уже импортирован в самом верху src/index.js.
import "./style.css";
import "@babel/polyfill";
  • @babel/preset-env определяет, какие преобразования и полифилы необходимы для любых браузеров или сред, выбранных в качестве целевых.

Взгляните на файл конфигурации Babel, .babelrc , чтобы узнать, как он включен:

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions"
     
}
   
]
 
]
}

Это установка Babel и веб-пакета. Узнайте, как включить Babel в свое приложение, если вы используете другой упаковщик модулей, чем веб-пакет.

Атрибут targets в .babelrc определяет, какие браузеры являются мишенью. @babel/preset-env интегрируется со списком браузеров, а это значит, что вы можете найти полный список совместимых запросов, которые можно использовать в этом поле, в документации по списку браузеров .

Значение "last 2 versions" транспилирует код в приложении для двух последних версий каждого браузера.

Отладка

Чтобы получить полное представление обо всех целях Babel браузера, а также обо всех включенных преобразованиях и полифилах, добавьте поле debug в .babelrc:

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"debug": true
     
}
   
]
 
]
}
  • Нажмите «Инструменты» .
  • Нажмите Журналы .

Перезагрузите приложение и просмотрите журналы состояния сбоев в нижней части редактора.

Целевые браузеры

Babel записывает в консоль ряд подробностей о процессе компиляции, включая все целевые среды, для которых был скомпилирован код.

Целевые браузеры

Обратите внимание, что в этот список включены браузеры, выпуск которых прекращен, например Internet Explorer. Это проблема, поскольку в неподдерживаемые браузеры не будут добавлены новые функции, и Babel продолжает переносить для них определенный синтаксис. Это неоправданно увеличивает размер вашего пакета, если пользователи не используют этот браузер для доступа к вашему сайту.

Babel также записывает список используемых плагинов преобразования:

Список используемых плагинов

Это довольно длинный список! Это все плагины, которые необходимо использовать Babel для преобразования любого синтаксиса ES2015+ в более старый синтаксис для всех целевых браузеров.

Однако Babel не показывает какие-либо конкретные используемые полифилы:

Полифиллы не добавлены

Это связано с тем, что весь @babel/polyfill импортируется напрямую.

Загружать полифилы по отдельности

По умолчанию Babel включает все полифилы, необходимые для полной среды ES2015+, когда @babel/polyfill импортируется в файл. Чтобы импортировать определенные полифиллы, необходимые для целевых браузеров, добавьте в конфигурацию запись useBuiltIns: 'entry' .

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"debug": true
       
"useBuiltIns": "entry"
     
}
   
]
 
]
}

Перезагрузите приложение. Теперь вы можете увидеть все включенные в него полифилы:

Список импортированных полифилов

Хотя теперь включены только необходимые полифилы для "last 2 versions" , это по-прежнему очень длинный список! Это связано с тем, что полифилы, необходимые целевым браузерам для каждой новой функции, по-прежнему включены. Измените значение атрибута на usage , чтобы включать только те функции, которые используются в коде.

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"debug": true,
       
"useBuiltIns": "entry"
       
"useBuiltIns": "usage"
     
}
   
]
 
]
}

При этом полифилы автоматически включаются там, где это необходимо. Это означает, что вы можете удалить импорт @babel/polyfill в src/index.js.

import "./style.css";
import "@babel/polyfill";

Теперь включены только необходимые для приложения полифилы.

Список полифилов, включенных автоматически

Размер пакета приложений значительно уменьшен.

Размер пакета уменьшен до 30,1 КБ.

Сужение списка поддерживаемых браузеров

Число включенных целевых браузеров по-прежнему довольно велико, и немногие пользователи используют устаревшие браузеры, такие как Internet Explorer. Обновите конфигурации до следующего:

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"targets": [">0.25%", "not ie 11"],
       
"debug": true,
       
"useBuiltIns": "usage",
     
}
   
]
 
]
}

Взгляните на детали полученного комплекта.

Размер пакета 30,0 КБ.

Поскольку приложение настолько маленькое, в этих изменениях нет особой разницы. Однако рекомендуемым подходом является использование процентной доли рынка браузеров (например ">0.25%" » и исключение конкретных браузеров, которые, как вы уверены, не используют ваши пользователи. Чтобы узнать больше об этом, прочтите статью Джеймса Кайла «Последние 2 версии», которую считают вредной .

Используйте <script type="module">

Есть еще много возможностей для улучшения. Хотя ряд неиспользуемых полифилов был удален, многие из них уже поставляются и не нужны для некоторых браузеров. Используя модули, новый синтаксис может быть записан и отправлен в браузеры непосредственно без использования ненужных полифиллов.

Модули JavaScript - это относительно новая функция, поддерживаемая во всех основных браузерах . Модули могут быть созданы с помощью атрибута type="module" для определения сценариев, которые импортируют и экспортируют из других модулей. Например:

// math.mjs
export const add = (x, y) => x + y;

<!-- index.html -->
<script type="module">
 
import { add } from './math.mjs';

  add
(5, 2); // 7
</script>

Многие новые функции Ecmascript уже поддерживаются в средах, которые поддерживают модули JavaScript (вместо того, чтобы нуждаться в Babel.) Это означает, что конфигурация Babel может быть изменена для отправки двух разных версий вашего приложения в браузер:

  • Версия, которая будет работать в новых браузерах, которые поддерживают модули и включают в себя модуль, который в значительной степени не перевозчик, но имеет меньший размер файла
  • Версия, которая включает в себя больший транспилированный скрипт, который будет работать в любом устаревшем браузере

Использование модулей ES с вавиловой

Чтобы иметь отдельные настройки @babel/preset-env для двух версий приложения, удалите файл .babelrc . Настройки Babel могут быть добавлены в конфигурацию WebPack, указав два разных формата компиляции для каждой версии приложения.

Начните с добавления конфигурации для сценария Legacy в 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
}

Обратите внимание, что вместо использования значения targets для "@babel/preset-env" вместо этого используются esmodules со значением false . Это означает, что Babel включает в себя все необходимые преобразования и полифиллы для нацеливания на каждый браузер, который еще не поддерживает модули ES.

Добавьте объекты entry , cssRule и corePlugins в начало файла webpack.config.js . Все они разделены как между модулем, так и между сценариями устаревшего, обслуживаемых в браузер.

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"})
];

Теперь аналогично, создайте объект конфигурации для сценария модуля ниже, где определяется legacyConfig :

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
}

Основное отличие здесь заключается в том, что расширение файла .mjs используется для имени выходного файла. Здесь установлено значение esmodules , что означает, что код, выводимый в этот модуль, представляет собой меньший, менее скомпилированный скрипт, который не проходит какое -либо преобразование в этом примере, поскольку все используемые функции уже поддерживаются в браузерах, которые поддерживают модули.

В самом конце файла экспортируют обе конфигурации за один массив.

module.exports = [
  legacyConfig
, moduleConfig
];

Теперь это создает как меньший модуль для браузеров, которые поддерживают его, так и больший транспилированный сценарий для старых браузеров.

Браузеры, которые поддерживают модули, игнорируют сценарии с атрибутом nomodule . И наоборот, браузеры, которые не поддерживают модули, игнорируют элементы сценариев с помощью type="module" . Это означает, что вы можете включить модуль, а также скомпилированную запасную сторону. В идеале, две версии приложения должны быть в index.html , как это:

<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js"></script>

Браузеры, которые поддерживают модули, получают и выполняют main.mjs и игнорируют main.bundle.js. Браузеры, которые не поддерживают модули, делают наоборот.

Важно отметить, что в отличие от обычных сценариев, сценарии модуля всегда отложены по умолчанию. Если вы хотите, чтобы эквивалентный сценарий nomodule также был отложен и выполнен только после анализа, вам нужно добавить атрибут defer :

<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js" defer></script>

Последнее, что нужно сделать здесь, - это добавить атрибуты module и nomodule в сценарий модуля и наследия соответственно, импортируйте Scriptexthtmlwebpackplugin в самом верху 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");

Теперь обновите массив plugins в конфигурациях, чтобы включить этот плагин:

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: ''
    },
    ]
  })
];

Эти настройки плагина добавляют атрибут type="module" для всех элементов сценария .mjs , а также атрибут nomodule для всех модулей сценариев .js .

Модули обслуживания в документе HTML

Последнее, что необходимо сделать, это вывести как унаследованные, так и современные элементы сценария в файл HTML. К сожалению, плагин, который создает конечный HTML -файл, HTMLWebpackPlugin , в настоящее время не поддерживает вывод сценариев модуля и номодуля. Хотя для решения этой проблемы создаются обходные пути и отдельные плагины, такие как BabelmultiTargetPlugin и HTMlWebpackMultibuildPlugin , более простой подход к добавлению элемента скрипта модуля используется вручную для целей этого урока.

Добавьте следующее в src/index.js в конце файла:

    ...
   
</form>
    <script type="module" src="main.mjs"></
script>
 
</body>
</
html>

Теперь загрузите приложение в браузер, который поддерживает модули, такие как последняя версия Chrome.

5,2 КБ модуль, извлеченный по сети для новых браузеров

Только модуль извлекается, с гораздо меньшим размером пучка, потому что он в значительной степени не перерансформирован! Другой элемент сценария полностью игнорируется браузером.

Если вы загружаете приложение в более старый браузер, будут извлечены только более крупный трансфиль -скрипт со всеми необходимыми полифилами и преобразованием. Вот скриншот для всех запросов, сделанных на более старой версии Chrome (версия 38).

Сценарий 30 КБ, полученный для старых браузеров

Заключение

Теперь вы понимаете, как использовать @babel/preset-env чтобы обеспечить только необходимые полифиллы, необходимые для целевых браузеров. Вы также знаете, как модули JavaScript могут повысить производительность, отправив две разные транспилированные версии приложения. С приличным пониманием того, как обе эти методы могут значительно сократить размер пакета, идти вперед и оптимизировать!

,

В этом CodeLab улучшите производительность этого простого приложения, которое позволяет пользователям оценивать случайных кошек. Узнайте, как оптимизировать комплект JavaScript, Minizming, сколько кода переносится.

Скриншот приложения

В примере примера вы можете выбрать слово или смайлики, чтобы передать, как сильно вам нравится каждая кошка. Когда вы нажимаете кнопку, приложение отображает значение кнопки под текущим изображением кошки.

Мера

Это всегда хорошая идея, чтобы начать с проверки веб -сайта, прежде чем добавлять какие -либо оптимизации:

  1. Чтобы просмотреть сайт, нажмите «Просмотреть приложение» . Затем нажмите Полноэкранный режим полноэкранный .
  2. Нажмите «Control+Shift+J» (или «Command+Option+J» на Mac), чтобы открыть DevTools.
  3. Откройте вкладку «Сеть» .
  4. Установите флажок Отключить кеш .
  5. Перезагрузите приложение.

Оригинальный запрос размера пакета

Для этого приложения используется более 80 КБ! Время выяснить, не используются ли части пакета:

  1. Нажмите Control+Shift+P (или Command+Shift+P на Mac), чтобы открыть меню команды . Командование меню

  2. Введите Show Coverage и нажмите Enter , чтобы отобразить вкладку покрытия .

  3. На вкладке «Покрытие» нажмите «Перезагрузить» , чтобы перезагрузить приложение при захвате покрытия.

    Перезагрузить приложение с покрытием кода

  4. Взгляните на то, сколько кода было использовано в сравнении с тем, сколько было загружено для основного пакета:

    Покрытие кода

Более половины пакета (44 кб) даже не используется. Это связано с тем, что большая часть кода в пределах состоит из полифиллов, чтобы обеспечить работу приложения в более старых браузерах.

Используйте @babel/preset-env

Синтаксис языка JavaScript соответствует стандарту, известному как Ecmascript, или ECMA-262 . Новые версии спецификации выпускаются каждый год и включают новые функции, которые прошли процесс предложения. Каждый крупный браузер всегда находится на разных этапах поддержки этих функций.

В приложении используются следующие функции ES2015:

Также используется следующая функция ES2017:

Не стесняйтесь погрузиться в исходный код в src/index.js чтобы увидеть, как все это используется.

Все эти функции поддерживаются в последней версии Chrome, но как насчет других браузеров, которые не поддерживают их? Babel , который включен в приложение, является наиболее популярной библиотекой, используемой для компиляции кода, которая содержит новый синтаксис в код, который могут понять более старые браузеры и среды. Это делает это двумя способами:

  • Полифиллы включены для эмуляции новых функций ES2015+, чтобы их API могли быть использованы, даже если он не поддерживается браузером. Вот пример полифилла метода Array.includes .
  • Плагины используются для преобразования кода ES2015 (или более позднего цвета) в более старый синтаксис ES5. Поскольку это изменение синтаксиса (например, функции стрелки), их нельзя эмулировать полифилами.

Посмотрите на package.json , чтобы увидеть, какие библиотеки Babel включены:

"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 - основной компилятор Babel. При этом все конфигурации Babel определены в .babelrc в корне проекта.
  • babel-loader включает Babel в процессе сборки WebPack.

Теперь посмотрите на webpack.config.js чтобы увидеть, как babel-loader включен как правило:

module: {
  rules: [
    //...
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: "babel-loader"
    }
  ]
},
  • @babel/polyfill предоставляет все необходимые полифиллы для любых новых функций Ecmascript, чтобы они могли работать в средах, которые их не поддерживают. Он уже импортируется в самом верху src/index.js.
import "./style.css";
import "@babel/polyfill";
  • @babel/preset-env идентифицирует, какие преобразования и полифиллы необходимы для любых браузеров или сред, выбранных в качестве целей.

Взгляните на файл конфигураций Babel, .babelrc , чтобы увидеть, как он включен:

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions"
     
}
   
]
 
]
}

Это настройка Вавилона и Webpack. Узнайте, как включить Babel в свое приложение, если вы используете другой модульный пакет, чем WebPack.

Атрибут targets в .babelrc идентифицирует, на какие браузеры целенаправленно. @babel/preset-env интегрируется с Browserslist, что означает, что вы можете найти полный список совместимых запросов, которые можно использовать в этом поле в документации BrowserList .

Значение "last 2 versions" транспорт код в приложении для последних двух версий каждого браузера.

Отладка

Чтобы получить полный взгляд на все целевые показатели вавилоны браузера, а также на все включенные преобразования и полифиллы, добавьте поле debug в .babelrc:

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"debug": true
     
}
   
]
 
]
}
  • Нажмите инструменты .
  • Нажмите журналы .

Перезагрузите приложение и посмотрите на журналы статуса глюка в нижней части редактора.

Целевые браузеры

Babel регистрирует ряд деталей в консоли о процессе компиляции, включая все целевые среды, для которых код был составлен.

Целевые браузеры

Обратите внимание на то, как в этот список включены в этот список прерванные браузеры, такие как Internet Explorer. Это проблема, потому что у неподдерживаемых браузеров не будут добавлены более новые функции, и Вабель продолжает транспилировать специфический синтаксис для них. Это излишне увеличивает размер вашего пакета, если пользователи не используют этот браузер для доступа к вашему сайту.

Вавилон также регистрирует список используемых плагинов преобразования:

Список используемых плагинов

Это довольно длинный список! Это все плагины, которые Вабель должен использовать для преобразования любого синтаксиса ES2015+ в старый синтаксис для всех целевых браузеров.

Тем не менее, Babel не показывает никаких конкретных полифиллов, которые используются:

Полифилса не добавлено

Это связано с тем, что весь @babel/polyfill импортируется напрямую.

Загрузите многофиллы индивидуально

По умолчанию Babel включает в себя каждый полифилл, необходимый для полной среды ES2015+, когда @babel/polyfill импортируется в файл. Чтобы импортировать конкретные полифиллы, необходимые для целевых браузеров, добавьте в конфигурацию useBuiltIns: 'entry' .

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"debug": true
       
"useBuiltIns": "entry"
     
}
   
]
 
]
}

Перезагрузить приложение. Теперь вы можете увидеть все конкретные полифиллы, включенные:

Список импортированных полифиллов

Хотя теперь включены только необходимые полифиллы для "last 2 versions" , это все еще супер длинный список! Это связано с тем, что полифиллы, необходимые для целевых браузеров для каждой новой функции, все еще включены. Измените значение атрибута на usage , чтобы включить только те, которые необходимы для функций, которые используются в коде.

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"debug": true,
       
"useBuiltIns": "entry"
       
"useBuiltIns": "usage"
     
}
   
]
 
]
}

При этом многофиллы автоматически включаются в необходимость. Это означает, что вы можете удалить импорт @babel/polyfill в src/index.js.

import "./style.css";
import "@babel/polyfill";

Теперь включены только необходимые полифиллы, необходимые для применения.

Список полифиллов автоматически включен

Размер пакета применения значительно уменьшается.

Размер пакета уменьшен до 30,1 кб

Сужение списка поддерживаемых браузеров

Количество включенных целей браузера по -прежнему довольно большое, и не многие пользователи используют прекращенные браузеры, такие как Internet Explorer. Обновите конфигурации на следующее:

{
 
"presets": [
   
[
     
"@babel/preset-env",
     
{
       
"targets": "last 2 versions",
       
"targets": [">0.25%", "not ie 11"],
       
"debug": true,
       
"useBuiltIns": "usage",
     
}
   
]
 
]
}

Взгляните на детали для получения пакета.

Размер пакета 30,0 КБ

Поскольку приложение настолько мало, на самом деле не так уж и большой разницы с этими изменениями. Тем не менее, использование процента доли рынка браузеров (например ">0.25%" ), а также исключение конкретных браузеров, которые, как вы уверены, не используют, это рекомендуемый подход. Взгляните на «Последние 2 версии», которая считается вредной статьей Джеймса Кайла, чтобы узнать больше об этом.

Используйте <script type = "module">

Есть еще больше возможностей для улучшения. Несмотря на то, что ряд неиспользованных полифиллов были удалены, есть много отправляемых, которые не нужны для некоторых браузеров. Используя модули, новый синтаксис может быть записан и отправлен в браузеры непосредственно без использования ненужных полифиллов.

Модули JavaScript - это относительно новая функция, поддерживаемая во всех основных браузерах . Модули могут быть созданы с помощью атрибута type="module" для определения сценариев, которые импортируют и экспортируют из других модулей. Например:

// math.mjs
export const add = (x, y) => x + y;

<!-- index.html -->
<script type="module">
 
import { add } from './math.mjs';

  add
(5, 2); // 7
</script>

Многие новые функции Ecmascript уже поддерживаются в средах, которые поддерживают модули JavaScript (вместо того, чтобы нуждаться в Babel.) Это означает, что конфигурация Babel может быть изменена для отправки двух разных версий вашего приложения в браузер:

  • Версия, которая будет работать в новых браузерах, которые поддерживают модули и включают в себя модуль, который в значительной степени не перевозчик, но имеет меньший размер файла
  • Версия, которая включает в себя больший транспилированный скрипт, который будет работать в любом устаревшем браузере

Использование модулей ES с вавиловой

Чтобы иметь отдельные настройки @babel/preset-env для двух версий приложения, удалите файл .babelrc . Настройки Babel могут быть добавлены в конфигурацию WebPack, указав два разных формата компиляции для каждой версии приложения.

Начните с добавления конфигурации для сценария Legacy в 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
}

Обратите внимание, что вместо использования значения targets для "@babel/preset-env" вместо этого используются esmodules со значением false . Это означает, что Babel включает в себя все необходимые преобразования и полифиллы для нацеливания на каждый браузер, который еще не поддерживает модули ES.

Добавьте объекты entry , cssRule и corePlugins в начало файла webpack.config.js . Все они разделены как между модулем, так и между сценариями устаревшего, обслуживаемых в браузер.

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"})
];

Теперь аналогично, создайте объект конфигурации для сценария модуля ниже, где определяется legacyConfig :

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
}

Основное отличие здесь заключается в том, что расширение файла .mjs используется для имени выходного файла. Здесь установлено значение esmodules , что означает, что код, выводимый в этот модуль, представляет собой меньший, менее скомпилированный скрипт, который не проходит какое -либо преобразование в этом примере, поскольку все используемые функции уже поддерживаются в браузерах, которые поддерживают модули.

В самом конце файла экспортируют обе конфигурации за один массив.

module.exports = [
  legacyConfig
, moduleConfig
];

Теперь это создает как меньший модуль для браузеров, которые поддерживают его, так и больший транспилированный сценарий для старых браузеров.

Браузеры, которые поддерживают модули, игнорируют сценарии с атрибутом nomodule . И наоборот, браузеры, которые не поддерживают модули, игнорируют элементы сценариев с помощью type="module" . Это означает, что вы можете включить модуль, а также скомпилированную запасную сторону. В идеале, две версии приложения должны быть в index.html , как это:

<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js"></script>

Браузеры, которые поддерживают модули, получают и выполняют main.mjs и игнорируют main.bundle.js. Браузеры, которые не поддерживают модули, делают наоборот.

Важно отметить, что в отличие от обычных сценариев, сценарии модуля всегда отложены по умолчанию. Если вы хотите, чтобы эквивалентный сценарий nomodule также был отложен и выполнен только после анализа, вам нужно добавить атрибут defer :

<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js" defer></script>

Последнее, что нужно сделать здесь, - это добавить атрибуты module и nomodule в сценарий модуля и наследия соответственно, импортируйте Scriptexthtmlwebpackplugin в самом верху 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");

Теперь обновите массив plugins в конфигурациях, чтобы включить этот плагин:

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: ''
    },
    ]
  })
];

Эти настройки плагина добавляют атрибут type="module" для всех элементов сценария .mjs , а также атрибут nomodule для всех модулей сценариев .js .

Модули обслуживания в документе HTML

Последнее, что необходимо сделать, это вывести как унаследованные, так и современные элементы сценария в файл HTML. К сожалению, плагин, который создает конечный HTML -файл, HTMLWebpackPlugin , в настоящее время не поддерживает вывод сценариев модуля и номодуля. Хотя для решения этой проблемы создаются обходные пути и отдельные плагины, такие как BabelmultiTargetPlugin и HTMlWebpackMultibuildPlugin , более простой подход к добавлению элемента скрипта модуля используется вручную для целей этого урока.

Добавьте следующее в src/index.js в конце файла:

    ...
   
</form>
    <script type="module" src="main.mjs"></
script>
 
</body>
</
html>

Теперь загрузите приложение в браузер, который поддерживает модули, такие как последняя версия Chrome.

5,2 КБ модуль, извлеченный по сети для новых браузеров

Только модуль извлекается, с гораздо меньшим размером пучка, потому что он в значительной степени не перерансформирован! Другой элемент сценария полностью игнорируется браузером.

Если вы загружаете приложение в более старый браузер, будут извлечены только более крупный трансфиль -скрипт со всеми необходимыми полифилами и преобразованием. Вот скриншот для всех запросов, сделанных на более старой версии Chrome (версия 38).

Сценарий 30 КБ, полученный для старых браузеров

Заключение

Теперь вы понимаете, как использовать @babel/preset-env чтобы обеспечить только необходимые полифиллы, необходимые для целевых браузеров. Вы также знаете, как модули JavaScript могут повысить производительность, отправив две разные транспилированные версии приложения. С приличным пониманием того, как обе эти методы могут значительно сократить размер пакета, идти вперед и оптимизировать!