npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

feast

v1.4.0

Published

Feast — Экспериментальный шаблонизатор на основе xhtml-синтаксиса с конвертацией в vdom

Downloads

75

Readme

Feast

Экспериментальный шаблонизатор на основе xhtml-синтаксиса с конвертацией в vdom (citojs).

Установка и запуск локального сервера

Настройка IDE / Подсветка кода / Language Injection


Работа из консоли

  • ./node_modules/feast/bin/assist create-block --name={name} --path=path/to/feast/blocks/

ES2015+ (+ @decorators)

import {configure, Block} from 'feast';
import UIIcon from '../path/to/Icon/Icon';

@configure({
	name: 'btn', // название блока
	events: {tap: 'handleTap'}, // обработчики события
	isolate: false, // разрешить наследование значений аттрибутов родительского блока
	isolateEvents: false, // разрешить всплытие событий
	blocks: {icon: UIIcon}, // используемые блоки
	defaults: {text: 'Wow!'}, // свойства по умолчанию
	template: `<button remit:click="tap"><b:icon/>{attrs.text}</button>`,
})
export default class extends Block {
	handleTap(evt) {
		// ...
	}
}

// Читый ES2015+ (без декоратора)
export default configure({
	// Опции
})(class extends Block {
	// Методы
});

TypeScript + TSX

import {configure, Block} from 'feast';
import UIIcon from '../path/to/Icon/Icon';

export interface ButtonProps {
	text: string;
}

@configure({
	name: 'btn', // название блока
	events: {tap: 'handleTap'}, // обработчики события
})
export class extends Block<ButtonProps> {
	template({text}) {
		return (
			<button remit-click="tap">
				<Icon/>
				{text}
			</button>
		);
	}

	handleTap(evt) {
		// ...
	}
}

Анимация

Базовые теги


Базовые атрибуты


Модификаторы


BEM

  • bem:mod — элемент и атрибут
  • bem:elem — спец. атрибут

CSS Modules


Создание блока

button.html — шаблон
<div>
	<bem:mod name="disabled" test="attrs.disabled" />

	<div bem:elem="text">
		{attrs.text}
	</div>
</div>
button.js — описание поведения блока

Полное описание методов feast.Block

import feast from 'feast';
import template from 'feast-tpl!./button.html';
import styleSheet from 'feast-css!./button.css';

export default feast.Block.extend({
	name: 'button', // уникальное название блока
	template,
	styleSheet,
	defaults: {
		text: null,
		disabled: false
	}
});

fn:if

Условные оператор.

  • test — любое javascript выражение
<fn:if test="attrs.value > 10">
	Поздравляем!
</fn:if>

<!-- ИЛИ -->

<div fn:if="attrs.value === 777">
	Wow!
</div>

fn:if animated

Анимированные if.

  • test — любое javascript выражение
  • animated или animated="slide"
<fn:if test="attrs.value > 10" animated>
	<b>Поздравляем!</b>
</fn:if>

<!-- ↓ первый frame ↓ -->
<b class="fx-enter">Поздравляем!</b>

<!-- ↓ анимация ↓ -->
<b class="fx-enter-active fx-enter-to">Поздравляем!</b>

fn:transition

Анимация вложенного контента:

  • name — название эффекта (optional)
  • appear — добавить аниация на изначальный рендер (optional)

Фазы
  • Изначальный рендер (appear)

    1. Первый frame: fx[-name]-appear-enter
    2. Анимация: fx[-name]-appear-enter-active fx[-name]-appear-enter-to
  • Добавление элемента

    1. Первый frame: fx[-name]-enter
    2. Анимация: fx[-name]-enter-active fx[-name]-enter-to
  • Удаление элемента

    1. Первый frame: fx[-name]-leave
    2. Анимация: fx[-name]-leave-active fx[-name]-leave-to
<fn:transition name="slide" appear>
	<b>Wow!</b>
</fn:transition>

