Случайны выбор дневника Раскрыть/свернуть полный список возможностей


Найдено 10220 сообщений
Cообщения с меткой

javascript - Самое интересное в блогах

Следующие 30  »
rss_rss_hh_new

[Перевод] Программируйте там, где затык будет, а не там, где он был

Пятница, 30 Сентября 2016 г. 10:39 (ссылка)

В 2013 году от рождества Христова мысль, что телефоны с ARM-профессорами будут запускать полноценный JavaScript также быстро, как десктопы, оснащенные x86, вызывала смех. В те старые времена, три года назад, iPhone 5 отставал по мощности примерно в 10 раз. Казалось, что ничего не может измениться в ближайшее время.



Но всё изменилось. Новый айфон 7 запускает JavaScript, согласно измерениям JetStream benchmark, БЫСТРЕЕ, чем самый быстрый на сегодняшний день Макбук (не про и не эйр). Лучший 5K iMac с 4Ггц процессором i7 теперь всего в два раза быстрее 7го айфона в этом тесте. Процессоры ARM улучшаются с совершенно безумной скоростью. Мур расслабился с десктопами, но бежит как сумасшедший в мобильном мире.



img



Скорость прогресса пристыдила многие предсказания будущего, но этот конкретный пример все же поражает. Три года назад — это не так давно! С того времени мы пришли от "на порядок хуже" до "быстрее, чем большинство лаптопов".



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



Вот цитата из 2013:



Уточню: на телефоне возможно сделать совместную работу в реальном времени. Но это просто невозможно с JavaScript. Разница в производительности между нативными и веб-приложениями сравнима с разницей между FireFox and IE8: она слишком большая для серьезной работы.

Разницы больше нет. Так что, видимо, теперь айфон 7 официально подходит для Серьезной Работы ;-)



И вот что самое смешное. В 2013 мы сделали приложение под айфон для нашего инструмента совместной работы Basecamp. Мы использовали JavaScript и веб в комбинации. Мы любим веселиться на работе, но мне кажется, что результат был все же Достаточно Серьезным.



Мы использовали мобильный веб в самом сердце наших нативных приложений, и в то время это был рисковый шаг. Шрам от Фейсбука, который отказался от HTML5 в пользу чистого натива в 2012 году, был все еще слишком свеж в памяти тех, кто работал на пересечении веба и нативного кода. И, честно говоря, пришлось идти на компромиссы. Все было не так быстро, как в нативном варианте, но было достаточно быстрым.



И это было во времена "разницы на порядок"! Сегодня производительность этой стратегии не просто достаточно хорошая, она настолько высокая, что может быть офигенной. Или, иными словами, не является проблемой.



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



Я не говорю, что гибридный подход не приводит к компромиссам. Все еще есть некоторые моменты, которые ощущаются менее нативно, и им не хватает этой маленькой детали чтобы быть идеальными. И, конечно же, есть приложения, вроде крытых 3D-игр, где нужно выдавить все возможные капли производительности. Но в сегодняшних условиях количество приложений, которые можно создать таким гибридным веб/нативным подходом, и которые будут просто офигеть какие классные, несомненно очень большое. Это число намного, намного больше, чем в 2013 году.



Преимущества в продуктивности при разработке мультиплатформенных сервисов с помощью гибридного подхода — поразительны. Мы бы просто не смогли сделать Basecamp 3 за 18 месяцев и покрыть веб для десктопа, веб для мобильных устройств, нативный iOS, нативный Android и email без гибрида и величественного монолита. Как минимум, не раздувая команду разработки. Это пять платформ и 200+ отдельных экранов.



Это напоминает мне ситуацию, которую я описал в статье Ruby has been fast enough for 13 years. Увеличение производительности означает не только то, что наши штуки становятся быстрее. Это также означает, что мы можем делать новые штуки, новыми способами. Способами, которые были до невозможности медленными раньше. Способами, которые заставляют плакать людей, умещавших полные компьютерные демо в 4 килобайта. Но эти способы, тем не менее, увеличивают общую продуктивность масс.



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



Подумайте об этом когда делаете приложение сегодня. Вы программируете для условий мира 2013 года? Или 2016? Или 2018? Программируйте там, где затык производительности будет, а не там, где он был.


Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/311398/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Делаем свою первую браузерную 2d игру с физикой

Пятница, 30 Сентября 2016 г. 10:33 (ссылка)

Теплым летним вечером посетила мысль, которая, наверняка, посещает многих: хочу сделать свою игру! Энергии было через край, поэтому работа пошла с огоньком.



Racing game



Итогом стал небольшой прототип браузерного 2d платформера с физикой.

Под катом — руководство для новичков от новичка по созданию такой игры. Если вы — опытный игродел, заходите делиться ценными советами!







Инструменты на проекте





Классический JavaScript — Для простоты я постарался воспользоваться самым базовым синтаксисом языка. Так же в проекте нет сборщика: каждый файл подключается как есть. Благодаря этому, надеюсь, проект будет понятен широкому кругу разработчиков.



PixiJS — Мне понравился этот движок 2d графики. Каких-либо замечаний по его работе не возникло. Плюс в наличии — хорошая документация.



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



JQuery — Возможности библиотеки используются минимально и ее можно спокойно убрать при желании. Но лично мне JQuery нравится, я с удовольствием его использую для работы с HTML.



Архитектура приложения





Максимально 60 раз в секунду браузер вызывает метод перерисовки экрана.

Код
//render.RootStage

function animate() {

requestAnimationFrame(animate);

//...
}




При каждой перерисовке идет обновление физической модели и последовательное рисование слоев игры: карты, игровой машины, призовых звезд.

Код
//render.RootStage

function animate() {

requestAnimationFrame(animate);

//обновление модели
game.step();

//перерисовка слоев
for (var i=0; i< stages.length; i++)
stages[i].update();
}




Если между перерисовками экрана пользователь нажимал на кнопки управления — то модель получает об этом информацию, которая будет учтена при следующей перерисовке.

Код
//render.RootStage

$("#moveRight").mousedown(function(){
game.car().startAccelerator();
});

$("#moveRight").mouseup(function(){
game.car().stopAccelerator();
});




Можно изобразить этот процесс в виде схемы:

Процесс игры



1. Обновление модели.

2. Вызов PhysicsJS для расчета физики.

3. Последовательный вызов слоев на перерисовку.

4. Опрос обновленной модели и перерисовка с помощью PixiJS.



Особенности реализации





Коллизии — физический движок дает удобное API определения коллизий. Не нужно самому вспоминать математику :)

Код
//physics.Game

var world = Physics({...});

world.add([
Physics.behavior('body-collision-detection'),
...
]);

world.on('collisions:detected', function(data){
for (var i = 0; i < data.collisions.length; i++)
onCollision(data.collisions[i]);
});




Но иногда коллизии не нужны... — например, когда собираешь призовые звезды. Мне кажется логичным включить в физический движок тип объектов, которые фиксируют факт столкновения с ними, но при этом не взаимодействуют с другими объектами (объекты-призраки). К сожалению, я не нашел в PhysicsJS такой возможности. В итоге, даже если удалять призовую звезду после коллизии, то движок уже изменил скорость игрока, замедлив его.





Уверен, есть более красивое решение, но я сделал так: после факта коллизии возвращаем игроку его характеристики до столкновения, благо PhysicsJS позволяет так себя обманывать.

Код
//model.car.Car

function onCollision(otherBody, pos, norm){

if(otherBody.objType == model.ObjectType.POINT)
carBody.backPrevForce();
}

//physics.BodyPhysicsImpl

function backPrevForce(){
var old = body.state.old;
body.state.acc.set(old.acc.x, old.acc.y);
body.state.vel.set(old.vel.x, old.vel.y);
body.state.angular.vel = old.angular.vel;
body.state.angular.acc = old.angular.acc;
}




Результат — сбор звезд не нарушает скорости игрока.





Разные модели у движков — физический движок делает поворот объекта вокруг его центра масс, а графический движок по умолчанию разворачивает по левому верхнему углу объекта. Если этот факт не учитывать, то результат будет довольно забавным.





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

Пример Uber-подобной анимации






Решением является рисование автомобиля относительно его центра, а не левого верхнего угла.

Код
//render.car.PlayerCar

function paintCabin(g, model){
//...
g.drawRect(model.x - model.w/2, model.y - model.h/2, model.w, model.h);
//...
}




Теперь все выглядит как надо





Показ кнопок управления на мобильном устройстве — сделал реверанс в сторону прогресса: показываю большие кнопки управления для мобильных устройств. Главное, не забыть, что зажатие кнопки делается touch событиями, и что нужно запретить появление выделения текста от этого долгого нажатия через css стиль.

Код
//render.RootStage

$("#moveRight").on('touchstart', function(){
game.car().startAccelerator();
});

$("#moveRight").on('touchend', function(){
game.car().stopAccelerator();
});


//main.css

.moveBtn {
-webkit-user-select: none;
-moz-user-select: none;
}








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



Выводы



В целом, процесс создания игры не показался мне очень сложным. Используя готовые движки для графики и физики, можно значительно упростить разработку, почти не думая о математике. Насколько это эффективный подход — вопрос для отдельной статьи. Спасибо всем за внимание и надеюсь, мои наработки помогут вам создать что-нибудь свое, если вы давно собирались это сделать! Удачи!



[Исходники на GitHub]

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/311066/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Универсальный (Изоморфный) проект на Koa 2.x + React + Redux + React-Router

Пятница, 30 Сентября 2016 г. 09:43 (ссылка)

Универсальный Koa

Сейчас много споров по поводу универсального (изоморфного) кода, есть свои за и против.

Я считаю, что за универсальным (изоморфним) кодом будущее, так как он позволяет взять лучшее с серверного и клиентского рендеринга.



В ходе разработки в нашей команде получился неплохой бойлер-плейт и я решил им поделиться. Код можно посмотреть здесь.

Я не хочу лить воду, так как на эту тему есть очень хороший туториал:



По поводу, что такое Kоа:



Запуск проекта для разработки:

       git clone https://github.com/BoryaMogila/koa_react_redux.git;
npm install;
npm run-script run-with-build;




Посмотреть тестовый запуск можно на url: localhost(127.0.0.1):4000/app/



Запуск проекта для продакшена:

// сборка скриптов
npm run-script build-production;
// сборка серверных скриптов и запуск ноды
npm run-script run-production;


Серверные скрипты собираются, а не используется babel-register потому, что при использовании lazy-loading при первом запросе роута время отдачи около двух секунд из-за транспиляции кода.

Клиентские скрипты собираются для продакшн сборки также и в gzip формате. Для раздачи скриптов настоятельно рекомендую использовать nginx вместо koa-serve-static (реально удобно)

Серверный код лежит в папке app, изоморфный и клиентский в папке src.



Контроллеры для api пишем в папке koa_react_redux/app/controllers/:

//koa_react_redux/app/controllers/getPostsController.js
export default async function(ctx) {
// ваш код по обработке данных и формированию ответа.
//................

// ответ в виде json
ctx.body = [
{
title: 'React',
text: 'React is a good framework'
},
{
title: 'React + Redux',
text: 'React + Redux is a cool thing for isomorphic apps'
},
{
title: 'React + Redux + React-router',
text: 'React + Redux + React-router is a cool thing for isomorphic flexible apps'
}
]
}




Серверные роуты прописываем в файле koa_react_redux/app/routes/index.js по типу:

      import getPosts from '../controllers/getPostsController';
router.get('/getPosts/', getPosts);




Универсальные роуты пишем в файле koa_react_redux/src/routes.js:

import React from 'react';
import Route from 'react-router/lib/Route'
import IndexRoute from 'react-router/lib/IndexRoute'
import App from './components/App';
// для lazy-loading
const
getPosts = (nextState, callback) => require.ensure(
[],
(require) => {
callback(null, require("./containers/Posts").default)
}
),
getPost = (nextState, callback) => require.ensure(
[],
(require) => {
callback(null, require("./containers/Post").default)
}
);
function createRoutes() {
return (

// если не нужен lazy-loading, тогда импортим компонент и пишем стандартно
//



)
}
export default createRoutes




Общие middleware для redux подключаем стандартно в файле koa_react_redux/src/storeCinfigurator.js

import { createStore, combineReducers, compose, applyMiddleware } from 'redux'
import promiseErrorLogger from './middlewares/promiseErrorLogger'

createStore(
combineReducers({
...reducers
}),
initialState,
compose(
applyMiddleware(
promiseErrorLogger,
)
)
)




Клиентские middleware в файле koa_react_redux/src/index.js:

import promiseErrorLogger from './middlewares/promiseErrorLogger'
import { configureStore} from './storeCinfigurator'

configureStore(browserHistory, window.init, [promiseErrorLogger]);




Серверные по аналогии в файле koa_react_redux/app/controllers/reactAppController.js.



Асинхронные экшены:

import {GET_POSTS} from './actionsTypes'
import superagentFactory from '../helpers/superagentFactory'

const superagent = superagentFactory();

export function getPosts(){
return {
type: GET_POSTS,
payload: superagent
.get('/getPosts/')
.then(res => res.body)
}
}




Для асинхронных экшенов редюсери:

import {GET_POSTS, PENDING, SUCCESS, ERROR} from '../actions/actionsTypes';


export default function(state = [], action = {}){
switch (action.type){
case GET_POSTS + SUCCESS:
return action.payload;
case GET_POSTS + PENDING:
return state;
case GET_POSTS + ERROR:
return state;
default:
return state;
}
}




Редюсеры для redux подключаем в файле koa_react_redux/src/reducers/index.js:

import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux'


import posts from './postsReducer'
const rootReducer = {
posts,
routing: routerReducer
};

export default rootReducer;




Общую конфигурацию пишем по аналогии config js в папке koa_react_redux/config/, была сделана своя обёртка для изоморфного использования.

Серверную конфигурацию пишем так:

const config = {
//общая конфигурация
};
// for cut server-side config
if (typeof cutCode === 'undefined') {
Object.assign(config, {
// серверная конфигурация
});
}
module.exports = config;




Для SEO наша команда использует библиотеку «шлем»))) (react-helmet)



Работает так:

// код пишем в компоненте

import Helmet from "react-helmet";


/ amp takes no value
title="My Title"
titleTemplate="MySite.com - %s"
defaultTitle="My Default Title"
base={{"target": "_blank", "href": "http://mysite.com/"}}
meta={[
{"name": "description", "content": "Helmet application"},
{"property": "og:type", "content": "article"}
]}
link={[
{"rel": "canonical", "href": "http://mysite.com/example"},
{"rel": "apple-touch-icon", "href": "http://mysite.com/img/apple-touch-icon-57x57.png"},
{"rel": "apple-touch-icon", "sizes": "72x72", "href": "http://mysite.com/img/apple-touch-icon-72x72.png"}
]}
script={[
{"src": "http://include.com/pathtojs.js", "type": "text/javascript"},
{"type": "application/ld+json", innerHTML: `{ "@context": "http://schema.org" }`}
]}
onChangeClientState={(newState) => console.log(newState)}
/>
...





Данные для server-rendering пишем в контейнере, который подключаем в роутах:

import {getPosts} from '../actions'
class Posts extends Component {
constructor(props) {
super(props);
}
// эта функция выполняется на сервере для получения данных
static fetchData(dispatch, uriParams, allProps = {}) {
const promiseArr = [
dispatch(getPosts()),
];
// массив асинхронных экшенов для получения серверных данных
return Promise.all(promiseArr);
}

render(){
return (
//ваша разметка
);
}
}




P.S. Советую разнести api и раздачу скриптов по отдельнных проектах во избежание казусов и надежности. Буду рад услышать ваши комментарии и замечания
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/310302/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

История одной одержимости, или как я писал календарный скрипт для Photoshop

Пятница, 30 Сентября 2016 г. 08:11 (ссылка)





Эта статья о том, как я писал скрипт для создания календарей в Photoshop, от самого начала и до самого конца, с багами и пасхалками. Много текста, некоторого кода и чуть-чуть иллюстраций.





В начале



Всё началось с идеи, схематично зарисованной на крошечном листке бумаги. Захотелось написать скрипт, который мог прямо в Photoshop создать календарь на заданный год. Изначально нужно было получить годовой календарик размером 6 на 2 месяцев. Однако в процессе разработки «хотелки» росли, и в итоге список желаемых настроек стал таким, чтобы пользователь мог задать:




  • год

  • свои имена месяцев и дней недели

  • своё начало недели (с понедельника, воскресенья или любого другого дня)

  • свои выходные (пометить как выходной любой день)

  • отображение номеров недель

  • некоторые интервалы между элементами

  • взаиморасположение элементов и порядок следования дней

  • цвета текстовых слоёв, шрифт и его размеры

  • свои праздники (загружается специально созданный текстовый файл, из которого вытаскиваются праздничные даты и отмечаются на календаре) – особое желание



Также хотелось, чтобы была функция, похожая по действию на «Предпросмотр» — создавался тестовый месяц, который бы чётко отражал результат настроек.

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



Инструментарий



Итак, план есть, пришло время выбрать необходимые инструменты.

