1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/powjs-powjs

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

PowJS

badge npm npm npm

PowJS — это компилирующийся в реальном времени шаблонный движок для ECMAScript 6 с использованием Real-DOM.

Реальный DOM компилируется и отображается непосредственно на дереве DOM. Дерево DOM является шаблоном.
Нативная грамматика, где команды соответствуют нативной грамматике ECMAScript.
Экспортирование представлений осуществляется через исходный код ECMAScript.
Вставка значений атрибутов позволяет использовать интерполяцию для значений атрибутов, таких как `name="somethin {{expr}}"`.
Интерполяция текстовых узлов позволяет использовать интерполяцию для текстовых узлов, таких как `{{expr}}`. Пробелы в начале и конце текстового узла всегда удаляются.
По умолчанию параметры верхнего уровня имеют значение `(v, k)`.
Передача параметров происходит наследственным образом, если не используется директива `param`.

Для получения дополнительной информации перейдите на [Wiki][].

Процесс

Строка HTML ----> Real DocumentFragment
                    |
                    V
Real Node   ----> PowJS <----> Представление
                    |
                    V
                  render(...args)
                    |
                    V
                  Real DocumentFragment ----> Real DOM
```## Установка

Среда NodeJS

```sh
yarn add powjs

Среда браузера

<script src="//unpkg.com/powjs"></script>

Начало работы

PowJS представляет собой модуль, основной функцией которого является:

module.exports = function (source, option) {
  /**
   * Аргументы
   *
   *   source:
   *      undefined     Возвращает PowJS.prototype
   *      string        Компиляция HTML-источника
   *      Node          Компиляция одного узла DOM
   *      [Node]        Компиляция нескольких узлов DOM
   *      [Array]       Загрузка уже скомпилированных представлений PowJS
   *      прочее        Вызов ошибки или пустой результат рендера
   *
   *   option:
   *      string        Опциональный префикс команд при компиляции, по умолчанию ''
   *      Object        Опциональный аддон при рендере
   *      прочее        Пропущено
   *
   * Возвращаемое значение
   *
   *   PowJS.prototype  Если source === undefined
   *   Инстанс PowJS    Если source instanceof Node
   *                       или Array.isArray(source)
   */
};

Процесс рендера выполняется в объекте DocumentFragment, что не влияет напрямую на страницу. Экспортированный вид представляет собой массив представлений, каждый из которых имеет структуру, соответствующую структуре узлов DOM:

/*! Генерировано PowJS. Не редактируйте */
module.exports = [
  [
    'TAG',
    {/*Неконкатенированные атрибуты*/},
    function(param, paramN) {
        /*направления или конкатенация*/
    },
    [
        /*...представление childNodes*/
    ]
    /* Может быть имя */
  ]
  /* более представлений ...*/
];
```Пример с навигационной панелью в виде хлебных крошек:

```html
<nav>
  <div class="nav-wrapper">
    <div class="col s12">
      <a href="#!" class="breadcrumb">Первый</a>
      <a href="#!" class="breadcrumb">Второй</a>
      <a href="#!" class="breadcrumb">Третий</a>
    </div>
  </div>
</nav>

Высокочитаемый шаблон с использованием PowJS:

<nav func="breadcrumb" param="пути">
  <div class="nav-wrapper">
    <div class="col s12" each="пути, путь">
      <a href="#!" class="breadcrumb">{{путь}}</a>
    </div>
  </div>
</nav>

Использование PowJS для компиляции и генерации кода (массив представлений):

const powjs = require('powjs');
let instance = powjs(htmlOrNodeOrView);
instance.toScript();
// instance.render(['Первый','Второй','Третий']);

Генерация:

