1. Установка БД
Она должна быть первым шагом, ведь нужно понять, возможно ли вообще доказательство концепции.Есть библиотека sql.js. Это буквально версия SQLite, скомпилированная в WebAssembly и через emscripten в старую добрую ASM.js. Воспользоваться версией для WASM нельзя: она должна получать двоичный файл по сети. В версии для ASM этого ограничения нет, и весь код доступен в одном модуле.
Хотя в PaintWorklet доступ к сети внутри воркера ограничен, import кода в модуле ES6 выполнить можно. Иными словами, внутри файла должна быть инструкция export. Но в sql.js нет сборки исключительно для ES6, поэтому скрипт пришлось изменить.
А теперь главный вопрос: можно ли установить БД в ворклете?
const SQL = await initSqlJs({
locateFile: file => `./${file}`,
});
const DB = new SQL.Database();
Получилось! Ошибок нет. Но и данных тоже нет, так что добавим их.
2. Запросы к БД
Самое простое, что можно сделать вначале — добавить фиктивные данные. В 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)
Теперь неплохо визуализировать результаты.
3. Простой способ визуализации
Я думал, это так же просто, как писать текст в сanvas: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 — препятствие, но делу оно не помешает.
4. Текст без API для текста
К счастью, библиотека opentype.js предлагает решение. Она позволяет проанализировать файл шрифта, а затем, получив строку текста, сгенерировать формы букв каждого символа. Итог этой операции — объект path со строкой, которую можно отобразить в контексте рисования.На этот раз, чтобы импортировать библиотеку opentype.js, вносить изменения не нужно: она уже есть в 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')
Здесь проблема, ведь шрифт OpenType нужно загрузить по сети. Я не могу этого сделать. Опять сорвалось! Или могу? Есть метод 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)
Я говорил, что в ворклете нет API для обработки строк base64? Нет даже atob и btoa. Пришлось найти реализацию на обычном JS. Этот код я поместил в отдельный файл: не очень это… эргономично — работать с 200 Кб строки кодированного шрифта вместе с оставшейся частью кода.
Вот как, злоупотребив модулем ES, я загрузил шрифт.
5. Отображение результатов, другой простой способ
Теперь всю тяжёлую работу выполняет библиотека 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, а запрос я захардкодил.
6. Запросы к базе данных через CSS
Запросы к БД лучше делать с помощью CSS: на самом деле это единственный способ взаимодействовать с воркером рисования вне его контекста — у этого воркера нет 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;
}
Кавычки убраны специально, иначе пришлось бы удалять их из строки перед её передачей в БД. Всё работает, миссия выполнена. Если вы уже попробовали sqlcss.xyz, то наверняка заметили, что я на этом не остановился — после небольшого рефакторинга сделал ещё пару изменений.
7. Локальный файл базы данных
Концепция доказана, но можно добиться большего. Хардкодить схему БД и сами данные — это плохо. Здорово делать запрос в любую БД, когда у вас есть её файл. Нужно просто прочитать его и кодировать в 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 элемента.
8. Пишем запросы к БД
Это, возможно, самая простая часть проекта. Точки с запятой в нашем свойстве 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 для импорта и просмотра локальной БД. Но как красиво отображать результаты, когда их много и всё нужно разделить на отдельные строки? Это уже другая тема, но если вы хотите разобраться в ней, вот весь код проекта.
Как подключиться к базе данных с помощью CSS
К старту курса по Fullstack-разработке на Python делимся материалом о том, как при помощи современных возможностей CSS и JS — ворклетов и API Houdini — подключиться к базе данных и выполнять запросы к...
habr.com