Как просто подружить Symfony2 и RequireJS на примере SPA

Ранее уже была публикация, которая демонстрировала как использовать 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 на GitHub можно найти пример этого Hello-приложения.
Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 4
  • 0
    Справедливости ради стоит заметить что этот же способ с небольшими изменениями подходит и для вариантов с browserify (согласитесь, commonjs поудобнее amd в плане использования) или es6 (с traceur или просто module-transpiler если только модули используются).

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

    Может по этому вопросу будет чего посоветовать?
    • 0
      Что касается инжекта инлайн-скриптов, могу лишь посоветовать избегать их по возможности. Я стараюсь делать так, чтобы связка клиентской части с серверной была только через лейаут, который раздает клиентскую часть. Как-то так :)
    • 0
      Было бы неплохо отредатировать README.md — убрать стандартный текст и добавить нужное про RequireJS

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.