Язык. Photoshop поддерживает следующие скриптовые языки: AppleScript, VBScript и JavaScript. Думаю, тут всем понятно, что первый подходит для MacOS, второй – для Windows, третий – для обоих. Был выбран JS, так как некоторое время назад я изучил его основы, а разработка такого скрипта могла послужить таким образом хорошей практикой (и послужила, надо сказать).

Текстовый редактор. Первую сотню строк я написал в обычном «блокноте», но так долго продолжаться не могло. Я вспомнил про Sublime Text с его восхитительной для меня функцией множественного курсора, которая, к слову, спасала меня очень много раз в процессе работы.

Скриптовые инструменты от XBYTOR. На деле я использовал только один его инструмент, ActionToJavascript.jsx, но зато он был очень полезен. Этот скрипт работает так — на входе задается записанная операция в Photoshop (это наподобие макросов в MS Office, только закрытый формат), а на выходе получается файлик с расширением *.jsx, в котором прописана функция, выполняющая c помощью низкоуровневых команд всё то же самое, что и исходная операция.

Scripting Listener для Photoshop. Официальный плагин от Adobe. Выполняет, по сути, то же самое, что инструмент выше, но работает по несколько другому принципу — он следит за действиями пользователя в окне программы и записывает их в два текстовых файла на рабочем столе. Первый файл — это JavaScript, второй — VBS или AppleScript, в зависимости от ОС. И, да, он пишет всегда, его нужно удалять или выключать перед запуском программы, добавляя тильду (~) в начале имени плагина.

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

Документация. Без неё написать что-то сложнее всплывающего окошка «Hello word!» будет довольно проблематично. В конце статьи вы можете найти некоторые ссылки.



Оптимизация



Оптимизировать работу приходилось прямо в процессе написания скрипта. Первые тормоза начались, когда скрипт уже мог создать три месяца. Изначально я хотел сделать всю работу в одном документе Photoshop. Но оказалось, что после первого месяца скорость начинала сильно провисать – и если январь составлялся влёт, то с каждым новым месяцем время создания увеличивалось. На февраль уже уходило полторы минуты, а на март – три. Объяснилось это просто – так как каждая дата, каждая надпись является отдельным текстовым слоем, то на создание и перемещение нового слоя в растущем стеке слоёв стало уходить всё больше и больше времени. Поэтому, скрепя сердце в первый раз, я перенёс создание каждого месяца в новый документ, а результат просто копировался в основной документ. Таким образом, решилась проблема увеличения времени создания месяцев – теперь каждый месяц в среднем создавался за одинаковое количество времени.



Следующая неожиданность приключилась, когда мне нужно было узнать, каких размеров каждый из полученных месяцев, чтобы их правильно расставить друг от друга (пользователь может выставлять различные интервалы). Быстро написав простую функцию, которая должна была вернуть значения высот и ширин месяцев, я запустил её. Photoshop намертво завис. Дав ему щедрых десять минут, после которых он так и не реанимировался, процесс пришлось убить. Догадываюсь, что однажды он всё-таки бы закончил, но десять минут это слишком много само по себе. Дело опять оказалось в количестве слоёв в документе – их было слишком много, чтобы сразу получить нужные цифры. И, снова скрепя сердце, я перенёс вычисление размеров на тот этап, где создавался каждый месяц. Теперь созданный месяц сразу определялся по размерам и показания записывались в массив для последующего использования. Звучит несложно, но на деле это заняло у меня довольно много времени.



Ошибки



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

Ошибка с отрисовкой. Это совет для тех, кто пишет скрипты с интерфейсом пользователя и при отрисовке использует сложный перерасчёт размеров элементов (кнопок, например) — проверяйте работу своего скрипта в Windows и MacOS. У меня был случай, что в одном месте скрипт зависал в системе MacOS. Я долго искал причину и она крылась в следующем: расчёт размера одной из кнопок должен был подгоняться по отношению к другой в сторону увеличения, причём эта операция была загнана мною в цикл (согласен, спорное решение). На Windows на всех версиях Photoshop это отрабатывало отлично, так как подгоняемая кнопка всегда была меньше основной. Но на MacOS оказалось наоборот — основная кнопка была меньше подгоняемой на один-два пикселя, и скрипт уходил в бесконечный цикл. Сейчас я это поправил, и единственный вывод, который можно сделать — не используйте сомнительные решения при перерасчёте элементов интерфейса (ваш Кэп).



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





Поклонники творчества сэра Терри Пратчетта, возможно, обратили внимание на кнопку «Holi WIZZARD». Мне нравятся такие вещи, не знаю, является это пасхалкой или нет (кстати, о них ниже), я видел нечто подобное у одного антивируса – там сканер называется Luke Filewalker (Люк Скай Файлоуокер).



Ошибки внутренние. Редко, но встречаются ошибки, которые очень сложно объяснить. В моём случае такой проблемой оказалось задание размера ширины (а, возможно, и высоты) блочного текста. Поясню: в Photoshop есть два вида текста – Point Text (короткий) и Paragraph Text (блочный). Первый не имеет границ, в отличие от второго.







Так вот, у меня произошло следующее – я задал размер ширины этого блочного текста в 800 пикселей. На выходе у меня получилась ширина в 3333 пикселей. Я не поверил своим глазам и стал проверять код. Ошибки не было. Дело было вечером, и я грешил на уставшую голову, поэтому стал проверять с особой тщательностью с листочком в руках. Ошибка на глаза не попадалась.



Спустя полчаса, когда я уже отчаялся понять, что же происходит, на глаза попалась ссылка с аналогичной проблемой. Оказывается, что если разрешение документа больше 72 dpi, то Photoshop выставляет какое-то своё значение. Решается это так: берём разрешение нашего документа (у меня оно было равно 300), делим стандартное 72 на наше разрешение и полученный результат умножаем на нужную ширину блока текста. В итоге, чтобы получить блок в 800 пикселей для документа с 300 dpi, нужно задать размер в 800* 72 / 300 = 192 пикселя. Вот так всё просто.



Ошибка с неделями. Ещё один момент, в котором я сильно сглупил, оказался в вычислении номеров недель. По своему незнанию, я сделал нумерацию недель, исходя из абсолютной уверенности в том, что первая неделя начинается с первого января. Я был настолько в этом уверен, что даже не удосужился проверить заранее, а сразу приступил к реализации. Лишь когда я закончил, то решил проверить, а правильно ли начинать нумерацию с 1го января. Оказалось, что нет.

Наверняка многие (или все), кто читает эту статью, знают, что существует стандарт ISO 8601 для формата даты и времени. И согласно нему, неделя начинается с понедельника, а первой неделей является та, на которую выпадает первый четверг января. И первая неделя может начинаться вовсе не с 1го января, а, к примеру, со 2го или даже 4го (как в этом 2016 году). Так что эту часть кода тоже пришлось полностью переписывать.



Ошибка с… массивом? Вот тут уж я не знаю в чём было дело, однако решение оказалось интересным.

Вот код, который создаёт семь выпадающих меню с именами шрифтов:

for (var i=0; i<7; i++){
fontGroup.nameOfFont = fontGroup.add('dropdownlist', undefined, arrFonts);
fontGroup.nameOfFont.selection = fontIndex[i];
}
/*
fontGroup – элемент «группа» в объекте «окно»
arrFonts – массив с именами шрифтов
fontIndex – массив с порядковым номером нужного имени шрифта для каждого из семи меню
*/




При первой генерации окна всё проходило хорошо и без проблем. Но если пользователь загрузит сохранения, то окно будет закрыто и переоткрыто с новым значением fontIndex. При пересоздании окна с подгруженными настройками Фотошоп вылетал, если шрифт имел имя (в моём случае) Aarcover (Plain):001.001. Остальные шрифты проходили нормально, но вот этот давал сбой всей программе.

Казалось бы, и ладно, вероятность крайне мала (на самом деле нет), но меня это теребило очень сильно. Первое решение проблемы оказалось таким:

alert(arrFonts);
for (var i=0; i<7; i++){
fontGroup.nameOfFont = fontGroup.add('dropdownlist', undefined, arrFonts);
fontGroup.nameOfFont.selection = fontIndex[i];
}


То есть, если перед этим циклом просто вызвать окно-предупреждение, в котором будут перечислены все элементы массива arrFonts (и это окошко получалось на весь экран и даже не было видно кнопки ОК), то окно скрипта сгенерируется нормально, как надо. Другие «алерты» с пустыми строками и бессвязным текстом не помогали, только с массивом шрифтов.

Спустя некоторое время появилось второе решение, которое работает, но я не понимаю, почему.

var tempArrFont = arrFonts.slice();
for (var i=0; i<7; i++){
fontGroup.nameOfFont = fontGroup.add('dropdownlist', undefined, tempArrFont);
fontGroup.nameOfFont.selection = fontIndex[i];
}


Получается, мы делаем копию «проблемного» массива методом slice, и используем эту копию вместо оригинала. Это сработало и используется теперь. Я буду рад увидеть в комментариях ответ, почему это так происходит и можно ли как-то это исправить.



Полезный перерыв



Где-то в начале декабря 2015 года я сделал паузу в написании скрипта. Но остановился я не потому, что устал или потерял интерес. Я отвлёкся от скрипта, чтобы написать другой скрипт. Не стану подробно останавливаться на этой вещи, скажу лишь то, что в процессе разработки этого скрипта я обнаружил, что создание текстового слоя с помощью низкоуровневых команд выполнялось на порядок быстрее, чем с помощью моей функции, использующей стандартные методы из руководства. Да, в новой функции было куда больше строк кода, чем в той функции, которую я написал изначально – и всё-таки она работала быстрее. Можно было бы догадаться и так, что низкий уровень наше всё, но в тот момент я основательно сглупил и проверил это лишь в середине.

Для большей наглядности приведу сравнительную табличку:



Время, затраченное на создание 600 текстовых слоёв, содержащих текст «01»

(НУ — низкоуровневое создание, СМ — создание стандартными методами)

















Версия Photoshop Стационарный

компьютер (хар-ки)
Ноутбук (хар-ки)
CS5 НУ: 0 мин 47 сек

СМ: 3 мин 43 сек

НУ: 2 мин 28 сек

СМ: 9 мин 49 сек

CC 2015 НУ: 1 мин 18 сек

СМ: 10 мин 42 сек

НУ: 3 мин 12 сек

СМ: 42 мин 36 сек



Увидев такие показатели (обратите внимание на CS5 – обходит новый CC по скорости, шельмец) меня охватило чувство разочарования – ведь моя старая функция создания текста в «календарном» скрипте уже органично вписана в его структуру. И переделывать всё мне очень и очень не хотелось. Но, подумав, что ведь на самом деле всё не так плохо, просто нужно очень внимательно и осторожно подменить одну функцию другой. Затем протестировать, исправить все появившиеся недочёты и полностью перейти на новый способ. Тем более, что уменьшив время на создание календаря, я только выиграю.



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

Сырой код функции
// Вспомогательные функции (используются в дальнейшем для сокращения),
// создано инструментом XBYTOR'а

cTID = function(s) { return app.charIDToTypeID(s); };
sTID = function(s) { return app.stringIDToTypeID(s); };

// Практически сырая функция, полученная при конвертации операции в скрипт
// Создаёт слой с текстом "TEXT TEXT", шрифтом Verdana, размером в 9,32 пункта, чёрного цвета

