Переводим проект на Feast

Цели

И это!

ОТВЕТЫ@MAIL.RU

Что дальше?

Что это дало?

			#! ./public
			#!   ./app/
			#!      ./blocks/
			#!         ./application/   // корневой блок
			#!            application.html
			#!            application.scss
			#!            application.js
			#!            application.spec.js
			#!      ./vendors/
			#!         ./feast/
			#!         ./pilotjs/
			#!      boot.js // файл запуска проект
			#!      sitemap.js // карта сайта
			#!   index.html // точка входа
			#! index.js // простенький сервер
		

index.js

			"use strict";
			#! const feast = require("feast");
			#!+ const app = feast.createSimpleApp({
				#! name: "Ответы@Mail.Ru"
			#!- });
			#! // Всё, вот и весь сервер ;]
		

index.html

Тут ничего интересного

boot.js

			#! import Pilot from "pilotjs";
			#! import sitemap from "./sitemap";
			#! import UIApplication from "./blocks/application/application";
			#! const router = Pilot.create(sitemap);
			#!+ export default function (el, data) {
				#!+ const getViewAttrs = () => ({
					#! name: data.name,
					#! router: router,
					#! route: router.route,
					#! request: router.request
				#!- });
				#! const view = new UIApplication(getViewAttrs());
				#! router.view = view;
				#! router.on("route", () => view.set(getViewAttrs()));
				#! view.renderTo(el);
				#! // ...History API...
				#! return router.nav(location.toString().split("#!").pop()).then(() => router);
			#!- };
		

sitemap.js

			export default {
				"#index": {
					url: "/"
				}
			}
		

«Бьем»

Готово!

Верстаем

Х часов спустя

«Оживляем»

Работа c API: api.js

			#! import feast from "feast";
			#! const api = (path, opts) => feast.resource(`/api/v2/${path}`, opts);
			#!+ export default {
				#!+ leaders: {
					#! users: api("usrrating", {body: "rating"}),
					#! questions: api("leadqst", {body: "qst"})
				#!- },
				#! question: api("question", {id: "qid"}),
				#! questions: api("questlist", {body: "qst"})
			#!- };
		

Маршрутизация: sitemap.js

			#! import api from "./api";
			#! import {mock as user} from "./blocks/user-card/user-card.spec";
			#! import {default as categories, index as categoriesIndex} from "./categories";
			#!+ export default {
				#! model: {/* модели доступные всем маршрутам */},

				#!+ "#index": {
					#! url: {/* описание маски */},
					#! model: {/* доп. модели */}
				#!- },

				#!+ "#ask": { ... }
				"#top": { ... }
				#!- "#question": { ... }
			#!- };
		

Модели доступные всем маршрутам

			export default {
				model: {
					#! categories: () => categories,
					#! currentUser: () => currentUser,
					#!+ leaders: {
						#! match: ["#index", "#question"], // список машрутов
						#! fetch: () =>
							#!+ Promise.all([
								#! api.leaders.users.find({n: 8}),
								#! api.leaders.questions.find({n: 5})
							#!- ])
							#!+		.then(results => ({
										users: results[0],
										questions: results[1]
							#!- 	}))
					#!- }
				}
			}
		

Машрутизация: #index

			"#index": {
				url: {
					#! pattern: "/:category?",
					#!+ params: {
						#!+ category: {
							#! validate: (value) => !!categoriesIndex[value]
						#!- }
					#!- }
				},
				model: {
					#! questions: (req) =>
						#! api.questions.find({n: 20, cat: req.params.category}),
					#! category: (req) =>
						#! ({root: categoriesIndex[req.params.category]})
				}
			}
		

Машрутизация: #ask, #top

			"#ask": {
				url: "/ask"
			},

			"#top": {
				url: "/top",
				model: {
					top: () => api.leaders.users.find({n: 20})
				}
			}
		

Машрутизация: #question

			url: "/question/:id",
			model: {
				#! question: (req) =>
				#!		api.question.findOne(req.params.id),
				#! category: (req, waitFor) =>
					#! waitFor("question").then(q =>
						#!+ ({
								active: q.cid,
								root: categoriesIndex[categoriesIndex[q.cid].parent]
						#!- }))
			}
		

Готово

Pilot v2.0

Pilot v2.0

			router.getUrl("#index", {category: "auto"});
			// → "/auto"

			router.getUrl("#index", {category: 305});
			// → "/auto"

			router.nav(router.getUrl("#index", {category: "auto"}));
			router.go("#index", {category: 305});
		

Pilot v2.0

			"#index": {
				url: {
					pattern: "/:category?",
					params: {
						category: {
							validate: (value) => !!categoriesIndex[value],
							#! encode: (value) => categoriesIndex[value].urlmame
						}
					}
				}
			}
		

Pilot v2.0

			url: {
				pattern: "path/:toName",
				params: {
					toName: {
						validate: (value) => ...,
						decode: (value) => ...,
						encode: (value) => ...,
					}
				},
				query: {
					// аналогично
				}
			}
		

application.html

			<div>
				#! <b:headline key="hl" .../>
				#! <b:portal-menu key="pm" .../>
				#!+ <b:layout key="grid">
					#!+ <fn:match name="left" args="layoutAttrs">
						...
					#!- </fn:match>
					#!+ <fn:match name="center" args="layoutAttrs">
						...
					#!- </fn:match>
				#!- </b:layout>
			</div>
		
			<fn:match name="left" args="layoutAttrs">
				#!+ <fn:if test="**attrs.route.model**.category.root">
					<b:categories-nav key="nav"
						active="{attrs.route.model.category.active}"
						model="{attrs.route.model.category.root}"
					/>
				#!- </fn:if>

				#!+ <fn:if test="attrs.route.model.leaders.questions">
					<b:leaders key="q" type="question"
						models="{attrs.route.model.leaders.questions}"
					/>
				#!-</fn:if>

				#!+ <fn:if test="attrs.route.model.leaders.users">
					<b:leaders key="cat" type="category"
						models="{attrs.route.model.leaders.users}"
					/>
				#!-</fn:if>

				#!+ <fn:if test="**attrs.route.is('#ask')**">
					<b:user-card key="cu" model="{attrs.route.model.currentUser}" />
				#!- </fn:if>
			</fn:match>
		
			<fn:match name="center" args="layoutAttrs">
				<div class="page">
					<fn:choose>
						#!+ <fn:when test="attrs.route.is('#index')">
							#! <b:form key="fa" mode="fast-ask"/>
							#! <h1 class="page-index--h1 bordered">Вопросы участников</h1>
							#! <b:questions key="qlist" items="{attrs.route.model.questions}"/>
						#!- </fn:when>
	
						#!+ <fn:when test="attrs.route.is('#ask')">
							<b:form key="ask" mode="ask" type="question"/>
						#!- </fn:when>
	
						#!+ <fn:when test="attrs.route.is('#question')">
							<b:question key="q" model="{attrs.route.model.question}"/>
						#!- </fn:when>
	
						#!+ <fn:when test="attrs.route.is('#top')">
							<b:leaders key="top" type="project" models="{attrs.route.model.top}"/>
						#!- </fn:when>
					</fn:choose>
				</div>
			</fn:match>
		

fn:add-class

fn:add-class

			<!-- Bad -->
			<div class="item {attrs.selected ? 'selected' : ''}">
				<span>text</span>
			</div>
			#!+ <!-- Good -->
			<div class="item">
				<fn:add-class **name="selected"** **test="attrs.selected"**/>
				<span>text</span>
			#!- </div>
		

8 187 bytes

The End