Today I learned...

2024-07-25


Typescript generics and indexed access types
I was writing a wrapper for creating SVG filter elements before I realized that I should just use the HTML elements instead (in Svelte that is).
I did learn something though, when you want to type something where the available parameters change depending on something (in my case a light type) you can use typescript generics and indexed access types.
Some light types:
export type LightTypes = {
  feDistantLight: FeDistantLightAttributes;
  fePointLight: FePointLightAttributes;
  feSpotLight: FeSpotLightAttributes;
};
Some example attributes:
export type FeDiffuseLightingAttributes = {
	in?: inType;
	diffuseConstant?: number;
	surfaceScale?: number;
	"lighting-color"?: string;
	result?: string;
};

export type FeDistantLightAttributes = {
	azimuth?: number;
	elevation?: number;
};
The function:
  feDiffuseLightingWithLight<K extends keyof LightTypes>(attrs: FeDiffuseLightingAttributes, type: K, lightAttrs: LightTypes[K]) {
    let elem = this.addElement("feDiffuseLighting", attrs) as SVGFilterElement;

    this.addElement(type, lightAttrs, elem);
    return this;
  }
This way when I call it with .feDiffuseLightingWithLight({ "lighting-color": "#4a7997", result: "textured" }, "feDistantLight", { azimuth: 90, elevation: 50 }) I can only see the azimuth and elevation properties within the object since I passed in "feDistantLight". Properties from other types of light will give a typescript error.

2024-07-23


Stubbing and linking packages for easier development
When using packages from (in particular) the Vue ecosystem it is likely that they use unbuild. Sometimes you can see in the package.json something like "dev": "unbuild --stub".
You can run this to build the package in stub mode which basically allows it to run using jiti. This allows you to edit your code and not have to rebuild and also doesn’t need you to run any watch process either.
You can then link the package to test it in an actual project. The syntax differs a bit depending on the package manager you use. For npm it’s npm link in the source package and npm link <packagename> in the consuming project. For bun it’s bun link in the source package and you get some instructions in the console.
Restart task using runCommands
It is possible to restart a task using multicommand in VSCode. First, with a user task:
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Install dependencies and start dev",
      "type": "shell",
      "command": "ni && d",
      "runOptions": { "runOn": "folderOpen" },
      "group": {
        "kind": "none",
        "isDefault": true
      },
      "presentation": {
        "reveal": "always",
        "panel": "shared"
      }
    },
  ]
}
This task runs ni and d which uses ni to install dependencies and then d which is an alias for nr dev that runs dev inside package.json. For a guide on how to add aliases see this.
This task runs when opening a folder. Sometimes you may want to rerun this task, for example after changing a package version in package.json and you want to install the new package.
In VSCode open the command palette Preferences: Open keyboard shortcuts (JSON) and paste this:
  {
    "key": "f8",
    "command": "runCommands",
    "args": {
      "commands": [
        {
          "command": "workbench.action.tasks.terminate",
          "args": "terminateAll"
        },
        {
          "command": "workbench.action.tasks.runTask",
          "args": "Install dependencies and start dev"
        }
      ]
    }
  }
Here I chose f8 to run two commands using runCommands: Terminating all tasks first, then running the specified task. It’s important that the args field has the correct name for the task that you specified in the task label.
When that is done you can press F8 at any time to close all tasks, and then run the task that installs dependencies and starts the dev server.

2024-04-22


Typescript Record<>
In Typescript there is apparently something called Record<> which allows you to create an object type without using {}.
For example:
Record<string, Pattern>
is the same as
{ [key: string]: Pattern }
but imo looks a bit more readable. 😊

2024-04-17


queueMicrotask()
I had an issue in Svelte 5 where I tried to use .focus() but it only worked in SSR, not when SSR was disabled.
const patternSelector = document.querySelector<HTMLButtonElement>("#nejime button")!;
patternSelector.focus();
Dominic from the Svelte team explained that SvelteKit tries to restore focus and this conflicted with the focus() I was trying to execute. To fix this I could use queueMicrotask() which makes sure my code runs after SvelteKit is done.
const patternSelector = document.querySelector<HTMLButtonElement>("#nejime button")!;
queueMicrotask(() => {
  patternSelector.focus();
});
Thanks Dominic! 😊

2023-11-30


has in Tailwind and UnoCSS
It’s possible to use has: in Tailwind/UnoCSS (instead of group/peer)
<div class="has-[button:active]:bg-red-500 has-[p:hover]:bg-pink-500">
  <button>I'm active</button>
  <p>more text</p>
</div>

more text

2023-11-27


Array buffer encoding, decoding to strings
I had a problem where I couldn’t return an array buffer (binary data) in a SvelteKit load function. You can only return POJOs. The solution is simple though - stringify when encoding and parse when decoding!
// Load function
const stringifiedImage = JSON.stringify(Array.from(new Uint8Array(image)));