function step1() {
var desc1 = new ActionDescriptor();
var ref1 = new ActionReference();
ref1.putClass(cTID('TxLr'));
desc1.putReference(cTID('null'), ref1);
var desc2 = new ActionDescriptor();
desc2.putString(cTID('Txt '), "TEXT TEXT");
var desc3 = new ActionDescriptor();
desc3.putEnumerated(sTID("warpStyle"), sTID("warpStyle"), sTID("warpNone"));
desc3.putDouble(sTID("warpValue"), 0);
desc3.putDouble(sTID("warpPerspective"), 0);
desc3.putDouble(sTID("warpPerspectiveOther"), 0);
desc3.putEnumerated(sTID("warpRotate"), cTID('Ornt'), cTID('Hrzn'));
desc2.putObject(cTID('warp'), cTID('warp'), desc3);
var desc4 = new ActionDescriptor();
desc4.putUnitDouble(cTID('Hrzn'), cTID('#Prc'), 35.5820105820106);
desc4.putUnitDouble(cTID('Vrtc'), cTID('#Prc'), 48.2254697286012);
desc2.putObject(cTID('TxtC'), cTID('Pnt '), desc4);
desc2.putEnumerated(sTID("textGridding"), sTID("textGridding"), cTID('None'));
desc2.putEnumerated(cTID('Ornt'), cTID('Ornt'), cTID('Hrzn'));
desc2.putEnumerated(cTID('AntA'), cTID('Annt'), cTID('AnCr'));
var desc5 = new ActionDescriptor();
desc5.putUnitDouble(cTID('Left'), cTID('#Pnt'), 0);
desc5.putUnitDouble(cTID('Top '), cTID('#Pnt'), -8.04805660247803);
desc5.putUnitDouble(cTID('Rght'), cTID('#Pnt'), 50.8121795654297);
desc5.putUnitDouble(cTID('Btom'), cTID('#Pnt'), 2.8260350227356);
desc2.putObject(sTID("bounds"), sTID("bounds"), desc5);
var desc6 = new ActionDescriptor();
desc6.putUnitDouble(cTID('Left'), cTID('#Pnt'), 0);
desc6.putUnitDouble(cTID('Top '), cTID('#Pnt'), -7);
desc6.putUnitDouble(cTID('Rght'), cTID('#Pnt'), 52.0618438720703);
desc6.putUnitDouble(cTID('Btom'), cTID('#Pnt'), 0);
desc2.putObject(sTID("boundingBox"), sTID("boundingBox"), desc6);
var list1 = new ActionList();
var desc7 = new ActionDescriptor();
desc7.putEnumerated(sTID("textType"), sTID("textType"), cTID('Pnt '));
desc7.putEnumerated(cTID('Ornt'), cTID('Ornt'), cTID('Hrzn'));
var desc8 = new ActionDescriptor();
desc8.putDouble(sTID("xx"), 1);
desc8.putDouble(sTID("xy"), 0);
desc8.putDouble(sTID("yx"), 0);
desc8.putDouble(sTID("yy"), 1);
desc8.putDouble(sTID("tx"), 0);
desc8.putDouble(sTID("ty"), 0);
desc7.putObject(cTID('Trnf'), cTID('Trnf'), desc8);
desc7.putInteger(sTID("rowCount"), 1);
desc7.putInteger(sTID("columnCount"), 1);
desc7.putBoolean(sTID("rowMajorOrder"), true);
desc7.putUnitDouble(sTID("rowGutter"), cTID('#Pnt'), 0);
desc7.putUnitDouble(sTID("columnGutter"), cTID('#Pnt'), 0);
desc7.putUnitDouble(cTID('Spcn'), cTID('#Pnt'), 0);
desc7.putEnumerated(sTID("frameBaselineAlignment"), sTID("frameBaselineAlignment"), sTID("alignByAscent"));
desc7.putUnitDouble(sTID("firstBaselineMinimum"), cTID('#Pnt'), 0);
var desc9 = new ActionDescriptor();
desc9.putDouble(cTID('Hrzn'), 0);
desc9.putDouble(cTID('Vrtc'), 0);
desc7.putObject(cTID('base'), cTID('Pnt '), desc9);
list1.putObject(sTID("textShape"), desc7);
desc2.putList(sTID("textShape"), list1);
var list2 = new ActionList();
var desc10 = new ActionDescriptor();
desc10.putInteger(cTID('From'), 0);
desc10.putInteger(cTID('T '), 10);
var desc11 = new ActionDescriptor();
desc11.putBoolean(sTID("styleSheetHasParent"), true);
desc11.putString(sTID("fontPostScriptName"), "Verdana");
desc11.putString(cTID('FntN'), "Verdana");
desc11.putString(cTID('FntS'), "Regular");
desc11.putInteger(cTID('Scrp'), 0);
desc11.putInteger(cTID('FntT'), 1);
desc11.putUnitDouble(cTID('Sz '), cTID('#Pnt'), 9.31999969482422);
desc11.putEnumerated(sTID("digitSet"), sTID("digitSet"), sTID("defaultDigits"));
desc11.putUnitDouble(sTID("markYDistFromBaseline"), cTID('#Pnt'), 5.76000165939331);
desc11.putEnumerated(sTID("textLanguage"), sTID("textLanguage"), sTID("russianLanguage"));
var desc12 = new ActionDescriptor();
desc12.putString(sTID("fontPostScriptName"), "MyriadPro-Regular");
desc12.putString(cTID('FntN'), "Myriad Pro");
desc12.putString(cTID('FntS'), "Regular");
desc12.putInteger(cTID('Scrp'), 0);
desc12.putInteger(cTID('FntT'), 0);
desc12.putUnitDouble(cTID('Sz '), cTID('#Pnt'), 12);
desc12.putDouble(cTID('HrzS'), 100);
desc12.putDouble(cTID('VrtS'), 100);
desc12.putBoolean(sTID("syntheticBold"), false);
desc12.putBoolean(sTID("syntheticItalic"), false);
desc12.putBoolean(sTID("autoLeading"), true);
desc12.putInteger(cTID('Trck'), 0);
desc12.putUnitDouble(cTID('Bsln'), cTID('#Pnt'), 0);
desc12.putDouble(sTID("characterRotation"), 0);
desc12.putEnumerated(cTID('AtKr'), cTID('AtKr'), sTID("metricsKern"));
desc12.putEnumerated(sTID("fontCaps"), sTID("fontCaps"), cTID('Nrml'));
desc12.putEnumerated(sTID("digitSet"), sTID("digitSet"), sTID("defaultDigits"));
desc12.putEnumerated(sTID("dirOverride"), sTID("dirOverride"), sTID("dirOverrideDefault"));
desc12.putEnumerated(sTID("kashidas"), sTID("kashidas"), sTID("kashidaDefault"));
desc12.putEnumerated(sTID("diacVPos"), sTID("diacVPos"), sTID("diacVPosOpenType"));
desc12.putUnitDouble(sTID("diacXOffset"), cTID('#Pnt'), 0);
desc12.putUnitDouble(sTID("diacYOffset"), cTID('#Pnt'), 0);
desc12.putUnitDouble(sTID("markYDistFromBaseline"), cTID('#Pnt'), 100);
desc12.putEnumerated(sTID("baseline"), sTID("baseline"), cTID('Nrml'));
desc12.putEnumerated(sTID("otbaseline"), sTID("otbaseline"), cTID('Nrml'));
desc12.putEnumerated(sTID("strikethrough"), sTID("strikethrough"), sTID("strikethroughOff"));
desc12.putEnumerated(cTID('Undl'), cTID('Undl'), sTID("underlineOff"));
desc12.putUnitDouble(sTID("underlineOffset"), cTID('#Pnt'), 0);
desc12.putBoolean(sTID("ligature"), true);
desc12.putBoolean(sTID("altligature"), false);
desc12.putBoolean(sTID("contextualLigatures"), false);
desc12.putBoolean(sTID("alternateLigatures"), false);
desc12.putBoolean(sTID("oldStyle"), false);
desc12.putBoolean(sTID("fractions"), false);
desc12.putBoolean(sTID("ordinals"), false);
desc12.putBoolean(sTID("swash"), false);
desc12.putBoolean(sTID("titling"), false);
desc12.putBoolean(sTID("connectionForms"), false);
desc12.putBoolean(sTID("stylisticAlternates"), false);
desc12.putBoolean(sTID("ornaments"), false);
desc12.putBoolean(sTID("justificationAlternates"), false);
desc12.putEnumerated(sTID("figureStyle"), sTID("figureStyle"), cTID('Nrml'));
desc12.putBoolean(sTID("proportionalMetrics"), false);
desc12.putBoolean(cTID('kana'), false);
desc12.putBoolean(sTID("italics"), false);
desc12.putBoolean(cTID('ruby'), false);
desc12.putEnumerated(sTID("baselineDirection"), sTID("baselineDirection"), sTID("rotated"));
desc12.putEnumerated(sTID("textLanguage"), sTID("textLanguage"), sTID("englishLanguage"));
desc12.putEnumerated(sTID("japaneseAlternate"), sTID("japaneseAlternate"), sTID("defaultForm"));
desc12.putDouble(sTID("mojiZume"), 0);
desc12.putEnumerated(sTID("gridAlignment"), sTID("gridAlignment"), sTID("roman"));
desc12.putBoolean(sTID("enableWariChu"), false);
desc12.putInteger(sTID("wariChuCount"), 2);
desc12.putInteger(sTID("wariChuLineGap"), 0);
desc12.putDouble(sTID("wariChuScale"), 0.5);
desc12.putInteger(sTID("wariChuWidow"), 2);
desc12.putInteger(sTID("wariChuOrphan"), 2);
desc12.putEnumerated(sTID("wariChuJustification"), sTID("wariChuJustification"), sTID("wariChuAutoJustify"));
desc12.putInteger(sTID("tcyUpDown"), 0);
desc12.putInteger(sTID("tcyLeftRight"), 0);
desc12.putDouble(sTID("leftAki"), -1);
desc12.putDouble(sTID("rightAki"), -1);
desc12.putInteger(sTID("jiDori"), 0);
desc12.putBoolean(sTID("noBreak"), false);
var desc13 = new ActionDescriptor();
desc13.putDouble(cTID('Rd '), 0);
desc13.putDouble(cTID('Grn '), 0);
desc13.putDouble(cTID('Bl '), 0);
desc12.putObject(cTID('Clr '), sTID("RGBColor"), desc13);
var desc14 = new ActionDescriptor();
desc14.putDouble(cTID('Rd '), 0);
desc14.putDouble(cTID('Grn '), 0);
desc14.putDouble(cTID('Bl '), 0);
desc12.putObject(sTID("strokeColor"), sTID("RGBColor"), desc14);
desc12.putBoolean(cTID('Fl '), true);
desc12.putBoolean(cTID('Strk'), false);
desc12.putBoolean(sTID("fillFirst"), true);
desc12.putBoolean(sTID("fillOverPrint"), false);
desc12.putBoolean(sTID("strokeOverPrint"), false);
desc12.putEnumerated(sTID("lineCap"), sTID("lineCap"), sTID("buttCap"));
desc12.putEnumerated(sTID("lineJoin"), sTID("lineJoin"), sTID("miterJoin"));
desc12.putUnitDouble(sTID("lineWidth"), cTID('#Pnt'), 1);
desc12.putUnitDouble(sTID("miterLimit"), cTID('#Pnt'), 4);
desc12.putDouble(sTID("lineDashOffset"), 0);
desc11.putObject(sTID("baseParentStyle"), cTID('TxtS'), desc12);
desc10.putObject(cTID('TxtS'), cTID('TxtS'), desc11);
list2.putObject(cTID('Txtt'), desc10);
desc2.putList(cTID('Txtt'), list2);
var list3 = new ActionList();
var desc15 = new ActionDescriptor();
desc15.putInteger(cTID('From'), 0);
desc15.putInteger(cTID('T '), 10);
var desc16 = new ActionDescriptor();
desc16.putBoolean(sTID("styleSheetHasParent"), true);
desc16.putEnumerated(cTID('Algn'), cTID('Alg '), cTID('Left'));
desc16.putUnitDouble(sTID("firstLineIndent"), cTID('#Pnt'), 0);
desc16.putUnitDouble(sTID("startIndent"), cTID('#Pnt'), 0);
desc16.putUnitDouble(sTID("endIndent"), cTID('#Pnt'), 0);
desc16.putUnitDouble(sTID("spaceBefore"), cTID('#Pnt'), 0);
desc16.putUnitDouble(sTID("spaceAfter"), cTID('#Pnt'), 0);
desc16.putInteger(sTID("dropCapMultiplier"), 1);
desc16.putDouble(sTID("autoLeadingPercentage"), 1.20000004768372);
desc16.putEnumerated(sTID("leadingType"), sTID("leadingType"), sTID("leadingBelow"));
desc16.putEnumerated(sTID("directionType"), sTID("directionType"), sTID("dirLeftToRight"));
desc16.putEnumerated(sTID("kashidaWidthType"), sTID("kashidaWidthType"), sTID("kashidaWidthMedium"));
desc16.putEnumerated(sTID("justificationMethodType"), sTID("justificationMethodType"), sTID("justifMethodAutomatic"));
desc16.putBoolean(sTID("hyphenate"), true);
desc16.putInteger(sTID("hyphenateWordSize"), 6);
desc16.putInteger(sTID("hyphenatePreLength"), 2);
desc16.putInteger(sTID("hyphenatePostLength"), 2);
desc16.putInteger(sTID("hyphenateLimit"), 0);
desc16.putDouble(sTID("hyphenationZone"), 36);
desc16.putBoolean(sTID("hyphenateCapitalized"), true);
desc16.putDouble(sTID("hyphenationPreference"), 0.5);
desc16.putDouble(sTID("justificationWordMinimum"), 0.80000001192093);
desc16.putDouble(sTID("justificationWordDesired"), 1);
desc16.putDouble(sTID("justificationWordMaximum"), 1.33000004291534);
desc16.putDouble(sTID("justificationLetterMinimum"), 0);
desc16.putDouble(sTID("justificationLetterDesired"), 0);
desc16.putDouble(sTID("justificationLetterMaximum"), 0);
desc16.putDouble(sTID("justificationGlyphMinimum"), 1);
desc16.putDouble(sTID("justificationGlyphDesired"), 1);
desc16.putDouble(sTID("justificationGlyphMaximum"), 1);
desc16.putEnumerated(sTID("singleWordJustification"), cTID('Alg '), cTID('JstA'));
desc16.putBoolean(sTID("hangingRoman"), false);
desc16.putInteger(sTID("autoTCY"), 0);
desc16.putBoolean(sTID("keepTogether"), true);
desc16.putEnumerated(sTID("burasagari"), sTID("burasagari"), sTID("burasagariNone"));
desc16.putEnumerated(sTID("preferredKinsokuOrder"), sTID("preferredKinsokuOrder"), sTID("pushIn"));
desc16.putBoolean(sTID("kurikaeshiMojiShori"), false);
desc16.putBoolean(sTID("textEveryLineComposer"), false);
desc16.putDouble(sTID("defaultTabWidth"), 36);
var desc17 = new ActionDescriptor();
desc17.putString(sTID("fontPostScriptName"), "MyriadPro-Regular");
desc17.putString(cTID('FntN'), "Myriad Pro");
desc17.putString(cTID('FntS'), "Regular");
desc17.putInteger(cTID('Scrp'), 0);
desc17.putInteger(cTID('FntT'), 0);
desc17.putUnitDouble(cTID('Sz '), cTID('#Pnt'), 12);
desc17.putDouble(cTID('HrzS'), 100);
desc17.putDouble(cTID('VrtS'), 100);
desc17.putBoolean(sTID("syntheticBold"), false);
desc17.putBoolean(sTID("syntheticItalic"), false);
desc17.putBoolean(sTID("autoLeading"), true);
desc17.putInteger(cTID('Trck'), 0);
desc17.putUnitDouble(cTID('Bsln'), cTID('#Pnt'), 0);
desc17.putDouble(sTID("characterRotation"), 0);
desc17.putEnumerated(cTID('AtKr'), cTID('AtKr'), sTID("metricsKern"));
desc17.putEnumerated(sTID("fontCaps"), sTID("fontCaps"), cTID('Nrml'));
desc17.putEnumerated(sTID("digitSet"), sTID("digitSet"), sTID("arabicDigits"));
desc17.putEnumerated(sTID("kashidas"), sTID("kashidas"), sTID("kashidaDefault"));
desc17.putEnumerated(sTID("diacVPos"), sTID("diacVPos"), sTID("diacVPosOpenType"));
desc17.putUnitDouble(sTID("diacXOffset"), cTID('#Pnt'), 0);
desc17.putUnitDouble(sTID("diacYOffset"), cTID('#Pnt'), 0);
desc17.putUnitDouble(sTID("markYDistFromBaseline"), cTID('#Pnt'), 0);
desc17.putEnumerated(sTID("baseline"), sTID("baseline"), cTID('Nrml'));
desc17.putEnumerated(sTID("strikethrough"), sTID("strikethrough"), sTID("strikethroughOff"));
desc17.putEnumerated(cTID('Undl'), cTID('Undl'), sTID("underlineOff"));
desc17.putBoolean(sTID("ligature"), true);
desc17.putBoolean(sTID("altligature"), false);
desc17.putBoolean(sTID("contextualLigatures"), true);
desc17.putBoolean(sTID("alternateLigatures"), false);
desc17.putBoolean(sTID("oldStyle"), false);
desc17.putBoolean(sTID("fractions"), false);
desc17.putBoolean(sTID("ordinals"), false);
desc17.putBoolean(sTID("swash"), false);
desc17.putBoolean(sTID("titling"), false);
desc17.putBoolean(sTID("connectionForms"), false);
desc17.putBoolean(sTID("stylisticAlternates"), false);
desc17.putBoolean(sTID("ornaments"), false);
desc17.putEnumerated(sTID("figureStyle"), sTID("figureStyle"), cTID('Nrml'));
desc17.putEnumerated(sTID("baselineDirection"), sTID("baselineDirection"), sTID("withStream"));
desc17.putEnumerated(sTID("textLanguage"), sTID("textLanguage"), sTID("englishLanguage"));
var desc18 = new ActionDescriptor();
desc18.putDouble(cTID('Rd '), 0);
desc18.putDouble(cTID('Grn '), 0);
desc18.putDouble(cTID('Bl '), 0);
desc17.putObject(cTID('Clr '), sTID("RGBColor"), desc18);
var desc19 = new ActionDescriptor();
desc19.putDouble(cTID('Rd '), 0);
desc19.putDouble(cTID('Grn '), 0);
desc19.putDouble(cTID('Bl '), 0);
desc17.putObject(sTID("strokeColor"), sTID("RGBColor"), desc19);
desc16.putObject(sTID("defaultStyle"), cTID('TxtS'), desc17);
desc15.putObject(sTID("paragraphStyle"), sTID("paragraphStyle"), desc16);
list3.putObject(sTID("paragraphStyleRange"), desc15);
desc2.putList(sTID("paragraphStyleRange"), list3);
var list4 = new ActionList();
desc2.putList(sTID("kerningRange"), list4);
desc1.putObject(cTID('Usng'), cTID('TxLr'), desc2);
executeAction(cTID('Mk '), desc1, DialogModes.NO);
};




Я смог удалить несколько строк без нарушения функционала. Чистил я до тех пор, пока это было легко: ищешь новый неповторяющийся дескриптор, смотришь, что он делает, удаляешь, проверяешь работу и, если всё работает без видимых изменений, продолжаешь так делать и дальше. Когда ошибки после каждого такого грубого хирургического вмешательства в тело функции стали возникать всё чаще, я остановился – лучшее враг хорошего. Затем нашёл, в каком месте задаётся шрифт, его размер и цвет, перевёл эти места в задаваемые переменные.



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

Немного причёсанный код функции
// Объект, содержащий необходимые параметры для создания текстового слоя
var sizeTestParam = {
text: сам текст
fontName: имя шрифта (postcript),
fontSize: размер шрифта,
color: цвет (объект)
}

newTextLayer(sizeTestParam, 'Left'); //Образец выполнения. Выравнивание тоже можно было поместить в объект

