Streams in Vite

I’m trying to use N3 to parse and query Turtle files in a Vite/Vue 3 project. The N3 readme says “N3.js seamlessly works in browsers via webpack or browserify” and I had trouble interpreting what that would mean in a Vite context. I made an attempt at properly learning all about bundling, but I did some trial and error in parallel, which in the end worked out fine:

First off, I had to add a shim for global in vite.config.js (thanks Richard Oliver Bray):

export default defineConfig({
   // ...
   define: {
     global: {},

Then I got problems when using N3.Store.match:

Uncaught TypeError: Cannot read properties of undefined (reading ‘call’)

The culprit was a line saying, i.e. Stream is what’s undefined. Here’s where I got lost installing and aliasing various browserified forks, before I just did:

yarn add events

and voilà!

N3 uses the Node.js Stream class. In a browser environment, it instead uses EventEmitter from the events library. So we just had to get that in place.

Sort empty strings last

You want to sort a list of strings in JavaScript, but some of the strings are empty, and you want those to come last. localeCompare is your friend, but it places empty strings first.

The solution is a bit of boolean sweetness:

["foo", "", "bar"].sort((a, b) =>
  a && b
    ? a.localeCompare(b)
    : !a - !b
// [ "bar", "foo", "" ]

For instance, if a is empty but b is not, the else case in the ternary resolves to true - false1 - 01 which means: put b before a.

Comma, comma & and

I recently had to concatenate author names with commas and an ampersand in the following manner:

commaAnd(['Emir Jong']);
// becomes: "Emir Jong"

commaAnd(['Kristian Josefsen', 'Tetyana Bohuňková']);
// becomes: "Kristian Josefsen & Tetyana Bohuňková"

commaAnd(['Luana Ferreira Carvalho', 'Jian Tu', 'Ambessa Afwerki']);
// becomes: "Luana Ferreira Carvalho, Jian Tu & Ambessa Afwerki"

Here are a few implementations in JavaScript:

function commaAnd(strs, comma = ", ", and = " & ") {
  const glue = (i) => ["", and, comma][Math.min(strs.length - i - 1, 2)];
  return strs.reduce((res, str, i) => res + str + glue(i), "");
function commaAnd(strs, comma = ", ", and = " & ") {
  return (
    ((s) => (s ? s + and : ""))(strs.slice(0, -1).join(comma)) + strs.slice(-1)
function commaAnd(strs, comma = ", ", and = " & ") {
  const init = strs.slice(0, -1).join(comma);
  return [init, strs.slice(-1)].filter((s) => s).join(and);


But then each author name needed a bit of markup as well. In Vue, we could concatenate HTML strings and use v-html, but we should avoid that if we can.

Here’s my method using slot scopes (much like that todo list example):

    <span v-for="(item, i) in items" :key="i"
      ><slot name="item" :item="item">{{ item }}</slot
      >{{ [null, and, comma][Math.min(items.length - i - 1, 2)] }}</span

export default {
  name: "CommaAnd",
  props: {
    items: { type: Array, default: Array },
    comma: { type: String, default: () => ", " },
    and: { type: String, default: () => " & " },

The usage is as follows:

<CommaAnd :items="authors">
  <template v-slot:item="{ item }">
    {{ fullName(item)
      affiliations.indexOf(item.affiliation) + 1

From the CommaAnd component, we call out to the parent component to define how to render each item. Inside the component, we only define what to do in between the items.

Note that much of the whitespace between elements and interpolations ({{ }}) must be eliminated when we are dealing with inline text. Furthermore, forcing HTML/XML into short lines is difficult (Prettier is doings its best here…) and the Vue syntax for slot scopes is a bit entangled, so the result is not super readable.

The resulting output is also pretty loud. Lots of <span>s in <span>s. If you don’t want that you should probably go with v-html anyway.

A cryptocurrency-mining bot brute-forced into my WordPress site

Someone brute-forced into a client’s WordPress site and added cryptomining js. Good thing they knocked down an HTML div on their way out!

Yesterday, at my parents’ cabin outside Stockholm, me and my father were having an office session after breakfast. Me procrastinating thesis work, him doing whatever he was doing. At one point he needed to check something on the website of FactWise, a company where he is involved.

The website didn’t appear as it should. The carousel in the footer was broken and the subsequent contact details bar had been pushed off to the side.

Broken design

The FactWise website is one of the first I’ve made, at least counting those which are still up and running today. Dad had a friend do the design, and I turned the PSDs into a WordPress theme and set it all up on shared hosting. It’s not beautiful but it works – kind of.

When dad showed me the buggy appearance, I was running out of other things to do instead of studying, so I started looking into it immediately. My first guess was that a stylesheet ref was broken. I looked through the Network tab in Firefox Developer Tools to find any 404s, but what I found was something else. Continue reading “A cryptocurrency-mining bot brute-forced into my WordPress site”