Введение в esbuild Bundler

Введение в esbuild Bundler Изучение

esbuild — это быстрый сборщик, который может оптимизировать код JavaScript, TypeScript, JSX и CSS. Эта статья поможет вам быстро освоиться с esbuild и покажет, как создать собственную систему сборки без других зависимостей.

Как работает esbuild?

Фреймворки, такие как Vite, приняли esbuild, но вы можете использовать esbuild как отдельный инструмент в своих собственных проектах.

  • esbuild упаковывает код JavaScript в один файл аналогично сборщикам, таким как Rollup. Это основная функция esbuild, и она разрешает модули, сообщает о проблемах синтаксиса, «встряхивает дерево» для удаления неиспользуемых функций, стирает операторы ведения журнала и отладчика, минимизирует код и предоставляет исходные карты.
  • esbuild объединяет код CSS в один файл. Это не полная замена препроцессорам, таким как Sass или PostCSS, но esbuild может обрабатывать частичные, синтаксические проблемы, вложенность, встроенное кодирование ресурсов, исходные карты, автоматическое префиксирование и минимизацию. Это может быть все, что вам нужно.
  • esbuild также предоставляет локальный сервер разработки с автоматическим связыванием и горячей перезагрузкой, поэтому нет необходимости в обновлении. Он не обладает всеми функциями, предлагаемыми Browsersync, но в большинстве случаев его достаточно.

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

Почему Bundle?

Объединение кода в один файл дает различные преимущества. Вот некоторые из них:

  • вы можете разрабатывать небольшие автономные исходные файлы, которые легче поддерживать
  • вы можете анализировать, претифицировать и проверять синтаксис кода в процессе сборки
  • упаковщик может удалять неиспользуемые функции — это называется встряхиванием дерева
  • вы можете объединять альтернативные версии одного и того же кода и создавать цели для старых браузеров, Node.js, Deno и т. д.
  • отдельные файлы загружаются быстрее, чем несколько файлов, и браузеру не требуется поддержка модуля ES.
  • объединение на производственном уровне может повысить производительность за счет минимизации кода и удаления операторов ведения журнала и отладки.
Читайте также:  Учебник по динамическому программированию: создание эффективных программ на Python

Зачем использовать esbuild?

В отличие от сборщиков JavaScript, esbuild представляет собой скомпилированный исполняемый файл Go, который реализует тяжелую параллельную обработку. Это быстро и до ста раз быстрее, чем Rollup, Parcel или Webpack. Это может сэкономить недели времени разработки в течение всего жизненного цикла проекта.

Кроме того, esbuild также предлагает:

  • встроенная сборка и компиляция для JavaScript, TypeScript, JSX и CSS
  • командная строка, JavaScript и API конфигурации Go
  • поддержка модулей ES и CommonJS
  • локальный сервер разработки с режимом просмотра и перезагрузкой в ​​реальном времени
  • плагины для добавления дополнительных функций
  • исчерпывающая документация и онлайн-инструмент для экспериментов

Почему следует избегать esbuild?

На момент написания esbuild достиг версии 0.18. Это надежный, но все еще бета-продукт.

esbuild часто обновляется, и параметры могут меняться между версиями. Документация рекомендует придерживаться определенной версии. Вы можете обновить его, но вам может потребоваться перенести файлы конфигурации и изучить новую документацию, чтобы обнаружить критические изменения.

Также обратите внимание, что esbuild не выполняет проверку типов TypeScript, поэтому вам все равно нужно запустить tsc -noEmit.

Супер-быстрый старт

При необходимости создайте новый проект Node.js с помощью npm init, затем установите esbuild локально в качестве зависимости разработки:

npm install esbuild --save-dev --save-exact

Для установки требуется около 9MB. Убедитесь, что это работает, выполнив эту команду, чтобы увидеть установленную версию:

./node_modules/.bin/esbuild --version

Или запустите эту команду, чтобы просмотреть справку CLI:

./node_modules/.bin/esbuild --help

Используйте CLI API для объединения сценария ввода ( myapp.js) и всех его импортированных модулей в один файл с именем bundle.js. esbuild выведет файл, используя формат по умолчанию, предназначенный для браузера, с немедленным вызовом функционального выражения (IIFE):

