Руководство по webpack

Время на прочтение
9 мин

Количество просмотров 221K

Доброго времени суток, друзья!

Представляю вашему вниманию перевод статьи «Webpack: A gentle introduction» автора Tyler McGinnis.

Перед изучением новой технологии задайте себе два вопроса:

  1. Зачем нужен этот инструмент?
  2. Какие задачи он выполняет?

Если вы не можете ответить на эти вопросы, возможно, вам не нужна изучаемая технология. Давайте попробуем ответить на эти вопросы применительно к Webpack.

Зачем нужен вебпак?

Вебпак — это сборщик модулей. Он анализирует модули приложения, создает граф зависимостей, затем собирает модули в правильном порядке в один или более бандл (bundle), на который может ссылаться файл «index.html».

App.js ->       |
Dashboard.js -> | Bundler | -> bundle.js
About.js ->     |

Какие проблемы решает вебпак?

Обычно, при создании приложения на JavaScript, код разделяется на несколько частей (модулей). Затем в файле «index.html» необходимо указать ссылку на каждый скрипт.

<body>

    ...
    
    <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
    <script src="libs/react.min.js"></script>
    <script src='src/admin.js'></script>
    <script src='src/dashboard.js'></script>
    <script src='src/api.js'></script>
    <script src='src/auth.js'></script>
    <script src='src/rickastley.js'></script>
</body>

Это не только утомительно, но и подвержено ошибкам. Важно не только не забыть про какой-нибудь скрипт, но и расположить их в правильном порядке. Если загрузить скрипт, зависящий от React, до загрузки самого React, приложение сломается. Вебпак решает эти задачи. Не нужно беспокоиться о последовательном включении всех скриптов.

<body>

    ...
    
    <script src='dist/bundle.js'></script>
</body>

Как мы скоро узнаем, сбор модулей является лишь одним из аспектов работы вебпака. При необходимости можно заставить вебпак осуществить некоторые преобразования модулей перед их добавлением в бандл. Например, преобразование SASS/LESS в обычный CSS, или современного JavaScript в ES5 для старых браузеров.

Установка вебпака

После инициализации проекта с помощью npm, для работы вебпака нужно установить два пакета — webpack и webpack-cli.

npm i webpack webpack-cli -D

webpack.config.js

После установки указанных пакетов, вебпак нужно настроить. Для этого создается файл webpack.config.js, который экспортирует объект. Этот объект содержит настройки вебпака.

module.exports = {}

Основной задачей вебпака является анализ модулей, их опциональное преобразование и интеллектуальное объединение в один или более бандл, поэтому вебпаку нужно знать три вещи:

  1. Точка входа приложения
  2. Преобразования, которые необходимо выполнить
  3. Место, в которое следует поместить сформированный бандл

Точка входа

Сколько бы модулей не содержало приложение, всегда имеется единственная точка входа. Этот модуль включает в себя остальные. Обычно, таким файлом является index.js. Это может выглядеть так:

index.js
  imports about.js
  imports dashboard.js
    imports graph.js
    imports auth.js
      imports api.js

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

module.exports = {
    entry: './app/index.js'
}

Преобразования с помощью лоадеров (loaders)

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

По умолчанию при создании графика зависимостей на основе операторов import / require() вебпак способен обрабатывать только JavaScript и JSON-файлы.

import auth from './api/auth' // 
import config from './utils/config.json' // 
import './styles.css' // ️
import logo from './assets/logo.svg' // ️

Едва ли в своем приложении вы решитесь ограничиться JS и JSON-файлами, скорее всего, вам также потребуются стили, SVG, изображения и т.д. Вот где нужны лоадеры. Основной задачей лоадеров, как следует из их названия, является предоставление вебпаку возможности работать не только с JS и JSON-файлами.

Первым делом нужно установить лоадер. Поскольку мы хотим загружать SVG, с помощью npm устанавливаем svg-loader.

npm i svg-inline-loader -D 

Далее добавляем его в настройки вебпака. Все лоадеры включаются в массив объектов module.rules:

module.exports = {
    entry: './app/index.js',
    module: {
        rules: []
    }
}

Информация о лоадере состоит из двух частей. Первая — тип обрабатываемых файлов (.svg в нашем случае). Вторая — лоадер, используемый для обработки данного типа файлов (svg-inline-loader в нашем случае).

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /.svg$/, use: 'svg-inline-loader' }
    ]
  }
}

Теперь мы можем импортировать SVG-файлы. Но что насчет наших CSS-файлов? Для стилей используется css-loader.

npm i css-loader -D 

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /.svg$/, use: 'svg-inline-loader' },
      { test: /.css$/, use: 'css-loader' }
    ]
  }
}

Теперь мы можем импортировать как SVG, так и CSS-файлы. Однако для того, чтобы наши стили работали корректно, нужно добавить еще один лоадер. Благодаря css-loader мы можем импортировать CSS-файлы. Но это не означает, что они будут включены в DOM. Мы хотим не только импортировать такие файлы, но и поместить их в тег <style>, чтобы они применялись к элементам DOM. Для этого нужен style-loader.

npm i style-loader -D 

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /.svg$/, use: 'svg-inline-loader' },
      { test: /.css$/, use: [ 'style-loader', 'css-loader' ] }
    ]
  }
}

Обратите внимание, что поскольку для обработки CSS-файлов используется два лоадера, значением свойства use является массив. Также обратите внимание на порядок следования лоадеров, сначала style-loader, затем css-loader. Это важно. Вебпак будет применять их в обратном порядке. Сначала он использует css-loader для импорта './styles.css', затем style-loader для внедрения стилей в DOM.

Лоадеры могут использоваться не только для импорта файлов, но и для их преобразования. Самым популярным является преобразование JavaScript следующего поколения в современный JavaScript с помощью Babel. Для этого используется babel-loader.

npm i babel-loader -D 

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /.svg$/, use: 'svg-inline-loader' },
      { test: /.css$/, use: [ 'style-loader', 'css-loader' ] },
      { test: /.(js)$/, use: 'babel-loader' }
    ]
  }
}

Существуют лоадеры почти для любого типа файлов.

Точка выхода

Теперь вебпак знает о точке входа и лоадерах. Следующим шагом является указание директории для бандла. Для этого нужно добавить свойство output в настройки вебпака.

const path = require('path')

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /.svg$/, use: 'svg-inline-loader' },
      { test: /.css$/, use: [ 'style-loader', 'css-loader' ] },
      { test: /.(js)$/, use: 'babel-loader' }
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index_bundle.js'
  }
}

Весь процесс выглядит примерно так:

  1. Вебпак получает точку входа, находящуюся в ./app/index.js
  2. Он анализирует операторы import / require и создает граф зависимостей
  3. Вебпак начинает собирать бандл, преобразовывая код с помощью соответствующих лоадеров
  4. Он собирает бандл и помещает его в dist/index_bundle.js

Плагины (plugins)

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

Давайте рассмотрим пример.

HtmlWebpackPlugin

Основной задачей вебпака является генерация бандла, на который можно сослаться в index.html.

HtmlWebpackPlugin создает index.html в директории с бандлом и автоматически добавляет в него ссылку на бандл.

Мы назвали бандл index_bundle.js и поместили его в dist. HtmlWebpackPlugin создаст новый файл index.html в директории dist и добавит в него ссылку на бандл — <script src='index_bundle.js'></script>. Здорово, правда? Поскольку index.html генерируется HtmlWebpackPlugin, даже если мы изменим точку выхода или название бандла, HtmlWebpackPlugin получит эту информацию и изменить содержимое index.html.

Как нам использовать этот плагин? Как обычно, сначала его нужно установить.

npm i html-webpack-plugin -D 

Далее добавляем в настройки вебпака свойство plugins.

const path = require('path')

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /.svg$/, use: 'svg-inline-loader' },
      { test: /.css$/, use: [ 'style-loader', 'css-loader' ] },
      { test: /.(js)$/, use: 'babel-loader' }
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index_bundle.js'
  },
  plugins: []
}

Создаем экземпляр HtmlWebpackPlugin в массиве plugins.


const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /.svg$/, use: 'svg-inline-loader' },
      { test: /.css$/, use: [ 'style-loader', 'css-loader' ] },
      { test: /.(js)$/, use: 'babel-loader' }
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index_bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin()
  ]
}

EnvironmentPlugin

Если вы используете React, то захотите установить process.env.NODE_ENV в значение production перед разворачиванием (деплоем) приложения. Это позволит React осуществить сборку в режиме продакшна, удалив инструменты разработки, такие как предупреждения. Вебпак позволяет это сделать посредством плагина EnvironmentPlugin. Он является частью вебпака, так что его не нужно устанавливать.

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /.svg$/, use: 'svg-inline-loader' },
      { test: /.css$/, use: [ 'style-loader', 'css-loader' ] },
      { test: /.(js)$/, use: 'babel-loader' }
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index_bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin(),
    new webpack.EnvironmentPlugin({
      'NODE_ENV': 'production'
    })
  ]
}

Теперь в любом месте нашего приложения мы можем установить режим продакшна с помощью process.env.NODE_ENV.

HtmlWebpackPlugin и EnvironmentPlugin — это лишь небольшая часть системы плагинов вебпака.

Режим (mode)

В процессе подготовки приложения к продакшну, необходимо выполнить несколько действий. Мы только что рассмотрели одно из них — установку process.env.NODE_ENV в значение production. Другое действие заключается в минификации кода и удалении комментариев для уменьшения размера бандла.

Существуют специальные плагины для решения указанных задачи, но есть более легкий способ. В настройках вебпака можно установить mode в значение development или production в зависимости от среды разработки.

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /.svg$/, use: 'svg-inline-loader' },
      { test: /.css$/, use: [ 'style-loader', 'css-loader' ] },
      { test: /.(js)$/, use: 'babel-loader' }
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index_bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin()
  ],
  mode: 'production'
}

Обратите внимание, что мы удалили EnvironmentPlugin. Дело в том, что после установки mode в значение production вебпак автоматически присваивает process.env.NODE_ENV значение production. Это также минифицирует код и удаляет предупреждения.

Запуск вебпака

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

У нас есть файл package.json, в котором мы можем создать script для запуска webpack.

"scripts": {
  "build": "webpack"
}

Теперь при выполнении команды npm run build в терминале будет запущен вебпак, который создаст оптимизированный бандл index_bundle.js и поместит его в dist.

Режимы разработки и продакшна

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

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

Для переключения между режимами необходимо создать два скрипта в package.json.

npm run build будет собирать продакшн-бандл.

npm run start будет запускать сервер для разработки и следить за изменениями файлов.

Если помните, мы установили mode в значение production в настроках вебпака. Однако теперь нам это не нужно. Мы хотим, чтобы переменная среды имела соответствующее значение в зависимости от выполняемой команды. Немного изменим скрипт build в package.json.

"scripts": {
  "build": "NODE_ENV='production' webpack",
}

Если у вас Windows, то команда будет такой: "SET NODE_ENV='production' && webpack".

Теперь в настроках вебпака мы можем менять значение mode в зависимости от process.env.NODE_ENV.

...

  mode: process.env.NODE_ENV === 'production' ? 'production' : 'development'
}

