Ранее уже была публикация, которая демонстрировала как использовать Symfony2 и RequireJS с помощью бандла HearsayRequireJSBundle. Способ имеет место быть, знаю из первого ряда, потому как принимал непосредственное участие в разработке второй версии этого бандла. Тем не менее, этот бандл не использую. В последнее время клиентскую часть чаще разрабатываю как SPA и нашел более простой способ, о нем и пойдет речь.
Главная идея способа состоит в том, чтобы поместить исходники клиентской части в публичную директорию, доступную из Web. В Symfony2 это директория web. Как результат, без каких-либо проблем можно очень просто настроить r.js оптимизатор, нужно лишь объяснить Symfony2 как отдавать исходники клиентской части в зависимости от окружения приложения и написать довольно простой конфиг для r.js оптимизатора.
Мы будем использовать Bower для установки необходимых JavaScript-зависимостей и gulp.js (возможен вариант Grunt, но мне больше нравится gulp.js) для сборки клиентской части, поэтому нам потребуются установленные Node.js и NPM.
Будем считать, что Bower и gulp.js уже установлены, теперь нам нужно добавить для них конфигурационные файлы, положим их в корень проекта:
bower.json
{
"name": "symfony-standard-requirejs",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"almond": "0.3.0",
"requirejs": "2.1.15",
"rjs": "2.1.15"
}
}
Выше мы указали, что нам нужен almond (легковесный AMD-загрузчик для prod окружения, замена для RequireJS), RequireJS и r.js оптимизатор.
package.json
{
"name": "symfony-standard-requirejs",
"private": true,
"dependencies": {
"gulp": "3.8.8",
"yargs": "1.3.2"
}
}
Выше мы указали, что нам нужен gulp.js и yargs.
gulpfile.js
var gulp = require('gulp'),
exec = require('child_process').exec,
argv = require('yargs').argv;
gulp.task('copy', function () {
// almond
gulp.src('bower_components/almond/almond.js')
.pipe(gulp.dest('web/app/vendor/almond'));
// requirejs
gulp.src('bower_components/requirejs/require.js')
.pipe(gulp.dest('web/app/vendor/requirejs'));
// rjs
gulp.src('bower_components/rjs/dist/r.js')
.pipe(gulp.dest('.'));
});
gulp.task('rjs', function (cb) {
var env = argv.env ? argv.env : 'dev',
cmd = [
'php app/console cache:clear --env=' + env,
'php app/console assets_version:increment --env=' + env,
'php app/console assetic:dump --env=' + env,
'node r.js -o web/app/app.build.js'
];
exec(cmd.join(' && '), function (err, stdout, stderr) {
console.log(stdout);
console.log(stderr);
cb(err);
});
});
gulp.task('build', ['copy', 'rjs']);
gulp.task('default', ['build']);
Выше мы указали следующие задачи:
- copy — скопирует JavaScript-зависимости из директории bower_components в публичную директорию web/app/vendor
- rjs — очистит кеш Symfony2, инкрементирует версию ассетов для prod окружения (нужно не забыть установить бандл KachkaevAssetsVersionBundle), дампнет ассеты и запустит r.js оптимизатор
- build — алиас для задач, указанных выше
- default — алиас для задачи build
Теперь нам нужно установить необходимые Node.js пакеты и JavaScript-зависимости, запустим для этого следующие команды:
npm install
bower install
Основные подготовительные работы окончены. Осталось объяснить Symfony2 как отдавать исходники клиентской части в зависимости от окружения приложения и написать довольно простой конфиг для r.js оптимизатора. Сделаем это следующим образом:
src/AppBundle/Resources/views/Default/index.html.twig
{% extends "AppBundle::layout.html.twig" %}
{% block javascripts %}
{% if app.environment == 'prod' %}
<script src="{{ asset('app/dist/main.js') }}"></script>
{% else %}
<script>var require = {urlArgs: 'bust=' + (new Date()).getTime()};</script>
<script data-main="app/main" src="{{ asset('app/vendor/requirejs/require.js') }}"></script>
{% endif %}
<script>
requirejs.config({
config: {
'src/config': {
user: {
id: 1,
username: 'John Doe'
}
}
}
});
</script>
{% endblock %}
Здесь следует отметить, что клиентская часть имеет некоторую структуру в файловой системе и чтобы было меньше вопросов, вкратце о ней напишу. Исходники расположены в публичной директории web, а именно в директории web/app, которая имеет следующую структуру:
├── app.build.js ├── dist │ └── .gitkeep ├── main.js ├── specs │ └── .gitkeep ├── src │ ├── app.js │ └── config.js └── vendor
Добавим конфиг для RequireJS
web/app/main.js
requirejs.config({
baseUrl: 'app'
});
require([
'src/app',
'src/config'
], function (App, config) {
App.start(config);
});
Добавить конфиг для r.js оптимизатора
web/app/app.build.js
({
baseUrl: '.',
mainConfigFile: 'main.js',
wrapShim: true,
name: 'vendor/almond/almond',
include: 'main',
out: 'dist/main.js',
findNestedDependencies: true,
preserveLicenseComments: false
})
В файле example.build.js можно прочесть подробнее о каждом параметре.
Давайте напишем простое Hello-приложение:
web/app/src/app.js
define([
], function () {
'use strict';
var App = {};
App.start = function (config) {
console.log('Hello, ' + config.user.username + '!');
};
window.App = App;
return App;
});
web/app/src/config.js
define([
'module'
], function (module) {
'use strict';
return module.config();
});
Для Symfony2 могут потребоваться дополнительные правки в контроллере, в примере я использую Symfony Standard Edition.
Откроем в браузере главную страницу приложения, в консоле браузера должно появится сообщение «Hello, John Doe!».
Для сборки клиентской части для prod окружения нужно добавить в деплой следующую команду:
npm install
bower install
gulp build --env=prod
Также не забываем добавить в .gitignore следующие строки:
/bower_components/
/node_modules/
/web/app/dist/
/web/app/vendor/
/r.js
Готово!
P.S. В репозитории symfony-standard-requirejs на GitHub можно найти пример этого Hello-приложения.
Автор: IgorTimoshenko