./node_modules/.bin/esbuild myapp.js --bundle --outfile=bundle.js

Вы можете установить esbuild другими способами, если вы не используете Node.js.

Пример проекта

Загрузите файлы примеров и конфигурацию esbuild с Github. Это проект Node.js, поэтому установите единственную зависимость esbuild с помощью:

npm install

Соберите исходные файлы в srcкаталог buildи запустите сервер разработки с помощью:

npm start

Теперь перейдите в localhost:8000браузере, чтобы просмотреть веб-страницу с часами реального времени. Когда вы обновляете какой-либо файл CSS в src/css/или src/css/partials, esbuild повторно объединяет код и перезагружает стили в реальном времени.

ы просмотреть веб-страницу с часами реального вре

Нажмите Ctrl|Cmd+ Ctrl|Cmd, чтобы остановить сервер.

Создайте производственную сборку для развертывания, используя:

npm run build

Изучите файлы CSS и JavaScript в buildкаталоге, чтобы увидеть уменьшенные версии без исходных карт.

Обзор проекта

Страница часов реального времени создается в buildкаталоге с использованием исходных файлов из src.

Файл package.jsonопределяет пять npmсценариев. Первый удаляет buildкаталог:

"clean": "rm -rf ./build",

Прежде чем произойдет какое-либо связывание, initзапускается скрипт clean, создает новый buildкаталог и копирует:

  1. статический HTML-файл от src/html/index.htmlдоbuild/index.html
  2. статические изображения от src/images/доbuild/images/
"init": "npm run clean && mkdir ./build && cp ./src/html/* ./build/ && cp -r ./src/images ./build",

Файл esbuild.config.jsуправляет процессом сборки esbuild с помощью JavaScript API. Это проще в управлении, чем передача параметров в CLI API, что может стать громоздким. npm bundleЗапускается скрипт, за initкоторым следует node./esbuild.config.js:

"bundle": "npm run init && node ./esbuild.config.js",

Последние два npmскрипта запускаются bundleс параметром productionили, developmentпереданным./esbuild.config.jsдля управления сборкой:

"build": "npm run bundle -- production",
"start": "npm run bundle -- development"

При./esbuild.config.jsзапуске он определяет, следует ли создавать мини- productionфайлы (по умолчанию) или developmentфайлы с автоматическими обновлениями, исходными картами и сервером с перезагрузкой в ​​реальном времени. В обоих случаях esbuild пакеты:

  • входной файл CSS src/css/main.cssдляbuild/css/main.css
  • входной файл JavaScript scr/js/main.jsдляbuild/js/main.js

Настройка esbuild

package.jsonимеет «type»так «module»что все.jsфайлы могут использовать модули ES. Сценарий esbuild.config.jsимпортирует esbuildи устанавливает productionModeпри trueкомплектации для производства или falseпри комплектации для разработки:

import { argv } from 'node:process';
import * as esbuild from 'esbuild';

const
  productionMode = ('development' !== (argv[2] || process.env.NODE_ENV)),
  target = 'chrome100,firefox100,safari15'.split(',');

console.log(`${ productionMode ? 'production' : 'development' } build`);

Объединить цель

Обратите внимание, что целевая переменная определяет массив браузеров и номеров версий для использования в конфигурации. Это влияет на объединенный вывод и изменяет синтаксис для поддержки определенных платформ. Например, esbuild может:

  • расширить нативную вложенность CSS в полные селекторы (вложенность осталась бы, если бы «Chrome115″была единственной целью)
  • добавьте свойства с префиксом поставщика CSS, где это необходимо
  • polyfill ??нулевой оператор объединения
  • удалить #из полей закрытого класса

Помимо браузеров, вы также можете настроить таргетинг nodeна esтакие версии, как es2020и esnext(последние функции JS и CSS).

Объединение JavaScript

Самый простой API для создания бандла:

await esbuild.build({
  entryPoints: ['myapp.js'],
  bundle: true
  outfile: 'bundle.js'
});

Это повторяет команду CLI, использованную выше:

./node_modules/.bin/esbuild myapp.js --bundle --outfile=bundle.js

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

// bundle JS
const buildJS = await esbuild.context({

  entryPoints: [ './src/js/main.js' ],
  format: 'esm',
  bundle: true,
  target,
  drop: productionMode ? ['debugger', 'console'] : [],
  logLevel: productionMode ? 'error' : 'info',
  minify: productionMode,
  sourcemap: !productionMode && 'linked',
  outdir: './build/js'

});

esbuild предлагает десятки вариантов конфигурации. Вот краткое изложение тех, которые используются здесь:

  • entryPointsопределяет массив точек входа файла для объединения. В примере проекта есть один скрипт по адресу./src/js/main.js.
  • formatустанавливает выходной формат. В примере используется esm, но при желании вы можете установить iifeдля старых браузеров или commonjsдля Node.js.
  • bundleустановить trueимпортированные модули в выходной файл.
  • target— это массив целевых браузеров, определенный выше.
  • dropпредставляет собой массив операторов consoleи/или debuggerдля удаления. В этом случае рабочие сборки удаляют оба, а разрабатываемые сборки сохраняют их.
  • logLevelопределяет подробность ведения журнала. В приведенном выше примере показаны ошибки во время рабочих сборок и более подробные информационные сообщения во время сборок для разработки.
  • minifyуменьшает размер кода, удаляя комментарии и пробелы и переименовывая переменные и функции, где это возможно. Пример проекта минимизируется во время производственных сборок, но улучшает код во время сборок для разработки.
  • sourcemapустановлен в linked(только в режиме разработки) создает связанную исходную карту в файле,.mapпоэтому исходный исходный файл и строка доступны в инструментах разработчика браузера. Вы также можете настроить inlineвключение исходной карты в связанный файл, bothсоздать и то, и другое или externalсоздать.mapфайл без ссылки из связанного JavaScript.
  • outdirопределяет выходной каталог связанного файла.

Вызовите метод объекта контекста rebuild(), чтобы запустить сборку один раз — обычно для рабочей сборки:

await buildJS.rebuild();
buildJS.dispose(); // free up resources

Вызовите метод объекта контекста watch(), чтобы он продолжал работать и автоматически перестраивался при изменении отслеживаемых файлов:

await buildJS.watch();

Объект контекста обеспечивает пошаговую обработку последующих сборок и повторное использование результатов предыдущих сборок для повышения производительности.

Входные и выходные файлы JavaScript

Входной src/js/main.jsфайл импортирует dom.jsи time.jsмодули из libподпапки. Он находит все элементы с классом clockи устанавливает их текстовое содержимое в текущее время каждую секунду:

import * as dom from './lib/dom.js';
import { formatHMS } from './lib/time.js';

// get clock element
const clock = dom.getAll('.clock');

if (clock.length) {

  console.log('initializing clock');

  setInterval(() => {

    clock.forEach(c => c.textContent = formatHMS());

  }, 1000);

}

dom.jsэкспортирует две функции. main.jsимпортирует оба, но использует только getAll():

// DOM libary

// fetch first node from selector
export function get(selector, doc = document) {
  return doc.querySelector(selector);
}

// fetch all nodes from selector
export function getAll(selector, doc = document) {
  return Array.from(doc.querySelectorAll(selector));
}

time.jsэкспортирует две функции. main.jsimports formatHMS(), но при этом используются другие функции модуля:

// time library

// return 2-digit value
function timePad(n) {
  return String(n).padStart(2, '0');
}

// return time in HH:MM format
export function formatHM(d = new Date()) {
  return timePad(d.getHours()) + ':' + timePad(d.getMinutes());
}

// return time in HH:MM:SS format
export function formatHMS(d = new Date()) {
  return formatHM(d) + ':' + timePad(d.getSeconds());
}

Полученный пакет разработки удаляет (tree Shakes), get()но dom.jsвключает все time.jsфункции. Также генерируется исходная карта:

// src/js/lib/dom.js
function getAll(selector, doc = document) {
  return Array.from(doc.querySelectorAll(selector));
}

// src/js/lib/time.js
function timePad(n) {
  return String(n).padStart(2, "0");
}

