Supporting CommonJS and ESM with Typescript and Node – Evert Pot

I maintain a few dozen Javascript libraries, and recently updated many of them
to support CommonJS and Ecmascript modules at the same time.

The first half of this article describes why and how I did it, and then all the
hoops I had to jump through to make things work.

Hopefully it’s a helpful document for the next challenger.

A quick refresher, CommonJS code typically looks like this:

const MyApp = require('./my-app');
module.exports = {foo: 5};

And ESM like this:

import MyApp from './my-app.js';
export default {foo: 5};

Except if you use Typescript! Most Typescript uses ESM syntax, but actually
builds to CommonJS. So if your Typescript code looks like the second and
think ‘I’m good’, make sure you also take a look at the built Javascript
code to see what you actually use.

Why support ESM

The general vibe is that ESM is going to be the future of Javascript code.
Even though I don’t think the developer experience is quite there yet, but
more people will start trying ESM.

If you decided to plunge in with ESM, I want my libraries to feel first-class.

For example, I’d want you to be able to default-import:

import Controller from '@curveball/controller';

At the same time most people are still on CommonJS, and I want to continue
to support this without breaking backwards compatibility for the forseeable
future.

The general approach

My goal is for my packages to ‘default’ to ESM. From the perspective of a
library maintainer you will be dealing with ESM.

During the build phase steps are being made to generate a secondary CommonJS
build. As a maintainer you might run into CommonJS-specific issues, but
I suspect people will only see those if the CI system reports an issue.

Our published NPM packages will have a directory structure roughly like this:

- package.json
- tsconfig.json - src/ # Typescript source
- cjs/ # CommonJS
- esm/ # ESM
- test/

We include the original typescript sources to make step-through debugging work
well, and have a seperate directory for the ESM and CommonJS builds.

If you just want to skip to the end and see an example of a package that has
all this work done, check out my @curveball/core package:

Typescript features we don’t use

esModuleInterop

We don’t use the esModuleInterop setting in tsconfig.json. This flag
lets you default-import non-ESM packages like this:

import path from 'node:path';

instead of the more awkward:

Truncated by Planet PHP, read more at the original (another 29675 bytes)

ImagickException: unable to open file ‘/tmp/magick-…’ – Christian Weiske

I’m moving to a new server, and my avatar image generation script did not work anymore:

$ php surrogator.php
processing mm.svg
PHP Fatal error: Uncaught ImagickException: unable to open file `/tmp/magick-bcfNKPgxfBoOcZ5_de_xB9LzxZLhN2Dq': No such file or directory @ error/constitute.c/ReadImage/614 in /home/cweiske/www/avatar.cweiske.de/surrogator.php:236
Stack trace:
#0 /home/cweiske/www/avatar.cweiske.de/surrogator.php(236): Imagick->readImage()
#1 /home/cweiske/www/avatar.cweiske.de/surrogator.php(155): surrogator\createSquare()
#2 {main} thrown in /home/cweiske/www/avatar.cweiske.de/surrogator.php on line 236

The mm.svg file clearly exists, and my user is able to create files in /tmp/ – which I tested with touch /tmp/foo.

Using strace helped me to find the issue:

$ strace php surrogator.php
[...]
lstat("/usr/lib/x86_64-linux-gnu/ImageMagick-6.9.11/modules-Q16/coders/svg.la", 0x7ffd264c61c0) = -1 ENOENT (Datei oder Verzeichnis nicht gefunden)
stat("/usr/lib/x86_64-linux-gnu/ImageMagick-6.9.11//modules-Q16/coders/svg.la", 0x7ffd264c61f0) = -1 ENOENT (Datei oder Verzeichnis nicht gefunden)
stat("/tmp/magick-Uoh--TjgMhCveOq8LHbHQyVLXA87cpvx", 0x7ffd264ca2b0) = -1 ENOENT (Datei oder Verzeichnis nicht gefunden)
stat("/home/cweiske/www/avatar.cweiske.de/raw/mm.svg", {st_mode=S_IFREG|0644, st_size=3013, ...}) = 0
write(2, "PHP Fatal error: Uncaught Imagi"..., 497PHP Fatal error: Uncaught ImagickException: unable to open file `/tmp/magick-Uoh--TjgMhCveOq8LHbHQyVLXA87cpvx': No such file or directory @ error/constitute.c/ReadImage/614 in /home/cweiske/www/avatar.cweiske.de/surrogator.php:236
[...]

PHP’s Imagick extension wants to load the svg.la module that is responsible for loading .svg images, and that fails.

It turned out that I had to install the imagemagick package – php-imagick alone was not enough.

Using PSR-3 placeholders properly – Larry Garfield

Using PSR-3 placeholders properly

In the last 2 years or so, I’ve run into a number of projects that claim to use the PSR-3 logging standard as published by the PHP Framework Interoperability Group (PHP-FIG, or just FIG). Unfortunately, it’s quite clear that those responsible for the project have not understood PSR-3 and how it is intended to work. This frustrates me greatly, as PSR-3’s design addresses a number of issues that these projects are not benefiting from, and it reduces interoperability between projects (which was the whole point in the first place).

Rather than just rant angrily online (fun as it is, it doesn’t actually accomplish anything), many of my PHP community colleagues encouraged me to blog about using PSR-3 properly. So, here we are.

Larry
26 February 2023 – 10:26am

PHPUK 2023 – Rob Allen

I have just returned from the 2023 edition of PHPUK and, as always, found it a valuable conference to catch up with the PHP community and find out what’s happening in the ecosystem.

This year, I was accepted to speak on the differences between RPC, REST and GraphQL APIs and was surprised and gratified that the room was at full capacity. Thank you to everyone that attended; I hope that you learnt something useful. I think that good APIs matter and am fairly pragmatic about implementations. In general, I would far rather that you wrote a really good RPC API than a bad GraphQL one for example.

The developer experience of an API matters, which is why it’s important that your API follows best practices for the architecture that you choose. For instance, follow the Relay pagination specification if you are writing a GraphQL API, or follow HTTP semantics and use status codes properly if you are writing a RESTful API. This stuff is key as developer word-of-mouth about how good your API is to use is valuable marketing.

The conference had many good talks. For instance Ian’s talk on PHP on Lambda provided valuable information and Pauline reprised her excellent talk on Git to fill the slot after a speaker pulled out. Pauline also spoke about the decentralised web, a talk I recommend that you seek out when the videos are published. All in all, the standard of talks was very high and I heard a good buzz about lots of them.

In our world of remote work, in an industry where interruptions are disruptive to the flow state that we get into when working, I can’t emphasise enough how useful the connections we make at in-person conferences can be. It’s a fantastic way to connect and talk about issues directly relevant to your work life and get other people’s thoughts with more context than is usually possible in other mediums.

The people I have met at conferences have materially affected my career for the better and I encourage everyone to try to attend a conference that’s relevant for their work at least per year.