Разработка больших приложений 

Vue

Webpack

Кирилл Кайсаров

github.com/markuplab

http://vuejs.org

http://webpack.github.io

Минутка ностальгии

Какими были Javascript приложения 

в период с 2006 по 2015 год

Write Less / Do More

Эра jQuery

$(element) - Основная структурная единица

1

Все манипуляции ориентированы на DOM ноды

Plugin основная единица экосистемы

2

Большинство популярных решений имели избыточный объем кода

Server Side Rendering

3

Получение отрендеренного html'a один из классических подходов эры

Model View Controller 

 ЭРА MVVC / MVC / VM / MC / MV / VC / ETC...

Plain JS Object - Основная архитектурная единица

1

new Constructor(), Object.prototype, Object.extend({}), итд...

Client-Side Rendering

2

Избавление сервера от дополнительной работы с шаблонами + гибкость

Триумф AMD

3

Загрузка модулей через AJAX, явные зависимости, и сборка на Java

Утром Data - Вечером HTML

4

Тренд на Dom-First Apps резко изменился на Data-First Apps

Наше время

 Компонентная эра

Компоненты как архитектурная единица

1

Теперь модуль это не только Javascript. Все asset'ы внутри.

Триумф CommonJS

2

NPM, Модули для сервера и браузера, browserify, изоморфность.

Реактивное и асинхронное программирование

3

Promise, Generators, NextTick, Pipes

Большая нагрузка на клиента - почва для оптимизаций

4

Ленивая загрузка, разделение приложения на части, benchmarking

Vue.JS

Основные принципы

Построен на принципах реактивного программирования

Основные принципы

1

Vue.nextTick(), Queue, Reactive Directives

Поддерживает концепцию Web Components

2

V-component, Vueify, Component Registry, Custom Directives

Сущность это простой (plain) Javascript объект

3

Template as HTML String, Component as Javascript Object

Единая сущность Vue

Основные принципы

4

Объект содержит в себе $data, $methods, $events итд...

Дружелюбен к модульным системам

5

Простая интеграция с Common.JS и другими модульными системами

Webpack

Основные принципы

Основные принципы

1

Module is Everything

HTML строка, CSS таблица, Javascript функция, нет разницы.

2

Только Javascript? Нет.

Webpack умеет собирать не только Javascript, также графику, шрифты итд...

3

Ленивая / Частичная загрузка из пакета

Важный инструмент для больших приложений без излишних расширений

Основные принципы

4

Ориентирован на Client-Side приложения

Hot Module Replacement, JSONP, и другие браузерные решения

5

AMD / CommonJS / Native

Нет разницы какую систему модулей вы используете

Какие задачи стоят

перед современными

Client-Side приложениями?

Технические требования приложения

Расширяется как в ширину так и в высоту

Приложение состоит из модулей

Приложение может быть интернационализировано

Приложение может быть менять свой внешний вид на разных платформах

Время для котикадинга

/** Module dependencies */
var Vue = require('vue');

/** Define application */
var app = module.exports = new Vue({
   el: '#v-app'
});

Точка входа в приложение

doctype html
html
  head
    title Simple application
  body#v-app

    script(src='/public/build.js')

Построим HTML Layout

$ sudo npm i webpack -g

Устанавливаем Webpack

module.exports = {
    entry: {
        app: "./components/app.js"
    },

    output: {
        path: "./public/build",
        filename: "app.js"
    }
}

Описываем webpack.config.js

$ webpack -p

Hash: f2f8823c3ad505796c0d
Version: webpack 1.4.15
Time: 3230ms
 Asset   Size  Chunks             Chunk Names
app.js  59934       0  [emitted]  app
    + 63 hidden modules

Собираем наше приложение

Создаем первый компонент

doctype html
html
  body#v-app
    #v-panel(v-component="panel")

Объявляем в Layout

Регистрируем компонент в нашем приложении

/** Define application */
var app = module.exports = new Vue({
   el: '#v-app',

  components: {
    panel : require('./panel')
  }
});

Описываем компонент

/** Define component */
module.exports = {
    template: '<div class="v-panel">Я панель по имени {{ firstName }} '
               + 'и фамилии <span v-html="lastName"></span></div>',

    data: function(){
      return { 
        firstName: 'Петя'  
      }
    },

    created: function() {
       this.$set('lastName', 'Петров');
    }
};

Создаем первый компонент

Создаем первый компонент

Результат

Капля оптимизации

/** Define component */
module.exports = {
    template: require('./template.html'),

    data: function(){
      return { 
        firstName: 'Петя'  
      }
    },

    created: function() {
       this.$set('lastName', 'Петров');
    }
};

Создаем первый компонент

/** Define component */
module.exports = {
    // Как загрузить HTML в require?!
    template: require('./template.html')
};

Опишем webpack.config.js

module.exports = {
    module: {
        loaders: [
            { test: /\.html$/, loader: "html" }
        ]
    }
};

Создаем первый компонент

Капля оптимизации

Webpack Loaders

Загрузчики это функции которые выполняют трансформацию файлов в модули приложения. Аналогичны browserify transform.

http://webpack.github.io/docs/using-loaders.html

/** Define component assets */
require('./styles.less')

/** Define component */
module.exports = {
    template: require('./template.html')
};

Дополним наш webpack.config.js загрузчиком стилей

module: {
    loaders: [
        { test: /\.html$/, loader: "html" },
        { test: /\.less$/, loader: "style!css!less" }
    ]
}