function formatHM(d = new Date()) {
  return timePad(d.getHours()) + ":" + timePad(d.getMinutes());
}

function formatHMS(d = new Date()) {
  return formatHM(d) + ":" + timePad(d.getSeconds());
}

// src/js/main.js
var clock = getAll(".clock");
if (clock.length) {
  console.log("initializing clock");
  setInterval(() => {
    clock.forEach((c) => c.textContent = formatHMS());
  }, 1e3);
}
//# sourceMappingURL=main.js.map

(Обратите внимание, что esbuild может переписать let и constto varдля корректности и скорости.)

Полученный производственный пакет минимизирует код до 322 символов:

function o(t,c=document){return Array.from(c.querySelectorAll(t))}function e(t){return String(t).padStart(2,"0")}function l(t=new Date){return e(t.getHours())+":"+e(t.getMinutes())}function r(t=new Date){return l(t)+":"+e(t.getSeconds())}var n=o(".clock");n.length&&setInterval(()=>{n.forEach(t=>t.textContent=r())},1e3);

Связка CSS

Связывание CSS в примере проекта использует объект контекста, аналогичный приведенному выше JavaScript:

// bundle CSS
const buildCSS = await esbuild.context({

  entryPoints: [ './src/css/main.css' ],
  bundle: true,
  target,
  external: ['/images/*'],
  loader: {
    '.png': 'file',
    '.jpg': 'file',
    '.svg': 'dataurl'
  },
  logLevel: productionMode ? 'error' : 'info',
  minify: productionMode,
  sourcemap: !productionMode && 'linked',
  outdir: './build/css'

});

Он определяет externalпараметр как массив файлов и путей, которые нужно исключить из сборки. В примере проекта файлы из src/images/каталога копируются в buildкаталог, чтобы HTML, CSS или JavaScript могли ссылаться на них напрямую. Если бы это не было установлено, esbuild копировал бы файлы в выходной build/css/каталог при их использовании в background-imageили подобных свойствах.

Параметр loaderизменяет способ обработки esbuild импортированного файла, на который не ссылаются как на externalресурс. В этом примере:

  • Изображения SVG встраиваются как URI данных.
  • Изображения PNG и JPG копируются в build/css/каталог и ссылаются на них как на файлы.

Входные и выходные файлы CSS

Файл записи src/css/main.cssимпортируется variables.cssи elements.cssиз partialsподпапки:

/* import */
@import './partials/variables.css';
@import './partials/elements.css';

variables.cssопределяет пользовательские свойства по умолчанию:

/* primary variables */
:root {
  --font-body: sans-serif;
  --color-fore: #fff;
  --color-back: #112;
}

elements.cssопределяет все стили. Примечание:

  • имеет bodyфоновое изображение, загруженное из внешнего imagesкаталога
  • вложен h1внутриheader
  • имеет h1фон SVG, который будет встроен
  • целевые браузеры не требуют префиксов поставщиков
/* element styling */
*, *::before, ::after {
  box-sizing: border-box;
  font-weight: normal;
  padding: 0;
  margin: 0;
}

body {
  font-family: var(--font-body);
  color: var(--color-fore);
  background: var(--color-back) url(/images/web.png) repeat;
  margin: 1em;
}

/* nested elements with inline icon */
header {

  & h1 {
    font-size: 2em;
    padding-left: 1.5em;
    margin: 0.5em 0;
    background: url(../../icons/clock.svg) no-repeat;
  }

}