Для сборки готового бандла для нашего приложения мы просто запускаем npm run build в терминале. В директории dist создаются файлы index.html и index_bunlde.js.

Сервер для разработки

Когда речь идет о разработке приложения принципиально важное значение имеет скорость. Мы не хотим презапускать вебпак и ждать новую сборку при каждом изменении. Вот где нам пригодится пакет webpack-dev-server.

Как следует из названия, это вебпак-сервер для разработки. Вместо создания дирекории dist, он хранит данные в памяти и обрабатывает их на локальном сервере. Более того, он поддерживает живую перезагрузку. Это означает, что при любом изменении webpack-dev-server пересоберет файлы и перезапустит браузер.

Устанавливаем пакет.

npm i webpack-dev-server -D 

Все, что осталось сделать, это добавить скрипт start в package.json.

"scripts": {
  "build": "NODE_ENV='production' webpack",
  "start": "webpack-dev-server"
}

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

Надеюсь, статья была вам полезной. Благодарю за внимание.

webpack — это сборщик модулей JavaScript с открытым исходным кодом. Он создан в первую очередь для JavaScript, но может преобразовывать внешние ресурсы, такие как HTML, CSS и изображения, если включены соответствующие загрузчики. webpack принимает модули с зависимостями и генерирует статические ресурсы, представляющие эти модули.

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

Какую проблему решает Webpack

Webpack решает проблему вечного подключения библиотек и фреймворков к HTML. В каком порядке их подключать, как решать конфликты, как сразу же оптимизировать картинки, как включать css в JavaScript, подобно тому, как это делается в React? Всеми этими вопросами занимается Webpack👍

Концепты

Webpack — статический сборщик модулей. Это значит что после компиляции у вас будет один файлик в котором все зависимости будут в правильном порядке, а все ассеты и стили будут зашиты в один файл.У самого WebPack есть несколько основных терминов:

  • Конфигурационный файл (Configuration)
  • Входная точка (Entry)
  • Точка выхода (Output)
  • Загрузчики (Loaders)
  • Плагины (Plugins)
  • Режимы (Modes)

Конфигурационный файл

Сам конфигурационный файл всегда один — webpack.config.js. В нем мы указываем экспортируемый объект, которй и является конфигурацией для Webpack:

module.exports = {

// Конфигурация
};

Тут нет ничего сложного, в следующих пунктах мы разберем основные свойства для данного объекта, чтобы заставить Webpack работать🔫

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

  • webpack.dev.js
  • webpack.prod.js

Названия файлов могут быть любые, это не стандарт, однако, внегласно принято называть их именно так😊

В package.json нужно просто указать команды для сборки, чтобы Webpack знал какую конфигурацию ему таскать:

{
../
«scripts»: {
«build:dev»: «webpack —config webpack.dev.js»,
«build:prod»: «webpack —config webpack.prod.js»
}
../
}

А куда делся webpack.config.js?!

Мы его в данном случае просто не используем Если мы просто запустим Webpack безо всяких флагов, то он будет искать именно webpack.config.js, однако если мы дали точное имя конфигурации, то он не будет ничего искать и просто сжует👄 данную конфигурацию и будет работать по ней👀

Входная точка

Сам Webpack строит граф (дерево, в случае если входной файл — один) зависимостей. Он проходится по всем импортам внутри ваших файлов, строит граф, а затем начинает импортировать все прямо в финальный файл.

Входная точка 🔴 — файл, с которого Webpack начнет построение этого графа. Это файл, который нам нужно включить первым и от которого будут идти все импорты.

Обычно данный файл является ./src/index.js, однако данную входную точку конечно же можно поменять:

module.exports = {
entry: ‘./path/to/index.js’,
};

Таким образом мы поменяем входной файл и Webpack будет работать начиная с файла, который мы указали в пути.

Точка выхода

Не секрет, что Webpack отдает нам один файл (спойлер: можно настроить так, чтобы отдавал несколько, но не суть сейчас). Именно этим «одним файлом» и является точка выхода 📤

По умолчанию этот файл находится в ./dist/main.js, однако вы можете также поменять это с помощью конфигурации:

// Импортируем модуль для работ с путями
const path = require(‘path’);

module.exports = {

// Указываем входную точку
entry: ‘./path/to/index.js’,

// Указываем точку выхода
output: {

// Тут мы указываем полный путь к директории, где будет храниться конечный файл
path: path.resolve(__dirname, ‘dist’),

// Указываем имя этого файла
filename: ‘my-first-webpack.bundle.js’,
},
};

Хороший вопрос: почему бы нам просто не указать имя точки выхода и на этом все. Зачем этот path, зачем разделять имя файла от пути?

Не менее хороший ответ😁: Webpack может упаковывать все что угодно, даже картинки формата png. Сами картинки он как не странно в код не запихнет, однако он перенесет их. Для переноса ему нужен точный путь к директории, где будет точка выхода, чтобы построить правильный путь. Имя файла и путь мы разделяем именно поэтому.

Лоадеры

Из коробки Webpack понимает только JavaScript и JSON, что будет если мы попытаемся запихнуть внутрь cjs, ejs, html, typescript? Ничего, Webpack отдаст нам ошибку. Для таких вещей придумали лоадеры😊

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

Правила 📏 — это в свою очередь просто объекты в конфигурации, которые указывают на файлы, которые нужно обработать и указывают лоадер

Все лоадеры указываются в конфигурации следующим образом:

const path = require(‘path’);

module.exports = {
entry: ‘./path/to/index.js’,
output: {
path: path.resolve(__dirname, ‘dist’),
filename: ‘my-first-webpack.bundle.js’,
},

// Указываем тут, что будем использовать спец. модуль для определенных файлов (лоадер)
module: {

// Указываем правила для данных модулей
rules: [

// Указываем правило для каждого лоадера
{test: /.txt$/, use: ‘raw-loader’},
],
},
};

Как можно увидеть есть свойство module, которое содержит в себе правила, массив rules в свою очередь уже содержит сами правила: в него включаются правила в виде объекта, где test — путь в виде Regex, use — сам лоадер, который должен быть использован.

Стоит уточнить, что перед тем как использовать лоадеры — их надо найти и скачать с помощью NPM😳

npm i -D raw-loader

Существует очень много лоадеров, от CSS и Sass до png и svg, рассмотрим их чуть позже в практическом примере😊

Плагины

Если лоадеры используются для того чтобы просто обработать файл, который мы импортируем в js, то плагины используются для того чтобы управлять не только импортами в JS.

Плагины🔌 — внешние модули для Webpack, которые позволяют управлять и обрабатывать файлы, которые не импортируются в JavaScript

Рассмотрим легенький пример, где мы будем импортировать index.html, это позволит не держать его в директории dist (то есть саму директорию можно будет удалять и ничего не потеряем), а также вставлять внутрь переменные окружения и пути к файлам.

Вот как это будет выглядеть в конфигурации (для упрощения часть кода с входной и выходной точкой — пропущена):

// Для того чтобы достучаться до плагина
const HtmlWebpackPlugin = require(‘html-webpack-plugin’);

// У самого Webpack уже есть встроенные плагины, их неплохо тоже импортировать
const webpack = require(‘webpack’);

module.exports = {
module: {
rules: [{ test: /.txt$/, use: ‘raw-loader’ }],
},

// Указываем новые плагины для обработки файлов
plugins: [

// Указываем что будем обрабатывать HTML с помощью плагина
new HtmlWebpackPlugin({ template: ‘./src/index.html’ })
],
};

Как мы можем увидеть тут мы используем плагин html-webpack-plugin для того чтобы обрабывать HTML. В данном случае он просто перенесет файл из ./src в ./dist 😊

Режим

У Webpack есть три режима (на самом деле два, но один из трёх является отсутсвием любого режима). По умолчанию без конфигурации у Webpack нет режима — none. Если мы напишем конфигурацию, то Webpack настоятельно посоветует (с вот таким лицом: 😠) добавить режим.

Делается это проще простого:

module.exports = {
mode: ‘production’,
};

Всего у Webpack (по-настоящему) существуют два режима:

  • Режим для разработки — development. Данный режим как не сложно догадаться рассчитан на разработку, он не сильно сжимает бандл (точку выхода, файл который получится в конце), а также может привязывать карты исходников и кучу других мелких фич
  • Режим продакшена — production. Режим, который будет сжимать ваш бандл, покуда сможет и делать совершенно нечитабельный (такой, что и обфускатор не потребуется), но оптимизированный код.

Продвинутое использование

Замечательно, вот мы и прорвались с основ к более продвинутому использованию🤩 Покрыть одной статьей асболютно все кейсы использования просто напросто невозможно, тут дело ограничивается только вашим воображением и условиями, при которых вам потребуется Webpack, однако пройтись по основным все же стоит😊

Входная точка

И по новой..♻ Снова пройдемся по всем концептам, только уже с более продвинутыми конфигурациями😳 Допустим, что у вас есть несколько файлов, которые сами по себе являются входными точками. Часто для сайтов например указывают следующие файлы:

  • main.js — главный файл, который выполняет функционал сайта
  • vendor.js — файл, который работает непосредственно с модулями

Что будем делать в таком случае? Не придётся же мерджить два файла?!😱

Две и более входные точки

Мы можем разделять файлы на две и более входных точки. Мы можем компилировать их в отдельные точки выхода, а также в один.С помощью данной конфигурации можно указать входные файлы:

module.exports = {
entry: {
main: ‘./src/main.js’,
vendor: ‘./src/vendor.js’,
},
};

Мы также можем передать в entry не объект с названиями файлов, а массив:

module.exports = {
entry: [‘./src/main.js’, ‘./src/vendor.js’],
};

Это хорошо, но как нам понять куда будут собираться данные исходники?🤔

В webpack.config.js указываем следующее:

const path = require(‘path’);

module.exports = {
output: {
path: path.resolve(__dirname, ‘dist’),

// Тут мы указываем, что будем компилировать каждый файл с таким же именем, но с постфиксом bundle.js
filename: ‘[name].bundle.js’,
},
};

[name] — в данном случае имя входного файла Данная строка является шаблонной (не в привычном понимании JavaScript), ибо внутри квадратных скобок мы указываем шаблон наименования. Все шаблоны можно посмотреть вот тут

Одним из полезных свойств является [contenthash]. Он используется в основном для продакшена, чтобы каждый раз при компиляции создавался новый хэш (хэш создается по контенту в файле, так что если файл изменился, то хэш будет новый💫). Данное свойство активно используется для обхода кэширования файлов в браузерах. Дело в том, что браузер не будет грузить все ваши файлы с одним и тем же названием каждый раз, как вы переходите на сайт, ибо это очень долго и влияет на время загрузки, даже если файлы поменялись. Проблема решается «в лоб», если так можно сказать😅 — мы просто меняем название файла, если он сам изменился.

А вдруг хочется один выходной файл, что делать тогда?!

Ответ на этот вопрос очень легкий😅 Просто включите файлы, которые вам нужны в одном бандле в main.js. Тот же vendors.js можно включить внутрь main.js и все😳

// File: main.js

// Импортируем работу с вендорами
import ‘./vendors.js’

Лоадеры

Точки выхода мы пропустим, так как я думаю мы уже обсудили их выше достаточно😊

Лоадеры в свою очередь могут таскать больше чем просто файлики .txt, естественно. Они используются для той же компиляции TypeScript:

module.exports = {
module: {
rules: [
{ test: /.ts$/, use: ‘ts-loader’ },
],
},
};

Важно: Помните, что перед тем как использовать лоадер — его нужно установить🚧

npm i -D ts-loader

А что если с файлом определенного типа нужно провести не одну операцию?🤔

Процессинг файлов с помощью лоадеров

Допустим у нас есть задачка: мы хотим процессить CSS-файлы с помощью SASS, а также включать их в JavaScript. Реализация у данной идеи будет следующая:

module.exports = {
module: {
rules: [

// Правило для CSS
{
test: /.css$/,
use: [
{loader: ‘style-loader’},
{loader: ‘css-loader’},
{loader: ‘sass-loader’}
]
}
]
}
}

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

Для плагинов последовательности и сам процессинг работает точно также😊

Цели (Target)

То, о чем мы ещё не поговорили — цели😳

JavaScript ныне работает как на клиентской стороне, так и на стороне сервера.

Мы можем создавать множественные цели, чтобы решать такие проблемы:

const path = require(‘path’);
const serverConfig = {
target: ‘node’,
output: {
path: path.resolve(__dirname, ‘dist’),
filename: ‘main.node.js’,
},
//…
};

const clientConfig = {
target: ‘web’,
output: {
path: path.resolve(__dirname, ‘dist’),
filename: ‘main.js’,
},
//…
};

module.exports = [serverConfig, clientConfig];

Однако опыт показывает что так делать не всегда удобно, а иногда и вовсе нежелательно🤔 Держать все конфигурации в одном файле — нехорошая идея, лучше создать две директории и два независимых webpack.config.js.

А что же на счёт target? Данное свойство указывает цель для чего мы создаем бандл, это может быть:

  • async-node — бандл для асинхронной ноды, который будет тянуть модули с промисами
  • node — бандл для синхронной ноды
  • electron-renderer — бандл для рендер-процесса в Electron
  • electron-preload — бандл для прелоудера в Electron
  • web — бандл для браузера. Данный таргет является дефолтным.
  • webworker — бандл для воркера.В зависимости от выбора бандла Webpack будет по-разному импортировать модули и обращаться с ними.

Кэширование

Мы можем кэшировать неизменные части нашего приложения, для того чтобы Webpack быстрее собирал наше приложение😌

По умолчанию файлы кэшируются в памяти в mode: development и не кэшируются вовсе, если mode: production🤔

Для того чтобы Webpack кэшировал все не в оперативной памяти, а в постоянной — достаточно просто указать следующее свойство:

module.exports = {
cache: {
type: ‘filesystem’, // По умолчанию ‘memory’
},
};

Webpack будет кэшировать билд в node_modules/.cache/webpack и автоматом пропускать то, что не изменилось!😱

Директорию для кэша можно изменить с помощью cacheDirectory:

const path = require(‘path’);
module.exports = {
cache: {
type: ‘filesystem’, // По умолчанию ‘memory’

// Устанавливаем диреторию для кэша
cacheDirectory: path.resolve(__dirname, ‘.temporary_cache’)
},
};

Для того чтобы отключить кэш вовсе достаточно просто добавить cache: false, интересным замечанием будет то, что cache: true — то же самое, что и cache: {type: ‘memory’} 😉

Watch или как Webpack подсматривает 👁

Webpack может компилировать изменения каждый раз, как только мы перекомпилируем файлы. Это очень полезно при разработке. Для того чтобы Webpack «подсматривал» за нашими файлами достаточно просто указать:

module.exports = {
//…
watch: true,
};

Если после того, как вы изменили файл сборка долго собирается и вообще вся ваша система на время повисла😡 это может быть следствием того, что Webpack начал подсматривать не за теми файлами. Это можно легко исправить, просто исключите огромные директории, которые не изменяются во время разработки:

module.exports = {
//…
watch: true,

// Настройки для watch
watchOptions: {

// Директории, которые watch будет игнорировать
ignored: [/node_modules/]
}
};

Если случилось так, что вы работаете с pnpm, который берёт модули из ссылок (то есть он не создает все время node_modules, а скачивает модули только один раз), то вам понадобится следующее свойство:

module.exports = {
//…
watch: true,

// Настройки для watch
watchOptions: {

// Разрешать Webpack следить за символьными ссылками
followSymlinks: true
}
};

DevServer 🛎

Webpack ещё и умеет запускать свой http-сервер, для того чтобы у вас была live-reload (перезагрузка при рекомпиляции)🌈

Для того чтобы заставить🔫 Webpack использовать devServer, достаточно просто указать следующие свойства в конфигурации:

const path = require(‘path’);

module.exports = {

// Конфигурация для нашего сервера
devServer: {

// Здесь указывается вся статика, которая будет на нашем сервере
static: {
directory: path.join(__dirname, ‘public’),
},

// Сжимать ли бандл при компиляции📦
compress: true,

// Порт на котором будет наш сервер
port: 9000,
},
};

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

module.exports = {
//…
devServer: {
// …
client: {

// Показывает ошибки при компиляции в самом браузере
overlay: {

// Ошибки
errors: true,

// Предупреждения
warnings: false,
},

// Показывает прогесс компиляции
progress: true
},
},
};

У самого сервера есть безграничные возможности: от поддержки Hot Module Replacement до поддержки WS-сервера и HTTPS соединения, все их можно посмотреть здесь😊

Пример на TypeScript

Как и обещал разберем пример😊

Постановка задачи: Написать конфигурацию для TypeScript с подтягиванием файлов [.ts, .tsx, .js], картами для исходников.

Задачу поставили, выполняем😇

// Подтягиваем модуль для удобной работы с путями
const path = require(‘path’);

module.exports = {

// Точка входа
entry: ‘./src/index.ts’,
mode: ‘development’
// Говорим, что нам нужна карта исходников🗺️
devtool: ‘inline-source-map’,
module: {
rules: [
// Компилируем TypeScript
{
test: /.tsx?$/,
use: ‘ts-loader’,
exclude: /node_modules/,
},
],
},

// Говорим что если не указано расширение файла, то пробуем эти варианты
// @see https://webpack.js.org/configuration/resolve/#resolveextensions
resolve: {
extensions: [‘.tsx’, ‘.ts’, ‘.js’],
},
// Точка выхода
output: {
filename: ‘bundle.js’,
path: path.resolve(__dirname, ‘dist’),
},
};

На этом можем завершать статью🥳 Мы научились пользоваться Webpack’ом, а также рассмотрели некоторые аспекты, которые позволят нам использовать его ещё эффективнее. Если вам понравилась статья, то вы можете перейти в мой канал, там ещё много всего интересного😉 Ещё увидимся!

Руководство по Webpack для начинающих

Разработка фронтенда перешла от традиционного к модульному подходу, при этом улучшился процесс инкапсуляции и усовершенствовалась структура кодовых баз. Инструментарий стал важной частью любого проекта, и сейчас существует множество возможных вариантов инструментов. Но браузер распознает только элементы, относящиеся к HTML, CSS и JavaScript. Поэтому в современных рабочих процессах важную роль играют пакеты модулей, а понимание инструментария остается ключевой частью любого проекта.

Skillfactory.ru

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

Что такое Webpack?

Webpack — это статический сборщик модулей. Его основная задача — пакетирование файлов JavaScript для использования в браузере, но он также способен преобразовывать, связывать и упаковывать практически любые ресурсы.

Webpack используется затем, чтобы собрать все зависимости, которые включают не только код, но и другие ресурсы, и создать граф зависимостей. Сборщик может работать только с JS-файлами, поэтому webpack должен предварительно обработать все остальные файлы и ресурсы, прежде чем они попадут в пакет.

Что такое граф зависимостей в Webpack?

Если какой-либо файл зависит от любого другого типа файлов, таких как JavaScript, CSS /SCSS, а также некодовых ресурсов, таких как изображения, SVG, веб-шрифты и т. д. в вашем приложении, то webpack рассматривает его как зависимость.

Когда webpack пакетирует приложение, он начинает с точки входа в файле webpack.config.js и рекурсивно строит граф зависимостей, который включает все зависимости, необходимые приложению. Он собирает их в один или несколько файлов в зависимости от потребностей, после чего они загружаются браузером.

Skillfactory.ru

Базовые понятия

Entry

Свойство entry указывает на начальную точку модуля. Webpack использует его для начала построения внутреннего графа зависимостей.

По умолчанию его значение —  ./src/index.js, но вы можете указать другую (или несколько других) точку или точки входа, задав свойство entry в конфигурационном файле webpack.

Сначала создадим файл webpack.config.js в корневой папке и назначим одну точку входа как ./app/index.js.

 entry в файле webpack.config.js

Для добавления нескольких точек входа, вы можете назначить массив файлов в свойстве entry.

Output

Свойство output указывает webpack, куда помещать создаваемые им пакеты и как называть эти файлы. По умолчанию оно принимает значение ./dist/main.js в качестве основного выходного файла, а папка под названием ./dist предназначена для любого статического содержимого.

Вы можете настроить эту часть процесса, определив поле output в вашей конфигурации:

output в файле webpack.config.js

Загрузчики

Как следует из названия этого свойства, оно позволяет webpack обрабатывать любые типы файлов и преобразовывать их в валидные модули, которые используются приложением и добавляются в граф зависимостей. Учтите, что Webpack понимает только файлы javascript и JSON.

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

Загрузчики также могут преобразовывать файлы с другого языка (например, TypeScript) в JavaScript с помощью соответствующих загрузчиков или подгружать встроенные изображения в виде URL данных, а также позволяют напрямую импортировать файлы CSS в модули JavaScript.

На более высоком уровне Webpack располагает двумя свойствами — test и use.

  1. Свойство test указывает, какой файл или файлы необходимо обработать.
  2. Свойство use указывает webpack, какие загрузчики используются для преобразования файлов в валидные модули.

Перед применением загрузчиков в файле webpack.config.js необходимо установить их в качестве зависимостей в приложении:

npm install -D style-loader css-loader

 загрузчик в файле webpack.config.js

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

  1. babel-loader: этот пакет позволяет компилировать файлы JavaScript с помощью babel и webpack;
  2. ts-loader: загружает TypeScript 2.0+, как JavaScript;
  3. sass-loader: загружает и компилирует файлы SASS/SCSS;
  4. svg-url-loader: загрузчик webpack, который загружает SVG-файл в виде строки DataUrl с кодировкой utf-8.

Плагины

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

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

Чтобы задействовать плагин, необходимо установить его в виде зависимости и использовать метод require() для его импорта и добавления в массив плагинов в конфигурационном файле webpack.

плагина в файле webpack.config.js

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

  1. eslint-webpack-plugin: этот плагин использует eslint для поиска и устранения проблем в коде JavaScript;
  2. webpack-bundle-analyzer: визуализация размера выходных файлов webpack с помощью интерактивной древовидной карты с возможностью масштабирования;
  3. clean-webpack-plugin: плагин webpack для удаления/очистки папки (папок) сборки;
  4. TerserWebpackPlugin: этот плагин использует terser для минификации/минимизации вашего JavaScript;
  5. purgecss-webpack-plugin: плагин для удаления неиспользуемых файлов CSS;
  6. uglifyjs-webpack-plugin: этот плагин использует uglify-js для минификации JavaScript.

