Webpack это: руководство для начинающих / Хабр
руководство для начинающих / Хабр
Доброго времени суток, друзья!
Представляю вашему вниманию перевод статьи «Webpack: A gentle introduction» автора Tyler McGinnis.
Перед изучением новой технологии задайте себе два вопроса:
- Зачем нужен этот инструмент?
- Какие задачи он выполняет?
Если вы не можете ответить на эти вопросы, возможно, вам не нужна изучаемая технология. Давайте попробуем ответить на эти вопросы применительно к 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 = {}
Основной задачей вебпака является анализ модулей, их опциональное преобразование и интеллектуальное объединение в один или более бандл, поэтому вебпаку нужно знать три вещи:
- Точка входа приложения
- Преобразования, которые необходимо выполнить
- Место, в которое следует поместить сформированный бандл
Точка входа
Сколько бы модулей не содержало приложение, всегда имеется единственная точка входа. Этот модуль включает в себя остальные. Обычно, таким файлом является 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'
}
}
Весь процесс выглядит примерно так:
- Вебпак получает точку входа, находящуюся в
./app/index.js
- Он анализирует операторы
import / require
и создает граф зависимостей - Вебпак начинает собирать бандл, преобразовывая код с помощью соответствующих лоадеров
- Он собирает бандл и помещает его в
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 / Хабр
Для начала установим webpack, делается это с помощью команд:
yarn add webpack webpack-cli -D
, если используете менеджер пакетов yarn npm i webpack webpack-cli --save-dev
, для менеджера пакетов npm
Настраивается Webpack с помощью конфигурационного файла webpack.config.js
, который хранится в корневой директории проекта.
Пример конфигурационного файла:
const path = require('path')
module.exports = {
watch: true,
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname,'build'),
publicPath: "/"
}
};
Начальная конфигурация представляет собой следующее:
- watch — заставляет webpack отслеживать изменения в файлах и заново выполнять сборку;
- entry — Указывает на точку входа в проект, и откуда нужно начать построение графа внутренних зависимостей проекта;
- output — Указывает путь, где будет располагаться создаваемый файл и как он будет называться;
Так же необходимо установить webpack-dev-server, который понадобится для разработки и отладки приложения.
yarn add webpack-dev-server
для менеджера пакетов yarn или npm i webpack-dev-server
если используется npm
Для настройки webpack-dev-server добавим devServer в нашем конфигурационном файле.
Параметры для webpack-dev-server:
module.exports = {
//...
devServer: {
port: 8000,
historyApiFallback: true,
hot: true,
},
};
Также нам нужно добавить/заменить в нашем package.json файле скрипт запуска проекта:
"start": "webpack-dev-server --mode development",
и скрипт для сборки билда:
"build": "webpack --mode production"
Загрузчики
Загрузчики (loaders) — это специальные модули, которые используются для «загрузки» других модулей. Так же они позволяют предварительно обрабатывать файлы по мере их импорта или «загрузки».
Загрузчики могут преобразовывать файлы, например TypeScript в JavaScript, sass в css. Они могут даже позволить нам делать такие вещи, как импорт файлов CSS и HTML непосредственно в наши модули JavaScript. Для их использования необходимо прописать нужные загрузчики в разделе module.rules файла конфигурации.
Примеры загрузчиков:
- babel-loader — использует babel для загрузки файлов ES2015.
- file-loader — для загрузки различных файлов (изображения, музыкальные дорожки и т.д.) в выходную директорию
- style-loader — используется для загрузки стилей
- css-loader — включает загрузку файлов стилей
- @svgr/webpack — лоадер, позволяющий использовать svg изображения как jsx элементы
Для использования babel-loader необходимо установить babel/core. Также установим пресет babel/preset-env, который компилирует ES2015+ в ES5 путем автоматического определения необходимых Babel плагинов и полифайлов. Далее создадим файл .babelrc и в него добавим ранее установленный пресет.
{
"presets": [
"@babel/preset-env"
]
}
Теперь добавим загрузчик в нашу конфигурацию для преобразования файлов Javascript. Это позволит нам использовать синтаксис ES2015 + в нашем коде (который будет автоматически конвертироваться в ES5 в окончательной сборке).
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
Пример конфигурации с лоадером file-loader
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
},
],
},
Пример конфигурации для лоадера @svgr/webpack
{
test : /\.(svg)$/,
use: [
{
loader: "@svgr/webpack"
}
]
}
Плагины
Плагины являются основой webpack, так как по сути вся его работа построена на системе плагинов. Они значительно расширяют возможности загрузчиков.
Загрузчики выполняют предварительную обработку файлов любого формата. Они работают на уровне отдельных файлов во время или до создания пакета. После того как отработают загрузчики наступает очередь плагинов. Плагины как правило отрабатывают только одну функцию.
Для их использования необходимо добавить нужные плагины в разделе plugins файла конфигурации.
Примеры плагинов:
- html-webpack-plugin — используется для создания html файлов
- copy-webpack-plugin — копирует отдельные файлы или целые каталоги, которые уже существуют, в каталог сборки.
- definePlugin — позволяет создавать глобальные константы
- HotModuleReplacementPlugin — включает HMR режим, обновляет только ту часть, которая изменилась, не перезагружая полностью приложение.
Пример конфигурации с добавленными плагинами:
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
'process.env': JSON.stringify(process.env),
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
}),
new CopyWebpackPlugin({
patterns: [
{ from: './public/favicon.ico', to: './public'}
]
}),
],
Также добавим плагин UglifyjsWebpackPlugin, который минимизирует js код, для этого нужно установить uglifyjs-webpack-plugin и добавить его в разделе optimization
optimization: {
minimizer: [new UglifyJsPlugin()]
},
Что такое Webpack? Современные инструменты frontend-разработчиков.
1 year ago |
6.8K
Всех приветствую, с вами Сергей Никонов и в этом уроке я расскажу про Webpack.
Итак, что же такое Webpack?
Webpack — это инструмент для сборки современных frontend-приложений.
В настоящий момент, по популярности, Webpack опережает похожие инструменты, такие как Gulp и Grunt.
Логика работы очень простая, но для того, чтобы понять какие задачи решает Webpack, вам необходимо понимать из чего состоит современное веб-приложение.
Смотрите видео Что такое Webpack
Дело в том, что современное веб-приложение может состоять из достаточно большого количества файлов и может быть написано с помощью современного JavaScript и различных препроцессоров, например SASS и других.
Браузеры пока в полной мере не поддерживают эти технологии и по этой причине, для преобразования кода на современном JavaScript в JavaScript ES5 и перевода кода из SASS в css и многих других задач, используются дополнительные инструменты, такие как webpack.
Когда вы один раз настроите Webpack для вашего фронтенд приложения, вебпак будет автоматически выполнять все рутинные задачи, а вам останется только наслаждаться написанием вашего кода!
В конечном итоге, используя Webpack, вы будете получать две версии вашего проекта: Первая — это исходный код, который вы будете писать с помощью современного javascript и использовать другие удобные инструменты и вторая версия вашего веб-приложения — это преобразованный код для публикации на хостинг. Как на схеме ниже:
При этом, если исходный код вашего веб-приложения будет состоять из большого количества файлов и компонентов, то для публикации вашего веб-приложения на хостинг, в результате преобразования, вы получите всего несколько файлов, при этом, webpack можно настроить таким образом, чтобы он сжимал весь конечный код и даже автоматически оптимизировать изображения вашего проекта.
Это позволяет сокращать размер вашей страницы и в итоге ваше веб приложен
Внутреннее устройство и оптимизация бандла webpack / Блог компании JUG Ru Group / Хабр
Webpack фактически стал стандартом для сборки крупных приложений на JS. Его используют практически все. Для разработчика webpack выглядит как магический черный ящик: если забросить в него файлы и небольшой конфиг, на выходе автоматически появится бандл.
Чтобы разобраться в секретах этой магии, мы обратились к эксперту, человеку, который неоднократно залезал внутрь webpack, — Алексею Иванову. Он готов объяснить, как выглядит бандл изнутри, как на него влияют разные настройки, к чему и почему могут привести некоторые из них, а также рассказать, как все это отладить и оптимизировать.
В основе материала — доклад Алексея Иванова на конференции HolyJS 2017, проходившей в Санкт-Петербурге 2-3 июня.
В компании Злые марсиане я занимаюсь сервисом «eBay for business». eBay выдвигает довольно жесткие требования, например по тому, сколько должен весить сайт, первая страница, сколько она должна грузиться. Чтобы не выйти за пределы ограничений, мы регулярно смотрим на содержимое наших бандлов: что туда попадает и что делать, чтобы туда не попадало всякого странного.
Для этого мы используем различные инструменты: webpack bundle analyzer, webpack runtime analyzer и другие. Выглядит это примерно так:
Он смотрит за вашим билдом. Когда вы закончили собирать бандл, он берет результат сборки, парсит его и показывает три красивые мапы. И когда вы занимаетесь этим постоянно, то начинаете замечать странности.
- React в бандле весит больше чем lib/react.js
- Несколько версий lodash или underscore
- Moment.js грузит 100+ локалей
- Непонятные полифилы
- Не работает tree shaking
- Изменяется кеш для не изменившихся чанков
- И так далее
И вот тут выходит так: вроде вы все сделали по инструкции, но что-то не работает. И инспектор говорит, что произошла какая-то фигня. Что делать в такой ситуации, на самом деле не очень понятно.
Моей первой идеей было пойти в интернет и искать новые мануалы, новые инструкции, которые рассказали бы, что же происходит не так. Но оказалось, что докладов о том, какой webpack хороший, — тысячи, а информации о том, что же делает webpack с вашим кодом, как у него внутри все устроено и почему он делает так, а не иначе, не было совсем.
После этого мне пришлось провести серию экспериментов: запустить webpack с пустым бандлом, с одним файликом и пустым js, с одним import и т.д. И мне это помогло, все возникшие проблемы я исправил. И тут я подумал, что я, наверное, не один такой, и с таким проблемами сталкивались многие. И поэтому решил поделиться своим опытом.
Содержание
- CommonJS
- Резолв путей до файлов
- Устройство бандла изнутри
- Глобальные константы и DefinePlugin
- UglifyJS dead code elimination
- ES6 modules и tree shaking
- Выделение чанков и асинхронная подгрузка
- Анализ результатов сборки
Начнем с CommonJS-модулей. CommonJS-модули — это такая штука, когда в одних файлах вы пишите require, а в других — exports или module.exports.
Про CommonJS-модули необходимо помнить следующее: они появились в node, и когда вы пользуетесь webpack, на самом деле используете не просто CommonJS-модули, а штуку, которая позволяет обеспечить совместимость с node. В общем CommonJS необходима:
- JS-файл с переменными require, exports, module
- this равно exports
- exports по умолчанию равно {}
- Чтобы мы могли использовать npm-модули в браузере, нам нужно эмулировать в браузере это поведение.
Вторая очевидная вещь — ее, наверное, все знают, но я все же расскажу. Что такое резолв путей? Вот у нас есть require(), внутри него пишем путь, по которому говорим, что нужно зарекуарить. Есть простые варианты, когда мы пишем черточку либо черточку с точками. И он идет либо в текущую папку, либо в корневую систему и т.д. Тут никаких нюансов нет, здесь все просто.
Дальше, если вы не написали расширение, то он сначала попытается добавить расширение js. Если его нет, он попытается найти папку, где лежит index.js. Если мы говорим про node, то она ищет по умолчанию не только js, а еще JSON, файлы с расширением node и другое. В webpack все это не используется, поэтому об этом можно пока забыть. Но это и так все знают.
Самое интересное — это то, что происходит, когда мы пытаемся сделать import модуля. Если мы написали module, что делает node и, соответственно, webpack? Она идет в текущую папку node, ищет папку node_modules, заходит в нее и ищет папку с названием модуля. Если она нашла ее, то она ее берет, и дальше все нормально.
Если она ее не нашла, она идет в папку на уровень выше, после этого — еще на уровень выше, и так пока не дойдет до корня файловой системы. И вот тут у нас есть первый нюанс, на который можно хорошо попасться. Он выглядит вот так:
Вы поставили какую-то библиотеку из npm, и в ней прописано, что ей нужна зависимость lodash 1.0.0, а вы в своем проекте используете lodash 5.0.0. И вот, если так произошло, npm создаст свою папку node_modules и поставит свою версию lodash. Если таких библиотек несколько, то в каждой может оказаться версия lodash, никак не связанная со всеми остальными. Если вы используете node, никаких проблем, а вот если webpack, то все версии lodash подгружаются в браузер. Это такие базовые вещи, которые webpack’у необходимо делать, чтобы хорошо работать.
Базовое устройство бандла
Базовое устройство бандла выглядит примерно так:
Вот у нас есть файл, и вроде бы там все хорошо, но есть нюансы. Браузер ни про require, ни про exports, ни про module ничего не знает. Ему надо об этом как-то рассказать. Самый простой способ — взять содержимое файла, обернуть его в функцию, в которой все эти штуки передать в параметрах, а потом в какой-то момент ее выполнить. На самом деле webpack примерно так и делает, но с небольшим изменением.
Первое изменение: мы меняем require на __webpack_require__. Зачем это нужно?
На самом деле, для двух вещей. Во-первых, чтобы, когда вы противоестественным образом подгружаете себе js в обход webpack, например, через JSONP или еще как-то, он не ломал билд. Потому что если создается функция с названием require, то могут быть всякие нехорошие вещи, а так есть некоторая защита от этого.
Во-вторых, из-за способа webpack помечать функции, которые он портирует внутрь бандла. Соответственно, мы можем делать всякие оптимизации.
Покажу еще раз. Вот тут у нас написан path:
Вот тут у нас в скобочках 0:
Почему так? В браузерах файловой системы нет. Поэтому когда webpack собирает модули в один файлик, он на самом деле кладет их в массив. Цифра в скобочках — по умолчанию индекс в этом массиве. Это нам аукнется еще в будущем. Почему массив? Потому что по сравнению со всякими объектами с ключами это самый компактный вариант и он будет меньше всего весить.
То есть пришел webpack, обернул все модули в функции, положил все функции в массив модулей и добавил в начало анонимную функцию, которая это все подгружает.
Первая строчка, которая нам важна, — это installedModules. То есть когда webpack грузит массив, он не инициализирует то, что в нем находится, автоматически — оно продолжает лежать в массиве мертвым кодом.
В тот момент, когда вы рекуарите первый файлик, webpack создает экземпляр этого файлика, который как-то может дальше жить, и в нем уже дальше все сохранено и более-менее поддерживается.
Дальше у нас есть функция __webpack_require__, которую мы будем передавать внутрь. И есть такое корневое место, которое вызывает ваш файлик, и вы начинаете строить ваш корневой бандл. То есть мы подгрузили все в массив, вызвали функцию, объявили функцию __webpack_require__ и вызываем корневой файл.
Что делает __webpack_require__
- Ищет инициированный модуль в кэше
- Создает заглушку и добавляет ее в массив
- Выполняет код модуля с this равным module.exports
- Возвращает module.exports
Как именно работает __webpack_require__, что он вообще делает? Опять, как я уже и говорил, смотрим, нет ли у нас в модуле кэша. Если нет, идем дальше, а если есть, возвращаем из него. Дальше, если его нет, мы создаем заглушку и добавляем ее в массив. Заглушка выглядит примерно так:
У нас есть айдишники (в основном цифры, но иногда — нет) и функция exports. По умолчанию у нее ставится пустой объект, поэтому когда мы эту штуку будем вызывать внутри модуля webpack, по умолчанию она там будет пустым объектом.
Дальше происходит следующее. Мы берем наш код и вызываем то, что у нас было в массиве, примерно в таком виде:
То есть мы вызываем не просто функцию, а именно через call, чтобы exports тоже попал в первый объект. Это нужно для обратной совместимости с node. В результате получается примерно следующее:
С этого момента у нашего модуля, который живет в installedModules, в exports уже не пустой объект, а то, что мы ему назначили и вернули. Почему это интересно и важно? Потому что, так как мы делаем все вот таким способом, у нас в бандле есть один рабочий экземпляр нашего модуля, и мы можем его использовать как замыкание.
То есть если мы объявляем в модуле какую-то переменную, она будет общей для всех instance’ов. Если мы в exports экспортируем какой-то метод, который позволяет, например, инкрементировать эту переменную внутри замыкания, то эта переменная тоже будет доступна внутри. Если вы тут объявляете какую-то библиотеку и назначаете ей некоторые плагины, то instance-библиотеки со всеми плагинами тоже будут общими. Соответственно, с помощью этой штуки можно делиться информацией со всеми модулями и делать другие интересные вещи.
После того как мы все это инициализировали, создали экземпляр модуля, мы возвращаем то, что находится внутри exports, и на этом успокаиваемся.
На самом деле, если бы мы говорили про CommonJS и самый простой бандл, то на этом можно было бы заканчивать, потому что в самом простом варианте webpack больше ничего не делает. На практике webpack стал популярным не из-за того, что он умеет вот так делать, а из-за того, что он умеет делать более сложные вещи. Например, он умеет делать вот так:
То есть когда вы указываете внутри require не полный путь файла, а какую-то регулярку, webpack сможет это собирать. При этом так как он не занимается анализом кода в живую, то он не может знать, что у вас на самом деле используется, и на всякий случай тащит туда все, что вообще может быть. То есть как это все работает? Webpack в данном случае создаст в массиве новый модуль, в котором запишет логику про resolve путей.
Внутри самого модуля живет карта, в которой описаны все возможные пути. То есть если у вас лежит 20 файлов, то он все сюда положит и сделает 40 вариантов имени, если вы указываете, например, с js или без js. Дальше он сделает функцию, которая будет проводить эволюцию выражения, переданного внутрь функции, и сравнивать то, что есть в массиве. Если она найдет совпадение, то вернет тело, если нет, выкинет ошибку. В этом месте тоже может возникнуть проблема. Я думаю, многие с ней сталкивались.
Проблема следующая. У нас есть библиотека moment.js, которая позволяет делать различные операции с датами. Когда вы ее используете через node, есть небольшой нюанс. Внутри корневого файла moment.js есть строчка require(‘./locale/’ + name). Соответственно, webpack идет внутрь папки locale, находит там 118 локалей, подгружает их все в bundle и создает карту примерно из 250 ключей. Наверное, это не совсем то, что хотелось бы видеть.
Для webpack есть ContextReplacementPlugin. Он проверяет первую часть по маске. Если маска совпала с тем, что написано в первом аргументе, он, вместо того чтобы возвращать то, что нашел в файловой системе, возвращает то, что вы ему передали вторым параметром.
Глобальные константы и DefinePlugin
У нас есть бандл, в нем все хорошо. Он подключает файлы, разруливает пути и так далее. Иногда нам нужно сделать так, чтобы он жил не на основе тех данных, которые у нас есть, а получал какую-то информацию снаружи.
Допустим, у вас есть dev и production-версии, у которых разный путь, и вы хотите, чтобы webpack при dev разработке работал с одним путем, а при production — с другим. Или есть разные номера версий. Еще один вариант использования, который применяется во многих библиотеках, — задание process.env.NODE_ENV. Данная переменная является эдаким общим шаблоном, который говорит, что эти функции не надо использовать в режиме разработки, а, например, функции для дебага — в режиме production.
Возникает вопрос, как нам передавать эти переменные? Существует DefinePlugin, где можно объявить данные переменные и, следовательно, они попадут внутрь бандла.
DefinePlugin берет строку слева, в нашем случае это VERSION, далее берет строку справа, идет в файлик, и регулярка заменяет старую строку на новую. В итоге результат выглядит примерно так:
JSON.stringify добавляет кавычки. Если бы кавычек не было, у нас была бы просто цифра 1.0.1 и все бы сломалось. Что здесь произошло? Если после замены какой-либо строки или числа webpack может понять, что это условие if, и он понимает, что левая часть сравнивается с правой и обе — константы, то он заменяет их на true либо на false. Так происходит для того, чтобы мог прийти UglifyPlugin и прибраться.
Вторая интересная вещь: как вы могли заметить, require осталась require. Она не заменилась на __webpack_require__. Соответственно, require в бандл не попала и в сборке ее не будет. Если же вы хотите отключить какую-то часть функционала, вот один из способов это сделать. Но, как и везде, тут есть нюансы. Проблема выглядит вот так:
Если вы хотите быть модным и использовать, например, babel, и у него есть деструктуринг, то вы можете написать NODE_ENV. К сожалению, это все поломает. Но почему? Вот так выглядит код после преобразования:
То есть одна переменная ссылается на вторую, которой тоже что-то приходит. И внутри ваше условие будет выглядеть так: NODE_ENV !== «production».
На самом деле если выкатить этот код на production, все будет работать так, как вы бы и хотели. Потому что переменной приходит false, и то, что внутри if, не выполнится. Но так как webpack не знает, что такое переменные, и он не делает полный анализ кода и не выясняет, что в этой переменной будет, то он не может понять, что то, что находится внутри, грузить не нужно. В этой ситуации модуль, который находится внутри и не должен грузиться, на самом деле подгрузится.
Поэтому, еще раз, если вы используете DefinePlugin, то обязательно используйте замену строк, т.е. полная строка заменяется на полную строку, никаких сокращений.
Что будет, если заменить process.env?
Webpack пытается эмулировать node, потому что большинство модулей, которые лежат в папке node_modules, могут быть и чисто node. Стандартная переменная в node — process. Поэтому когда вы не указали process.env, а какая-то из библиотек, которая в импорте, использует process.env или просто process, webpack думает, что это node-модуль, и добавляет полифил. В итоге мало того, что код не уменьшился, так еще и полифил добавился. То есть любую переменную, которая по умолчанию есть внутри node, если она используется в вашем файле и не заменена и не объявлена, при сборке webpack заменит полифилом.
Функции отладки в библиотеках
Redux
…
Если вы не добавите process для React, Redux и пр., будет много полифилов.
Сжатие кода
Что же делает Uglify со всем нашим кодом?
UglifyPlugin
- Удаляет пробелы
- Переименовывает переменные короткими именами
- Делает dead code elimination
Сначала приходит UglifyPlugin и убирает лишние пробелы, переносы, заменяет длинные названия переменных короткими, но делает это внутри функции.
Но если переменные объявлены вне функции, глобально, то доступ к ним имеют и другие функции. Поэтому когда мы отдадим такой код Uglify,
то все переменные останутся. Теперь мы подошли к самому интересному, к нашему true и false.
Что здесь происходит? Мы каким-то образом внутри условия сделали значение, которое стало константой и гарантированно не поменяется. Когда сюда придет Uglify и увидит это, то оставит вот так:
Если в условии живет переменная, которая является не статической, Uglify не сможет понять, что происходит, и ничего не удалит.
Поэтому даже если вы объявили переменную перед условием и указали, что она false, и сразу после нее идет if, то Uglify все равно не будет разбирать данный код. Весь этот код в итоге останется.
ES6 modules
Больше ничего глобального не происходит. Отличие, которое есть у import и export, примерно следующее. Во-первых, ключи в import и export обязательно иммутабельны, т.е. мы не можем собирать их из частей, они всегда должны быть константой. Во-вторых, import и export должны жить в верхнем scope.
В чем радость от использования import и export? Она выглядит примерно вот так:
Tree shaking
В теории webpack может точно определить, что используется у нас в приложении, и помечать их соответственно. На практике все несколько сложнее.
Одна из главных фич, которая есть в webpack 2, — понимание import и умение делать Tree shaking. Было бы замечательно, если бы все работало, но есть проблема.
На самом деле то, что export не используется, еще не означает, что код не выполнится и у него не будет сайд-эффектов. Если у кода есть сайд-эффекты, существует вероятность что-то поломать.
Webpack очень сильно печется об обратной совместимости. Поэтому он пытается сделать так, чтобы никакое удаление не сломало билды. Поэтому он делает следующее:
Что произойдет после того, как webpack прочтет это? Сначала место, которое он импортирует, превращает в CommonJS-модуль.
Тут уже интереснее. Что происходит в файле, с которого ушел export? Во-первых, убрали export перед словом const. Во-вторых, у той константы, которая экспортируется, мы вручную написали __webpack_exports__ с каким-то ключом. И все вроде бы хорошо. Когда Uglify приходит сюда и видит неиспользованную константу 2, он ее удаляет.
Но существуют нюансы.
Если у одного из экспортов использовалась переменная, которую раньше импортировали из какого-то import, то в этом месте webpack и Uglify уже ничего не удалят. Точнее, const method удалится, а import останется и его содержимое тоже добавится в бандл. Почему? Во-первых, потому что webpack не знает, будет ли он использоваться или нет, есть ли там сайд-эффекты или нет, поэтому он его оставил. Во-вторых, используется модуль, поэтому он его тоже оставил. После этого к нам пришел Uglify, увидел метод и удалил его, а import оставил, потому что это на самом деле вызов из массива, там могут быть сайд-эффекты и на самом деле Uglify про него ничего не знает. Поэтому он эту переменную оставит, и она будет жить внутри массива.
Например, мы решили использовать lodash-es, который написан с import и export. Мы импортируем из него метод и надеемся, что все остальное не попадет, но на самом деле так не сработает.
В строке, где from, мы сделали импорт всех модулей, которые есть в lodash, и теперь все они попадут к вам в бандл. Здесь от этой проблемы не уйти. Необходимо использовать какой-нибудь babel-плагин, который будет заменять lodash на конкретные методы, либо вручную записать вплоть до метода все, что необходимо.
И еще очень важно: по умолчанию, если вы используете babel с дефолтными настройками, то он на самом деле транспилирует все ваши красивые импорты и экспорты в обычный require. Поэтому если у вас стоит babel с дефолтными настройками, то во внутрь webpack’a у вас никакие импорты не попадут, а будут лишь только старые require. Соответственно, если вы хотите, чтобы они работали, необходимо в babel заменить транспилинг import и export.
Чанки
Чанк — это кусок кода, который можно загружать синхронно или асинхронно. Для того чтобы загрузить чанк, необходимо немного поправить код, который занимается инициализацией. Он правится примерно так — добавляется функция window[«webpackJsonp»].
Чанки
- Синхронные и асинхронные
- В первый файл добавляется функция window[«webpackJsonp»]
- В следующих файлах вызывается функция webpackJsonp со списком модулей и id модулей, которые надо запустить
- Все модули попадают в общий массив и используются оттуда
У нас есть какой-то файл, который первым грузится синхронно, вы добавляете его в шапку. В этот файл добавили функцию. Все остальные файлы состоят из вызова этой функции, на вход которой приходит объект, в котором есть, например, id чанка, список модулей и т.д.
Дальше, после того, как мы это все загрузили, webpack берет загруженные модули и просто по нужным индексам добавляет в изначальный массив. Больше ничего не происходит, дальше продолжаем использовать первый массив.
С синхронными чанками все просто. У нас есть два файла: сначала мы загрузили файл, в котором есть код подгрузки, потом после него добавили еще один файл, в котором подгружаем следующий кусок модулей. Здесь необходимо понимать: если мы грузим его так, то нам важен порядок и, соответственно, мы не можем добавлять асинхронных подгрузок или проводить параллельную загрузку, потому что вторая функция выполнится, ничего не найдет и все сломает.
Асинхронные чанки работают точно так же. Там есть всего один нюанс. Нам нужно их грузить не в момент загрузки страницы, а отдельным запросом с помощью добавления тега script в шапку на лету. В коде это выглядит примерно так:
У нас есть функция import, в которой мы прописываем, что хотим импортировать, и она потом нам возвращает промисы. И когда она готова, возвращает нам ошибку или передает то, что мы загрузили. В транспилированном виде это выглядит примерно следующим образом:
У нас добавляется функция __webpack_require__.e, которая асинхронно загружает другие файлы. В ней нам важен один момент, который выглядит вот так:
Если бы у нас чанки назывались цифрами, то ничего страшного бы не было. Мы бы передавали туда цифры, и все было бы хорошо. Но для того чтобы использовать кэш или удобнее разбираться с ним, мы обычно их именуем или добавляем какую-то хитрую строчку в url, чтобы этот url был уникальным. Когда webpack’у необходимо подгрузить этот файл, ему нужно знать имя.
Соответственно, все имена всех чанков в виде объектов всегда хранятся внутри первого файла. И каждый раз, когда мы меняем чанк, у него меняется хэш и имя — и эта часть кода тоже генерируется.
CoomonsChunkPlugin
Собственно, как создаются чанки? Самый простой способ — при помощи CommonChunkPlugin.
Работает очень просто. Мы добавляем плагин, говорим ему, что «minChunks: 2» — это значит, что если в двух чанках используется какой-то общий плагин, то давайте создадим отдельный чанк, который будет грузиться синхронно, и в нем будет лежать общая часть. Но при этом есть пара нюансов.
Первый нюанс — когда мы вот так написали, и у нас есть чанки, которые создаются через import, то он не будет с ними работать, так как данные чанки считаются детьми, и у них отдельная логика.
Например, вы работаете с React и, чтобы у вас было не пять копий, а одна, вам необходимо добавить children: true, тогда он будет выносить общий модуль и из детей.
Второй нюанс: когда у нас есть код, который берет все, что есть в папке node_modules, и выносит в отдельный чанк. Какая здесь логика? Мы обновляем модули редко, они у нас лежат в отдельном файле, и мы хотим закэшировать их. Свой код меняем часто, кэшировать его хотим отдельно, и нужно, чтобы обновлялся только он, а не все заново. Так большая часть станет как бы константой и не будет грузиться каждый раз, а меньшая часть будет обновляться.
Вот такой код позволяет нам это сделать. Но с ним есть один нюанс. Он не работает.
Изменяющиеся индексы
Пример с node_modules не работает для кэша:
- При добавлении файлов меняются индексы
- Код загрузки и инициализации живет в первом файле:
- меняется стартовый индекс
- меняются ссылки на чанки
Он не работает по двум причинам. Первая выглядит так:
В __webpack_require__ индекс не всегда первый. Там может быть другой, случайный индекс из массива. Соответственно, если вы удаляете или добавляете какой-то файл, индекс первого объекта может меняться. Код первого чанка меняется всегда и, соответственно, у него меняется хэш, сумма и все остальное.
Вторая проблема: чтобы асинхронно грузить чанки, необходима карта их имен. Соответственно, если содержимое какого-либо чанка меняется, то карта, которая нужна для их подгрузки, тоже меняется.
Первый файл, где живет функция webpack, которая загружает все это, гарантированно будет меняться всегда при каждом изменении любого файла. Что с этим нужно сделать?
Необходимо сделать две вещи. Во-первых, зафиксировать имена файлов. Для этого в webpack есть два встроенных плагина. Первый позволяет оставлять те имена, которые вы использовали раньше внутри webpack’а, но это не очень удобно для production, так как имена становятся очень длинными. Второй позволяет менять имена на четырехбуквенные хэши.
Во-вторых, необходимо ту часть кода, которая отвечает за подгрузку новых модулей и чанков, вынести в отдельный чанк. Соответственно, это можно сделать примерно так:
Здесь «minChunks: Infinity» означает, что будет только код загрузки и 0 своих чанков. Соответственно, у вас получится не два файла, а три: первый — с кодом загрузки, второй — с node_modules, третий — с вашим кодом. Конечно, кода станет больше, но зато будет работать кэширование.
Вот так можно подключить два плагина подряд:
Анализ бандла
Для анализа бандла есть два полезных плагина:
webpack-bundle-analyzer
Строит treemap бандлов. Удобно проверять, не попали ли в бандл:
- Две версии одной библиотеки
- Копии библиотеки в разных чанках
- Библиотеки, которые должны были вырезаться по условию
- Непредвиденные зависимости у библиотек
- Просто большие файлы
И второй, более удобный плагин:
webpack-runtime-analyzer
Показывает отношения между файлами в графе — кто на кого ссылается, кто кого добавил в сборку. Удобно использовать, чтобы понять:
- Кто именно использует файл
- Кто именно подключил библиотеку
Итого
- Сделайте пустой бандл и посмотрите содержимое, там 40 строчек
- Не бойтесь ходить в исходники и смотреть, что получилось в коде
- После добавления библиотек всегда запускайте анализатор банда и смотрите, что он с собой притащил
- После добавления чанков проверяйте их содержимое
Если вы любите JS так же, как мы, и с удовольствием копаетесь во всей его нутрянке, вам могут быть интересные вот эти доклады на нашей декабрьской конференции HolyJS 2017 Moscow:
Что такое Webpack
Webpack — это сборщик модулей . Webpack может позаботиться об объединении вместе с отдельным средством выполнения задач. Однако грань между сборщиком и исполнителем задач стала размытой благодаря плагинам webpack, разработанным сообществом. Иногда эти плагины используются для выполнения задач, которые обычно выполняются за пределами веб-пакета, таких как очистка каталога сборки или развертывание сборки, хотя вы можете отложить эти задачи за пределами веб-пакета.
React и Hot Module Replacement (HMR) помог популяризировать webpack и привел к его использованию в других средах, таких как Ruby on Rails.Несмотря на свое название, webpack не ограничивается только сетью. Он также может объединяться с другими целями, как описано в главе «Цели сборки».
Если вы хотите лучше понять инструменты сборки и их историю, ознакомьтесь с приложением «Сравнение инструментов сборки».
Самый маленький проект, который вы можете связать с webpack, состоит из входных и выходных . Процесс объединения начинается с определяемых пользователем записей . Сами записи — это модули и могут указывать на другие модули через импорта .
Когда вы объединяете проект с помощью webpack, он проходит через импорт, строит граф зависимостей проекта, а затем генерирует выходных на основе конфигурации. Кроме того, можно определить точек разделения для создания отдельных пакетов в самом коде проекта.
Внутренне webpack управляет процессом объединения, используя так называемые блоки , и этот термин часто встречается в документации, связанной с webpack. Чанки — это более мелкие фрагменты кода, которые включены в пакеты, отображаемые в выводе webpack.
Webpack поддерживает форматы модулей ES2015, CommonJS, MJS и AMD из коробки. Также имеется поддержка WebAssembly, нового способа запуска низкоуровневого кода в браузере. Механизм загрузчика также работает для CSS, с поддержкой @import
и url ()
через css-loader . Вы можете найти плагины для конкретных задач, таких как минификация, интернационализация, HMR и т. Д.
Граф зависимостей — это ориентированный граф, который описывает, как узлы связаны друг с другом.В этом случае определение графа определяется через ссылки (
требуется
,импорт
) между файлами. Webpack статически обходит их, не запуская источник для генерации графа, необходимого для создания пакетов.
Процесс выполнения Webpack
Webpack начинает свою работу с записей . Часто это модули JavaScript, с которых webpack начинает свой процесс обхода. Во время этого процесса webpack оценивает совпадения записей с конфигурациями загрузчика , которые сообщают webpack, как преобразовать каждое совпадение.
Начиная с webpack 5 есть поддержка экспериментов. Они представляют будущую функциональность, которая скрыта за флагом функции и позволяет раннее тестирование.
Запись сама по себе является модулем, и когда webpack встречает его, он пытается сопоставить модуль с файловой системой, используя конфигурацию resolve
. Например, вы можете указать webpack выполнять поиск в определенных каталогах в дополнение к node_modules
.
Можно настроить способ сопоставления веб-пакетов с расширениями файлов, и вы можете определить конкретные псевдонимы для каталогов.В главе «Использование пакетов» эти идеи рассматриваются более подробно.
Если пройти разрешение не удалось, webpack вызовет ошибку времени выполнения. Если веб-пакету удалось разрешить файл, веб-пакет выполняет обработку сопоставленного файла на основе определения загрузчика. Каждый загрузчик применяет определенное преобразование к содержимому модуля.
Способ сопоставления загрузчика с разрешенным файлом можно настроить разными способами, в том числе по типу файла и расположению в файловой системе.Гибкость Webpack даже позволяет вам применить определенное преобразование к файлу на основе , где он был импортирован в проект.
Такой же процесс разрешения выполняется для загрузчиков webpack. Webpack позволяет применять аналогичную логику при определении того, какой загрузчик следует использовать. По этой причине у загрузчиков есть собственные конфигурации разрешения. Если webpack не может выполнить поиск загрузчика, это вызовет ошибку времени выполнения.
Чтобы решить эту проблему, webpack полагается на пакет расширенного разрешения, расположенный ниже.
Webpack разрешит каждый обнаруженный модуль при построении графа зависимостей. Если запись содержит зависимости, процесс будет выполняться рекурсивно для каждой зависимости до завершения обхода. Webpack может выполнять этот процесс для файлов любого типа, в отличие от специализированных инструментов, таких как компилятор Babel или Sass.
Webpack дает вам контроль над обработкой различных ресурсов, с которыми он сталкивается. Например, вы можете выбрать встроенных ресурсов в свои пакеты JavaScript, чтобы избежать запросов.Webpack также позволяет использовать такие методы, как CSS-модули, для объединения стилей с компонентами. Экосистема Webpack наполнена плагинами, расширяющими его возможности.
Хотя webpack используется в основном для связывания JavaScript, он может захватывать такие ресурсы, как изображения или шрифты, и создавать для них отдельные файлы. Записи — это только начальная точка процесса объединения, и то, что излучает веб-пакет, полностью зависит от того, как вы его настраиваете.
Предполагая, что все загрузчики были найдены, webpack оценивает соответствующие загрузчики снизу вверх и справа налево ( styleLoader (cssLoader ('./main.css '))
) при запуске модуля через каждый загрузчик по очереди. В результате вы получите вывод, который Webpack внедрит в получившийся пакет . В главе «Определения загрузчика» эта тема подробно рассматривается.
Если оценка загрузчика завершена без ошибок выполнения, webpack включает исходный код в комплект. Хотя загрузчики могут многое, они не обеспечивают достаточной мощности для сложных задач. Плагины могут перехватывать событий времени выполнения , предоставляемых webpack.
Хорошим примером является извлечение пакета, выполняемое модулем MiniCssExtractPlugin
, который при использовании с загрузчиком извлекает файлы CSS из пакета в отдельный файл.Без этого шага CSS будет встроен в результирующий JavaScript, поскольку webpack по умолчанию обрабатывает весь код как JavaScript. Идея извлечения обсуждается в главе Разделение CSS.
После оценки каждого модуля webpack записывает , вывод . Вывод включает сценарий начальной загрузки. Это небольшая среда выполнения, которая выполняет результат в браузере и загружает список пакетов манифеста.
Манифест может быть извлечен в отдельный файл, как обсуждается далее в книге.Вывод отличается в зависимости от цели сборки, которую вы используете (таргетинг на Интернет — это не
.
Введение в Webpack
Что такое веб-пакет?
Webpack — это инструмент, который позволяет компилировать модули JavaScript, также известный как сборщик модулей .
При большом количестве файлов он генерирует один файл (или несколько файлов), запускающий ваше приложение.
Может выполнять множество операций:
- поможет вам объединить ваши ресурсы.
- следит за изменениями и повторно запускает задачи.
- может выполнять транспиляцию Babel в ES5, что позволяет использовать новейшие функции JavaScript, не беспокоясь о поддержке браузером.
- может преобразовывать CoffeeScript в JavaScript
- может преобразовывать встроенные изображения в URI данных.
- позволяет использовать require () для файлов CSS.
- может запускать веб-сервер разработки.
- может справиться с заменой горячего модуля.
- может разбить выходные файлы на несколько файлов, чтобы избежать загрузки огромного js-файла при первом обращении к странице.
- может сотрясать дерево.
Webpack не ограничивается использованием во внешнем интерфейсе, он также полезен в серверной части Node.js.
Предшественники webpack и все еще широко используемые инструменты:
Есть много общего в том, что они и Webpack могут делать, но главное отличие состоит в том, что они известны как task runners , тогда как webpack родился как сборщик модулей.
Это более специализированный инструмент: вы указываете точку входа в свое приложение (это может быть даже HTML-файл с тегами скрипта), а webpack анализирует файлы и объединяет все, что вам нужно для запуска приложения, в одном выходном файле JavaScript (или в больше файлов, если вы используете разделение кода).
Установка webpack
Webpack можно установить глобально или локально для каждого проекта.
Глобальная установка
Вот как установить его глобально с помощью Yarn:
пряжа global add webpack webpack-cli
с npm:
нпм я -g webpack webpack-cli
, как только это будет сделано, вы сможете запустить
Локальная установка
Webpack также можно установить локально.Это рекомендуемая установка, поскольку веб-пакет можно обновлять для каждого проекта, и у вас меньше сопротивления использованию новейших функций только для небольшого проекта, а не обновлению всех проектов, которые у вас есть, которые используют веб-пакет.
с пряжей:
пряжа добавить webpack webpack-cli -D
с npm:
npm i webpack webpack-cli --save-dev
Как только это будет сделано, добавьте это в свой package.json
файл:
{
//...
"scripts": {
"build": "webpack"
}
}
, как только это будет сделано, вы можете запустить webpack, набрав
в корне проекта.
Конфигурация Webpack
По умолчанию веб-пакет (начиная с версии 4) не требует никакой конфигурации, если вы соблюдаете эти соглашения:
- точка входа вашего приложения —
./src/index.js
- вывод помещается в
./dist/main.js
. - Webpack работает в производственном режиме
Конечно, вы можете настроить каждую мелочь веб-пакета, когда вам нужно.Конфигурация webpack хранится в файле webpack.config.js
, в корневой папке проекта.
Точка входа
По умолчанию точка входа — ./src/index.js
В этом простом примере в качестве отправной точки используется файл ./index.js
:
module.exports = {
/*...*/
запись: './index.js'
/*...*/
}
Выход
По умолчанию вывод создается в ./dist/main.js
. Этот пример помещает выходной пакет в приложение .js
:
module.exports = {
/*...*/
выход: {
путь: path.resolve (__ dirname, 'dist'),
имя файла: 'app.js'
}
/*...*/
}
Погрузчики
Использование webpack позволяет вам использовать import
или require
statement in your JavaScript code to not only include other JavaScript, but any kind of file, for example CSS.
Webpack стремится обрабатывать все наши зависимости, а не только JavaScript, и загрузчики — один из способов сделать это.
Например, в вашем коде вы можете использовать:
с использованием этой конфигурации загрузчика:
module.exports = {
/*...*/
модуль: {
правила: [
{тест: /\.css$/, используйте: 'css-loader'},
]
}
/*...*/
}
Регулярное выражение предназначено для любого файла CSS.
Погрузчик может иметь опции:
module.exports = {
/*...*/
модуль: {
правила: [
{
тест: /\.css$/,
использование: [
{
загрузчик: 'css-loader',
параметры: {
модули: true
}
}
]
}
]
}
/ *... * /
}
Для каждого правила может потребоваться несколько загрузчиков:
module.exports = {
/*...*/
модуль: {
правила: [
{
тест: /\.css$/,
использование:
[
'стиль-загрузчик',
'css-loader',
]
}
]
}
/*...*/
}
В этом примере css-loader
интерпретирует import 'style.css' директиву
в CSS. style-loader
затем отвечает за внедрение этого CSS в DOM, используя тег