.clock {
  display: block;
  font-size: 5em;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

Получившийся пакет разработки расширяет вложенный синтаксис, встраивает SVG и генерирует исходную карту:

/* src/css/partials/variables.css */
:root {
  --font-body: sans-serif;
  --color-fore: #fff;
  --color-back: #112;
}

/* src/css/partials/elements.css */
*,
*::before,
::after {
  box-sizing: border-box;
  font-weight: normal;
  padding: 0;
  margin: 0;
}
body {
  font-family: var(--font-body);
  color: var(--color-fore);
  background: var(--color-back) url(/images/web.png) repeat;
  margin: 1em;
}
header h1 {
  font-size: 2em;
  padding-left: 1.5em;
  margin: 0.5em 0;
  background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><style>*{fill:none;stroke:%23fff;stroke-width:1.5;stroke-miterlimit:10}<\/style></defs><circle cx="12" cy="12" r="10.5"></circle><circle cx="12" cy="12" r="0.95"></circle><polyline points="12 4.36 12 12 16.77 16.77"></polyline></svg>') no-repeat;
}
.clock {
  display: block;
  font-size: 5em;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* src/css/main.css */
/*# sourceMappingURL=main.css.map */

Получившийся производственный пакет уменьшит код до 764 символов (здесь опущен SVG):

:root{--font-body: sans-serif;--color-fore: #fff;--color-back: #112}*,*:before,:after{box-sizing:border-box;font-weight:400;padding:0;margin:0}body{font-family:var(--font-body);color:var(--color-fore);background:var(--color-back) url(/images/web.png) repeat;margin:1em}header h1{font-size:2em;padding-left:1.5em;margin:.5em 0;background:url('data:image/svg+xml,<svg...></svg>') no-repeat}.clock{display:block;font-size:5em;text-align:center;font-variant-numeric:tabular-nums}

Наблюдение, восстановление и служение

Оставшаяся часть esbuild.config.jsскрипта объединяется один раз для производственных сборок перед завершением:

if (productionMode) {

  // single production build
  await buildCSS.rebuild();
  buildCSS.dispose();

  await buildJS.rebuild();
  buildJS.dispose();

}

Во время сборок разработки скрипт продолжает работать, отслеживает изменения файлов и снова автоматически объединяется. Контекст buildCSSзапускает веб-сервер разработки с build/корневым каталогом:

else {

  // watch for file changes
  await buildCSS.watch();
  await buildJS.watch();

  // development server
  await buildCSS.serve({
    servedir: './build'
  });

}

Запустите сборку разработки с помощью:

npm start

Затем перейдите к localhost:8000 для просмотра страницы.

В отличие от Browsersync, вам потребуется добавить собственный код на страницы разработки для перезагрузки в реальном времени. Когда происходят изменения, esbuild отправляет информацию об обновлении через событие, отправленное сервером. Самый простой вариант — полностью перезагрузить страницу при любых изменениях:

new EventSource('/esbuild').addEventListener('change', () => location.reload());

В примере проекта объект контекста CSS используется для создания сервера. Это потому, что я предпочитаю вручную обновлять изменения JavaScript — и потому, что я не смог найти способ, с помощью которого esbuild может отправлять события как для обновлений CSS, так и для JS! HTML-страница включает следующий сценарий для замены обновленных файлов CSS без полного обновления страницы (горячая перезагрузка):

<script type="module">
// esbuild server-sent event - live reload CSS
new EventSource('/esbuild').addEventListener('change', e => {

  const { added, removed, updated } = JSON.parse(e.data);

  // reload when CSS files are added or removed
  if (added.length || removed.length) {
    location.reload();
    return;
  }

  // replace updated CSS files
  Array.from(document.getElementsByTagName('link')).forEach(link => {

    const url = new URL(link.href), path = url.pathname;

    if (updated.includes(path) && url.host === location.host) {

      const css = link.cloneNode();
      css.onload = () => link.remove();
      css.href = `${ path }?${ +new Date() }`;
      link.after(css);

    }

  })

});

Обратите внимание, что esbuild в настоящее время не поддерживает горячую перезагрузку JavaScript — не то чтобы я все равно ему доверял!

Заключение

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

Существует полный набор плагинов, если вам требуется более продвинутая функциональность. Имейте в виду, что они часто включают Sass, PostCSS или аналогичные инструменты сборки, поэтому они эффективно используют esbuild в качестве средства запуска задач. Вы всегда можете создать свои собственные плагины, если вам нужны более легкие настраиваемые параметры.

Пользуюсь esbuild год. Скорость поразительна по сравнению с аналогичными сборщиками, и часто появляются новые функции. Единственным незначительным недостатком являются критические изменения, требующие обслуживания.

Оцените статью
bestprogrammer.ru
Добавить комментарий