Рубріки: Мнение

Разработчик решил опровергнуть мем и подключился к базе данных через CSS — вот его инструкция

Богдан Мирченко

Шутка, смысл которой приходится объяснять, — плохая шутка. А еще хуже, когда она не шутка вообще. Расхожую шутку о том, что к базе данных невозможно подключиться с помощью CSS, решил проверить разработчик Ли Мейчин. И в итоге перевел ее в разряд несмешных. 

В сети последнее время распространился следующий твит.

Тот самый твит

Его автор, чтобы высмеять рекрутеров, которые не разбираются в технологиях и путают стеки, написал, что они настолько некомпетентны, что могут написать что-то вроде: «Ищем специалиста, который смог бы подключиться к базе данных с помощью CSS». Твит и его скриншоты, уже в качестве мемов, набирают массу лайков, но есть вероятность, что выполнить описанные в посте условия все-таки можно. И вот как, по словам Ли Мейчина, это сделать. 

Способ есть, но не все так просто

Стоит отметить, что, по словам автора, данный способ работает только в Chrome, но при желании можно взять любую базу данных SQLite и запросить ее через CSS. Вот как это работает. 

Новый набор API, так называемый Houdini, дает браузеру возможность управлять CSS через собственную объектную модель на JavaScript. Это означает, что пользователь может создавать пользовательские стили CSS, добавлять пользовательские свойства и так далее. 

В ходе проекта пригодится интерфейс CSS Paint Worklet, который позволяет «рисовать» на элементе, как в известном и всеми любимом Canvas, и заставляет браузер обрабатывать его как изображение в CSS. На сайте houdini.how есть несколько примеров работы с интерфейсом. 

Однако worklet предоставляет только часть API Worker, плюс также сильно урезан сам контекст canvas. Что это значит? У пользователя нет доступа к сети, поэтому он может попрощаться с fetch и XmlHttpRequest. Не будет также функции drawText и других JS API. Однако не все потеряно. Будем разбираться с проблемой поэтапно. 

Настройка базы данных

Это первое, что нужно сделать, чтобы проверить, возможно ли подключить базу данных (БД) с помощью CSS. 

Существует библиотека под названием sql.js7. Это буквально версия SQLite, скомпилированная в WebAssembly и старый добрый ASM.js через emscripten. К сожалению, нельзя использовать версию WASM, потому что она должна получить двоичный файл по сети. В версии ASM нет этого ограничения, поскольку весь код доступен в одном модуле. 

Несмотря на то, что  PaintWorklet ограничивает сетевой доступ внутри рабочего, все равно можно импортировать код с помощью import, если это модуль ES6. Это означает, что в файле должен присутствовать оператор export. Sql.js не имеет сборки только для ES6, поэтому автор модифицировал скрипт, чтобы все работало. 

А теперь момент истины: получится ли создать базу данных внутри worklet?

const SQL = await initSqlJs({
  locateFile: file => `./${file}`,
});

const DB = new SQL.Database();

Успех! Ошибок нет, но данных тоже нет. Надо это исправить. 

Запрос базы данных

Проще всего будет создать сколько-то фальшивых данных. В Sql.js есть несколько функций, которые позволяют это сделать.

DB.run('CREATE TABLE test (name TEXT NOT NULL)')
DB.run(
  'INSERT INTO test VALUES (?), (?), (?), (?)',
  ['A', 'B', 'C', 'D']
)

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

const result = DB.exec('SELECT * FROM test')
console.log(result)

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

Рендеринг результатов (простой способ)

class SqlDB {
  async paint(ctx, geom, properties) {
    const result = DB.exec('SELECT * FROM test');
    ctx.font = '32px monospace';
    ctx.drawText(JSON.stringify(result), 0, 0, geom.width);
  }
}

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

Конечно, еще возможно рисовать пути и кривые, поэтому отсутствие удобного API — препятствие, но не критичный момент. 

Создание текста без text API

В решение этой задачи может помочь библиотека под названием opentype.js. Она способна анализировать файл шрифта, а затем, получив строку текста, генерировать буквенные обозначения каждого символа. Практическим результатом этой операции является объект path, представляющий строку, который затем может быть отображен в контексте. 

Автор модифицировал библиотеку opentype, чтобы импортировать ее, потому что она уже доступна в JSPM. Если передать JSPM пакет npm, он автоматически сгенерирует модуль ES6, который можно будет импортировать прямо в браузер.

import opentype from 'https://ga.jspm.io/npm:opentype.js@1.3.4/dist/opentype.module.js'

opentype.load('fonts/firasans.otf')

Один нюанс — при выполнении кода шрифт загружается по сети. Но решить проблему можно с помощью метода parse, который принимает буфер массива. Тогда достаточно просто закодировать шрифт в base64 и декодировать его в модуле.