[
  [
    "NAV", 0, 0,
    [
      [
        "DIV",{ class: "nav-wrapper" }, 0,
        [
          [
            "DIV", { class: "col s12" }, function(пути) {
              this.each(пути);
            },
            [
              [
                "A", { href: "#!", class: "breadcrumb" }, 0,
                [
                  [
                    "#", 0, function(путь, k, $l, $n) {
                      this.text(`${путь}`);
                    }
                  ]
                ]
              ]
            ]
          ]
        ]
      ]
    ],
    "breadcrumb"
  ]
]

Можно представить псевдокодом следующим образом:

function breadcrumb(пути) {
  create('nav');
  createChild(function() {
    create('div', {class: 'nav-wrapper'});
    createChild(function(){
      create('div', {class: 'col s12'});
      eachCreateChild(пути, function(путь) {
        create('a', {href: '#!', class: 'breadcrumb'});
        createChild(function(путь) {
          text(путь);
        });
      });
    });
  });
}

Еще более простое представление, без имени функции (представления), использует значение параметра по умолчанию v:

create('nav');
createChild(function() {
  create('div', {class: 'nav-wrapper'});
  createChild(function(){
    create('div', {class: 'col s12'});
    eachCreateChild(v, function(путь) {
      create('a', {href: '#!', class: 'breadcrumb'});
      createChild(function(путь) {
        text(путь);
      });
    });
  });
});
``````html
<nav>
  <div class="nav-wrapper">
    <div class="col s12" each="v">
      <a href="#!" class="breadcrumb">{{v}}</a>
    </div>
  </div>
</nav>

Инструкции

Инструкции в узле являются атрибутами, значения которых представляют собой выражения или операторы ECMAScript, и конечный результат — создание функции представления. func = "name" Устанавливает имя сгенерированной функции представления param = "v,k" Параметры сгенерированной функции представления: см. пример ниже if = "условие" Условие отрисовки и изменяемой метки: см. раздел ниже let = "a=выражение,b=1" Локальные переменные: let a=выражение,b=1; do = "код" Выполнение кода: код; text = "выражение" Установка текста: this.text(выражение); html = "выражение" Установка HTML: this.html(выражение); end Сохраняет текущий узел, завершает отрисовку: return this.end(); end = "условие" Сохраняет текущий узел, завершает отрисовку при выполнении условия: если(условие) return this.end(); skip Пропускает отрисовку подузлов: this.skip(); skip = "условие" Отрисовывает подузлы при выполнении условия: если(условие) this.skip(); break Пропускает отрисовку братских узлов: this.break(); break = "условие" Пропускает отрисовку братских узлов при выполнении условия: если(условие) this.break(); render = "аргументы,...,аргументыN" Отображает подузлы: this.render(аргументы,...,аргументыN); each = "выражение,аргументы,...,аргументыN" Проходит по массиву и отображает подузлы: this.each(выражение,аргументы,...,аргументыN);### Порядок инструкций

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

Порядок выполнения инструкций:

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

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

<тег func="имя" param="данные" if="Array.isArray(данные)" 
  id="{{данные[0]}}" each="данные" break class="статичное">
  <!-- ... -->
</тег>

Соответствующий псевдокод:

function имя(данные) {
  if(Array.isArray(данные)) {
    this.create('тэг', {class:'статичное'});
  } else {
    return;
  }
  this.attr('id', данные[0]);
}
```    func   = "имя"           Устанавливает имя сгенерированной функции представления
    param  = "v,k"           Параметры сгенерированной функции представления: см. пример ниже
    if     = "условие"       Условие отрисовки и изменяемой метки: см. раздел ниже
    let    = "a=выражение,b=1" Локальные переменные: let a=выражение,b=1;
    do     = "код"           Выполнение кода: код;
    text   = "выражение"     Установка текста: this.text(выражение);
    html   = "выражение"     Установка HTML: this.html(выражение);
    end                           Сохраняет текущий узел, завершает отрисовку: return this.end();
    end   = "условие"        Сохраняет текущий узел, завершает отрисовку при выполнении условия: если(условие) return this.end();
    skip                          Пропускает отрисовку подузлов: this.skip();
    skip  = "условие"         Отрисовывает подузлы при выполнении условия: если(условие) this.skip();
    break                        Пропускает отрисовку братских узлов: this.break();
    break = "условие"         Пропускает отрисовку братских узлов при выполнении условия: если(условие) this.break();
    render = "аргументы,...,аргументыN" Отображает подузлы: this.render(аргументы,...,аргументыN);
    each   = "выражение,аргументы,...,аргументыN" Проходит по массиву и отображает подузлы: this.each(выражение,аргументы,...,аргументыN);### Порядок инструкций

Первое, инструкции как атрибуты узла не повторяются, это указано в спецификациях HTML DOM.

Порядок выполнения инструкций:

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

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

```html
<тег func="имя" param="данные" if="Array.isArray(данные)"
  id="{{данные[0]}}" each="данные" break class="статичное">
  <!-- ... -->
