Створення веб-додатків з використанням мікрофронтендів та Module Federation

У цій статті ми розберемо процес розробки веб-застосунку на основі підходу мікрофронтендів з використанням технології Module Federation.

Мікрофронтенди – це підхід у веб-розробці, при якому фронтенд поділяється на безліч дрібних, автономних елементів. Ці частини розробляються різними командами, мабуть, з використанням різних технологій, але в результаті вони спільно функціонують як єдине ціле. Такий підхід дозволяє вирішувати проблеми, пов’язані з великими програмами, спрощує процес розробки та тестування, сприяє використанню різноманітних технологій та покращує можливості повторного використання коду.

Мета нашого проекту – створити банківську програму, що має функціональність для перегляду та редагування банківських карток і транзакцій.

Для реалізації виберемо AntdDesign , React.js у комбінації з Module Federation

Схема роботи додатка

На схемі представлена ​​архітектура веб-додатку, що використовує мікрофронтенд з інтеграцією через Module Federation. Вгорі зображення знаходиться Host , який є головним додатком (Main app) і служить контейнером для інших додатків.

Існують два мікрофронтенди: Cards і Transactions , кожне з яких розроблено окремою командою і виконує свої функції в рамках банківського додатку.

Також на схемі є компонент Shared , який містить загальні ресурси, такі як типи даних, утиліти, компоненти та інше. Ці ресурси імпортуються як у Host, так і в мікрододатки Cards та Transactions, що забезпечує консистентність та перевикористання коду у всій екосистемі програми.

Крім того, тут зображено Event Bus , який є механізмом для обміну повідомленнями та подіями між компонентами системи. Це забезпечує спілкування між Host і мікропрограмами, а також між самими мікропрограмами, що дозволяє їм реагувати на зміни станів.

Дана схема демонструє модульну та розширювану структуру веб-додатку, що є однією з ключових переваг підходу мікрофронтендів. Це дозволяє розробляти програми, які легше підтримувати, оновлювати та масштабувати.

Ми організуємо наші програми всередині директорії packages та налаштуємо Yarn Workspaces, що дозволить нам ефективно використовувати спільні компоненти з модуля shared між різними пакетами.

"workspaces": [
    "packages/*"
  ],

Module Federation, введений Webpack 5, дозволяє різним частинам програми завантажувати код один одного динамічно. За допомогою цієї функції ми забезпечимо асинхронне завантаження компонентів

Webpack-конфіг для host-програми

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

const deps = require('./package.json').dependencies;
const isProduction = process.env.NODE_ENV === 'production';

module.exports = {
  // Решта конфігурації Webpack, не пов'язана безпосередньо з Module Federation
  // ...

  plugins: [
    // Плагін Module Federation для інтеграції мікрофронтендів
    new ModuleFederationPlugin({
      remotes: {
        // Визначення віддалених мікрофронтендів, доступних для цього мікрофронтенду
        'remote-modules-transactions': isProduction
          ? 'remoteModulesTransactions@https://microfrontend.fancy-app.site/apps/transactions/remoteEntry.js'
          : 'remoteModulesTransactions@http://localhost:3003/remoteEntry.js',
        'remote-modules-cards': isProduction
          ? 'remoteModulesCards@https://microfrontend.fancy-app.site/apps/cards/remoteEntry.js'
          : 'remoteModulesCards@http://localhost:3001/remoteEntry.js',
      },
      shared: {
        // Визначення загальних залежностей між різними мікрофронтендами
        react: { singleton: true, requiredVersion: deps.react },
        antd: { singleton: true, requiredVersion: deps['antd'] },
        'react-dom': { singleton: true, requiredVersion: deps['react-dom'] },
        'react-redux': { singleton: true, requiredVersion: deps['react-redux'] },
        axios: { singleton: true, requiredVersion: deps['axios'] },
      },
    }),
    new HtmlWebpackPlugin({
      template: path.join(__dirname, 'src', 'index.html'), // Шаблон HTML для Webpack
    }),
  ],

  // Інші налаштування Webpack
  // ...
};

Webpack-конфіг для програми “Банківські карти”

const path = require('path');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const deps = require('./package.json').dependencies;

module.exports = {
  // Інші налаштування Webpack...

  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, 'src', 'index.html'), // Шаблон HTML для Webpack
    }),
    // Налаштування Module Federation Plugin
    new ModuleFederationPlugin({
      name: 'remoteModulesCards', // Ім'я мікрофронтенду
      filename: 'remoteEntry.js', // Ім'я файлу, який буде точкою входу для мікрофронтенду
      exposes: {
        './Cards': './src/root', // Визначає, які модулі та компоненти будуть доступні для інших мікрофронтендів.
      },
      shared: {
        // Визначення залежностей, які будуть використовуватися як спільні між різними мікрофронтендами
        react: { requiredVersion: deps.react, singleton: true },
        antd: { singleton: true, requiredVersion: deps['antd'] },
        'react-dom': { requiredVersion: deps['react-dom'], singleton: true },
        'react-redux': { singleton: true, requiredVersion: deps['react-redux'] },
        axios: { singleton: true, requiredVersion: deps['axios'] },
      },
    }),
  ],

  // Інші налаштування Webpack...
};

Тепер ми легко можемо імпортувати наші програми в host-додаток.

import React, { Suspense, useEffect } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { Main } from '../pages/Main';
import { MainLayout } from '@host/layouts/MainLayout';

// Лініве завантаження компонентів Cards та Transactions з віддалених модулів
const Cards = React.lazy(() => import('remote-modules-cards/Cards'));
const Transactions = React.lazy(() => import('remote-modules-transactions/Transactions'));

const Pages = () => {
  return (
    <Router>
		   <MainLayout>
          {/* Використання Suspense для керування станом завантаження асинхронних компонентів */}
          <Suspense fallback={<div>Loading...</div>}>
            <Routes>
              <Route path={'/'} element={<Main />} />
              <Route path={'/cards/*'} element={<Cards />} />
              <Route path={'/transactions/*'} element={<Transactions />} />
            </Routes>
          </Suspense>
        </MainLayout>
    </Router>
  );
};
export default Pages;

Далі для команди “Банківські карти” налаштуємо Redux Toolkit

// Імпортуємо функцію configureStore із бібліотеки Redux Toolkit
import { configureStore } from '@reduxjs/toolkit';

// Імпортуємо кореневий редьюсер
import rootReducer from './features';

// Створюємо сховище за допомогою функції configureStore
const store = configureStore({
  // Встановлюємо кореневий редьюсер
  reducer: rootReducer,
  // Встановлюємо проміжне програмне забезпечення за замовчуванням
  middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
});

// Експортуємо сховище
export default store;

// Визначаємо типи для диспетчера та стану програми
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
// Імпортуємо React
import React from 'react';

// Імпортуємо головний компонент програми
import App from '../app/App';

// Імпортуємо Provider із react-redux для зв'язку React та Redux
import { Provider } from 'react-redux';

// Імпортуємо наше сховище Redux
import store from '@modules/cards/store/store';

// Створюємо головний компонент Index
const Index = (): JSX.Element => {
  return (
    // Повертаємо наш додаток у Provider, передаючи в нього наше сховище
    <Provider store={store}>
      <App />
    </Provider>
  );
};

// Експортуємо головний компонент
export default Index;

У додатку має бути система ролей:

  • USER – може переглядати сторінки,
  • MANAGER – має право на редагування,
  • ADMIN – може редагувати та видаляти дані.

Host-програма надсилає запит на сервер для отримання інформації про користувача та зберігає ці дані у своєму сховищі. Необхідно ізольовано отримати ці дані у додатку “Банківські карти”.

Для цього потрібно написати middleware для Redux-сторонка host-додатку, щоб зберігати дані в глобальний об’єкт window

// Імпортуємо функцію configureStore та тип Middleware з бібліотеки Redux Toolkit
import { configureStore, Middleware } from '@reduxjs/toolkit';

// Імпортуємо кореневий редьюсер та тип RootState
import rootReducer, { RootState } from './features';

// Створюємо проміжне програмне забезпечення, яке зберігає стан програми в глобальному об'єкті window
const windowStateMiddleware: Middleware<{}, RootState> =
  (store) => (next) => (action) => {
    const result = next(action);
    (window as any).host = store.getState();
    return result;
  };

// Функція завантаження стану з глобального об'єкта window
const loadFromWindow = (): RootState | undefined => {
  try {
    const hostState = (window as any).host;
    if (hostState === null) return undefined;
    return hostState;
  } catch (e) {
    console.warn('Error loading state from window:', e);
    return undefined;
  }
};

// Створюємо сховище за допомогою функції configureStore
const store = configureStore({
  // Встановлюємо кореневий редьюсер
  reducer: rootReducer,
  // Додаємо проміжне ПЗ, яке зберігає стан у window
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(windowStateMiddleware),
  // Завантажуємо попередній стан із window
  preloadedState: loadFromWindow(),
});

// Експортуємо сховище
export default store;

// Визначаємо тип для диспетчера
export type AppDispatch = typeof store.dispatch;

Винесемо константи в модуль shared

export const USER_ROLE = () => {
  return window.host.common.user.role;
};

Для синхронізації зміни ролі користувача між усіма мікрофронтендами ми використовуємо event bus . У модулі shared реалізуємо обробники для відправки та прийому подій.

// Імпортуємо канали подій та типи ролей
import { Channels } from '@/events/const/channels';
import { EnumRole } from '@/types';

// Оголошуємо змінну для обробника подій
let eventHandler: ((event: Event) => void) | null = null;

// Функція обробки зміни ролі користувача
export const onChangeUserRole = (cb: (role: EnumRole) => void): void => {
  // Створюємо обробник подій
  eventHandler = (event: Event) => {
    // Наводимо подію до типу CustomEvent
    const customEvent = event as CustomEvent<{ role: EnumRole }>;
    // Якщо у події є деталі, виводимо їх у консоль і викликаємо callback-функцію
    if (customEvent.detail) {
      console.log(`On ${Channels.changeUserRole} - ${customEvent.detail.role}`);
      cb(customEvent.detail.role);
    }
  };

  // Додаємо обробник подій на глобальний об'єкт window
  window.addEventListener(Channels.changeUserRole, eventHandler);
};

// Функція для припинення прослуховування зміни ролі користувача
export const stopListeningToUserRoleChange = (): void => {
  // Якщо обробник подій існує, видаляємо його та обнулюємо змінну
  if (eventHandler) {
    window.removeEventListener(Channels.changeUserRole, eventHandler);
    eventHandler = null;
  }
};