import opentype from 'https://ga.jspm.io/npm:opentype.js@1.3.4/dist/opentype.module.js'
import base64 from 'https://ga.jspm.io/npm:base64-js@1.5.1/index.js'

const font = 'T1RUTwAKAIAAAwA ... 3 days later ... wAYABkAGgAbABwAIAKM'

export default opentype.parse(base64.toByteArray(font).buffer)

Примечание: у workletнет API для работы со строками base64, atob и даже btoa. Для это автору пришлось найти простую JS-реализацию. Он поместил код в отдельный файл, потому что неудобно работать с 200-килобайтной строкой закодированного шрифта рядом с остальным кодом. Именно так он использовал модуль ES для загрузки шрифта. 

Рендеринг результатов (еще один простой способ)

Библиотека opentype делает основную работу, поэтому остается только все красиво выровнять.

import font from './font.js'

const SQL = await initSqlJs({
  locateFile: file => `./${file}`,
});

const DB = new SQL.Database();

DB.run('CREATE TABLE test (name TEXT NOT NULL)')
DB.run(
  'INSERT INTO test VALUES (?), (?), (?), (?)',
  ['A', 'B', 'C', 'D']
)

class SqlDB {
  async paint(ctx, geom, properties) {
    const query = DB.exec('SELECT * FROM test')
    const result = query[0].values.join(', ')

    const size = 48
    const width = font.getAdvanceWidth(queryResults, size)
    const point = {
      x: (geom.width / 2) - (width / 2),
      y: geom.height / 2
    }

    const path = font.getPath(result, point.x, point.y, size)
    path.draw(ctx)
  }
}

registerPaint('sql-db', SqlDb)

Вот что происходит на уровне HTML и CSS.

<html>
  <head>
    <script>
      CSS.paintWorklet.addModule('./cssdb.js')
    </script>
    <style>
      main {
        width: 100vw;
        height: 100vh;
        background: paint(sql-db);
      }
    </style>
  </head>
  <body>
    <main></main>
  </body>
</html>

Все работает, но CSS здесь мало. 

Запрос через CSS

Как нам использовать CSS для запроса к базе данных? Фактически это единственный способ взаимодействия с Paint Worklet вне его контекста, поскольку нет API обмена сообщениями. 

Чтобы это сделать, потребуется пользовательское свойство CSS. Определение inputProperties дает преимущество подписки на изменения этого свойства, поэтому при изменении значения этого свойства будет происходить повторный рендеринг. Нет необходимости устанавливать какие-либо другие обработчики.

class SqlDb {
  static get inputProperties() {
    return [
      '--sql-query',
    ]
  }

  async paint(ctx, geom, properties) {
    // ...
    const query = DB.exec(String(properties.get('--sql-query')))
  }
}

Эти свойства CSS известны как типизированные свойства, но они, по сути, заключены в специальный класс CSSProperty, который сам по себе не очень полезен. Поэтому для его использования необходимо вручную преобразовать его в строку, число или что-то подобное, как описано выше. 

Далее нужно просто по-быстрому настроить CSS.

main {
  // ...
  --sql-query: SELECT name FROM test;
}

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

Жесткое кодирование схемы базы данных доказывает концепцию, но можно сделать лучше. Например, было бы здорово, если бы можно было делать запрос к любой БД, то есть просто прочитать файл с базой данных и закодировать его в base64, как мы делали это ранее с файлом шрифта.

const fileInput = document.getElementById('db-file')
fileInput.onchange = () => {
  const reader = new FileReader()
  reader.readAsDataURL(fileInput.files[0])

  reader.onload = () => {
    document.documentElement.style.setProperty(
        '--sql-database',
        `url('${reader.result}')`
    )
  }
}

Для этого автор сделал дополнительное свойство CSS, в котором можно указать базу данных SQLite в виде URI данных в кодировке base64. URI данных в основном нужен только для демонстрации и для того, чтобы убедиться, что он действителен для DOM. 

Заметьте, в коде последний шаг сделан для того, чтобы сделать запрос проще, потому что в противном случае придется зайти в отладчик, чтобы поработать с CSS элементом. 

Как писать собственные запросы

А вот это, возможно, наименее сложная часть проекта. У пользовательского свойства есть небольшая проблема с точками с запятой, а SQLite не волнует, опущена ли точка с запятой, поэтому можно удалить ее, если она есть во входных данных.

const queryInput = document.getElementById('db-query')
queryInput.onchange = () => {
  let query = queryInput.value;
  if (query.endsWith(';')) {
    query = query.slice(0, -1)
  }

    document.documentElement.style.setProperty(
    '--sql-query',
    queryInput.value
  )
}

Вот как использовать CSS для импорта и просмотра собственной базы данных. Таким образом, если Ли Мейчин прав в своих рассуждениях, то порочащая рекрутеров шутка не имеет смысла.

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023