return {
  stringifiedImage
}
// +page.svelte

export let data;
const parsedImage = JSON.parse(data.stringifiedImage);
Easy!
React Native (expo) liveshare using tunnel
I had an issue when pair programming when we couldn’t use the shared server created in Liveshare, it was stuck in a loading state.
The solution was simple: running expo with expo start --tunnel creates a tunnel using Ngrok that gave us a link that we could connect to. That way we can pair program while seeing the code on both of our mobiles/emulators. Nice!

2023-11-15


Function call and bind
There are two interesting methods: .call() and .bind()
Using function.call() it’s possible to specify what the this value will be.
There’s also a more modern function.bind() which returns a new function. This one also has a this that can be specified.
You can also do function.bind(function, alreadySetValue) for partial functions with pre specified arguments.
Vitest - pass props to render
It’s possible to pass props to render if the component you’re testing needs them.
const { container } = render(Layout, {
  props: {
    data,
  },
});
In this case I’m passing load function data to my layout route.
Vitest - test load function data
It’s possible to test a load function to Vitest in case you want to have an integration test for your server/data.
Simply import load (named) from the file and then do  const data = await load({ fetch }) and you’ll be able to test the data.

2023-10-27


Cache busting
It is possible to prevent browsers from using cached (read outdated) files by using a technique called cache busting.
In Webpack this can be done by adding a hash to the file names. In Vite this can be done by importing images instead of having them as static files, this way Vite will hash the images automatically. Nice!
Debugging in browser
It’s possible to debug using the browser devtools. Simply set the project environment to development and open the sources tab where there will be a source folder in which you can open your files and add breakpoints.
If necessary, enable source maps in the framework.
Webpack config per environment
It’s possible to have multiple webpack config files, for example one for dev and one for prod. After creating the files they can be used with:
--config webpack.prod.js
which can be added to the scripts in package.json.

2023-10-24


Using custom animations in UnoCSS
You can quite easily add custom animations in UnoCSS. In uno.config.js add this:
theme: {
    animation: {
      keyframes: {
        "in-out-custom":
          "{from,60%,75%,90%,to{animation-timing-function:cubic-bezier(0.215,0.61,0.355,1)}0%{opacity:0;transform:translate3d(0,-1500px,0)}60%{opacity:1;transform:translate3d(0,25px,0)}75%{transform:translate3d(0,-10px,0)}90%{transform:translate3d(0,5px,0)}to{transform:translate3d(0,0,0)}}",
      },
      durations: {
        "in-out-custom": "1s",
      },
      timingFns: {
        "in-out-custom": "ease-in-out",
      },
      counts: {
        "in-out-custom": "infinite",
      },
    },

  },
After that you can use it with animate-in-out-custom. After tweaking the animation and you’re happy with it you can set the counts value to 1 if it should only animate once.
It is also possible to use arbitrary values for the animation if you do something like this: class: "animate-[in-out-custom_2s_ease-in-out_infinite] keyframes-in-out-custom"

2023-10-21


2023-10-20


Typescript - generics
In Typescript there’s something called generics that allows you to write functions or classes that can defined with a generic type parameter (placeholder) that will be replaced by the actual type that’s passed into the function.
This allows us to write more flexible functions and up with less duplicated code.

2023-10-19


Classes - Public, private, protected
In classes you can have public, private or protected properties/methods.
Public means that it is public, you can edit it from outside the class.
Private means that it is private, you can’t edit it from outside the class.
Protected means that it is protected, you can’t edit it from outside the class except if it’s in a derived sub class.
Normally you add underscores in front of private/protected properties and use getters/setters to interact with them instead.
Four pillars of OOP
There are four pillars of OOP:
  1. Data Hiding: Concept: Protecting data from direct external access. In JavaScript: Use closures or private variables to limit access to specific properties.
  2. Inheritance: Concept: Creating new objects based on existing ones. In JavaScript: Implement inheritance through prototype chains or ES6 class inheritance.
  3. Polymorphism: Concept: Treating different objects as if they’re of a common type. In JavaScript: Achieve polymorphism by method overriding or function overloading.
  4. Encapsulation: Concept: Bundling data and methods into a single unit (class). In JavaScript: Use object literals or class definitions to group related data and functions for clean and modular code.
Thanks chatgpt! 😇
Getting value from input using Typescript without intermediate variable
It is possible to get a value directly from an input element without using an intermediate variable.
First in the event arguments add e: Event. Then simply do const value = (<HTMLInputElement>e.target).value which will cast the target as an input element (or whatever you want) that allows you to do .value. Easy!

2023-10-18


Typescript project conversion
I converted a project to Typescript (not perfectly but 0 errors at least) and found out some interesting things!

Async return type

For normal functions you could do something like
function func(): string {}
but if it’s async you need to surround the type with Promise<>
async function func(): Promise<string> {}