Mode

Свойство mode принимает три значения: production, development и none. Вы можете включить встроенную оптимизацию webpack в зависимости от каждой отдельной среды.

demo в файле webpack.config.js

Читайте также:

  • Введение в Webpack для новичков
  • Как с нуля создать проект на React, используя Webpack 4 и Babel
  • Разработка современных приложений с помощью WEBPACK

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Vijay Kumar P: The Webpack Guide For Beginners

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

В данном руководстве используется webpack 4.30.

  • Webpack-что это?
  • Базовые понятия Webpack
  • Как работает Webpack
  • Приступим
  • Работа со скриптами
  • Работа со стилями
  • Управление ресурсами
  • Ускорение процесса разработки с помощью webpack-dev-server
  • Очистка вывода
  • Заключение

Webpack – это статический модульный сборщик. В проекте он обрабатывает все файлы и ресурсы как модули. При этом сборщик опирается на граф зависимостей, в котором описывается взаимосвязь модулей с помощью ссылок (операторы require и import).

Таким образом, webpack статически перемещается по всем модулям для построения графа и использует его для генерации одного бандла

Бандл – это файл JavaScript, содержащий код из всех модулей проекта и объединенных в правильном порядке. Когда webpack создает граф зависимостей, он не выполняет исходный код, а объединяет модули и их зависимости в бандл.

  • Entry – модуль, который используется для построения внутреннего графа зависимостей. С его помощью webpack определяет, от каких модулей и библиотек зависит точка входа (напрямую и не напрямую). Затем включает их в граф, пока не останется ни одной зависимости. По умолчанию для свойства entry установлено значение ./src/index.js. Но можно указать другой модуль в файле конфигурации сборщика.
  • Output – это свойство указывает, где webpack должен сохранять бандл и как называть его файл (или файлы). Значением по умолчанию является ./dist/main.js для основного бандла и ./dist для других сгенерированных файлов.
  • Загрузчики. По умолчанию webpack понимает только файлы JavaScript и JSON. Чтобы обработать другие типы файлов и конвертировать их в модули, сборщик использует загрузчики. Например, загрузчик может трансформировать файлы из языка CoffeeScript в JavaScript или встроенные изображения в URL-адреса. С помощью загрузчиков можно даже импортировать CSS-файлы прямо из модулей JavaScript.
  • Плагины. Используются для задач, которые не могут выполнять загрузчики.
  • Режимы. Webpack позволяет настроить режим на development, production или none. Благодаря этому он может использовать встроенные оптимизации для каждой среды. По умолчанию установлено значение Режим none означает, что все опции оптимизации по умолчанию будут отключены. Чтобы узнать больше об опциях, которые webpack использует в development и production, посетите страницу конфигурации режимов.

В любом веб-проекте есть файлы HTML, CSS, JavaScript, ресурсы, как шрифты, изображения и т.д. Таким образом, рабочий процесс webpack включает в себя настройку файла index.html с соответствующими ссылками на CSS, JavaScript и необходимые ресурсы.

При выполнении задач webpack опирается на конфигурацию. Они прописаны в файле webpack.config.js. В нем указано, как файлы и ресурсы следует трансформировать.

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

Если модуль содержит зависимости, процесс выполняется рекурсивно для каждой зависимости. Затем webpack объединяет все модули проекта в небольшое количество бандлов (обычно только один), чтобы их можно было загрузить в браузер.

Файлы нашего проекта можно найти в репозитории на Github.

Сначала создадим новый каталог и перейдем в него. Затем инициализируем новый проект:

mkdir learn-webpack
cd learn-webpack
npm init -y

После этого нужно локально установить webpack и webpack CLI:

npm install webpack webpack-cli --save-dev

Теперь код сгенерированного файла package.json должен выглядеть следующим образом:

{
  "name": "learn_webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.30.0",
    "webpack-cli": "^3.3.0"
  }
}

Также webpack можно использовать в качестве менеджера задач. Для этого нужно разместить задачу и ее инструкции в разделе scripts файла package,json. Попробуем сделать это прямо сейчас. Откройте файл package.json и измените объект scripts следующим образом:

"scripts": {
  "test": "echo "Error: no test specified" && exit 1",
  "dev": "webpack --mode development",
  "build": "webpack --mode production"
},

Свойство scripts позволяет ссылаться на локально установленные пакеты npm по их именам. Мы используем его и флаг —mode для определения задач dev и build, которые будут запускать webpack в режиме разработки (npm run dev) и производства (npm run build) соответственно.

Создадим каталог src и поместим в него файл index.js. Теперь мы можем запустить задачу dev, чтобы webpack работал в режиме разработки:

$ npm run dev

> learn_webpack@1.0.0 dev C:UsersUserWebpacklearn_webpack
> webpack --mode development

Hash: 5bb3bdc1efd7b7f4b627
Version: webpack 4.30.0
Time: 226ms
Built at: 2019-04-16 17:48:32
  Asset     Size  Chunks             Chunk Names
main.js  3.8 KiB    main  [emitted]  main
Entrypoint main = main.js
[./src/index.js] 27 bytes {main} [built]

Отобразим результат работы сборщика в браузере. Для этого создадим файл index.html в каталоге dist:

<!doctype html>
<html>
  <head>
    <title>Getting Started</title>
  </head>
  <body>
    <script src="main.js"></script>
  </body>
</html>

Теперь, если мы откроем этот файл в браузере, то увидим сообщение Hello webpack.

В некоторых случаях создание файла index.html вручную может быть проблематичным. Например, если мы изменим имя точки входа, то сгенерированный пакет будет переименован. Но файл index.html по-прежнему будет ссылаться на старое имя. Поэтому нужно будет вручную обновлять HTML- файл каждый раз, когда понадобится переименовать точку входа или добавить новую. Этого можно избежать с помощью html-webpack-plugin. Установите этот плагин:

npm install html-webpack-plugin --save-dev

Чтобы активировать плагин, создайте файл webpack.config.js и поместите в него следующий код:

const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require('path');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      title: "Webpack Output",
    }),
  ],
};

Чтобы активировать плагин webpack, нужно включить его и добавить в массив plugins. При необходимости передаем плагину необходимые параметры.

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

$ npm run build

> learn_webpack@1.0.0 build C:UsersUserWebpacklearn_webpack
> webpack --mode production

Hash: e56a796f5ccfebcc8270
Version: webpack 4.30.0
Time: 1088ms
Built at: 2019-04-16 20:44:47
    Asset       Size  Chunks             Chunk Names
index.html  183 bytes          [emitted]
  main.js  956 bytes       0  [emitted]  main
Entrypoint main = main.js
[0] ./src/index.js 27 bytes {0} [built]
Child html-webpack-plugin for "index.html":
    1 asset
    Entrypoint undefined = index.html
    [2] (webpack)/buildin/global.js 472 bytes {0} [built]
    [3] (webpack)/buildin/module.js 497 bytes {0} [built]
        + 2 hidden modules

Откроем файл index.html. Как видим, плагин автоматически создает обновленный файл index.html, который использует параметр title из конфигурации:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack Output</title>
  </head>
  <body>
  <script src="main.js"></script></body>
</html>

Теперь определим пользовательские имена для свойств input и output. В webpack.config.js перед свойством plugins добавляем следующий код:

entry: './src/app.js',
output: {
  filename: '[name].bundle.js',
  path: path.resolve(__dirname, 'dist')
},

После этого создадим файл src/component.js:

export default (text = "Hello webpack") => {
  const element = document.createElement("p");

  element.innerHTML = text;

  return element;
};

 Переименуем index.js в app.js, чтобы отразить внесенные изменения, и заменяем его содержимое следующим кодом:

import component from "./component";

document.body.appendChild(component());

Теперь запустим webpack:

$ npm run build

> learn_webpack@1.0.0 build C:UsersUserWebpacklearn_webpack
> webpack --mode production

Hash: 9f78936f8a2a21061f0b
Version: webpack 4.30.0
Time: 1689ms
Built at: 2019-04-17 23:43:40
        Asset       Size  Chunks             Chunk Names
    index.html  190 bytes          [emitted]
main.bundle.js   1.04 KiB       0  [emitted]  main
Entrypoint main = main.bundle.js
[0] ./src/app.js + 1 modules 227 bytes {0} [built]
    | ./src/app.js 79 bytes [built]
    | ./src/component.js 148 bytes [built]
Child html-webpack-plugin for "index.html":
    1 asset
    Entrypoint undefined = index.html
    [2] (webpack)/buildin/global.js 472 bytes {0} [built]
    [3] (webpack)/buildin/module.js 497 bytes {0} [built]
        + 2 hidden modules

Изучим и проясним для себя информацию из вывода, предоставляемого webpack. Вверху указаны хэш сборки, версия webpack и время выполнения сборки.

Далее идет перечень файлов, сгенерированных в каталоге dist (index.html и main.bundle.js). Под ним расположен модуль ввода (app.js) и его зависимость (component.js). Вывод после Child html-webpack-plugin for «index.html»: относится к работе html-webpack-plugin. Поэтому его игнорировать.

Теперь в папке dist есть новый сгенерированный пакетный файл main.bundle.js. Если открыть файл index.html в браузере, то мы увидим сообщение Hello webpack. Кроме этого в коде файла index.html изменилось значение атрибута src в теге script на main.bundle.js.

Мы рассмотрим, как перенести ES6 в ES5-совместимый код, который работает во всех браузерах. Начнем с выполнения приведенной ниже команды:

npm run dev -- --devtools false

Затем откроем файл main.bundle.js:

/***/ "./src/component.js":
/*!**************************!*
  !*** ./src/component.js ***!
  **************************/
/*! обеспечение экспорта: по умолчанию */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* экспорт по умолчанию */ __webpack_exports__["default"] = ((text = "Hello webpack") => {
  const element = document.createElement("p");

  element.innerHTML = text;

  return element;
});

/***/ })

Современные функции из стандарта JavaScript ES6 (стрелочная функция и объявление const) из модуля component.js по умолчанию не преобразованы в ES5-совместимый код. Чтобы код работал в более старых браузерах, необходимо добавить загрузчик Babel:

npm install babel-loader @babel/core @babel/preset-env --save-dev

Затем в файле webpack.config.js добавьте module после свойства output:

module: {
  rules: [
    {
      test: /.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env']
        }
      }
    }
  ]
},

При определении правил для загрузчика webpack нужно установить три основных свойства:

  • test – описывает, какие файлы следует трансформировать.
  • exlude – определяет, какие файлы из загрузчика не следует обрабатывать.
  • use – указывает, какой загрузчик следует использовать для сопоставленных модулей.

Еще раз введите приведенную ниже команду:

npm run dev -- --devtools false

На этот раз код в файле main.bundle.js компилируется в следующий:

/***/ "./src/component.js":
/*!**************************!*
  !*** ./src/component.js ***!
  **************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* экспорт по умолчанию */ __webpack_exports__["default"] = (function () {
  var text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "Hello webpack";
  var element = document.createElement("p");
  element.innerHTML = text;
  return element;
});

/***/ })

Теперь мы можем использовать современные функции JavaScript. При этом webpack преобразует код так, чтобы его могли выполнять устаревшие браузеры.

