Development

CSS color filters & black

I ran into an issue. Code like this:

.link:hover {
  filter: brightness(1.5);
}

… worked perfectly on a colored icon but had no effect on a text label. It turned out that the label used the color #000. brightness() is a purely mathematical function that multiplies the R, G, and B values by the given factor. This makes no difference for pure black, since 0 * anything = 0.

Evict password managers

There are multiple password managers like 1pass, KeePass, and LastPass. Most of them have browser extensions. They are great. But they alter the page behavior, including HTML. Sometimes we need to prevent them from doing that.

Many years ago, people just did this:

<input autocomplete="off"

Now it doesn’t work. These extensions don’t respect this attribute. You might have seen this one:

<input data-lpignore="true"

It doesn’t work either (at least not with KeePass)

I came up with a simple and pretty silly solution:

<input type="text" onfocus="event.target.type='password'"

And it works ;-) The extensions don’t care about text-inputs. And they are not smart enough to detect new controls. At least for now.

upd: It helped with KeePass, but didn’t work out with 1Password.

New blog

The idea

In June, I started learning Rust. Why? Just out of curiosity. A system-level language with a lot of syntax sugar, new patterns, and approaches. So, I started with the Rust Book, got through half of it, and decided to build something real.

My previous blog was made with PHP 5.5 more than ~8 years ago. It wasn’t bad, but I completely quit PHP. I didn’t want to touch that old codebase. So I came to the conclusion: why not write a new one, this time in Rust?

Here it is. You’re welcome ;)

AI

I didn’t want to vibe-code it. However, I didn’t want to face all the obstacles by myself. So I used ChatGPT a lot, for almost every aspect of the development. I turned off Copilot and didn’t use Cursor. Because I wanted to write the blog “myself”. To feel it. At least in some merit. GTP was always in a separate browser tab. More like a helper, not an author.

Rust. First impression

Rust is wonderful, but it’s a system-level language. I had no real experience in this area before. Just a few simple C++ LeetCode problems, which gave me no real C++ knowledge. So it was a real challenge. But I wasn’t completely unarmed. I still remembered something about pointers, the heap, allocation, and other important system-level stuff. Though it was purely theoretical in my mind.

Read more

"Native code" label

How to detect if a function is a core part of browser API? This way:

  const a = () => {};
  a.toString(); // () => {}
  console.log.toString(); // function log() { [native code] }

You see? [native code] label. Sounds good, right?

  const a = () => {};
  const b = a.bind(null);
  b.toString(); // function () { [native code] }

Oops. But we still have this log name in the string snapshot.

Babel to EsBuild migration for Webpack

Recently I had some time to take a look at the performance of my pet project. One thing bothered me: the project is relatively small, but the bundle size was too big: 780 KiB. Whereas the project had only a few dependencies: react (~10 KiB), react-router (~70 KiB), react-dom (~90 KiB), dayjs (~3 KiB), lodash (~70 KiB). The bundle should not be that big for so few dependencies.

So I ran webpack-dev-analyzer and found a weird thing: the sum of the libs is 3 times smaller than the final bundle size. Then I decided to take a look at @statoscope/webpack-plugin. It was a good tool but it gave me the same result. The bundle has way too much trash.

Ok. Probably it’s just webpack wrappers over 300+ small modules. %cut% So I asked Perplexity - what can I do with it? Turned out all the proper options were already activated. So, I made a decision - it’s time to try esbuild. It made a huge impact on my work project, so I knew it could work out.

I knew that esbuild didn’t support a lot of stuff that babel supports. So I expected that it would take a day to migrate the codebase to esbuild. But surprisingly once I replaced babel-loader with esbuild-loader the build command worked out. The build didn’t work but the bundle was assembled without any errors.

Did it help? Nope. Instead of 780 KiB, I got ~1 MiB. Luckily I found out that there’s also EsbuildPlugin, which replaces TerserPlugin and works better. Added this line, it worked like a charm, and… Whoa! 280 KiB. That’s huuuge.

So I figured out - it worked. Now I have to find a way to fix the app (spoiler: it didn’t work at all).

Implicit React imports

React 17+ supports the possibility of avoiding adding import React from ‘react’ to every TSX-file. esbuild supports it too, but somehow it didn’t work out of the box. Solution:

  const esbuildLoader: RuleSetRule = {
    test: /\.(js|ts|tsx)$/,
    loader: 'esbuild-loader',
    exclude: /node_modules/,
    options: {
       target: 'es2020',
       minify: cfg.PROD,
+      // Support implicit React imports:
+      jsx: 'automatic',
+      jsxFactory: 'React.createElement',
+      jsxImportSource: 'react',
    } satisfies LoaderOptions,
  };

TSX-Control-Statements

I just removed the packages and all their <If/>s. I liked them though. There’s a plugin for Vite that adds their support. But I still use Webpack and the plugin works via string replacements, not via AST.

SourceMaps

To enable sourcemaps I had to disable EsbuildPlugin for development. Theoretically, it supports sourcemaps, but no matter what I did it didn’t work. I even debugged the package itself. Didn’t help. So I surrendered and enabled it only for the prod build.

TypeScript

EsBuild supports TypeScript syntax out of the box but it doesn’t check types. So it builds extremely fast but I need to know my errors. Solution:

  webpackConfig.plugins!.push(
    new ForkTsCheckerWebpackPlugin({
      typescript: {
        configFile: cfg.paths.TS_CONFIG,
      },
    })
  );

How does it work? The build and the type-checking processes are run in parallel. The 2nd doesn’t linger the 1st. You still can see the errors in the browser (and CLI) console. And visually too. Nice.

ES-Lint

For some reason, it stopped working. The solution was pretty simple:

  webpackConfig.plugins!.push(
    new ESLintPlugin({
      extensions: ['ts', 'tsx'],

Final results

  • File sizes
    • Before: 641 KiB + 234 KiB + 29.5 KiB (css) = 904.5 KiB
    • After: 221 KiB + 55 KiB + 8.1 KiB (o_O, css) = 284.1 KiB
    • After minifyIdentifiers: true (scary thing) & minifyWhitespace: true: 138 KiB + 44.2 KiB + 8.1 KiB = 190.1 KiB
  • DOMContentLoaded:
    • Before: 737ms
    • After: 231ms
    • After-2: 131ms
  • Build time: esbuild was ~3x faster

Afterthought

Taking a deeper look I found:

  • The main issue wasn’t in babel. The main issue was that I used inline-source-map for prod. I didn’t want to do it. Just a mistake. And both bundle analyzers didn’t notice that. Even when I manually looked at the file I barely found it (the horisontal scroll was too big). Fixing this issue dropped the file size 3x times even with babel.
  • esbuild has a better wrapper logic for a ton of tiny files than babel does.
  • Whitespace minification is quite important. GZip doesn’t eliminate the difference.
  • Name mangling is a beast. But be careful, it can easily break your app in runtime.
Read more