// Функція для надсилання події про зміну ролі користувача
export const emitChangeUserRole = (newRole: EnumRole): void => {
  // Виводимо в консоль інформацію про подію
  console.log(`Emit ${Channels.changeUserRole} - ${newRole}`);
  // Створюємо нову подію
  const event = new CustomEvent(Channels.changeUserRole, {
    detail: { role: newRole },
  });
  // Відправляємо подію
  window.dispatchEvent(event);
};

Для реалізації сторінки редагування банківської картки, на якій враховано ролі користувачів, ми розпочнемо з встановлення механізму передплати на подію оновлення ролі. Це дозволить сторінці реагувати на зміни та адаптувати доступні функції редагування відповідно до поточної ролі користувача.

import React, { useEffect, useState } from 'react';
import { Button, Card, List, Modal, notification } from 'antd';
import { useDispatch, useSelector } from 'react-redux';
import { getCardDetails } from '@modules/cards/store/features/cards/slice';
import { AppDispatch } from '@modules/cards/store/store';
import { userCardsDetailsSelector } from '@modules/cards/store/features/cards/selectors';
import { Transaction } from '@modules/cards/types';
import { events, variables, types } from 'shared';
const { EnumRole } = types;
const { USER_ROLE } = variables;
const { onChangeUserRole, stopListeningToUserRoleChange } = events;

export const CardDetail = () => {
  // Використання Redux для диспетчеризації та отримання стану
  const dispatch: AppDispatch = useDispatch();
  const cardDetails = useSelector(userCardsDetailsSelector);

  // Локальний стан для ролі користувача та видимості модального вікна
  const [role, setRole] = useState(USER_ROLE);
  const [isModalVisible, setIsModalVisible] = useState(false);

  // Ефект для завантаження деталей картки при монтуванні компонента
  useEffect(() => {
    const load = async () => {
      await dispatch(getCardDetails('1'));
    };
    load();
  }, []);

  // Функції для керування модальним вікном
  const showEditModal = () => {
    setIsModalVisible(true);
  };

  const handleEdit = () => {
    setIsModalVisible(false);
  };

  const handleDelete = () => {
    // Відображення повідомлення про видалення
    notification.open({
      message: 'Card delete',
      description: 'Card delete success.',
      onClick: () => {
        console.log('Notification Clicked!');
      },
    });
  };

  // Ефект для передплати та відписки від подій зміни ролі користувача
  useEffect(() => {
    onChangeUserRole(setRole);
    return stopListeningToUserRoleChange;
  }, []);

  // Умовний рендеринг, якщо деталі картки не завантажені
  if (!cardDetails) {
    return <div>loading...</div>;
  }

  // Функція визначення дій з урахуванням ролі користувача
  const getActions = () => {
    switch (role) {
      case EnumRole.admin:
        return [
          <Button key="edit" type="primary" onClick={showEditModal}>
            Edit
          </Button>,
          <Button key="delete" type="dashed" onClick={handleDelete}>
            Delete
          </Button>,
        ];
      case EnumRole.manager:
        return [
          <Button key="edit" type="primary" onClick={showEditModal}>
            Edit
          </Button>,
        ];
      default:
        return [];
    }
  };

  // Рендеринг компонента Card з деталями картки та діями
  return (
    <>
      <Card
        actions={getActions()}
        title={`Card Details - ${cardDetails.cardHolderName} `}
      >
        {/* Відображення різних атрибутів картки */}
        <p>PAN: {cardDetails.pan}</p>
        <p>Expiry: {cardDetails.expiry}</p>
        <p>Card Type: {cardDetails.cardType}</p>
        <p>Issuing Bank: {cardDetails.issuingBank}</p>
        <p>Credit Limit: {cardDetails.creditLimit}</p>
        <p>Available Balance: {cardDetails.availableBalance}</p>
        {/* Список останніх транзакцій */}
        <List
          header={<div>Recent Transactions</div>}
          bordered
          dataSource={cardDetails.recentTransactions}
          renderItem={(item: Transaction) => (
            <List.Item>
              {item.date} - {item.amount} {item.currency} - {item.description}
            </List.Item>
          )}
        />
        <p>
          <b>*For demonstration events from the host, change the user role.</b>
        </p>
      </Card>
      {/* Модальне вікно для редагування */}
      <Modal
        title="Edit transactions"
        open={isModalVisible}
        onOk={handleEdit}
        onCancel={() => setIsModalVisible(false)}
      >
        <p>Form edit card</p>
      </Modal>
    </>
  );

Для налаштування розгортання програми через GitHub Actions створимо файл конфігурації .yml , який визначає робочий процес CI/CD. Ось приклад простого конфігу:

name: Build and Deploy Cards Project

# Цей workflow запускається при подіях push або pull request 
# але тільки для змін в директорії 'packages/cards'.
on:
  push:
    paths:
      - 'packages/cards/**'
  pull_request:
    paths:
      - 'packages/cards/**'

# Визначення задач (jobs) для виконання
jobs:
  # Перше завдання: Встановлення залежностей
  install-dependencies:
    runs-on: ubuntu-latest  # Завдання виконується на останній версії Ubuntu

    steps:
      - uses: actions/checkout@v2  # Выполняет checkout кода репозитория

      - name: Set up Node.js  # Встановлює Node.js версії 16
        uses: actions/setup-node@v2
        with:
          node-version: '16'

      - name: Cache Node modules  # Кешування Node модулів для прискорення збирання
        uses: actions/cache@v2
        with:
          path: '**/node_modules'
          key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}

      - name: Install Dependencies  # Встановлення залежностей проекту через Yarn
        run: yarn install

  # Друге завдання: Тестування та складання
  test-and-build:
    needs: install-dependencies  # Це завдання вимагає завершення завдання install-dependencies
    runs-on: ubuntu-latest  # Запускається на останній версії Ubuntu

    steps:
      - uses: actions/checkout@v2  # Виконує checkout коду репозиторію

      - name: Use Node.js  # використовує Node.js версії 16
        uses: actions/setup-node@v2
        with:
          node-version: '16'

      - name: Cache Node modules  # Кешування Node модулів
        uses: actions/cache@v2
        with:
          path: '**/node_modules'
          key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}

      - name: Build Shared Modules  # Складання загальних модулів
        run: yarn workspace shared build

      - name: Test and Build Cards  # Тестування та складання workspace Cards
        run: |
          yarn workspace cards test
          yarn workspace cards build

      - name: Archive Build Artifacts  # Архівація артефактів складання для розгортання
        uses: actions/upload-artifact@v2
        with:
          name: shared-artifacts
          path: packages/cards/dist

  # Третє завдання: Розгортання Cards
  deploy-cards:
    needs: test-and-build  # Це завдання вимагає завершення завдання test-and-build
    runs-on: ubuntu-latest  # Запускається на останній версії Ubuntu

    steps:
      - uses: actions/checkout@v2  # Виконує checkout коду репозиторію

      - name: Use Node.js  # Використовує Node.js версії 16
        uses: actions/setup-node@v2
        with:
          node-version: '16'

      - name: Cache Node modules  # Кешування Node модулів
        uses: actions/cache@v2
        with:
          path: '**/node_modules'
          key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}

      - name: Download Build Artifacts  # Скачування артефактів збирання з попереднього завдання
        uses: actions/download-artifact@v2
        with:
          name: shared-artifacts
          path: ./cards

      - name: Deploy to Server  # Розгортання артефактів на сервері за допомогою SCP
        uses: appleboy/scp-action@master
        with:
		  host: ${{ secrets.HOST }}
          username: root
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          source: 'cards/*'
          target: '/usr/share/nginx/html/microfrontend/apps'

Тут ми можемо додати такі функції, як версіонування та A/B тестування, керуючи ними через Nginx.

У результаті, у нас виходить система, де кожна команда, яка працює над різними модулями, має свою програму в структурі мікрофронтенду.

Цей підхід прискорює процес складання, оскільки більше не потрібно очікувати на перевірку всього додатка. Код можна оновлювати частинами та проводити регресивне тестування для кожного окремого компонента.

Також значно зменшується проблема із конфліктами злиття (мердж-конфліктами), оскільки команди працюють над різними частинами проекту незалежно одна від одної. Це підвищує ефективність роботи команд та спрощує процес розробки загалом.

Джерело

Чому typeof null === «object» у сучасному прочитанні

Завдання унарного оператора  typeof  рядкове подання типу операнда. Інакше кажучи,  typeof 1 поверне рядок  "number", а  typeof "" поверне  "string". Усі можливі значення типів, що повертаються оператором типувикладені в специфікації  ECMA-262 – 13.5.1 . За задумом, що повертається оператором, значення має відповідати прийнятим у тій же специфікації типів даних. Однак, при детальному розгляді, можна помітити, що  typeof null повинен повертати  "object", незважаючи на те, що  Null це цілком собі самостійний тип, він описаний в розділі  6.1.2 . Причина тому – звичайний людський фактор, або просто невинна помилка в коді. Як ця помилка могла статися, спробуємо розібратися у цій статті.

Mocha

Почати варто, мабуть, з самого початку JavaScript, і саме прототипної мови Mocha, створеної  Бренданом Айком  в 1995-му році всього за 10 днів, який пізніше був перейменований в LiveScript, а ще пізніше, в 1996-му, став відомим нам сьогодні JavaScript.

На жаль, вихідний код Mocha не був опублікований і ми не знаємо, як саме він виглядав у далекому 1995-му, проте, у коментарях до  статті  в блозі доктора Алекса Раушмайєра, Айк писав, що використовував техніку “Discriminated Union”, вона ж – “Tagged Union”, де він використовував  struct із двома полями.

Структура могла б виглядати, наприклад, так:

enum JSType {
  OBJECT,
  FUNCTION,
  NUMBER,
  STRING,
  BOOLEAN,
};

union JSValue {
  std::string value;
  // ... other details
};

struct TypeOf {
  JSType type;
  JSValue values;
};

У самій статті, Алекс Раушмайер наводить приклад коду движка SpiderMonkey (використовується в Mozilla Firefox) від 1996-го року

JS_PUBLIC_API(JSType)
JS_TypeOfValue(JSContext *cx, jsval v)
{
    JSType type = JSTYPE_VOID;
    JSObject *obj;
    JSObjectOps *ops;
    JSClass *clasp;

    CHECK_REQUEST(cx);
    if (JSVAL_IS_VOID(v)) {
        type = JSTYPE_VOID;
    } else if (JSVAL_IS_OBJECT(v)) {
        obj = JSVAL_TO_OBJECT(v);
        if (obj &&
            (ops = obj->map->ops,
             ops == &js_ObjectOps
             ? (clasp = OBJ_GET_CLASS(cx, obj),
                clasp->call || clasp == &js_FunctionClass)
             : ops->call != 0)) {
            type = JSTYPE_FUNCTION;
        } else {
            type = JSTYPE_OBJECT;
        }
    } else if (JSVAL_IS_NUMBER(v)) {
        type = JSTYPE_NUMBER;
    } else if (JSVAL_IS_STRING(v)) {
        type = JSTYPE_STRING;
    } else if (JSVAL_IS_BOOLEAN(v)) {
        type = JSTYPE_BOOLEAN;
    }
    return type;
}