</тег>

Соответствующий псевдокод:

function имя(данные) {
  if (Array.isArray(данные)) {
    this.create('тег', {class: 'статичное'});
  } else {
    return;
  }
  this.attr('id', данные[ Yöntem dökümü için bu satırın çevirisi yapılmadı. ]);
  this.each(данные);
  this.break();
}

Инструкция skip не должна использоваться после each, render, html, text, так как подузлы уже были отрисованы. Аналогично, если первым используется end, то последующие инструкции не будут выполнены.

Может потребоваться использовать инструкцию do после each, render для дальнейшей обработки.

PowJS проверяет наличие явных конфликтов между инструкциями во время компиляции, но важно иметь хорошую практику записи порядка инструкций. Например, инструкции func, param, if должны быть расположены в начале для удобства чтения.### ИнтерполяцияИнтерполяция может использоваться в текстовых узлах или ненаправленных атрибутах, но инструкции могут использовать только ECMAScript, использование интерполяции запрещено.

Интерполяция преобразуется в шаблонные строки ECMAScript, PowJS просто заменяет {{, }} на ${}, {}.

abc {{exp}} def  ===> `abc ${exp} def`

func

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

<b func="name"><i>{{@name}}</i></b>
<b func="name"><i if="something && '@name'"></i></b>
<b func="name"><i do="if(something) return this.call('name', arg)"></i></b>

Как показано выше, есть два способа вызова функции подпредставлений:

  • В текстовом узле, текстовое выражение заключено в {{@, }}
  • Возвращается строка имени представления, начинающаяся с @ в if, при этом текущий узел ещё не создан, поведение — передача узла
  • Используется this.call для передачи имени представления и других аргументов, когда текущий узел уже создан, поведение — создание подузла

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

Пример:

{{@name}}<span func="name">да</span>
<!-- render().html() вывод: -->
<span>да</span><span>да</span>

Пример:

<i if="'@name'">никогда</i><span func="name">да</span>
<!-- render().html() вывод: -->
<span>да</span><span>да</span>
```Пример:

```html
<b><i if="'@name'">никогда</i></b><span func="name">да</span>
<!-- render().html() вывод: -->
<b><span>да</span></b><span>да</span>

Пример:

<i do="return this.call('name')">никогда</i><span func="name">да</span>
<!-- render().html() вывод: -->
<i><span>да</span></i><span>да</span>

Пример:

<b><i do="return this.call('name')">никогда</i></b><span func="name">да</span>
<!-- render().html() вывод: -->
<b><i><span>да</span></i></b><span>да</span>

param

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