Чтобы добавить CSS в проект, потребуются два загрузчика:

npm install css-loader style-loader --save-dev

css-loader преобразует CSS-код в JavaScript и разрешает любые зависимости, а style-loader выводит CSS в теге <style> в HTML- документе.

Добавим в файл webpack.config.js необходимую конфигурацию:

{
  test: /.css$/,
  use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
},

Создадим файл src/style.css:

Затем импортируем его в файл app.js:

Когда мы запустим webpack, а затем откроем файл index.html, сообщение «Hello webpack будет красного цвета.

Далее мы рассмотрим пример с изображениями. Сначала нужно установить загрузчик файлов:

npm install file-loader --save-dev

Затем добавить новое правило в файл webpack.config.js:

{
  test: /.(png|jpg|gif)$/,
  use: [
    {
      loader: 'file-loader'
    },
  ],
},

Чтобы протестировать загрузчик, создадим файл image-component.js в каталоге src со следующим кодом:

import image from "./image.png"

const img = document.createElement("img")
img.src = image
document.body.appendChild(img)

Здесь мы  импортируем изображение как модуль и используем его в теге <img/>. Нужно поместить это изображение в каталог src.

После этого импортируем компонент изображения в файл app.js:

import './image-component'

Теперь, когда мы запустим webpack и откроем страницу, над сообщением Hello webpack будет выводиться изображение.

Нам приходиться перестраивать проект всякий раз, когда вносим какие-то изменения в код. К счастью, webpack предоставляет веб-сервер, который автоматически создает и обновляет страницу. Чтобы установить его, запустите приведенную ниже команду:

npm install webpack-dev-server --save-dev

Чтобы использовать сервер, нужно обновить dev-скрипт в файле package.json:

"dev": "webpack-dev-server --mode development"

А затем настроить сервер в файле webpack.config.js, добавив следующее свойство:

devServer: {
  contentBase: './dist',
  open: true
},

После этого webpack-dev-server начинает обслуживать файлы из каталога dist и автоматически открывать страницу входа.

Теперь при запуске webpack (npm run dev) мы увидим, как страница открывается в браузере автоматически на localhost: 8080:

i 「wds」: Project is running at http://localhost:8080/
i 「wds」: webpack output is served from /
i 「wds」: Content not from webpack is served from ./dist

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

По мере расширения проекта папка dist становится дольно объемной. При каждой сборке webpack будет генерировать бандлы, и помещать их в папку dist. Но при этом сборщик не отслеживает, какие файлы используются в проекте по факту. Поэтому рекомендуется очищать папку dist перед каждой сборкой, чтобы генерировались только используемые файлы. Для этого нужно установить и настроить clean-webpack-plugin:

npm install --save-dev clean-webpack-plugin

В webpack.config.js:

const CleanWebpackPlugin = require('clean-webpack-plugin');

...

plugins: [
  ...
  new CleanWebpackPlugin()
],

Затем запустите webpack (npm run build) и проверьте папку dist. Теперь вы увидите в ней только файлы, сгенерированные из сборки. В нашем случае файл, который следует удалить, это main.js.

Webpack – это полезный и мощный инструмент. В данном руководстве представлены только его базовые возможности. Но webpack способен на большее. Вот список ресурсов для дальнейшего изучения возможностей сборщика:

  • Официальная документация webpack.

Язык JavaScript повсеместно используется для создания больших веб-сервисов. Для таких проектов приходится импортировать много стороннего кода (Lodash, React, Angular и др.). Из-за этого код усложняется, и в нём гораздо чаще возникают ошибки. Чем больше в вашем коде будет зависимостей, тем большей головной болью станет подключение тегов <script> в правильном порядке.

Webpack создаёт граф зависимостей для JavaScript, CSS и прочих, выдавая однофайловые сборки кода так, чтобы вы могли импортировать все необходимые ресурсы JavaScript всего одним тегом <script>.

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

Создание приложения

В качестве тестового приложения мы сделаем карту для ленивцев, которая поможет найти магазины в Кембридже, где продают травяной чай из гибискуса. Потому что каждый ленивец в кембриджском заповеднике Fresh Pond знает, что чай из гибискуса — лучший чай, чтобы умерить свой горячий темперамент!

sloths

[badge style=»blue»]Прим.[/badge]: на самом деле в заповеднике Fresh Pond не обитают ленивцы, но они правда любят вкусные цветы гибискуса после долгого дня, проведённого на деревьях.

Создайте каталог с именем webpack-mocha-tutorial, в него добавьте другой каталог app/src и запустите пакетный менеджер npm init или yarn init. Исходный код приложения находится здесь. Также в статье будут ссылки на коммиты, чтобы прослеживать изменения кода по ходу чтения.

Основная структура приложения будет выглядеть так:

  • У вас есть файл app/src/distance.js, экспортирующий функцию, которая запускает формулу вычисления расстояния (на самом деле нужно использовать ортодому) и функцию, которая сообщает, какая точка из массива точек ближе всего находится.
// Функция distance() принимает 2 значения, представленных
// числами x и y, и возвращает расстояние между ними
//
// [TODO] Используйте ортодому
function distance(p2, p1) {
 let yDist = p2.y - p1.y;
 let xDist = p2.x - p1.x;
 return Math.sqrt(Math.pow(yDist, 2) + Math.pow(xDist, 2));
}
// sortByDistance принимает ваше местоположение и массив точек
// и возвращает отсортированный массив точек
function sortByDistance(myPt, points) {
 return points.sort(
   (pt1, pt2) => distance(pt1, myPt) - distance(pt2, myPt));
}
  • Также у вас есть файл app/src/page.js, который использует код из distance.js, чтобы вывести ближайший магазин из списка, а затем отобразить его на странице.
let stores = [
 {name: "Cambridge Naturals",     x: -71.1189, y: 42.3895},
 {name: "Sarah's Market",         x: -71.1311, y: 42.3823},
 {name: "Whole Foods Fresh Pond", x: -71.1420, y: 42.3904},
];
let here = {name: "You are here",  x: -71.1470, y: 42.3834};
let nearest = sortByDistance(here, stores)[0];
document.getElementById("nearest-store").innerHTML = nearest.name;
  • И, наконец, у вас есть страница index.html.
<!DOCTYPE html>
<html>
 <head>
   <title>Ближайший магазин чая из гибискуса</title>
 </head>
 <body>
   <p>Nearest store is <span id="nearest-store"></span></p>
   <script src="app/src/distance.js"></script>
   <script src="app/src/page.js"></script>
 </body>
</html>

Общая структура каталогов такова:

dependencies tree
Таким образом, файл distance.js определяет функции расстояния, затем файл page.js запускает их, помещая результат функции sortByDistance() в дерево документов (DOM). Но если посмотреть на зависимость между файлами, то видно, что файл page.js зависит от файла distance.js, а не наоборот (Commit 2).

Для работы с Webpack необходимо его установить с помощью npm или yarn:

$ yarn add --dev webpack webpack-cli

Теперь у вас подключён Webpack и доступна его командная строка. Но прежде, чем можно будет запустить сборку, файл page.js должен импортировать код из distance.js. А distance.js экспортирует свои функции с помощью строки:

module.exports = {distance, sortByDistance};

И чтобы page.js мог использовать экспортированную функцию sortByDistance(),  добавляем строку:

import {sortByDistance} from "./distance";

Отлично, теперь все зависимости JavaScript связаны. Используем Webpack для создания приложения. Выполним следующую команду:

$ npx webpack app/src/page.js

Сейчас вы должны увидеть новый файл dist/main.js, который содержит весь ваш код из page.js и distance.js. Далее получаем index.html с импортом dist/main.js вместо всех скриптов из app/src, изменив код страницы следующим образом:

<!DOCTYPE html>
<html>
 <head>
   <title>Ближайший магазин чая из гибискуса</title>
 </head>
 <body>
   <p>Ближайший магазин: <span id="nearest-store"></span></p>
   <script src="dist/main.js"></script>
 </body>
</html>

Теперь можете открыть страницу в браузере, код по-прежнему должен работать. Поскольку файл main.js содержит весь код из distance.js и page.js, можно импортировать всё из одного файла.

Это работает так: с помощью команды $ npx webpack app/src/page.js вы указываете, что отправной точкой (в терминологии Webpack — точкой входа в ваш код JavaScript) является page.js. Webpack читает файл page.js и видит в нём строку import {sortByDistance} from ./distance. Теперь он знает, что distance.js является зависимостью к page.js. И из всех зависимостей в вашем коде Webpack строит граф и использует его для построения пакетного JavaScript-файла dist/main.js (Commit 3).

webpack

Webpack строит граф зависимостей из точки входа, page.js

Между прочим, это также работает, когда ваш код импортирует сторонние зависимости в каталог node_modules

Прим. перев.: все модули, используемые в npm, по умолчанию подключаются из директории node_modules.

Давайте попробуем выполнить некоторые манипуляции с графом зависимостей с помощью jQuery вместо document.getElementById(). Сначала установим jQuery:

$ yarn add --dev jquery

Затем обновим page.js, чтобы включить в него jQuery:

import {sortByDistance} from "./distance";
import $ from "jQuery";
let stores = [
 {name: "Cambridge Naturals",     x: -71.1189, y: 42.3895},
 {name: "Sarah's Market",         x: -71.1311, y: 42.3823},
 {name: "Whole Foods Fresh Pond", x: -71.1420, y: 42.3904},
];
let here = {name: "You are here",  x: -71.1470, y: 42.3834};
let nearest = sortByDistance(here, stores)[0];
$("#nearest-store").html(nearest.name);

Теперь граф зависимостей выглядит так:

граф зависимостей, dependencies graph

Ваш новый граф зависимостей, где page.js включает jQuery

И если выполнить $ npx webpack app/src/page.js и перезагрузить index.html (несмотря на то, что размер файла dist/main.js намного больше из-за кода jQuery) приложение по-прежнему работает.

Прежде чем продолжить, перейдите в файл package.json и добавьте эти три строчки:

"scripts": {
 "build": "webpack app/src/page.js"
}

Теперь, чтобы запустить сборку пакета, можно просто выполнить $ yarn build вместо $ npx webpack app/src/page.js. Если команда для сборки изменится, будет удобнее просто обновить её в файле package.json с помощью новой команды сборки и вы по-прежнему можете выполнять сборку с помощью $ yarn build, вместо того чтобы привыкать к запуску новой команды (Commit 4).

Настройка Webpack с помощью файла webpack.config.js

То, что можно сделать с помощью команды $ npx webpack app/src/page.js, является стандартным режимом работы Webpack. Если выполнить $ webpack [entry-file.js], то он создаст граф зависимостей из входного файла и выдаст пакетный файл dist/main.js. Но задать расположение точек входа и выхода программы можно, настроив файл конфигурации. Поместите следующий код в файл с именем webpack.config.js:

module.exports = {
 entry: __dirname + "/app/src/page.js",
 output: {
   path: __dirname + "/dist/",
 }
}

Теперь можно выполнить $ npx webpack или сделать ту же сборку, что и раньше, без указания точки входа в программу в аргументах командной строки, т. к. теперь всё это есть в webpack.config.js. Также это значит, что нужно обновить скрипт файла package.json следующим образом:

"build": "webpack",

Если в файле конфигурации изменить путь вывода на что-то вроде  __dirname + "/somewhere_else", то при повторном выполнении команды $ yarn build пакетный файл будет помещён в somewhere_else/main.js (Commit 5).

Файл конфигурации предназначен не только для настройки расположения входных и выходных файлов. Также можно настроить что именно Webpack делает, когда встречает файлы разных типов, используя специальные загрузчики, которые на самом деле являются JavaScript-программами, преобразующими ваш код. Например, в файле конфигурации может быть правило, определяющее, что, если Webpack встречает файл TypeScript в своём графе зависимостей, этот файл отправляется в загрузчик, который преобразует его из TypeScript в обычный JavaScript.

Загрузчик, который будет использоваться далее, — это Babel. Если вы не использовали его раньше, Babel — это инструмент, который берёт код JS, использующий современные функции, и преобразует его в эквивалент, совместимый со старыми версиями JavaScript. Это позволяет вашему приложению работать в старых браузерах или в браузерах, которые ещё не поддерживают некоторые новые функции JavaScript. В конце концов, некоторые ленивцы не обновляли свои браузеры с 2009 года. Некоторая часть написанного кода не будет работать в браузере 2009 года:

return points.sort((pt1, pt2) =>
 distance(pt1, myPt) — distance(pt2, myPt));

Здесь используется стрелочная функция и старые браузеры её не воспринимают. Поэтому используем загрузчик и отправим эту функцию в прошлое. Для начала выполним следующее:

$ yarn add --dev babel-core babel-loader@7.1.5 babel-preset-env

Затем в файле webpack.config.js добавим следующий код в module.exports:

module: {
 rules: [
   {
     test: /.js$/,
     exclude: ["/node_modules/"],
     use: [
       {
         loader: "babel-loader",
         options: {
           presets: ["env"],
         },
       },
     ],
   },
 ],
},

Этот код добавит новое правило в ваш Webpack. Если в дереве зависимостей Webpack встречает файл, который заканчивается на .js (например, distance.js), и этот файл отсутствует в папке node_modules (например, jQuery), то к этому файлу применяется данное правило.

Любой файл, который соответствует этому правилу, проходит через все загрузчики в блоке use (в данном случае только через загрузчик babel). Таким образом, файлы distance.js и page.js пройдут через загрузчик, что приведёт к удалению стрелочной функции в distance.js, а затем Webpack продолжит свой путь сборки. Тем временем, как только Webpack встречает jQuery, он просто загружает этот код как он есть без загрузчиков, поскольку jQuery находится в каталоге node_modules.

загрузчик Babel, loader Babel

Теперь, если выполнить $ yarn build и посмотреть исходный код в dist/main.js, фрагмент, соответствующий вашей функции сортировки, использует ключевое слово function, а не стрелочную функцию (Commit 6).

до использования загрузчика

после использования загрузчика

Подсвеченный код на верхнем изображении — это функция sortByDistance() из dist/main.js до использования загрузчика, а подсвеченный код внизу — та же функция после добавления загрузчика. Обратите внимание на то, как выше мы используем стрелочную функцию, а ниже присутствует уже знакомое браузеру 2009 года ключевое слово function.

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

Добавление тестовых сценариев в сборку

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

$ yarn add --dev mocha chai

Затем создайте новый каталог app/test и новый файл app/test/distance.test.js, содержащий следующий фрагмент:

import {expect} from "chai";
import {distance, sortByDistance} from "../src/distance";
describe("distance", function() {
 it("calculates distance with the good ol' Pythagorean Theorem", function() {
   let origin = {x: 0.0, y: 0.0};
   let point = {x: 3.0, y: 4.0};
   expect(distance(point, origin)).to.equal(5);
 });
});
describe("sortByDistance", function() {
 it("sortsByDistance", function() {
   let places = [
     {name: "Far away", x: 100, y: 50},
     {name: "Nearby", x: 20, y: 10},
   ];
   let origin = {name: "Origin", x: 0, y: 0};
   let sorted = sortByDistance(origin, places);
     expect(sorted[0].name).to.equal("Nearby");
     expect(sorted[1].name).to.equal("Far away");
   });
});

Теперь у вас есть тестовые сценарии для функций distance() и sortByDistance(), устанавливающие, что distance() вычисляет формулу расстояния, а sortByDistance() сортирует массивы координат с помощью формулы расстояния, используя наборы тестов Mocha и установки Chai. Довольно стандартная тестовая настройка.

Однако, если выполнить $ mocha app/test/distance.test.js, будет ошибка «Код JavaScript недопустим», потому что он содержит ключевое слово import, которое Node в данный момент не поддерживает. Но что если обойти это ограничение, используя Webpack для управления зависимостями тестового кода?

Прим.: это можно легко исправить, просто используя require вместо import в тестовых файлах. Но тестовый код также будет проходить через процесс сборки, если вы тестируете JavaScript-код типа Flow, который использует аннотации, или веб-приложения Vue.js, которые используют файлы .vue. Все они должны быть преобразованы в обычный JavaScript.

Список тестовых инструкций:

  1. Webpack строит граф зависимостей, начинающийся с тестовых файлов, а не с файлов приложения.
  2. Webpack создаёт файл JavaScript, содержащий весь тестовый код и его зависимости без ключевого слова import.
  3. Выполняются тесты, запуская Mocha для этого JavaScript-файла.

Всё это будет выглядеть следующим образом:

dependencies, builds

Как можно увидеть, будут происходить две отдельные сборки. Одна из которых содержит код приложения в качестве точки входа и папку dist в качестве выходной директории, а другая — тестовые файлы в качестве точки входа и папку test-dist в качестве выходного каталога. Итак, давайте обновим конфигурационный файл, чтобы Webpack поддерживал вторую сборку:

let glob = require("glob");
let entry = __dirname + "/app/src/page.js";
let outputPath = __dirname + "/dist/";
if (process.env.TESTBUILD) {
 entry = glob.sync(__dirname + "/app/test/**/*.test.js");
 outputPath = __dirname + "/test-dist/";
}
module.exports = {
 entry: entry,
 output: {
   path: outputPath,
 },
 // остальная часть config-файла остаётся прежней

Давайте разберёмся, что этот код делает. В строке 4 есть оператор if, который  выполняется, если системная переменная TESTBUILD имеет непустое значение. Так что, если вы выполните $ TESTBUILD=true webpack, то вам придётся вводить оператор if, но это не потребуется, если просто выполнить $ npx webpack.

Внутри оператора if происходит выбор, какой JS-файл является точкой входа. Вместо уже установленного выходного каталога dist будет папка test-dist. А вместо точки входа app/src/path.js — массив файлов, соответствующих глобальному выражению app/test/**/*.test.js. Другими словами, это все файлы, которые находятся в каталоге app/test и имеют путь, заканчивающийся на .test.js.

Новая точка входа и выходной путь передаются в объект module.exports, затем запускается Webpack для создания тестовой сборки. Конфигурация Webpack представляет собой обычный код JavaScript, поэтому можно использовать стандартную библиотеку Node и операторы if для её настройки. Если выполнить $ TESTBUILD=true npx webpack, то можно увидеть каталог test-dist. А если запустить $ npx mocha test-dist/main.js, то можно увидеть, как выполняются ваши тесты.

successful testing

Наконец, в разделе scripts вашего package.json добавьте следующую строку:

"test": "TESTBUILD=true webpack && mocha test-dist/main.js && rm -rf test-dist"

Это означает, что когда вы выполняете $ yarn test, создаётся каталог test-dist (каталог сборки тестирования) с помощью Webpack, затем запускается Mocha для этой сборки, и, наконец, код $ rm -rf test-dist удаляет каталог test-dist, поскольку он больше не используется (Commit 7).

Маппинг исходных файлов тестового кода

Тестовая сборка готова, но есть один нюанс, касающийся тестирования кода. Если запустить Mocha для файла test-dist/main.js и один из этих тестов не пройдёт, как это будет выглядеть? Давайте сделаем тест формулы расстояния в app/test/distance.test.js ошибочным:

describe("distance", function() {
 it("calculates distance with the good ol' Pythagorean Theorem", function() {
   let origin = {x: 0.0, y: 0.0};
   let point = {x: 3.0, y: 4.0};
   expect(distance(point, origin)).to.equal(2071);
 });
});

Выполните $ yarn test и вот что получится:

Assertion error

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

Код с ошибкой находится в строке 8 в файле app/test/distance.test.js, но Mocha запускается для файла test-dist/main.js, поэтому, с точки зрения Mocha, ошибка находится в строке 116. К счастью, Webpack поддерживает source maps, которые покажут, какая строка кода соответствует ошибке. Source maps (или карты кода) — это файлы исходного кода, которые показывают точное соответствие элементов готового рабочего кода проекта и вашего кода разработки. Выполняется своего рода проход декодером по пакетному файлу main.js, чтобы получить исходные строки кода, которые соответствуют связному коду. Давайте обновим оператор if в файле webpack.config.js:

let entry = __dirname + "/app/src/path.js";
let outputPath = __dirname + "/dist/";
let devtool = "";
if (process.env.TESTBUILD) {
 entry = glob.sync(__dirname + "/app/test/**/*.test.js");
 outputPath = __dirname + "/test-dist/";
 devtool = "source-map";
}

Затем в объект module.exports добавим строку:

devtool: devtool,

Теперь в тестировочных сборках каталог test-dist будет содержать файл типа source maps. Выполните $ npx webpack TESTBUILD=true и каталог test-dist будет содержать файл main.js.map в дополнение к пакету main.js.

devtool: sourcemap

Чтобы Mocha мог использовать эту source map при запуске тестов, необходимо установить ещё один пакет:

$ yarn add --dev source-map-support

Теперь, чтобы его использовать, нужно обновить скрипт Mocha в разделе scripts.test файла package.json:

TESTBUILD=true webpack && mocha test-dist/main.js --require source-map-support/register && rm -rf test-dist

Флаг --require source-map-support/register требует пакет source-map-support, это означает, что Mocha будет использовать source map, если она доступна. Теперь, если вы выполните $ yarn test и получите ошибку, вы увидите, в какой строке она находится, и сможете исправить код (Commit 8).

ошибочный сценарий тестирования

Итак, теперь у вас есть пример настройки обычных и тестовых сборок с использованием маппинга. Существует также множество других способов, которыми вы можете воспользоваться в своих сборках, например, объединение нескольких загрузчиков JavaScript для обработки вашего кода как на конвейере или же запуск Webpack как отладочного сервера, чтобы моментально видеть, как изменения в коде влияют на окончательную сборку Webpack. Продолжайте пробовать различные пакеты для компоновки файла webpack.config.js!

Для более точной настройки Webpack вам может быть полезен следующий материал:

Перевод статьи Webpack: From 0 to automated testing

Небольшую статью о том, что такое Webpack, и как начать с ним работать, опубликовал сайт webdevblog.ru. Это перевод англоязычной статьи A (Very) Beginner’s Guide to WEBPACK.

В среде Node у нас есть модульная система «CommonJS», которая использует module.exports/require для изоляции файлов (или модулей). Вплоть до ES6 браузеры нативно не поддерживали модули. По умолчанию каждый скрипт в HTML-документе выполняется по порядку загрузки используя одну общую область видимости.

Но появился…Webpack!

Из Webpack 5 документации:

«Webpack — это пакетный сборщик модулей. Основная цель — связать несколько файлов JavaScript в один файл для использования в браузере, но он также способен преобразовывать, связывать или упаковывать практически любой ресурс или ассет».

Что это значит? Давайте посмотрим на Webpack в действии, создав небольшую JavaScript-программу в Node.

Setup

Создайте новый проект с помощью npm и установите webpack и webpack-cli.

mkdir hello-webpack && cd hello-webpack
npm init -y
npm install --save-dev webpack webpack-cli

Теперь, в вашей корневой папке проекта, сделайте каталоги src и public. Папка src будет содержать наш необработанный исходный код, и мы дадим указание Webpack сгенерировать собранный код в папку public. Вам также необходимо создать файл с именем webpack.config.js — но об этом позже. Ваш проект должен выглядеть так:

hello-webpack/
├── src/
├── public/
├── webpack.config.js
└── package.json

package.json

{
  "name": "hello-webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.11"
  }
}