Алгоритм хоч і відрізняється від оригінального коду Mocha, добре ілюструє суть помилки. У ньому просто немає перевірки на тип  Null. Натомість у разі  val === "null"алгоритм потрапляє у гілку  else if (JSVAL_IS_OBJECT(v)) і повертає JSTYPE_OBJECT

Чому саме “object”?

Справа в тому, що значення змінної в ранніх версіях мови було 32-бітним числом без знака ( uint_32), Де перші три біти, якраз, і вказують на тип змінної. За такої схеми були прийняті такі значення цих перших трьох бітів:

  • 000:  object  – змінна є посиланням на об’єкт
  • 001:  int  – змінна містить 31-бітове ціле число
  • 010:  double  – змінна є посиланням на число з точкою, що плаває
  • 100:  string  – Змінна є посиланням на послідовність символів
  • 110:  boolean  – Змінна є булевим значенням

У свою чергу  Null був покажчиком на машинний  nullptr, який, у свою чергу, виглядає, як 0x00000000

Тому перевірка  JSVAL_IS_OBJECT(0x00000000) повертає  true, адже перші три біти рівні  000, що відповідає типу  object.

Спроби виправити баг

Пізніше ця проблема була визнана багом. У 2006-му році Ейх запропонував скасувати оператор  typeof і замінити на функцію type(), яка б враховувала, в тому числі і  Null ( архівна копія пропозиції ). Функція може бути вбудованою або бути частиною опціонального пакета  reflection. Однак, у будь-якому випадку, такий фікс не був би сумісний з попередніми версіями мови, що породило б безліч проблем з вже існуючим JavaScript кодом, написаним розробниками по всьому світу. Потрібно було б створювати механізм перевірки версій коду та/або опції мови, що настроюються, що не виглядало реалістичним.

У підсумку пропозиція не була прийнята, а оператор  typeof у специфікації  ECMA-262  так і залишився у своєму оригінальному вигляді.

Ще пізніше, 2017-го було висунуто ще одну пропозицію  Builtin.is and Builtin.typeOf . Основна мотивація в тому, що оператор  instanceof не гарантує правильну перевірку типів змінних із різних реалмів. Пропозиція була пов’язана безпосередньо з  Null, проте, його текст передбачав виправлення і цього бага у вигляді створення нової функції  Builtin.typeOf(). Пропозиція так само була прийнята, т.к. окремий випадок, продемонстрований у мотиваційній частині, хоч і не дуже елегантно, але може бути вирішений існуючими методами.

Сучасний Null

Як я писав вище, баг з’явився в 1995 році в прототипній мові Mocha, ще до появи самого JavaScript і до 2006 року Брендан Ейх не залишав надій виправити його. Проте з 2017-го ні розробники, ні ECMA більше не намагалися цього зробити. З тих пір мова JavaScript стала набагато складнішою, як і її реалізації в популярних двигунах.

SpiderMonkey

Від коду SpiderMonkey, який публікував Алекс Раушмайєр у своєму блозі 2013 року, не залишилося і сліду. Тепер двигун (на момент написання статті, версія FF 121) бере значення типувід заздалегідь визначеного тегу змінної

JSType js::TypeOfValue(const Value& v) {
  switch (v.type()) {
    case ValueType::Double:
    case ValueType::Int32:
      return JSTYPE_NUMBER;
    case ValueType::String:
      return JSTYPE_STRING;
    case ValueType::Null:
      return JSTYPE_OBJECT;
    case ValueType::Undefined:
      return JSTYPE_UNDEFINED;
    case ValueType::Object:
      return TypeOfObject(&v.toObject());
#ifdef ENABLE_RECORD_TUPLE
    case ValueType::ExtendedPrimitive:
      return TypeOfExtendedPrimitive(&v.toExtendedPrimitive());
#endif
    case ValueType::Boolean:
      return JSTYPE_BOOLEAN;
    case ValueType::BigInt:
      return JSTYPE_BIGINT;
    case ValueType::Symbol:
      return JSTYPE_SYMBOL;
    case ValueType::Magic:
    case ValueType::PrivateGCThing:
      break;
  }
  
  ReportBadValueTypeAndCrash(v);
}

Тепер двигун точно знає, якого типу змінна передана оператор, т.к. після декларування, об’єкт змінної містить біт, що вказує на її тип. Для  Null оператора повертає значення  JSTYPE_OBJECTявним чином, як того вимагає специфікація

enum JSValueType : uint8_t {
  JSVAL_TYPE_DOUBLE = 0x00,
  JSVAL_TYPE_INT32 = 0x01,
  JSVAL_TYPE_BOOLEAN = 0x02,
  JSVAL_TYPE_UNDEFINED = 0x03,
  JSVAL_TYPE_NULL = 0x04,
  JSVAL_TYPE_MAGIC = 0x05,
  JSVAL_TYPE_STRING = 0x06,
  JSVAL_TYPE_SYMBOL = 0x07,
  JSVAL_TYPE_PRIVATE_GCTHING = 0x08,
  JSVAL_TYPE_BIGINT = 0x09,
#ifdef ENABLE_RECORD_TUPLE
  JSVAL_TYPE_EXTENDED_PRIMITIVE = 0x0b,
#endif
  JSVAL_TYPE_OBJECT = 0x0c,

  // This type never appears in a Value; it's only an out-of-band value.
  JSVAL_TYPE_UNKNOWN = 0x20
};

V8

Подібний підхід застосовується і в двигуні V8 (на момент написання статті, версія  12.2.165 ). Тут  Null є так званим типом  Oddball, тобто. об’єкт типу  Null инциализируется ще до виконання JS-коду, проте наступні посилання значення  Null ведуть цей єдиний об’єкт.

Ініціалізатор класу  Oddball  виглядає так

void Oddball::Initialize(Isolate* isolate, Handle<Oddball> oddball,
                         const char* to_string, Handle<Object> to_number,
                         const char* type_of, uint8_t kind) {
  STATIC_ASSERT_FIELD_OFFSETS_EQUAL(HeapNumber::kValueOffset,
                                    offsetof(Oddball, to_number_raw_));

  Handle<String> internalized_to_string =
      isolate->factory()->InternalizeUtf8String(to_string);
  Handle<String> internalized_type_of =
      isolate->factory()->InternalizeUtf8String(type_of);
  if (IsHeapNumber(*to_number)) {
    oddball->set_to_number_raw_as_bits(
        Handle<HeapNumber>::cast(to_number)->value_as_bits(kRelaxedLoad));
  } else {
    oddball->set_to_number_raw(Object::Number(*to_number));
  }
  oddball->set_to_number(*to_number);
  oddball->set_to_string(*internalized_to_string);
  oddball->set_type_of(*internalized_type_of);
  oddball->set_kind(kind);
}

Крім зони  Isolate , посилання на саме значення змінної та enum  типу, він так само, явно приймає значення  toString,  toNumber і  typeof, які далі буде зберігати всередині класу. Що дозволяє при ініціалізації глобальної купи (Heap) визначити потрібні значення цих параметрів  Oddball

// Initialize the null_value.
Oddball::Initialize(isolate(), factory->null_value(), "null",
                    handle(Smi::zero(), isolate()), "object", Oddball::kNull);

Тут бачимо, що з ініціалізації Null, в клас передаються: toString="null" ,  toNumber=0,  typeof="object".

Сам оператор typeof  просто бере значення через геттер класу type_of()

// static
Handle<String> Object::TypeOf(Isolate* isolate, Handle<Object> object) {
  if (IsNumber(*object)) return isolate->factory()->number_string();
  if (IsOddball(*object))
    return handle(Oddball::cast(*object)->type_of(), isolate); // <- typeof null === "object"
  if (IsUndetectable(*object)) {
    return isolate->factory()->undefined_string();
  }
  if (IsString(*object)) return isolate->factory()->string_string();
  if (IsSymbol(*object)) return isolate->factory()->symbol_string();
  if (IsBigInt(*object)) return isolate->factory()->bigint_string();
  if (IsCallable(*object)) return isolate->factory()->function_string();
  return isolate->factory()->object_string();
}

Джерело

As const у Typescript

TypeScript

У розробці часто виникають ситуації, коли точність типів та небажання допускати неясності в коді стають першорядними завданнями. У таких випадках розробникам доводиться шукати інструменти, що надають максимальну ясність та строгість у визначенні даних. Один із таких інструментів — ключове слово as const. У цій статті ми розглянемо, як as constможе підвищити рівень суворості та передбачуваності, а також розглянемо практичні приклади його використання для створення незмінних та точних типів.

Докладніше про “as const”

Коли ви використовуєте as const змінну або значення, TypeScript уточнює тип цієї змінної до її точного значення або комбінації літеральних типів. Це часто використовується для створення незмінних значень та гарантування того, що TypeScript розглядатиме значення як конкретні літерали, а не розширюватиме типи.

Приклад :

Уявіть, що нам потрібно написати функцію для відкриття вхідних дверей до будинку. Пишемо функцію, яка приймає параметром ключ, та проводить з ним будь-які операції.

const wallet = {
  key: "open_door_pls"
}

const openDoor = (key: "open_door_pls") => {
  //...
}

Але коли ми намагаємося використовувати функцію openDoor з ключем з нашого гаманця, чомусь відбувається таке:

const wallet = {
  key: "open_door_pls"
}

const openDoor = (key: "open_door_pls") => {
  //...
}

openDoor(wallet.key) // ERROR: Argument of type 'string' is not assignable to parameter of type '"open_door_pls"'

Чому ж ми потрапили до такої ситуації?

Вся справа в тому, що wallet.key у нас ніяк не прив’язаний до значення "open_door_pls”, і по суті є просто елементом з типом string, значення якого можна легко змінити на інше:

const wallet = {
  key: "open_door_pls"
}

wallet.key = "cucumber" // код отрабатывает без ошибок

Щоб уникнути такої поведінки, і зробити всі елементи wallet повноцінними, незмінними ( readonly) значеннями, ми можемо скористатися конструкцією as const:

const wallet = {
  key: "open_door_pls"
} as const

wallet.key = "cucumber" // ERROR: Cannot assign to 'key' because it is a read-only property.

Елементи wallet прив’язалися до своїх значень і тепер мають прапор readonly.

