Webpack 3
Обновленная версия статьи (под Webpack 3):
Установка webpack 2 как devDependencies:
npm i -D webpack
Узнать ВСЕ версии webpack:
npm view webpack versions
С последней:
npm view webpack versions --json
Установить конкретную версию:
npm i -D webpack@2.2.0
Создаем bundle через консоль:
webpack ./src/app.js ./dist/app.bundle.js -p --watch
Что (./src/app.js
) пропускаем через webpack; куда (./dist/app.bundle.js
).
Используйте флаг -p
для production, чтобы минифицировать код.
Используйте флаг --watch
, чтобы отслеживать изменения в файлах.
HTML Webpack Plugin
html-webpack-plugin
динамически создает html-файл, плюс вставляет в него сгенерированный bundle.js.
Установка:
npm i html-webpack-plugin --save-dev
Первоисточник: HTML Webpack Plugin
Чтобы кастомизировать шаблон: Writing Your Own Templates
Пример использования с кастомизацией шаблона:
const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/app.js',
output: {
path: path.resolve(__dirname,"./dist"), // папка, в том числе, для HtmlWebpackPlugin
filename: 'app.bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
title: 'Project demo', // кастомизируем шаблон
// minify: { collapseWhitespace: true },
template: 'src/index.html' // каркас для выходного 'шаблона'
})
]
}
Loader
Loader - это функции принимающие в качестве параметра путь (например, к img)
и вставляют результат в финальный bundle. Например, если нам нужно вставить css мы используем css-loader
и т.д. Как уже упоминалось выше: в то время как loader оперируют с преобразованиями на отдельных файлах,
плагины оперируют с большими кусками кода.
CSS, style, SASS:
css-loader
npm i css-loader --save-dev
module: {
rules: [{
test: /\.css$/,
use: 'css-loader'
}
]
},
css-loader
включает css-код в выходной bundle.js. То есть без style-loader
ничего не произойдет (стили не будут применены):
css-loader
просто вставит ваш css код в js-файл.
style-loader
style-loader - добавляет CSS в DOM, посредством
инъекции тега style
.
npm install style-loader --save-dev
module: {
rules: [{
test: /\.css$/,
loaders: 'style-loader!css-loader' // OR: use: ['style-loader', 'css-loader']
}
]
},
sass-loader
sass-loader
module: {
rules: [{
test: /\.scss$/,
//loaders: 'style-loader!css-loader' // OR: use: ['style-loader', 'css-loader']
use: ['style-loader', 'css-loader', 'sass-loader']
}
]
},
Создаем отдельный css-файл (extract-text-webpack-plugin)
Для этого нам понадобится extract-text-webpack-plugin , который извлекает текст из bundle, или bundles, в отдельный файл.
# for webpack 3
npm install --save-dev extract-text-webpack-plugin
# for webpack 2
npm install --save-dev extract-text-webpack-plugin@2.1.2
# for webpack 1
npm install --save-dev extract-text-webpack-plugin@1.0.1
const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: './src/app.js',
output: {
// папка, в том числе, для HtmlWebpackPlugin
path: path.resolve(__dirname,"./dist"),
filename: 'app.bundle.js'
},
module: {
rules: [
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
//resolve-url-loader may be chained before sass-loader if necessary
use: ['css-loader', 'sass-loader'],
// генерируем отдельный файл:
publicPath: '/dist'
})
}
]
},
plugins: [
new HtmlWebpackPlugin({
// кастомизируем шаблон
title: 'Project demo',
// minify: { collapseWhitespace: true },
// каркас для выходного 'шаблона'
template: 'src/index.html'
}),
new ExtractTextPlugin('style.css')
]
}
Webpack Dev Server
webpack-dev-server
npm i webpack-dev-server -D
//Далее это (в package.json):
"scripts": {
"dev": "webpack -d --watch"
},
// заменим на:
"scripts": {
"dev": "webpack-dev-server"
},
// отличия см. ниже
Webpack Dev Server: больше опций
Документация к настройкам Webpack Dev Server: Webpack Dev Server
Чем отличпется команда "dev": "webpack-dev-server"
от "dev": "webpack -d"
:
первая команда работает с файлами из
памяти, второя читает непосредственно из папки (например, у нас это папка dist
).
Кастомизируем Webpack Dev Server
в файл webpack.config.js
:
module.exports = {
entry: './src/app.js',
output: {
//...
},
module: {
//...
},
devServer: {
contentBase: path.join(__dirname, "dist"),
compress: true,
port: 9001,
stats: 'errors-only',
open: true
},
plugins: [
//...
]
}
Как установить react и babel
Устанавливаем Babel:
npm i -D babel babel-preset-react babel-preset-es2015
Далее в вашем проекте создайте файл .babelrc
. Файл .babelrc используем для конфигурации babel.
Содержимое .babelrc
:
{
"presets": [
"es2015",
"react"
]
}
Для webpack необходимо настроить соответствующий loader
(setup):
npm install --save-dev babel-loader babel-core
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
}
Устанавливаем react
(Installing React):
npm i -D react react-dom
Далее вы можете для примера создать небольшой пример на React Hello World with ES6 and JSX,
и не забудьте расположить где-нибуть в html файле div
элемент с id
равным root
.
Несколько шаблонов в webpack и RimRaf
Допустим при каждой сборке нам потребовалось очищать папку dist
. Для этого вопользуемся
rimraf
npm install rimraf --save-dev
Для этого в package.json добавим:
"scripts": {
"dev": "webpack-dev-server",
"prod": "npm run clean && webpack -p",
"clean": "rimraf ./dist/*"
},
Зададим несколько шаблонов:
//...
plugins: [
new HtmlWebpackPlugin({
// кастомизируем шаблон
title: 'Project demo',
// minify:
// { collapseWhitespace: true },
// каркас для выходного 'шаблона'
template: 'src/index.html',
// меняем output (см. выше) на свой, то есть определяем место, куда будем генерировать шаблон
// filename: './../index.html'
hash: true
}),
new HtmlWebpackPlugin({
title: 'Contact page',
template: 'src/contact.html',
// автоматически сохранится в папку dist (так как она указана в output, см. выше)
filename: 'contact.html',
hash: true
}),
//...
Но отсюда возникает закономерный вопрос: как указать разные bundle
(js) для разных страниц?
Если вы хотите несколько выходящих (output
) bundles, вам потребуется определить несколько
entry
+ не забудьте поменять в otput
значение у filename
на [name].bundle.js
:
//...
entry: {
app: './src/app.js',
contact: './src/contact.js'
},
output: {
// папка, в том числе, для HtmlWebpackPlugin
path: path.resolve(__dirname,"./dist"),
filename: '[name].bundle.js'
},
//...
Далее, чтобы исключить лишние chunks ('куски') js кода, нужно воспользовать опциями excludeChunks
(исключаем) или chunks
:
//...
plugins: [
new HtmlWebpackPlugin({
// кастомизируем шаблон
title: 'Project demo',
// minify:
// { collapseWhitespace: true },
// каркас для выходного 'шаблона'
template: 'src/index.html',
// меняем output (см. выше) на свой, то есть определяем место, куда будем генерировать шаблон
// filename: './../index.html'
hash: true,
excludeChunks: ['contact']
}),
new HtmlWebpackPlugin({
title: 'Contact page',
template: 'src/contact.html',
// автоматически сохранится в папку dist (так как она указана в output, см. выше)
filename: 'contact.html',
hash: true,
chunks: ['contact']
}),
//...
Также на stack: создаем конфиг для множества entry points / templates: Webpack конфиг для множества entry points / templates
Как использовать pug (jade) шаблоны в Webpack
Подробнее почитать о pug: pug
Также для 3 Webpack нам потребуется установить HTML Loader
npm i -D html-loader
npm i -D pug pug-html-loader
module: {
rules: [
// pug
{
test: /\.pug$/,
use: ["html-loader", "pug-html-loader"]
}
//...
plugins: [
new HtmlWebpackPlugin({
title: 'Project demo',
template: 'src/index.pug'
}),
Hot Module Replacement - CSS
При помощи Hot Module Replacement можно обновлять CSS без перезагрузки страницы.
Настройки приведены здесь: webpack.config.js
// подключим webpack
var webpack = require('webpack');
module: {
rules: [
{
test: /\.scss$/,
// для 'Hot Module Replacement' закомментируем:
/*
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
//resolve-url-loader may be chained before sass-loader if necessary
use: ['css-loader', 'sass-loader'],
// генерируем отдельный файл:
publicPath: '/dist'
})
*/
// для Hot Module Replacement:
use: ['style-loader', 'css-loader', 'sass-loader']
},
//...
plugins: [
// Hot Module Replacement:
new ExtractTextPlugin({
filename: 'style.css',
// для Hot Module Replacement отключим плагин ExtractTextPlugin
disable: true,
allChunks: true
}),
// Hot Module Replacement:
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin()
//...
Обратите внимание: в консоли при каждом изменении в scss файле будет отображаться: [HMR] App is up to date
.
Production vs Development Environment
Обратите внимание, что в предыдущем разделе мы отключили ExtractTextPlugin
для HMR, но для Production он понадобится.
Для этого в package.json добавим для prod
NODE_ENV=production
:
"scripts": {
"dev": "webpack-dev-server",
"prod": "set NODE_ENV=production&&npm; run clean && webpack -p",
"clean": "rimraf ./dist/*"
},
// webpack.config.js
//...
var isProd = process.env.NODE_ENV === 'production';
var cssDev = ['style-loader', 'css-loader', 'sass-loader'];
var cssProd = ExtractTextPlugin.extract({
fallback: 'style-loader',
//resolve-url-loader may be chained before sass-loader if necessary
use: ['css-loader', 'sass-loader'],
// генерируем отдельный файл:
publicPath: '/dist'
});
var cssConfig = isProd ? cssProd : cssDev;
//...
module: {
rules: [
{
test: /\.scss$/,
use: cssConfig
},
//...
new ExtractTextPlugin({
filename: 'style.css',
// в зависимости от режима (dev/prod)
disable: !isProd,
allChunks: true
}),
Как загружать изображения с webpack 2
file-loader
Для этого нам понадобятся два loaders:
image-webpack-loader (оптимизация) и
file-loader - сканирует все файлы и пытается загрузить их в папку dist
(при prod, или туда, куда вы укажите). file-loader
понадобится для того, чтобы webpack мог
работать с картинками как с модулями, то есть с помощью file-loader
мы сможем загружать картинки в javascript и работать с ними).
npm install --save-dev file-loader
// webpack.config.js
module: {
rules: [
//...
{
test: /\.(gif|png|jpe?g|svg)$/i,
// ?name=[path][name].[ext] - сохранили оригинальное имя и путь в папке dist: dist\src\images\pugjs.png
//use: ["file-loader?name=[path][name].[ext]"]
// ?name=[name].[ext]&outputPath;=images/ - оригинальное имя сохранит в папке images: dist\images\pugjs.png
use: ["file-loader?name=[name].[ext]&outputPath;=images/"]
}
/* app.scss */
body {
color: red;
background: whitesmoke url('./images/pugjs.png') center no-repeat;
}
<!-- contact.html -->
<!-- не сработает -->
<img src="./images/pugjs.png" alt="" />
<!-- сработает -->
<img src=<%= require("./images/pugjs.png") %> />
Внутри html, а также css, можно также указать publicPath
для картинок.
module: {
rules: [
use: [
"file-loader?name=[name].[ext]&outputPath;=images/&publicPath;=images/",
"image-webpack-loader" // напомню, лоадеры работают с конца
]
}
При этом на выходе мы получим:
<!-- contact.html -->
<img src=images/images/pugjs.png />
/* style.css*/
body{color:red;background:#f5f5f5 url(images/images/pugjs.png) 50% no-repeat}
Как итог: outputPath
- это место, куда будут копироваться картинке при сборке;
publicPath
- это путь в исходниках, заданный принудительно.
image-webpack-loader
Чтобы отптимизировать изображения прежде чем залить их в папку dist
опцианально можно
использовать image-webpack-loader
(напомню, лоадеры работают с конца в массиве).
npm install image-webpack-loader --save-dev
Bootstrap и webpack 2
Мы будем использовать bootstrap-loader
npm install bootstrap-loader -D
При установке возникнут предупреждения с целью установить:
npm i -D resolve-url-loader url-loader
Далее вы можете выбрать, какую версию bootstrap вам нужно поставить: installation
Установим, например, 3-й bootstrap:
npm install --save-dev bootstrap-sass
Чтобы настроить bootstrap в корне проекта создайте файл .bootstraprc
и скопируйте в него данный конфиг - именно в этом файле определяется какие скрипты/стили компонентов следует подключать в проект, а какие нет.
Далее создадим файл webpack.bootstrap.config.js
(также в корне проекта), в котором проверяется режим работы на основе
process.env.BOOTSTRAPRC_LOCATION
, и скопируем в файл webpack.bootstrap.config.js
данный контент.
Подключаем шрифты
Также потребуется подключить loader для шрифтов, которые использует bootstrap: Icon fonts
module: {
loaders: [
{ test: /\.(woff2?|svg)$/, loader: 'url-loader?limit=10000' },
{ test: /\.(ttf|eot)$/, loader: 'file-loader' },
],
},
Добавим папку font для шрифтов:
{ test: /\.(woff2?|svg)$/, loader: 'url-loader?limit=10000$name=fonts/[name].[ext]' },
{ test: /\.(ttf|eot)$/, loader: 'file-loader?name=fonts/[name].[ext]' },
Подключаем jQuery
Подключим jQuery
npm i -D imports-loader jquery
module: {
loaders: [
// Bootstrap 3
{ test:/bootstrap-sass[\/\\]assets[\/\\]javascripts[\/\\]/, loader: 'imports-loader?jQuery=jquery' },
],
},
Переопределяем переменный bootstrap
В файле .bootstraprc
можно раскомментировать (и задать свой путь) для
bootstrapCustomizations: ./src/bootstrap/customizations.scss
, где мы сможем переопределить любое значение достпупное
в файле _variables.scss
.
/* ./src/bootstrap/customizations.scss */
$brand-success: green !default;
/* app.scss */
@import "./bootstrap/customizations.scss";
body {
h1 {
color: $brand-success;
}
}
ProvidePlugin
ProvidePlugin - автоматически загружает модули,
вместо того чтобы import
или require
их повсюду.
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
Миграция (разница между версиями)
p.s. Изменений между 2-й и 3-й версией совсем немного, так что вы без труда сможете перейти с одной на другую.
Для того чтобы определить, как изменилось поведение того или иного loader/plugin и других частей webpack при переходе с одной версии на другую, можно воспользоваться руководством: migrating
Исходники
package.json:
{
"name": "webpack-101",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack-dev-server",
"prod": "set NODE_ENV=production&&npm; run clean && webpack -p",
"clean": "rimraf ./dist/*"
},
"author": "",
"license": "ISC",
"devDependencies": {
"babel": "^6.23.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"bootstrap-loader": "^2.2.0",
"bootstrap-sass": "^3.3.7",
"css-loader": "^0.28.7",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.5",
"html-loader": "^0.5.1",
"html-webpack-plugin": "^2.30.1",
"image-webpack-loader": "^3.4.2",
"imports-loader": "^0.7.1",
"jquery": "^3.2.1",
"node-sass": "^4.6.1",
"pug": "^2.0.0-rc.4",
"pug-html-loader": "^1.1.5",
"react": "^16.1.1",
"react-dom": "^16.1.1",
"resolve-url-loader": "^2.2.0",
"rimraf": "^2.6.2",
"sass-loader": "^6.0.6",
"style-loader": "^0.19.0",
"url-loader": "^0.6.2",
"webpack": "^3.8.1",
"webpack-dev-server": "^2.9.4"
}
}
webpack.config.json:
const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
var webpack = require('webpack');
// for bootstrap
var bootstrapEntryPoints = require("./webpack.bootstrap.config");
var isProd = process.env.NODE_ENV === 'production';
var cssDev = ['style-loader', 'css-loader', 'sass-loader'];
var cssProd = ExtractTextPlugin.extract({
fallback: 'style-loader',
//resolve-url-loader may be chained before sass-loader if necessary
use: ['css-loader', 'sass-loader'],
// генерируем отдельный файл:
publicPath: '/dist'
});
var cssConfig = isProd ? cssProd : cssDev;
// for bootstrap
var bootstrapConfig = isProd ? bootstrapEntryPoints.prod : bootstrapEntryPoints.dev;
module.exports = {
entry: {
app: './src/app.js',
contact: './src/contact.js',
// for bootstrap
bootstrap: bootstrapConfig
},
output: {
// папка, в том числе, для HtmlWebpackPlugin
path: path.resolve(__dirname,"./dist"),
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.scss$/,
// для 'Hot Module Replacement' закомментируем
/*
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
//resolve-url-loader may be chained before sass-loader if necessary
use: ['css-loader', 'sass-loader'],
// генерируем отдельный файл:
publicPath: '/dist'
})
*/
// для Hot Module Replacement
// use: ['style-loader', 'css-loader', 'sass-loader']
// в зависимости от режима (dev/prod)
use: cssConfig
},
// настраиваем babel
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
// pug
{
test: /\.pug$/,
use: ["html-loader", "pug-html-loader"]
},
{
test: /\.(gif|png|jpe?g|svg)$/i,
// ?name=[path][name].[ext] - сохранили оригинальное имя и путь в папке dist: dist\src\images\pugjs.png
//use: ["file-loader?name=[path][name].[ext]"]
// ?name=[name].[ext]&outputPath;=images/ - оригинальное имя сохранит в папке images: dist\images\pugjs.png
use: [
//"file-loader?name=images/[name].[ext]",
"file-loader?name=[name].[ext]&outputPath;=images/", //&publicPath;=images/
"image-webpack-loader" // напомню, лоадеры работают с конца
]
},
// for bootstrap:
// https://github.com/shakacode/bootstrap-loader#icon-fonts
{ test: /\.(woff2?|svg)$/, loader: 'url-loader?limit=10000$name=fonts/[name].[ext]' },
{ test: /\.(ttf|eot)$/, loader: 'file-loader?name=fonts/[name].[ext]' },
{ test:/bootstrap-sass[\/\\]assets[\/\\]javascripts[\/\\]/, loader: 'imports-loader?jQuery=jquery' },
]
},
devServer: {
contentBase: path.join(__dirname, "dist"),
compress: true,
port: 9001,
stats: 'errors-only',
open: true,
// Hot Module Replacement:
hot: true
},
plugins: [
new HtmlWebpackPlugin({
// кастомизируем шаблон
title: 'Project demo',
// minify:
// { collapseWhitespace: true },
// каркас для выходного 'шаблона'
// template: 'src/index.html',
template: 'src/index.pug',
// меняем output (см. выше) на свой, то есть определяем место, куда будем генерировать шаблон
// filename: './../index.html'
hash: true,
excludeChunks: ['contact']
}),
new HtmlWebpackPlugin({
title: 'Contact page',
template: 'src/contact.html',
// автоматически сохранится в папку dist (так как она указана в output, см. выше)
filename: 'contact.html',
hash: true,
chunks: ['bootstrap', 'contact']
}),
//new ExtractTextPlugin('style.css'),
// Hot Module Replacement:
new ExtractTextPlugin({
filename: '/css/[name].css',
// для Hot Module Replacement отключим плагин ExtractTextPlugin
//disable: true,
// в зависимости от режима (dev/prod)
disable: !isProd,
allChunks: true
}),
// Hot Module Replacement:
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin()
]
}
Полезные ресурсы
- канал Ihatetomatoes: на youtube, исходники на на github
- канал Loftschool: на youtube, исходники на на github
Старая версия статьи: webpack 2
plugins
В то время как loader оперируют с преобразованиями на отдельных файлах, плагины (plugins) оперируют с большими кусками кода.
Например, commons-chunk-plugin
другой корневой плагин, который может быть использован, чтобы создать отдельный модуль с общим кодом из нескольких точек входа.
Генерируем страницу из pug-файла при помощи HtmlWebpackPlugin
:
// plugins
plugins: [ // кастомизирует процесс сборки webpack
new HtmlWebpackPlugin({
filename: 'index.html',
chunks: ['index'],
template: PATHS.source + '/pages/index/index.pug'
}),
new HtmlWebpackPlugin({
filename: 'blog.html',
chunks: ['blog'],
template: PATHS.source + '/pages/blog/blog.pug'
}),
],
loader
loader loaders говорят webpack , что нужно делать, когда он встречает imports
для разных типов файлов.
Вы можете объединить loader's вместе в серию трансформаций.
Хороший пример того как это работает - импортировать Sass из нашего JavaScript.
// loader
module: {
rules: [
{
test: /\.pug$/,
loader: 'pug-loader',
options: {
pretty: true
}
}
]
},
Обрабатываем Pug в скрипте - 'на лету' при помощи pug-loader
(pug-loader):
// ./test.pug
div=title
//blog/blog.js
import tmpl from './test.pug';
let el = document.querySelector('.test')
let data = {
title: 'хохохохоох'
};
el.innerHTML = tmpl(data);
webpack-dev-server:
//webpack-dev-server - стартуем:
"scripts": {
"start": "webpack-dev-server --env development",
},
webpack-dev-server редактируется посредством объеста devServer
, который является свойством module
(module.exports
)
Production & Development
Разделение конфига на development
и production
.
Разделяем задачи посредством параметра env
(--env development
, где development
это значение для env
):
"scripts": {
"start": "webpack-dev-server --env development",
"build": "webpack --env production",
},
// only development
const developmentConfig = {
devServer: {
// в консоли увидем только ошибки
stats: 'errors-only',
port: 9000
}
};
module.exports = function (env) {
// для production будет один объект, а для development другой
if (env === 'production') {
return common;
};
if (env === 'development') {
return Object.assign(
{},
common,
developmentConfig
);
};
};
Комментарии к статье
Классная статья. 1 вопрос только:
Как сделать так что бы file-loader брал пути картинок из .html файла...
<head>
<title>Document</title>
</head>
<body>
<img src="./images/blue.svg" alt="">
<img src="./images/test.jpg" alt="">
Обе картинки не перекидываюся в папку продакшена image, соответственно и не отображаются в собраном проекте.
картинки которые добавляю в CSS background: url("./images/red.svg") no-repeat;
нормально обрабатываются и выводятся.
</body>
Вообщем надо что бы картинки из обычного .html файла(не .pug и тд) тоже попадали в image при сборке
Статья классная только не работают ни html webpack plugin ни css-plugin
Спасибо, очень полезная статья. Столкнулся с проблемой pug+img никак не хочет подключать картинку по относительному пути.
Может подскажите как правильно всё это дело настроить? Прикрепил скрин: https://yadi.sk/d/tDM5dmai3TpJp4
хм, странно но заработало таким образом img.hello(src="./img/1.jpg"), без require : ))