SlideShare a Scribd company logo

Перепись приложения. Нативного. На JS. Done. | Odessa Frontend Meetup #10

KeepSolid Sign — приложение для подписи документов под все популярные платформы. Его ядро написано на C++ и однажды поступила задача сделать веб-версию. Тимофей Лавренюк рассказывает о том, через что получилось пройти, чтобы в итоге сделать веб-приложение не хуже нативного.

1 of 217
Download to read offline
Перепись приложения. Нативного.Перепись приложения. Нативного.
На JS. DoneНа JS. Done
Тимофей Лавренюк
@geek_timofey
1
О себеО себе
2
О проектеО проекте
3 . 1
3 . 2
Архитектура
3 . 3
Архитектура
3 . 3
ЯдроЯдро
Архитектура
3 . 3
ЯдроЯдро
RPC ServerRPC ServerАрхитектура
3 . 3
ЯдроЯдро
А что умеет?А что умеет?
3 . 4
ЯдроЯдро
Работа с локальной БДА что умеет?А что умеет?
3 . 4
ЯдроЯдро
ШифрованиеРабота с локальной БДА что умеет?А что умеет?
3 . 4
ЯдроЯдро
ШифрованиеРабота с локальной БД
Общение с RPC-сервером
А что умеет?А что умеет?
3 . 4
ЯдроЯдро
ШифрованиеРабота с локальной БД
Работа с PDFОбщение с RPC-сервером
А что умеет?А что умеет?
3 . 4
Настал 2017 годНастал 2017 год
3 . 5
Настал 2017 годНастал 2017 год
Нужна web-версия...
3 . 5
СРОЧНО НУЖНАСРОЧНО НУЖНА
WEB-ВЕРСИЯ !!!11WEB-ВЕРСИЯ !!!11 3 . 6
3 . 7
WEB-ВерсияWEB-Версия
1.01.0
3 . 8
Старая архитектура
3 . 9
SPASPA
Старая архитектура
3 . 9
SPASPA
REST APIREST API
ЯдроЯдро
Старая архитектура
3 . 9
SPASPA
REST APIREST API
ЯдроЯдро
RPC ServerRPC ServerСтарая архитектура
3 . 9
Старая WEB-версия
3 . 10
Не было режима автораНе было режима автора
Старая WEB-версия
3 . 10
Не было режима автораНе было режима автора
Зависило от интернетЗависило от интернет
соединениясоединения
Старая WEB-версия
3 . 10
Не было режима автораНе было режима автора
Зависило от интернетЗависило от интернет
соединениясоединения
Был PHPБыл PHP
Старая WEB-версия
3 . 10
3 . 11
Полноценное web-приложениеПолноценное web-приложение
Задача:Задача:
3 . 12
Полноценное web-приложениеПолноценное web-приложение
Задача:Задача:
Не уступающее нативным клиентам
3 . 12
3 . 13
Увидел PHP
3 . 14
Пришлось переписать
веб приложение
3 . 15
Новая WEB-версия
3 . 16
Есть режим автораЕсть режим автора
Новая WEB-версия
3 . 16
Есть режим автораЕсть режим автора
Работает в OfflineРаботает в Offline
Новая WEB-версия
3 . 16
Есть режим автораЕсть режим автора
Работает в OfflineРаботает в Offline
Не уступает нативнымНе уступает нативным
клиентамклиентам
Новая WEB-версия
3 . 16
Криптография
Offline + Web Worker Работа с PDFОбщение с RPC-сервером
Framework Progressive Web App
4
КриптографияКриптография
5 . 1
Основная проблемаОсновная проблема
5 . 2
Основная проблемаОсновная проблема
Никто не знает как работает шифрование в проекте
5 . 2
5 . 3
5 . 4
5 . 5
RSARSA
AESAES
Основные алгоритмы
5 . 6
RSARSA
5 . 7
AESAES
cipher = encrypt(block, key) // шифруем block с помощью key
block = decrypt(cipher, key) // расшифровываем cipher с помощью key
5 . 8
Как реализовать алгоритмыКак реализовать алгоритмы
шифрования?шифрования?
5 . 9
5 . 10
JS библиотека
5 . 10
JS библиотека
Web Crypto API
5 . 10
JS библиотека
Web Crypto API
OpenSSL -> WebAssembly
5 . 10
JS библиотекаJS библиотека
5 . 11
JS библиотекаJS библиотека
+ Изоморфный код
5 . 11
JS библиотекаJS библиотека
+ Поддержка RSA
+ Изоморфный код
5 . 11
JS библиотекаJS библиотека
+ Поддержка RSA
+ Изоморфный код
- Производительность
5 . 11
JS библиотекаJS библиотека
+ Поддержка RSA
+ Изоморфный код
- Производительность
- Размер
5 . 11
Web Crypto APIWeb Crypto API
5 . 12
Web Crypto APIWeb Crypto API
+ Размер
5 . 12
Web Crypto APIWeb Crypto API
+ Размер
+ Производительность
5 . 12
Web Crypto APIWeb Crypto API
- Неполная поддержка RSA
+ Размер
+ Производительность
5 . 12
Web Crypto APIWeb Crypto API
- Неполная поддержка RSA
- Изоморфный код
+ Размер
+ Производительность
5 . 12
Алгоритм PBKDF2Алгоритм PBKDF2
5 . 13
OpenSSL -> WebAssemblyOpenSSL -> WebAssembly
5 . 14
OpenSSL -> WebAssemblyOpenSSL -> WebAssembly
+ Изоморфный код
5 . 14
OpenSSL -> WebAssemblyOpenSSL -> WebAssembly
+ Изоморфный код
+ Производительность
5 . 14
OpenSSL -> WebAssemblyOpenSSL -> WebAssembly
+ Изоморфный код
+ Производительность
- Размер
5 . 14
JS библиотекаJS библиотека
5 . 15
Node-ForgeNode-Forge
JS библиотекаJS библиотека
5 . 15
Node-ForgeNode-Forge
JS библиотекаJS библиотека
Про улучшение оптимизации - в следующих слайдах...
5 . 15
NodeForge FailsNodeForge Fails
5 . 16
Offline + Web WorkerOffline + Web Worker
6 . 1
Что хранить в Offline?Что хранить в Offline?
6 . 2
СтатикаСтатика
Что хранить в Offline?Что хранить в Offline?
6 . 2
СтатикаСтатика
Что хранить в Offline?Что хранить в Offline?
6 . 2
СтатикаСтатика
Данные пользователяДанные пользователя
Что хранить в Offline?Что хранить в Offline?
6 . 2
СтатикаСтатика
Данные пользователяДанные пользователя
Что хранить в Offline?Что хранить в Offline?
6 . 2
СтатикаСтатика
Данные пользователяДанные пользователя
ФайлыФайлы
Что хранить в Offline?Что хранить в Offline?
6 . 2
СтатикаСтатика
Данные пользователяДанные пользователя
ФайлыФайлы
Что хранить в Offline?Что хранить в Offline?
6 . 2
СтатикаСтатика
7 . 1
Service WorkerService Worker
7 . 2
7 . 3
7 . 3
{
"globDirectory": "dist",
"globPatterns": [
"index.html",
"*.js",
"assets/**/*.{png,svg}",
"assets/*.{png,svg}"
],
"swSrc": "src/service-workers/main.worker.js",
"swDest": "dist/service-worker.js"
}
workbox.config.js
7 . 4
{
"assets": [
"src/assets",
"src/service-workers",
"src/manifest.json",
{
"glob": "workbox-sw.js",
"input": "node_modules/workbox-sw/build",
"output": "./workbox-3.5.0"
},
{
"glob": "workbox-core.dev.js",
"input": "node_modules/workbox-core/build/",
"output": "./workbox-3.5.0"
},
{
"glob": "workbox-precaching.dev.js",
"input": "node_modules/workbox-precaching/build/",
"output": "./workbox-3.5.0"
}
],
}
angular.json
7 . 5
importScripts('workbox-3.5.0/workbox-sw.js');
workbox.setConfig({
debug: true,
modulePathPrefix: 'workbox-3.5.0/'
});
workbox.skipWaiting();
workbox.clientsClaim();
workbox.precaching.precacheAndRoute([]);
main.worker.js
7 . 6
workbox injectManifest
7 . 7
workbox injectManifest
workbox.precaching.precacheAndRoute([
{
"url": "index.html",
"revision": "4f8109353581284e76b88e568d642376"
},
{
"url": "0.js",
"revision": "2af14762103b4c1f18e620bd30a53d2e"
},
{
"url": "main.js",
"revision": "ef2fb7f5913e6614c9ee3621ad299d1b"
}
])
service-worker.js
7 . 7
СтатикаСтатика
7 . 8
Данные пользователяДанные пользователя
8 . 1
Service WorkerService Worker
8 . 2
Persistent StoragePersistent Storage
8 . 3
8 . 4
LocalStorageLocalStorage
8 . 4
LocalStorageLocalStorage Небольшой лимит
8 . 4
File APIFile API
LocalStorageLocalStorage Небольшой лимит
8 . 4
File APIFile API FileWriter deprecated
LocalStorageLocalStorage Небольшой лимит
8 . 4
File APIFile API FileWriter deprecated
CookiesCookies
LocalStorageLocalStorage Небольшой лимит
8 . 4
File APIFile API FileWriter deprecated
CookiesCookies WATTT??
LocalStorageLocalStorage Небольшой лимит
8 . 4
 CacheStorage CacheStorage
File APIFile API FileWriter deprecated
CookiesCookies WATTT??
LocalStorageLocalStorage Небольшой лимит
8 . 4
 CacheStorage CacheStorage Нет гибкости
File APIFile API FileWriter deprecated
CookiesCookies WATTT??
LocalStorageLocalStorage Небольшой лимит
8 . 4
 CacheStorage CacheStorage Нет гибкости
File APIFile API FileWriter deprecated
CookiesCookies WATTT??
WebSQLWebSQL
LocalStorageLocalStorage Небольшой лимит
8 . 4
 CacheStorage CacheStorage Нет гибкости
File APIFile API FileWriter deprecated
CookiesCookies WATTT??
WebSQLWebSQL Deprecated
LocalStorageLocalStorage Небольшой лимит
8 . 4
 CacheStorage CacheStorage Нет гибкости
File APIFile API FileWriter deprecated
CookiesCookies WATTT??
WebSQLWebSQL Deprecated
IndexedDBIndexedDB
LocalStorageLocalStorage Небольшой лимит
8 . 4
 CacheStorage CacheStorage Нет гибкости
File APIFile API FileWriter deprecated
CookiesCookies WATTT??
WebSQLWebSQL Deprecated
IndexedDBIndexedDB
Большой лимит
Хорошая поддержка
Не Deprecated
LocalStorageLocalStorage Небольшой лимит
8 . 4
IndexedDBIndexedDB
8 . 5
IndexedDBIndexedDB
объектно-ориентированная база данныхобъектно-ориентированная база данных
8 . 5
IndexedDBIndexedDB
объектно-ориентированная база данныхобъектно-ориентированная база данных
хранит обьекты, проиндексированные с ключомхранит обьекты, проиндексированные с ключом
8 . 5
IndexedDBIndexedDB
объектно-ориентированная база данныхобъектно-ориентированная база данных
хранит обьекты, проиндексированные с ключомхранит обьекты, проиндексированные с ключом
выполнение операций происходит асинхронновыполнение операций происходит асинхронно
8 . 5
IndexedDBIndexedDB
объектно-ориентированная база данныхобъектно-ориентированная база данных
хранит обьекты, проиндексированные с ключомхранит обьекты, проиндексированные с ключом
выполнение операций происходит асинхронновыполнение операций происходит асинхронно
умеет хранить JS-объекты и блобыумеет хранить JS-объекты и блобы
8 . 5
IndexedDBIndexedDB
объектно-ориентированная база данныхобъектно-ориентированная база данных
хранит обьекты, проиндексированные с ключомхранит обьекты, проиндексированные с ключом
выполнение операций происходит асинхронновыполнение операций происходит асинхронно
имеет жутко неудобное низкоуровневое APIимеет жутко неудобное низкоуровневое API
умеет хранить JS-объекты и блобыумеет хранить JS-объекты и блобы
8 . 5
https://caniuse.bitsofco.de/embed/index.html?
feat=indexeddb&periods=future_2,future_1,current&accessible-
colours=false
ПоддержкаПоддержка
8 . 6
DebugDebug 8 . 7
IndexedDB в боюIndexedDB в бою
9 . 1
9 . 2
1. Открыть или создать базу
9 . 2
1. Открыть или создать базу
var open = indexedDB.open("MyDatabase", 1);
9 . 2
1. Открыть или создать базу
var open = indexedDB.open("MyDatabase", 1);
2. Создать Schema
9 . 2
1. Открыть или создать базу
var open = indexedDB.open("MyDatabase", 1);
2. Создать Schema
open.onupgradeneeded = () => {
const db = open.result;
const store = db.createObjectStore("MyObjectStore", {keyPath: "id"});
const index = store.createIndex("NameIndex", ["name.last", "name.first"]);
};
9 . 2
9 . 3
3. Создать транзакцию
9 . 3
3. Создать транзакцию
open.onsuccess = () => {
const db = open.result
const tx = db.transaction("MyObjectStore", "readwrite")
const store = tx.objectStore("MyObjectStore");
const index = store.index("NameIndex");
}
9 . 3
4. Запросить данные
3. Создать транзакцию
open.onsuccess = () => {
const db = open.result
const tx = db.transaction("MyObjectStore", "readwrite")
const store = tx.objectStore("MyObjectStore");
const index = store.index("NameIndex");
}
9 . 3
4. Запросить данные
const getJohn = store.get(12345); // по id
const getBob = index.get(["Smith", "Bob"]); // через index
3. Создать транзакцию
open.onsuccess = () => {
const db = open.result
const tx = db.transaction("MyObjectStore", "readwrite")
const store = tx.objectStore("MyObjectStore");
const index = store.index("NameIndex");
}
9 . 3
9 . 4
5. Получить данные
9 . 4
5. Получить данные
getJohn.onsuccess = () => {
console.log(getJohn.result.name.first); // => "John"
};
getBob.onsuccess = () => {
console.log(getBob.result.name.first); // => "Bob"
};
9 . 4
5. Получить данные
getJohn.onsuccess = () => {
console.log(getJohn.result.name.first); // => "John"
};
getBob.onsuccess = () => {
console.log(getBob.result.name.first); // => "Bob"
};
5. Закрыть транзакцию
9 . 4
5. Получить данные
getJohn.onsuccess = () => {
console.log(getJohn.result.name.first); // => "John"
};
getBob.onsuccess = () => {
console.log(getBob.result.name.first); // => "Bob"
};
5. Закрыть транзакцию
tx.oncomplete = function() {
db.close();
};
9 . 4
Boilerblate!!!1Boilerblate!!!1
9 . 5
Недостатки чистого IndexedDBНедостатки чистого IndexedDB
9 . 6
Недостатки чистого IndexedDBНедостатки чистого IndexedDB
Тяжело поддерживать
9 . 6
Недостатки чистого IndexedDBНедостатки чистого IndexedDB
Тяжело поддерживать
Не поддержки JOIN'ов
9 . 6
Недостатки чистого IndexedDBНедостатки чистого IndexedDB
Тяжело поддерживать
Не поддержки JOIN'ов
Нельзя частично обновить документ
9 . 6
Недостатки чистого IndexedDBНедостатки чистого IndexedDB
Тяжело поддерживать
Не поддержки JOIN'ов
Нельзя частично обновить документ
Скудная сортировка
9 . 6
Недостатки чистого IndexedDBНедостатки чистого IndexedDB
Тяжело поддерживать
Не поддержки JOIN'ов
Нельзя частично обновить документ
Скудная сортировка
Auto-commit транзакций
9 . 6
"Никто не использует IndexedDB в чистом виде"
- Все JS-разработчики
9 . 7
"Никто не использует IndexedDB в чистом виде"
- Все JS-разработчики
https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API 9 . 7
9 . 8
БиблиотекиБиблиотеки
ОберткиОбертки
9 . 8
БиблиотекиБиблиотеки
ОберткиОбертки
DB EngineDB Engine
9 . 8
БиблиотекиБиблиотеки
ОберткиОбертки
Idb
ZangoDb
MiniMongo
jsStore
PouchDB
Dexie
LocalForage
DB EngineDB Engine
9 . 8
БиблиотекиБиблиотеки
ОберткиОбертки
Idb
ZangoDb
MiniMongo
jsStore
PouchDB
Dexie
LocalForage
DB EngineDB Engine
YDN-DB
AlaSQL
Lovefield
9 . 8
9 . 9
9 . 10
КроссбраузерностьКроссбраузерность
9 . 10
КроссбраузерностьКроссбраузерность
Chrome
Firefox
IE 11+, Edge
Safari 10+
9 . 10
SQL-подобный APISQL-подобный APIКроссбраузерностьКроссбраузерность
Chrome
Firefox
IE 11+, Edge
Safari 10+
9 . 10
SQL-подобный APISQL-подобный API
select, insert, update, delete
group by, order by, limit, skip
join
КроссбраузерностьКроссбраузерность
Chrome
Firefox
IE 11+, Edge
Safari 10+
9 . 10
SQL-подобный APISQL-подобный API
select, insert, update, delete
group by, order by, limit, skip
join
КроссбраузерностьКроссбраузерность
Chrome
Firefox
IE 11+, Edge
Safari 10+
Отличная производительностьОтличная производительность
Оптимизация и анализ запросов
9 . 10
Создание SchemaСоздание Schema
9 . 11
Создание SchemaСоздание Schema
const schemaBuilder = lf.schema.create('KEEPSOLID_SIGN_DB', 1.0);
schemaBuilder.createTable('Documents')
.addColumn('id', lf.Type.STRING)
.addColumn('parentId', lf.Type.OBJECT)
.addColumn('type', lf.Type.STRING)
.addColumn('signOrder', lf.Type.NUMBER)
.addColumn('encryptionKey', lf.Type.STRING)
.addIndex('idxSignOrder', ['signOrder'], false, lf.Order.DESC);
.addPrimaryKey(['id']);
schemaBuilder.connect().then((db) => {
// Можно работать с базой
});
9 . 11
Создание SchemaСоздание Schema
const schemaBuilder = lf.schema.create('KEEPSOLID_SIGN_DB', 1.0);
schemaBuilder.createTable('Documents')
.addColumn('id', lf.Type.STRING)
.addColumn('parentId', lf.Type.OBJECT)
.addColumn('type', lf.Type.STRING)
.addColumn('signOrder', lf.Type.NUMBER)
.addColumn('encryptionKey', lf.Type.STRING)
.addIndex('idxSignOrder', ['signOrder'], false, lf.Order.DESC);
.addPrimaryKey(['id']);
schemaBuilder.connect().then((db) => {
// Можно работать с базой
});
9 . 11
Типы полейТипы полей
9 . 12
Типы полейТипы полей
String
9 . 12
Типы полейТипы полей
String
Number
9 . 12
Типы полейТипы полей
String
Number
Integer (32bit)
9 . 12
Типы полейТипы полей
String
Number
Integer (32bit)
Boolean
9 . 12
Типы полейТипы полей
String
Number
Integer (32bit)
Boolean
Object
9 . 12
Типы полейТипы полей
String
Number
Integer (32bit)
Boolean
Object
Date
9 . 12
Типы полейТипы полей
String
Number
Integer (32bit)
Boolean
Object
Date
Array Buffer
9 . 12
SQL-подобный APISQL-подобный API
SQL Lovefield
9 . 13
SQL-подобный APISQL-подобный API
SQL Lovefield
SELECT *
FROM Documents
WHERE type = "TEMPLATE"
9 . 13
SQL-подобный APISQL-подобный API
SQL Lovefield
SELECT *
FROM Documents
WHERE type = "TEMPLATE"
Database
.select()
.from(document)
.where(document.type.eq('TEMPLATE'))
.exec()
9 . 13
SQL-подобный APISQL-подобный API
SQL Lovefield
SELECT *
FROM Documents
WHERE type = "TEMPLATE"
Database
.select()
.from(document)
.where(document.type.eq('TEMPLATE'))
.exec()
SELECT encryptionKey
FROM Documents
WHERE signOrder >= 1
ORDER BY signOrder DESC
LIMIT 10
9 . 13
SQL-подобный APISQL-подобный API
SQL Lovefield
SELECT *
FROM Documents
WHERE type = "TEMPLATE"
Database
.select()
.from(document)
.where(document.type.eq('TEMPLATE'))
.exec()
SELECT encryptionKey
FROM Documents
WHERE signOrder >= 1
ORDER BY signOrder DESC
LIMIT 10
Database
.select(document.encryptionKey)
.from(document)
.where(document.signOrder.gte(1))
.orderBy(document.signOrder, lf.Order.DESC)
.limit(10)
.exec()
9 . 13
SQL Lovefield
9 . 14
SQL Lovefield
SELECT *
FROM Documents d, Files f
WHERE d.fileId = f.id
AND d.id = '123'
9 . 14
SQL Lovefield
SELECT *
FROM Documents d, Files f
WHERE d.fileId = f.id
AND d.id = '123'
Database
.select()
.from(document, file)
.where(lf.op.and(
document.fileId.eq(file.id),
document.id.eq('123'),
))
.exec()
9 . 14
SQL Lovefield
SELECT *
FROM Documents d, Files f
WHERE d.fileId = f.id
AND d.id = '123'
Database
.select()
.from(document, file)
.where(lf.op.and(
document.fileId.eq(file.id),
document.id.eq('123'),
))
.exec()
SELECT * FROM document
INNER JOIN file
ON document.fileId = file.id
WHERE document.id = '123'
9 . 14
SQL Lovefield
SELECT *
FROM Documents d, Files f
WHERE d.fileId = f.id
AND d.id = '123'
Database
.select()
.from(document, file)
.where(lf.op.and(
document.fileId.eq(file.id),
document.id.eq('123'),
))
.exec()
SELECT * FROM document
INNER JOIN file
ON document.fileId = file.id
WHERE document.id = '123'
Database
.select()
.from(document)
.innerJoin(
file,
document.fileId.eq(file.id)
)
.where(document.id.eq('123'))
.exec()
9 . 14
function idb_and (index1, keyRange1, index2, keyRange2, onfound, onfinish) {
var openCursorRequest1 = index1.openCursor(keyRange1);
var openCursorRequest2 = index2.openCursor(keyRange2);
assert(index1.objectStore === index2.objectStore);
var primKey = index1.objectStore.keyPath;
var set = {};
var resolved = 0;
function complete() {
if (++resolved === 2) onfinish();
}
function union(item) {
var key = JSON.stringify(item[primKey]);
if (!set.hasOwnProperty(key)) {
set[key] = true;
onfound(item);
}
}
openCursorRequest1.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
union(cursor.value);
} else {
complete();
}
}
openCursorRequest2.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
union(cursor.value);
} else {
complete();
}
}
}
Чистый API
9 . 15
function idb_and (index1, keyRange1, index2, keyRange2, onfound, onfinish) {
var openCursorRequest1 = index1.openCursor(keyRange1);
var openCursorRequest2 = index2.openCursor(keyRange2);
assert(index1.objectStore === index2.objectStore);
var primKey = index1.objectStore.keyPath;
var set = {};
var resolved = 0;
function complete() {
if (++resolved === 2) onfinish();
}
function union(item) {
var key = JSON.stringify(item[primKey]);
if (!set.hasOwnProperty(key)) {
set[key] = true;
onfound(item);
}
}
openCursorRequest1.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
union(cursor.value);
} else {
complete();
}
}
openCursorRequest2.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
union(cursor.value);
} else {
complete();
}
}
}
Чистый API
9 . 15
Интересные особенностиИнтересные особенности
9 . 16
Интересные особенностиИнтересные особенности
Позволяет задавать Schema в YAML (SPAC)
9 . 16
Интересные особенностиИнтересные особенности
Позволяет задавать Schema в YAML (SPAC)
Типы Storage: IndexedDB, Memory, Firebase
9 . 16
Интересные особенностиИнтересные особенности
Позволяет задавать Schema в YAML (SPAC)
Типы Storage: IndexedDB, Memory, Firebase
Поддерживает Import/Export в Javascript-объект
9 . 16
Интересные особенностиИнтересные особенности
Позволяет задавать Schema в YAML (SPAC)
Типы Storage: IndexedDB, Memory, Firebase
Поддерживает Import/Export в Javascript-объект
Поддерживает Data Observation
9 . 16
Data ObservationData Observation
9 . 17
Data ObservationData Observation
var query = db.select()
.from(documents)
.where(documents.id.eq('1'));
var handler = function(changes) {
// Будет вызываться всегда, когда происходит изменение данных
};
db.observe(query, handler);
db.update(documents)
.set(documents.title, 'New Title')
.where(documents.id.eq('1'))
.exec();
db.unobserve(query, handler);
9 . 17
НюансыНюансы
9 . 18
Изменение SchemaИзменение Schema
9 . 19
Вывод ошибокВывод ошибок
9 . 20
Вывод ошибокВывод ошибок
Constraint error: (202) Attempted to insert NULL value to non-nullable field
Documents.signOrder.
9 . 20
Хранение файловХранение файлов
10 . 1
10 . 2
ArrayBuffer
10 . 2
Offline + Web WorkerOffline + Web Worker
11 . 1
11 . 2
FrameworkFramework
12 . 1
АрхитектураАрхитектура
12 . 2
Redux + Side Effects + Entities + Selectors
12 . 3
Side EffectsSide Effects
@Effect()
documentCreate$ = this.actions$.pipe(
ofType(DocumentActions.Types.DocumentCreate),
map(action => action.payload.query),
exhaustMap(query =>
this.dbService.createDocument(query)
),
flatMap(() => [
new DocumentAction.DocumentCreateSuccess();
new DocumentAction.GenerateDocumentPreview();
])
);
12 . 4
EntitiesEntities
export interface Document {
id: string;
name: string;
}
export interface State extends EntityState<Document> {
activeDocumentId: number | null;
}
12 . 5
EntitiesEntities
export interface Document {
id: string;
name: string;
}
export interface State extends EntityState<Document> {
activeDocumentId: number | null;
}
export const adapter: EntityAdapter<Document> = createEntityAdapter<Document>();
12 . 5
EntitiesEntities
export interface Document {
id: string;
name: string;
}
export interface State extends EntityState<Document> {
activeDocumentId: number | null;
}
export const adapter: EntityAdapter<Document> = createEntityAdapter<Document>();
12 . 5
Entity adapterEntity adapter
addOne
addMany
addAll
removeOne
removeMany
removeAll
updateOne
updateMany
upsertOne
upsertMany
map
12 . 6
SelectorsSelectors
export const getDocumentState = (state: AppState) => state.documents;
export const getDocumentEntities = (state: DocumentState) => state.entities;
export const getDocumentEntities = createSelector(
getDocumentState,
getDocumentEntities
);
export const getAllDocuments = createSelector(getDocumentEntities, entities =>
Object.keys(entities).map(id => entities[id])
);
12 . 7
BoilerplateBoilerplate
12 . 8
SchematicsSchematics
ng generate action
store
entity
reducer
effect
container
feature
12 . 9
class GetDocumentPreviewAction implements IDocumentAction {
readonly type = DocumentActionTypes.GetDocumentPreview;
documentType: DocumentType;
constructor(public payload: any) { }
}
class GetDocumentPreviewSuccessAction implements IDocumentAction {
readonly type = DocumentActionTypes.GetDocumentPreviewSuccess;
documentType: DocumentType;
constructor(public payload: IFileWithData) { }
}
class GetDocumentPreviewFailureAction implements IDocumentAction {
readonly type = DocumentActionTypes.GetDocumentPreviewFailure;
documentType: DocumentType;
}
12 . 10
https://github.com/pelotom/unionize
import { unionize, ofType, UnionOf } from 'unionize';
const DocumentActions = unionize({
GET_DOCUMENT_PREVIEW: ofType<{ id: string }>(),
GET_DOCUMENT_PREVIEW_SUCCESS: ofType<IFileWithData>()
GET_DOCUMENT_PREVIEW_FAILURE: {}
});
type DocumentAction = UnionOf<typeof DocumentActions>;
12 . 11
Коммуникация с Web WorkerКоммуникация с Web Worker
12 . 12
12 . 13
Effect
12 . 13
Effect
Action
12 . 13
Effect
Action
ReduxRedux
12 . 13
Effect
Action
ReduxRedux
12 . 13
Effect
Action
ReduxRedux ComutterComutter
12 . 13
Effect
Action
ReduxRedux ComutterComutter
12 . 13
Effect
Action
ReduxRedux Web WorkerComutterComutter
12 . 13
Effect
Action
ReduxRedux Web WorkerComutterComutter
12 . 13
Effect
Action
ReduxRedux Web Worker
Success/Failure
Action
ComutterComutter
12 . 13
Effect
Action
ReduxRedux Web Worker
Success/Failure
Action
ComutterComutter
12 . 13
Redux Devtools
12 . 14
ВыводыВыводы
13 . 1
13 . 2
Спасибо за вниманиеСпасибо за внимание
Тимофей Лавренюк
@geek_timofey
14

