Snakeskin

This is Frank, a snake-cowboy who loves templates.




Привет! Хочу рассказать о своей разработке — языке программирования текстовых шаблонов «Snakeskin». Проекту больше трех лет, всеми детскими болезнями, я полагаю, он благополучно переболел (и излечился), поэтому хочу поделиться результатом.


Демка


Основной репозиторий


Документация


Плагины для Gulp, Grunt, Webpack и прочее


Gitter — здесь можно задать любой интересующий вопрос


Немного истории


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


В отделе основным был TemplateToolkit2 — популярный в частности у Perl-разработчиков шаблонизатор, а на клиенте использовался простейший MicroTemplate (by Джон Резиг). Ещё в то время активно форсились XSLT-подобные движки, но по ряду причин (обсуждение которых лежит за рамками этой статьи) нам они не подошли. Время от времени мы экспериментировали и с другими: Handlebars, Dust, Closure Templates, плюс свои велосипеды, конечно же… Всё это привело к наличию целого зоопарка шаблонизаторов в проекте.


Моим фаворитом являлся Google Closure Templates: он был близок мне как программисту, т. к. шаблон позиционировался как функция, которая просто возвращает строку, плюс весьма неплохие по тем временам фичи; но очень огорчала необходимость править код на Java, чтобы добавить какой-нибудь банальный фильтр, да и скорость трансляции была не ахти (это реально ощущалось).


И я захотел сделать свой собственный Closure Templates с блэкджеком и шлюхами: естественно, чтобы был написан на JS и, как следствие, открыт к модификациям без необходимости знать Java. Плюс, мне понравилась модель наследования шаблонов, основанная на статических блоках, которую я подсмотрел в Django Templates (отсюда и название — отсылка к Python) — она-то и легла в основу существующей системы наследования.


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


Поработав некоторое время с результатом и выпустив с десяток обновлений, я, потирая руки, сел за компьютер с мыслью «It’s time to make things right», и где-то через месяц выпустил 3-ю версию: выкинул хардкод, переписал код на ES6 (в то время не было нормальных трансляторов, поэтому я ещё и транслятор свой собственный запилил (опять же, с жутким хардкодом на регулярках — да-да, я люблю регулярки)), добавил построение дерева при парсинге и много новых фич.


Версия вышла стабильной, мощной и, по сути, представляла собой Closure Templates на стероидах. Я был доволен результатом и стал использовать Snakeskin в своих личных проектах, время от времени выпуская новые обновления и патчи.


Чуть позже я познакомился с HAML и Jade, мне понравился их подход к синтаксису, и было решено добавить в Snakeskin нечто подобное (результатом этого решения стал Jade-like синтаксис). Спустя несколько месяцев активной разработки я выпустил четвертую версию, ставшую поистине вехой в истории языка и определившую его дальнейшее развитие. Пятая и шестая были не более чем модификацией четвёртой версии, но с ломающими изменениями, которые были необходимы, а так как в качестве паттерна версионирования для Snakeskin мною был выбран SemVer — пришлось апать мажорную версию.


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


Я решил, что так дальше жить нельзя — нужно всё стандартизировать и удалить мусор. Разработка затянулась на полтора года (из которых, правда, активная была максимум полгода — сказывалась нехватка свободного времени), но, в итоге, получился самый стабильный и продуманный на данный момент релиз Snakeskin: версия 7; и я искренне им горжусь.


Первый взгляд


Наиболее подходящим для Snakeskin мне кажется определение, что он — просто «сахар» над JS, как CoffeeScript или TypeScript, но имеет достаточно узкую специализацию: написание шаблонов. Конечно, вполне можно написать на SS хоть всё приложение целиком, но это будет, хех, не очень удобно. SS предназначен для использования вместе с основным языком — преимущественно JS:


select.ss


- namespace select

- template main(options)
  < select
    - forEach options => el
      < option value = ${el.value}
        {el.label}

select.js


import { select } from 'select.ss';

