Croissant is Coming for You



This content originally appeared on dbushell.com (blog) and was authored by dbushell.com (blog)

Last week I documented Croissant development in a lengthy tome that should have been a series. I’m also writing a series of dev notes. I suppose the main blog is a series now because this is part two. My gift of a Googly eyes CodePen was a success. I have no gift this week.

Here’s a boring screenshot.

Croissant as a native macOS app

Inspired UI design and copywriting if I dare say so myself.

Tauri MacOS App

Croissant is a progressive web app and will be hosted as a website eventually. I have not abandoned those principles!

iOS and macOS allow PWAs to be “installed” albeit with questionable enthusiasm from Apple. I’ve been side-questing around Tauri 2.0 this week in pursuit of a better PWA experience. Tauri packages a native app out of a website with minimal fuss.

Tauri required 16.1 MB of node_modules to generate 16.9 MB of Croissant.app. The web assets are only 250 KBs. That’s like 98.5% overhead.

Out of curiosity I followed the Electron tutorial to create the most basic Electron app. Electron required 347.2 MB of node_modules to produce a 264.4 MB macOS app. The major difference between Tauri and Electron is that Electron ships embedded Chromium and Node.js whereas Tauri uses the native web view provided by the OS.

Tauri’s homepage claims:

By using the OS’s native web renderer, the size of a Tauri app can be little as 600KB.

Naturally they refuse to elaborate on that 600 KB claim.

I checked the App Size docs and copy-pasted config. The freshly compiled Croissant.app shrunk to 6.6 MB. I should read more of this documentation. A 6.6 MB native macOS app that wraps my PWA is smaller than a website like nextjs.org (to select a random example). And my app works unlike nextjs.org.

Persistant Data

Croissant uses IndexedDB to store RSS feed data. I’m using Dexie for now to help.

Safari is known for the odd IndexedDB bug but I believe it is stable now. Safari has (or had?) a 7-day policy on purging storage. How this affects PWAs is confusing:

Web applications added to the home screen are not part of Safari and thus have their own counter of days of use. Their days of use will match actual use of the web application which resets the timer. We do not expect the first-party in such a web application to have its website data deleted.

I’m struggling to find a concrete answer so please @ me if you know the current state of Safari storage.

Anyway, I was concerned about how Tauri apps stored IndexedDB data, and if it persisted across updates. On macOS I found data in the following directory.

/Users/dbushell/Library/WebKit/com.dbushell.croissant

Inside that location there is an empty WebsiteData/IndexedDB directory. Curious. Spelunking deeper into randomly named directories I found what I was looking for.

Size  Date Modified  Name
164M  18 Jul 07:19   IndexedDB.sqlite3
33k   18 Jul 07:12   IndexedDB.sqlite3-shm
29k   18 Jul 07:19   IndexedDB.sqlite3-wal

It was SQLite all along! This is not surprising, Apple love a good SQLite database. It’s sad Apple get all the fun and we’re stuck with IndexedDB. Web SQL needs a comeback. I did consider SQL Wasm; it’s not worth the cost.

This database persists across app updates. It probably hangs around after the app is deleted. That said, some Tauri devs have had issues:

General recommendation is to store your app data separately and not rely on IndexedDB as it may change again in a future major version of tauri.

Issue #11252 — @amrbashir (Tauri developer)

I’m very reluctant to use Tauri’s store plugin. It’s effectively proprietary for my purposes. I already have a cross-platform database. I’ll stick to using IndexedDB for now. I have import/export functionality. I could always write a migration script.

Tauri Plugins

Speaking of Tauri plugins, one does not simply open an external hyperlink from a Tauri app.

meme of Boromir from Lord of the Rings quoted as saying 'One does not simply open a hyperlink'

Hyperlinks are kind of a big deal in RSS feeds. Some feeds are nothing but links. Whilst Tauri is a web view it is not a web browser. There is a special Opener plugin for that. I have to detect external clicks. I do this conditionally because Tauri is not my only target.

if ("__TAURI__" in globalThis) {
  globalThis.addEventListener('click', (ev) => {
    const link = ev.target?.closest('a[href][target="_blank"]');
    if (link) {
      globalThis.__TAURI__.opener.openUrl(link.href);
    }
  });
}

The Croissant website itself is “no build” (a deliberate choice) which means I have to use the awkward __TAURI__ global. Were I to acquiesce to a build toolchain I could import.

import { openUrl } from '@tauri-apps/plugin-opener';

Why they can’t expose a fake module like the global object I don’t know.

The Tauri HTTP Client plugin solved my proxy server dependency to bypass CORS issues. Previously an early experiment led to a 88.8 MB app including a Deno sidebar binary. Reducing that to 6.6 MB has made me very happy.

Annoyingly, the opener plugin will not open the blob: URLs I generate to export data and prompt a download. For that I had to combine the save dialog and file system write plugins. This added another conditional branch.

const path = await globalThis.__TAURI__.dialog.save({
  defaultPath: "croissant.json",
  filters: [{
      name: "croissant.json",
      extensions: ["json"],
    },
  ],
});
if (path) {
  await globalThis.__TAURI__.fs.writeTextFile(
    path,
    JSON.stringify(data)
  );
}

It’s not much code but it adds complexity. I’m in danger of turning my web app into a Tauri app. I’ve set myself the restriction of not implementing anything in Tauri that I cannot do on a website. And it’s pretty much feature complete now.

Release Date?

If you would like to use the Croissant macOS app, or a hosted web version, please get in touch. It might be September before it’s ready because my August is busy.

Theoretically I could cross compile for Windows and Linux but I have no desire to do that right now. @ me if you’re interested.

Once the app design and functionality is stable I’ll consider how I plan to distribute it, host it, open source it, sell it? I will do another blog post expanding on my design principles for Croissant. It’s super minimal. If you like “features” prepare to be disappointed!


This content originally appeared on dbushell.com (blog) and was authored by dbushell.com (blog)