Оптимізуйте довгі завдання у JavaScript

Вам казали «не блокуйте основний потік» та «розбивайте свої довгі завдання», але що означає робити ці речі?

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

  • “Не блокуйте основний потік”.
  • “Розбивайте свої довгі завдання”.

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

Щоб зрозуміти, чому так важливо оптимізувати завдання JavaScript, вам необхідно зрозуміти роль завдань і те, як браузер їх обробляє — і це починається з розуміння того, що таке завдання.

Що таке завдання?

Завдання  це будь-яка окрема частина роботи, яку виконує браузер. Завдання включають такі роботи, як рендеринг, аналіз HTML і CSS, виконання написаного вами коду JavaScript та інші речі, над якими ви не можете безпосередньо контролювати. З усього цього JavaScript, який ви пишете та розгортаєте в Інтернеті, є основним джерелом завдань.

Оптимізація JavaScript
Зображення завдання,  click обробником події клацання у профільнику продуктивності в Chrome DevTools.

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

Яка основна нитка?

Основний потік  це місце, де в браузері виконується більшість завдань. Він недаремно називається основним потоком: це єдиний потік, в якому майже весь написаний вами JavaScript виконує свою роботу.

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

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

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

Оптимізація JavaScript
Візуалізація одного довгого завдання порівняно з тим самим завданням, розбитим на п’ять більш коротких завдань.

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

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

У верхній частині попереднього малюнка обробник подій, поставлений у чергу в результаті взаємодії з користувачем, повинен був дочекатися одного довгого завдання, перш ніж він зможе запуститися. Це затримує взаємодію. Внизу обробник подій може запуститися раніше. Оскільки обробник подій мав можливість запускатись між меншими завданнями, він запускається раніше, ніж якби йому доводилося чекати завершення довгого завдання. У верхньому прикладі користувач міг помітити затримку; внизу взаємодія могла здатися  миттєвою  .

Проблема, однак, у тому, що поради «розбивати свої довгі завдання» та «не блокувати основний потік» є недостатньо конкретними, якщо ви вже не знаєте,  як  це робити. Це те, що пояснить це керівництво.

Стратегії управління завданнями

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

function saveSettings () {  validateForm();  showSpinner();  saveToDatabase();  updateUI();  sendAnalytics();}

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

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

Оптимізація JavaScript
Одна функція  saveSettings() , що викликає п’ять функцій. Робота виконується як частина одного довгого монолітного завдання.

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

Вручну відкласти виконання коду

Один із методів, який розробники використовували для розбиття завдань на дрібніші, — це  setTimeout() . Використовуючи цей метод, ви передаєте функцію  setTimeout() . Це переносить зворотний дзвінок в окреме завдання, навіть якщо ви вкажете тайм  0 .