class Select {
  constructor(options) {
    this.template = select.main(options);
  }
}

const newSelect = new Select([{value: 1, label: 'Раз'}, {value: 2, label: 'Два'}])

Тут в основной файл на JS подключается как модуль файл на Snakeskin (такую бесшовную интеграцию дает, например, плагин для WebPack). Из него импортируем namespace select, и объявляем класс Select. При создании инстанса Select, мы выполняем функцию main (в которую был транслирован шаблон main), и присваиваем свойству template результат её работы — для newSelect он будет таким:


<select>
  <option value="1">
    Раз
  </option>
  <option value="2">
    Два
  </option>
</select>

Как видите, SS транслируется в JS (если конкретно, то в ES5), который потом очень просто использовать в основном коде.


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


Одним из «требований времени», например, стала необходимость бесшовной интеграции с фреймворками и библиотеками, которые имеют собственный язык шаблонов (вроде Angular или React — ну а я предпочитаю Vue) — и теперь Snakeskin это отлично удаётся.


Пример использования SS для создания шаблонов Angular:


- namespace myApp
- template main()
  < label
    Name:
  < input type = text | ng-model = yourName | placeholder = Enter a name here
  < hr
  < h1
    Hello {{yourName}}!

Результат работы main


<label>
  Name:
</label>
<input type="text" ng-model="yourName" placeholder="Enter a name here">
<hr>
<h1>
  Hello {{yourName}}!
</h1>

Snakeskin значительно сокращает количество кода, позволяет повторно использовать элементы вёрстки (через наследование, композицию, примеси и т. д.), а Angular осуществляет data-binding. С технической точки зрения SS генерирует шаблон, который потом использует Angular.


Где можно использовать


  • Серверная шаблонизация — тут всё просто: подключаем SS как модуль, компилируем файл — и node. js работает с его шаблонами как с функциями:

'use strict';

const http = require('http');
const ss = require('snakeskin');

// Компилируем файл шаблонов
// Метод вернёт объект с шаблонами-функциями
const tpls = ss.compileFile('./myTpls.ss');

http.createServer((req, res) => {
  res.writeHead(200, {'Content-Type': 'text/html'});

  // Вызываем шаблон foo и передаём параметры
  res.write(tpls.foo('bar', 'bla'));
  res.end();
}).listen(8888);

Разумеется, на практике это будет серверный фреймворк типа Express или Koa, но это не имеет значения. Также, шаблоны можно (и желательно) предварительно транслировать с помощью плагина для Gulp или Grunt и подключать полученные файлы, ну или, как выше — использовать WebPack.


  • Генерация статических сайтов: у плагинов есть опция вызывать скомпилированный шаблон в момент трансляции и возвращать результат его работы. Плагин сам вычислит главный шаблон, либо его можно указать явно.


  • Использование транслированных в JS шаблонов на клиенте: «скомпилированные» модули можно подключать через внешний тег <script>, либо как модуль (с помощью Webpack, Browserify, RequireJS или любой другой системы управлениями модулями).

Краткий обзор языка


Здесь я пробегусь по основным концепциям, а если у вас останутся вопросы — добро пожаловать в документацию или в Gitter.


Основное


Шаблоны


Как уже неоднократно упоминалось, шаблон Snakeskin после трансляции становится функцией JavaScript:


- namespace myApp
- template main()
  Hello world!

после трансляции превратится во что-то вроде:


if (exports.myApp === 'undefined') {
  var myApp = exports.myApp = {};
}

exports.myApp.main = function main() {
  return 'Hello world!';
}

Конечно, это упрощенный код, но в целом это выглядит примерно так.


Синтаксис


SS поддерживает 2 разных вида синтаксиса:


  • Classic: директивы заключены в фигурные скобки; блочные (которые могут содержать внутри себя другой код на SS) должны быть явно закрыты:

{namespace myApp}
{template main(name = 'world')}
  Hello {name}!
{/template}