// Сама функция создания
function newTextLayer() {
var paramset = arguments[0];
var justific = arguments[1];
if (justific===undefined) {justific = 'Cntr'} // Выравнивание текста
var text = paramset.text + ""||"";
var textLength = text.length;
var fontName = paramset.fontName||"TimesNewRomanPS-BoldMT";
var fontSize = paramset.fontSize||5;
var colorR = paramset.color.rgb.red||0;
var colorG = paramset.color.rgb.green||0;
var colorB = paramset.color.rgb.blue||0;

var desc1 = new ActionDescriptor();
var ref1 = new ActionReference();
ref1.putClass(cTID('TxLr'));
desc1.putReference(cTID('null'), ref1);
var desc2 = new ActionDescriptor();
desc2.putString(cTID('Txt '), text);
// КООРДИНАТЫ ТЕКСТА (СЕЙЧАС В ПРОЦЕНТАХ)
var desc4 = new ActionDescriptor();
desc4.putUnitDouble(cTID('Hrzn'), cTID('#Prc'), 5);
desc4.putUnitDouble(cTID('Vrtc'), cTID('#Prc'), 5);
desc2.putObject(cTID('TxtC'), cTID('Pnt '), desc4);

desc2.putEnumerated(sTID("textGridding"), sTID("textGridding"), cTID('None'));
desc2.putEnumerated(cTID('Ornt'), cTID('Ornt'), cTID('Hrzn'));
desc2.putEnumerated(cTID('AntA'), cTID('Annt'), cTID('AnCr'));
var list1 = new ActionList();
var desc7 = new ActionDescriptor();
desc7.putEnumerated(sTID("textType"), sTID("textType"), cTID('Pnt '));
desc7.putEnumerated(cTID('Ornt'), cTID('Ornt'), cTID('Hrzn'));
desc7.putInteger(sTID("rowCount"), 1);
desc7.putInteger(sTID("columnCount"), 1);
desc7.putBoolean(sTID("rowMajorOrder"), true);
desc7.putUnitDouble(sTID("rowGutter"), cTID('#Pxl'), 0);
desc7.putUnitDouble(sTID("columnGutter"), cTID('#Pxl'), 0);
desc7.putUnitDouble(cTID('Spcn'), cTID('#Pxl'), 0);
desc7.putEnumerated(sTID("frameBaselineAlignment"), sTID("frameBaselineAlignment"), sTID("alignByAscent"));
desc7.putUnitDouble(sTID("firstBaselineMinimum"), cTID('#Pxl'), 0);
var desc9 = new ActionDescriptor();
desc9.putDouble(cTID('Hrzn'), 0);
desc9.putDouble(cTID('Vrtc'), 0);
desc7.putObject(cTID('base'), cTID('Pnt '), desc9);
list1.putObject(sTID("textShape"), desc7);
desc2.putList(sTID("textShape"), list1);
var list2 = new ActionList();
var desc10 = new ActionDescriptor();
desc10.putInteger(cTID('From'), 0);

// КОЛИЧЕСТВО СИМВОЛОВ В ТЕКСТЕ (ИНАЧЕ ДРУГОЙ ЦВЕТ-РАЗМЕР)
desc10.putInteger(cTID('T '), textLength);

var desc11 = new ActionDescriptor();
desc11.putBoolean(sTID("styleSheetHasParent"), true);

// ШРИФТ
desc11.putString(sTID("fontPostScriptName"), fontName);
desc11.putInteger(cTID('Scrp'), 0);
desc11.putInteger(cTID('FntT'), 1);

// РАЗМЕР ТЕКСТА
desc11.putUnitDouble(cTID('Sz '), cTID('#Pnt'), fontSize);
desc11.putEnumerated(sTID("digitSet"), sTID("digitSet"), sTID("defaultDigits"));
desc11.putUnitDouble(sTID("markYDistFromBaseline"), cTID('#Pxl'), 100);

// ЦВЕТ ШРИФТА
var colorDesc = new ActionDescriptor();
colorDesc.putDouble(cTID('Rd '), colorR);
colorDesc.putDouble(cTID('Grn '), colorG);
colorDesc.putDouble(cTID('Bl '), colorB);


desc11.putObject(cTID('Clr '), sTID("RGBColor"), colorDesc);
var desc13 = new ActionDescriptor();
desc13.putString(sTID("fontPostScriptName"), "MyriadPro-Regular");
desc13.putString(cTID('FntN'), "Myriad Pro");
desc13.putString(cTID('FntS'), "Regular");
desc13.putInteger(cTID('Scrp'), 0);
desc13.putInteger(cTID('FntT'), 0);
desc13.putUnitDouble(cTID('Sz '), cTID('#Pxl'), 12);
desc13.putDouble(cTID('HrzS'), 100);
desc13.putDouble(cTID('VrtS'), 100);
desc13.putBoolean(sTID("syntheticBold"), false);
desc13.putBoolean(sTID("syntheticItalic"), false);
desc13.putBoolean(sTID("autoLeading"), true);
desc13.putInteger(cTID('Trck'), 0);
desc13.putUnitDouble(cTID('Bsln'), cTID('#Pxl'), 0);
desc13.putEnumerated(cTID('AtKr'), cTID('AtKr'), sTID("metricsKern"));
desc13.putEnumerated(sTID("fontCaps"), sTID("fontCaps"), cTID('Nrml'));
desc13.putEnumerated(sTID("digitSet"), sTID("digitSet"), sTID("defaultDigits"));
desc13.putEnumerated(sTID("dirOverride"), sTID("dirOverride"), sTID("dirOverrideDefault"));
desc13.putEnumerated(sTID("kashidas"), sTID("kashidas"), sTID("kashidaDefault"));
desc13.putEnumerated(sTID("diacVPos"), sTID("diacVPos"), sTID("diacVPosOpenType"));
desc13.putUnitDouble(sTID("diacXOffset"), cTID('#Pxl'), 0);
desc13.putUnitDouble(sTID("diacYOffset"), cTID('#Pxl'), 0);
desc13.putUnitDouble(sTID("markYDistFromBaseline"), cTID('#Pxl'), 100);
desc13.putEnumerated(sTID("baseline"), sTID("baseline"), cTID('Nrml'));
desc13.putEnumerated(sTID("otbaseline"), sTID("otbaseline"), cTID('Nrml'));
desc13.putEnumerated(sTID("strikethrough"), sTID("strikethrough"), sTID("strikethroughOff"));
desc13.putEnumerated(cTID('Undl'), cTID('Undl'), sTID("underlineOff"));
desc13.putUnitDouble(sTID("underlineOffset"), cTID('#Pxl'), 0);
desc13.putBoolean(sTID("ligature"), true);
desc13.putBoolean(sTID("altligature"), false);
desc13.putBoolean(sTID("contextualLigatures"), false);
desc13.putBoolean(sTID("alternateLigatures"), false);
desc13.putBoolean(sTID("oldStyle"), false);
desc13.putBoolean(sTID("fractions"), false);
desc13.putBoolean(sTID("ordinals"), false);
desc13.putBoolean(sTID("swash"), false);
desc13.putBoolean(sTID("titling"), false);
desc13.putBoolean(sTID("connectionForms"), false);
desc13.putBoolean(sTID("stylisticAlternates"), false);
desc13.putBoolean(sTID("ornaments"), false);
desc13.putBoolean(sTID("justificationAlternates"), false);
desc13.putEnumerated(sTID("figureStyle"), sTID("figureStyle"), cTID('Nrml'));
desc13.putBoolean(sTID("proportionalMetrics"), false);
desc13.putBoolean(cTID('kana'), false);
desc13.putBoolean(sTID("italics"), false);
desc13.putBoolean(cTID('ruby'), false);
desc13.putEnumerated(sTID("baselineDirection"), sTID("baselineDirection"), sTID("rotated"));
desc13.putEnumerated(sTID("textLanguage"), sTID("textLanguage"), sTID("englishLanguage"));
desc13.putEnumerated(sTID("japaneseAlternate"), sTID("japaneseAlternate"), sTID("defaultForm"));
desc13.putDouble(sTID("mojiZume"), 0);
desc13.putEnumerated(sTID("gridAlignment"), sTID("gridAlignment"), sTID("roman"));
desc13.putBoolean(sTID("enableWariChu"), false);
desc13.putInteger(sTID("wariChuCount"), 2);
desc13.putInteger(sTID("wariChuLineGap"), 0);
desc13.putDouble(sTID("wariChuScale"), 0.5);
desc13.putInteger(sTID("wariChuWidow"), 2);
desc13.putInteger(sTID("wariChuOrphan"), 2);
desc13.putEnumerated(sTID("wariChuJustification"), sTID("wariChuJustification"), sTID("wariChuAutoJustify"));
desc13.putInteger(sTID("tcyUpDown"), 0);
desc13.putInteger(sTID("tcyLeftRight"), 0);
desc13.putDouble(sTID("leftAki"), -1);
desc13.putDouble(sTID("rightAki"), -1);
desc13.putInteger(sTID("jiDori"), 0);
desc13.putBoolean(sTID("noBreak"), false);
desc13.putEnumerated(sTID("lineCap"), sTID("lineCap"), sTID("buttCap"));
desc13.putEnumerated(sTID("lineJoin"), sTID("lineJoin"), sTID("miterJoin"));
desc13.putUnitDouble(sTID("lineWidth"), cTID('#Pxl'), 1);
desc13.putUnitDouble(sTID("miterLimit"), cTID('#Pxl'), 4);
desc13.putDouble(sTID("lineDashOffset"), 0);
desc11.putObject(sTID("baseParentStyle"), cTID('TxtS'), desc13);
desc10.putObject(cTID('TxtS'), cTID('TxtS'), desc11);
list2.putObject(cTID('Txtt'), desc10);
desc2.putList(cTID('Txtt'), list2);
var list3 = new ActionList();
var desc16 = new ActionDescriptor();
desc16.putInteger(cTID('From'), 0);
desc16.putInteger(cTID('T '), 16);
var desc17 = new ActionDescriptor();
desc17.putBoolean(sTID("styleSheetHasParent"), true);

// ВЫРАВНИВАНИЕ
desc17.putEnumerated(cTID('Algn'), cTID('Alg '), cTID(justific));

desc17.putUnitDouble(sTID("firstLineIndent"), cTID('#Pxl'), 0);
desc17.putUnitDouble(sTID("startIndent"), cTID('#Pxl'), 0);
desc17.putUnitDouble(sTID("endIndent"), cTID('#Pxl'), 0);
desc17.putUnitDouble(sTID("spaceBefore"), cTID('#Pxl'), 0);
desc17.putUnitDouble(sTID("spaceAfter"), cTID('#Pxl'), 0);
desc17.putInteger(sTID("dropCapMultiplier"), 1);
desc17.putDouble(sTID("autoLeadingPercentage"), 1.20000004768372);
desc17.putEnumerated(sTID("leadingType"), sTID("leadingType"), sTID("leadingBelow"));
desc17.putEnumerated(sTID("directionType"), sTID("directionType"), sTID("dirLeftToRight"));
desc17.putEnumerated(sTID("kashidaWidthType"), sTID("kashidaWidthType"), sTID("kashidaWidthMedium"));
desc17.putEnumerated(sTID("justificationMethodType"), sTID("justificationMethodType"), sTID("justifMethodAutomatic"));
desc17.putBoolean(sTID("hyphenate"), true);
desc17.putInteger(sTID("hyphenateWordSize"), 6);
desc17.putInteger(sTID("hyphenatePreLength"), 2);
desc17.putInteger(sTID("hyphenatePostLength"), 2);
desc17.putInteger(sTID("hyphenateLimit"), 0);
desc17.putDouble(sTID("hyphenationZone"), 36);
desc17.putBoolean(sTID("hyphenateCapitalized"), true);
desc17.putDouble(sTID("hyphenationPreference"), 0.5);
desc17.putDouble(sTID("justificationWordMinimum"), 0.80000001192093);
desc17.putDouble(sTID("justificationWordDesired"), 1);
desc17.putDouble(sTID("justificationWordMaximum"), 1.33000004291534);
desc17.putDouble(sTID("justificationLetterMinimum"), 0);
desc17.putDouble(sTID("justificationLetterDesired"), 0);
desc17.putDouble(sTID("justificationLetterMaximum"), 0);
desc17.putDouble(sTID("justificationGlyphMinimum"), 1);
desc17.putDouble(sTID("justificationGlyphDesired"), 1);
desc17.putDouble(sTID("justificationGlyphMaximum"), 1);
desc17.putEnumerated(sTID("singleWordJustification"), cTID('Alg '), cTID('JstA'));
desc17.putBoolean(sTID("hangingRoman"), false);
desc17.putInteger(sTID("autoTCY"), 0);
desc17.putBoolean(sTID("keepTogether"), true);
desc17.putEnumerated(sTID("burasagari"), sTID("burasagari"), sTID("burasagariNone"));
desc17.putEnumerated(sTID("preferredKinsokuOrder"), sTID("preferredKinsokuOrder"), sTID("pushIn"));
desc17.putBoolean(sTID("kurikaeshiMojiShori"), false);
desc17.putBoolean(sTID("textEveryLineComposer"), false);
desc17.putDouble(sTID("defaultTabWidth"), 36);
var desc18 = new ActionDescriptor();
desc18.putString(sTID("fontPostScriptName"), "MyriadPro-Regular");
desc18.putString(cTID('FntN'), "Myriad Pro");
desc18.putString(cTID('FntS'), "Regular");
desc18.putInteger(cTID('Scrp'), 0);
desc18.putInteger(cTID('FntT'), 0);
desc18.putUnitDouble(cTID('Sz '), cTID('#Pxl'), 12);
desc18.putDouble(cTID('HrzS'), 100);
desc18.putDouble(cTID('VrtS'), 100);
desc18.putBoolean(sTID("syntheticBold"), false);
desc18.putBoolean(sTID("syntheticItalic"), false);
desc18.putBoolean(sTID("autoLeading"), true);
desc18.putInteger(cTID('Trck'), 0);
desc18.putUnitDouble(cTID('Bsln'), cTID('#Pxl'), 0);
desc18.putDouble(sTID("characterRotation"), 0);
desc18.putEnumerated(cTID('AtKr'), cTID('AtKr'), sTID("metricsKern"));
desc18.putEnumerated(sTID("fontCaps"), sTID("fontCaps"), cTID('Nrml'));
desc18.putEnumerated(sTID("digitSet"), sTID("digitSet"), sTID("arabicDigits"));
desc18.putEnumerated(sTID("kashidas"), sTID("kashidas"), sTID("kashidaDefault"));
desc18.putEnumerated(sTID("diacVPos"), sTID("diacVPos"), sTID("diacVPosOpenType"));
desc18.putUnitDouble(sTID("diacXOffset"), cTID('#Pxl'), 0);
desc18.putUnitDouble(sTID("diacYOffset"), cTID('#Pxl'), 0);
desc18.putUnitDouble(sTID("markYDistFromBaseline"), cTID('#Pxl'), 0);
desc18.putEnumerated(sTID("baseline"), sTID("baseline"), cTID('Nrml'));
desc18.putEnumerated(sTID("strikethrough"), sTID("strikethrough"), sTID("strikethroughOff"));
desc18.putEnumerated(cTID('Undl'), cTID('Undl'), sTID("underlineOff"));
desc18.putBoolean(sTID("ligature"), true);
desc18.putBoolean(sTID("altligature"), false);
desc18.putBoolean(sTID("contextualLigatures"), true);
desc18.putBoolean(sTID("alternateLigatures"), false);
desc18.putBoolean(sTID("oldStyle"), false);
desc18.putBoolean(sTID("fractions"), false);
desc18.putBoolean(sTID("ordinals"), false);
desc18.putBoolean(sTID("swash"), false);
desc18.putBoolean(sTID("titling"), false);
desc18.putBoolean(sTID("connectionForms"), false);
desc18.putBoolean(sTID("stylisticAlternates"), false);
desc18.putBoolean(sTID("ornaments"), false);
desc18.putEnumerated(sTID("figureStyle"), sTID("figureStyle"), cTID('Nrml'));
desc18.putEnumerated(sTID("baselineDirection"), sTID("baselineDirection"), sTID("withStream"));
desc18.putEnumerated(sTID("textLanguage"), sTID("textLanguage"), sTID("englishLanguage"));
var desc19 = new ActionDescriptor();
desc19.putDouble(cTID('Rd '), 0);
desc19.putDouble(cTID('Grn '), 0);
desc19.putDouble(cTID('Bl '), 0);
desc18.putObject(cTID('Clr '), sTID("RGBColor"), desc19);
var desc20 = new ActionDescriptor();
desc20.putDouble(cTID('Rd '), 0);
desc20.putDouble(cTID('Grn '), 0);
desc20.putDouble(cTID('Bl '), 0);
desc18.putObject(sTID("strokeColor"), sTID("RGBColor"), desc20);
desc17.putObject(sTID("defaultStyle"), cTID('TxtS'), desc18);
desc16.putObject(sTID("paragraphStyle"), sTID("paragraphStyle"), desc17);
list3.putObject(sTID("paragraphStyleRange"), desc16);
desc2.putList(sTID("paragraphStyleRange"), list3);
var list4 = new ActionList();
desc2.putList(sTID("kerningRange"), list4);
desc1.putObject(cTID('Usng'), cTID('TxLr'), desc2);
executeAction(cTID('Mk '), desc1, DialogModes.NO);
};


И ещё кое-что добавлю. Я не уверен, но кажется не все низкоуровневые функции выполняются быстрее стандартных методов — однажды мне показалось, что перемещение слоя на заданное количество пикселов по X и Y выполнялось быстрее именно стандартным методом. Экспериментируйте.



Пасхальные яйца



Без этого нельзя ну никак. В скрипте есть несколько пасхалок, и некоторые из них видны только мне в исходном коде (например, одна функция называется fireStarter). Но есть те, которые я оставил для своих пользователей. Ниже спойлер, кому интересно.

Тык
3е сентября. Кнопка Simple Mode имеет иконку, изображающую (я старался, по крайней мере) отрывной календарь с этой датой.

«Нужно боольше золота!». Если несколько раз кликнуть по кнопке info, то — правильно, появляются некоторые фразы из Warcraft III вместо справки.

Морской бой. Не совсем он, на самом деле. Скорее, что-то по его мотивам. Если в одном из полей, где пользователь заводит интервалы между элементами месяца ввести не цифру, а слово «egg», то будет предложено сыграть в морской бой. Игрок выбирает количество выстрелов, мины, авиаудары и идёт искать вражеские корабли. Если топит все корабли — выиграл, иначе — проиграл, всё просто.







Справочные материалы к скрипту



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



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

И для полного комплекта неплохо было бы иметь свой маленький форум для вопросов. Для этого я воспользовался сервисом «Google группы».

Плюсом, я добавил к скрипту директорию «Examples» с созданными календарями. Один из них перекидной с фотографиями моего города, сделанные лучшим другом — отдельная гордость.

Картинки с превьюшками




На мой взгляд, всего этого должно быть достаточно для моего пользователя.

Разумеется, я добавил все ссылки — на руководство, видеоуроки и форум прямо в справочное меню скрипта. Ролики и форум находятся в интернете, руководство прилагается к скрипту, но может быть дополнительно скачано из моей личной публичной папки на диске Google (помимо английской версии, есть и русская).



Открытие магазинчика



«И разве мой талант, и мой душевный жар не заслужили скромный гонорар?»

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



В конце



Весь этап разработки, от идеи до выхода в свет, уложился в период с середины октября 2015 года до 28 сентября 2016. Сезоны менялись — осень, зима, весна, лето, снова осень — а мои мысли были заняты этим проектом. Если вы захотите спросить меня — а стоило ли оно того, то отвечу однозначно — для меня стоило точно. Я получил идеальный для себя результат, многократно превзошедший оригинальную задумку — что может быть лучше?



