Предисловие


Качество приложения зависит не только от того, какие задачи и с какой скоростью оно решает, но и от таких, казалось бы, второстепенных факторов как «красота кода».

Под красотой кода я (полагаю, и многие другие) понимаю:

  • Читабельность
  • Простоту изменения и дополнения
  • Возможность другим разобраться, как это работает

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

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


Gulp


Что есть gulp?
Это сборщик проектов.

Для чего он нужен?
Вопрос сложнее.

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

  • Оптимизированные картинки
  • Минимизированные стили и скрипты
  • Прочее

По сути вы получаете две копии приложения: рабочую, которая понятна вам, в которую удобно вносить правки и публичную (public), уже собранную из ваших кусочков и оптимизированную.

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

Gulp по сути является js скриптом (набором скриптов), который работает на сервере node.js.
Это вовсе не означает, что вам надо изучить nodejs, чтобы использовать gulp, но базовые знания его менеджера пакетов (npm) понадобятся.

Задача первая: поставить локально сервер node.js.
Не буду заострять на этом внимание, слишком просто. После установки мы сможем использовать npm.

Задача вторая: установить (по сути скачать) gulp с помощью менеджера пакетов.
Для маководов это заключается в следующем: в терминале напишем
npm install gulp -g

Флаг -g означает, что мы устанавливаем его глобально, чтобы можно было запускать его в терминале командой
gulp
gulp task


Затем настроим окружение для нашего проекта, перейдя в консоли в нужную папку
mkdir assets public assets/js assets/img assets/css
touch gulpfile.js
npm init
npm i --save-dev gulp

По порядку:
  • Создаем структуру папок
  • Создаем файл настроек нашего сборщика
  • Инициализируем npm, чтобы установить полезные в сборке модули
  • Устанавливаем gulp с ключем "--save-dev", то есть в директории нашего проекта

Аналогично с последней строчкой установим все модули, нужные нам в сборке
npm i --save-dev gulp gulp-autoprefixer gulp-concat-css gulp-connect gulp-livereload gulp-minify-css gulp-sass gulp-rename gulp-uncss gulp-uglify  gulp-imagemin  imagemin-pngquant

Кратко о них
  • gulp-autoprefixer // Добавляет стили с префиксами для поддержки браузеров
  • gulp-concat-css // Склеивает стили в один файл
  • gulp-connect // Сервер
  • gulp-livereload // Обновляет страницу в браузере после изменений кода
  • gulp-minify-css // Минификация стилей
  • gulp-sass // Без комментариев (пока не нужно)
  • gulp-rename // Переименовывает файлы
  • gulp-uncss // Удаляет лишние стили
  • gulp-uglify // Минификация скриптов
  • gulp-imagemin // Минификация изображений
  • imagemin-pngquant // Минификация изображений


Примеры использования и подключения наипонятнейшим образом описаны здесь.

Теперь все, что нам нужно — это описать файл настроек, то как именно надо собирать наш проект.
Для этого в нашем файле настроек (gulpfile.js) создадим задачи (таски)
gulp.task('js',function(){                 // Таск "js"
    gulp.src('./assets/js/*.js')           // С чем работаем
    .pipe(uglify())                            // Что именно делаем. В данном случае минифицируем
    .pipe(gulp.dest('./public/js/'))    // Куда складываем результат
    .pipe(connect.reload());           // Обновляем страницу (не обязательно)
});

На самостоятельное изучение оставляю полный код моего
gulpfile.js
var gulp = require('gulp');
var concatCss = require('gulp-concat-css');
var minifyCss = require('gulp-minify-css');
var rename = require(«gulp-rename»);
var autoprefixer = require('gulp-autoprefixer');
var livereload = require('gulp-livereload');
var connect = require('gulp-connect');
var sass = require('gulp-sass');
var uglify = require('gulp-uglify');
var imagemin = require('gulp-imagemin');
var pngquant = require('imagemin-pngquant');

// Основные
gulp.task('css', function () {
gulp.src('./assets/css/*.css')
.pipe(concatCss(«style.min.css»))
.pipe(minifyCss({compatibility: 'ie8'}))
.pipe(autoprefixer({
browsers: ['last 10 versions'],
cascade: false
}))
.pipe(gulp.dest('./public/css/'));

gulp.src('./assets/css/fight/*.css')
.pipe(concatCss(«fight.min.css»))
.pipe(minifyCss({compatibility: 'ie8'}))
.pipe(autoprefixer({
browsers: ['last 10 versions'],
cascade: false
}))
.pipe(gulp.dest('./public/css/'))
.pipe(connect.reload());
});

gulp.task('sass', function () {
gulp.src('./assets/sass/*.ccss')
.pipe(sass(«style.css»))
.pipe(minifyCss(''))
.pipe(rename(«style.sass.min.css»))
.pipe(autoprefixer({
browsers: ['last 10 versions'],
cascade: false
}))
.pipe(gulp.dest('./public/css/'))
.pipe(connect.reload());
});

gulp.task('html',function(){
gulp.src('./assets/*.html')
.pipe(gulp.dest('./public/'))
.pipe(connect.reload());
});

gulp.task('fonts',function(){
gulp.src('./assets/font/**/*')
.pipe(gulp.dest('./public/font/'))
.pipe(connect.reload());
});

gulp.task('js',function(){
gulp.src('./assets/js/*.js')
.pipe(uglify())
.pipe(gulp.dest('./public/js/'))
.pipe(connect.reload());
});
gulp.task('jslibs',function(){
gulp.src('./assets/js/libs/*.js')
.pipe(uglify())
.pipe(gulp.dest('./public/js/libs/'))
.pipe(connect.reload());
});
gulp.task('jsmods',function(){
gulp.src('./assets/js/modules/**/*.js')
.pipe(uglify())
.pipe(gulp.dest('./public/js/modules/'))
.pipe(connect.reload());
});