Этот режим удобно использовать для генерации текста с управляющими пробелами, например кода на Python Markdown.


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


  • Jade-like: основан на управляющих пробелах и похож на Jade (отсюда и название). Пример выше с его использованием будет выглядеть так:

- namespace myApp
- template main(name = 'world')
  Hello {name}!

Главные плюсы этого синтаксиса — краткость и наглядность. Идеально подходит для генерации XML-подобных структур.


Также SS поддерживает смешанный синтаксис:


- namespace myApp

{template hello(name = 'world')}
  Hello {name}!
{/template}

- template main(name)
  += myApp.hello(name)

Подробнее про синтаксис и его виды.


Инструменты code-reuse


Наследование


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


Пример наследования шаблонов.


- namespace myApp

/// Метод sayHello шаблона base
/// В SS метод можно объявлять как внутри шаблона,
/// так и вне его -- с указанием имени шаблона,
/// которому метод будет принадлежать
- block base->sayHello(name = 'world')
  Hello {name}!

- template base()
  - doctype

  < html
    < head

      /// Статичный блок head
      /// Чтобы сделать такой блок методом,
      /// достаточно просто добавить круглые скобки после имени
      - block head
        < title
          /// Свойство шаблона `title`, которое сразу выводится
          - title = 'Главная страница' ?

    < body
      - block body
        /// Вызов метода sayHello
        += self.sayHello()

/// Доопределяем родительский метод
- block child->sayHello()
  /// Вызываем метод sayHello родителя
  - super
  Hello people!

/// Добавляем новый метод
- block child->go()
  Let's go!

/// Шаблон child наследуется от base
- template child() extends myApp.base

  /// Переопределяем свойство
  - title = 'Дочерняя страница'

  /// Доопределяем статичный блок
  - block body
    - super
    += self.go()

Результат выполнения child:


<!DOCTYPE html>
<html>
<head>
  <title>Дочерняя страница</title>
</head>

<body>
  Hello world! Hello people! Let's go!
</body>
</html>

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


Композиция


Т. к. все шаблоны в Snakeskin это функции, то, естественно, любой шаблон может вызвать любой другой шаблон: для этого служит директива call.


- namespace myApp

- template hello(name = 'world')
  Hello {name}!

- template main(name)
  - call myApp.hello(name)

  /// Или короткая форма
  += myApp.hello(name)

Шаблон как значение


В Snakeskin можно присвоить шаблон переменной или свойству объекта, передать его как аргумент в функцию, и так далее.


- namespace myApp

- template wrap(content)
  < .wrapper
    {content}

- template main(name)
  += myApp.wrap()
    < .hello
      Hello world!

Результат выполнения main


<div class="wrapper">
  <div class="hello">
    Hello world!
  </div>
</div>

Модули


Каждый файл, написанный на Snakeskin, представляет собой модуль: глобальные переменные инкапсулируются в нём, а все шаблоны — экспортируются. Модули могут подключать другие модули с помощью директивы include.


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


math.ss


- namespace math
- template sum(a, b)
  {a + b}

app.ss


- namespace myApp
- include './math'

- template main()
  1 + 2 =
  += math.sum(1, 2)

Результат вызова myApp.main


1 + 2 = 3

Приятные плюшки


  • Богатый набор встроенных директив


    В Snakeskin есть: директивы, семантически эквивалентные операторам в JS, такие как if, for, var, return, etc; директивы, специфичные для языка шаблонов и упрощающие разметку XML-подобных структур: tag, attr, doctype, comment и другие; директивы для асинхронной генерации шаблона: await, yield, parallel, waterfall; и множество других.


    Хозяйке на заметку: Snakeskin — это всё-таки не JavaScript, поэтому некоторые директивы в нюансах могут работать не так, как работают аналогичные операторы в JS; например, у переменных, объявленных через var — блочная область видимости (так работает let из ES2015). В директиве with вообще устранены архитектурные недостатки одноименного оператора из JS, что делает её использование в рамках SS вполне себе «good practice», и просто упрощает и ускоряет написание кода.


  • Механизм фильтров


    Filters_Everywhere.jpg


    Фильтры присутствуют в том или ином виде в большинстве шаблонных движков, но в SS они — часть ядра языка, вследствие чего использовать их можно буквально везде: при создании переменных, в циклах, при декларации аргументов блоков и шаблонов, в директивах… В общем, вообще везде.



