Pilot pens

As a guy who likes notebooks, I never bothered about pens that I use. Until a few weeks ago, when I needed to quickly write down something in my Moleskine Cahier, and a pen I was using didn't want to write despite it was full of ink. I decided to buy a better pen, and bought a pack of Pilot PBS-GP-F-L pens.

And they're just great. Write fine and always, always very good.


Using make in frontend projects

Summary: Using make can greatly simplify the maintainance of your project. If any command requires more than a few words to type, consider adding it to a Makefile.

It’s common to use package.json scripts to run commands in node-based projects, for example, npm run dev.

Recently, I came across several articles discussing the use of make and decided to give it a try.

As a result, I’ve started using make in all of my frontend projects, and I really like it. Here’s a simplified example of a Makefile for one of my work projects:

devpod: # start a devcontainer and ssh into it
	devpod up .
	devpod ssh lmst
dev: # run in a dev mode
	npx vite
type:
	npx tsc --noEmit
size:
	npx vite build
	npx size-limit
test:
	npx tap

I think it’s better than scripts in a package.json file, because of:

  • Multi-line commands.
  • Comments: each line can be commented. You can describe why you run one command before another. In a package.json file, all your commands are placed on a single line
  • Works great with non-Node.js commands. In one of our projects, we use Selenium for integration tests, and I no longer struggling to remember how to install all dependencies and run tests

For development I use devcontainers + devpod setup, and connecting to a devcontainer is pretty much to type:

devpod up .
devpod ssh projname

Now I’m just typing make devpod, and after a few seconds I’m inside a devcontainer.

In a team, this approach will work only if each of team’s members uses a platform with a make available. On linux and Mac, it’s already included. On a Windows system you need to install make by yourself.

A small tutorial on how to start using “make”

It’s pretty easy to get started with make -just create a Makefile in the project’s root directory.

Makefile consists of targets. For example, in the make build command, build is a target.

Let’s add a type target, so we’ll be able to use make type to run typescript checks in our project:

type:
	npx tsc --noEmit

Nice! To build our project, we’ll use build target. Typically, we want to check types and then build the project. For this, we can use dependencies:

type:
	npx tsc --noEmit

build: type
	npx vite build

Now, if we run make build, actually these commands will be executed:

npx tsc --noEmit
npx vite build

Variables

Some commands differ by just one flag, such as running typescript type checks mentioned above either single time or in watch mode:

npx tsc --noEmit
npx tsc --noEmit --watch

We can duplicate these commands:

type:
	npx tsc --noEmit
type-watch:
	npx tsc --noEmit --watch

Or we can use variables:

type:
        npx tsc --noEmit $(WATCH)

type-watch: WATCH = --watch
type-watch: type

Here, the type-watch target just sets a WATCH variable, and re-uses type target.

A “docs” Pitfall

If you run a target and there’s a file or directory with the same name exists, make will think that this target is up to date and won’t do anything!

A classic example here is a docs target - most probably you would like to run make docs to generate the documentation, but if your documentation is placed in the docs directory, you’ll see this message:

make: 'docs' target is up to date.

To always run the specified target in such scenarios, we need to use a special .PHONY target:

.PHONY: docs

docs:
	npx vitepress

Links

Movies list, part I

This is the list of movies I’ve watched and liked. I’m starting it right now (2025-03-10).

Paprika

Paprika

I didn’t understand it, but it was very interesting.

Pantheon

Pantheon

This is the great one! I had been under impression from it for a few days after watching.

Долгая ночь

Плохие дети

The day of the Jackal (2024)

The day of the Jackal

The day of the Jackal (1973)

I read the book when I was a teenager and decided to watch 1973 version before starting the 2024 series.

The day of the Jackal 1973

Longlegs

This one is strange. I should have dropped it for sure, but there was something hypnotizing that kept me watch until the end.

Longlegs

How to declare a type for 'onClick'-like events in Typescript

In this article, I’ll tell you how to create a type for all HTML elements’ events, like onClickonDragstart, etc.

Suppose you want to create a wrapper component around some div tag. And you want to accept event handlers as component properties, like this:

const component = CreateMyComponent({
  onClick: (e) => { ...},
  onDragstart: () => {...},
})

For this, we need two things: types for html element events and template literal types.

HtmlElementEventMap

This is the type that maps event names to their callback types.

How I’ve found it? I searched for “typescript dom event types”, and the second link redirected me to the DOM manipulation page in the typescript documentation. There was a link to DOM type definitions, where I searched for string “click”.

interface HTMLElementEventMap extends ElementEventMap, GlobalEventHandlersEventMap {
}

The most interesting part lies in the GlobalEventHandlersEventMap type (the full source of DOM-related types):