function saveSettings () {  // Do critical work that is user-visible:  validateForm();  showSpinner();  updateUI();// Defer work that isn't user-visible to a separate task:  setTimeout(() => {    saveToDatabase();    sendAnalytics();  }, 0);}

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

function processData () {  for (const item of largeDataArray) {    // Process the individual item here.  }}

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

Крім  setTimeout() , існує кілька інших API, які дозволяють відкласти виконання коду до наступного завдання. Один із них  передбачає використання postMessage()  для більш швидкого тайм-ауту. Ви також можете розбити роботу за допомогою  requestIdleCallback() , але будьте обережні! —  requestIdleCallback() планує завдання із мінімально можливим пріоритетом і лише під час простою браузера. Коли основний потік перевантажений, завдання, заплановані за допомогою  requestIdleCallback() , можуть не запуститися.

Використовуйте async/await для створення точок плинності

У частині цього керівництва, що залишилася, ви зустрінете фразу «поступитися основною ниткою», але що це означає? Чому вам слід зробити це? Коли вам це потрібно зробити?

Коли завдання розбиті на частини, іншим завданням краще розставити пріоритети за допомогою внутрішньої схеми пріоритезації браузера. Один із способів переходу до основного потоку включає використання комбінації  Promise , яка дозволяється викликом  setTimeout() :

function yieldToMain () {  return new Promise(resolve => {    setTimeout(resolve, 0);  });}

У функції  saveSettings() можна перейти до основного потоку після кожної частини роботи, якщо ви  await функцію  yieldToMain() після кожного виклику функції:

async function saveSettings () {  // Create an array of functions to run:  const tasks = [    validateForm,    showSpinner,    saveToDatabase,    updateUI,    sendAnalytics  ]// Loop over the tasks:  while (tasks.length > 0) {    // Shift the first task off the tasks array:    const task = tasks.shift();// Run the task:    task();// Yield to the main thread:    await yieldToMain();  }}

В результаті колись монолітне завдання тепер розбито на окремі завдання.

Оптимізація JavaScript
Функція  saveSettings() тепер виконує свої дочірні функції окремі завдання.

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

Поступайтеся тільки за необхідності

Що робити, якщо у вас є безліч завдань, але ви хочете виконати їх тільки в тому випадку, якщо користувач спробує взаємодіяти зі сторінкою? Саме для цього і був створений метод  isInputPending() .

isInputPending() — це функція, яку можна запустити в будь-який час, щоб визначити, чи намагається користувач взаємодіяти з елементом сторінки: виклик  isInputPending() поверне  true . Інакше він повертає  false .

Припустимо, у вас є черга завдань, які вам потрібно виконати, але ви не хочете заважати жодним вхідним даним. Цей код, який використовує як  isInputPending() так і нашу спеціальну функцію  yieldToMain() , гарантує, що введення не буде затримано, поки користувач намагається взаємодіяти зі сторінкою:

async function saveSettings () {  // A task queue of functions  const tasks = [    validateForm,    showSpinner,    saveToDatabase,    updateUI,    sendAnalytics  ];while (tasks.length > 0) {    // Yield to a pending user input:    if (navigator.scheduling.isInputPending()) {      // There's a pending user input. Yield here:      await yieldToMain();    } else {      // Shift the task out of the queue:      const task = tasks.shift();// Run the task:      task();    }  }}

Під час роботи  saveSettings() він циклічно перебиратиме завдання у черзі. Якщо  isInputPending() повертає  true під час циклу,  saveSettings() викличе  yieldToMain() , щоб можна було обробити введення користувача. В іншому випадку наступне завдання буде перенесено з початку черги і виконуватиметься безперервно. Він робитиме це доти, доки не залишиться більше завдань.

Оптимізація JavaScript
saveSettings() запускає чергу завдань для п’яти завдань, але користувач клацнув, щоб відкрити меню, доки виконувався другий робочий елемент. isInputPending() передає основному потоку обробку взаємодії та відновлює виконання інших завдань.

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

Інший спосіб використання  isInputPending() – особливо якщо ви турбуєтеся про надання запасного варіанту для браузерів, які його не підтримують, – це використовувати підхід, заснований на часі, у поєднанні з  необов’язковим оператором ланцюжка  :

async function saveSettings () {  // A task queue of functions  const tasks = [    validateForm,    showSpinner,    saveToDatabase,    updateUI,    sendAnalytics  ];    let deadline = performance.now() + 50;while (tasks.length > 0) {    // Optional chaining operator used here helps to avoid    // errors in browsers that don't support `isInputPending`:    if (navigator.scheduling?.isInputPending() || performance.now() >= deadline) {      // There's a pending user input, or the      // deadline has been reached. Yield here:      await yieldToMain();// Extend the deadline:      deadline = performance.now() + 50;// Stop the execution of the current loop and      // move onto the next iteration:      continue;    }// Shift the task out of the queue:    const task = tasks.shift();// Run the task:    task();  }}

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

Прогалини в поточних API

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

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

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

Спеціальний API планувальника

API-інтерфейс планувальника зараз пропонує функцію  postTask() , яка на момент написання доступна в браузерах Chromium і Firefox під прапором. postTask() забезпечує більш детальне планування завдань і є одним із способів допомогти браузеру розставити пріоритети в роботі, щоб завдання з низьким пріоритетом поступалися місцем основного потоку. postTask() використовує обіцянки та приймає налаштування  priority .

API  postTask() має три пріоритети, які можна використовувати:

  • 'background' для завдань із найнижчим пріоритетом.
  • 'user-visible' для завдань із середнім пріоритетом. Це значення за промовчанням, якщо  priority не встановлено.
  • 'user-blocking' для критичних завдань, які необхідно виконувати із високим пріоритетом.

Як приклад візьмемо наступний код, де API  postTask() використовується для запуску трьох завдань з максимально можливим пріоритетом, а двох завдань, що залишилися, — з мінімально можливим пріоритетом.

function saveSettings () {  // Validate the form at high priority  scheduler.postTask(validateForm, {priority: 'user-blocking'});// Show the spinner at high priority:  scheduler.postTask(showSpinner, {priority: 'user-blocking'});// Update the database in the background:  scheduler.postTask(saveToDatabase, {priority: 'background'});// Update the user interface at high priority:  scheduler.postTask(updateUI, {priority: 'user-blocking'});// Send analytics data in the background:  scheduler.postTask(sendAnalytics, {priority: 'background'});};

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

Оптимізація JavaScript
Під час запуску  saveSettings() функція планує виконання окремих функцій за допомогою  postTask() . Критична робота, пов’язана з користувачем, запланована з високим пріоритетом, а робота, про яку користувач не знає, запланована для виконання у фоновому режимі. Це дозволяє швидше виконувати взаємодію з користувачем, оскільки робота розбивається на частини  та  відповідним чином розподіляється за пріоритетами.

Це спрощений приклад використання  postTask() . Можна створювати екземпляри різних об’єктів  TaskController , які можуть розділяти пріоритети між завданнями, включаючи можливість змінювати пріоритети для різних екземплярів  TaskController у міру потреби.

Вбудований вихід із продовженням через scheduler.yield

Однією з пропонованих частин API планувальника є  scheduler.yield API, спеціально розроблений для передачі основного потоку в браузері  , який в даний час доступний для тестування в якості вихідної пробної версії  . Її використання нагадує функцію  yieldToMain() , продемонстровану раніше у цій статті:

async function saveSettings () {  // Create an array of functions to run:  const tasks = [    validateForm,    showSpinner,    saveToDatabase,    updateUI,    sendAnalytics  ]// Loop over the tasks:  while (tasks.length > 0) {    // Shift the first task off the tasks array:    const task = tasks.shift();// Run the task:    task();// Yield to the main thread with the scheduler    // API's own yielding mechanism:    await scheduler.yield();  }}

Ви помітите, що наведений вище код багато в чому вам знайомий, але замість використання  yieldToMain() ви викликаєте і  await scheduler.yield() .

Оптимізація JavaScript
Візуалізація виконання завдання без поступки, зі поступкою, а також зі поступкою та продовженням. При використанні  scheduler.yield() виконання завдання поновлюється з того місця, де його було зупинено, навіть після точки виходу.

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

Висновок

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

  • Перейдіть до основного потоку для вирішення критичних завдань, з якими стикається користувач.
  • Використовуйте  isInputPending() , щоб перейти до основного потоку, коли користувач намагається взаємодіяти зі сторінкою.
  • Розставте пріоритети задач за допомогою  postTask() .
  • Нарешті,  виконуйте якнайменше роботи у своїх функціях.

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

Джерело

Створення веб-додатків з використанням мікрофронтендів та 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не потрібно.

Джерело