- namespace myApp
- template main((str|trim), name = ('World'|lower))
  - var a = {foo: 'bar'} |json

В SS из коробки есть много полезных встроенных фильтров, а если их не хватит, то добавить свой — элементарно.


  • Двунаправленная модульная интеграция с JS


    В программу на JS можно импортировать шаблоны SS, а Snakeskin может импортировать модули JavaScript (с помощью директивы import), поддерживая все основные виды модулей: umd, amd, commonjs, native и global.



- namespace myApp
- import { readdirSync } from 'fs'

/// Выводит содержимое директории ./foo
- template main((str|trim), name = ('World'|lower))
  - forEach readdirSync('./foo') => dirname
    {dirname}


- namespace myComponent
- template render()
  < .hello
    {{ this.name }}

import React from 'react';
import { myComponent } from './myComponent.ss';

const Foo = React.createClass({
  render: myComponent.render
});

Для такой бесшовной интеграции, когда шаблон возвращает элемент, созданный с помощью React, используйте Webpack-плагин c включенным флагом jsx.



- namespace myApp
- template main()
  < .hello

    /// hello__wrap
    < .&__wrap

      /// hello__cont
      < .&__cont

  • Умная интерполяция


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



- namespace myApp
- template main(area)
  < ${area ? 'textarea' : 'input'}.b-${area ? 'textarea' : 'input'}
    Бла бла бла

В зависимости от значения area результат будет выглядеть либо так (при area == true):


<textarea class="b-textarea">
  Бла бла бла
</textarea>

либо так (при area == false):


<input class="b-input" value="Бла бла бла">

  • Декораторы шаблонов


    Благодаря механизму декораторов, в Snakeskin легко интегрировать дополнительные модули — например, типограф:



- namespace demo
- import Typograf from 'typograf'

/// Функцию-декоратор можно написать на JS или на SS
- template typograf(params)
  - return
    () => target
      - return
        () =>
          - return new Typograf(params).execute(target.apply(this, arguments))

/// Результат шаблона index всегда будет обработан типографом
- @typograf({lang: 'ru'})
- template index()
  Спорт - это правильно!

  • Асинхронные шаблоны


    SS позволяет создавать шаблоны-генераторы и async-шаблоны, плюс содержит ряд директив для удобного использования популярной библиотеки async.



- namespace myApp

- async template main(db)
  - forEach await db.getData() => el
    {el}

- template *foo(data)
  - for var i = 0; i < data.length; i++
    {data.value}

    - if i % 1e3 === 0
      - yield

Также загляни в раздел «Директивы для асинхронной работы».


  • Настраиваемый рендеринг


    Из коробки Snakeskin поддерживает четыре режима рендеринга: в строку (по умолчанию), в Buffer, в DocumentFragment и в JSX; также есть возможность добавить свой рендерер — например, чтобы сгенерировать кастомный Virtual DOM.


  • Информативные сообщения об ошибках


    В транслятор Snakeskin встроен мощный отладчик кода, который помогает находить большинство синтаксических и логических ошибок при трансляции шаблонов.


  • Поддержка всеми основными системами сборок


    Gulp, Grunt, WebPack.


  • Хорошая кодовая база


    Snakeskin полностью написан на ES2015, содержит большое количество тестов и проходит максимально строгую проверку Google Closure Compiler в режиме ADVANCED. Код хорошо документирован в соответствии со стандартом JSDoc от Google.


  • Подробная и понятная документация


    Которая, кстати, написана на Snakeskin.



