Разработчик решил опровергнуть мем и подключился к базе данных через 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 — препятствие, но не критичный момент.
В решение этой задачи может помочь библиотека под названием 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 для запроса к базе данных? Фактически это единственный способ взаимодействия с 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 для импорта и просмотра собственной базы данных. Таким образом, если Ли Мейчин прав в своих рассуждениях, то порочащая рекрутеров шутка не имеет смысла.
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…
Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…