Inline type annotation for object

I had an object as a parameter and had to do something like this:
export function editMode({ selectedList, listItemsUl, API_BASE, headerName }: {selectedList: List, listItemsUl: HTMLUListElement, API_BASE: string, headerName: HTMLDivElement })
If you’re feeling less silly you could do this:
interface EditModeParams { 
selectedList: List; 
listItemsUl: HTMLUListElement; 
API_BASE: string; 
headerName: HTMLDivElement; } 
export function editMode(params: EditModeParams) {}
I also had to do this but using as, for example
itemListArray.push({ title: listItemInput.value, checked: false } as {title: string, checked: boolean});

Type cast event targets

I found that I had to cast the targets as actual elements to use things like .value properly.
let currentTarget = e.target as HTMLInputElement;

Inlay hints

Kind of wish I had this enabled before converting everything but there is a feature in VSCode which inlays the inferred types inline so you can see if they match with the actual types you want!
To enable them search for typescript inlay in Settings and enable the ones you want. I enabled everything 😇
You can also configure the color with
"editorInlayHint.background": "red",
"editorInlayHint.foreground": "black"

cssText

Apparently .style doesn’t work in Typescript so you have to type .style.cssText instead!

2023-10-17


Typescript type casting
Typescript has something called type casting which can be used if you want a more specific type.
For example
const myForm = document.querySelector("#my-form");
would give an Element type but maybe we want a more specific type. We could do type casting:
const myForm = document.querySelector("#my-form") as HTMLFormElement;
If we do this it’s more apparent that myForm really is a HTML form element.
We can do the same thing with the e in an event callback function which by default has an any type. By doing this instead e: UIEvent we make it obvious that we’re working with some kind of UI event.
Typescript - variables
Explicit type annotation:
let movieTitle: string;
let movieLength: number;
let isCategoryAction: boolean;
Implicit type annotation (type inference):
let tvSerie = "La Brea";
let isDrama = true;
Any type Should not be used unless necessary
let director: any = "Cristopher Nolan";
director = 10;
director = true;
director();

2023-10-16


Using UnoCSS with Tailwind extension
I really like UnoCSS but prefer the Tailwind extension because I find it has better intellisense and a bit better performance. Here’s how to use the Tailwind extension together with UnoCSS:
  1. Install the Tailwind extension in VSCode.
  2. Install the Tailwind package as a dev dependency. npm i --save-dev tailwindcss note that we’re not really using it, this is just for the extension to start.
  3. Add a tailwind.config.js (or .ts) file:
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{html,js,svelte}"],
};
That’s it!

2023-10-15


Svelte in markdown
Since I’m using mdsvex I can also use Svelte code in these posts.
Following taken from mdsvex try page:

Good stuff in your markdown

Markdown is pretty good but sometimes you just need more.
Sometimes you need a boinger like this:
Not many people have a boinger right in their markdown.
pretty cool!
Test snapshots should be in alphabetical order
I submitted a PR and was surprised (not really) when CI failed, it turns out snapshots should be in alphabetical order.
…makes sense!
I also learned that you shouldn’t really type the snapshots by hand, it’s easier to add a test, run it, see what comes in (vs what the snapshot was before), if it was the correct thing just copy paste the snapshot. And place it in alphabetical order. 😊

2023-10-14


You can get your metadata from mdsvex by simply doing
// ... Vite glob import above
for (let path in modules) {
  const post = await modules[path]();
  const metadata = post.metadata;
Handy if you want to access title/tags or whatever in your SvelteKit load functions!
Using/adding changesets
I was asked to add a changeset when submitting a PR. There was a nice guide here: changesets/docs/adding-a-changeset.md at main · changesets/changesets (github.com)
  1. Run npx changeset
  2. Select the packages using arrows and space to select, enter to confirm. (in monorepo)
  3. Select what kind of bump you want (major, minor or patch).
  4. Provide a message. Optional, add markdown to make it look nicer.
  5. Commit!
npm link (using your forks as packages)
I wanted to make a change in a package and then use the package in my other project to see if my change fixed a bug.
I was wondering how to do that but it was pretty easy:
  1. In the forked project, run npm link.
  2. In the other project, run npm link forked-package-name-here.
  3. Now you can use your forked package as a dependency in your other project!
I also think you need to run npm unlink at some point.
Wider!
I want a really wide day so I can test my CSS… 😇
Even wider!
Let’s break the layout! 😇
And uh… let’s fix it later.

2023-10-13


2023-10-12


2023-10-11


In Linux file casing matters
Unlike on Windows or Mac, in Linux file casing matters. You can have something that builds fine locally using for example Vite but have it break when deployed if using a workflow that builds using a Linux machine.
There is no DOM in React Native
Instead there are elements like <Box>.
+page.server.js instead of +page.js can give errors
When used with mdsvex using a server only rendered file causes file can cause passing imports as page data to fail.