gulp.task('img',function(){
gulp.src('./assets/img/*')
.pipe(imagemin({
progressive: true,
svgoPlugins: [{removeViewBox: false}],
use: [pngquant()]
}))
.pipe(gulp.dest('./public/img/'))
.pipe(connect.reload());
});

// Connect
gulp.task('connect', function() {
connect.server({
root: 'public',
livereload: true
});
});

// Watch
gulp.task('watch',function(){
gulp.watch("./assets/css/**/*.css", [«css»]);
gulp.watch("./assets/*.html", [«html»]);
gulp.watch("./assets/js/*.js", [«js»]);
gulp.watch("./assets/js/libs/*.js", [«jslibs»]);
gulp.watch("./assets/js/modules/**/*.js", [«jsmods»]);
});

// Default
gulp.task('default', [«html», «css», «sass», «js»,«jslibs», «jsmods», «connect», «watch»]);

Заострю внимание лишь на паре вещей.
Любая из созданных задач (тасков) запускается из консоли по шаблону
gulp task

Если мы напишем просто «gulp», что запустится таск по умолчанию, то есть default.

Таск watch — встроенный таск для отслеживания изменений в файлах. Его настройка настолько очевидна (см приведенный код), что уверен, каждый справится. Этот таск позволяет не вызывать каждый раз процесс сборки проекта после любого изменения кода. Как только вы сохраните файл, gulp это увидит и пересоберет проект, а в данном случае еще и обновит страницу в браузере, и вам останется только перевести взгляд на нужный монитор, чтобы увидеть результат.
Чтобы собрать проект с вышеприведенными настройками просто введите в консоли (находясь в папке проекта)
gulp img
gulp

Работу с картинками вынес в отдельный таск для удобства (моего).
После этого не закрывайте консоль, у вас запущен watch'ер и сервер.

По сути вы можете писать в каждом отдельном файле маленькую js функцию, а сборщик соберет все это в один файл.
Тут мы вплотную подошли к вопросу модульности. Вышеприведенная ситуация очень похожа на модульный подход (издалека) — каждой функции (модулю) отдельный файл. Не запутаешься. Но что делать когда один модуль зависит от другого, от нескольких других, а те еще от других.
Да, тут уже посложнее, надо продумать правильный порядок подключения модулей. И тут нам на помощь приходит requirejs, который осуществляет AMD (Asynchronous module definition) подход (есть еще common.js, но о нем в следующей статье).

Require.js


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

Для начала скачаем reuirejs и подключим в нашем index.html, это будет единственный скрипт, который мы подключим таким образом.
<script data-main="js/config" src="js/libs/require.js"></script>

В параметре data-main передаем путь к точке входа нашего приложения (не указываем расширение).

Config.js в простейшем случае представляет собой список алиасов (хотя можно обойтись и без него)
Config.js
requirejs.config({
	paths: {
		"rClck" : "modules/disable_rclck",
		"app" : "app",
		"socket" : "modules/webSockets",
		"Actions" : "modules/Actions",
		"User" : "modules/User",
		"userList" : "modules/userlist",
		"Matreshka" : "libs/matreshka.min",
		"Modal" : "modules/Modal",
		"fight" : "modules/fight",
		"jquery" : "libs/jquery",
		"myCards" : "modules/myCards",
		"opCards" : "modules/opCards",
		"mana" : "modules/mana"
	}
});

require(['app'],function(app){
	app.menu();
});



Синтаксис requirejs прост — передаем массив зависимостей (допустимы как пути, так и алиасы, все без расширения js указывается), описываем функцию, которая срабатывает после удовлетворения зависимостей (не забываем в качестве аргументов передавать модули, от которых зависим).
require(['app'],function(app){
	app.menu();
});


Подключили модуль app, после вызываем метод menu() этого модуля.
Описание модулей выглядит следующим образом: передаем массив зависимостей, пишем функцию-коллбэк, которая возвращает наш модуль.
define(['socket'],function(socket){ // Завтсть от модуля socket

	var app = {
		menu: function(){
			require(['Actions', 'Modal'],function(Actions, Modal){ // Выполняем функцию после удовлетворения зависимостей
				if (socket.readyState === 1) {
					Actions.exec('loginFailed','Введите желаемый логин (не менее 4 символов)');
				}else{
					Modal.box('Нет соединения с сервером');
				}
			});
		}
	}

	return app; // Возвращаем объект модуля
})


Метод socket.readyState проверяет, есть ли соединение с сервером. Если да, то вызываем метод Actions.exec, если нет, модальное окно (Modal.box).

Actions, socket и Modal — отдельные самостоятельные модули наравне с модулем app. У них есть свои зависимости, своя логика, свои методы.

Все, что раньше у нас было в одном файле, мы разбили на модули. Просто потому, что логически это разные куски кода, отвечающие за разные задачи, будь то соединение с сервером по WebSockets, манипулирование картами или «прочие задачи».

К прочим задачам я отношу то, что связывает все модули. Раньше у нас все это было в объекте Actions
Actions
var Actions = {
	send: function(method, args){
		console.log('send: ' + method);
		args = args || '';
		socketInfo.method = method;
		socketInfo.args = args;
		socket.send(JSON.stringify(socketInfo));
	},
	resume: function(){
		User = JSON.parse(localStorage['PlayingCardsUser']);
		this.send('reConnect',User.login);
	},
	setMotion: function(login){
		console.log('setMotion: ' + login);
		User.currentMotion = login;
		this.motionMsg();
		this.getCard();
		this.setTimer();
	}
...



Данные с сервера приходят в формате JSON в виде
{'function': 'fooName', 'args': [массив/объект аргументов]}

И раньше мы вызывали нужный метод так:
socket.onmessage = function (e){
	if (typeof e.data === "string"){
		var request = JSON.parse(e.data);
		Actions[request.function](request.args);
	};
}


Но по мере разрастания и усложнения нашего приложения объект Actions будет разрастаться до уровня, когда уже трудно будет держать его в памяти и ориентироваться в нем. Именно поэтому разобьем его на мелкие модули (простейшие единицы, выполняющие элементарную функцию. Эту идеологию я увидел в gulp, и она мне понравилась).

Для начала нам понадобится обертка вызова методов Actions
define(["socket"],function(socket){

	// Config
	var path = 'modules/Actions/';

	var Actions = {
		exec: function(){
			var args = Array.prototype.slice.call(arguments);
			var actionName = args.shift();

			require([path + actionName],function(action){ // Подключаем модуль
				action.run.apply(action,args);                  // Запускаем его
			});	
		}
	}

	return Actions;
})


В метод exec модуля Actions в качестве первого аргумента принимаем название нашего действия (action), остальные аргументы передаем аргументами в этот модуль.
В простейшем случае модуль действия будет выглядеть так:
define(['myCards'],function(myCards){ // Зависит от модуля myCards

	var action = {
		run: function(card){             // Метод run()  мы вызываем при подключении в Actions.js:    action.run.apply(action,args);     

			myCards.hand.add(card); // Реализация модуля

		}
	}

	return action; // Возвращаем объект модуля
})


Этот модуль является оберткой вызова метода добора карт
myCards.hand.add(card);


Это нужно для того, чтобы максимально изолировать модули друг от друга. Чтобы в любой момент можно было безболезненно переписать его, например, используя другой фреймворк.
Теоретически можно было бы сразу с сервера получать информацию о том, что надо запустить метод myCards.hand.add(), но мы же работаем с тем, что есть. Пытаемся понять, как разгрести кучу кода, которая уже есть, разложить по полочкам.

Итак, имея изначально один js файл, где описывалось все от и до, мы разделили его на модули, отвечающие за реализацию карт (на столе и в руке каждого игрока), websocket'ов и прочего. А так же давайте напишем новый модуль и внедрим его, чтобы лучше во всем разобраться. Это будет модуль, отвечающий за ману:
mana.js
define(['Matreshka'],function(Matreshka){ // Зависит от модуля Matreshka (js фреймворк)
	
	// var diamond = $('#icons #diamond').html();
	var diamond = '';

	/////////// Карты в моей руке
	var manaModel = Matreshka.Class({ // Модель списка
		'extends': Matreshka.Object,
		constructor: function(data){
			this.jset(data);
			this.on('render',function(){
				this.bindNode('active', ':sandbox', Matreshka.binders.className( 'active' ));
			});
		}
	});


	var manaArray = Matreshka.Class({ // Класс списка
		'extends': Matreshka.Array,
		Model: manaModel,
		itemRenderer: '<li></li>',
		constructor: function(){
			this.bindNode('sandbox','#mana'); // Засовываем в песочницу
		},
		add: function(){
			// Добавить активный кристалл
			if (this.length >= 10) return;
			this.push({active:true});
		},
		spend: function(num){
			var num = num || 0;
			var actives = this.filter(function(obj){
				if (obj.active) return true;
				return false;
			});
			if(actives.length < num) {
				console.log('Недостаточно маны');
				return false;
			}
			// Деактивировать num маны
			for (var i = this.length - 1; i >= this.length - num; i--) {
				this[i].active = false;
			};
			return true;
		},
		setAllActive: function(){
			for (var i = this.length - 1; i >= 0; i--) {
				this[i].active = true;
			};
		}
	});

	var mana = new manaArray; // Экземпляр класса списка

	return mana;
})



Модуль представляет собой список (массив) кристаллов маны. Что он должен уметь?

  • Добавлять кристалл на каждом ходу: add()
  • Тратить полные (активные) кристаллы маны: spend()
  • Пополнять (делать активными) кристаллы маны каждый ход: setAllActive()


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

Давайте внедрим наш новый модуль в имеющуюся игрушку, пусть отныне учитывается стоимость карт.
Начнем с пополнения кристаллов маны каждый ход. Каждый ход начинается с вызова действия (модуля, вызываемого модулем Actions через наш метод exec) setMotion, который устанавливает то, какой игрок сейчас ходит.
setMotion.js
define(['Actions', 'User', 'myCards','mana'],function(Actions, User, myCards, mana){ 

	var action = {
		run: function(login){

			User.currentMotion = login;
			Actions.exec('motionMsg');  // Сообщение о том, чей сейчас ход
			Actions.exec('setTimer');     // Установка таймера (ход длится 2 минуты)
			if (User.meCurrent()) {          // Если мой ход
				myCards.arena.enableAll();  // Активируем мои карты на арене. Спящие просыпаются
				Actions.exec('getCard');       //  Добираем карту
				mana.setAllActive();            // Делаем все кристаллы активными
				mana.add();                         // Добавляем кристалл маны
			};
		}
	}

	return action;
})



Как видите, внедрение первого шага оказалось несложным. Мы просто добавили вызов этого метода в нужный момент времени (в начале нашего хода). И если мы перепишем наш модуль, все будет работать как и работало. Главное не забыть реализовать ту же логику.

Как тратить ману? Очевидно, это происходит на этапе выкладывания карт из руки на стол. Внесем изменение в этот метод:
Было
				this.on('click::sandbox',function(){ // Клик по карте в руке
					if(!User.meCurrent() || myCards.arena.length >= 7) return;  // Если хожу не я или на арене много карт - false
					myCards.arena.push(this); // Добавляем карту на арену
					myCards.hand.splice(myCards.hand.indexOf(this),1); // Убираем ее же из руки
					Actions.exec('send', 'putCard',this.toJSON());  // Показываем это сопернику
				});


Стало
				this.on('click::sandbox',function(){ // Клик по карте в руке
					if(!User.meCurrent() || myCards.arena.length >= 7) return;
                                        if(!mana.spend(this.mana)) return;                       // Если не удается потратить нужное кол-во маны - false
					myCards.arena.push(this); 
					myCards.hand.splice(myCards.hand.indexOf(this),1);
					Actions.exec('send', 'putCard',this.toJSON()); 
				});



Заключение


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

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

Ссылки


Комментарии (71)


  1. Proxmiff
    17.08.2015 07:38

    А почему выбрали requirejs, а не webpack?


    1. enleur
      17.08.2015 09:11
      +2

      Недавно еще была шумиха вокруг browserify, теперь webpack. Чем webpack лучше requirejs?


      1. Fesor
        17.08.2015 09:40
        +2

        Не знаю чем там что лучше, но… может просто юзать модули из es2015 + esperanto + babel?


        1. seokirill
          17.08.2015 10:09

          Разве babael c gulp нельзя использовать? Ссылка


          1. Fesor
            17.08.2015 11:14

            Не понял вопроса, тип то что я не перечислил gulp? Это уже детали, я вот с gulp-ом использую.


            1. seokirill
              17.08.2015 11:17

              Подумал, вы приводите babel в качестве преимущества webpack


              1. Fesor
                17.08.2015 12:29
                +1

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


                1. seokirill
                  17.08.2015 12:30

                  Выходит, это вопрос религии


                  1. Fesor
                    17.08.2015 12:48
                    +1

                    Религия это глупость. gulp просто универсален и позволяет решить все юзкейсы связанные со сборкой (особенно 4-ая версия хороша), webpack для меня лично просто был лишним. То что я не увидел в нем преимуществ для моего воркфлоу не означает что их нет для других людей.


      1. DIKunin
        17.08.2015 09:42

        Вопрос предпочтения нотации. Лично мне больше нравится добавлять модули commonjs стилем — поэтому я перешел на browserify/webpack с require и ни разу не пожалел. Но, как говорится, на вкус и цвет фломастеры у всех разные)


        1. enleur
          17.08.2015 10:24
          +1

          Не знаю как webpack, но от browserify отказался потому что он не умеет в браузере работать без предварительной компиляции. С Requirejs удобно как раз таки в режиме разработки все зависимости в браузере подгружать.


          1. DIKunin
            17.08.2015 10:41

            Соглашусь — дополнительный шаг компиляции — не всегда супер удобен — динамическая подгрузка — в данном вопросе для Require — плюс по сравнению с browserify.


          1. xGromMx
            17.08.2015 15:54

            Тогда вам подойдет jspm.io. Большой плюс — легкость работы с babel.


            1. Fesor
              17.08.2015 16:37

              легкость работы с babel.


              return gulp
                .src('src/**/*.js', {sourcemaps: true})
                .pipe(util.plumber())
                .pipe(babel({
                  modules: 'system'
                }))
              


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


              1. xGromMx
                17.08.2015 16:39

                Я сам долгое время пользуюсь gulp, но потихоньку поглядую на npm scripts + webpack.


      1. frst
        17.08.2015 12:39
        +3

        webpack.github.io/docs/comparison.html

        можно на горячую обновлять реакт, есть автоматическая разбивка бандла на части, можно в бандл закинуть картинки и шаблоны, можно настроить так чтобы сливались только картинки менее определенного размера, а большие складывались в ресурсы, дофига магии разной полезной, и common js синтаксис нормально поддерживается, lazy loading умеет, компилится в dev режиме за долю секунды, вобщем при переходе с requirejs стало жить приятнее


        1. tamtakoe
          19.08.2015 14:02

          Раз Вебпак такой крутой, сбилдить папку dist в таком AMD-проекте раз плюнуть. Разумеется изменение состава модулей не должно влиять на код сборки.

          project
          |- dist
             |- vendor-<random>.js
             |- module1-<random>.js
             |- module2-<random>.js
             |- module3-<random>.js
             |- index.html
          |- src
             |- module1
                |- index.js
                |- service1.js
                |- someDir
                   |- service2.js
             |-module2
             |-module3
          |- vendor
          |- config.js //like requireJs config
          |- index.html
          gulpfile.js
          


          Есть ссылки на подобные примеры?


          1. xGromMx
            19.08.2015 14:10

            1. tamtakoe
              19.08.2015 18:44

              Читал


      1. xGromMx
        17.08.2015 15:52

        Все просто, я могу с легкостью писать amd, common, es 2015 и не будет проблем это все интегрировать github.com/webpack/webpack/tree/master/examples/mixed


        1. seokirill
          17.08.2015 16:01

          Это стандартный функционал webpack'а? Любопытно.


          1. xGromMx
            17.08.2015 16:02

            Да, более того, есть куча лоадеров.


    1. seokirill
      17.08.2015 10:07

      Webpack реализует подход другой. Он на очереди в списке. Просто про require раньше узнал. Webpack еще и gulp, получается, заменяет.


      1. Proxmiff
        17.08.2015 10:47
        +1

        Webpack еще и gulp, получается, заменяет.

        Многие задачи решает, но таскраннер все равно нужен.



  1. justusebrain
    17.08.2015 10:00
    +6

    Мир уже перешел на быстрый вебпак с инкрементальными обновлениями и require-ом всего, что только можно, а тут «level up» заключается в require.js. Смех.


    1. seokirill
      17.08.2015 10:12

      Level Up для новичков заключается в модульном подходе в принципе. И про webpack расскажем. Если подскажите, как протестировать производительность, буду благодарен. Я такой же новичек во всем этом.


      1. RusSuckOFF
        17.08.2015 12:26
        +1

        Про модульный подход уже слишком много написано даже на хабре.


    1. Ununtrium
      17.08.2015 10:43

      Ну так статья «от новичка для новичков», что вы хотите. Технических статей тут и так не хватает, хоть что-то.


      1. seokirill
        17.08.2015 10:58
        +2

        От «старших товарищей» приветствую конструктив. Часто подсказывает, в какую сторону вообще думать.


    1. enleur
      17.08.2015 11:55

      Все же в чем профит от перехода с requirejs на webpack?


      1. andreysmind
        17.08.2015 12:23
        +2

        Возможность писать import $ from 'jquery'; без 100500 разных скобочек. Да и вообще код чище и аккуратнее выглядит.
        hot-reload, простое подключение модулей для less, jsx, прочих модных слов.
        конфиг webpack для меня выглядит понятнее и логичнее чем shim require.js
        Я require.js пользовался довольно долго, а вот с тех пор как первый раз увидел как работает проект с webpack, про другие варианты даже думать забыл.


        1. f0rk
          17.08.2015 12:41

          Только причем тут webpack? CommonJS и ES6 modules прекрасно работают и без webpack.


          1. andreysmind
            17.08.2015 13:07

            Не причем, тоже прокомментировать что-нибудь захотелось.
            в webpack _мне_es6_модули_так_удобнее_подключать_.


    1. tamtakoe
      19.08.2015 13:28

      Перешли только школьники. Впрочем, они каждый год переходят на что-то новое. Читая комментарии про CommonJS и es2015, создается впечатление, что народ даже со средними проектами не работал. В большом проекте прекомпилятор добавит столько тормозов и ошибок компиляции, что никакой Webpack не спасет. Статья, конечно, дурацкая, человек рассказывает о первом опыте, но это никак не превозносит Webpack, который, вообще глупо сравнивать с RequireJS, т. к. это технологии разных уровней.


      1. justusebrain
        19.08.2015 14:05

        Люди переходят на новые технологии, которые облегчают жизнь (и действительно это делают, в том числе благодаря «школьникам», которые посылают патчи и пишут об ошибках), что за нытьё про школьников/хипстеров/etc?

        > В большом проекте прекомпилятор добавит столько тормозов и ошибок компиляции, что никакой Webpack не спасет.
        Пруфы? Особенно про ошибки компиляции. Боюсь даже представить, сколько же будет тормозов от вебпака, который бьёт бандл на чанки и в дев-режиме обновляет только конкретный чанк. И это включая лоадеры типа scss/babel.

        > но это никак не превозносит Webpack, который, вообще глупо сравнивать с RequireJS, т. к. это технологии разных уровней
        RequireJS только для джса, вебпак для всего, был бы подходящий лоадер. Зачем нужен requirejs, если вебпак в разы лучше? Особенно пресловутые чанки и hot mode.


        1. tamtakoe
          19.08.2015 18:57
          -2

          Пруфы?

          Ошибки не по вине компилятора, а по вине разработчика. Отступ лишний поставил, IDE при вставке куска напортачила. Сложнее система — больше ошибок.


          1. f0rk
            19.08.2015 19:06
            +1

            Что? Какой отступ? Какая IDE? Вы что несете?


            1. tamtakoe
              19.08.2015 20:07
              -1

              yaml и stylus файлы чувствительны к отступам. Копипаст там не всегда корректно работает, какой-то не тот вид пробела может поломать сборку. С компилятором Кофе скрипта больше пары дней не выдержал, пока не заметил, что вместо того чтобы писать код разбираюсь почему опять сломалась сборка. И консолька ОС далеко не так информативна как консолька Хрома. Jade, polymer — та же фигня. А еще библиотеки имеют свойство обновляться. А еще нужно чтобы со всем этим хламом умели новые сотрудники работать. У адекватного разработчика в проекте будет только самое необходимое, а для этого Вебпак вовсе необязателен.


              1. Fesor
                19.08.2015 21:27
                +1

                у меня на бэкэнде в yaml все конфиги, а еще есть ansible у которого вообще все (кроме модулей) на yaml, и почему-то никаких проблем. stylus — тут так же как и с python, привык и нет проблем (хотя я сам испольюзую less и потиху подумываю уходить на postcss).

                вместо того чтобы писать код разбираюсь почему опять сломалась сборка

                Ну а если нет таких проблем? Ну вот… я на кофе не пишу (и не люблю по непонятным мне причинам), но я повсеместно использую сборщики, и у меня почему-то никаких проблем с настройкой сборки нет. Более того, 90% всего того что настраивается можно спокойно реюзать. Меня в этом плане очень радует gulp4, у него больше гибкости в плане управления тасками.

                И консолька ОС далеко не так информативна как консолька Хрома.

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

                Jade, polymer

                А как это вообще попало в один список? На счет jade, хоть я его и использую, согласен. Мне он нравится, позволяет не писать лишних символов и удобно бороться с излишне жирными директивами… Но полимер то? Вы еще скажите что web-components это все лишняя отвлекающая штука.


                1. tamtakoe
                  20.08.2015 03:27

                  Для меня jade нет имеет смысла потому что архитектура не предусматривает больших шаблонов (больше 100 строк), как и стилевых файлов и скриптов. Polymer случайно затесался. Имел в виду полифилы (или по нашему костыли), которые добавляются сборкой.


                  1. Fesor
                    20.08.2015 10:07

                    Ну у меня тоже нет больших шаблонов (больше 100 строк), и jade тут только лучше смотрится. Что до полифилов добавляемых сборкой — у меня пока такого так же нет, все полифилы подключаются через bower а уж то что подключено через bower хэндлится сборкой.


          1. Fesor
            19.08.2015 19:54

            Вот давайте так, если у вас нет опыта работы с ES2015/TypeScript/Coffee то и не надо придумывать несуществующих проблем. IDE прекрасно с ними работает (с typescript лучше чем с js собственно). А сделать ошибку синтаксическую можно и в js.


            1. tamtakoe
              19.08.2015 20:29
              -1

              С Coffee точно не все так гладко. По крайней мере пару лет назад было. В любом случае, ES2015/TypeScript/Coffee не дают такого профита, чтобы на них писать что-либо отличное от исследовательского или игрового проекта. Даже синтаксический сахар там не такой сладкий как в ES5 был. Часто классы используете? Можно посчитать количество классов в коде Ангуляра, Реакта, в своем собственном проекте — десятка не наберется. Без стрелочныйх функций не прожить? Хотите обучать новых сотрудников как пользоваться генераторами, хотя сами смутно представляете? В данный момент (2015-2016) всё это баловство. А потом народ пишет, что Вебпак крут, потому что в нем легко баловаться, при том что до сих пор нет ни одной статьи где бы говорилось почему он действительно крут.


              1. Fesor
                19.08.2015 21:21
                +1

                классы — регулярно (2/3 директив содержат контроллер и не содержат линка, все контроллеры и сервисы — классы). В случае со сложной бизнес логикой на клиенте у меня появляются еще сущности, которые так же являются классами и имеют внутри какую-то логику. И да, код от этого становится чище (есть с чем справнивать), что положительно сказывается на суппорте приложения. Так же проще организовать внутренние правила аля «дробите приложение на директивы, юзайте контроллеры и т.д.». Без классов, только с Object.create как-то грустно все же.

                Стрелочные функции — опять же удобно, особенно когда часто пользуешься map/reduce/filter/etc. Это, как вы правильно заметили, просто сахар, который позволяет сократить количество информационного мусора.

                Что до генераторов — говорите за себя, я знаю как устроены и генераторы и корутинки могу пописать, но да, их я сейчас не использую ибо для команды это перебор. Генераторы и корутины это круто, но их чаще можно встретить на бэкэнде (php, python, ruby). Для JS будет счастьем async/await, что валяются в черновиках ES7.

                В данный момент (2015-2016) всё это баловство.

                На текущий момент (2015-2016) использование ES2015 это уже необходимость, так как при должном подходе это упрощает поддержку кода за счет того сахара и плюшек. которые оно дает. Да взять хотя бы дефолтные значения для аргументов, это совсем уж мелочь но так упрощает восприятие кода.

                Ну а webpack — я не знаю чем он так крут и я его не использую так как не вижу преимущест перед gulp конкретно для себя.


                1. tamtakoe
                  20.08.2015 04:10
                  -1

                  все контроллеры и сервисы — классы

                  Ангуляр давно решил эту проблему. Там ни Object.create не нужно использовать, ни new делать. Другие фреймворки предоставляют свои обертки. Со времен jQuery не встречал фронтендского проекта, где бы слово new (по отношению к пользовательским классам) употреблялось бы чаще 20 раз.

                  я знаю как устроены и генераторы

                  Нужно не просто знать, а уметь использовать, чего пока никто не умеет даже на node.js. Даже когда async/await появится, должны появиться фреймворки их использующие, выработана практика, набиты шишки, средний разработчик должен уметь с этим работать. А это не год и не два.

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

                  Совсем отошли от темы :-) Просто на фронтенде СТОЛЬКО нерешенных задач… Судя по публикациям, народ до сих пор топчется на уровне «делаем приложение на Ангуляре/Реакте/Метеоре за 10 дней», зато в комментариях все достигли таких высот, что от нечего делать обновляются на ES6.


                  1. Fesor
                    20.08.2015 10:22

                    Ангуляр давно решил эту проблему

                    Можно подробнее, какую именно проблему решил ангуляр и почему там не нужны классы? Про Object.create ладно, заменим просто на Object.defineProperties, мы же должны определить методы прототипа и все такое. Тут вопрос в том как организовать методы объектов и т.д. С классами это очень легко и хорошо читается. А уж инджектор позоботится обо всем остальном.

                    Нужно не просто знать, а уметь использовать

                    У вас может выборка не репрезентативна? Опять же, корутины мало кто пишет потому что это сложнее чем юзать промисы, но их пишут. С генераторами намного проще, это просто сахар над итераторами. Ну и да, генераторы уже реализованы в хроме и фаерфоксе (не полностью но с большего).

                    Судя по публикациям, народ до сих пор топчется на уровне «делаем приложение на Ангуляре/Реакте/Метеоре за 10 дней»

                    Да я вам больше скажу, я не видел на гитхабах ни одного проекта на ангуляре, который я мог бы спокойно дать посмотреть другим как пример «как надо делать». Какого рода публикаций вы ждете (вдруг кто будет читать этот диалог да напишет чего)? Ну и ES6 не от нечего делать, за 4 месяца после внедрения люди начали писать более чистый код, что я считаю достаточным оправданием что бы усложнять инфраструктуру. С ES6 мне было проще определить какие-то границы и правила о том «как надо чего делать», а эти правила в последующим упростят людям поддержку кода и позволят быстро мигрировать на angular2 как только он выйдет. Была б моя воля я бы сразу всех на TypeScript перевел, но это было бы уже слишком резким изменением.


                    1. tamtakoe
                      20.08.2015 11:43

                      Можно подробнее, какую именно проблему решил ангуляр и почему там не нужны классы?

                      Классы в Ангуляре создаются через методы-обертки factory(<имя>, <конструктор>), соntroller(<имя>, <конструктор>) и т. п., поэтому код типа (и более сложный )

                      var MyClass = function() { ... }
                      MyClass.ptototype.blabla = blabla
                      
                      var myClass = new MyClass()
                      
                      //или в es2015
                      
                      class MyClass { ... } 
                      


                      приходится писать крайне редко.

                      Какого рода публикаций вы ждете

                      Любых, которые бы показывали что люди растут, а не кидаются с одной технологии на другую. Сейчас подавляющее большинство публикаций из разряда: «новая технология такая-то», «мой первый опыт, не бейте сильно», «посмотрите какие костыли мы внедрили в своей компании». По тому же Вебпаку ни одной статьи, описывающей бест практис, а не детские примеры.


                      1. Fesor
                        20.08.2015 11:55

                        Классы в Ангуляре создаются через методы-обертки factory

                        Да, и это проблема:

                        angular
                            .module('app')
                            .factory({
                                foo: fooFactory,
                                bar: barFactory,
                                buz: buzFactory
                             })
                             .service('fooBar', FooBar)
                        ;
                        
                        
                        // среднестатистическая фабрика
                        function fooFactory (depA, depB) {
                            return {
                               foo: foo
                            }
                        
                            function foo () { return 'foo'; }
                        }
                        
                        // ваш вариант
                        function barFactory(depA, depB) {
                             function Bar() {}
                             Bar.prototype.foo = function () { return 'foo'; };
                        
                             return Bar;
                        }
                        
                        // а если мы захотим рид онли свойства (ну а вдруг? Я лично считаю это дурным тоном но всякое бывает)
                        function buzFactory(depA, depB) {
                            var readOnlyVal = 'read only';
                         
                            function Buz () {}
                        
                            Object.defineProperties(Buz.prototype, {
                                buz: { value: buz },
                                readOnlyVal: { get: function () { return readOnlyVal; } }
                            });
                        
                            function buz() { return 'buz'; }; 
                        }
                        
                        // ну и как это у меня
                        
                        class FooBar {
                            constructor(depA, depB) {
                               this.depA = depA;
                               this.depB = depB;
                            }
                        
                            fooBar () {
                                return 'foobar';
                            }
                        
                            get readOnlyVal () {
                                return readOnlyVal; // ну тип это может быть взятие значений из weakmap например
                            }
                        }
                        


                        возможно кода больше но зато аккуратнее как-то. ну и для людей пришедших из других языков это кажется логичнее (я вообще php-шник).

                        Любых, которые бы показывали что люди растут

                        С этим сложно, на нормальную статью нужно убить довольно много времени, альтруизма не хватит.


      1. Fesor
        19.08.2015 16:42

        В большом проекте прекомпилятор добавит столько тормозов и ошибок компиляции

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

        это никак не превозносит Webpack,

        Статья описывает не только профит require.js но и как собирать модули, так что сравнения webpack vs amd никто не делает (это глупо так как оный поддерживает и amd).


      1. f0rk
        19.08.2015 18:55

        Читая комментарии про CommonJS и es2015, создается впечатление, что народ даже со средними проектами не работал. В большом проекте прекомпилятор добавит столько тормозов и ошибок компиляции, что никакой Webpack не спасет.


        Ухты как интересно! Расскажите, в чем проблема commonjs на больших проектах, а то пока что звучит как очень безосновательное утверждение.


        1. tamtakoe
          19.08.2015 19:44
          -1

          CommonJS-модули не работают в браузерах т. к. у них нет изолированной области видимости. Для этого каждый файл нужно обернуть. Это должен делать сборщик и делать это на каждый чих. Изменения на сайте появляются не сразу, особенно, когда зависимостей много, что раздражает. При отладке не виден исходный файл, source map опять же добавляют лишнюю сложность, а сложный проект нужно делать как можно проще, потому что его собственной сложности хватает. Необходимость использования CommonJS модулей на фронтенде и на node.js преувеличена. Максимум библиотечку типа lodash и набор функций для валидации. Синтаксис не проще и не сложнее. Пока использовать их на фронтенде в любых проектах кроме исследовательских бессмысленно. Возможно через пару лет всё изменится.


          1. Fesor
            19.08.2015 19:59
            +1

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

            И с этим нет никаких проблем, инкрементная сборка происходит быстрее чем вы можете переключить внимани е от IDE к дебагеру/браузеру. А с livereload еще и успевает за это время загрузиться. Скажем для reactjs есть hot reload, когда отдельные компоненты перезагружаются без необходимости перезагрузки страницы. И там на каждый чих компилится JSX.

            source map опять же добавляют лишнюю сложность

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

            Необходимость использования CommonJS модулей на фронтенде и на node.js преувеличена

            Альтернатива? На node.js к слову необходимость крайне высока.

            Максимум библиотечку типа lodash

            А теперь скажите что для вас большой проект? Или вы предлагаете все писать тупо на javascript без фреймворков и лишней «сложности»? Я конечно чуть утрирую ваши слова, но выходит как-то так. Для большинства эти инструменты экономят кучу сил и времени. Да, можно и без них, но сложность возврастает в других местах. Ну и да, вы пишите тесты?


            1. tamtakoe
              19.08.2015 20:55

              geometria.ru, vozovoz.ru большие проекты? Сейчас RequireJS, Angular, Stylus, Jasmine, Karma, Gulp. От Coffee через пару дней отказался, Jade тоже пошел лесом, Stylus пока больше плюсов приносит. От jQuery удалось избавиться, как и от всех компонентов его использующих. Lodash бы тоже кастрировал так как по факту из него используется несколько функций, причем многие в костыльных местах. Возможно вместо Jasmine вернусь к Mocha, чтобы одна технология и на фронтенде и на бэкенде была. Чем больше говна выпиливается, тем проще: не нужно учить лишнего, не нужно обучать лишнему, не нужно ловить ошибки в чужом коде, не нужно следить за совместимостью, код проще читается.

              Кучу сил и времени экономит хорошая архитектура, а не модные плюшки.


              1. f0rk
                19.08.2015 21:19
                +1

                geometria.ru, vozovoz.ru большие проекты?


                Обычные сайтики на ангуляре, на фронтенд не больше 1-1.5к человекочасов каждый.


                1. tamtakoe
                  20.08.2015 04:16

                  На поддержку и 0.5 хватит. На разработку значительно больше :-) Приведите примеры сложных сайтов на Ангуляре. Самому интересно глянуть.


                  1. f0rk
                    20.08.2015 18:24
                    +1

                    На поддержку? 0.5? Вы вообще в курсе что такое поддержка? Если на разработку ушло более 1.5к — это очень грустно.

                    Приведите примеры сложных сайтов на Ангуляре.

                    Сайт в традиционном понимании (интернет-магазин, веб-портал и т.п.) — , на мой взгляд, очень редко бывает большим, сложным проектом. Если хотите посмотреть большой проект на JS, вот вам пример: sdk.amazonaws.com/js/aws-sdk-2.1.45.js, как ни странно, на commonjs. Видимо в amazon не в курсе, что его не стоит в больших проектах использовать.


          1. f0rk
            19.08.2015 20:36
            +1

            CommonJS-модули не работают в браузерах т. к. у них нет изолированной области видимости.
            Открою секрет, они не по-этому в браузере не работают.
            Для этого каждый файл нужно обернуть.
            Browserify делает это автоматически.
            Изменения на сайте появляются не сразу, особенно, когда зависимостей много, что раздражает.
            На больших проектах изменения на сайте появляются только после деплоя из CI-системы.
            source map опять же добавляют лишнюю сложность
            УжсУжсУжс… какая же все таки сложная штука source map, никогда бы не подумал.
            Необходимость использования CommonJS модулей на фронтенде и на node.js преувеличена.
            Неверно.

            … и т.д. и т.п.

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


  1. voidnugget
    17.08.2015 18:43
    +2

    Посмотрел на это

    gulp.task('js',function(){
        gulp.src('./assets/js/*.js')
            .pipe(dosomestuff());
    });
    
    и дальше перестал читать…

    Постоянно наблюдаю ситуацию когда юзвари забывают возвращать потоки с тасков в gulp'e или не знают про merge-stream.

    Надо делать вот так
    gulp.task('js',function(){
        return gulp.src('./assets/js/*.js')
            .pipe(doSomeStuff());
    });
    

    и вот так
    var merge = require('merge-stream');
    
    gulp.task('js',function(){
        merged = merge();
        [
          'mdpi', 'hdpi', 'xhdpi'
        ].forEach(function(density) {
        merged.add(gulp.src('app/images/**/*.@(png|gif|jpeg)')
            .pipe(doSomeMagicStuff()))
            .pipe(gulp.dest('dist/images/' + density)));
        });
    
        return merged;
    });
    

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

    А то большая часть gulp адептов меня расстраивают :|

    Мне, обычно, browserify watchify debowerify deamdify babelify с головою хватает — 2-3 секунды на сборку бандла погоды не делают, а WebPack просто не обеспечивает должной гибкости, да и больших преимуществ по скорости работы, как многие голосят, я раньше не замечал…


    1. voidnugget
      17.08.2015 18:48
      +1

      Ещё, кстати, нужно использовать callback'и

      var plato = require('plato');
      
      gulp.task('plato', function(done) {
          plato.inspect(config.projectFiles.js, 'reports', config.plato, done);
      });
      


  1. PerlPower
    17.08.2015 23:38
    -1

    Какой фреймворк у нас сегодня в моде, d
    С чем ты опять проснуля не в ладу… d


    1. voidnugget
      18.08.2015 04:12
      +2

      Работал почти со всем что более-менее актуально и распространено.
      Получается такая ситуация что как только контра хочет разработать какое-то актуальное решение — нужно пилить свой гибрид лигерада с токамаком. Так было с Rails / Grails / Gulp.js / React.js / Sproutcore (ember) / Cappuccino / Symfony / Play2…

      A практике получается так что MVC подходит только для простых UI, и на бэкенде порождает тонну шаблонной копипасты из-за Scaffolding'a. Рано или поздно, люди всеравно скатываются в CQRS-ES с всякими German / Celery / Sidekiq / beanstalk / rabbitmq и т.п. Ну или, что ещё хуже, потом обзывают это всё микросервисами. Хотя на самом деле для REST CRUD'ов, не зависимо от размера (нормализованной) базы, хватит 6 шаблонных front controller'ов.

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

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


  1. ShpuntiK
    20.08.2015 11:55
    +1

    Кажется requirejs и AMD уже умер. CommonJS и ES6 modules — все крутые ребята на них сидят.


    1. tamtakoe
      20.08.2015 15:35
      -1

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


      1. Fesor
        20.08.2015 16:01

        requirejs нужен для того что бы загружать модули, но без http2 с мультиплексирование трафика всеравно придется модули собирать в бандлы.


      1. xGromMx
        20.08.2015 16:24

        Вот прикольная альтернатива для require.js jspm.io


        1. Fesor
          20.08.2015 16:31
          +1

          Это не альтернатива. Это совсем из другой оперы, это просто менеджер пакетов для тех кому лень выбирать между npm/bower и для тех кто хочет один менеджер пакетов. У меня помимо npm и bower есть еще composer и ansible-galaxy так что мне эта штука вообще как-то не понравилась. Проблемы она мои не решает и только привносит лишнюю сущность.


          1. xGromMx
            20.08.2015 16:34

            Насколько я помню там github.com/systemjs/systemjs, который позволяет делать также как и в require.js


            1. Fesor
              20.08.2015 16:46

              ну так дали бы ссылку на systemjs, я использую его как загрузчик в dev окружении (es2015 модули).


      1. ShpuntiK
        20.08.2015 20:24
        +1

        Что вам мешает писать модульный код с CJS/ES6 modules и асинхронно их загрузать по мере необходимости? Тот же webpack это позволяет легко сделать.


        1. Fesor
          20.08.2015 22:57
          +1

          Человеку не нравится слово webpack судя по всему.