Теперь я хочу только одного. Отдохнуть. Отпустив результат работы в свободное плавание, я почувствовал тоску, облегчение и невероятную усталость. Да, есть ещё поддержка и прочие вещи, которые требуют моего внимания, но это уже другое. А вообще, уже есть идеи, как ещё его улучшить, или даже сделать совершенно новую версию с другим подходом. Но чуть позже.



А, из побочных эффектов — я стал постоянно обращать внимание на календари, оказавшиеся поблизости. Каждый раз я задаю себе вопрос — а мой скрипт так сможет? И теперь в большинстве случаев я отвечаю себе «Да». Бывают и ответы «нет», конечно, но это особые календари, где, к примеру, все числа закручены в спираль. Хотя, если написать скрипт… Впрочем, нет, не сейчас.

Код писался в свободное время, временами и по ночам, я был одержим этим проектом, спасибо жене за понимание.

И всем, кто осилил эту статью – вы славные ребята. Надеюсь, каждый из вас нашёл что-нибудь полезное здесь для себя.



P.S. Если кому понравился котяня, то он вот здесь (~ 4 Mb).



Дополнительные материалы:



Документация Adobe Photoshop — лично я использовал документацию к версии CS5 (для основного кода) и CS2 (там есть раздел по UI)

Scripting Listener — плагин на Photoshop CC от Adobe для Windows и MacOS

Adobe ExtendScript Toolkit — версии CS3-CS5 и версия СС
Original source: habrahabr.ru.

https://habrahabr.ru/post/311390/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Про девушек желающих стать программистами

Четверг, 29 Сентября 2016 г. 11:56 (ссылка)

Привет, меня зовут Максим, я хочу рассказать вам немного про девушек, которые становятся программистами.

Буквально пара слов о себе: я работаю в компании JetBrains front-end разрабочиком, до этого я 5 лет работал full-stack разработчиком и писал на php и javascript, а еще раньше фрилансил на всем подряд.



Параллельно со свой работой я являюсь наставником в JS интенсиве от проекта htmlacademy. Это такой ускоренный курс обучения программированию на JavaScript, где любой желающий может пройти путь от var a = 1; до написания собственного веб-приложения. Причем технологический уровень там достаточно серьезный, webpack и прочие хипстерские штучки, т.е. совсем не VanilaJS + jQuery.



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

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



Я пока не смог найти объяснение этому странному феномену, возможно все дело в женской усидчивости, возможно, в каком-то природном трудолюбии, но факт остается фактом. Многие молодые люди просто не доходят до финального задания и только девушки, в большинстве своем, доводят обучение до логического завершения.



Так что милые и прекрасные дамы, если вы вдруг надумали научиться программировать но боялись это сделать — попробуйте, может оказаться, что это именно то, к чему у вас есть талант. И возможно совсем скоро индустрия IT перевернет гендерные предрассудки, а мужчины станут в ней таким же меньшенством, как женщины сейчас.



image



P.S. На фото девушка Виктория, прекрасная студентка из последнего потока, которая успешно завершила свой проект, и как я уверен скоро найдет замечательное место работы :)

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/311274/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

JSX: антипаттерн или нет?

Среда, 28 Сентября 2016 г. 15:14 (ссылка)

Довольно часто приходится слышать, что React и особенно JSX-шаблоны – это плохо, и хороший разработчик так не делает. Однако нечасто объясняется, чем именно вредит смешивание верстки и шаблонов в одном файле. И с этим мы попробуем сегодня разобраться.



Подход "каждой технологии свой файл" использовался с начала существования веба, поэтому неудивительно, что слом этого шаблона вызывает отторжение некоторых разработчиков. Но перед тем, как заявлять "нет, мы так делать не будем никогда", будет полезно разобраться истории и понять, почему JSX пользоваться можно, а смешивать скрипты и html – нет.





История



С самого начала Веб состоял только из статических HTML-страниц. Вы открывали адрес, сервер возвращал контент страницы. Верстка, стили, скрипты – все было в общей куче.



Однако по мере усложнения сайтов появилась необходимость переиспользовать стили и скрипты на разных страницах. Со временем подход "CSS и JS отдельно от HTML" стал общей рекомендацией. Особенно при условии, когда HTML генерируется серверным движком, в отличие от CSS и JS файлов, которые меняются только при разработке и отдаются сервером как есть. Разделение контента на статический и динамический позволяет пользователю загружать меньше данных, за счет кеширования, а разработчику удобнее редактировать статические файлы, а не искать куски Javascript в шаблонах используемой CMS.



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



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



Наиболее подходящим способом организации такой массы кода стал компонентный подход. Каждый более или менее независимый блок оформляется в отдельный компонент, со своими стилями, шаблоном и Javascript, а потом из этих блоков собирается страница целиком.



Этот подход реализуется большинством современных JS-фреймворков, но с одним отличием: Angular, Ember и Marionette поощряют создание отдельного файла с шаблоном, а React предлагает писать HTML внутри JS. И это становится красной тряпкой для некоторых разработчиков.



Шаблон и компонент



А в чем смысл создания отдельного файла с шаблоном? Часто приводится довод: разделить разные сущности, потому что так правильно. Хотя исторически причиной отделения HTML и JS было разделение статики и динамики, что уже неактуально для рендеринга на клиенте



Итак, насколько же компонент и его шаблон разные? Возьмем фрагмент кода





Линиями показаны взаимосвязи, в которых изменение изменения одного файла затронут и другой. Если бы это была связь между двумя JS-модулями, рефакторинг напрашивается сам собой. Но для view и шаблона так не делают, потому что это неправильно.



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



А насколько это вредит в реальному проекту? Попробуем провести эксперимент и объединить шаблон с JS:



import Marionette from 'backbone.marionette';
import {warningText} from '../constants';

export default Marionette.ItemView.extend({
template(templateData) {
const {title, img_url, count} = templateData;
const isSelected = this.model.isSelected();
const isOverLimit = this.model.get('count') > this.model.get('maxAvailable')
return `

${title}








${isOverLimit ? warningText : ''}
`;
},
events: {
'click btn-less': 'onLessClick',
'click btn-more': 'onMoreClick'
}
});


Благодаря появлению в EcmaScript6 template strings, создание встроенных шаблонов стало приятнее. Подсветка HTML внутри строки так же настраивается. А по сравнению с прошлым примером, кода стало меньше за счет удаления прослойки, которая готовила данные в шаблон.



Так стоит ли так делать в своих проектах с использованием не React? Скорее всего, нет, потому что они не заточены на такой стиль. Но я надеюсь, что теперь стало понятнее, что встраивание HTML в код компонента не так уж плохо и приносит пользу в некоторых случаях.


Original source: habrahabr.ru.

https://habrahabr.ru/post/311226/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Создание шейдеров на основе Babylon.js и WebGL: теория и примеры

Вторник, 27 Сентября 2016 г. 13:38 (ссылка)

Во время своего доклада на второй день конференции Build 2014 евангелисты Microsoft Стивен Гуггенхаймер и Джон Шевчук рассказали о реализации поддержки Babylon.js для Oculus Rift. Одним из ключевых пунктов их демонстрации было упоминание разработанной нами технологии имитации линз:





Я также присутствовал на докладе Фрэнка Оливье и Бена Констебля на тему использования графики в IE с применением Babylon.js.



Эти доклады напомнили мне об одном вопросе, который мне часто задают в отношении Babylon.js: «Что вы подразумеваете под шейдерами?» Я решил посвятить этому вопросу целую статью с целью объяснить принцип работы шейдеров и привести несколько примеров их основных типов.

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



Теория



Прежде чем начинать наши опыты, нужно понять, как всё функционирует.



Работая с аппаратно ускоренной 3D графикой, мы имеем дело с двумя разными процессорами: центральным (CPU) и графическим (GPU). Графический процессор – это всего лишь разновидность крайне специализированного центрального процессора.



GPU – это конечный автомат, настраиваемый посредством CPU. К примеру, именно CPU дает GPU команду отображать линии вместо треугольников, включить прозрачность и т. д.



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



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



Вершина – это своего рода точка в 3D пространстве (в отличие от точки в 2D пространстве).



Существует 2 вида шейдеров: вершинные и пиксельные (фрагментные) шейдеры.



Графический пайплайн



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



С помощью буфера индексов, содержащего список индексов вершин, 3 вершины объединяются в треугольник. Каждая запись в буфере индексов соответствует номеру вершины в буфере вершин (это позволяет избежать дублирования вершин).



К примеру, буфер индексов на примере ниже – это список из двух граней: [1 2 3 1 3 4]. Первая грань содержит вершины 1, 2 и 3. Вторая грань содержит вершины 1, 3 и 4. Таким образом, в данном случае геометрия состоит из четырех вершин:





Vertex — Вершина

Vertex Buffer — Буфер вершин

Index Bufer — Буфер индексов

Face — Грань


Вершинный шейдер выполняется на каждой вершине треугольника. Основное предназначение вершинного шейдера – отобразить пиксель для каждой вершины (то есть выполнить проекцию 3D вершины на 2D экран).





Используя эти 3 пикселя (задающие параметры 2D треугольника на экране), GPU проанализирует все относящиеся к пикселю (по крайней мере, к его положению) значения и применит пиксельный шейдер, чтобы сгенерировать цвет для каждого пикселя данного треугольника.





То же самое выполняется для всех граней в буфере индексов.



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



GLSL



Как было сказано ранее, для рендеринга треугольников графическому процессору потребуются 2 шейдера: вершинный и пиксельный. Оба пишутся на специальном языке под названием GLSL (Graphics Library Shader Language), который немного похож на C.



Специально для Internet Explorer 11 мы разработали компилятор, преобразовывающий GLSL в HLSL (High Level Shader Language) – шейдерный язык DirectX 11. Это позволило нам повысить безопасность кода шейдера:





Вот пример простого вершинного шейдера:



precision highp float;

// Attributes
attribute vec3 position;
attribute vec2 uv;

// Uniforms
uniform mat4 worldViewProjection;

// Varying
varying vec2 vUV;

void main(void) {
gl_Position = worldViewProjection * vec4(position, 1.0);

vUV = uv;
}




Структура вершинного шейдера



Вершинный шейдер содержит следующие элементы:




  • Атрибуты: Атрибут определяет часть вершины. По умолчанию вершина должна иметь, по крайней мере, данные о положении (vector3:x, y, z). Но вы, как разработчик, можете предоставить больше данных. К примеру, в коде выше есть vector2 под названием uv (координаты текстуры, позволяющие нам применять 2D текстуру на 3D объект).




  • Uniform-переменные: Определяются центральным процессором и используются шейдером. Единственная uniform-переменная, которая есть у нас в данном случае, – это матрица, используемая для проекции положения вершины (x, y, z) на экран (x, y).




  • Varying-переменные: Представляют собой значения, которые создаются вершинным шейдером и передаются в пиксельный. В нашем случае вершинный шейдер передаст в пиксельный шейдер значение vUV (простая копия uv). Следовательно, здесь определяются координаты текстуры и положение пикселя. GPU добавит эти значения, а использовать их будет непосредственно пиксельный шейдер.




  • main: Функция main() – это код, который выполняется в GPU для каждой вершины. Он должен как минимум давать значение для gl_position (положение текущей вершины на экране).



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



Волшебство в основе матриц



Матрица в нашем шейдере называется worldViewProjection. Она проецирует положение вершины в переменную gl_position. Но как же нам получить значение этой матрицы? Поскольку это uniform-переменная, нам нужно определить её на стороне CPU (с помощью JavaScript).



Это трудный для понимания аспект работы с 3D графикой. Нужно неплохо разбираться в сложных математических вычислениях (или пользоваться 3D движком вроде Babylon.js, о чем мы поговорим позже).



Матрица worldViewProjection состоит из трех отдельных матриц:





В результате получается матрица, позволяющая преобразовывать 3D вершины в 2D пиксели, учитывая при этом позицию точки обзора и всё, что относится к положению, масштабу и повороту текущего объекта.



Задача 3D дизайнера – создать эту матрицу и поддерживать актуальность её данных.



И снова шейдеры



После того как вершинный шейдер выполнится на каждой вершине (то есть 3 раза), мы получим 3 пикселя с правильным значением vUV и gl_position. Далее GPU перенесет эти значения на каждый пиксель внутри треугольника, образованного тремя основными пикселями.



Затем к каждому пикселю будет применен пиксельный шейдер:



precision highp float;
varying vec2 vUV;
uniform sampler2D textureSampler;

void main(void) {
gl_FragColor = texture2D(textureSampler, vUV);
}


Структура пиксельного (или фрагментного) шейдера



По своей структуре пиксельный шейдер похож на вершинный:




  • Varying-переменные: Представляют собой значения, которые создаются вершинным шейдером и передаются в пиксельный шейдер. В нашем случае пиксельный шейдер получит из вершинного шейдера значение vUV.




  • Uniform-переменные: Определяются центральным процессором и используются шейдером. Единственная uniform-переменная, которая есть у нас в данном случае – это семплер, который нужен для считывания цветов текстуры.




  • main: Функция main – это код, который выполняется в GPU для каждого пикселя. Он должен как минимум давать значение для gl_FragColor (цвет текущего пикселя).



Этот пиксельный шейдер очень простой: он считывает цвет текстуры, используя координаты текстуры из вершинного шейдера (который, в свою очередь, получил их из вершины).



Вот что получилось в итоге. Рендеринг выполняется в реальном времени; вы можете двигать сферу мышкой.



Чтобы получить такой результат, нужно хорошенько поработать с кодом WebGL. Конечно, WebGL – это очень мощный API. Но он низкоуровневый, поэтому придется всё делать самостоятельно: от создания буфера до определения структуры вершин. Вам также нужно будет выполнять множество математических вычислений, настраивать состояния, управлять загрузкой текстуры и так далее.



Слишком сложно? BABYLON.ShaderMaterial спешит на помощь



Я знаю, о чем вы подумали: «Шейдеры – это, конечно, круто, но я не хочу разбираться во всех тонкостях WebGL и самостоятельно производить все вычисления».



Не проблема! Именно поэтому мы и создали Babylon.js.



Вот как выглядит код для той же сферы в Babylon.js. Для начала вам понадобится простая веб-страница:






















Шейдеры здесь задаются тегами script. В Babylon.js их также можно задавать в отдельных файлах формата .fx.



Babylon.js доступен для скачивания по ссылке здесь или в нашем репозитории на GitHub. Для получения доступа к объекту BABYLON.StandardMaterial нужна версия 1.11 и выше.



Наконец, основной JavaScript-код выглядит следующим образом:

«use strict»;



document.addEventListener("DOMContentLoaded", startGame, false);

function startGame() {
if (BABYLON.Engine.isSupported()) {
var canvas = document.getElementById("renderCanvas");
var engine = new BABYLON.Engine(canvas, false);
var scene = new BABYLON.Scene(engine);
var camera = new BABYLON.ArcRotateCamera("Camera", 0, Math.PI / 2, 10, BABYLON.Vector3.Zero(), scene);

camera.attachControl(canvas);

// Creating sphere
var sphere = BABYLON.Mesh.CreateSphere("Sphere", 16, 5, scene);

var amigaMaterial = new BABYLON.ShaderMaterial("amiga", scene, {
vertexElement: "vertexShaderCode",
fragmentElement: "fragmentShaderCode",
},
{
attributes: ["position", "uv"],
uniforms: ["worldViewProjection"]
});
amigaMaterial.setTexture("textureSampler", new BABYLON.Texture("amiga.jpg", scene));

sphere.material = amigaMaterial;

engine.runRenderLoop(function () {
sphere.rotation.y += 0.05;
scene.render();
});
}
};


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



При создании объекта BABYLON.ShaderMaterial нужно указать элемент DOM, используемый для хранения шейдеров или базовое имя файлов, в которых находятся шейдеры. Для второго варианта потребуется также создать по файлу для каждого шейдера, используя следующий принцип именования: basename.vertex.fx и basename.fragment.fx. Затем нужно будет создать материал вроде этого:



var cloudMaterial = new BABYLON.ShaderMaterial("cloud", scene, "./myShader",
{
attributes: ["position", "uv"],
uniforms: ["worldViewProjection"]
});


Нужно также указать имена любых используемых атрибутов и uniform-переменных. Затем можно напрямую задать значения uniform-переменных и семплеров с помощью функций setTexture, setFloat, setFloats, setColor3, setColor4, setVector2, setVector3, setVector4 и setMatrix.



Довольно просто, правда?



Помните матрицу worldViewProjection? С Babylon.js и BABYLON.ShaderMaterial вам не придется о ней волноваться. Объект BABYLON.ShaderMaterial вычислит всё автоматически, так как мы объявляем матрицу в списке uniform-переменных.



Объект BABYLON.ShaderMaterial может самостоятельно управлять следующими матрицами:




  • world;

  • view;

  • projection;

  • worldView;

  • worldViewProjection.



Никаких сложных расчетов. К примеру, при каждом выполнении sphere.rotation.y += 0.05 матрица world данной сферы генерируется и передается в GPU.



CYOS: Создайте шейдер своими руками



Мы пойдем еще дальше и создадим страницу, на которой можно будет динамически редактировать шейдеры и наблюдать за результатом в реальном времени. Для этого нам понадобится прежний код, а также объект BABYLON.ShaderMaterial для компиляции и выполнения созданных шейдеров.