Создаем первый компонент

Результат template.html

module.exports = "<div class=\"v-panel\">\n  Я панель по имени {{ firstName }} 
                  и фамилии\n 
                  <span v-html=\"lastName\"></span>\n</div>\n";
/***/ 
    function(module, exports, __webpack_require__) {
    exports = module.exports = __webpack_require__(32)();
    exports.push([module.id, "span {\n  color: #999;\n}\n", ""]);
/***/ }

Результат styles.less

Создаем первый компонент

Создаем первый компонент

Результат

Технические требования приложения

Расширяется как в ширину так и в высоту

Приложение состоит из модулей

Приложение может быть интернационализировано

Приложение может быть менять свой внешний вид на разных платформах

Добавим немного динамики

Controller - компонент отвечающий за состояние приложения

Содержит в себе все нужные для работы компоненты

1

Является точкой разрыва приложения. App -> Controller -> Components

2

Controller определяется в зависимости от состояния URL

3

Схема сбора и запуска приложения

Client

App

URL

State

Controller

Component

Component

Component

Controller

Controller

Контроллер - место где наше приложение начинает ответвляться от приложения. Именно тут мы можем разделить наше приложение на части.

Для решения этой задачи подойдет require.ensure

представленный в CommonJS спецификации. 

Webpack поддерживает этот метод.

http://wiki.commonjs.org/wiki/Modules/Async/A

require.ensure(['increment'], function(require) {
    var inc = require('increment').inc;
    var a = 1;
    inc(a); // 2
});

Синтаксис:

Создадим явную карту наших контроллеров

/** Routes map */
var map = {
  main: require('./controllers/main'),
  alt: require('./controllers/alt')
};

Создаем контроллеры

Проблематика:

Основной проблематикой клиентских загрузчиков модулей является "неумение" работать с динамическими аргументами require(). В текущих реализациях мы вынуждены указывать имена модулей явно. Node.JS умеет подтягивать модули динамически.

Делаем нашу карту модулей ленивой

/** Routes map */
var map = {
  main: require('promise?bluebird!./controllers/main'),
  alt: require('promise?bluebird!./controllers/alt')
};

Создаем контроллеры

Альтернативный вариант вызова Webpack Loaders

Для трансформации модулей можно использовать дополнительные строковые параметры внутри require() отделив их через знак "!". В указаном выше случае мы загружаем модуль пропуская его через promise-loader.

Немного о Promise-Loader

Создаем контроллеры

Трансформация этим модулем позволяет сделать отложенный require.ensure и обернуть его в Promise объект.

// Указываем библиотеку для обертки в Promise
var load = require("promise?bluebird!./file.js");
 
// Модуль не будет загружен пока вы не вызовете функцию
load().then(function(module) {
    // Здесь вы можете работать с вашим модулем
});

Контроллеры выделены в отдельные файлы

$ webpack
Hash: 5fa98cfa191aa5dafdb4
Version: webpack 1.5.3
Time: 827ms
 Asset    Size  Chunks             Chunk Names
app.js  349774       0  [emitted]  app
1.1.js     287       1  [emitted]  
2.2.js     294       2  [emitted]  
    + 78 hidden modules

Создаем контроллеры

Результат

Создаем контроллеры

Указываем элемент в котором расположим контроллер

doctype html
html
  head
    title Simple application

  body#v-app
    #v-panel(v-component="panel")
    #v-controller(v-el="controller")

    script(src='/public/build.js')

Создаем контроллеры

Загружаем и запускаем контроллер

module.exports = new Vue({
  el: '#v-app',

  created: function() {
    var controller = url.getController();
    var load = map[controller]();

    load.then(function (module) {
      var ctrl = new Vue(module);
      ctrl.$mount(this.$$.controller);
    }.bind(this));
  }

  ...
});

Создаем контроллеры

Связать управление контроллерами с HTML5 History API

Следующие шаги

1

Состояние приложения можно хранить в this.$data

Спроектировать ленивую загрузку внутри контроллера

2

Актуально для больших контроллеров

3

Как инструмент дополнительной оптимизации

Использовать ApplicationCache и Deduplication Plugin

Технические требования приложения

Расширяется как в ширину так и в высоту

Приложение состоит из модулей

Приложение может быть интернационализировано

Приложение может быть менять свой внешний вид на разных платформах

module.exports = {
  template: require('./templates/' + platform + '.html')
};

Динамическая загрузка модулей

- templates
    - ios.html
    - android.html
    - windows.html

Отлично подходит для кросс-платформенных решений

Другие возможности

Динамическая загрузка модулей

Другие возможности

При такой загрузке в сборку попадут все модули из папки ./templates с форматом .html, а также внутри загрузчика появиться карта соответствия имени файла, id'шнику модуля.

Технические требования приложения

Расширяется как в ширину так и в высоту

Приложение состоит из модулей

Приложение может быть интернационализированно

Приложение может быть менять свой внешний вид на разных платформах

var messages = require("json!po!./locale/en_US/LC_MESSAGES/messages.po");

Загрузка интернациональных пакетов

Другие полезные загрузчики

- autoprefixer
- mocha
- handlebars
- 6to5
- file
- json

Другие возможности

Технические требования приложения

Расширяется как в ширину так и в высоту

Приложение состоит из модулей

Приложение может быть интернационализировано

Приложение может быть менять свой внешний вид на разных платформах

Вопросы???

deck

By Kirill Kaysarov