Що зрештою вирішило нашу проблему з параметром функції openDoor:

const wallet = {
  key: "open_door_pls"
} as const;

const openDoor = (key: "open_door_pls") => {
  //...
};

openDoor(wallet.key) // код отрабатывает без ошибок

Чому не Object.freeze()?

Проблема Object.freeze() полягає в тому, що після заморожування об’єкта readonly присвоюється лише елементам на верхньому рівні вкладеності.

const car = Object.freeze({
  name: "Porshe Cayenne",
  equipment:{
    engine: "MDC.AB"
  }
});

car.equipment.engine = "F8CV";

При використанні as constкомпілятора на будь-якому рівні вкладеності вкаже, що програміст намагається змінити константу.

const car = {
  name: "Porshe Cayenne",
  equipment:{
    engine: "MDC.AB"
  }
} as const;

car.equipment.engine = "F8CV"; // ERROR: Cannot assign to 'engine' because it is a read-only property.

Заміна enum-ам?

as constчудово підходить як альтернатива enum.

Про мінуси enum можна докладно почитати у цій статті.

Приклад переходу коду enumз as const:

Код, з використанням enum:

enum Wallet {
  KEY = "open_door_pls"
};

const openDoor = (key: "open_door_pls") => {
  //...
};

openDoor(Wallet.KEY)

Код, переписаний на as const:

const wallet = {
  key: "open_door_pls"
} as const;

const openDoor = (key: "open_door_pls") =>{
  //...
};

openDoor(wallet.key);

Ще одна дійсно крута особливість as const полягає в тому, що використання as const дозволяє бути дуже гнучким у поводженні з ключовим об’єктом:

const friendsDict = {
  Alfred: "101 Pine Road, Toronto, ON M5A 1A1, Canada",
  Liam: "777 Sycamore Lane, Tokyo, 100-0001, Japan",
  Mia: "666 Willow Street, Paris, 75001, France",
} as const;

type FriendName = keyof typeof friendsDict; // "Alfred" | "Liam" | "Mia"
type FriendAddress = (typeof friendsDict)[keyof typeof friendsDict];
//"101 Pine Road, Toronto, ON M5A 1A1, Canada" | "777 Sycamore Lane, Tokyo, 100-0001, Japan" | "666 Willow Street, Paris, 75001, France"

Резюмуючи

Ми розглянули конструкцію as const у TypeScript та її роль у створенні більш строгих та передбачуваних типів даних. Сподіваюся, що ця стаття допомогла вам краще зрозуміти, як використання as const може підвищити рівень безпеки та ясності вашого коду.

Таким чином, впровадження as const у ваш код може бути ключем до полегшення його підтримки у майбутньому та зменшення ймовірності помилок. Користуйтеся цим інструментом з розумом, і ваш код стане чистішим, надійнішим та легко підтримуваним!

Джерело

Найкращі пошукові пакети для JavaScript

JavaScript

Попит на функції пошуку зростає, і багато розробників намагаються впровадити їх у свої програми. Однак створення таких додатків з нуля – складне та трудомістке завдання. На щастя, існує безліч бібліотек з відкритим вихідним кодом, які дозволяють звільнити розробників від цього тягаря.

У цьому посібнику читач знайде перелік найкращих пошукових пакетів для JavaScript. У статті буде проведено огляд та порівняння пошукових пакетів з урахуванням швидкості, простоти використання, типу пропонованого пошуку, бази даних та інших характеристик. Крім того, читачі зрозуміють, що таке функціональність пошуку та навіщо вона потрібна.

  • Функціональність пошуку
  • Як застосувати пошукову функціональність JavaScript
  • Пошукові пакети для JavaScript

Функціональність пошуку

Один з ключових способів покращити дизайн сайту, щоб допомогти користувачам знайти потрібну інформацію, – це зробити програму доступною для пошуку за допомогою функції пошуку. Користувачі можуть швидко знаходити контент, задаючи пошук за певними фразами на сайті або у додатку, не вникаючи та не вивчаючи всю структуру. Нижче наведено причини, з яких це доцільно:

  • Поліпшення залучення користувачів та зниження відсотка відмов
  • Економія часу користувачів на пошук товарів
  • Підвищення темпи зростання з допомогою продажу більшої кількості товарів та послуг.

Існує три різні типи пошуку:

Автозаповнюваний пошук

Функція пошукової системи, яка відображає ключові фрази та інші пропозиції в режимі реального часу. Її також називають передиктивним пошуком чи автоповідомленням. Пропозиції ґрунтуються на ключовому слові, яке користувач вводить у поле пошуку.

Пошукова система пропонує кілька прогнозів на основі введеного запиту та зіставляє його з даними у пошуковому індексі. Наприклад, якщо ви введете слово “електроніка” на веб-сторінці, присвяченій електроніці, пошукова система запропонує “електроніку” на цій сторінці.

Повнотекстовий пошук

Це функція пошукової системи, що дозволяє ефективно знаходити дані, коли користувач розуміє потрібну інформацію лише частково. Система шукає текст у великих текстових даних, сховище яких перебуває у електронному вигляді. У відповідь система видає результати, які містять деякі або всі слова із запиту, а не точні збіги.

Наприклад, якщо в полі пошуку ввести слово “пшениця”, результати будуть містити не тільки слово “пшениця”, але й елементи, пов’язані з ним, наприклад “хліб” та “паста”.

Нечіткий пошук

Це функція пошукової системи для пошуку рядків, що майже збігаються із заданим рядком. Її також називають нечітким пошуком. Нечіткий пошук повертає результати, що базуються на приблизному збігу пошукового запиту. Крім того, він діє як коректор орфографії та робить пошук гнучким. Він може виконувати вставку, видалення та заміну запитів.

Наприклад, при пошуку “strng” буде запропоновано “string”, а при пошуку “husttle” – “hustle”.

Як застосувати пошукову функціональність JavaScript

Для пошуку інформації на сайті користувачам потрібна вкладка пошуку, яка забезпечує доступ до функцій пошуку. Пошукова функціональність JavaScript дозволяє користувачам сайту знаходити вміст за певними фразами без необхідності розумітися на структурі сайту.

Але реалізація пошукової функціональності з нуля може зайняти багато часу та роботи. Веб-розробка стала простішою, оскільки розробникам не потрібно щоразу вводити складний код, щоб додати функцію пошуку. Деякі компанії пропонують безкоштовний доступ до своїх пошукових програм.

Пошукові програми дозволяють заощадити час та ресурси, забезпечуючи кращий контроль для підприємства.

Пошукові пакети для JavaScript

Нижче наведено пошукові бібліотеки JavaScript для створення продуктивного та миттєвого пошуку. У цьому розділі ми розглянемо пошукові пакети JavaScript та порівняємо бібліотеки за їхніми можливостями. Без зайвих слів, почнемо.

Fuse.js – це бібліотека, побудована на JavaScript, яка використовується для додавання полегшеного пошуку на стороні клієнта у браузері користувача. Функції пошуку корисні на веб-сайтах та програмах, щоб користувачі могли ефективно знаходити те, що їм потрібно. Fuse.js надає можливості нечіткого пошуку для програм та веб-сайтів.

Fuse.js можна використовувати і на бекенді, оскільки він не має залежностей від DOM. З іншого боку, Fuse.js може бути повільним під час роботи з величезними масивами даних. Визначаючи, який обсяг даних надто великий для Fuse.js, майте на увазі, що вам доведеться завантажувати весь набір даних на стороні клієнта, оскільки Fuse.js необхідний доступ до всього набору даних. Fuse.js не дуже добре працює з великими наборами даних.

Як застосувати Fuse.js

Щоб використовувати Fuse.js, вам не потрібно створювати окремий бекенд лише для пошуку. При створенні цієї бібліотеки основними міркуваннями були простота та продуктивність.

Fuse.js – це швидка та проста у використанні бібліотека, і реалізація функції пошуку на JavaScript стала легкою. При реалізації пошукової функціональності Fuse.js потребує конфігурацій, які дозволяють вносити зміни та створювати потужні рішення.

Щоб встановити Fuse.js , вам потрібно спочатку ініціалізувати проект Node.js. Потім додайте його у свій проект за допомогою npm:

$ npm install --save fuse.js

Якщо ви шукаєте потужний, легкий пошуковий пакет Javascript з функцією нечіткого пошуку, fuse.js– найкращий варіант.

Apache Solr – це пошукова система з відкритим вихідним кодом, яка використовується для створення пошукових програм. Solr побудований на базі пошукової бібліотеки Apache Lucene . Він готовий до роботи на підприємствах, має високу масштабованість і швидкість. Solr дозволяє створювати складні та високопродуктивні додатки.

Apache Solr збирає структуровані, напівструктуровані та неструктуровані дані з різних джерел у сховищі, індексує їх, а потім робить їх доступними для швидкого пошуку. Бібліотека може працювати з великими масивами даних. Вона підтримує розраховану на багато користувачів архітектуру, що дозволяє масштабувати, розподіляти і підтримувати індекси для великих додатків.

Балансування навантаження на запити, автоматизація завдань, централізоване налаштування, розподілене миттєве індексування, готова до масштабування інфраструктура – ось лише деякі з можливостей Solr.

Як застосувати Apache Solr

Apache Solr дає змогу розширити можливості користувачів. Це стабільна, надійна та стійка до відмови пошукова платформа. Розробники бекенда можуть скористатися такими функціями, як об’єднання, кластеризація, можливість імпорту документів у різних форматах та багато інших.

Solr – це платформа повнотекстового пошуку з REST-подібним API. Розробники можуть легко передавати документи через JSON, XML і HTTP. Apache Solr простий у використанні. Його легко встановити та налаштувати. Він орієнтований працювати з текстом, оптимізований для пошуку, а результати пошуку сортуються по релевантності.

Щоб встановити та розпочати роботу з apache solr. Перейдіть на сайт Solr, щоб отримати інструкції зі встановлення .

Lunr.js – це пакет повнотекстового пошуку для JavaScript. Він дозволяє здійснювати всебічний пошук набору даних. Він невеликий, потужний і, що найголовніше, простий у використанні.

Повнотекстовий пошук – це просунута техніка пошуку у базі даних. Ця техніка зазвичай використовується для швидкого пошуку документів або записів за ключовим словом. Вона також дозволяє ранжувати документи щодо релевантності за допомогою системи оцінок.

Як застосувати Lunr.js

Lunr.js працює на стороні клієнта за допомогою веб-застосунків, створених на JavaScript. Він здійснює пошук даних у індексі, створеному за клієнта. Це дозволяє уникнути складних зворотних мережевих викликів між клієнтом та сервером.

Lunr.js – це більш компактна альтернативна бібліотека Apache Solr. Вона має повнотекстову підтримку 14 мов та пропонує нечітке зіставлення термінів. Lunr.js – одна з найкращих пошукових бібліотек для створення додатків на JavaScript. Реалізувати функціональність повнотекстового пошуку шляхом жорсткого кодування дуже складно, особливо у браузері. На щастя, lunr.jsце потужний і простий у використанні інструмент для додавання пошукової функціональності.

Lunr.js – гарний вибір для миттєвого пошуку. Це пов’язано з тим, що він підтримує потужну систему ранжирування та систему плагінів для обробки ключових слів під час індексації та запиту.

Щоб розпочати роботу з lunr.js , спочатку ініціалізуйте проект Node.js. Потім установіть пакет lunr.jsза допомогою npm:

$ npm install lunr

ElasticLunr.js – це легкий пакет повнотекстового пошуку із вбудованим JavaScript для пошуку у браузері та офлайн. Він був створений на основі Lunr.js, але є більш гнучким. Він пропонує додаткові можливості налаштування та швидшу обробку запитів.

Як застосувати ElasticLunr.js

Пошуковий пакет не вимагає розгортання та пропонує функції автономного пошуку. Він легко працює з програмами, створеними за допомогою гібридних фреймворків JavaScript та Cordova. Швидкість – важлива характеристика, elasticlunr.jsдуже швидка в порівнянні з тим lunr.js, що полегшує пошук.

Щоб розпочати роботу з ElasticLunr.js, перейдіть на цей сайт та отримайте інструкції з встановлення.

InstantSearch.js – це бібліотека пошуку з відкритим кодом для Vanilla JavaScript. Вона дозволяє швидко створити пошуковий інтерфейс, використовуючи пошуковий API Algolia у вашому зовнішньому додатку. Пошуковий пакет створює продуктивні та миттєві пошукові можливості. Algolia – це платформа пошуку як послуги, яка дозволяє вам легко впровадити пошук у реальному часі у вашу програму за допомогою настроюваних, попередньо створених віджетів та інструментів для створення блоків.

Як застосувати InstantSearch.js

InstantSearch прагне максимально спростити розробку відмінних пошукових програм, пропонуючи повноцінне пошукове середовище. InstantSearch пропонує зовнішні віджети, які ви можете зібрати в унікальний пошуковий досвід, щоб задовольнити значну частину цієї величезної мети. Також використовуйте InstantSearch, якщо вам потрібно налаштувати компоненти на власний розсуд.

Щоб почати роботу з instantsearch.js , вам необхідно мати встановлений Node.js та yarnабо npm. Вам знадобляться базові знання JavaScript та обліковий запис в Algolia.

$ npm install instantsearch.js algoliasearch

FlexSearch – це бібліотека повнотекстового та незалежного пошуку на JavaScript для веб-браузерів та Node.js. Це одна з найшвидших пошукових бібліотек для розробників JavaScript завдяки алгоритму скорингу, що називається контекстним пошуком .

Як застосувати FlexSearch

Якщо говорити про швидкість, то FlexSearch перевершує всі інші пошукові бібліотеки. Крім того, вона надає гнучкі можливості пошуку, такі як частковий збіг, фонетичні перетворення та пошук по кількох полях. FlexSearch пропонує безліч варіантів налаштування для підвищення ефективності використання пам’яті та швидкості роботи.

Щоб почати роботу з flexsearch, встановіть бібліотеку пошуку за допомогою npm:

$ npm install flexsearch

List.js – це проста у використанні, потужна та надшвидка бібліотека пошуку для Vanilla JavaScript. Це ідеальна бібліотека для додавання пошуку, сортування, фільтрів та гнучкості до таблиць, списків та HTML-елементів. List.js створена так, щоб бути невидимою та працювати з існуючим HTML.

Як застосувати List.js

List.js простий і непомітний, немає залежностей. Він малий, дуже швидкий, легко застосовується до існуючих HTML-елементів та обробляє тисячі елементів. Бібліотека пошуку працює зі списками, таблицями та іншими HTML-елементами, наприклад, <table><ul><div>і т.д.

Щоб розпочати роботу з list.js , установіть бібліотеку пошуку за допомогою npm:

$ npm install list.js

JS Search – це потужна бібліотека пошуку на стороні клієнта для об’єктів JavaScript та JSON. Вона є полегшеною реалізацією Lunr.js і має широкі можливості налаштування для підвищення продуктивності.

Як застосувати JS Search

JS Search – це швидкий та простий у використанні пошуковий пакет. Щоб почати роботу з js-search , ви можете встановити його за допомогою npm:

$ npm install js-search

MiniSearch – це крихітна, легка, повнотекстова та потужна бібліотека пошуку для JavaScript. Вона призначена для роботи як у Node.js, так і у веб-браузері.

Як застосувати MiniSearch

MiniSearch має функції повнотекстового пошуку, серед яких (нечіткий пошук, пошук за префіксами, ранжування тощо). Бібліотека пошуку також може працювати в автономному режимі та швидко обробляти запити без затримок мережі.

MiniSearch має ефективний індекс пам’яті, дає точний збіг, має механізм автопідбору пошукових запитів та не має зовнішніх залежностей.

Щоб розпочати роботу з minisearch , встановіть його за допомогою npm:

$ npm install --save minisearch

Висновок

У статті було розглянуто найкращі пошукові пакети на JavaScript, які має знати кожен розробник. Створити пошукову функціональність з нуля не так просто. Використання будь-якого з пакетів пошуку заощадить вам значну кількість часу та роботи. Це керівництво допомогло вам дізнатися про деякі поширені пакети пошуку в JavaScript та їх можливості. Бібліотеки пошуку покращать роботу сайту або програми, оскільки користувачі зможуть легко отримати потрібну їм інформацію.

Джерело

Що таке регенеративні фінанси (ReFi)?

ReFi

Що таке ReFi?

Регенеративні фінанси (Regenerative finance, ReFi) – концепція та напрямок децентралізованих фінансів (DeFi), в рамках якого створюються комплексні економічні моделі. Останні, крім матеріальної винагороди, припускають сприятливий вплив на довкілля та вирішення суспільних проблем.

Головна мета ReFi полягає в підтримці зростання регенеративної економіки, наголошуючи на екологічному аспекті, соціальному благополуччі та ресурсній стійкості, також сприяючи великомасштабному співробітництву з таких глобальних питань, як зміна клімату та втрата біорізноманіття.

Основні принципи та концепція регенеративних фінансів запропонована у 2015 році Джоном Фуллертоном із Capital Institute.

ReFi має багато спільного з економікою замкнутого циклу, оскільки проекти цього сегменту також фокусуються на відновленні ресурсів та чистої енергії.

Традиційні економічні системи часто критикують за нерівномірний розподіл ресурсів та пріоритизацію короткострокового прибутку над довгостроковою стійкістю.

У свою чергу, ReFi властиві:

  • всеосяжний підхід, що бере до уваги взаємозалежність економічних, соціальних та екологічних факторів;
  • акцент на сталому розвитку — особливий акцент робиться на фінансуванні екологічних ініціатив та підприємств, пов’язаних із відновлюваними джерелами енергії;
  • фокус на соціальній складовій — ReFi покликані зменшити проблему економічної нерівності та підвищити добробут суспільства (наприклад, завдяки фінансуванню ініціатив щодо розширення доступу до освіти, створення робочих місць та будівництва недорогого житла);
  • зміщення акценту від довгострокових вигод до сталого довгострокового ефекту;
  • заохочення відкритості, прозорості та підзвітності;
  • активну участь спільнот у ухваленні рішень.

Для реалізації поставленої мети ReFi-проекти можуть використовувати залучений капітал, у тому числі за допомогою токенсейлів, а також принципи ДАО. Відповідні платформи зазвичай ґрунтуються на смарт-контрактах, що ріднить сегмент зі сферою DeFi.

ReFi також перетинається з рухом децентралізованої науки (DeSci), який використовує Ethereum та інші екосистеми для фінансування, створення, зберігання та розповсюдження наукових знань.

Які галузі охоплює ReFi?

Один із головних напрямів ReFi — токенізація вуглецевих кредитів , покликана сприяти скороченню шкідливих викидів в атмосферу.

Добровільний вуглецевий ринок (Voluntary carbon market, VCM) — це механізм фінансування ініціатив, які мають перевірений позитивний вплив на викиди CO₂. Після перевірки ці проекти отримують активи, іменовані вуглецевими кредитами, які можуть продавати окремим особам та організаціям, бажаючим підтримати діяльність у сфері клімату.

Перехід VCM на новий цифровий вуглецевий ринок (Digital carbon market, DCM) покликаний відкрити доступ широкого кола користувачів, усунути посередників, сприяти зростанню ліквідності та підвищення швидкості операцій.

Значними елементами ландшафту DCM є проекти Verra, Gold Standard, Toucan Protocol, C3, Moss.Earth, Klima Infinity, Senken та Nori. Вони сприяють інвестуванню у вуглецеві кредити та сприяють організації ініціатив щодо збереження клімату.

Відомий проект ReFi-екосистеми – Celo. Він позиціонується як «мобільно-орієнтований» та «вуглецево-негативний» блокчейн. Ініціатива “Ультразелені гроші” заснована на механізмі спалювання токенів, який відправляє 20% комісій за транзакції в мережі до фонду компенсації викидів вуглецю, що робить токен CELO дефляційним.

Регенеративні фінанси також покликані допомогти зберегти історичні записи та артефакти культурної спадщини , використовуючи технологію блокчейн та NFT. Зокрема, на цьому напрямі сфокусовано компанію Monuverse.

До ReFi також можна віднести проекти, що спеціалізуються на соціально відповідальному кредитуванні . За допомогою подібних платформ позичальники можуть отримати доступ до фінансування освітніх та інших ініціатив, що відповідають «регенеративним принципам».

Web3 також уможливлює випуск і торгівлю децентралізованими «зеленими облігаціями» . Останні дають можливість широкому колу користувачів брати участь у фінансуванні екологічних ініціатив, де блокчейн та смарт-контракти роблять платежі прозорими та автоматизують нарахування відсотків на вкладені кошти.

Які ризики властиві ReFi?

Як і будь-якому іншому сегменту криптоіндустрії, ReFi властиві ризики та підводні камені.

Наприклад, під питанням може бути фінансова стійкість проектів сегмента децентралізованих фінансів. Критики концепції стверджують, що орієнтовані на довгострокову перспективу вкладення з фокусом на екологію та соціальний ефект поступаються за рентабельністю інвестиціям у традиційні проекти.