each-render

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

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

  • В значении (параметре) должно начинаться с :, из последующих аргументов извлекаются параметры формы подслоя param.
  • В each используется xxx- для создания пользовательских параметров формы, в противном случае используются параметры формы по умолчанию.Инструкция each всегда добавляет следующие четыре параметра в фиксированном порядке после пользовательских параметров:
  1. Пройденное значение можно использовать с префиксом val- для переопределения имени аргумента, по умолчанию v.
  2. Пройденный ключ можно использовать с префиксом key- для переопределения имени аргумента, по умолчанию k.
  3. Общее количество элементов можно использовать с префиксом len- для переопределения имени аргумента, по умолчанию $l.
  4. Номер текущего прохода начинается с 1, можно использовать префикс num- для переопределения имени аргумента, по умолчанию $n.Поведение:
  • Дедукция аргументов действует только для внутреннего уровня.
  • Не выполняется синтаксический анализ, используется простое строковое преобразование.
  • Дополнительные параметры each всегда добавляются после пользовательских параметров в строго определённом порядке.
  • Внутренний уровень может использовать param для переопределения имени аргумента.

Пример: параметры render не начинаются с :, поэтому дедукция аргументов не применяется.

<ul render="k,v"><li>{{k}}{{v}}</li></ul>
<!-- pow.render(1,2).html() вывод: -->
<ul><li>12</li></ul>

Пример: параметры render начинаются с :, поэтому происходит дедукция аргументов.

<ul render=":k,v"><li>{{k}}{{v}}</li></ul>
<!-- pow.render(1,2).html() вывод: -->
<ul><li>21</li></ul>

Пример: параметры each всегда добавляются в конце.

<dl param="array, id" each=":array,id">
<dd>{{id}}:{{item}}</dd> <!-- функция(id,item,v,k,$l,$n) -->
</dl>

<dl param="array, id" each="array,id,val-item">
<dd>{{id}}:{{item}}</dd> <!-- функция(id,item,k,$l,$n) -->
</dl>

<dl param="array, id" each=":array,id,val-item,num-row">
<dd>{{id}}:{{item}}</dd> <!-- функция(id,item,key,$l,row) -->
</dl>

Алгоритм извлечения имен аргументов для дедукции аргументов:

/**
 * Разделение выражения аргументов для дедукции аргументов
 * @param {string} expOfRenderOrEach Без начального ':'
 * @return {array}
 */
function splitArguments(expOfRenderOrEach) {
  return expOfRenderOrEach.match(/(key-|val-)?(([a-z]\w*),|([a-z]\w*)$)/ig)
    .map(function(s) {
      return s.endsWith(',') ? s.slice(0, -1) : s;
    });
}

if

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

<ul param="data" if="Array.isArray(data)"></ul>

Создаётся:

[
  [
    function(data) {
      return Array.isArray(data) && "UL";
    }
  ]
]

Пример: если условие заканчивается на ;, то не добавляется дефолтное имя узла.

<ul param="data" if="Array.isArray(data) && 'OL'||'DIV';"></ul>

Создаётся:

[
  [
    function(data) {
      return (Array.isArray(data) && "OL") || "DIV";
    }
  ]
]

Пример: использование заполнителя --- в кавычках, но PowJS не проверяет наличие этих кавычек.

<ul param="data" if="Array.isArray(data) && '---'"></ul>

Создаётся:

[
  [
    function(data) {
      return Array.isArray(data) && "UL";
    },
  ]
]

внутренняя реализация команды:

directives.if = function(exp, tag) {
  if (exp.includes('---')) {
    exp = exp.replace(/---/g, tag);
    return `return ${exp};`;
  }

  if (exp.endsWith(';')) return `return ${exp}`;
  return `return ${exp} && '${tag}';`;
};

то есть:

  1. Включает заполнитель ---, заменяет --- текущим именем тэга
  2. Заканчивается на ||, добавляет TAG
  3. В противном случае добавляет && TAG

влияние возвращаемого значения:- Нечётное значение — отказаться от создания узла

  • Пустое значение — отказаться от создания узла
  • Начинается с # — создать узел Text, содержимое узла — всё после #
  • Начинается с @ — вызвать названный вид, передать наследуемые аргументы
  • Начинается с = — присвоить значение = после this.parent.textContent, типичный случай стиль
  • Начинается с ! — создать узел комментария, содержимое узла — всё после !
  • Начинается с : — псевдоузел, выполнить действие this.node = this.parent, затем продолжить выполнение последующих команд (кода)
  • Начинается с буквы — создать узел Element
  • Любое другое — не создавать узел и продолжить выполнение последующих команд (кода), обратите внимание, что при этом this.node === null### skip-break