Я использовал для CYOS редактор кода под названием ACE. Он невероятно удобен и оснащен функцией подсветки синтаксиса.



В поле Templates можно выбирать предустановленные шейдеры, мы поговорим о них немного позже. Вы также можете изменить 3D объект, используемый для предпросмотра шейдеров в поле Meshes.



Кнопка Compile используется для создания нового объекта BABYLON.ShaderMaterial из шейдеров. Вот её код:




// Compile
shaderMaterial = new BABYLON.ShaderMaterial("shader", scene, {
vertexElement: "vertexShaderCode",
fragmentElement: "fragmentShaderCode",
},
{
attributes: ["position", "normal", "uv"],
uniforms: ["world", "worldView", "worldViewProjection"]
});

var refTexture = new BABYLON.Texture("ref.jpg", scene);
refTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE;
refTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE;

var amigaTexture = new BABYLON.Texture("amiga.jpg", scene);

shaderMaterial.setTexture("textureSampler", amigaTexture);
shaderMaterial.setTexture("refSampler", refTexture);
shaderMaterial.setFloat("time", 0);
shaderMaterial.setVector3("cameraPosition", BABYLON.Vector3.Zero());
shaderMaterial.backFaceCulling = false;

mesh.material = shaderMaterial;


Подозрительно просто, правда? Итак, осталось только получить 3 предварительно вычисленных матрицы: world, worldView и worldViewProjection. Данные о вершинах будут содержать значения положения, нормали и координат текстур. Также загрузятся 2 следующие текстуры:





amiga.jpg





ref.jpg



А это renderLoop, где я обновляю 2 uniform-переменные:




  • переменную time – чтобы получать забавные анимации;

  • переменную cameraPosition – чтобы получать данные о положении камеры в шейдерах (что очень пригодится при расчете освещения);



engine.runRenderLoop(function () {
mesh.rotation.y += 0.001;

if (shaderMaterial) {
shaderMaterial.setFloat("time", time);
time += 0.02;

shaderMaterial.setVector3("cameraPosition", camera.position);
}

scene.render();
});


К тому же, CYOS теперь доступен и для Windows Phone благодаря проделанной нами работе для Windows Phone 8.1:





Basic



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



Чтобы высчитать положение пикселя, нужна матрица worldViewProjection и положение вершины:



precision highp float;

// Attributes
attribute vec3 position;
attribute vec2 uv;

// Uniforms
uniform mat4 worldViewProjection;

// Varying
varying vec2 vUV;

void main(void) {
gl_Position = worldViewProjection * vec4(position, 1.0);

vUV = uv;
}


Координаты текстуры (uv) передаются в пиксельный шейдер неизмененными.



Обратите внимание на первую строчку: precision mediump float; – её обязательно нужно добавить в вершинный и пиксельный шейдер для правильной работы в Chrome. Она отвечает за то, чтобы для улучшения производительности не использовались числа высокой точности.



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



precision highp float;

varying vec2 vUV;

uniform sampler2D textureSampler;

void main(void) {
gl_FragColor = texture2D(textureSampler, vUV);
}


Как было видно ранее, uniform-переменная textureSampler заполнена текстурой amiga, поэтому результат выглядит так:





Black and white



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



Самый простой способ добиться данного эффекта – взять всего один компонент, например, как показано ниже:



precision highp float;

varying vec2 vUV;

uniform sampler2D textureSampler;

void main(void) {
gl_FragColor = vec4(texture2D(textureSampler, vUV).ggg, 1.0);
}


Мы использовали .ggg вместо .rgb (в компьютерной графике эта операция называется swizzle). Но если нужно получить настоящий черно-белый эффект, лучше всего вычислить относительную яркость, которая учитывает все компоненты цвета:



precision highp float;

varying vec2 vUV;

uniform sampler2D textureSampler;

void main(void) {
float luminance = dot(texture2D(textureSampler, vUV).rgb, vec3(0.3, 0.59, 0.11));
gl_FragColor = vec4(luminance, luminance, luminance, 1.0);
}


Скалярное произведение вычисляется следующим образом:



result = v0.x * v1.x + v0.y * v1.y + v0.z * v1.z


В нашем случае:



luminance = r * 0.3 + g * 0.59 + b * 0.11 


(эти значения рассчитываются с учетом того, что человеческий глаз более чувствителен к зеленому цвету)





Cell shading



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



В данном случае нам потребуется добавить в пиксельный шейдер положение вершины и нормаль к вершине. Вершинный шейдер будет выглядеть так:



precision highp float;

// Attributes
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;

// Uniforms
uniform mat4 world;
uniform mat4 worldViewProjection;

// Varying
varying vec3 vPositionW;
varying vec3 vNormalW;
varying vec2 vUV;

void main(void) {
vec4 outPosition = worldViewProjection * vec4(position, 1.0);
gl_Position = outPosition;

vPositionW = vec3(world * vec4(position, 1.0));
vNormalW = normalize(vec3(world * vec4(normal, 0.0)));

vUV = uv;
}


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



Вот как будет выглядеть пиксельный шейдер:



precision highp float;

// Lights
varying vec3 vPositionW;
varying vec3 vNormalW;
varying vec2 vUV;

// Refs
uniform sampler2D textureSampler;

void main(void) {
float ToonThresholds[4];
ToonThresholds[0] = 0.95;
ToonThresholds[1] = 0.5;
ToonThresholds[2] = 0.2;
ToonThresholds[3] = 0.03;

float ToonBrightnessLevels[5];
ToonBrightnessLevels[0] = 1.0;
ToonBrightnessLevels[1] = 0.8;
ToonBrightnessLevels[2] = 0.6;
ToonBrightnessLevels[3] = 0.35;
ToonBrightnessLevels[4] = 0.2;

vec3 vLightPosition = vec3(0, 20, 10);

// Light
vec3 lightVectorW = normalize(vLightPosition - vPositionW);

// diffuse
float ndl = max(0., dot(vNormalW, lightVectorW));

vec3 color = texture2D(textureSampler, vUV).rgb;

if (ndl > ToonThresholds[0])
{
color *= ToonBrightnessLevels[0];
}
else if (ndl > ToonThresholds[1])
{
color *= ToonBrightnessLevels[1];
}
else if (ndl > ToonThresholds[2])
{
color *= ToonBrightnessLevels[2];
}
else if (ndl > ToonThresholds[3])
{
color *= ToonBrightnessLevels[3];
}
else
{
color *= ToonBrightnessLevels[4];
}

gl_FragColor = vec4(color, 1.);
}


Этот шейдер предназначен для симуляции света, поэтому, чтобы не вычислять плавное затенение по всей поверхности объекта, мы будем высчитывать интенсивность света на основе нескольких порогов яркости. Например, если интенсивность равна от 1 (максимум) до 0.95, цвет объекта, взятый из текстуры, будет накладываться напрямую, без изменений. Если же интенсивность будет от 0.95 до 0.5, к значению цвета будет применен множитель 0.8 и так далее.



В итоге процесс создания такого шейдера можно разбить на 4 шага:




  • Сначала объявляем пороги яркости и константы для каждой степени интенсивности.

  • Рассчитываем освещение на основе алгоритма Фонга (исходя из соображения, что источник света не движется).



vec3 vLightPosition = vec3(0, 20, 10);

// Light
vec3 lightVectorW = normalize(vLightPosition - vPositionW);

// diffuse
float ndl = max(0., dot(vNormalW, lightVectorW));


Интенсивность света, падающего на пиксель, зависит от угла между нормалью к вершине и направлением света.




  • Получаем цвет текстуры для пикселя.

  • Проверяем порог яркости и применяем константу соответствующей степени интенсивности.



В итоге мы получим нечто похожее на мультипликационный эффект:





Phong



Мы уже использовали алгоритм Фонга в предыдущем примере. Теперь рассмотрим его подробнее.



С вершинным шейдером всё будет довольно просто, так как основная часть работы придется на пиксельный:



precision highp float;

// Attributes
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;

// Uniforms
uniform mat4 worldViewProjection;

// Varying
varying vec3 vPosition;
varying vec3 vNormal;
varying vec2 vUV;

void main(void) {
vec4 outPosition = worldViewProjection * vec4(position, 1.0);
gl_Position = outPosition;

vUV = uv;
vPosition = position;
vNormal = normal;
}


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



precision highp float;

// Varying
varying vec3 vPosition;
varying vec3 vNormal;
varying vec2 vUV;

// Uniforms
uniform mat4 world;

// Refs
uniform vec3 cameraPosition;
uniform sampler2D textureSampler;

void main(void) {
vec3 vLightPosition = vec3(0, 20, 10);

// World values
vec3 vPositionW = vec3(world * vec4(vPosition, 1.0));
vec3 vNormalW = normalize(vec3(world * vec4(vNormal, 0.0)));
vec3 viewDirectionW = normalize(cameraPosition - vPositionW);

// Light
vec3 lightVectorW = normalize(vLightPosition - vPositionW);
vec3 color = texture2D(textureSampler, vUV).rgb;

// diffuse
float ndl = max(0., dot(vNormalW, lightVectorW));

// Specular
vec3 angleW = normalize(viewDirectionW + lightVectorW);
float specComp = max(0., dot(vNormalW, angleW));
specComp = pow(specComp, max(1., 64.)) * 2.;

gl_FragColor = vec4(color * ndl + vec3(specComp), 1.);
}


В предыдущем примере мы использовали только диффузную составляющую, так что осталось только добавить зеркальную. Эта картинка из статьи на Википедии объясняет принцип работы шейдера:





Автор: Brad Smith aka Rainwarrior



Результат:





Discard



Для этого типа шейдера я бы хотел ввести новое понятие: ключевое слово discard. Такой шейдер будет игнорировать любой пиксель не красного цвета, создавая в результате иллюзию полого объекта.



Вершинный шейдер будет в данном случае таким же, как и для базового шейдера:



precision highp float;

// Attributes
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;

// Uniforms
uniform mat4 worldViewProjection;

// Varying
varying vec2 vUV;

void main(void) {
gl_Position = worldViewProjection * vec4(position, 1.0);

vUV = uv;
}


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



precision highp float;

varying vec2 vUV;

// Refs
uniform sampler2D textureSampler;

void main(void) {
vec3 color = texture2D(textureSampler, vUV).rgb;

if (color.g > 0.5) {
discard;
}

gl_FragColor = vec4(color, 1.);
}




Результат выглядит весьма забавно:





Wave



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



Для данного примера нам пригодится пиксельный шейдер с затенением по Фонгу.



В вершинном шейдере мы используем uniform-переменную под названием time, чтобы получить динамические значения. Эта переменная будет генерировать волну, в которой вершины будут менять свое положение:



precision highp float;

// Attributes
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;

// Uniforms
uniform mat4 worldViewProjection;
uniform float time;

// Varying
varying vec3 vPosition;
varying vec3 vNormal;
varying vec2 vUV;

void main(void) {
vec3 v = position;
v.x += sin(2.0 * position.y + (time)) * 0.5;

gl_Position = worldViewProjection * vec4(v, 1.0);

vPosition = position;
vNormal = normal;
vUV = uv;
}


Синус умножается на position.y, и это дает следующий результат:





Spherical Environment Mapping



На создание данного шейдера нас вдохновил этот прекрасный тутор. Рекомендую ознакомиться с ним самостоятельно, а затем посмотреть шейдер Wave в CYOS.





Fresnel



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



Вершинный шейдер здесь точно такой же, как и для шейдера с заливкой ячеек, и мы легко можем вычислить необходимое для пиксельного шейдера значение френелевского отражения (для определения направления просмотра данные нормали и положения камеры можно использовать):



precision highp float;

// Lights
varying vec3 vPositionW;
varying vec3 vNormalW;

// Refs
uniform vec3 cameraPosition;
uniform sampler2D textureSampler;

void main(void) {
vec3 color = vec3(1., 1., 1.);
vec3 viewDirectionW = normalize(cameraPosition - vPositionW);

// Fresnel
float fresnelTerm = dot(viewDirectionW, vNormalW);
fresnelTerm = clamp(1.0 - fresnelTerm, 0., 1.);

gl_FragColor = vec4(color * fresnelTerm, 1.);
}




Ваш шейдер



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



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



» Репозиторий Babylon.js;

»Форум Babylon.js;

» CYOS;

» Статья о GLSL на Википедии;

» Документация по GLSL.


И еще несколько моих статей на ту же тему:



» Introduction to WebGL 3D with HTML5 and Babylon.JS;

» Cutting Edge Graphics in HTML.


А также уроки по JavaScript от нашей команды:



» Practical Performance Tips to Make your HTML/JavaScript Faster (серия уроков из семи частей, затрагивающая множество тем: от адаптивного дизайна до оптимизации производительности и казуальных игр);

» The Modern Web Platform Jump Start (основы HTML, CSS и JS);

» Developing Universal Windows App with HTML and JavaScript Jump Start (используйте уже написанный JS-код, чтобы создать приложение).


И, конечно же, вы всегда можете воспользоваться некоторыми нашими бесплатными инструментами для оптимизации работы в вебе: Visual Studio Community, пробную версию Azure и кроссбраузерные инструменты для тестирования на Mac, Linux или Windows.
Original source: habrahabr.ru.

https://habrahabr.ru/post/311138/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
avia-free

Оператор ? в JS

Вторник, 27 Сентября 2016 г. 14:18 (ссылка)


Я писал про CSS, теперь пришло время написать и про ЯваСкрипт.



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



Оператор ? в JS
Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Javascript-фреймворки: должен остаться только один

Понедельник, 27 Сентября 2016 г. 03:24 (ссылка)

Сергей Аверин (
XEK )



Сергей Аверин



Изначально я хотел сделать доклад про сравнение фреймворков, но потом подумал, что закидают помидорами, поэтому доклад — просто адский троллинг, как водится у меня. И он, скорее, не про HighLoad, а про менеджерскую задачу, которая стоит над всем этим делом, включая фронтенд.



Про что же, все-таки, получился доклад? Доклад про то, как выбирали новый фреймворк, почему выбирали, и какие задачи решали.







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



А вот теперь настало время очешуительных историй.



Немного про компанию:







Acronis — очень большой российский бизнес, который обслуживает 5 млн. пользователей, из которых примерно процентов 10 — это корпоративные клиенты, которым их работы покупают лицензии. Соответственно, компания сама по себе тоже очень большая, у нас 17 разных офисов, RND находятся в трех разных странах, и самый большой RND находится в Москве. Но при этом разработка у нас распределенная, и есть удаленщики, которых я здесь не посчитал.



Масштаб разрабатываемого софта самый разный: есть у нас коробочный софт под Windows, есть коробочный софт с веб-интерфейсом, который мы продаем на предприятия, есть софт, который мы сами продаем и сами внедряем, и это смесь и коробочного и cloud-софта. Есть и cloud-софт, который мы продаем по подписке и домашним пользователям, и корпоративным. Есть даже софт, который мы продаем сначала крупным хостинг-компаниям, которые уже от своего имени продают его под своим брендом их конечным юзерам. И у всего этого дела есть огромное количество веб-интерфейсов. Речь идет именно о веб-продуктах, а не о веб-сайтах, как таковых.







Когда я пришел в компанию, стал разбираться, чего же у нас есть, как же все происходит, выяснилось, что внутри компании есть веб-админки разного рода, которые мы делаем сразу на куче разных технологий, начиная от true-фронтенд типа Ext JS или AngularJS, заканчивая сайтами, которые пишутся на Ruby on Rails и в которых jQuery просто переключает страницы или графики.



Перед коллективом стояли такие проблемы.







Во-первых, технологий много, они все разные, разработчиков из одного отдела в другой не перетащить, не помочь какому-то проекту, потому что Ruby on Rails разработчики, которые пишут на jQuery, про AngularJS ничего не знают. Во-вторых, выяснилось, что существенное количество людей работают part-time — то бэкенд-разработчиками, то фронтенд-разработчиками. У нас, например, есть ребята, которые на PHP пишут, и они же делают на AngularJS сайт, который работает с ним как с бэкендом. Их не очень много, у них проект маленький, и смысл заводить им отдельного фронтенд-разраба отсутствует.



В итоге я насчитал, что у нас есть java-люди, ruby-люди, python-люди и php-люди, которые все делают фронтенд. И при этом в компании нет верстальщика, ни одного. Т.е. есть несколько людей, которые экспертно знают эту область, но обычно они работают кодерами на AngularJS. В итоге в этих проектах совершенно разный стиль кода, совершенно разный стиль комментариев, они по-разному деплоятся, в каком-то месте, с помощью Ruby on Rails мы собираем AngularJS приложения с помощью ruby‘шного сборщика. И в итоге — везде разный зоопарк. В каждом отделе зоопарк свой. А еще, короче, получается так, что наш самый флагманский продукт, в котором написан Ext JS, это огромная админка, там сотни экранов, он сейчас построен на базе версии 4, а в 2015-ом году, совсем недавно, зарелизилась уже версия 6 этого фреймворка и, вроде как, мы отстаем аж на 5 лет от версии фреймворка, которую мы используем. Возможно, тоже надо что-то с этим делать.