Заключение


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


Выражаю искреннюю признательность trikadin за помощь с написанием и редактурой статьи. Кстати, этот парень работает фронтэндером в «Едадиле», и сейчас они проводят у себя внедрение Snakeskin как основного языка шаблонов для Web. Говорит, что он счастлив и не понимает, как жил без SS раньше:)


Также хочу поблагодарить коллектив форума javascript.ru за идеи по развитию языка и поддержку.


О найденных багах пишите в Issues на GitHub-e проекта, а появившиеся вопросы задавайте либо здесь в комментариях, либо в Gitter’е — я всегда с удовольствием отвечу и объясню.


Удачи!

Поделиться с друзьями
-->

Комментарии (27)


  1. k12th
    24.05.2016 19:34
    +1

    А есть подсветка синтаксиса? Хоть для какого-нибудь редактора…


    1. kobezzza
      24.05.2016 19:36
      +1

      Для WebStorm и прочих от JB — настройки
      Для атома в ближайших планах написать плагинчик.


      1. k12th
        24.05.2016 19:41
        +2

        Шикарно, спасибо. Еще бы плагинчик для browserify, но это я сам, может быть, попробую…


        1. kobezzza
          24.05.2016 19:47
          +1

          Забыл про Browserify, каюсь, сделаю. Просто я давно сполз на WebPack и забыл о Browserify :)


        1. trikadin
          24.05.2016 19:47

          Честно говоря, лучше просто перейти на вебпак) Хотя понимаю, что это не всегда бывает возможным.


          1. k12th
            24.05.2016 20:59
            +1

            При всем уважении, я попробовал разок и мне не понравилось.


            1. bustEXZ
              25.05.2016 09:39

              как может не понравится то, что хорошо :) Может вы не те доки читали? Он достаточно прост, без соплей как в gulp (хотя это иногда дает некоторые удобства в представлении того, что происходит)


              1. k12th
                25.05.2016 10:38

                Последний раз, когда я проверял, с доками было грустно. Хороших туториалов тоже не попадалось. И hello world на реакте весом в метр и 20k LOC как-то не вдохновляет.


                1. trikadin
                  26.05.2016 02:54

                  Ну, он не для Hello World'ов, а для сборки средних и крупных проектов со сложными зависимостями.


                  А вообще — достаточно почитать "motivation". чтобы понять, зачем он и для чего.
                  http://webpack.github.io/docs/motivation.html


                  1. kobezzza
                    26.05.2016 10:44

                    Добавлю, что есть также замечательный скринкаст от Ильи Кантора.


                  1. m0sk1t
                    26.05.2016 11:01
                    +1

                    Комментарии не радуют, особенно здесь http://webpack.github.io/docs/usage.html


                    1. trikadin
                      26.05.2016 20:33

                      Не стоит вдохновляться теми, кто не осилил)


                  1. k12th
                    26.05.2016 12:47

                    То есть я должен использовать два разных сборщика, один для сложных проектов, один для простых? В принципе, не проблема, конечно, и так где grunt, где gulp, где кучка npm-скриптов, но все-таки…


                    1. gearbox
                      26.05.2016 15:11

                      Да при чем здесь сборщик и реакт? Собирайте без реакта для маленьких проектов. Собирайте jsx без реакта хоть на своей либе. Собирайте tsx без реакта на своей либе. Собирайте css, html, js — как хотите и куда хотите. Собирайте статику, spa, да хоть расширения для броузеров. Зачем упираться только в реакт?


                    1. trikadin
                      26.05.2016 17:33

                      Gulp — это таск-раннер, webpack — сборщик. Таск-раннер выполняет задачи в указанной последовательности. Сборщику указываются входные точки, и он собирает выходной файл со всеми зависимостями. По сути, это можно сравнить с компиляцией в компилируемых языках, когда собирается итоговый экзешник. Вебпак хорош тем, что он может собрать один (или не один) итоговый файл из вообще всех зависимостей: картинок, стилей, шаблонов — чего угодно. Например, в моём проекте я использую БЭМ-подход. Каждый блок представлен папкой, в которой лежит его шаблон (на Snakeskin), класс (на ES2015) и стили (Stylus). Блоки могут наследоваться, также у них есть зависимости (другие блоки). Страницы — это тоже блоки, которые являются входными точками для Webpack. Вебпак собирает весь проект в несколько бандлов, компилируя шаблоны и стили, прогоняя JS через бабель, вынося общие для нескольких страниц блоки в отдельные .css и .js файлы.


                      Вот зачем нужен вебпак.


                      1. k12th
                        26.05.2016 18:02
                        +2

                        Но ведь то же самое я делал (и делаю) на browserify и, прости господи, grunt/gulp — прогоняя JS через babel, с jade и stylus. При этом я могу отдельно от остального собрать стили и перекомпилить шаблоны. Без оверхеда в почти мегабайт. Ну вроде бы бандловую арифметику browserify не умеет, но если у каждого бандла будет этот меговый довесок, то и не надо.

                        В чем-то заманчиво, конечно, да и от экспресса отставать не хочется. Но такого качественного скачка, как при переходе с шелл-скриптов и, в лучшем случае, Makefile на Grunt, явно не будет. Я подожду второй версии, может, там хотя бы конфиги будут не Хищниками для Чужих написаны.


                        1. trikadin
                          26.05.2016 20:27

                          Расскажите, лол, как с помощью чистого галпа собрать бандл из index-файла с кучей зависимостей. Или как с помощью чистого browserify собрать клиентскую часть так, чтобы стили (которые ещё надо пропустить через stylus и autoprefixer), сохранились как отдельные бандлы (скажем, один common.css и ещё отдельный для каждой страницы), а шаблоны для генерации HTML-кода страниц (которые потом использует нода) чтобы вообще лежали в отдельной папке. Входных точек (страниц) — пять, а блоков — несколько десятков. И чтобы в js-файле для страницы появился класс блока, а в css-файле — его стили, мне достаточно просто указать блок в списке зависимостей страницы.


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


                          Вебпак — сложная штука, но стоит потратить на его изучение несколько дней, оно окупится) Разница между ним и browserify — примерно как между WebStorm и Notepad++. Код можно писать и там, и там, и даже, вроде как, разница по функциям в мелочах, в общем-то… Но на самом деле, она огромная)


                          1. gearbox
                            26.05.2016 21:22

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

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


                            1. trikadin
                              26.05.2016 21:28

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


      1. isxam
        24.05.2016 21:02
        +1

        думаю будет удобнее в репозитории хранить исходники плагина, а джарку можно загружать во вкладке releases, так в ваш плагин смогут контрибьютить
        извиняюсь, если скопитанил :)


        1. trikadin
          24.05.2016 21:14

          К огромнейшему сожалению, это не плагин. Это настройки для "Editor -> FIle Types" в WebStorm)


          1. isxam
            24.05.2016 21:27
            +2

            извиняюсь, упустил в ридми File->Import Settings->Choose «snakeskin.jar»

            но в релизы добавить не помешало бы, чтобы в один клик скачать, не клонируя репозиторий (View Raw не считаю за правильный метод)
            справедливости ради в джарке удобочитаемые xml исходники конфигов которые можно было бы и версионировать ;)


            1. kobezzza
              24.05.2016 23:50

              Спасибо, сделал релиз.


  1. sapog
    25.05.2016 14:44

    Дайте ссылку на первые версии вашего шаблонизатора, который был на регулярках


    1. kobezzza
      25.05.2016 14:45

      На GitHub в истории можно посмотреть легко.


      1. sapog
        25.05.2016 15:33

        если я не ошибаюсь, если судить по тегам, там версия начинается с 2.3, а хотелось бы посмотреть именно первые версии


        1. kobezzza
          25.05.2016 15:43

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

          Одна из первых версий