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

NIH syndrome

Not invented here (NIH) is the tendency to avoid using or buying products, research, standards, or knowledge from external origins. It is usually adopted by social, corporate, or institutional cultures. Research illustrates a strong bias against ideas from the outside.

Wikipedia