Сохраняйте контроль: руководство по Webpack и React, Pt. 1
Опубликовано: 2022-03-11При запуске нового проекта React у вас есть множество шаблонов на выбор: Create React App, react-boilerplate
и React Starter Kit, и это лишь некоторые из них.
Эти шаблоны, принятые тысячами разработчиков, способны поддерживать разработку приложений в очень больших масштабах. Но они оставляют опыт разработчика и выходные данные пакета обремененными различными значениями по умолчанию, что может быть не идеальным.
Если вы хотите сохранить большую степень контроля над процессом сборки, вы можете инвестировать в пользовательскую конфигурацию Webpack. Как вы узнаете из этого руководства по Webpack, эта задача не очень сложна, и знания могут быть даже полезны при устранении неполадок в конфигурациях других людей.
Веб-пакет: начало работы
То, как мы сегодня пишем JavaScript, отличается от кода, который может выполнять браузер. Мы часто полагаемся на другие типы ресурсов, транспилированные языки и экспериментальные функции, которые еще не поддерживаются в современных браузерах. Webpack — это сборщик модулей для JavaScript, который может восполнить этот пробел и создать код, совместимый с разными браузерами, без каких-либо затрат, когда речь идет об опыте разработчиков.
Прежде чем мы начнем, вы должны иметь в виду, что весь код, представленный в этом руководстве по Webpack, также доступен в виде полного примера файла конфигурации Webpack/React на GitHub. Пожалуйста, не стесняйтесь ссылаться на нее и возвращаться к этой статье, если у вас есть какие-либо вопросы.
Базовая конфигурация
Начиная с Legato (версия 4), для запуска Webpack не требуется никакой настройки. Выбор режима сборки применит набор значений по умолчанию, более подходящих для целевой среды. В духе этой статьи мы собираемся отбросить эти настройки по умолчанию и самостоятельно реализовать разумную конфигурацию для каждой целевой среды.
Во-первых, нам нужно установить webpack
и webpack-cli
:
npm install -D webpack webpack-cli
Затем нам нужно заполнить webpack.config.js
конфигурацией со следующими параметрами:
-
devtool
: включает генерацию исходной карты в режиме разработки. -
entry
: основной файл нашего приложения React. -
output.path
: корневой каталог для хранения выходных файлов. -
output.filename
: Шаблон имени файла, используемый для сгенерированных файлов. -
output.publicPath
: путь к корневому каталогу, в котором файлы будут развернуты на веб-сервере.
const path = require("path"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; const isDevelopment = !isProduction; return { devtool: isDevelopment && "cheap-module-source-map", entry: "./src/index.js", output: { path: path.resolve(__dirname, "dist"), filename: "assets/js/[name].[contenthash:8].js", publicPath: "/" } }; };
Приведенная выше конфигурация отлично работает для простых файлов JavaScript. Но при использовании Webpack и React нам нужно будет выполнить дополнительные преобразования перед отправкой кода нашим пользователям. В следующем разделе мы будем использовать Babel, чтобы изменить способ, которым Webpack загружает файлы JavaScript.
JS-загрузчик
Babel — это компилятор JavaScript с множеством плагинов для преобразования кода. В этом разделе мы введем его в качестве загрузчика в нашу конфигурацию Webpack и настроим для преобразования современного кода JavaScript в такой, который понятен обычным браузерам.
Во-первых, нам нужно установить babel-loader
и @babel/core
:
npm install -D @babel/core babel-loader
Затем мы добавим раздел module
в наш конфиг Webpack, babel-loader
ответственным за загрузку файлов JavaScript:
@@ -11,6 +11,25 @@ module.exports = function(_env, argv) { path: path.resolve(__dirname, "dist"), filename: "assets/js/[name].[contenthash:8].js", publicPath: "/" + }, + module: { + rules: [ + { + test: /\.jsx?$/, + exclude: /node_modules/, + use: { + loader: "babel-loader", + options: { + cacheDirectory: true, + cacheCompression: false, + envName: isProduction ? "production" : "development" + } + } + } + ] + }, + resolve: { + extensions: [".js", ".jsx"] } }; };
Мы собираемся настроить Babel, используя отдельный файл конфигурации, babel.config.js
. Он будет использовать следующие функции:
-
@babel/preset-env
: преобразует современные функции JavaScript в код, совместимый с предыдущими версиями. -
@babel/preset-react
: преобразует синтаксис JSX в обычные вызовы функций JavaScript. -
@babel/plugin-transform-runtime
: уменьшает дублирование кода за счет извлечения помощников Babel в общие модули. -
@babel/plugin-syntax-dynamic-import
: включает синтаксис динамическогоimport()
в браузерах, в которых отсутствует встроенная поддержкаPromise
. -
@babel/plugin-proposal-class-properties
: включает поддержку предложения синтаксиса поля общедоступного экземпляра для написания компонентов React на основе классов.
Мы также включим несколько оптимизаций производства для React:
-
babel-plugin-transform-react-remove-prop-types
удаляет ненужные типы prop-types из производственного кода. -
@babel/plugin-transform-react-inline-elements
оцениваетReact.createElement
во время компиляции и встраивает результат. -
@babel/plugin-transform-react-constant-elements
извлекает статические элементы React как константы.
Команда ниже установит все необходимые зависимости:
npm install -D @babel/preset-env @babel/preset-react @babel/runtime @babel/plugin-transform-runtime @babel/plugin-syntax-dynamic-import @babel/plugin-proposal-class-properties babel-plugin-transform-react-remove-prop-types @babel/plugin-transform-react-inline-elements @babel/plugin-transform-react-constant-elements
Затем мы babel.config.js
следующими настройками:
module.exports = { presets: [ [ "@babel/preset-env", { modules: false } ], "@babel/preset-react" ], plugins: [ "@babel/plugin-transform-runtime", "@babel/plugin-syntax-dynamic-import", "@babel/plugin-proposal-class-properties" ], env: { production: { only: ["src"], plugins: [ [ "transform-react-remove-prop-types", { removeImport: true } ], "@babel/plugin-transform-react-inline-elements", "@babel/plugin-transform-react-constant-elements" ] } } };
Эта конфигурация позволяет нам писать современный JavaScript таким образом, чтобы он был совместим со всеми соответствующими браузерами. Существуют и другие типы ресурсов, которые могут нам понадобиться в приложении React, и мы рассмотрим их в следующих разделах.
Загрузчик CSS
Когда дело доходит до стилизации приложений React, нам как минимум нужно иметь возможность включать простые файлы CSS. Мы собираемся сделать это в Webpack, используя следующие загрузчики:
-
css-loader
: анализирует файлы CSS, разрешая внешние ресурсы, такие как изображения, шрифты и импорт дополнительных стилей. -
style-loader
: во время разработки вставляет загруженные стили в документ во время выполнения. -
mini-css-extract-plugin
: извлекает загруженные стили в отдельные файлы для производственного использования, чтобы воспользоваться кэшированием браузера.
Давайте установим вышеуказанные загрузчики CSS:
npm install -D css-loader style-loader mini-css-extract-plugin
Затем мы добавим новое правило в раздел module.rules
нашей конфигурации Webpack:
@@ -1,4 +1,5 @@ const path = require("path"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -25,6 +26,13 @@ module.exports = function(_env, argv) { envName: isProduction ? "production" : "development" } } + }, + { + test: /\.css$/, + use: [ + isProduction ? MiniCssExtractPlugin.loader : "style-loader", + "css-loader" + ] } ] },
Мы также добавим MiniCssExtractPlugin
в раздел plugins
, который мы включим только в рабочем режиме:
@@ -38,6 +38,13 @@ module.exports = function(_env, argv) { }, resolve: { extensions: [".js", ".jsx"] - } + }, + plugins: [ + isProduction && + new MiniCssExtractPlugin({ + filename: "assets/css/[name].[contenthash:8].css", + chunkFilename: "assets/css/[name].[contenthash:8].chunk.css" + }) + ].filter(Boolean) }; };
Эта конфигурация работает для простых файлов CSS и может быть расширена для работы с различными процессорами CSS, такими как Sass и PostCSS, которые мы обсудим в следующей статье.
Загрузчик изображений
Webpack также можно использовать для загрузки статических ресурсов, таких как изображения, видео и другие двоичные файлы. Наиболее общий способ обработки таких типов файлов — использование file-loader
или url-loader
, который предоставит своим потребителям URL-ссылку на необходимые ресурсы.
В этом разделе мы добавим url-loader
для обработки распространенных форматов изображений. Что отличает url-loader
от file-loader
, так это то, что если размер исходного файла меньше заданного порога, он встроит весь файл в URL как содержимое в кодировке base64, тем самым устраняя необходимость в дополнительном запросе.
Сначала мы устанавливаем url-loader
:
npm install -D url-loader
Затем мы добавляем новое правило в раздел module.rules
нашей конфигурации Webpack:
@@ -34,6 +34,16 @@ module.exports = function(_env, argv) { isProduction ? MiniCssExtractPlugin.loader : "style-loader", "css-loader" ] + }, + { + test: /\.(png|jpg|gif)$/i, + use: { + loader: "url-loader", + options: { + limit: 8192, + name: "static/media/[name].[hash:8].[ext]" + } + } } ] },
SVG
Для изображений SVG мы будем использовать загрузчик @svgr/webpack
, который преобразует импортированные файлы в компоненты React.
@svgr/webpack
:
npm install -D @svgr/webpack
Затем мы добавляем новое правило в раздел module.rules
нашей конфигурации Webpack:
@@ -44,6 +44,10 @@ module.exports = function(_env, argv) { name: "static/media/[name].[hash:8].[ext]" } } + }, + { + test: /\.svg$/, + use: ["@svgr/webpack"] } ] },
Изображения SVG в качестве компонентов React могут быть удобными, а @svgr/webpack
выполняет оптимизацию с использованием SVGO.