В методе рендера render рендеринг потомков является шагом текущего уровня; skip пропускает текущий уровень, то есть пропускает рендеринг потомков. А рендеринг потомков происходит в цикле, break выходит из цикла, то есть пропускает рендеринг братских уровней.

do

Когда другие команды не удовлетворяют требованиям, do является последним средством, позволяющим напрямую использовать оригинальный ECMAScript код.

Пример:

<div param="array" if="isArray(array)" 
  do="if(isArray(array[0])) return this.each(array)">
  ...
</div>

генерирует:

[
  [
    function(array) {
      return isArray(array) && "DIV";
    },
    0,
    function(array) {
      if (isArray(array[0])) return this.each(array);
      this.render(array); // PowJS дополняет недостающее поведение
    },
    [["#..."]]            // # начинается с Text узла
  ]
]

Если используемые команды не связаны с рендерингом, PowJS дополняет this.render. Аналогично, использование команды skip позволяет избежать выполнения дополняющего this.render.

Атрибуты и методы

Атрибуты:

x       add-on, если требуется, можно установить в любое время
node    только для чтения, текущий узел, созданный рендером
parent  только для чтения, родительский узел текущего узла, самый верхний уровень — это временный узел типа DocumentFragment BODY

Методы: create() Внутренний метод, создаёт текущий узел end() Внутренний метод, завершает команду break() Внутренний метод, прерывает выполнение команды render(...) Входной метод для отрисовки, отрисовывает и возвращает this each(x,...) Обход отрисовки, отрисовывает и возвращает this, внутренне вызывает this.render(..., v, k) text(expr) Уникальная команда html(expr) Уникальная команда call(name,...) Вызов представления addon(object) Дополнительный метод, устанавливает плагин или контекст, возвращает this isRoot() Дополнительный метод, проверяет является ли this корнем представления isReal() Дополнительный метод, проверяет соединён ли текущий узел с реальным DOM страницы attr(attrName,v) Дополнительный метод, устанавливает или возвращает значение атрибута текущего узла prop(propName,v) Дополнительный метод, устанавливает или возвращает значение свойства текущего узла. Например, checked. firstChild() Дополнительный метод, возвращает первый дочерний узел this.parent childNodes() Дополнительный метод, возвращает все дочерние узлы this.parent lastChild() Дополнительный метод, возвращает последний дочерний узел this.parent query(selector) Дополнительный метод, выполняет выборку всех узлов this.parent согласно селектору slice(...) Дополнительный метод, вызывает Array.prototype.slice inc() Дополнительный метод, увеличивает счётчик: return ++counter pow(inc) Дополнительный метод, генерирует уникальный ID если (inc) this.inc(); return '-pow-' + counter; toScript() Дополнительный метод, экспортирует исходный код представления exports(target) Дополнительный метод, экспортирует исходный код представления, префикс module.exports = renew(node) Операция узла, заменяет узел node отрисованным узлом appendTo(node) Операция узла, добавляет отрисованный узел в конец node insertBefore(node) Операция узла, вставляет отрисованный узел перед node insertAfter(node) Операция узла, вставляет отрисованный узел после node removeChilds() Операция узла, удаляет все дочерние узлы этого узла### Каждый

Уникальная команда each, этот метод может проходить через объект типа [object Object] или ArrayLike. Обязательные параметры всегда передаются значением, ключом (индексом) в метод render.

Конец

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

isRoot

Вершина представления создана в виде экземпляра PowJS, который является вершинным экземпляром, во время отрисовки создаются временные экземпляры. У вершинного экземпляра свойства parent и node являются одним и тем же объектом, и свойство root вершины равно null. Реализация:

PowJS.prototype.isRoot = function() {
  return !this.root;
};

Этот объект является:

document.createElement('template').content;

Процесс рендера происходит в template -> DocumentFragment, что не直接影响页面。

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

  • В шаблоне вершина имеет несколько узлов
  • render выполняется несколько раз
  • Дочерние узлы были добавлены на страницу (или удалены)

Факты:- view — массив представлений, каждый элемент которого представляет собой представление одного узла.

  • render — рендеринг дочерних узлов, проходящий через массив представлений view и генерирующий дочерние узлы для родителя.
  • each — проход по рендерингу дочерних узлов, вызывающий render с передачей значений и ключей (индексов).Не следует использовать методы attr, prop для вершинного объекта. Будьте осторожны при использовании методов text, html.

Почему

Использование this.parent === this.node для isRoot имеет более глубокие причины:

  • Это позволяет разделить this.parent и this.node, создавая больше возможностей изменения.
  • Например, установка this.parent или this.node на узел на странице позволяет осуществлять реальное время рендеринг.

Addon

Свойство x экземпляра PowJS представляет собой объект addon, предоставленный пользователем, который объединяет плагины и контекст пользователя. Когда имя свойства совпадает с именем свойства в addon и это функция, то эта функция является плагином, который будет выполнен при рендере, если его имя совпадает с атрибутом. Естественно, если он не определяется как плагин, то управляет им сам пользователь (в контексте).

Прототип функции плагина:

/**
 * Прототип плагина, если он был выполнен, PowJS больше не будет устанавливать это свойство
 * @param  {PowJS}   pow  текущий экземпляр PowJS
 * @param  {string}  val  значение свойства
 * @param  {string}  key  имя свойства
 */
function plugin(pow, val, key) {
    //...
}

Пример:

let pow = require('powjs');

pow(`<img src="1.jpg" do="this.attr('src','2.jpg')">`, {
  src: function(pow, val) {
      pow.attr('data-src', val);
  }
}).render().html();
// вывод: <img data-src="2.jpg">

Псевдоузлы

Псевдоузлы не создаются как DOM узлы и служат эффектом блока кода.Учитывая, что пользовательские узлы ещё не поддерживаются большинством браузеров, можно создать псевдоузлы с помощью if="':';":

<div param="array" if="':';" each="array,val-name">
<b>{{name}}</b>
</div>
<!-- render([1,2,3]) вывод -->
<b>1</b><b>2</b><b>3</b>

Представление:```js [ [ function(массив) { return ":"; }, 0, function(массив) { this.each(массив); }, [ [ "B", 0, 0, [ [ "#", 0, function(имя, k, $l, $n) { this.text(${имя}); } ] ] ] ] ] ]


## xPowJS

Для расширения прототипа PowJS можно использовать `require('powjs')()`. Чтобы избежать конфликтов с будущими версиями, в PowJS сохранены свойства и методы, начинающиеся с символа `$`, гарантируется же отсутствие использования префикса `x`.

На самом деле, свойства с префиксом `x` уже используются для пользовательских плагинов.

## shadowRoot

PowJS не управляет shadowRoot. Если в конструкторе используется PowJS для создания потомков, то это независимо.

Поэтому `powjs(shadowroot).html()` не будет содержать внутренний HTML элемента `shadowroot.shadowRoot`, что полностью соответствует первоначальному значению Shadow DOM.

## Лицензия

Лицензия MIT <https://gitee.com/powjs/powjs/blob/master/LICENSE>

[PowJS]: https://gitee.com/powjs/powjs
[Wiki]: https://gitee.com/powjs/powjs/wiki

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

Вы можете оставить комментарий после Вход в систему

Введение

Компиляторный Real-DOM шаблонный движок. Развернуть Свернуть
MIT
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/powjs-powjs.git
git@api.gitlife.ru:oschina-mirror/powjs-powjs.git
oschina-mirror
powjs-powjs
powjs-powjs
master