public/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <script src="../src/game.js" defer></script>
  <script src="../src/main.js" defer></script>
  <link rel="stylesheet" href="style.css" />
  <title>Click Me</title>
</head>
<body>
  <button id="button">Click Me!</button>
</body>
</html>

public/style.css

button {
  height: 300px;
  width: 300px;
  font-size: 40px;
  background-color: goldenrod;
  color: white;
  border-radius: 50%;
  cursor: pointer;
}

src/game.js

  let numTimesClicked = 0;

  function win() {
    alert('You win!');
    reset();
  }

  function reset() {
    numTimesClicked = 0;
  }

  function click() {
    numTimesClicked++;
    console.log(`You've been clicked!`);
    if (numTimesClicked === 10) win();
  }

src/main.js

const button = document.getElementById('button');

button.addEventListener('click', function() {
  click();
});

Зачем нужен Webpack?

В командной строке запустите open public/index.html. Вы должны увидеть желтую кнопку. При нажатии кнопка должна вывести сообщение в вашу консоль. Если вы нажмете кнопку 10 раз, должно появиться сообщение о том, что вы выиграли! Супер! Все сделано!

Шучу. Посмотрите на файл index.html. Что произойдет, если вы не включите ключевое слово defer в строки 7 и 8? Как насчет того, чтобы переупорядочить файлы JavaScript?

<!-- remove 'defer' from lines 7 and 8 -->
<!-- re-order 'game.js' and 'main.js' -->
  <script src="../src/main.js"></script>
  <script src="../src/game.js"></script>

Вы увидите что-то подобное в своей консоли.

Э-э-э. ** Помните ту вещь, которую я сказал вначале о сценариях, выполняемых по порядку? Атрибут defer говорит вашему браузеру не запускать определенный файл JavaScript до тех пор, пока HTML-файл не будет полностью загружен. Без этой задержки ваш JavaScript начнет выполняется, как только начнет загружаться HTML. И тогда код в вашем файле ‘main.js’ запуститься перед кодом ‘game.js’. То есть ваша программа попытается запустить функцию ‘click()’ до того, как она будет определена.

Вот почему у вас сейчас есть ошибка в вашей консоли.

Связывание модулей с Webpack

Теперь, когда мы знаем, зачем нам нужен Webpack, давайте посмотрим на него в действии.

Webpack — это упаковщик модулей. Его целью является обработка вашего приложения путем отслеживания его зависимостей, а затем их объединение в один или несколько файлов, которые можно запустить в браузере. Так же, как приложения Node настраиваются с помощью package.json, вы можете настроить Webpack в своем файле webpack.config.js.

webpack.config.js

В основе Webpack лежат несколько ключевых компонентов: точка входа (entry point), место вывода (output location), загрузчики (loaders — https://webpack.js.org/concepts/loaders/) и плагины (plugins — https://webpack.js.org/concepts/plugins/). Я сосредоточусь только на вводе и выводе, но вы определенно будете использовать два других при настройке Webpack для более крупных проектов.

Entry: файл JavaScript, с которого начинается сборка Webpack.
module.exports = {
  entry: './path/to/my/entry/file.js'
};
Output: Имя и путь для упакованного файла JavaScript.
const path = require('path');

module.exports = {
  entry: './path/to/my/entry/file.js', // отправная точка для нашей программы
  output: {
    path: path.resolve(__dirname, 'directory_name'), // абсолютный путь к каталогу, в который мы хотим поместить вывод
    filename: 'my-first-webpack.bundle.js' // имя файла, который будет содержать наш вывод - мы могли бы назвать его как угодно, но типично bundle.js
  }
};

Ваш файл webpack.config.js может выглядеть примерно так:

const path = require('path');

module.exports = {
  mode: "development", // could be "production" as well
  entry: './src/main.js', 
  output: {
    path: path.resolve(__dirname, 'public'), 
    filename: 'bundle.js' 
  }
};

scripts

Теперь, когда у нас есть конфигурация Webpack, нам нужно добавить скрипты запуска в наш package.json. Для этого мы можем выбрать любое слово, которое захотим, но обычно используется build. Также если мы хотим, чтобы Webpack отслеживал изменения в файлах которые мы редактируем, мы можем добавить флаг «—w» в конце команды. (Если бы мы это не сделаем, то нам нужно запускать команду сборки каждый раз, когда мы вносим изменения в исходные файлы.)

Раздел scripts в webpack.config.js должен выглядеть так:

  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "build": "webpack --w"
  },

Что дальше?

И что теперь в консоли?

Это ваша первая сборка. Метаданные в вашей консоли говорят вам, насколько большим получился ваш пакет сборки. Вот Это Да! Теперь, когда вы сделали это, вы можете использовать модули ES. Это означает, что по мере роста вашей программы вы можете импортировать и экспортировать функции между файлами JavaScript. И это круто!

Последний штрих

Мы почти закончили. Мы настроили Webpack для сборки нашего файла main.js и вывода его в bundle.js в наш каталог /public.

Теперь мы можем использовать ES-модули в JavaScript. Помните, как функция click вызывалась до того, как она появилась в браузере? Теперь мы можем использовать синтаксис export и import, чтобы экспортировать функцию из game.js и вызывать ее в main.js, чтобы полностью избежать этой проблемы:

game.js

// функция click () должна быть выше этой строчки
export default click;

main.js

// В начале main.js
import click from './game'

Наконец, нам нужно внести небольшое изменение в наш HTML-файл. Прежде чем мы узнали о Webpack, index.html загрузил два отдельных файла JavaScript. Теперь весь код в этих файлах будет упакован в bundle.js — так что мы можем просто указать тег скрипта на bundle.js.

Идем дальше и заменим ваши теги script ссылкой на bundle.js:

  <!-- <script src="../src/game.js" defer></script>
  <script src="../src/main.js" defer></script> -->
  <script src="bundle.js" defer></script>

Теперь запустите open public/index.html.

Ваша программа выглядит и функционирует точно так же, как и раньше? Супер! Вы все сделали правильно.

Загляните в свой DevTools и перейдите на вкладку «Sources». Щелкнуть на bundle.js и увидите свой прекрасно упакованный JavaScript.

Что мы узнали?

Webpack — это пакетный инструмент, который упаковывает все ваши файлы JavaScript в один файл. Мы узнали что:

  • Webpack объединяет код JS и помогает поддерживать модули ES
  • Две основные концепции: точка входа и выхода
  • Как настраивать webpack.config.js

Отличная работа! Вы узнали так много, и все же, есть еще много всего, чему можно научиться. Вы так же можете прочитать о компиляторе Babel — https://github.com/babel/babel-loader. Webpack обычно используется с Babel для того чтобы новый синтаксис JavaScript мог работать в старых браузерах. Вы также можете прочитать о том, как Webpack обрабатывает CSS-файлы, code splitting и другие интересные вещи. Webpack также не единственный инструмент для этих целей — есть еще grunt (https://gruntjs.com/), gulp (https://gulpjs.com/) и browserify (http://browserify.org/).

Happy coding!

Webpack — это сборщик модулей для JavaScript-приложений. Он позволяет разделять код на модули, которые затем могут быть импортированы и использованы в других частях приложения. Это полезно для структурирования кода, оптимизации производительности и поддержки сторонних библиотек.

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

Подготовка

Для начала установите Node.js с официального сайта https://nodejs.org, если у вас его ещё нет. Затем создайте новую папку для проекта и перейдите в нее с помощью командной строки или терминала. В нашем случае это папка two_numbers, но у вас может быть любая другая.

mkdir two_numbers
cd two_numbers

Инициализация проекта

Находясь внутри папки, инициализируйте проект с помощью следующей команды:

npm init -y

Эта команда создаст файл package.json, который является основным файлом для управления зависимостями и настройками проекта на Node.js.

Параметр -y означает, что все вопросы, которые обычно задаются при создании файла package.json, будут пропущены, и будут использованы значения по умолчанию. В результате будет создан файл package.json с минимальными настройками, без дополнительного ввода со стороны пользователя.

Он выглядит примерно так:

{
  "name": "two_numbers",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "webpack serve",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "css-loader": "^6.7.3",
    "style-loader": "^3.3.2",
    "webpack": "^5.78.0",
    "webpack-cli": "^5.0.1",
    "webpack-dev-server": "^4.13.2"
  }
}

Установка webpack, webpack-cli и webpack-dev-server

Установите webpack, webpack-cli и webpack-dev-server с помощью следующей команды:

npm install webpack webpack-cli webpack-dev-server --save-dev

Команда webpack-cli позволит запускать Webpack из терминала.

webpack-dev-server — это удобный сервер разработки с горячей перезагрузкой. Это означает, что при изменении файлов в вашем проекте сервер автоматически пересоберёт сборку и обновит страницу в браузере. Без этого пакета вам придется самостоятельно настраивать сервер разработки и обновлять страницу в браузере после каждого изменения в коде.

О ключе --save-dev мы уже писали в одной из статей журнала.

Установка css-loader и style-loader

Webpack по умолчанию обрабатывает только JavaScript-файлы, поэтому для корректной работы с другими типами файлов, такими как CSS или HTML, необходимы специальные загрузчики и плагины.

css-loader позволяет Webpack обрабатывать и импортировать CSS в JavaScript-файлы. Он интерпретирует @import и url() как import/require() и разрешает их. В результате, после обработки css-loader, ваш CSS будет представлен в виде строки в JavaScript-коде.

style-loader. Поскольку после обработки css-loader ваш CSS превратился в строку JavaScript, style-loader используется для внедрения этих стилей в DOM. style-loader добавляет тег <style> внутри <head> вашего HTML-файла, содержащий все стили, включенные в сборку.

Таким образом, чтобы импортировать CSS в наш проект, нам нужно установить style-loader и css-loader. Для этого выполните следующую команду:

npm install style-loader css-loader --save-dev

Установка html-webpack-plugin

html-webpack-plugin автоматически генерирует HTML-файл на основе шаблона, вставляет туда ссылки на ваши файлы (например, JavaScript и CSS), и сохраняет его в указанной директории (обычно в папке dist)

Установите его:

npm install html-webpack-plugin --save-dev