Пришло, конечно же, начальство и поставило задачу. Задача была дана сверху:







Нам нужен толстый клиент на фронтенд-технологиях, который будет общаться со stateless backend API максимально везде, где мы можем это внедрить. Нужна единая библиотека UI-компонентов, единый look and feel для всей компании, потому что мы немножко страдаем от того, что у нас шрифты где-то разные, где-то по пикселям верстка поплыла, и в итоге получается так, что у нас два соседних отдела рисуют какие-то общие элементы для всех наших админок, которые дизайнерами нарисованы одинаково, немножко по-разному — где-то верстка на HTML, где-то верстка запихана в JS-код в каком-то собственном шаблонизаторе. И CSS-слой тоже принципиально разный, где-то он накладывается из трех-четырех файлов поверх друг друга, заскиневая существующих фреймворк.



Очень хочется, чтобы мы, наконец-то, могли подключить нормального верстальщика и нанять разработчика с меньшей зарплатой, который бы отвечал, с одной стороны, за общение с дизайнерами, подготовку дизайнов, а с другой стороны java-скрипторам приносил и говорил: «Вот, они хотят этого, здесь нужно анимировать этот интерфейс…». И при этом мы бы могли нанимать JS-кодеров в проекты не топового уровня. Сейчас у нас работает достаточно много ребят, которые прошли огонь и воду, они знают Java, они знают .Net, и в этом плане для них любой javascript-фреймворк — это не проблема, потому что они, в принципе, очень опытные. Поэтому хочется как-то иметь возможность нанимать не одного из ста, а хотя бы двух из пятидесяти.



И есть такой еще отдельный pqrity. У нас большая часть людей, которая делает фронтенд, это либо бывшие, либо текущие бэкендеры, поэтому очень хочется, чтобы все это было понятно бэкенд-разработчикам. Менеджмент, когда видит код на JS, он начинает говорить, что «Блин, мы ничего не понимаем, все плохо, давайте перепишем», а зачастую все это не так.







Посмотрели, что же, на самом деле, мы имеем в компании. Оказалось, что у нас есть партнерский продукт, который написан на Dojo. Для него мы пишем плагины на Dojo, это очень древний фреймворк, очень особенный, он работает как компонентная модель. Это не совсем MVS, но с этим мы решили ничего не делать, потому что мы не можем никакой другой фреймворк использовать, иначе мы не сможем с этой партнеркой работать.



Для сайта, собственно, у нас почти нет никакой динамики. Нам важно, чтобы его индексировали поисковики, и нам важно, чтобы он работал быстро. Нам там Javascript особо не нужен. Поэтому какой-то суперфреймворк для него, вообще, не уперся. Это задача, которую мы не должны решать.



Из оставшегося, из чего на самом деле делаются админки, это Angular JS первой версии, это Ruby on Rails, плюс генерирующие страницы на бэкенде, плюс какие-то jQuery-дописки, которые делают некую динамику. И большая часть продуктов написана на ExtJS 4. Это очень высокоуровневые фреймворки, которые больше всего напоминают .Net или CuTI, на котором большая часть продуктов компании — серьезных и взрослых — написана.







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







Каждый класс имеет очень глубокое дерево наследования, т.е. в этом плане очень сильно слизано с того, как строится CuTI, .Net и прочие MVC фреймворки, которые работают где-то в винде или в линуксе под десктоп. В итоге получается, что зачастую код, который ты дебажишь, находится где-нибудь на три уровня выше, чем тебе сейчас надо. Разобраться в этом действительно сложно.



Класс Ext.panel:







Это только методы из него. Там еще столько же событий, столько же свойств разных. Совершенно понятно, что ни один разработчик запомнить все эти методы не может. Поэтому постоянно приходится работать с документацией, чтобы понять, как сделать даже самую простую штуку, потому что размер какого-нибудь компонента… Там есть аж три разных метода, и они три разных размера возвращают — каждый по-своему.







В итоге мне было печально, очень. Я первое время вообще ничего не понимал, смотрел на все это дело и задавал себе такой вопрос: «Господи, зачем же вы все это написали?».



Индексная страница флагманского приложения была мною напечатана на листах А0 и занимала 51 лист шрифтом 6.







Вот увеличенное это дело:







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







Полезли смотреть, как же делается UI в этом фреймворке. Оказалось, что пользователь получает сразу библиотеку UI компонентов, которая скинится CSS’ом. Ты должен CSS’ом дописать поверх, тогда получишь свой кастомный скин. И много компонентов, которых не было, их пришлось реализовать с нуля. В итоге зачастую, не везде, но очень часто, я натыкался на что-нибудь такое. Это javascript класс, который с одной стороны UI компонент, но с другой, зачастую видно, что в нем аж три разных сущности смешивается — это javascript-код, html-код и это какие-то очень странные директивы местного шаблонизатора, эти xindex, xcount или этот tipeof values string, запиханные в фигурные, квадратные скобки…



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







Дальше выяснилось, что это единственный видимый мною фреймворк, в котором есть такая шутка, как layouting. Полезли посмотреть, что же, как же, почему, откуда эти теги? Оказалось, что при изменении размеров окна, при изменении размеров какого-то компонента фреймворк так же, как это на декстопе, вычисляет сам размер блоков и вставляет их inline CSS’ом. Типа left, right, width, height. Мало того, там очень хитрый алгоритм, который берет текст и оценивает его размеры, запихивает его в невидимый div, и смотрит, какого размера этот div получился при заданной ширине. И так он мимикрирует под функции оценки размеров текста, сколько этот текст займет в этом блоке, и вычисляет эти блоки. А потом выяснилось, что алгоритм этот, еще и древовидный, т.е. он по 6-7 проходов делает, потому что он, как браузер, тоже натыкается на какой-то компонент, который не влазит, начинает его увеличивать и родительский компонент тоже пересчитывать. В итоге, начинаешь двигать правую страницу браузера и четко видишь затупы его про изменения размера этого фреймворка. Но, с другой стороны, это дает тебе типа бенефис, что во всех браузерах это работает абсолютно одинаково, и про проблемы совместимости между браузерами ты не паришься, потому что максимально тебе этот вопрос закрыли.







Попутно выяснилось, что компонентов очень до фига. И все эти компоненты, каждый из них — это инстанс javascript’ового класса, у которого от трех до пяти уровней наследования, а то и до семи-восьми. Для каждого из них этим волшебным layout’ером оцениваются размеры, они высчитываются и выставляются. У родительских потом высчитываются размеры, и в итоге все это дело генерирует такие веселые штучки — inline css’ы, которые вставляются прямо в div’ы этих компонентов, прямо в теги. Поэтому получается такое огромное DOM-дерево, странное.







В попытке понять, как же этот layouting работает, я наткнулся на такое совершенно страннейшее место.







В этот момент мне стало совсем плохо, и я понял, что я не пойму никогда, как это происходит.







Вы все еще помните, что речь идет о производительности?







Все это дело занимает почти 2 Мб уже в пожатом виде, поэтому совершенно неудивительно, что на каких-то тестах типа «а как это будет работать на мобиле?» выдает веселые времена загрузки. Но, тем не менее, это не такая большая проблема для нас, потому что большинство клиентов корпоративные, и они лезут в админку, когда им нужно либо что-то настроить, т.е. в первую неделю после покупки продукта, либо когда адский пипец, т.е. нужно достать бэкап. И человек, у которого адский пипец, готов подождать 16 секунд, пока, наконец, загрузится админка. Тем более, здесь речь идет о достаточно тонком канале.







С фреймворком понятно, очень сложно и запутано. А сам приложение? Полезли в код приложения.







Получается так, что в коде мало комментариев. Какие-то трудные места не объяснены. Все приложения жестко связаны. Вместо одного класса — длинного, на 2,5 тыс. строк, мы имеем дело обычно с тремя-четырьмя классами по 1000 строк, которые очень жестко знают друг про друга и так по кольцу, общаясь, пересылают себе события, меняют состояния внутри себя. И у нас внутри очень большие проблемы с границами между моделью и бизнес-логикой, и между бизнес-логикой и view. Получается так, что у нас есть view-классы, которые называются как-нибудь типа «волшебная реализующая определенный функционал панель», которая и одновременно отрисовывает себя и отрисовывает дочерние компоненты, и еще у нее до фига бизнес-логики, которая умеет переключать какие-то панели внутри, и которая умеет отсылать уведомления, даже зачастую они из view-классов лазят на сервер.







Поэтому (очень условно) получилось так, что у нас такой не mvc, а M+CV. Т.е. граница между контролем и view очень не определена. Получилось так, что и в моделях есть бизнес-логика, и в контроллерах есть бизнес-логика, и во view есть бизнес-логика, и UI логика, и состояние приложения тоже размазано по всем трем видам классов. Поэтому трудно, например, понять откуда пришли эти данные, или кто является авторитативной точкой, откуда эти данные взялись, и кто хранит на текущий момент самую достоверную копию этих данных, потому что она есть в нескольких частях приложения.







А еще получилось так, что сам фреймворк Ext JS всех очень толкает к архитектуре построенной на publish/subscribe, потому что, в принципе, у него все UI компоненты завязаны на этот механизм, и программисты невольно выбирают то, что предлагает фреймворк. И, вроде как, publish/subscribe — правильный паттерн, но когда ты начинаешь смотреть на приложение, в котором типа 100 страниц, в итоге получается, что ты пытаешься разобраться: «Блин, а к чему же приводит нажатие вот этой кнопки в этой панели, и что же она дергает?».







И выясняется, что, во-первых, события могут неявным образом попадать в родительские классы, которые, в свою очередь, генерят вторичные события, которые попадают в какие-нибудь классы с бизнес-логикой, которые в свою очередь генерят еще события. Затем часть событий игнорируется фреймворком, потом меняется какая-то модель, эта модель генерирует события о том, что «данные во мне поменялись». Появляется еще пяток классов, которые подписаны на ее изменения, и в себе меняют какое-то состояние, которое зависит от состояния этой модели.



В итоге, через три часа ты, наконец, строишь стройную картину и понимаешь, что у всех событий названия очень похожи, и везде есть onclick. И тебе нужно понять, это onclick от этой кнопки, которую ты нажал или onclick от соседней кнопки, которую ты нажал. Потому что фреймворк не запрещает подписку любого компонента на любой. Т.е. ты можешь добраться из кого-нибудь суперконтроллера до кнопки в конкретной панели и подписаться на нее onclick. И это будет просто onclick в кавычках. И такие дела очень трудно дебажить, потому что нет какой-то уникальной селективности этих компонентов. Т.е. там есть свой механизм, называется ComponentQuery, похожий на XPath, в котором можно написать RegEx-запрос, который тебе говорит: «Найди мне там все кнопки, которые вложены в определенную панель с определенным названием». Поэтому, когда ты пытаешься чего-то отрефакторить, очень трудно найти все, вообще, места, где мы реагируем на конкретное событие. Зачастую у нас это даже приводило к циклам событий, когда приложение зависает, потому что постоянно появляются дочерние события, оно начинает их обрабатывать, и этот цикл бесконечен. Пока ты не напишешь какой-нибудь хитрый if типа «если ко мне это событие уже приходило, я его не обрабатываю», то мы дальше его не обрабатываем. Т.е. речь идет о запутанной архитектуре.



Все нормально.







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







Вот так вот получилось. Причем непонятно, на самом деле, почему получилось. Потому что, на мой взгляд, могло быть лучше. Но, короче, объекты Arrays мы проверили, остальное — как получится. И вечная эта история про то, что когда ты начинаешь писать тесты, ты многое узнаешь про свое приложение, что на самом деле ни фига не надо было делать такое количество связей, потому что замокать 15 объектов ради вызова одной функции, очень геморройно. И в этом плане получили тесты «давай, до свидания».



Какие выводы? Как бы, вот такие, конечно, выводы…







Но на самом деле, настоящие выводы в чем?







Что фреймворк очень сложный для понимания разработчиков, особенно разработчиков, которые до этого не защищали докторскую диссертацию по Java. Во-вторых, код, который получился, очень запутанный. В нем очень много связей, все классы друг про друга знают, и события, которые появляются, никем не контролируются, и это приводит к фейерверко-образным взрывам изменения стейтов приложения, когда 15 мест подписаны на какой-нибудь стейт, события потом генерируют свои дочерние события, и еще какие-нибудь другие места начинают обрабатывать эти события… В этом плане фреймворк жутко асинхронный, и эта асинхронность взрывает мозг. Получается так, что компоненты правят люди, которые понимают одновременно язык шаблонизатора, намешанный JS-код, html, CSS и все это в одном файле. Обычного верстальщика нанять? Я не знаю… Сколько сейчас получает верстальщик? За 40-60 тыс. невозможно найти верстальщика и посадить его за простую рутинную работу «в этой панели убери эту кнопку», потому что сборка всего UI происходит в JS-коде методом созданий инстансов JS-классов. Т.е. ты описываешь большое дерево, какую кнопку в какую панель вложить, какие параметры передать, какие ссылки, на какие события потом подписаться.



Соответственно, главный мой вопрос был — виноват ли в этом фреймворк? На самом деле, конечно, виноват. Частично. Потому что он подталкивает тебя к publish/subscribe, потому что он подталкивает тебя к такому mvс, который они сами делают, псевдо mvc. Потому что в нем нет dependency injection из коробки, потому что он не говорит тебе, как тестировать это все дело…







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



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



Конечно, хочется менее связанную архитектуру, потому что тестировать это нереально, написать тесты для этого очень трудно. Поэтому хочется какой-то изоляции. Понятные микрокомпоненты, каждый из которых ответственен за свой маленький кусочек. И проще UI слой.







К чему главный посыл всей этой презентации (а это самый главный слайд) — что все ваши песни про производительность фронтенда превращаются в лапшевидный код, отсутствие стандартов и «мы наняли нового чувака, а он четыре месяца правит одну багу». Давайте сначала улучшим производительность разработчиков, дадим им в руки нормальные, а не гнутые инструменты, и они сделают код, который будет намного лучше работать, потому что эти задачи были решены на уровне фундамента. Т.е. мы будем стоять на плечах гигантов.



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



Конечно, я полез в Интернет. Ну, а что делать? Тут все бы полезли в Интернет.







Выяснилось, что за пять лет, пока я управлял и работал на PHP, в мире JS многое поменялось, в этом плане пришлось многое выучить заново.







И эти тысячи фреймворков, каждый из которых как две капли воды зачастую похож на друг друга, особенно во flux фреймворках — это прямо «придумай свое название точка JS». И из этого ада там, где Google Web Toolkit уже давно умер, надо как-то выбрать. Блин, что делать? Конечно, надо было что-то подумать, как бы посмотреть, как другие где-то что-то выбирают в Интернете, залезть и где-то спереть результат.



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







Именно про компании и сайты я оттуда слайды вырезал, остаются именно честные цифры. Это количество строчек кода, написанных вообще с момента появления Github на разных технологиях. Плюс-минус.







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



И везде картинка разная.







На этой картинке тренд популярности запросов в гугле. Их тут несколько… Здесь, если присмотреться, видно, что все технологии идут на самом деле на спад, кроме React js и Angular js.







Если взять какие-нибудь старые более-менее фреймворки дипа Dojo, Yahoo ui и нашего любимого Ext js, то тоже все идет на спад. В этом плане с React js и Angular js никто не поборется, потому что это единственное, что идет вверх.



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







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







А это уже российский рынок. Мы пытались оценить, кого же мы можем нанять, потому что очевидно, что мы не в космосе работаем, и нам нужно делать продукты в компании, продуктов много и планов — громадье, поэтому кого-то надо нанять. Здесь, вообще, картина другая. Выяснилось, что так или иначе с какой-то из версий ExtJS (поскольку он достаточно долог) и с какой-то из версий Backbone (поскольку он тоже родился в 2008 году) так или иначе кто-то имел дело. Т.е. это никакой гарантии не дает на то, что этот человек прямо сейчас готов чего-то делать realtime на этом фреймворке, но, тем не менее, выяснилось, что среди растущих технологий React js и Angular js, появились внезапно Backbone и ExtJS, причем вакансии по ним есть, и их достаточно много. Вакансий по Backbone больше, чем со знанием React. Вот какая удивительная картина на российском рынке.







В итоге мы все эти исследования максимально скрестили и оставили то, что нам интересно. Туда добавили еще Dojo и добавили ExtJS 6, на который изначально хотели переходить. А Dojo, потому что его используют Parallels, с которым мы тесно интегрируемся, и на котором у них кое-что написано.







Здесь нагло было вырезано пять слайдов. Я не буду рассказывать про то, почему нам это все не подошло, потому что рассказ там очень скучный. Я не буду на этом останавливаться. Если вы полезете изучать для себя, то сформируете и какую-то картину для себя. Мы оценивали, исходя из наших задач. Одна из задач была в том, что технология должна быть более-менее встраиваемой по частям в существующий код, т.е. нам нужен не монолитный фреймворк, а, скорее, набор каких-то методик и библиотек, которые мы можем потихоньку внести. В этом плане оказалось, что Knockout — это вообще не фреймворк, а UI библиотека, Dojo — это просто мамонт, а ExtJS 6, хоть и привносит новую архитектуру, но на самом деле мало чем отличается от ExtJS 4. Т.е. там появилась поддержка какая-то, более хорошая работа с мобильными устройствами, но концепция не поменялась. Там чуть лучше стала архитектура, но для нас, как мы оценили, примерно одинаковая, что мы на ExtJS 6 будем переписывать честно архитектуру, прямо так, как они предлагают, что мы будем на любом другом фреймворке переделывать — примерно одинаково по затратам. В итоге отказались, потому что все тот же UI слой, все тот же publish/subscribe.