<!-- ↓ первый frame ↓ -->
<b class="fx-slide-appear-enter">Wow!</b>

<!-- ↓ анимация ↓ -->
<b class="fx-slide-appear-enter-active fx-slide-appear-enter-to">Wow!</b>

fn:choose

Блок ветвления.

<fn:choose>
	<fn:when test="attrs.userName">
		Привет, {attrs.userName}!
	</fn:when>
	
	<fn:otherwise>
		Авторизуйтесь!
	</fn:otherwise>
</fn:choose>

fn:for

Перебор массива или объекта. ВАЖНО: У всех рутовых элементов внутри fn:for должен быть задан уникальный key-атрибут

  • data — массив или объект (любое javascript выражение)
  • as — название переменной очередного элемент массива (опционально)
  • key — индекс или ключ (опционально)
  • filter — функция фильтрации списка, на вход получает два элемента: as и key (опционально)
  • cached — vdom-кеширование по ключу (опционально)
<ul>
	<fn:for data="attrs.items" as="item">
		<li key="{item.id}">
			<a href="{item.href}">{item.text}</a>
		</li>
	</fn:for>
</ul>

fn:value

Вывести любое javascript выражение

  • output — режим вывода
    • raw — «как есть»
    • text — обычный текст (по умолчанию)
<h1>
	Привет <fn:value>attrs.username</fn:value>!
</h1>

или короткий синтаксис

<h1>Привет {attrs.username}!</h1>

