LINUX.ORG.RU

Сообщения kodoi

 

Cruzo – минималистичный UI фреймворк с роутером, компонентами и разными плюшками (~14 kb gzip)

 , , , ,

Хотел рассказать вам о своих наработках, делаю этот фреймворк с 2020г, с появлением LLM нагенерил доков и UI-тестов, убрал с себя рутину, наконец-то зарелизил. Сам фреймворк без зависимостей.

При этом есть: шаблонизатор с реактивностью, компоненты, роутер, HTTP-обертка, шаблоны {{ }} через байткод (без eval, CSP-friendly).


Всё, что нужно для SPA:

import {
  Template,
  AbstractComponent,
  RxBucket,
  routerService,
  RouteUrlBucket,
  HttpClient,
  componentsRegistryService,
} from "cruzo";

~44 KB min / ~14 KB gzip в бандле приложения (production, tree-shake).

UI-kit (input, select, modal…) — отдельные импорты, в цифру не входит.

Сопоставимая комплектация

«Маленькое SPA: UI-runtime + роутер + HTTP + реактивный стейт», без тяжёлого UI-kit:

СтекЧто входитgzip (порядок)
cruzotemplate VM, components, rx, bucket, router, http~14 KB
Reactreact + react-dom + react-router-dom~55–60 KB
Angularcore + router + common/http (+ zone в классической сборке)~90–130 KB только framework-chunk

React легче только пока у тебя одна кнопка без роутера. Angular — для простой админки часто overkill и по KB, и по голове.


Счётчик

import { AbstractComponent, componentsRegistryService } from "cruzo";

class Counter extends AbstractComponent {
  static selector = "counter";
  count$ = this.newRx(0);

  getHTML() {
    return `
      <button onclick="{{ root.inc() }}">
        ping: {{ root.count$::rx }}
      </button>
    `;
  }

  inc() {
    this.count$.update(this.count$.actual + 1);
  }
}

componentsRegistryService.define(Counter);
componentsRegistryService.initApp();
<counter></counter>

Форма на RxBucket

import { AbstractComponent, RxBucket } from "cruzo";
import { InputConfig } from "cruzo/ui-components/input";
import { SelectConfig } from "cruzo/ui-components/select";

class SearchPanel extends AbstractComponent {
  static selector = "search-panel";

  innerBucket = new RxBucket({
    search: { config: InputConfig({ placeholder: "найти..." }) },
    sort: {
      config: SelectConfig({
        placeholder: "сортировка",
        getItems: async () => [
          { label: "Новые", value: "new" },
          { label: "Старые", value: "old" },
        ],
      }),
    },
  });

  query$ = this.newRxValueFromBucket(this.innerBucket, "search");
  sort$ = this.newRxValueFromBucket(this.innerBucket, "sort");

  getHTML() {
    return `
      <input-component component-id="search" bucket-id="${this.innerBucket.id}"></input-component>
      <select-component component-id="sort" bucket-id="${this.innerBucket.id}"></select-component>
      <pre>query: {{ root.query$::rx }}</pre>
      <pre>sort: {{ root.sort$::rx }}</pre>
    `;
  }
}

Роутер + ленивая подгрузка

import { RouteUrlBucket, delay, routerService } from "cruzo";

export const routes = new RouteUrlBucket({
  home: {
    url: "/",
    componentSelectorUnbox: () => "home-page",
    routeSelectorUnbox: () => "#app",
  },
  lazy: {
    url: "/lazy-demo",
    componentSelectorUnbox: () => "lazy-page",
    routeSelectorUnbox: () => "#app",
    loadResources: async () => {
      await delay(2000); // демо-задержка
      await import("./lazy-page.js");
    },
  },
});

routerService.update();

HTTP из того же пакета

import { HttpClient } from "cruzo";

const api = new HttpClient("https://api.example.com", {
  params: async (_m, _url, opts) => {
    opts.headers ??= {};
    opts.headers.Authorization = "Bearer " + token();
  },
});

const me = await api.get("/me");

Всё это — cruzo, те самые ~14 KB gzip.


Почему я решил сделать VM, а не eval, ведь бандл был бы намного меньше!

Выражения в {{ }} компилируются в байткод и исполняются маленькой VM. Подмножество JS — в {{ }} не весь язык, а фиксированный набор. Парсер отсекает лишнее до исполнения, защита от выполнения произвольного JS.

Ну еще, если есть тег Content-Security-Policy: default-src ‘self’; script-src ‘self’, то eval(…) просто не выполнится.


Ссылки

kodoi
()

JS-библиотека для нечеткого поиска текста.

 , , , ,

https://github.com/MaratBektemirov/alif

Добрый вечер. Хотел поделиться с форумом своими наработками в области нечеткого поиска.

import { ABGD } from "alif"

const probabilityResult = (score, maxScore, templatePermanentLength, wordPermanentLength, x1) => {
  return score/maxScore;
};

const abgd = new ABGD('russian');

const templateTuples = [
  ['отец','мыл','машину'],
  ['мама','мыла','раму'],
  ['машина','отца','помыта'],
  ['отец','отец','отец','отец','мыл','машину'],
  ['рама','мамы','моется'],
].map((arrString) => abgd.getTuple(arrString))

const tuple = abgd.getTuple(['мама','мыла','раму'])

abgd.getTuplesProbabilities(
  templateTuples,
  tuple,
  probabilityResult
)
-> [
    [1,1],
    [0.5555555555555556,4],
    [0.2777777777777778,0],
    [0.1388888888888889,3],
    [0,2]
  ]

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

kodoi
()

RSS подписка на новые темы