Наступний аспект — складність та трудомісткість оцінки соціальних та екологічних наслідків фінансових рішень. ReFi-проекти можуть пом’якшити проблему шляхом впровадження універсальних стандартів використання технологій, а також за допомогою блокчейну та інших передових технологій для підвищення прозорості моніторингу процесів.

Важливо враховувати і потенційні регуляторні перешкоди , які можуть виникнути через прогалини в нормативно-правовій базі. Шляхом вирішення проблеми може стати конструктивний діалог з владою, традиційними фінансовими установами та бізнес-структурами, а також просвітницька робота для популяризації ReFi.

Немаловажна й проблема масштабування — ReFi-проекти можуть надавати обмежений вплив на глобальному рівні через труднощі із залученням коштів та логістичних труднощів. Змінити ситуацію може розвиток технологій, створення глобальних мереж та спільних підприємств, а також партнерства із спільнотами, що поділяють принципи регенеративних фінансів.

Деякі ReFi-проекти ставлять високі цілі, пропонуючи розпливчасті формулювання про те, як ці цілі будуть досягнуті. Найчастіше подібні компанії прагнуть отримати зиск на хайпі навколо екологічних та інших соціальних ініціатив.

Інвесторам слід проявляти особливу пильність, якщо:

  • інформація про токеноміку недостатня;
  • значна частина пропозиції монет зосереджена руках засновників і вузького кола ранніх інвесторів;
  • у проекту відсутня детальна дорожня карта з чітко окресленими датами та запланованими діями для реалізації поставлених цілей.

Перш ніж вкладати кошти у нову ReFi-ініціативу, учасникам ринку слід уважно вивчити бекграунд команди проекту.

Якщо платформа пропонує користувачеві розкрити сид-фразу – це шахрайство, націлене на спустошення гаманця. Слід взаємодіяти лише з перевіреними проектами, наприклад, із тими, хто залучив інвестиції від відомих венчурних фірм.

Як співвідносяться ReFi та DeFi?

Використовуючи блокчейн та смарт-контракти, DeFi та ReFi багато в чому схожі між собою. Однак, будучи тісно взаємопов’язаними напрямками, вони служать дещо різним цілям.

У контексті криптоіндустрії в цілому DeFi можна як більш широку категорію, де ReFi – її складова частина. Цей напрямок розвивається паралельно з іншими безпосередньо не пов’язаними з фінансами новими підсегментами на кшталтDeSci,DePINіDeSoc.

Які перспективи сегмента ReFi?

За всієї значущості концепції регенеративних фінансів ще рано говорити про практичну користь відповідних проектів. Головна причина в низькому інтересі широкої аудиторії — поки що мало хто готовий брати участь у подібних ініціативах.

Наприклад, добовий обсяг торгів токеном Moss Carbon Credit (MCO2) складає $39 400, за даними CoinGecko на 23 грудня. Актив низьколіквідний і не представлений на популярних платформах, таких як Binance або OKX.

Модель ReFi ще слабо поширена у реальних проектних розробках. Представленими на ринку рішеннями поки що користується лише невелика кількість по-справжньому зацікавлених учасників.

Прихильники регенеративних фінансів намагаються вирішити важливе, певною мірою утопічне завдання. Однак розвиток сегменту та популяризація концепції з часом можуть зробити ReFi набагато популярнішим, змінивши ставлення громадськості до криптовалютів загалом.

Вилучення прибутку не є головною метою при взаємодії із платформами регенеративних фінансів. Рух ліквідності розуміється як ланка в ланцюзі та засіб для комплексного вирішення екологічних, суспільних та наукових завдань.

Значна частина громадськості, як і раніше, сприймає використання криптоактивів у відриві від «реального світу». У багатьох блокчейн-індустрія асоціюється тільки зі спекуляціями монетами, що не мають цінності, для швидкого збагачення. З можливою популяризацією ідеї ReFi у сфері децентралізованих фінансів і криптовалют в цілому з’являється шанс на оновлення іміджу.

Джерело

Що таке RWA (Real World Assets)?

Real World Assets

У чому суть RWA?

Real World Assets (RWA) — термін, що означає ринок активів «реального світу», випущених у формі токенів на блокчейні. У RWA включають нерухомість, право власності, предмети мистецтва, коштовності та метали, традиційні фінансові інструменти на ринках капіталу.

Концепція має на увазі токенізацію вже існуючих активів для перенесення цінності в децентралізовані фінансові програми, скорочення витрат або підвищення ефективності управління коштами на традиційних ринках.

Що належать до RWA?

Ринок Real World Assets ще остаточно не сформований, але вже встиг розділитись на категорії проектів, що належать до загального тренду токенізованих активів реального світу.

Централізовані стейблкоіни. Популярні монети, такі як USDT від Tether або USDC від Circle, за своєю природою є RWA. Компанії-емітенти таких токенів блокують фіатні валюти, державні або комерційні цінні папери на своїх рахунках як забезпечення. Потім вони випускають стейблкоіни, що є своєрідною токенізацією.

Приватне кредитування Одним із учасників цього сектору єДАНО MakerDAO, емітент стейблкоіна DAI. У середині 2022 року організація відкрила кредитну лінію на $100 млн у токенах для американського Huntingdon Valley Bank. Як забезпечення банк заклав позабалансові кредити. Свої розробки на цьому ринку запропонував і емітент стейблкоіна USDC — він запустив протокол з відкритим кодом Perimeter, який є основою для створення токенізованих ринків кредитування.

Державні облігації. Сектор представлений компаніями, що випускають стейблкоіни, у забезпеченні яких лежать казначейські облігації будь-якої країни. Наприклад, Ondo Finance випускає стабільну монету USDY під заставу короткострокових казначейських облігацій США. Сюди слід віднести проект Mountain Protocol, що запустив стейблкоїн USDM на базі блокчейна Ethereum. Він також забезпечений казначейськими облігаціями, які приносять пасивний дохід у розмірі 5% річних.

Токенізовані цінних паперів. Новатором у цьому секторі стала біржа криптовалют Bitfinex. У вересні 2021 року розпочала роботу її дочірня платформа Bitfinex Securities, орієнтована на RWA-ринок. У жовтні 2023 року вона випустила першу токенізовану облігацію з трирічним періодом погашення та ставкою 10%.

Інтерес до сектору виявили й інституційні інвестори. Найбільший банк Великобританії HSBC планує у 2024 році запропонувати клієнтам сервіс для зберігання токенізованих цінних паперів. У жовтні 2023 року конгломерат JPMorgan запустив Tokenized Collateral Network (TCN), сервіс конвертації акцій у цифрові активи. У розробці брали участь фінансові гіганти BlackRock та Barclays.

Нерухомість. У січні 2023 року конгломерат Société Générale зайняв у MakerDAO $7 млн ​​у DAI. Як актив для забезпечення кредиту виступили регульовані законодавством Франції іпотечні облігації на $40 млн.

Сертифікати вуглецевих кредитів . 2022 року набрав популярність ринок «зелених» сертифікатів. Наприклад, проект KlimaDAO, який інвестував мільярдер Марк Кьюбан, пропонував криптокористувачам вуглецеві кредити. Теоретично зростання цін такі сертифікати мало спонукати компанії, забруднюючі довкілля, скоротити викиди. Однак це дуже нерозвинений ринок, повний шахрайства.

Твори мистецтва та предмети колекціонування. Цей сегмент представлений за допомогою ринку NFT. У 2021 році відразу кілька компаній та інвесторів перенесли цінність у блокчейн шляхом токенізації картин відомих художників. Так, швейцарський криптобанк Sygnum випустив серію NFT на основі роботи Пабло Пікассо «Дівчинка у береті», розділивши її на 4000 токенів. А засновник Tron Джастін Сан купив картини Пікассо та Енді Уорхола за $22 млн із наміром токенізувати їх через JUST NFT Fund.

Дорогоцінні метали. Учасниками цієї галузі ринку RWA є емітенти стейблкоїнів, які мають у забезпеченні дорогоцінні метали у фізичній формі. Як приклад можна навести токени від компанії Paxos або XAUT від Tether.

Які перспективи ринку RWA?

Головний аргумент, який дозволяє говорити про перспективи токенізованих активів «реального світу» — вартість глобального ринку перевищує сотні трильйонів доларів США незадіяної ліквідності. Станом на 2020 рік лише ринок нерухомості оцінили більш як на $326 трлн. Передбачається, що, принаймні, мала його частина перейде на блокчейн.

На кінець листопада 2023 року, згідно з даними сервісу Rwa.xyz, обсяг заблокованих коштів у протоколах RWA становив $4,5 млрд при $571 млн виданих кредитів. З листопада 2021 року зростання суми кредитів склало понад 6700% з $8,5 млн.

Однак, судячи з прогнозів інституційних компаній, основне зростання ще не відбулося, і ринок має величезні перспективи. Наприклад, аналітики Coinbase вважають, що “бум токенізації” відбудеться в наступні один-два роки. А оцінка зростання всього ринку RWA становить від $5 трлн до $16 трлн до 2030 року, згідно з прогнозами Citigroup та Boston Consulting відповідно.

Джерело

Функція TypeScript 5.3, про яку вам не розповіли

TypeScript 5.3

20 листопада 2023 року команда TypeScript випустила TS 5.3.

Одна з найважливіших змін у TypeScript 5.3 не була згадана у примітках до релізу.

Швидкий приклад коду

// Це було б помилкою у 5.2, але дозволено у 5.3!
const array = ["a", "b", "c"] as const satisfies string[];
 
const returnWhatIPassIn = <const T extends any[]>(t: T) => {
  return t;
};
 
// результат - any[] в TS 5.2, но ['a', 'b', 'c'] в 5.3
const result = returnWhatIPassIn(["a", "b", "c"]);

Повне пояснення

Робота з масивами, доступними для читання, TS може іноді доставляти деякі незручності.

Допустимо, ви хочете оголосити масив роутів як const.

Це дозволить вам повторно використовувати роути, оголошені типу.

const arrayOfRoutes = [
  { path: "/home", component: Home },
  { path: "/about", component: About },
] as const;

// type Route = "/home" | "/about"
type Route = (typeof arrayOfRoutes)[number]["path"];

Але якщо ви хочете переконатися, що масив arrayOfRoutes відповідає певному типу?

Для цього можна використовувати satisfies.

const arrayOfRoutes = [
  { path: "/home", component: Home },
  { path: "/about", component: About },
] as const satisfies {
  path: string;
  component: React.FC;
}[];

// Тип є "readonly" і не може бути 
// присвоєний типу, що змінюється

Єдина проблема полягає в тому, що TypeScript 5.2 це призведе до помилки! Але чому?

Масиви, доступні для читання, та масиви, що змінюються.