Примечание. Для некоторых анимаций или даже эффектов при наведении курсора вам может потребоваться манипулировать SVG с помощью JavaScript. К счастью, @svgr/webpack
по умолчанию встраивает содержимое SVG в пакет JavaScript, что позволяет обойти необходимые для этого ограничения безопасности.
Файл-загрузчик
Когда нам нужно сослаться на любые другие типы файлов, общий file-loader
выполнит эту работу. Он работает аналогично url-loader
, предоставляя URL-адрес актива коду, который его требует, но не пытается его оптимизировать.
Как всегда, сначала мы устанавливаем модуль Node.js. В этом случае file-loader
:
npm install -D file-loader
Затем мы добавляем новое правило в раздел module.rules
нашей конфигурации Webpack. Например:
@@ -48,6 +48,13 @@ module.exports = function(_env, argv) { { test: /\.svg$/, use: ["@svgr/webpack"] + }, + { + test: /\.(eot|otf|ttf|woff|woff2)$/, + loader: require.resolve("file-loader"), + options: { + name: "static/media/[name].[hash:8].[ext]" + } } ] },
Здесь мы добавили file-loader
для загрузки шрифтов, на которые вы можете ссылаться из своих файлов CSS. Вы можете расширить этот пример, чтобы загрузить любые другие типы файлов, которые вам нужны.
Плагин среды
Мы можем использовать DefinePlugin()
Webpack, чтобы предоставить переменные среды из среды сборки в наш код приложения. Например:
@@ -1,5 +1,6 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -65,7 +66,12 @@ module.exports = function(_env, argv) { new MiniCssExtractPlugin({ filename: "assets/css/[name].[contenthash:8].css", chunkFilename: "assets/css/[name].[contenthash:8].chunk.css" - }) + }), + new webpack.DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify( + isProduction ? "production" : "development" + ) + }) ].filter(Boolean) }; };
Здесь мы заменили process.env.NODE_ENV
строкой, представляющей режим сборки: "development"
или "production"
.
HTML-плагин
В отсутствие файла index.html
наш пакет JavaScript бесполезен, просто лежит там, и никто не может его найти. В этом разделе мы представим html-webpack-plugin
для создания HTML-файла для нас.
html-webpack-plugin
:
npm install -D html-webpack-plugin
Затем мы добавляем html-webpack-plugin
в раздел plugins
нашей конфигурации Webpack:
@@ -1,6 +1,7 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const webpack = require("webpack"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -71,6 +72,10 @@ module.exports = function(_env, argv) { "process.env.NODE_ENV": JSON.stringify( isProduction ? "production" : "development" ) + }), + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, "public/index.html"), + inject: true }) ].filter(Boolean) };
Сгенерированный файл public/index.html
загрузит наш пакет и загрузит наше приложение.
Оптимизация
Есть несколько методов оптимизации, которые мы можем использовать в процессе сборки. Мы начнем с минимизации кода, процесса, с помощью которого мы можем уменьшить размер нашего пакета без каких-либо затрат с точки зрения функциональности. Мы будем использовать два плагина для минимизации нашего кода: terser-webpack-plugin
для кода JavaScript и optimize-css-assets-webpack-plugin
для CSS.
Давайте установим их:
npm install -D terser-webpack-plugin optimize-css-assets-webpack-plugin
Затем добавим в наш конфиг раздел optimization
:
@@ -2,6 +2,8 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); +const TerserWebpackPlugin = require("terser-webpack-plugin"); +const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -75,6 +77,27 @@ module.exports = function(_env, argv) { isProduction ? "production" : "development" ) }) - ].filter(Boolean) + ].filter(Boolean), + optimization: { + minimize: isProduction, + minimizer: [ + new TerserWebpackPlugin({ + terserOptions: { + compress: { + comparisons: false + }, + mangle: { + safari10: true + }, + output: { + comments: false, + ascii_only: true + }, + warnings: false + } + }), + new OptimizeCssAssetsPlugin() + ] + } }; };
Приведенные выше настройки обеспечат совместимость кода со всеми современными браузерами.
Разделение кода
Разделение кода — это еще один метод, который мы можем использовать для повышения производительности нашего приложения. Разделение кода может относиться к двум различным подходам:
- Используя динамический оператор
import()
, мы можем извлекать части приложения, которые составляют значительную часть размера нашего пакета, и загружать их по требованию. - Мы можем извлекать код, который меняется реже, чтобы использовать кэширование браузера и повысить производительность для повторных посетителей.
Мы заполним раздел optimization.splitChunks
нашей конфигурации Webpack настройками для извлечения сторонних зависимостей и общих фрагментов в отдельные файлы:
@@ -99,7 +99,29 @@ module.exports = function(_env, argv) { sourceMap: true }), new OptimizeCssAssetsPlugin() - ] + ], + splitChunks: { + chunks: "all", + minSize: 0, + maxInitialRequests: 20, + maxAsyncRequests: 20, + cacheGroups: { + vendors: { + test: /[\\/]node_modules[\\/]/, + name(module, chunks, cacheGroupKey) { + const packageName = module.context.match( + /[\\/]node_modules[\\/](.*?)([\\/]|$)/ + )[1]; + return `${cacheGroupKey}.${packageName.replace("@", "")}`; + } + }, + common: { + minChunks: 2, + priority: -10 + } + } + }, + runtimeChunk: "single" } }; };
Давайте подробнее рассмотрим варианты, которые мы использовали здесь:
-
chunks: "all"
: по умолчанию обычное извлечение чанков влияет только на модули, загруженные с помощью динамическогоimport()
. Этот параметр также включает оптимизацию загрузки точки входа. -
minSize: 0
: по умолчанию для извлечения доступны только фрагменты размером выше определенного порога. Этот параметр включает оптимизацию для всего распространенного кода независимо от его размера. -
maxInitialRequests: 20
иmaxAsyncChunks: 20
: эти параметры увеличивают максимальное количество исходных файлов, которые могут быть загружены параллельно для импорта точки входа и импорта точки разделения, соответственно.
Дополнительно указываем следующую конфигурацию cacheGroups
:
-
vendors
: настраивает извлечение для сторонних модулей.-
test: /[\\/]node_modules[\\/]/
: шаблон имени файла для сопоставления сторонних зависимостей. -
name(module, chunks, cacheGroupKey)
: группирует отдельные фрагменты из одного модуля вместе, давая им общее имя.
-
-
common
: настраивает извлечение общих фрагментов из кода приложения.-
minChunks: 2
: чанк будет считаться общим, если на него ссылаются как минимум из двух модулей. -
priority: -10
: Назначает отрицательный приоритетcommon
группе кеша, чтобы куски для группы кешаvendors
рассматривались в первую очередь.
-
Мы также извлекаем код среды выполнения Webpack в один блок, который может использоваться несколькими точками входа, указав runtimeChunk: "single"
.
Сервер разработки
До сих пор мы сосредоточились на создании и оптимизации производственной сборки нашего приложения, но у Webpack также есть собственный веб-сервер с перезагрузкой в реальном времени и отчетами об ошибках, которые помогут нам в процессе разработки. Он называется webpack-dev-server
, и нам нужно установить его отдельно:
npm install -D webpack-dev-server
В этом фрагменте мы вводим раздел devServer
в нашу конфигурацию Webpack:
@@ -120,6 +120,12 @@ module.exports = function(_env, argv) { } }, runtimeChunk: "single" + }, + devServer: { + compress: true, + historyApiFallback: true, + open: true, + overlay: true } }; };
Здесь мы использовали следующие варианты:
-
compress: true
: включает сжатие ресурсов для более быстрой перезагрузки. -
historyApiFallback: true
: включает откат кindex.html
для маршрутизации на основе истории. -
open: true
: открывает браузер после запуска сервера разработки. -
overlay: true
: отображает ошибки Webpack в окне браузера.
Вам также может потребоваться настроить параметры прокси-сервера для пересылки запросов API на внутренний сервер.
Webpack и React: оптимизированы для производительности и готовы!
Мы только что узнали, как загружать различные типы ресурсов с помощью Webpack, как использовать Webpack в среде разработки, а также несколько методов оптимизации производственной сборки. Если вам нужно, вы всегда можете просмотреть полный файл конфигурации, чтобы найти вдохновение для собственной настройки React/Webpack. Оттачивание таких навыков является стандартной платой за проезд для всех, кто предлагает услуги по разработке React.
В следующей части этой серии мы расширим эту конфигурацию инструкциями для более конкретных вариантов использования, включая использование TypeScript, препроцессоры CSS и расширенные методы оптимизации, включающие рендеринг на стороне сервера и ServiceWorkers. Оставайтесь с нами, чтобы узнать все, что вам нужно знать о Webpack, чтобы запустить свое приложение React в производство.