Чтобы вывести смивол { и } используйте экранирование \{.


fn:attr

Заменить или установить атрибут родительского элементу

  • name — название
  • value — значение
  • test — любое javascript выражение (опционально)
<a>
	<fn:attr name="href" value="#!{attrs.href}" test="!attrs.disabled"/>
	...
</a>

fn:add-class

Добавить css-класс родительскому элементу

  • name — название класса (через пробел)
  • test — любое javascript выражение (опционально)
<div>
	<fn:add-class name="selected" test="attrs.value === 'foo'"/>
	...
</div>

fn:match и fn:apply-match

Определение и использование подшаблона.

fn:match
  • name — имя подшаблона
  • args — названия аргументов через запятую, которые будут переданны в match от apply-match (опционально)
fn:apply-match
  • name — имя вызываемого подшаблона
  • args — аргументы через запятую, которые нужно передать в match (опционально)

notify.html

<div>
	<h1 bem:elem="title"><fn:apply-match name="title"/><h1>
	<div bem:elem="content">
		<fn:apply-match name="content"/>
	</div>
</div>

Использование:

<b:notify>
	<fn:match name="title">Заголовок</fn:match>
	<fn:match name="content">Какое-то содержание</fn:match>
</b:notify>

id

Доступ к вложеному feast.Block по уникальному id

var App = feast.Block.extend({
	name: 'app',
	template: '<div><b:nav id="nav"/></div>',
	didMount() {
		const nav = this.ids.nav;
	}
});

ref

Доступ к вложеному HTMLElement по уникальному ref

var Form = feast.Block.extend({
	name: 'form',
	template: '<form><button ref="send"/></form>',
	didMount() {
		const sendEl = this.refs.send;
	}
});

on-event

Подписаться на DOM-событие

<div on-click="_this.handleClick(evt)"/>

remit:event

Преобразование DOM-события в пользовательское

var Btn = feast.Block.extend({
	name: 'btn',
	template: '<button remit:click="tap" event:details="attrs.details"/>',
	events: {
		'tap': 'handleTap'
	},
	handleTap(evt) {
		const details = evt.details; // если `event:details` не задан, то details будет ссылкой на `this`
	}
});

Всплытие remit:event

Правильный способ организации всплытия remit-событий
var SubscribeForm = feast.Block.extend({
	name: 'subscribe-form',
	template: feast.parse('<button remit:click="subscribe" value="..."/>')
});

var App = feast.Block.extend({
	name: 'app',
	blocks: {'subscribe-form': SubscribeForm},
	template: feast.parse('<div><b:subscribe-form type="news" remit:subscribe="subscribe:news"/></div>'),
	events: {
		'subscribe:news': 'handleSubscribe'
	},
	handleSubscribe(evt) {
		const type = evt.type.split(':').pop();
	}
});

Модификаторы значения и выражения

Сигнатура

function format(value, format) {
	// применяем модицикацию
	return newValue;
}

Пример

import dateFormat from './data-format';

feast.Block.extend({
	mods: {
		trim: (val) => val.trim(),
		ucfirst: (val) => val.charAt(0).toUpperCase() + val.substr(1),
		'bem-prefix': (val, prefix) => prefix + val,
		dateFormat
	},

	template: `<div class="{attrs.activeClass | trim | bem-prefix:'b-'}">
		Hello, {attrs.name | ucfirst}!
		Сейчас: {Date.now() | dateFormat:"hh:mm"}
	</div>`
});

Модификаторы события

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

<!-- Перехватываем `enter`, отменяем действие по умолчанию и останавливаем всплытие -->
<input remit:keydown.enter.prevent.stop="send"/>
  • Модификатор события
    • prevent — отменить действие по умолчанию
    • stop — остановить всплытие
  • Фильтрация по
    • Названию клавиши
      • enter
      • esc
      • tab
      • left
      • right
      • up
      • down
      • spacebar
      • shift
      • ctrl
      • alt
      • XXXXX — произвольный keyCode
    • Зажатой клавише
      • alt-pressed
      • ctrl-pressed
      • shift-pressed
      • meta-pressed
    • Кнопке мыши
      • left-btn
      • right-btn

Расширение

// on-click.my-modifier="..."
feast.vdom.eventModifiers['my-modifier'] = function myModifier(evt) {
	if (expression(evt)) {
		// Отменяем исполнение слушателя события
		return false;
	}
};

// Добавляем имя клавиши
feast.vdom.eventModifiers.KEYS.q = 'Q'.charCodeAt(0); // да-да, сначало в нижнем, потом верхнем регистре

use:mixin

Использовать примеси на стадии компиляции.

feast.tags.$mixins['form-element'] = function formElementMixin(node, attrs) {
	attrs.name = '{attrs.name}';
	attrs.type = '{attrs.type || "text"}';
	attrs.tabindex = '{attrs.index}';
	attrs.placeholder = '{attrs.placeholder}';
	attrs.required = '{attrs.required}';
	// и так далее
};

var Inp = feast.Block.extend({
	name: 'inp',
	template: '<input use:mixin="form-element"/>'
});

bem:mod

  • name — название модификатора
  • value — значение модикатора (опционально)
  • test — условие добавления модификатора (любое javascript выражение, опционально)
<div>
	<bem:mod name="flat"/>
	<bem:mod name="size" value="{attrs.size}"/>
	<bem:mod name="expanded" test="attrs.expanded"/>
</div>

<!-- ИЛИ -->
<div bem:mod="size_{attrs.size}">
	<!-- увы, но так можно добавить только один модификатор -->
</div>

bem:elem

Спец. атрибут для BEM-именования элементов (только плоское именование, никаких элемент элемента).

<div>
	<h2 bem:elem="title">...</h2>
	<div bem:elem="text">...</div>
</div>

CSS Modules

Для работы используйте feast/src/require-css.js

require.config({
	// ...
	map: {
		'*': {
			'feast-css': '/node_modules/feast/src/require-css.js',
			// ...
		},
	},
});

feast-css

import css from "feast-css!./style.css";

console.log(css.className); // -jdy73jk

feast-css: expose

Обеспечивает доступ к классом из внешнего css

/* expose {form, container as formContainer} from "../form/form.css" */

.button { color: red; }
.form .button { color: green; }
.formContainer .container { margin: 10px; }

feast.Block

// Описание блока
import feast from 'feast';

import template from 'feast-tpl!path/to/block-name/block-name.html';
import styleSheet from 'feast-css!path/to/block-name/block-name.css'; // только если нужна модульность для CSS (уникальные имена CSS-селекторов)

// Используемые блоки
import UIBtn from 'path/to/btn/btn';

export default feast.Block.extend({
	name: 'block-name', // Имя блока, оно же используется как CSS-класс
	
	template, // или `'<div/>'`
	styleSheet, // опционально, только если нужна модульность, css: `{'block-name': '{uniq_hash}'}

	// Изолировать аттрибуты
	//   `true` — по умолчанию
	//   `false` — наследовать аттрибуты родителя
	isolate: false,

	// Изолировать события испускаемые блоком рамками этого блока, по умолчанию `true`
	isolateEvents: false,

	// Список используемых блоков
	blocks: {
		btn: UIBtn
	},

	// Обработка изменения атрибутов
	attrChanged: {
		'attr-name': function (newValue, oldValue) {
			// Любое действие
		}
	},

	// Обработка событий
	events: {
		'click': 'handleClick' // название события => название метода
	},

	handleClick(/** Event */evt) {
		// Обработка события `click`
	},

	didMount() {
		// Блок добавлен в DOM
		// Подписываемся на DOM событие (unmount элемента такие события будут сняты автоматически)
		this.$on(document, 'click', 'handleOutsideClick');
	},

	didUnmount() {
		// Блок извлечен из DOM
	},
	
	handleOutsideClick(/** Event */evt) {
		// Проверяем, что клик сделан за пределами блока
		if (this.el.contains(evt.target)) {
		}
	}
});


// Использование
import UIBlockName from 'path/to/block-name/block-name';

const block = new UIBlockName({
	'attr-bool': false,
});

// Рендер блока
block.renderTo(document.body);

// Уничтожить блок
block.destroy(); // генерируем событие `destroy`, на которое можно подписаться через `on`

Методы

  • get(attrName:string):* — получить значение атрибута
  • set(attrName:string, value:*):void 0 — изменить значение атрибута
  • set(attributes:object):void 0 — изменить значения атрибутов
  • is(attrName:string):boolean — проверить значение атрибута на истинность
  • invert(attrName:string):boolean — инвертировать значение атрибута
  • on(name:string, fn:function) — подписаться на событие блока
  • off(name:string, fn:function) — отписаться от события блока
  • broadcast(name:string[, details:*, [, originalEvent:Event]) — распространить событие вверх по vdom-дереву (т.е. дереву блоков)
  • $on(target:HTMLElement, eventName:string, handle:string|function) — подписаться на DOM событие
  • $off(target:HTMLElement[, eventName:string[, handle:string|function]]) — отписаться от DOM события или событий
  • one(target:HTMLElement[, eventName:string[, handle:string|function]])
  • setTemplateMatch(name:string, match:Function) — установить функцию отвечающую за apply-match

WebStorm / Language Injection

  1. Официальная документация
  2. Editor > Language Injection > *click* [+] > XML Attributes Injection
  • name: feast-keywords
  • Language > ID: JavaScript
  • XML Tag > Local name: if|for|when|var|add-class|attr|match|mod|apply-match
  • XML Attrbiute > Local name: test|data|as|key|args
  1. Editor > Language Injection > *click* [+] > XML Attributes Injection
  • name: feast-interpolation-in-html
  • Language > ID: JavaScript
  • XML Attrbiute > Local name: [a-zA-Z-]+
  • Advanced > Value pattern: \{(.*?)\}
  1. Editor > Language Injection > *click* [+] > XML Attributes Injection
  • name: feast-interpolation-in-feast
  • Language > ID: JavaScript
  • XML Tag > Local name: mod|add-class|attr
  • XML Tag > Local namespace: bem|fn
  • XML Attrbiute > Local name: name|value
  • Advanced > Value pattern: \{(.*?)\}
  1. Editor > Language Injection > *click* [+] > XML Tag Injection
  • name: feast-value
  • Language > ID: JavaScript
  • XML Tag > Local name: value