Це тому, що arrayOfRoutes доступний лише для читання, а ви не можете використовувати масив, доступний для зміни, для масиву, доступного для читання.

Тому виправлення полягало в тому, щоб зробити тип, який ми покриваємо, масивом readonly:

const arrayOfRoutes = [
  { path: "/home", component: Home },
  { path: "/about", component: About },
] as const satisfies readonly {
  path: string;
  component: React.FC;
}[];
 
// Помилок більше немає!

Тепер, коли ми використовуємо масив, доступний лише читання, TypeScript щасливий.

Параметри типу Const

Те саме відбувається і при використанні параметрів типу const, але ще більш згубно.

У цьому випадку const виводить річ, передану в T, ніби вона була const.

Але якщо ви спробуєте обмежити його масивом, це не спрацює!

const returnWhatIPassIn = <const T extends any[]>(t: T) => {
  return t;
};
 
// Результат: any[] в TS 5.2!
const result = returnWhatIPassIn(["a", "b", "c"]);

До версії TS 5.3 виправлення полягало в додаванні readonly до параметра type:

const returnWhatIPassIn = <const T extends readonly any[]>(
  t: T
) => {
  return t;
};
 
// Результат: ['a', 'b', 'c']!
const result = returnWhatIPassIn(["a", "b", "c"]);

Але це виправлення було важко знайти і вимагало глибоких знань про те, як працюють параметри типу const.

Як TypeScript 5.3 виправив ситуацію

Починаючи з версії 5.3 TypeScript пом’якшив правила роботи з масивами, доступними для читання.

У цих двох ситуаціях TypeScript діє ефективніше.

Ключове слово satisfies тепер дозволяє передавати масиви з readonly:

// Це було б помилкою у 5.2, але дозволено у 5.3!
// const array: ["a", "b", "c"]
const array = ["a", "b", "c"] as const satisfies string[];

Параметри типу Const тепер визначають переданий тип замість того, щоб за промовчанням використовувати свої обмеження:

const returnWhatIPassIn = <const T extends any[]>(t: T) => {
  return t;
};
 
// Результат: any[] в TS 5.2, но ['a', 'b', 'c'] в 5.3
// const result: ["a", "b", "c"]
const result = returnWhatIPassIn(["a", "b", "c"]);

Зверніть увагу на невелику різницю! Якби ви вказали readonlystring[] замість string[], то отримали б назад масив readonly.

Тому вам все одно потрібно вказати readonly, якщо ви хочете отримати назад масив, доступний для читання.

// Це було б помилкою у 5.2, але дозволено у 5.3!
// const array: readonly ["a", "b", "c"]
const array = [
  "a",
  "b",
  "c",
] as const satisfies readonly string[];

const returnWhatIPassIn = <const T extends readonly any[]>(
  t: T
) => {
  return t;
};
 
// результат - any[] в TS 5.2, но ['a', 'b', 'c'] в 5.3
// const result: readonly ["a", "b", "c"]
const result = returnWhatIPassIn(["a", "b", "c"]);

Але це значне поліпшення, що робить роботу з параметрами типу const, так і з задовольнячими набагато простіше.

TypeScript – ви повинні кричати про такі речі!

Переклад статті “The TypeScript 5.3 Feature They Didn’t Tell You About

Що таке рішення третього рівня (Layer 3)?

Layer 3

Як з’явилися L3-рішення?

Рішення третього рівня (Layer 3 чи L3) — загальна назва протоколів, розгорнутих з урахуванням вже існуючих L2-решень. Як і у випадку з L2, третій рівень мереж призначений для масштабування та розширення функціональних можливостей основного блокчейну.

Концепція подібних розробок існує вже понад вісім років. Одним із перших ідею про багаторівневе масштабування блокчейнів сформулював у 2015 році засновник Ethereum Віталік Бутерін. Найбільшу популярність цієї концепції принесли напрацювання StarWare, опубліковані наприкінці 2021 року.

На листопад 2023 року проектування рішень третього рівня зосереджено навколо блокчейну Ethereum.

<h2Для чого це потрібно?

L2-рішення призначені масштабування базового блокчейну. Вони усувають проблему низької пропускної спроможності та високих комісій за транзакції. У свою чергу, L3 має дві фундаментальні цінності, які важко реалізувати на другому рівні:

  • масштабованість, що настроюється. Третій рівень блокчейну дає можливість реалізувати індивідуальні налаштування для окремих додатків. Наприклад, відмінні від EVM обчислення чи використання інших форматів даних;
  • функціонал, що настроюється. L3 дозволить створювати окремі програми та мережі з унікальними точними налаштуваннями. Наприклад, можна реалізувати функцію конфіденційних транзакцій без відображення будь-яких даних на другому рівні.

L3 можна представити як рівень запуску окремих унікальних додатків чи мереж, пов’язаних стандартами L2.

Які L3-рішення існують на ринку?

Ринок L3-рішень лише почав формуватися. Основну розробку ведуть команди L2-рішень на блокчейні Ethereum, включаючи Optimism, Arbitrum та zkSync. Кожна з них має власне бачення.

Hyperchains . Компанія Matter Labs, що стоїть за zkSync, розраховує створити мережу взаємопов’язаних блокчейнів, які використовують доказ із нульовим розголошенням. Щоб реалізувати цей план команда випустила технологічний стек для розробників ZK Stack. Архітектура дозволить розгортати «гіперцепи» як паралельні рішення L2, так і протоколи третього рівня.

У рамках Hyperchains вже запустили тест L3-рішення Pathfinder. На початку 2024 року очікується реліз гібридної криптобіржі GRVT, що поєднує в собі інтерфейс централізованої біржі типу Robinhood та функції некастодіального зберігання активів, як у Uniswap.

Superchain. Це концепт масштабування Ethereum від команди L2-рішення Optimism на основі технологічного стека OP Stack. Поки що ідея команди полягає у створенні паралельних рішень, що діють незалежно один від одного, з подальшою можливістю розробки L3.

У рамках Superchain вже працює накопичувальний клієнт OP Stack від однієї із найбільших венчурних криптокомпаній a16z. У серпні 2023 року американська біржа Coinbase на основі архітектури Optimism запустила мережу другого рівня Base.

Інтерес у перенесенні своїх мереж у Superchain виник і в інших L2. Так, перейти на архітектуру OP Stack запропонувала спільноті компанія cLabs – розробник L2-мережі Celo.

Orbit. Команда Offchain Labs, відповідальна за L2-рішення Arbitrum, у червні 2023 року випустила свій набір інструментів для розробників, націлений на реалізацію L3-рішень на базі Arbitrum. Orbit націлена на створення саме L3-додатків із високим ступенем індивідуальних налаштувань.

Екосистема L3 на Arbitrum вже представлена ​​десятком проектів, що включають Web3 – фінансові ігри, dappsта NFT -проекти.

Які ще рішення називають L3?

L3-рішеннями можуть називатися будь-які мережі та інфраструктурні проекти, побудовані поверх існуючих L1 та/або L2, але не конкурують з ними. Вони виконуватимуть складні обчислення, роботу зі сторонніми даними, пропонуватимуть нові сценарії використання. Наприклад, L3-рішення може принести в блокчейн дані з реального світу, щоб використовувати їх на першому та другому рівнях у DeFi.

До L3 відносять децентралізовану мережу оракулів Chainlink, що відповідає за поставку правильних офчейн-даних для виконання смарт-контрактів на всіх рівнях блокчейну.

До рішень третього рівня також відносять інфраструктурні проекти на кшталт Orbs, що працює як надбудова до різних мереж із чудовим консенсусом та архітектурою. Orbs працює з Ethereum, TON, Polygon, BNB Chain,  Avalanche та Fantom, забезпечуючи розробникам та компаніям додатковий функціонал та сумісність для їх додатків.

У цій же області знаходяться рішення від Polkadot, відомі як парачейни. Саме цей функціонал створює додатковий шар для взаємодії Web3-додатків, спрощуючи обмін даними та активами.

Джерело

Використання Content-Security-Policy разом із React & Emotion

Content-Security-Policy(CSP) – це HTTP заголовок, який покращує безпеку веб-застосунків за рахунок заборони небезпечних дій, таких як завантаження та відправка даних на довільні домени, використання eval, inline-скриптів і т.д. У цій статті буде зроблено фокус на директиві style-srcта її використання разом із CSS-in-JS бібліотекою emotion.

Коротко про CSP та style-src

Content-Security-Policyзаголовок повинен бути виставлений у відповіді разом із завантажуваною веб-сторінкою (наприклад, index.html). Це виглядає так:

Content-Security-Policy: style-src 'self'

style-src– це директива, яка відповідає за те, які стилі можна завантажувати та застосовувати на сторінці. Можливі значення:

  • 'none'– усі стилі заборонені
  • 'self'– Дозволені файли стилів, які завантажуються з того ж домену, що і основний документ (сторінка)
  • <url>, наприклад https://example.com– дозволені файли стилів з цього домену, також допускаються wildcard (*) на місці під-домену та порту
  • '<hash-algorithm>-<base64-value>', наприклад 'sha256-ozBpjL6dxO8fsS4u6fwG1dFDACYvpNxYeBA6tzR+FY8='– дозволені файли стилів та inline -стилі (тег <style>), у яких хеш збігається із зазначеним значенням
  • 'nonce-<value>', наприклад 'nonce-abc'– дозволяються inline -стилі, у яких атрибут nonceзбігається із зазначеним (у прикладі – abc)
  • 'unsafe-hashes'– дозволяє inline -стилі, зазначені в атрибуті styleрядком, наприклад <div style="color:red;"></div>, при цьому хеш значення атрибута повинен збігатися з хешом, вказаним у'<hash-algorithm>-<base64-value>'
  • 'unsafe-inline'– дозволяє всі inline -стилі, створені через тег<style>
  • 'unsafe-eval'– дозволяє додати/змінити CSS declarations, які призводять до парсингу рядка, наприклад, за допомогою CSSStyleDeclaration.cssText

Директива може приймати кілька значень через пропуск. У цьому випадку це сприймається як логічне “або” – при задоволенні хоча б одного значення стилі дозволяються.

CSP та emotion

emotionдодає styleелементи динамічно і в останніх версіях не може виймати всі стилі в окремий файл під час складання програми. Це означає, що для того, щоб можна було використовувати emotionразом з style-src, є такі опції:

  1. 'unsafe-inline'– Найпростіша опція з усіх. Не вимагає будь-яких налаштувань з боку emotion. При цьому ми знижуємо безпеку нашої програми, тому це рішення можна використовувати лише як тимчасове.
  2. 'nonce-<value>'– можна дозволити inline-стилі, створені emotion. Для цього потрібно задати nonceпри створенні cache.