C подготовкой и установкой закончили, теперь переходим к структуре проекта и коду.

Структура проекта

Для удобства откройте папку с проектом прямо в VS Code через меню Файл→Открыть папку (File→Open Folder). В итоге слева будет находиться структура проекта и все файлы, а справа вы будете их редактировать.

В корне проекта создайте пустой файл webpack.config.js и добавьте туда следующий код базовой конфигурации проекта. Обратите внимание на поясняющие комментарии.

const path = require('path'); // Импортируем модуль "path" для работы с путями файлов
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js', // Точка входа для сборки проекта

  output: {
    filename: 'bundle.js', // Имя выходного файла сборки
    path: path.resolve(__dirname, 'dist'), // Путь для выходного файла сборки
  },

  module: {
    rules: [
      {
        test: /.css$/, // Регулярное выражение для обработки файлов с расширением .css
        use: ['style-loader', 'css-loader'], // Загрузчики, используемые для обработки CSS-файлов
      },
    ],
  },
  
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
    }),
  ],

  devServer: {
    static: {
      directory: path.join(__dirname, 'dist'), // Каталог для статики
    },
    open: true, // Автоматически открывать браузер
  },

  mode: 'development', // Режим сборки
};

В файле package.json измените поле scripts на следующее:

"scripts": {
  "start": "webpack serve",
  "build": "webpack"
},

Это позволит запускать сервер разработки и сборку проекта с помощью кратких и удобных команд npm start и npm run build, вместо того чтобы вводить полные команды в терминал каждый раз.

Теперь создайте внутри проекта папку src, а в ней пустой файл index.js. В этом файле напишем основной код приложения, которое складывает два числа из текстовых полей.

function addNumbers() {
  const num1 = parseFloat(document.getElementById("number1").value);
  const num2 = parseFloat(document.getElementById("number2").value);
  const result = num1 + num2;
  document.getElementById("result").innerText = `Result: ${result}`;
}

document.getElementById("calculate").addEventListener("click", addNumbers);

Теперь создайте в той же папке файл index.html и добавьте туда этот код:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Калькулятор сложения</title>
</head>
<body>
  <input type="number" id="number1" placeholder="Первое число">
  <input type="number" id="number2" placeholder="Второе число">
  <button id="calculate">Сложить</button>
  <p id="result"></p>
  <script src="bundle.js"></script>
</body>
</html>

В папке src создайте файл styles.css и добавьте следующий код:

body {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
  margin: 0;
}

input, button, p {
  width: 100%;
  max-width: 300px;
  margin-bottom: 10px;
}

Итоговая структура проекта после всех действий будет выглядеть так:

Теперь ваш проект готов к использованию. Для запуска сервера разработки выполните следующую команду в терминале:

npm start

Webpack автоматически откроет браузер, и вы увидите страницу с текстовыми полями для ввода чисел и кнопкой Add. Также после запуска сервера вы можете перейти по адресу localhost:8082.

В открывшемся окне ведите два числа и нажмите кнопку Add для выполнения сложения. Результат будет отображаться под кнопкой. Готово!

Так зачем использовать Webpack?

Webpack позволяет легко структурировать код, разделяя его на модули и импортируя их при необходимости. Если в дальнейшем вы решите добавить сторонние библиотеки или фреймворки, то Webpack поможет вам интегрировать их в ваш проект, минимизировать размер файла сборки и улучшить производительность. А с использованием webpack-dev-server вы получаете автоматическую перезагрузку страницы при изменении исходного кода, что ускоряет процесс разработки.

В этом примере мы использовали только базовые возможности Webpack, но с его помощью вы можете сделать гораздо больше, например, обрабатывать и минифицировать CSS, использовать различные загрузчики (loaders) и плагины для транспиляции и оптимизации кода.

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

Дальше

  • Что такое Webpack
  • Как копировать статические файлы с помощью Webpack

«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.

ТелеграмПодкастБесплатные учебники

Webpack является одним из самых мощных и гибких инструментов для сборки frontend. Она содержит основные понятия, однако для начала их будет достаточно.

Инструменты сборки стали неотъемлемой частью веб-разработки, в основном из-за возрастающей сложности JS-приложений. Бандлеры позволяют нам упаковывать, компилировать, организовывать множество ресурсов и библиотек, необходимых для современного веб-проекта.
В этом гайде будет рассмотрен webpack, мощный бандлер с открытым исходным кодом, который может обрабатывать огромное количество различных задач. Автор статьи покажет вам как писать модули, бандл код, использовать некоторые плагины загрузчика. Пособие подойдет для тех, кто только начинает изучать этот инструмент, однако уже имеет некоторые знания JS.

Как и во многих других аспектах веб-разработки, здесь нет стандартного набора инструментов, который нужно использовать. Прямо сейчас разработчики могут выбирать между webpack, Gulp, Browserify, NPM scripts, Grunt и еще десятком других. Можно, конечно, провести их глубокое сравнение, но в целом все эти инструменты очень похожи, поэтому чаще всего дело сводится к личным предпочтениям и типу проекта, над которым вы работаете.
Тут некоторые плюсы и минусы, которые помогут вам решить, подходит ли этот бандлер именно вам:

Плюсы:

  • Великолепен для работы с одностраничными приложениями
  • Воспринимает как require()- так и import-синтаксисы модуля
  • Позволяет осуществлять продвинутое разделение кода
  • Hot Reload для более быстрой разработки с помощью React, Vue.js и подобных фреймворков
  • Наиболее популярный инструмент разработки по версии обзора JS в 2016 году

Минусы:

  • Не подойдет для новичков
  • Работа с файлами CSS, картинками и другими не JS ресурсами по началу сбивает с толку
  • Документация могла бы быть лучше
  • Очень много изменений, большинство гайдов 2016 уже устарели

Установка

Самый простой способ установки это использовать менеджер пакетов. Мы будем пользоваться npm, но вы можете использовать Yarn или любую другую альтернативу. В обоих случаях вам нужно иметь Node.js и готовый к работе package .json на компьютере.
Предпочтительнее устанавливать его локально без тега –g. Это поможет каждому, кто работает над вашим проектом, быть уверенным в том, что у вас одна и та же версия webpack’a.

npm install webpack --save-dev

После того как вы установили бандлер, лучше всего запустить его с помощью скрипта Node.js. Добавьте эти строки в ваш package.json.

//...
    "scripts": {
        "build": "webpack -p",
        "watch": "webpack --watch"
    },
//...

Теперь с помощью вызова npm run build из командной строки мы можем сделать webpack bundle нашими файлами ( означает создание и уменьшает связанный код). С запуском npm run watch начнется процесс, который автоматически свяжет наши файлы в случае изменения любого из них.
Последняя часть нашей настройки — это указать webpack, какие файлы связывать. Рекомендуем сделать это путем создания config файла.

Config файл webpack’a

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

В корневой директории вашего проекта добавьте файл webpack.config.js.

webpack.config.js

var path = require('path');

module.exports = {
  entry: './assets/js/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};

entry указывает webpack’y, какой из JavaScript файлов является основным. Существует множество различных стратегий настройки входных точек, но в большинстве случаев достаточно одной записи. В output мы указываем имя нашего пакета и путь к нему. После запуска webpack мы получим весь наш JavaScript в файле bundle.js. Это единственный файл, который мы будем связывать в нашем HTML:

<script src= "./dist/bundle.js" >

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

Модули Webpack

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

Мы хотим добавить модуль, который приветствует наших пользователей. Мы создаем файл greeter.js и экспортируем простую функцию.

greeter.js

function greet() {
    console.log('Have a great day!');
};

export default greet;

Чтобы использовать этот модуль, мы должны импортировать его и вызвать его в нашей точке входа, которым является, если вы посмотрите на файл конфигурации, — index.js.

index.js

import greet from './greeter.js';

console.log("I'm the entry point");
greet();

Теперь, когда мы запускаем bundler с npm run build и открываем наш HTML в браузере, мы видим следующее:

Наша точка входа и наш модуль greeter были скомпилированы в один файл, называемый bundle.js, и он был выполнен браузером. Вот простая схема того, что происходит на данный момент:

Требуемые библиотеки

Мы хотим, чтобы наше приложение указывало, в какой день недели он встречает пользователей. Для этого мы будем использовать moment.js, импортируя его непосредственно в наш модуль greeter.
Сначала мы должны установить библиотеку через npm:

npm install moment --save

Затем в нашем модуле приветствия мы просто импортируем библиотеку точно так же, как мы импортировали локальные модули в предыдущую точку:

greeter.js

import moment from 'moment';

function greet() {
    var day = moment().format('dddd');
    console.log('Have a great ' + day + '!');
};

export default greet;

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

Наша блок-схема теперь выглядит так:

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

Загрузчики

Загрузчики — это способ webpack выполнять задачи во время компоновки и до или после обработки файлов каким-либо образом. Например, они могут компилировать TypeScript, загружать компоненты Vue.js, отображать шаблоны и многое другое. Большинство загрузчиков написаны сообществом, поскольку список популярных загрузчиков можно посмотреть здесь.
Предположим, мы хотим добавить линтер к нашему проекту, который проверяет наш код JS на наличие ошибок. Мы можем это сделать, включив загрузчик JSHint, который будет перехватывать все виды плохого кода.
Сначала нам нужно установить JSHint и загрузчик webpack JSHint:

npm install jshint jshint-loader --save-dev

Теперь мы собираемся добавить несколько строк в наш файл конфигурации webpack. Это инициализирует загрузчик, сообщает ему, какие типы файлов следует проверять, а какие — игнорировать.

webpack.config.js

var path = require('path');

module.exports = {
  entry: './assets/js/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  // добавим загрузчик JSHint 
  module: {
    rules: [{
      test: /.js$/, // запустим загрузчик во всех файлах .js
      exclude: /node_modules/, // проигнорируем все файлы в папке  node_modules 
      use: 'jshint-loader'
    }]
  }
};

Теперь, когда webpack запущен, он покажет нам список предупреждений в терминале (которые мы проигнорируем):

Поскольку moment.js находится в папке node_modules, он не будет линтирован загрузчиком JSHint:

Дальнейшее чтение

На этом мы заканчиваем наше знакомство с webpack! Поскольку это урок для новичков, мы попытались охватить только самые полезные и обязательные концепции инструмента. Мы надеемся, что руководство было полезным, не слишком запутывающим, и, исходя из названия, займет 15 минут.
В ближайшем будущем автор планирует добавить вторую часть к этому учебному пособию, объясняя, как работать с модулями CSS и другими более продвинутыми функциями. Тем временем, если вы хотите больше узнать о webpack (и есть намного больше), он рекомендует проверить эти потрясающие ресурсы:

  • webpack.js.org- официальный сайт проекта, множество руководств и документов, доступных там
  • Awesome webpack -курированный список ресурсов webpack
  • Webpack 2 – A full tutorial — почти двухчасовой бесплатный видеоурок.
  • Примеры Webpack —
    список различных конфигураций webpack

Понравилась статья? Поделить с друзьями:
  • К основным стилям руководства не относится стиль
  • Руководство по ремонту nissan teana j33
  • Тринальгин уколы инструкция по применению что лечится
  • Руководство по эксплуатации vw polo 1998
  • Ивпр 203м руководство по эксплуатации