Recommended

ORM технологии в .NET (Nhibernate, Linq To SQL, Entity Framework)
ORM технологии в .NET (Nhibernate, Linq To SQL, Entity Framework)ORM технологии в .NET (Nhibernate, Linq To SQL, Entity Framework)
ORM технологии в .NET (Nhibernate, Linq To SQL, Entity Framework)Pavel Tsukanov
 
Инфраструктура распределенных приложений на nodejs / Станислав Гуменюк (Rambl...
Инфраструктура распределенных приложений на nodejs / Станислав Гуменюк (Rambl...Инфраструктура распределенных приложений на nodejs / Станислав Гуменюк (Rambl...
Инфраструктура распределенных приложений на nodejs / Станислав Гуменюк (Rambl...Ontico
 
Безопасность Node.js / Илья Вербицкий (Независимый консультант)
Безопасность Node.js / Илья Вербицкий (Независимый консультант)Безопасность Node.js / Илья Вербицкий (Независимый консультант)
Безопасность Node.js / Илья Вербицкий (Независимый консультант)Ontico
 
Micro orm для жизни. Кожевников Дмитрий D2D Just.NET
Micro orm для жизни. Кожевников Дмитрий D2D Just.NETMicro orm для жизни. Кожевников Дмитрий D2D Just.NET
Micro orm для жизни. Кожевников Дмитрий D2D Just.NETDev2Dev
 
Application Security - ответы на ежедневные вопросы / Сергей Белов (Mail.Ru G...
Application Security - ответы на ежедневные вопросы / Сергей Белов (Mail.Ru G...Application Security - ответы на ежедневные вопросы / Сергей Белов (Mail.Ru G...
Application Security - ответы на ежедневные вопросы / Сергей Белов (Mail.Ru G...Ontico
 
Node.js введение в технологию, КПИ #ITmeetingKPI
Node.js введение в технологию, КПИ  #ITmeetingKPINode.js введение в технологию, КПИ  #ITmeetingKPI
Node.js введение в технологию, КПИ #ITmeetingKPITimur Shemsedinov
 
Чеклист по клиентской оптимизации - Лавлинский Николай, РИТ++ 2017
Чеклист по клиентской оптимизации - Лавлинский Николай, РИТ++ 2017Чеклист по клиентской оптимизации - Лавлинский Николай, РИТ++ 2017
Чеклист по клиентской оптимизации - Лавлинский Николай, РИТ++ 2017Николай Лавлинский
 

More Related Content

What's hot

Приключения проекта от компьютера разработчика до серьезных нагрузок / Андрей...
Приключения проекта от компьютера разработчика до серьезных нагрузок / Андрей...Приключения проекта от компьютера разработчика до серьезных нагрузок / Андрей...
Приключения проекта от компьютера разработчика до серьезных нагрузок / Андрей...Ontico
 
Ошибки проектирования высоконагруженных проектов / Максим Ехлаков (OneTwoRent)
Ошибки проектирования высоконагруженных проектов / Максим Ехлаков (OneTwoRent)Ошибки проектирования высоконагруженных проектов / Максим Ехлаков (OneTwoRent)
Ошибки проектирования высоконагруженных проектов / Максим Ехлаков (OneTwoRent)Ontico
 
Андрей Ситник
Андрей СитникАндрей Ситник
Андрей СитникCodeFest
 
Хайлоад и безопасность в мире DevOps: совместимы ли? / Юрий Колесов (security...
Хайлоад и безопасность в мире DevOps: совместимы ли? / Юрий Колесов (security...Хайлоад и безопасность в мире DevOps: совместимы ли? / Юрий Колесов (security...
Хайлоад и безопасность в мире DevOps: совместимы ли? / Юрий Колесов (security...Ontico
 
Быстрое построение backendов c помощью реактивных потоков
Быстрое построение backendов c помощью реактивных потоковБыстрое построение backendов c помощью реактивных потоков
Быстрое построение backendов c помощью реактивных потоковCodeFest
 
Devconf2013 new-features-in-mysql-and-mariadb
Devconf2013 new-features-in-mysql-and-mariadbDevconf2013 new-features-in-mysql-and-mariadb
Devconf2013 new-features-in-mysql-and-mariadbSergey Petrunya
 
Serghei Iakovlev "Chaos engineering in action"
Serghei Iakovlev "Chaos engineering in action"Serghei Iakovlev "Chaos engineering in action"
Serghei Iakovlev "Chaos engineering in action"Fwdays
 
Как сделать сложное простым. История создания Проект1917 / Сергей Спорышев (I...
Как сделать сложное простым. История создания Проект1917 / Сергей Спорышев (I...Как сделать сложное простым. История создания Проект1917 / Сергей Спорышев (I...
Как сделать сложное простым. История создания Проект1917 / Сергей Спорышев (I...Ontico
 
MySQL: чек-лист для новичка в highload (Cвета Cмирнова, Aнастасия Распопина ...
MySQL:  чек-лист для новичка в highload (Cвета Cмирнова, Aнастасия Распопина ...MySQL:  чек-лист для новичка в highload (Cвета Cмирнова, Aнастасия Распопина ...
MySQL: чек-лист для новичка в highload (Cвета Cмирнова, Aнастасия Распопина ...Anastasia Rostova
 
Дмитрий Стогов
Дмитрий СтоговДмитрий Стогов
Дмитрий СтоговCodeFest
 
Быстрое прототипирование бэкенда игры с геолокацией на OpenResty, Redis и Doc...
Быстрое прототипирование бэкенда игры с геолокацией на OpenResty, Redis и Doc...Быстрое прототипирование бэкенда игры с геолокацией на OpenResty, Redis и Doc...
Быстрое прототипирование бэкенда игры с геолокацией на OpenResty, Redis и Doc...Ontico
 
Веб-разработка без наркотиков с помощью PostgreSQL, Nginx и c2h5oh / Миша Кир...
Веб-разработка без наркотиков с помощью PostgreSQL, Nginx и c2h5oh / Миша Кир...Веб-разработка без наркотиков с помощью PostgreSQL, Nginx и c2h5oh / Миша Кир...
Веб-разработка без наркотиков с помощью PostgreSQL, Nginx и c2h5oh / Миша Кир...Ontico
 
Архитектура HAWQ / Алексей Грищенко (Pivotal)
Архитектура HAWQ / Алексей Грищенко (Pivotal)Архитектура HAWQ / Алексей Грищенко (Pivotal)
Архитектура HAWQ / Алексей Грищенко (Pivotal)Ontico
 
Zabbix в Badoo или о чем не пишут в мануале, Илья Аблеев (Badoo)
Zabbix в Badoo или о чем не пишут в мануале, Илья Аблеев (Badoo)Zabbix в Badoo или о чем не пишут в мануале, Илья Аблеев (Badoo)
Zabbix в Badoo или о чем не пишут в мануале, Илья Аблеев (Badoo)Badoo Development
 
Практические примеры использования API в инфраструктурных продуктах Cisco для...
Практические примеры использования API в инфраструктурных продуктах Cisco для...Практические примеры использования API в инфраструктурных продуктах Cisco для...
Практические примеры использования API в инфраструктурных продуктах Cisco для...Cisco Russia
 
Распространенные ошибки применения баз данных (Сергей Аверин)
Распространенные ошибки применения баз данных (Сергей Аверин)Распространенные ошибки применения баз данных (Сергей Аверин)
Распространенные ошибки применения баз данных (Сергей Аверин)Ontico
 
Хорошо поддерживаемое в продакшне приложение / Николай Сивко (okmeter.io)
Хорошо поддерживаемое в продакшне приложение / Николай Сивко (okmeter.io)Хорошо поддерживаемое в продакшне приложение / Николай Сивко (okmeter.io)
Хорошо поддерживаемое в продакшне приложение / Николай Сивко (okmeter.io)Ontico
 
Tempesta FW: challenges, internals, use cases / Александр Крижановский (Tempe...
Tempesta FW: challenges, internals, use cases / Александр Крижановский (Tempe...Tempesta FW: challenges, internals, use cases / Александр Крижановский (Tempe...
Tempesta FW: challenges, internals, use cases / Александр Крижановский (Tempe...Ontico
 
Денис Иванов
Денис ИвановДенис Иванов
Денис ИвановCodeFest
 

What's hot (20)

Приключения проекта от компьютера разработчика до серьезных нагрузок / Андрей...
Приключения проекта от компьютера разработчика до серьезных нагрузок / Андрей...Приключения проекта от компьютера разработчика до серьезных нагрузок / Андрей...
Приключения проекта от компьютера разработчика до серьезных нагрузок / Андрей...
 
Ошибки проектирования высоконагруженных проектов / Максим Ехлаков (OneTwoRent)
Ошибки проектирования высоконагруженных проектов / Максим Ехлаков (OneTwoRent)Ошибки проектирования высоконагруженных проектов / Максим Ехлаков (OneTwoRent)
Ошибки проектирования высоконагруженных проектов / Максим Ехлаков (OneTwoRent)
 
Андрей Ситник
Андрей СитникАндрей Ситник
Андрей Ситник
 
Хайлоад и безопасность в мире DevOps: совместимы ли? / Юрий Колесов (security...
Хайлоад и безопасность в мире DevOps: совместимы ли? / Юрий Колесов (security...Хайлоад и безопасность в мире DevOps: совместимы ли? / Юрий Колесов (security...
Хайлоад и безопасность в мире DevOps: совместимы ли? / Юрий Колесов (security...
 
Быстрое построение backendов c помощью реактивных потоков
Быстрое построение backendов c помощью реактивных потоковБыстрое построение backendов c помощью реактивных потоков
Быстрое построение backendов c помощью реактивных потоков
 
Devconf2013 new-features-in-mysql-and-mariadb
Devconf2013 new-features-in-mysql-and-mariadbDevconf2013 new-features-in-mysql-and-mariadb
Devconf2013 new-features-in-mysql-and-mariadb
 
Serghei Iakovlev "Chaos engineering in action"
Serghei Iakovlev "Chaos engineering in action"Serghei Iakovlev "Chaos engineering in action"
Serghei Iakovlev "Chaos engineering in action"
 
Как сделать сложное простым. История создания Проект1917 / Сергей Спорышев (I...
Как сделать сложное простым. История создания Проект1917 / Сергей Спорышев (I...Как сделать сложное простым. История создания Проект1917 / Сергей Спорышев (I...
Как сделать сложное простым. История создания Проект1917 / Сергей Спорышев (I...
 
MySQL: чек-лист для новичка в highload (Cвета Cмирнова, Aнастасия Распопина ...
MySQL:  чек-лист для новичка в highload (Cвета Cмирнова, Aнастасия Распопина ...MySQL:  чек-лист для новичка в highload (Cвета Cмирнова, Aнастасия Распопина ...
MySQL: чек-лист для новичка в highload (Cвета Cмирнова, Aнастасия Распопина ...
 
Дмитрий Стогов
Дмитрий СтоговДмитрий Стогов
Дмитрий Стогов
 
Быстрое прототипирование бэкенда игры с геолокацией на OpenResty, Redis и Doc...
Быстрое прототипирование бэкенда игры с геолокацией на OpenResty, Redis и Doc...Быстрое прототипирование бэкенда игры с геолокацией на OpenResty, Redis и Doc...
Быстрое прототипирование бэкенда игры с геолокацией на OpenResty, Redis и Doc...
 
Веб-разработка без наркотиков с помощью PostgreSQL, Nginx и c2h5oh / Миша Кир...
Веб-разработка без наркотиков с помощью PostgreSQL, Nginx и c2h5oh / Миша Кир...Веб-разработка без наркотиков с помощью PostgreSQL, Nginx и c2h5oh / Миша Кир...
Веб-разработка без наркотиков с помощью PostgreSQL, Nginx и c2h5oh / Миша Кир...
 
Архитектура HAWQ / Алексей Грищенко (Pivotal)
Архитектура HAWQ / Алексей Грищенко (Pivotal)Архитектура HAWQ / Алексей Грищенко (Pivotal)
Архитектура HAWQ / Алексей Грищенко (Pivotal)
 
Zabbix в Badoo или о чем не пишут в мануале, Илья Аблеев (Badoo)
Zabbix в Badoo или о чем не пишут в мануале, Илья Аблеев (Badoo)Zabbix в Badoo или о чем не пишут в мануале, Илья Аблеев (Badoo)
Zabbix в Badoo или о чем не пишут в мануале, Илья Аблеев (Badoo)
 
Практические примеры использования API в инфраструктурных продуктах Cisco для...
Практические примеры использования API в инфраструктурных продуктах Cisco для...Практические примеры использования API в инфраструктурных продуктах Cisco для...
Практические примеры использования API в инфраструктурных продуктах Cisco для...
 
Attacking MongoDB
Attacking MongoDBAttacking MongoDB
Attacking MongoDB
 
Распространенные ошибки применения баз данных (Сергей Аверин)
Распространенные ошибки применения баз данных (Сергей Аверин)Распространенные ошибки применения баз данных (Сергей Аверин)
Распространенные ошибки применения баз данных (Сергей Аверин)
 
Хорошо поддерживаемое в продакшне приложение / Николай Сивко (okmeter.io)
Хорошо поддерживаемое в продакшне приложение / Николай Сивко (okmeter.io)Хорошо поддерживаемое в продакшне приложение / Николай Сивко (okmeter.io)
Хорошо поддерживаемое в продакшне приложение / Николай Сивко (okmeter.io)
 
Tempesta FW: challenges, internals, use cases / Александр Крижановский (Tempe...
Tempesta FW: challenges, internals, use cases / Александр Крижановский (Tempe...Tempesta FW: challenges, internals, use cases / Александр Крижановский (Tempe...
Tempesta FW: challenges, internals, use cases / Александр Крижановский (Tempe...
 
Денис Иванов
Денис ИвановДенис Иванов
Денис Иванов
 

Similar to Перепись приложения. Нативного. На JS. Done. | Odessa Frontend Meetup #10

И снова разработка под iOS. Павел Тайкало
И снова разработка под iOS. Павел ТайкалоИ снова разработка под iOS. Павел Тайкало
И снова разработка под iOS. Павел ТайкалоStanfy
 
My Open Source (Sept 2017)
My Open Source (Sept 2017)My Open Source (Sept 2017)
My Open Source (Sept 2017)Roman Dvornov
 
Node.JS: возможности для РНР-разработчика
Node.JS: возможности для РНР-разработчикаNode.JS: возможности для РНР-разработчика
Node.JS: возможности для РНР-разработчикаAlexei Smolyanov
 
Истинный DevOps. Секрет 42.
Истинный DevOps. Секрет 42.Истинный DevOps. Секрет 42.
Истинный DevOps. Секрет 42.Nikita Borzykh
 
Что нового в MySQL 8.0? / Дмитрий Ленев (Oracle)
Что нового в MySQL 8.0? / Дмитрий Ленев (Oracle)Что нового в MySQL 8.0? / Дмитрий Ленев (Oracle)
Что нового в MySQL 8.0? / Дмитрий Ленев (Oracle)Ontico
 
Денис Паясь
Денис ПаясьДенис Паясь
Денис ПаясьCodeFest
 
Python Meetup
Python Meetup Python Meetup
Python Meetup iQSpace
 
Как мы разрабатываем новый фронтенд / Филипп Нехаев (Tinkoff.ru)
Как мы разрабатываем новый фронтенд / Филипп Нехаев (Tinkoff.ru)Как мы разрабатываем новый фронтенд / Филипп Нехаев (Tinkoff.ru)
Как мы разрабатываем новый фронтенд / Филипп Нехаев (Tinkoff.ru)Ontico
 
So Your WAF Needs a Parser
So Your WAF Needs a ParserSo Your WAF Needs a Parser
So Your WAF Needs a Parseryalegko
 
Конструктор / Денис Паясь (Яндекс)
Конструктор / Денис Паясь (Яндекс)Конструктор / Денис Паясь (Яндекс)
Конструктор / Денис Паясь (Яндекс)Ontico
 
Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...
Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...
Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...Ontico
 
Работа со статикой в Django
Работа со статикой в DjangoРабота со статикой в Django
Работа со статикой в DjangoMoscowDjango
 
Scala, SBT & Play! for Rapid Application Development
Scala, SBT & Play! for Rapid Application DevelopmentScala, SBT & Play! for Rapid Application Development
Scala, SBT & Play! for Rapid Application DevelopmentAnton Kirillov
 
Alexei Sintsov - "Between error and vulerability - one step"
Alexei Sintsov - "Between error and vulerability - one step"Alexei Sintsov - "Between error and vulerability - one step"
Alexei Sintsov - "Between error and vulerability - one step"Andrew Mayorov
 
Oracle NoSQL Database
Oracle NoSQL DatabaseOracle NoSQL Database
Oracle NoSQL DatabaseAndrey Akulov
 
Sphinx. настройка, эксплуатация
Sphinx. настройка, эксплуатацияSphinx. настройка, эксплуатация
Sphinx. настройка, эксплуатацияandreyborue
 
кри 2014 elastic search рациональный подход к созданию собственной системы а...
кри 2014 elastic search  рациональный подход к созданию собственной системы а...кри 2014 elastic search  рациональный подход к созданию собственной системы а...
кри 2014 elastic search рациональный подход к созданию собственной системы а...Vyacheslav Nikulin
 
JavaScript on frontend and backend (in Russian
JavaScript on frontend and backend (in RussianJavaScript on frontend and backend (in Russian
JavaScript on frontend and backend (in RussianMikhail Davydov
 
Node JS проблемы надежности, и пути их решения
Node JS проблемы надежности, и пути их решенияNode JS проблемы надежности, и пути их решения
Node JS проблемы надежности, и пути их решенияAlexander Kucherenko
 

Similar to Перепись приложения. Нативного. На JS. Done. | Odessa Frontend Meetup #10 (20)

И снова разработка под iOS. Павел Тайкало
И снова разработка под iOS. Павел ТайкалоИ снова разработка под iOS. Павел Тайкало
И снова разработка под iOS. Павел Тайкало
 
My Open Source (Sept 2017)
My Open Source (Sept 2017)My Open Source (Sept 2017)
My Open Source (Sept 2017)
 
Node.JS: возможности для РНР-разработчика
Node.JS: возможности для РНР-разработчикаNode.JS: возможности для РНР-разработчика
Node.JS: возможности для РНР-разработчика
 
Истинный DevOps. Секрет 42.
Истинный DevOps. Секрет 42.Истинный DevOps. Секрет 42.
Истинный DevOps. Секрет 42.
 
Что нового в MySQL 8.0? / Дмитрий Ленев (Oracle)
Что нового в MySQL 8.0? / Дмитрий Ленев (Oracle)Что нового в MySQL 8.0? / Дмитрий Ленев (Oracle)
Что нового в MySQL 8.0? / Дмитрий Ленев (Oracle)
 
Diplom 1
Diplom 1Diplom 1
Diplom 1
 
Денис Паясь
Денис ПаясьДенис Паясь
Денис Паясь
 
Python Meetup
Python Meetup Python Meetup
Python Meetup
 
Как мы разрабатываем новый фронтенд / Филипп Нехаев (Tinkoff.ru)
Как мы разрабатываем новый фронтенд / Филипп Нехаев (Tinkoff.ru)Как мы разрабатываем новый фронтенд / Филипп Нехаев (Tinkoff.ru)
Как мы разрабатываем новый фронтенд / Филипп Нехаев (Tinkoff.ru)
 
So Your WAF Needs a Parser
So Your WAF Needs a ParserSo Your WAF Needs a Parser
So Your WAF Needs a Parser
 
Конструктор / Денис Паясь (Яндекс)
Конструктор / Денис Паясь (Яндекс)Конструктор / Денис Паясь (Яндекс)
Конструктор / Денис Паясь (Яндекс)
 
Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...
Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...
Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...
 
Работа со статикой в Django
Работа со статикой в DjangoРабота со статикой в Django
Работа со статикой в Django
 
Scala, SBT & Play! for Rapid Application Development
Scala, SBT & Play! for Rapid Application DevelopmentScala, SBT & Play! for Rapid Application Development
Scala, SBT & Play! for Rapid Application Development
 
Alexei Sintsov - "Between error and vulerability - one step"
Alexei Sintsov - "Between error and vulerability - one step"Alexei Sintsov - "Between error and vulerability - one step"
Alexei Sintsov - "Between error and vulerability - one step"
 
Oracle NoSQL Database
Oracle NoSQL DatabaseOracle NoSQL Database
Oracle NoSQL Database
 
Sphinx. настройка, эксплуатация
Sphinx. настройка, эксплуатацияSphinx. настройка, эксплуатация
Sphinx. настройка, эксплуатация
 
кри 2014 elastic search рациональный подход к созданию собственной системы а...
кри 2014 elastic search  рациональный подход к созданию собственной системы а...кри 2014 elastic search  рациональный подход к созданию собственной системы а...
кри 2014 elastic search рациональный подход к созданию собственной системы а...
 
JavaScript on frontend and backend (in Russian
JavaScript on frontend and backend (in RussianJavaScript on frontend and backend (in Russian
JavaScript on frontend and backend (in Russian
 
Node JS проблемы надежности, и пути их решения
Node JS проблемы надежности, и пути их решенияNode JS проблемы надежности, и пути их решения
Node JS проблемы надежности, и пути их решения
 

More from OdessaFrontend

Викторина | Odessa Frontend Meetup #19
Викторина | Odessa Frontend Meetup #19Викторина | Odessa Frontend Meetup #19
Викторина | Odessa Frontend Meetup #19OdessaFrontend
 
Использование Recoil в React и React Native приложениях | Odessa Frontend Mee...
Использование Recoil в React и React Native приложениях | Odessa Frontend Mee...Использование Recoil в React и React Native приложениях | Odessa Frontend Mee...
Использование Recoil в React и React Native приложениях | Odessa Frontend Mee...OdessaFrontend
 
Великолепный Gatsby.js | Odessa Frontend Meetup #19
Великолепный Gatsby.js | Odessa Frontend Meetup #19Великолепный Gatsby.js | Odessa Frontend Meetup #19
Великолепный Gatsby.js | Odessa Frontend Meetup #19OdessaFrontend
 
Функциональное программирование с использованием библиотеки fp-ts | Odessa Fr...
Функциональное программирование с использованием библиотеки fp-ts | Odessa Fr...Функциональное программирование с использованием библиотеки fp-ts | Odessa Fr...
Функциональное программирование с использованием библиотеки fp-ts | Odessa Fr...OdessaFrontend
 
Canvas API как инструмент для работы с графикой | Odessa Frontend Meetup #18
Canvas API как инструмент для работы с графикой | Odessa Frontend Meetup #18Canvas API как инструмент для работы с графикой | Odessa Frontend Meetup #18
Canvas API как инструмент для работы с графикой | Odessa Frontend Meetup #18OdessaFrontend
 
Викторина | Odessa Frontend Meetup #17
Викторина | Odessa Frontend Meetup #17Викторина | Odessa Frontend Meetup #17
Викторина | Odessa Frontend Meetup #17OdessaFrontend
 
Антихрупкий TypeScript | Odessa Frontend Meetup #17
Антихрупкий TypeScript | Odessa Frontend Meetup #17Антихрупкий TypeScript | Odessa Frontend Meetup #17
Антихрупкий TypeScript | Odessa Frontend Meetup #17OdessaFrontend
 
Частые ошибки при разработке фронтенда | Odessa Frontend Meetup #17
Частые ошибки при разработке фронтенда | Odessa Frontend Meetup #17Частые ошибки при разработке фронтенда | Odessa Frontend Meetup #17
Частые ошибки при разработке фронтенда | Odessa Frontend Meetup #17OdessaFrontend
 
OAuth2 и OpenID Connect простым языком | Odessa Frontend Meetup #17
OAuth2 и OpenID Connect простым языком | Odessa Frontend Meetup #17OAuth2 и OpenID Connect простым языком | Odessa Frontend Meetup #17
OAuth2 и OpenID Connect простым языком | Odessa Frontend Meetup #17OdessaFrontend
 
Объекты в ECMAScript | Odessa Frontend Meetup #16
Объекты в ECMAScript | Odessa Frontend Meetup #16Объекты в ECMAScript | Odessa Frontend Meetup #16
Объекты в ECMAScript | Odessa Frontend Meetup #16OdessaFrontend
 
Фриланс как профессиональная деградация | Odessa Frontend Meetup #16
Фриланс как профессиональная деградация | Odessa Frontend Meetup #16Фриланс как профессиональная деградация | Odessa Frontend Meetup #16
Фриланс как профессиональная деградация | Odessa Frontend Meetup #16OdessaFrontend
 
Cлайдер на CSS | Odessa Frontend Meetup #16
Cлайдер на CSS | Odessa Frontend Meetup #16Cлайдер на CSS | Odessa Frontend Meetup #16
Cлайдер на CSS | Odessa Frontend Meetup #16OdessaFrontend
 
Современный станок верстальщика
Современный станок верстальщикаСовременный станок верстальщика
Современный станок верстальщикаOdessaFrontend
 
Викторина | Odessa Frontend Meetup #15
Викторина | Odessa Frontend Meetup #15Викторина | Odessa Frontend Meetup #15
Викторина | Odessa Frontend Meetup #15OdessaFrontend
 
DRY’им Vuex | Odessa Frontend Meetup #15
DRY’им Vuex | Odessa Frontend Meetup #15DRY’им Vuex | Odessa Frontend Meetup #15
DRY’им Vuex | Odessa Frontend Meetup #15OdessaFrontend
 
А/Б тестирование: Что? Как? Зачем? | Odessa Frontend Meetup #15
А/Б тестирование: Что? Как? Зачем? | Odessa Frontend Meetup #15А/Б тестирование: Что? Как? Зачем? | Odessa Frontend Meetup #15
А/Б тестирование: Что? Как? Зачем? | Odessa Frontend Meetup #15OdessaFrontend
 
Пощупать 3д в браузере | Odessa Frontend Meetup #15
Пощупать 3д в браузере | Odessa Frontend Meetup #15Пощупать 3д в браузере | Odessa Frontend Meetup #15
Пощупать 3д в браузере | Odessa Frontend Meetup #15OdessaFrontend
 
Викторина | Odessa Frontend Meetup #14
Викторина | Odessa Frontend Meetup #14Викторина | Odessa Frontend Meetup #14
Викторина | Odessa Frontend Meetup #14OdessaFrontend
 
Викторина | Odessa Frontend Meetup #13
Викторина | Odessa Frontend Meetup #13Викторина | Odessa Frontend Meetup #13
Викторина | Odessa Frontend Meetup #13OdessaFrontend
 
Структуры данных в JavaScript | Odessa Frontend Meetup #13
Структуры данных в JavaScript | Odessa Frontend Meetup #13Структуры данных в JavaScript | Odessa Frontend Meetup #13
Структуры данных в JavaScript | Odessa Frontend Meetup #13OdessaFrontend
 

More from OdessaFrontend (20)

Викторина | Odessa Frontend Meetup #19
Викторина | Odessa Frontend Meetup #19Викторина | Odessa Frontend Meetup #19
Викторина | Odessa Frontend Meetup #19
 
Использование Recoil в React и React Native приложениях | Odessa Frontend Mee...
Использование Recoil в React и React Native приложениях | Odessa Frontend Mee...Использование Recoil в React и React Native приложениях | Odessa Frontend Mee...
Использование Recoil в React и React Native приложениях | Odessa Frontend Mee...
 
Великолепный Gatsby.js | Odessa Frontend Meetup #19
Великолепный Gatsby.js | Odessa Frontend Meetup #19Великолепный Gatsby.js | Odessa Frontend Meetup #19
Великолепный Gatsby.js | Odessa Frontend Meetup #19
 
Функциональное программирование с использованием библиотеки fp-ts | Odessa Fr...
Функциональное программирование с использованием библиотеки fp-ts | Odessa Fr...Функциональное программирование с использованием библиотеки fp-ts | Odessa Fr...
Функциональное программирование с использованием библиотеки fp-ts | Odessa Fr...
 
Canvas API как инструмент для работы с графикой | Odessa Frontend Meetup #18
Canvas API как инструмент для работы с графикой | Odessa Frontend Meetup #18Canvas API как инструмент для работы с графикой | Odessa Frontend Meetup #18
Canvas API как инструмент для работы с графикой | Odessa Frontend Meetup #18
 
Викторина | Odessa Frontend Meetup #17
Викторина | Odessa Frontend Meetup #17Викторина | Odessa Frontend Meetup #17
Викторина | Odessa Frontend Meetup #17
 
Антихрупкий TypeScript | Odessa Frontend Meetup #17
Антихрупкий TypeScript | Odessa Frontend Meetup #17Антихрупкий TypeScript | Odessa Frontend Meetup #17
Антихрупкий TypeScript | Odessa Frontend Meetup #17
 
Частые ошибки при разработке фронтенда | Odessa Frontend Meetup #17
Частые ошибки при разработке фронтенда | Odessa Frontend Meetup #17Частые ошибки при разработке фронтенда | Odessa Frontend Meetup #17
Частые ошибки при разработке фронтенда | Odessa Frontend Meetup #17
 
OAuth2 и OpenID Connect простым языком | Odessa Frontend Meetup #17
OAuth2 и OpenID Connect простым языком | Odessa Frontend Meetup #17OAuth2 и OpenID Connect простым языком | Odessa Frontend Meetup #17
OAuth2 и OpenID Connect простым языком | Odessa Frontend Meetup #17
 
Объекты в ECMAScript | Odessa Frontend Meetup #16
Объекты в ECMAScript | Odessa Frontend Meetup #16Объекты в ECMAScript | Odessa Frontend Meetup #16
Объекты в ECMAScript | Odessa Frontend Meetup #16
 
Фриланс как профессиональная деградация | Odessa Frontend Meetup #16
Фриланс как профессиональная деградация | Odessa Frontend Meetup #16Фриланс как профессиональная деградация | Odessa Frontend Meetup #16
Фриланс как профессиональная деградация | Odessa Frontend Meetup #16
 
Cлайдер на CSS | Odessa Frontend Meetup #16
Cлайдер на CSS | Odessa Frontend Meetup #16Cлайдер на CSS | Odessa Frontend Meetup #16
Cлайдер на CSS | Odessa Frontend Meetup #16
 
Современный станок верстальщика
Современный станок верстальщикаСовременный станок верстальщика
Современный станок верстальщика
 
Викторина | Odessa Frontend Meetup #15
Викторина | Odessa Frontend Meetup #15Викторина | Odessa Frontend Meetup #15
Викторина | Odessa Frontend Meetup #15
 
DRY’им Vuex | Odessa Frontend Meetup #15
DRY’им Vuex | Odessa Frontend Meetup #15DRY’им Vuex | Odessa Frontend Meetup #15
DRY’им Vuex | Odessa Frontend Meetup #15
 
А/Б тестирование: Что? Как? Зачем? | Odessa Frontend Meetup #15
А/Б тестирование: Что? Как? Зачем? | Odessa Frontend Meetup #15А/Б тестирование: Что? Как? Зачем? | Odessa Frontend Meetup #15
А/Б тестирование: Что? Как? Зачем? | Odessa Frontend Meetup #15
 
Пощупать 3д в браузере | Odessa Frontend Meetup #15
Пощупать 3д в браузере | Odessa Frontend Meetup #15Пощупать 3д в браузере | Odessa Frontend Meetup #15
Пощупать 3д в браузере | Odessa Frontend Meetup #15
 
Викторина | Odessa Frontend Meetup #14
Викторина | Odessa Frontend Meetup #14Викторина | Odessa Frontend Meetup #14
Викторина | Odessa Frontend Meetup #14
 
Викторина | Odessa Frontend Meetup #13
Викторина | Odessa Frontend Meetup #13Викторина | Odessa Frontend Meetup #13
Викторина | Odessa Frontend Meetup #13
 
Структуры данных в JavaScript | Odessa Frontend Meetup #13
Структуры данных в JavaScript | Odessa Frontend Meetup #13Структуры данных в JavaScript | Odessa Frontend Meetup #13
Структуры данных в JavaScript | Odessa Frontend Meetup #13
 

Перепись приложения. Нативного. На JS. Done. | Odessa Frontend Meetup #10