При використанні @emotion/reactабо @emotion/styledце можна зробити так:

import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";

export function App() {
  const cache = createCache({
    key: 'my-app',
    nonce: getNonceValue(),
  });
  
  return (
    <CacheProvider cache={cache}>
      {/* children */}
    </CacheProvider>
  );
}

Якщо використовується @emotion/cssбезпосередньо, то потрібно створити свій екземпляр emotion:

import createEmotion from '@emotion/css/create-instance';

export const {
  flush,
  hydrate,
  cx,
  merge,
  getRegisteredStyles,
  injectGlobal,
  keyframes,
  css,
  sheet,
  cache
} = createEmotion({
  key: 'my-app',
  nonce: getNonceValue(),
});

При використанні createEmotionпотрібно змінити всі місця, де раніше імпортувався @emotion/cssна цей модуль:

// import { css } from "@emotion/css"; 
import { css } from "./emotion";

Передача nonce на фронтенд

Т.к. значення CSP заголовка недоступне коду, що виконується на клієнті, значення потрібно додатково передати іншим чином. Один із варіантів – це створення inline-скрипту зі значенням, яке виставляється на бекенді:

<script id="nonce" type="application/json">
  "abc"
</script>

На фронтенді це можна використовувати таким чином:

function getNonceValue() {
  const nonceElement = document.getElementById("nonce");
  return JSON.parse(nonceElement.textContent);
}

Зверніть увагу на type="application/json"– таким чином браузер не вважає це виконуваним кодом, і особливе значення script-srcне потрібно.

Джерело

Що таке пули ліквідності та як вони працюють?

пули ліквідності

Що таке ліквідність?

Ліквідність — фундаментальна категорія як традиційного, так криптовалютного ринків. Вона характеризує можливість продати активи швидко та безперешкодно за ціною, близькою до ринкової.

Ліквідність, як правило, забезпечується безліччю покупців та продавців. Якщо фінансовий інструмент малопопулярний і, відповідно, неліквідний, може знадобитися деякий час для його конвертації у готівку.

Крім того, операція може бути пов’язана зі значним прослизанням. Останнє означає різницю в ціні, за яку учасник ринку хотів продати актив у порівнянні з тією, за якою він був фактично реалізований.

Що таке пул ліквідності?

Ключовим елементом сегмента децентралізованих фінансів (DeFi) є пули ліквідності. Останні є сукупністю криптоактивів, заблокованих в смарт-контракті.

Пули ліквідності використовуються у широкому спектрі платформ «фінансового Lego», охоплюючи сектори криптокредитування, DEX, децентралізованого страхування, синтетичних активів тощо.

Важливим компонентом є автоматичний маркетмейкер (AMM). Цей програмний алгоритм задіяний у більшості DeFi-протоколів. Механізм служить контролю ліквідності і ціноутворення криптоактивів на децентралізованих платформах, забезпечуючи автоматизовану торгівлю.

Як працює пул ліквідності?

Створити пул ліквідності може будь-який учасник DeFi-екосистеми. Для цього інвестору потрібно заблокувати в смарт-контракті кілька криптоактивів рівними за вартістю частинами, встановивши початкові ціни.

Користувачі AMM-платформ, що надають гроші в пули, називаються провайдерами ліквідності (liquidity providers, LP). Ціни LP токенів, що розміщуються, визначаються в залежності від співвідношення попиту та пропозиції (ринкова вартість монети зростає, якщо багато користувачів її купує, і навпаки), а також за допомогою задіяної в алгоритмі конкретної платформи формули.

За криптоактиви, що надаються, провайдери ліквідності отримують частку від торгових комісій. Розмір цієї винагороди залежить від обсягу внесеної ліквідності, що виражається у спеціальних криптоактивах – LP-токенах. Останні можна задіяти на різних платформах фінансового Lego.

пул ліквідності

Процес пасивного заробітку на криптоактивах шляхом їх розміщення на DeFi-платформах у співтоваристві називають «прибутковим фермерством» («фармінгом доходу», yield farming) або «майнінгом ліквідності».

Розміщеними у пулі криптовалютами може торгувати будь-який учасник ринку. При цьому не потрібні контрагенти (покупці та продавці, як на традиційних платформах) і книга ордерів — у роботу вступає механізм АММ, який передбачає торгівлю безпосередньо з пулом.

Більшість DEX працює відповідно до моделі Constant Product Market Maker (CPMM). Остання вперше з’явилася на платформі Bancor, але справжньої популярності набула з появою біржі Uniswap.

Відповідно до CPMM добуток вартості двох активів у пулі є постійною величиною:

Токен A * Токен B = K

де:

Токен А: вартість Токена А

Токен В: вартість Токену В

К: константа

Співвідношення між токенами у пулі диктує ціни. Наприклад, якщо хтось купує ETH у парі DAI/ETH, пропозиція ETH у пулі зменшується, а DAI – збільшується. Як результат, ціна ефіру зростає, стейблкоїну зменшується. Вплив такої операції на ринкову вартість активів залежить від обсягу правочину щодо величини пулу. Якщо TVL останнього значний, а операція всього на кілька доларів вплив на ціни активів буде невеликим.

При необхідності LP може будь-якої миті вийти з пулу ліквідності, просто погасивши свої LP-токени. Учаснику ринку на гаманець відразу ж повернеться спочатку внесена кількість монет та відсотковий дохід, нарахований завдяки торговій активності.

Які є платформи DeFi на основі пулів ліквідності?

Більшість DEX базується на AMM-механізмі відповідно до моделі CPMM. Частка цього бірж у сегменті наближається до 90%.

Відповідний показник “гібридних” DEX – ~9,5%, децентралізованих бірж на основі книги ордерів – ~1%.

Uniswap – беззмінний лідер сегменту по TVL та обсягу торгів. На момент написання матеріалу (20.10.2023) біржа підтримує 8 мереж, включаючи Ethereum, Arbitrum, Optimism, Polygon, Base, BNB Chain, Avalanche та Celo.

У липні Uniswap Labs представила UniswapX — протокол агрегування ліквідності децентралізованих бірж з відкритим вихідним кодом. У тому ж місяці розробники окреслили терміни запуску Uniswap v4 – протягом чотирьох місяців після хардфорку Dencun у блокчейні Ethereum.

Значною популярністю серед учасників криптовалютного ринку користується платформа Curve. Вона розроблена для ефективної торгівлі між стейблкоінами та іншими токенами однакової вартості з мінімальним ковзанням та комісією.

Вагома частка ринку також припадає на Balancer. Ця платформа дозволяє створювати пули для трьох та більше токенів.

Які переваги у пулів ліквідності?

Будучи ключовим елементом DeFi-екосистеми, що постійно розвивається, пули ліквідності пропонують можливість будь-яким учасникам ринку отримувати пасивний дохід від криптоактивів.

Найважливіша їхня властивість — permissionless. Це означає, що будь-який користувач може створити пул ліквідності та, відповідно, новий ринок. При цьому немає жодних процесів розгляду чи схвалення — все відбувається децентралізовано, без посередників.

Учасником DeFi-екосистеми може стати як великий, так і дрібний інвестор – вхідний бар’єр практично відсутній. Така відкритість сприяє розвитку більш інклюзивної та справедливої ​​фінансової системи, де кожен може привнести ліквідність ринку і допомогти активізувати торговельну активність.

DEX ґрунтуються на смарт-контрактах із відкритим вихідним кодом, припускаючи прозорість та доступність для зовнішнього аудиту.

Які недоліки у пулів ліквідності?

DeFi-екосистема все ще далека від стадії зрілості. Поряд із незаперечними перевагами, ключовим елементам децентралізованих фінансів властива низка недоліків:

  • пули ліквідності та протоколи загалом можуть бути підконтрольні вузькому колу учасників ринку, що суперечить концепції децентралізації;
  • ризики злому через уразливості та помилки в коді;
  • ймовірність тягнути килимок;
  • значні прослизання, особливо на низьколіквідних ринках (характерно для нових та маловідомих монет та платформ з невеликим TVL).

Крім іншого, учасники DeFi-сегменту схильні до ризику непостійних збитків (Impermanent loss, IL). Йдеться про ситуацію, коли ціни активів у пулі ліквідності суттєво відрізняються від тих, що були при депонуванні коштів у пули. За сильних рухів ринку іноді вигідніше просто тримати монети в гаманці, ніж блокувати їх у смарт-контрактах для отримання пасивного доходу.

Наведемо спрощений приклад такої ситуації.

Припустимо, користувач створив на новій DEX пул WBTC/USDT, депонувавши для цього 1 WBTC та 20 000 USDT відповідно.

Ціна біткоїну різко пішла вгору, за короткий час цифрове золото подорожчало до 25 000 USDT. Дізнавшись про нову біржу, арбітражери побачили можливість заробити на курсовій різниці. Для цього вони почали активно викуповувати «обгорнуті» біткоїни

з пулу, доки ціна не досягла паритету з ширшим ринком.

Тепер у пулі переважає USDT, а запас WBTC практично вичерпано.

Отже: Початковий депозит: 20 000 USDT + 1 WBTC. Зважаючи на те, що кошти вносяться на DEX рівними частинами, загальна вартість активів у пулі еквівалентна 40 000 USDT.

На тлі ралі біткоїну до 25 000 USDT вартість ліквідності в пулі має становити 45 000 USDT. Але оскільки WBTC вичерпано, у пулі міститься 40 000 USDT і 0 BTC. Альтернативні витрати становитимуть 5000 USDT.

Ця різниця і є Impermanent loss. Такі збитки називають «непостійними», тому що вартість активів у пулі все ще може досягти паритету зі значеннями ширшого ринку. Збиток стає «постійним» лише тому випадку, якщо постачальники ліквідності виходять залишають пул у разі виникнення IL.

Існують калькулятори непостійних збитків, наприклад, від платформ dilydefi.org та CoinGecko .

Торгові операції через пули ліквідності також можуть бути пов’язані зі значними прослизаннями. Особливо якщо йдеться про нові, маловідомі і (можливо, поки що) неліквідні монети.

Прослизання означає різницю між очікуваною ціною та фактичною ціною здійснення торгової операції. Причина такого розбіжності у тому, що ринкові вартості активів у пулі не перебувають у сталості, змінюючись від угоди до угоди і щоразу знаходячи новий рівноважний рівень.
Торгові операції в контексті AMM не проводяться миттєво — у проміжку між ініціюванням та підтвердженням транзакції користувача може статися кілька інших досить великих угод. Останні можуть суттєво впливати на ціни, особливо якщо обсяг ліквідності в пулі невеликий.

Джерело