React с нуля. Настройка связки gulp + webpack + babel + react

В прошлый раз я остановился на том, что запустил все необходимое мне, а именно набор из gulp, webpack, babel и react.

Но по факту не было полной автоматизации, webpack приходилось запускать вручную, а gulp выступал в роли инициатора компиляции ES6 в ES5. (Конечно, моей целью было именно разобраться в том, что и как работает, и эта цель была выполнена.)

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

Разделение обязанностей

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

А это означает, что нужно передать компиляцию ES5 тоже в webpack. (напоминаю, сейчас babel запускается из gulp, а webpack забирает готовый es5 код)

Gulp + Webpack

Сначала нужно подружить сам webpack с gulp’ом.
Для этого открываем официальную доку webpack, раздел usage with gulp.

Зачем-то там в первую очередь описано использование webpack’а в стиле всяких gulp-аддонов, с помощью stream. Мой подход не позволяет так делать, т.к. я хочу изучить технологию, а не просто взять и юзать непонятные готовые решения. Поэтому листаем чуть ниже и находим Without webpack-stream.

Видим, что для логов используется gulp-util. Ставим:

npm install --save-dev gulp-util

и добавляем в наш gulpfile

var gutil = require("gulp-util");
var webpack = require("webpack");

(сам webpack у нас уже установлен)
Далее берем оттуда же код для gulp:

gulp.task("webpack", function(callback) {
// run webpack
webpack({
// configuration
}, function(err, stats) {
if(err) throw new gutil.PluginError("webpack", err);
gutil.log("[webpack]", stats.toString({
// output options
}));
callback();
});
});

Так как мы уже чуток знаем webpack, сразу меняем настройки. Для этого заменяем пустой объект настроек на следующий код:

require('./webpack.config.js')

Файл настроек у нас уже есть. Вписывать настройки вебпака сразу в gulpfile.js считаю некорректным — пусть лежит отдельно.

Далее нужно отредактировать наш gulpfile.js, с учетом изменившегося порядка сборки. А именно — заменить задачу, которая отвечает за сборку JS (watcher в задаче default):

gulp.watch(path.watch.js, ['webpack']);

Задачу scripts удаляем, она более не актуальна.

Так, вроде все норм. Теперь…

Gulp + Webpack + Babel

Нужно научить webpack собирать ES6 код посредством Babel. Для таких задач используется специальный механизм в webpack, называемый loaders (“загрузчики”, как пишут в русских статьях, но этот термин немного некорректно звучит в этом контексте, имхо).

Суть загрузчиков в том, чтобы сначала передать файлы на обработку “на сторону”, чтобы webpack работал уже с готовым JS (CSS итд).

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

Как раз удачно подвернулась статья A Comprehensive Overview of WebPack, в которой расписан раздел Support for ES6.

Читаем: To enable Webpack to convert ES6 to ES5 with babel, you need three things: babel-loader, babel-core, and babel-preset-es2015.

Ок, babel-preset-es2015 у нас уже есть, поэтому делаем:

npm install --save-dev babel-core babel-loader

И начинаем настройку webpack.config.js . Для этого берем код из статьи:

module.exports = {
// ...
module: {
loaders: [
{
test: /.js$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['es2015']
}
}
],
}
};

И подгоняем под наши требования:

module.exports = {
entry: "./src/js/app.js",
module: {
loaders: [{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react']
}
}]
},
output: {
path: path.resolve(__dirname, './build/js/'),
filename: 'bundle.js'
}
};

Что именно мы сделали:

  • Заменили входную точку
  • Заменили выходной файл
  • Добавили презет react в загрузчик babel-loader
  • Добавили расширение файлов .jsx в обрабатываемые (на будущее)

Итог

Итак, как все происходит. Запускаем команду gulp.
Gulp запускает слежение на исходными стилями и скриптами.
Как только происходит изменение в скриптах, gulp запускает задачу webpack.
Задача в свою очередь запускает сам webpack, который берет конфиг из файла webpack.config.js. В конфиге указано: для js-файлов запускать загрузчик babel-loder, который, в свою очередь, знает, что код у нас написан на ES6 под React.

Вот такая вот цепочка. Можно работать! :)


Наводим порядок

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

"devDependencies": {
"babel-cli": "^6.24.0",
"babel-loader": "^6.4.1",
"babel-preset-env": "^1.2.2",
"babel-preset-es2015": "^6.24.0",
"babel-preset-react": "^6.23.0",
"gulp": "^3.9.0",
"gulp-autoprefixer": "^3.1.0",
"gulp-csscomb": "^3.0.7",
"gulp-group-css-media-queries": "^1.1.0",
"gulp-sass": "^2.0.4",
"gulp-sourcemaps": "^2.5.0",
"gulp-util": "^3.0.8",
"webpack": "^2.3.2"
},
"dependencies": {
"react": "^15.4.2",
"react-dom": "^15.4.2"
}

Сохраняем. Далее вызываем команду npm prune — она удалит неиспользуемые пакеты.

И еще для успокоения души чуток облагородим таск styles:

gulp.task('styles', function() {
return gulp.src(path.src.styles)
.pipe(sourcemaps.init())
.pipe(sass().on('error', sass.logError))
.pipe(autoprefixer({
browsers: ['last 2 versions', 'Safari >= 8'],
cascade: false
}))
.pipe(gcmq())
.pipe(csscomb())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(path.build.styles));
});

Всё :)

Следующие шаги — настройка webpack с целью минимизации выходного файла, и, собственно, пора бы уже писать код! :)