interface GlobalEventHandlersEventMap {
    "abort": UIEvent;
    "animationcancel": AnimationEvent;
    "animationend": AnimationEvent;
    "animationiteration": AnimationEvent;
    "animationstart": AnimationEvent;
    "auxclick": MouseEvent;
    "beforeinput": InputEvent;
    "beforetoggle": Event;
    "blur": FocusEvent;
    "cancel": Event;
    "canplay": Event;
    "canplaythrough": Event;
    "change": Event;
    "click": MouseEvent;
    "close": Event;
    "compositionend": CompositionEvent;
    "compositionstart": CompositionEvent;
    "compositionupdate": CompositionEvent;
    "contextlost": Event;
    "contextmenu": MouseEvent;
    "contextrestored": Event;
    "copy": ClipboardEvent;
    "cuechange": Event;
    "cut": ClipboardEvent;
    "dblclick": MouseEvent;
    "drag": DragEvent;
    "dragend": DragEvent;
    "dragenter": DragEvent;
    "dragleave": DragEvent;
    "dragover": DragEvent;
    "dragstart": DragEvent;
    "drop": DragEvent;
    "durationchange": Event;
    "emptied": Event;
    "ended": Event;
    "error": ErrorEvent;
    "focus": FocusEvent;
    "focusin": FocusEvent;
    "focusout": FocusEvent;
    "formdata": FormDataEvent;
    "gotpointercapture": PointerEvent;
    "input": Event;
    "invalid": Event;
    "keydown": KeyboardEvent;
    "keypress": KeyboardEvent;
    "keyup": KeyboardEvent;
    "load": Event;
    "loadeddata": Event;
    "loadedmetadata": Event;
    "loadstart": Event;
    "lostpointercapture": PointerEvent;
    "mousedown": MouseEvent;
    "mouseenter": MouseEvent;
    "mouseleave": MouseEvent;
    "mousemove": MouseEvent;
    "mouseout": MouseEvent;
    "mouseover": MouseEvent;
    "mouseup": MouseEvent;
    "paste": ClipboardEvent;
    "pause": Event;
    "play": Event;
    "playing": Event;
    "pointercancel": PointerEvent;
    "pointerdown": PointerEvent;
    "pointerenter": PointerEvent;
    "pointerleave": PointerEvent;
    "pointermove": PointerEvent;
    "pointerout": PointerEvent;
    "pointerover": PointerEvent;
    "pointerup": PointerEvent;
    "progress": ProgressEvent;
    "ratechange": Event;
    "reset": Event;
    "resize": UIEvent;
    "scroll": Event;
    "scrollend": Event;
    "securitypolicyviolation": SecurityPolicyViolationEvent;
    "seeked": Event;
    "seeking": Event;
    "select": Event;
    "selectionchange": Event;
    "selectstart": Event;
    "slotchange": Event;
    "stalled": Event;
    "submit": SubmitEvent;
    "suspend": Event;
    "timeupdate": Event;
    "toggle": Event;
    "touchcancel": TouchEvent;
    "touchend": TouchEvent;
    "touchmove": TouchEvent;
    "touchstart": TouchEvent;
    "transitioncancel": TransitionEvent;
    "transitionend": TransitionEvent;
    "transitionrun": TransitionEvent;
    "transitionstart": TransitionEvent;
    "volumechange": Event;
    "waiting": Event;
    "webkitanimationend": Event;
    "webkitanimationiteration": Event;
    "webkitanimationstart": Event;
    "webkittransitionend": Event;
    "wheel": WheelEvent;
}

Template literal types

Template literal types allow us to use string interpolation in type declarations.

For example, this is how we can create a onClick literal type:

type DomEvent = 'Click'

// type DomEventHandlerName = 'onClick'
type DomEventHandlerName = `on${DomEvent}`

Implementation

All we need now is to loop over all events and create a on type for each of them:

export type HTMLEventHandler = {
  [K in keyof HTMLElementEventMap as `on${Capitalize<K>}`]? : (evt: HTMLElementEventMap[K]) => void
}

We used Capitalize helper type, which capitalizes the first character in a string.

Create a tooltip with CSS

Recently I was needed to add a quick tooltip to a span element in a project with vuetify and vue2. My first try was to use vuetify’s v-tooltip, but, you know, it didn’t work after 3 minutes of copy-pasting code samples from docs.

So I just googled something like “HTML native tooltip”, and found an example of a tooltip on stackoverflow, which impressed me with its small amount of code and nice results.

Demo

Hover me

The solution is to use data-attribute and attr() function:

[data-tooltip]:hover::after {
  display: block;
  position: absolute;
  content: attr(data-tooltip);
  border: 1px solid black;
  background: #eee;
  padding: .25em;
}
<div data-tooltip="Hello, World!">Hello, World!</div>

Often CSS-only solutions look very tricky, but this one is very good, I like it.

More info