Filtered by JavaScript

Reset

## Comparing Deno vs Node vs Bun

### August 5, 20240 commentsBun, JavaScript

This is an unscientific comparison update from previous blog posts that compared Node and Bun, but didn't compare with Deno.

### Temperature conversion

From Converting Celsius to Fahrenheit round-up it compared a super simple script that just prints a couple of lines of text after some basic computation. If you include Deno on that run you get:

``````
❯ hyperfine --shell=none --warmup 3 "bun run conversion.js" "node conversion.js" "deno run conversion.js"
Benchmark 1: bun run conversion.js
Time (mean ± σ):      22.2 ms ±   2.1 ms    [User: 12.4 ms, System: 8.6 ms]
Range (min … max):    20.6 ms …  36.0 ms    136 runs

Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.

...

Summary
bun run conversion.js ran
1.97 ± 0.35 times faster than deno run conversion.js
2.41 ± 0.39 times faster than node conversion.js
``````

Note that `bun run` and `deno run` both support `.ts` files whereas Node needs it to be `.js` (unless you use something like `--require @swc-node/register`). So in this benchmark, I let `bun run` and `deno run` use the `.js` version.

``````
❯ deno --version
deno 1.45.2 (release, x86_64-apple-darwin)
v8 12.7.224.12
typescript 5.5.2

❯ node --version
v20.14.0

❯ bun --version
1.1.21
``````

### Leibniz formula

In Leibniz formula for π in Python, JavaScript, and Ruby I wrote a simple program that computes the value of π using the Leibniz formula. It became a comparison of that code implementation in Python vs. Ruby vs. Node.

But let's redo the test with Bun and Deno too. Code was

``````
let sum = 0;
let estimate = 0;
let i = 0;
const epsilon = 0.0001;

while (Math.abs(estimate - Math.PI) > epsilon) {
sum += (-1) ** i / (2 * i + 1);
estimate = sum * 4;
i += 1;
}
console.log(
`After \${i} iterations, the estimate is \${estimate} and the real pi is \${Math.PI} ` +
`(difference of \${Math.abs(estimate - Math.PI)})`
);
``````

Running it once, it prints:

``````
❯ deno run pi.js
After 10000 iterations, the estimate is 3.1414926535900345 and the real pi is 3.141592653589793 (difference of 0.0000999999997586265)
``````

Running them becomes more of a measurement of how fast the programs start rather than how fast they run, but it's nevertheless and interesting to know too:

``````
❯ hyperfine --warmup 3 "node pi.js" "bun run pi.js" "deno run pi.js"
Benchmark 1: node pi.js
Time (mean ± σ):      54.9 ms ±   6.5 ms    [User: 42.6 ms, System: 11.3 ms]
Range (min … max):    50.2 ms …  83.9 ms    48 runs

Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.

...

Summary
bun run pi.js ran
1.92 ± 1.01 times faster than deno run pi.js
2.37 ± 0.31 times faster than node pi.js
``````

### Conclusion

Both of these programs that I'm comparing with are super trivial and take virtually no time to run, once they've started. So it becomes more a test of warm-start performance. Alas, it's cool to see that both Deno and Bun make a better job of it here. Bun is almost 2x faster than Deno and 2.5x faster than Node.

## Converting Celsius to Fahrenheit round-up

### July 22, 20240 commentsGo, Node, Python, Bun, Ruby, Rust, JavaScript

In the last couple of days, I've created variations of a simple algorithm to demonstrate how Celcius and Fahrenheit seem to relate to each other if you "mirror the number".
It wasn't supposed to be about the programming language. Still, I used Python in the first one and I noticed that since the code is simple, it could be fun to write variants of it in other languages.

It was a fun exercise.

And speaking of fun, I couldn't help but to throw in a benchmark using `hyperfine` that measures, essentially, how fast these CLIs can start up. The results look like this:

``````
Summary
./conversion-rs ran
1.31 ± 1.30 times faster than ./conversion-go
1.88 ± 1.33 times faster than ./conversion-cr
7.15 ± 4.64 times faster than bun run conversion.ts
14.27 ± 9.48 times faster than python3.12 conversion.py
18.10 ± 12.35 times faster than node conversion.js
67.75 ± 43.80 times faster than ruby conversion.rb
``````

It doesn't prove much, that you didn't expect. But it's fun to see how fast Python 3.12 has become at starting up.

Head on over to https://github.com/peterbe/temperature-conversion to play along. Perhaps you can see some easy optimizations (speed and style).

## Node watch mode and TypeScript

### July 21, 20240 commentsNode, JavaScript

You might have heard that Node now has watch mode. It watches the files you're saving and re-runs the `node` command automatically. Example:

``````
// example.js

function c2f(c) {
return (c * 9) / 5 + 32;
}
console.log(c2f(0));
``````

Now, run it like this:

```❯ node --watch example.js
32
Completed running 'example.js'```

Edit that `example.js` and the terminal will look like this:

```Restarting 'example.js'
32
Completed running 'example.js'```

(even if the file didn't change. I.e. you just hit Cmd-S to save)

Now, `node` doesn't understand TypeScript natively, yet. So what are you to do: Use `@swc-node/register`! (see npmjs here)
You'll need to have a `package.json` already or else use globally installed versions.

Example, using `npm`:

``````
npm init -y
npm install -D typescript @swc-node/register
npx tsc --init
``````

Now, using:

``````
// example.ts

function c2f(c: number) {
return (c * 9) / 5 + 32;
}
console.log(c2f(123));
``````

You can run it like this:

``````
❯ node --watch --require @swc-node/register example.ts
253.4
Completed running 'example.ts'
``````

## Converting Celsius to Fahrenheit with TypeScript

### July 16, 20240 commentsBun, JavaScript

This is a continuation of Converting Celsius to Fahrenheit with Python, but in TypeScript:

``````
function c2f(c: number): number {
return (c * 9) / 5 + 32;
}

function isMirror(a: number, b: number) {
function massage(n: number) {
if (n < 10) return `0\${n}`;
else if (n >= 100) return massage(n - 100);
return `\${n}`;
}
return reverseString(massage(a)) === massage(b);
}

function reverseString(str: string) {
return str.split("").reverse().join("");
}

function printConversion(c: number, f: number) {
console.log(`\${c}°C ~= \${f}°F`);
}

for (let c = 4; c < 100; c += 12) {
const f = c2f(c);
if (isMirror(c, Math.ceil(f))) {
printConversion(c, Math.ceil(f));
} else if (isMirror(c, Math.floor(f))) {
printConversion(c, Math.floor(f));
} else {
break;
}
}
``````

And when you run it:

``````
❯ bun run conversion.ts
4°C ~= 40°F
16°C ~= 61°F
28°C ~= 82°F
40°C ~= 104°F
52°C ~= 125°F
``````

## In TypeScript, how to combine known and unknown keys to an object

More than happy to be informed of a better solution here! But this came up in a real-work situation and I "stumbled" on the solution by more or less guessing.

In plain JavaScript, you have an object which you know you set certain keys on. But because this object is (ab)used for a templating engine, we also put keys/values on it that are not known in advance. In our use case, these keys and booleans came from parsing a `.yml` file which. It looks something like this:

``````
// Code simplified for the sake of the example

const context = {
currentVersion: "3.12",
currentLanguage: "en",
activeDate: someDateObject,
// ... other things that are values of type number, bool, Date, and string
// ...
}
if (someCondition()) {
context.hasSomething = true
}

for (const [featureFlag, truth] of Object.entries(parseYamlFile('features.yml')) {
context[featureFlag] = truth
}

const rendered = render(template: { context })
``````

I don't like this design where you "combine" an object with known keys with a spread of unknown keys coming from an external source. But here we are and we have to convert this to TypeScript, the clock's ticking!

### In comes TypeScript

Intuitively, from skimming the simplified pseudo-code above you might try this:

``````
type Context = {
currentVersion: string
currentLanguage: string
activeDate: Date
[featureFlag: string]: boolean
}
``````

TypeScript Playground demo here

Except, it won't work:

```Property 'currentVersion' of type 'string' is not assignable to 'string' index type 'boolean'.
Property 'currentLanguage' of type 'string' is not assignable to 'string' index type 'boolean'.
Property 'activeDate' of type 'Date' is not assignable to 'string' index type 'boolean'.```

Make sense, right? We're saying the type should have this, that, and that, but also saying that it can be anything. So it's a conflict.

### How I solved it

I'll be honest, I'm not sure this is very intuitive, either. But it works:

``````
type FeatureFlags = {
[featureFlag: string]: boolean
}
type Context = FeatureFlags & {
currentVersion: string
currentLanguage: string
activeDate: Date
}
``````

TypeScript Playground demo

It does imply that the inheritance, using the `&` is more than just semantic sugar. It means something.

## Simple object lookup in TypeScript

Ever got this error:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ foo: string; bar: string; }'. No index signature with a parameter of type 'string' was found on type '{ foo: string; bar: string; }'.(7053)

Yeah, me too. What used to be so simple in JavaScript suddenly feels hard in TypeScript.

In JavaScript,

``````
const greetings = {
good: "Excellent",
}
const answer = prompt("How are you?")
if (typeof answer === "string") {
}
``````

To see it in action, I put it into a CodePen.

Now, port that to TypeScript,

``````
const greetings = {
good: "Excellent",
}
const answer = prompt("How are you?")
if (typeof answer === "string") {
}
``````

Same. Except it doesn't work.
You can view it here on the TypeScript playground

This is the error you get about `greetings[answer]`:

Full error:

Element implicitly has an 'any' type because the expression of type 'string' can't be used to index type '{ good: string; bad: string; }'. No index signature with a parameter of type 'string' was found on type '{ good: string; bad: string; }'.(7053)

The simplest way of saying is that that object `greetings`, does not have any keys that are type `string`. Instead, the object has keys that are exactly `good` and `bad`.

I'll be honest, I don't understand the exact details of why it works like this. What I do know is that I want the red squiggly lines to go away and for `tsc` to be happy.
But what makes sense, from TypeScript's point of view is that, at runtime the `greetings` object can change to be something else. E.g. `greetings.bad = 123` and now `greetings['bad']` would suddenly be a number. A wild west!

This works:

``````
const greetings: Record<string, string> = {
good: "Excellent",
}
const answer = prompt("How are you?")
if (typeof answer === "string") {
}
``````

All it does is that it says that the `greetings` object is always a strings-to-string object.

See it in the TypeScript playground here

This does not work:

``````
const greetings = {
good: "Excellent",
}
const answer = prompt("How are you?")
if (typeof answer === 'string') {
}
``````

To be able to use `as keyof greetings` you need to do that on a type, not on the object. E.g.
This works, but feels more clumsy:

``````
type Greetings = {
good: string
}
const greetings: Greetings = {
good: "Excellent",
}
const answer = prompt("How are you?")
if (typeof answer === 'string') {
}
``````

### In conclusion

TypeScript is awesome because it forces you to be more aware of what you're doing. Just because something happen(ed) to work in JavaScript, when you first type it, doesn't mean it will work later.

Note, I still don't know (please enlighten me), what's the best practice between...

``````
const greetings: Record<string, string> = {
``````

...versus...

``````
const greetings: {[key:string]: string} = {
``````

The latter had the advantage that you can give it a name, e.g. "key".

## UPDATE (July 1, 2024)

Incorporating Gregor's utility function from the comment below yields this:

``````
function isKeyOfObject<T extends object>(
key: string | number | symbol,
obj: T,
): key is keyof T {
return key in obj;
}

const stuff = {
foo: "Foo",
bar: "Bar"
}
const v = prompt("What are you?")
if (typeof v === 'string') {
console.log("Hello " + (isKeyOfObject(v, stuff) ? stuff[v] : "stranger"))
}
``````

TypeScript Playground demo

Previous page
Next page