Истинных кандидатов, на самом деле, было только два.







Первый — AngularJS — по понятным причинам, который еще очень активно отстаивали и несколько отделов компании, и некоторые внешние советчики. Но с ним возникла очень интересная дилемма. С одной стороны, это выглядело действительно очень хорошим решением, с другой стороны, я видел очень много статей на Хабре и, вообще, в англоязычном Интернете, в которых обсуждают некие допущенные при его проектировке именно концептуальные ошибки, приводящие к тому, что все, как бы, очень круто, но мы запутались.



Наше собственное зрение на это дело такое, что если брать версию 1, на которой у нас в компании есть два проекта, и посидеть-покурить исходники, то у этого фреймворка реально хорошая модульность разделения на отдельные кусочки, каждый из которых делает свой маленький функционал — вот это очень полезно и хорошо. Но нет единого стиля раскладки по файлам, нет единого стиля именования каких-то переменных и единого стиля форматирования кода, т.е. это та вещь, которую хотелось бы как-то получить до кучи с фреймворком, но ни фига не получится. И со временем этот стиль раскладки меняется. Также от себя добавлю, что зачастую находятся места в коде, которые по-хорошему должны бы быть отдельными классами, но почему-то их в виде объекта с функциями туда пихают. Трудно дебажить, очень много разной хитрой «магии», очень сложно интегрировать с какими-то новыми технологиями, потому что стандарты абсолютно все свои… Как собирать это дело — тоже не очень понятно, однако этот вопрос можно решить. Но самая беда, что начальству я это продать не мог, потому что мы все говорили, что «Ребята, они делают новую версию, и код будет несовместим». Что делать дальше — непонятно.







ОK, полезли смотреть версию 2. Версия 2 прямо вообще отлично выглядит, как то, что нужно, такой бэкендно-ориентированный подход, все разложено по полочкам, хороший очень синтаксис на TypeScript — то, что надо. Но, блин, нет шансов на релиз, вообще, нет. Я, самое главное, не понимаю, эта супер-мега гугл корпорация, она давно работает с python… Пример того, как python переходит с версии 2 на 3 — это просто хрестоматийный пример того, как надо это делать. Надо поддерживать синтаксис старой версии, надо быть backword compatible. А здесь какой-то адский ад просто. Он будет несовместим. «Мы сейчас что-то новое делаем. Вот альфа-версия, для нее ни хрена нет документации. Когда мы запустимся — непонятно и, самое главное, у нас во всех трех языках еще разный синтаксис». Ну, зашибись, блин!



Это что, это решение? Вы извините меня, это локомотив фронтенд-разработки, это, типа, самый крутой фреймворк, который все страшно любят? И что мне делать с админками, которые сейчас пишет четыре человека на AngularJS? Прийти и сказать: «Ребята, вы знаете, мы просто садимся и все переписываем. У нас ничего не станет лучше, мы просто переезжаем на новую версию». — «А зачем переезжаем?» — «Ну, потому что старую забросили». А они меня, конечно же, спросят: «Слушай, а есть такая штука, как в python, типа, «by 2 to 3». Т.е. давай натравим какой-то парсер, и он нам сейчас переформатирует код, и оно хоть как-то начнет собираться». Ты говоришь: «Извините, нет. Причем, мало того, что нет, оно сейчас еще все поменяется. Завтра у нас будет другой синтаксис». И это продать тоже никому невозможно. Совет директоров на меня посмотрит и уволит на завтра и все. Т.е. что делать с AngularJS — непонятно.







Мы дальше стали смотреть, и было второе решение — это React JS, который сделал Facebook. C удивлением оказалось, что это не фреймворк, а всего лишь UI библиотека. У нее очень четкая понятная структурность, четко понятно, что это единый data flow, который только получает данные, только их отрисовывает. Каждый компонент изолирован, у каждого компонента есть декларация, чего он получает на вход, чего он в итоге отрисовывает, чего внутри он хранит в виде промежуточного стейта. Нет, вообще, каких-то магических фильтров и прочей фигни, которую надо откуда-то подключать, учить и знать, как работает… В этом плане все максимально предсказуемо и это очень классно. Мне понравилось, что есть очень простая возможность тюнинга производительности — четко понятно, как она работает, четко понятно, как ее использовать, ничего суперсложного в этом нет. Есть даже серверный рендеринг, который нам не нужен, но круто, что он есть.







До кучи к этому шла архитектура Flux, которую активно двигает Facebook. Мы посмотрели на все это дело, у нас, как бы, был so-so. С одной стороны то, что one-way data flow, и то, что синхронная обработка как у стейт-машины, где четко понятно определенное состояние приложения — это супер. Но как приложение делится на независимые блоки и как разделить его, как в Angular JS, на какие-то отдельные сущности, не очень понятно. Потому что, мне кажется, там явно есть антипаттерны в виде того, что store’ы хранят данные, одновременно реализуют бизнес-логику, реализуют еще логику изменения этих данных. Т.е. это и модель, и контролер одновременно. Идея с единым диспетчером, как единый Event Bus с уникально именованными событиями, где можно быстро найти в коде, где что поменялось, делать быстрый рефакторинг, и компоненты работают изолировано друг от друга — это супер. Но совершенно непонятно, как обеспечивается динамика, как открыть, например, на одном экране два окна, которые бы работали независимо друг от друга, но при этом генерировали бы одинаково, например, события…



По большому счету, Facebook ни фига не зарелизил код, этого нет, это ни фига не фреймворк, это некая архитектурная идея. Причем, изобретенная в 80-х годах, которая называется Event Bus.







ОК. Давайте посмотрим какие-то сторонние фреймворки, которые реализуют эту фигню. Я пересмотрел штук пять, они все как две капли друг на друга похожи, и они все в этом плане намного хуже, чем AngularJS, и намного хуже, чем Ext JS, потому что в них нет почти ничего. Да, там лучше видно, как работает архитектура, но все еще нет речи ни о каком динамическом создании store’ов, их выгрузки и какой-то линковки UI компонентов между store’ами так, чтобы части приложения работали изолировано друг от друга и можно было бы два компонента создать одинаковыми, с одинаковым view, и два контроллера, и они бы не воевали друг с другом этими событиями. Поэтому в этом плане с разработкой веб-приложений все очень странно, т.е. зачастую непонятно, как это делать. Зато в этих фреймворках видно развитие javascript за последние 5 лет, в виде изоморфности, в виде npm-модулей, в виде вынесения функционала библиотеки, в виде поддержки ES6 — это хорошо. Только, блин, ни фига нет, вообще, никакой инфраструктуры тестирования у них, и непонятно как писать тест. Особенно с этой новой суперархитектурой непонятно, как писать тест. И нет интернационализации.



Попутно мы наткнулись на typescript’ы. В этом плане это, вообще, бомба.







Я подумал, что самый супер — убрать излишний креатив разных разработчиков, которые очень любят делать свои «велосипеды» — с помощью этого отлично можно. Typescript — это транс-компилятор, т.е. ты пишешь код, напоминающий javascript, с таким же синтаксисом, только расставляя типы. Т.е. у каждой переменной есть какой-то тип, причем, они могут быть сложными, может быть у переменной одновременно несколько типов, и в итоге он эти типы убирает, создавая тебе javascript-файл с абсолютно таким же синтаксисом, где просто убраны эти типы, и там еще есть несколько полезных syntactic sugarвещей, которые соответствуют стандартам ECMAScript 2006. Т.е. просто компилятор из ECMAScript 6 в 5.







Там, например, есть такие сложные типы данных, когда ты можешь сделать целый контракт, т.е. там объекты с вложенной структурой, и теперь у тебя разные части приложения заранее знают, что из API приходит вот такой вот хитрый вложенный объект, или, например, повторяются в нем поля. Мы используем это как некий клей между отдельными частями приложения, т.е. каждый пишет эту бизнес-логику, ориентируясь на какой-то интерфейс. И компилятор при сборке сразу же проверяет, можно ли скомпилить это, правильно ли ты организуешь доступ к этим полям, правильный ли тип в них подразумеваешь.







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







Точно так же, как в python’е, есть поддержка дженериков. Есть декораторы, как в в python’е, есть дженерики, как это принято в мире С++, и которыми мы активно пользуемся. И есть составные типы. В редких случаях, можно сказать, что в этой переменной может быть или строка, или число, тогда, когда этого не избежать. Он тоже нормально с этим работает.







В итоге после этого исследования вопросов стало только больше. Какую архитектуру выбрать? Какое решение правильное? Как идти в ногу с развитием javascript, в виде ECMAScript 6? Где взять библиотеку UI компонентов?



И в этом плане Ext JS очень активно держит удар, в нем все это есть. В нем есть своя сборка, в нем есть механизм интернационализации, в нем есть понятная архитектура и прочее. Т.е. то, что получилось тогда, получилось странно, но только альтернативы то нет. Ну, как бы, она есть, но ее надо делать самому. В итоге, хотим фреймворк с Typescript, все хотели на этот момент фреймворк с Typescript. Без — уже не хотел никто.







Ну ладно, чего делать? Напишем задачу сами себе. JS-кодеры должны писать код. Верстальщики должны верстать и общаться с дизайнерами. Явно нужно сделать максимально простой UI слой, чтобы в нем разобрался дизайнер, и чтобы это было понятно программисту без жуткого наследования CSS-стилей в три-четыре уровня без жуткого наследования компонентов. Нужно сделать четкую и понятную архитектуру, код, примерно понятный бэкенд-разработчикам, и нужно разделение зон ответственности — отдельно верстка, отдельно код, отдельно контроллеры, отдельно хранение данных, стараться запихать состояние приложения в один какой-то тип классов, назвав его моделью. И, главное, больше границ, правил и стандартов: именования, форматирования кода, раскладки по файлам, всего этого дела. И при этом на Typescript. И при этом еще так, чтобы это все дело не было монолитным фреймворком, потому что все это дело нужно будет еще внедрить в существующий код.







Мы в итоге взяли фреймворк, который называется Este.js. Он ни о чем вам не говорит, он был наиболее революционно-инновационный. Плавно переписывали, пока от него не осталось вообще ничего.



В итоге было несколько проблем.







Первая проблема. Получается так, что store — это некий антипаттерн во flux. На практике у нас ничего хорошего с этим не вышло. Мы в итоге обратно разделили их на классы, которые хранят данные и могут их менять, и отдельно бизнес-логику в контроллере. Это примерно то же самое, куда сейчас идет развитие flux, когда у них есть отдельно вынесенные объекты со стейтом. У нас этот объект со стейтом просто называется store.







Было вот так.



Получилось примерно вот так:







Т.е. изолированные блоки, примерно так же, как в AngularJS, с таким же механизмом, в виде модулей. В каждом модуле там один или несколько своих store’ов, которые общаются с одним из нескольких контроллеров. И в итоге все это дело работает отдельно от view. View только читает данные store’ов и генерирует некие action’ы, которые единый dispatcher раскидывает (на самом деле, не единый, у каждого изолированного блока может быть еще свой) между контроллерами, каждый из которых подписывается на события и решает, реагирует он на это событие или нет. В итоге у нас приложение работает как набор вложенных друг в друга приложений, в каждом из которых внутри flux. Это иерархический MVC, где view просто вложены друг в друга, а контролеры изолированы.



Вторая проблема в том, что шаблоны Facebook предлагает делать вот так:







Мы, конечно, долго мучились, мы поняли, что это вообще не вариант, и мы стреляем себе в ногу. Я абсолютно не понимаю, зачем мне нужно смешивать кучу JS-кода, фигурных скобочек, круглых скобочек, логику и все вместе в UI компоненте, когда, по большому счету, суть UI компонента в том, что он берет какой-то шаблон и рендерит его в браузере. Мы посмотрели на такой проект как wix-react-templates, написали свой, сильно-сильно похожий. Но в нем нет кода. Только есть блоки, которые обеспечивают итерацию, if, then, else и переменные, больше ничего нет. Т.е. задача компонента — в эту переменную запихать заранее то, что в ней будет выведено. И задача компонента — указать сколько раз итерировать какой блок с какими данными. Так выглядит шаблон:







Вот такой мы написали свой собственный парсер этих шаблонов, который делает из него DOM-дерево и генерит такие шаблоны, сильно такие же, как их генерит JSХ:







Вот, это дело уже автоматически генерится, и с этим делом работает React.







И суть UI компонента в том, что он декларирует некие пропсы, декларирует некие стейты и в самом простом способе эти пропсы запихивает в блоки и в переменные. Построили некое дерево, которое потом рендерится.







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







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







С динамикой беда в том, что не очень Facebook объясняет, как это делается, как сделать независимой работу двух компонентов. Мы в этом плане нашли несколько вариантов решений и сейчас их прорабатываем.



Что в итоге получилось?







Получился фреймворк с хорошим ООП и с маленьким интерфейсом, с дженериками, близко похожий на то, как это было написано на java. С dependency injection, которая обеспечивает связь классов внутри одного модуля, у которого только две внешние зависимости — это React и lodash. В котором можно сделать любую архитектуру — захотим рефлакс, можно сделать рефлакс, захотим MVC — можно сделать MVC. Потому что там три с половиной файла и плюс React. И с нормальным механизмом сборки через web-pack…







Мы сделали два пилотных проекта — один на Ext JS 6, другой на React. Абсолютно с нуля, без использования существующего кода, оба повторяют один и тот же функционал существующей админки, но только одну единственную страницу на ней, на которой некоторые кнопки не кликаются. Достаточно немного, все это вместе заняло где-то месяц работы в сумме.



Задачи были — сделать анимации, сделать кастомный, нами рисуемый скроллбар, сделать отдельный интерфейс для узких экранов типа а-ля планшетов, сделать отдельно мобильную версию, когда экран совсем узкий, и сделать, чтобы все это авторизировалось и ходило в json rest, грузило реальные данные, которые у нас есть в API и с ними позволяло работать.



В итоге получились такие цифры:







На них можно не смотреть, но суть в том, что оба проекта реализованы с нуля, получились так, что flux + react выигрывал от 2-х до 5-ти раз. Но с большой звездочкой, что когда у нас будет столько же UI компонент, сколько есть в ExtJS, размеры билдов и скорость инициализации, она, конечно, упадет, потому что в этом плане мы тягаемся с монстром индустрии.







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







Я некоторые из них заверстал. Прямо сделал рабочими компонентами на react’е, это у меня заняло где-то 4 дня.







В итоге получилось так, что мы процентов 10 сделали, оценили, как 2 тыс. строк кода за 4 дня и получили примерную оценку в 2 человеко-месяца для реализации всех UI компонент, из которых состоят наши проекты.







Долго думали, как делать, но пока решили так, что новые проекты сразу делаем на новом фреймворке, попутно создавая эту UI библиотеку в отдельном репозитории. Старый код не трогаем, но встраиваем «независимыми блоками» в него новый функционал в виде как «берем отдельную панель или страницу, ее переписываем на новой». Либо, если это новый функционал, то, вообще, делаем эту страницу сызнова. Соответственно, новый фреймворк прикидывается UI компонентом внутри Ext JS, делая вид, что это какая-то панель, которая как-то отрисовывается и внутри себя чего-то делает, а наружу, максимум, одно-два события отдает. И, конечно же, при модификации старого кода ты либо портируешь существующий функционал, либо правишь, как есть.







Так что суть доклада, как всегда: превозмогайте трудности; велосипеды, баги, костыли — делайте все свое. Делайте свое, потому что ваша команда теперь обладает технологией и экспертными знаниями, как она работает. Т.е. мы читали исходники react’а и, наконец, понимаем, как же он, блин, работает.



Контакты




XEK

s@averin.ru

facebook

twitter

Блог компании Acronis



Этот доклад — расшифровка одного из лучших выступлений на конференции разработчиков высоконагруженных систем HighLoad++. Сейчас мы активно готовим конференцию 2016 года — в этом году HighLoad++ пройдёт в Сколково, 7 и 8 ноября.



Также некоторые из этих материалов используются нами в обучающем онлайн-курсе по разработке высоконагруженных систем HighLoad.Guide — это цепочка специально подобранных писем, статей, материалов, видео. Уже сейчас в нашем учебнике более 30 уникальных материалов. Подключайтесь!



Original source: habrahabr.ru.

https://habrahabr.ru/post/311096/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество

Следующие 30  »

<javascript - Самое интересное в блогах

Страницы: [1] 2 3 ..
.. 10

LiveInternet.Ru Ссылки: на главную|почта|знакомства|одноклассники|фото|открытки|тесты|чат
О проекте: помощь|контакты|разместить рекламу|версия для pda