Usando SQLite3 en la web con Sveltekit y @sqlite.org/sqlite-wasm

Estoy muy emocionado porque después de tanto tiempo y gracias al Origin Private File System podremos tener SQLite3 en los navegadores, con todas sus ventajas y todo contenido desde una página web.

Si quieres puedes ver el hola mundo sin Svelte, es decir, con JS puro y aprender cómo usar SQLite3 en el navegador web sin servidores.

Ya había leído eso hace tiempo pero tenías que hacer unas cosas “experimentales”.

El punto es que ando aprendiendo un poco de Svelte con Tailwind haciendo una app de notas (a las que les agregaré encriptación) y me dio curiosidad por ver si SQLite3 ya estaba “portado” totalmente, porque si bien ya existía con WebAssembly no había manera de crear archivos binarios en el navegador web.

Entonces encontré a: https://developer.chrome.com/blog/sqlite-wasm-in-the-browser-backed-by-the-origin-private-file-system/

Y vi que ya era posible. Lo siguiente era hacerlo funcionar con SvelteKit que usa a su vez Vite para todo su entorno.

Te contaré cómo lo hice y de dónde me he guiado.

Instalando dependencia

Lo primero que tenemos que hacer es instalar el paquete con:

npm install @sqlite.org/sqlite-wasm

Agregar encabezados para OPFS

Ahora vamos a configurar el “servidor” de desarrollo para que agregue unos encabezados y también excluya al paquete de optimizeDeps. Mi vite.config.js queda así:

import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';

export default defineConfig({
	optimizeDeps: {
		exclude: ['@sqlite.org/sqlite-wasm'],
	},
	plugins: [
		sveltekit(),
		{
			name: 'configure-response-headers',
			configureServer: (server) => {
				server.middlewares.use((_req, res, next) => {
					res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
					res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
					next()
				})
			},
		},
	]

});

Lo importante es optimizeDeps y el configure-response-headers.

Nota: cuando la aplicación sea compilada para producción será necesario agregar esos encabezados en el lugar donde la vayamos a servir. Aquí lo hacemos de ese modo porque Vite se encarga de hacerlo, pero si después la alojamos en un servidor Apache o similares debemos configurar lo necesario.

Creando web worker

Luego hice el worker.js colocado en src/worker.js. Quedó así con mis modificaciones (solo puse el hola e hice que los errores y registros se imprimieran en la consola):

import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
console.log("Hola")

const log = (...args) => { console.log(...args) };
const error = (...args) => { console.log(...args) };

const start = function (sqlite3) {
  log('Running SQLite3 version', sqlite3.version.libVersion);
  let db;
  if ('opfs' in sqlite3) {
    db = new sqlite3.oo1.OpfsDb('/mydb.sqlite3');
    log('OPFS is available, created persisted database at', db.filename);
  } else {
    db = new sqlite3.oo1.DB('/mydb.sqlite3', 'ct');
    log('OPFS is not available, created transient database', db.filename);
  }
  try {
    log('Creating a table...');
    db.exec('CREATE TABLE IF NOT EXISTS t(a,b)');
    log('Insert some data using exec()...');
    for (let i = 20; i <= 25; ++i) {
      db.exec({
        sql: 'INSERT INTO t(a,b) VALUES (?,?)',
        bind: [i, i * 2],
      });
    }
    log('Query data with exec()...');
    db.exec({
      sql: 'SELECT a FROM t ORDER BY a LIMIT 3',
      callback: (row) => {
        log(row);
      },
    });
  } finally {
    db.close();
  }
};

log('Loading and initializing SQLite3 module...');
sqlite3InitModule({
  print: log,
  printErr: error,
}).then((sqlite3) => {
  log('Done initializing. Running demo...');
  try {
    start(sqlite3);
  } catch (err) {
    error(err.name, err.message);
  }
});

SQLite3 con SvelteKit

Finalmente en mi componente cargué al Worker.

Aquí quiero dejar claro que, por alguna razón, al hacer un new Worker, me decía que “Worker” no está definido (debe ser alguna cuestión de la arquitectura que desconozco porque apenas estoy iniciando) así que lo tuve que cargar con import dentro del onMount guiándome de https://medium.com/geekculture/sveltekit-web-worker-8cfc0c86abf6

let syncWorker = null;
const loadWorker = async () => {
  const SyncWorker = await import('../../worker?worker');
  syncWorker = new SyncWorker.default();
};

En este caso worker es el nombre del archivo. Los ../ son para referirme a la carpeta padre. Bien podría quedar así:

import('../../worker.js?worker')

Lo importante también es agregar el ?worker ya que el nombre del archivo puede ir sin la extensión. Y finalmente en el onMount:

onMount(() => {
  loadWorker();
  agregarElementoALista();
});

Conclusión

Obviamente faltan muchas cosas a partir de aquí, pero ya es el “hola mundo” de SQLite3 “nativamente” en la web, usando todo lo existente de esa librería. Podemos crear tablas, consultar con varias condiciones, crear índices, unir tablas y todo lo que podemos hacer con SQLite3.

A mí me agrada bastante porque al fin tenemos algo robusto para almacenar datos en el navegador, con todo lo que SQL ofrece. Ya no será necesario usar localStorage con JSON.stringify ni librerías como PouchDB (aunque claro, sirven bastante bien y no por eso valen menos).

Espero poder integrar y terminar todo esto para luego publicar un ejemplo con JavaScript puro.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *