Marvin Hagemeister

フィード

記事のアイキャッチ画像
Speeding up the JavaScript ecosystem - Isolated Declarations
Marvin Hagemeister
Unbeknownst to many, the new isolatedDeclaration feature shipped in TypeScript 5.5 is much more important than you might realize. It just revolutionized how we package and distribute JavaScript code. You don't need to create *.d.ts files manually anymore by invoking the tsc compiler. "Go to source" (the thing when you do ctrl+click or cmd+click on macOS) actually works now and it leads you right to the TypeScript source code instead of a *.d.ts file or some compiled JavaScript code. And on top of it all it makes publishing packages way faster than ever before.How did it achieve that? Let's take a step back and assess the current situation of packaging JavaScript code in 2024.Packaging for npm in 2024 is a messHonestly, it's a mess. There is no point in sugar-coating it. A buddy of mine wanted to publish his first every library on npm, but was quickly discouraged when he realized how much work and specialized knowledge that entails. You have to care about CommonJS vs ESM, fiddle with a
4ヶ月前
記事のアイキャッチ画像
Speeding up the JavaScript ecosystem - Server Side JSX
Marvin Hagemeister
In the realm of web development, the efficiency of server-side rendering HTML plays a crucial role in delivering fast and responsive user experiences. However, a notable challenge arises from the existing JSX transforms that turn JSX into valid JavaScript: They are primarily tailored for browser environments, often generating excessive memory overhead and causing frequent Garbage Collection pauses. This overhead can be nearly eliminated by rethinking how we transform JSX on the server.Optimizing JSX for the serverWhen looking at the transpiled output of commonly used JSX transforms, it’s easy to see why it’s causing frequent GC pauses. They typically don’t differentiate between static and dynamic parts of a JSX "block" and allocate many short-lived objects for both an element and their attributes. The amount of objects to be allocated grows very quickly, the more JSX elements you have.// input<h1 id="heading">hello world</h1>;// output: "classic" createElement transformReact.createElem
6ヶ月前
記事のアイキャッチ画像
Speeding up the JavaScript ecosystem - Tailwind CSS
Marvin Hagemeister
Admittedly, I currently don’t have a bigger project written with Tailwind CSS at hand at the moment. Those that do use Tailwind are too small in scope to make a meaningful performance analysis. So I thought what better way than to profile Tailwind on its very own tailwindcss.com site! Right at the start I ran into a problem though: The project is built with Next.js which makes it very difficult to obtain meaningful traces off. What’s more is that the traces contained too much noise completely unrelated to TailwindCSS.Instead, I decided to run the Tailwind CLI over the project with the very same config to obtain some performance traces. Running the CLI build takes a total of 3.2s, whereas at runtime 1.4s were spent in Tailwind. The machine the numbers are pulled from is my personal MacBook M1 Air. Looking at the profile we can make out some key areas where time is spent:As usual in my posts, the x-axis of the flame graph doesn’t show time “when it happened” but rather the accumulated ti
1年前
記事のアイキャッチ画像
Speeding up the JavaScript ecosystem - The barrel file debacle
Marvin Hagemeister
Let's imagine you are working on a big project with many files. You add a new file to work on a new feature and import a function from another directory into your code.import { foo } from "./some/other-file";export function myCoolCode() {// Pretend that this is super smart code :)const result = foo();return result;}Excited about finishing your feature, you run the code and realize that it takes an awfully long time to complete. The code you've written is pretty straight forward and shouldn't consume that much time. Concerned by this, you sprinkle in some measuring code to see how long your function took to do its thing.import { foo } from "./some/other-file";export function myCoolCode() {console.time();const result = foo();console.timeEnd();return result;}You run the code again, and to your surprise the measurements you inserted show that it's blazing fast™. You repeat the measuring steps, but this time insert the console.time() statements in the main entry file of your project and run
1年前
記事のアイキャッチ画像
Speeding up the JavaScript ecosystem - Polyfills gone rogue
Marvin Hagemeister
In the previous posts we looked at runtime performance and I thought it would be fun to look at node modules install time instead. A lot has been already written about various algorithmic optimizations or using more performant syscalls, but why do we even have this problem in the first place? Why is every node_modules folders so big? Where are all these dependencies coming from?It all started when a buddy of mine noticed something odd with the dependency tree of his project. Whenever he updated a dependency, it would pull in several new ones and with each subsequent update the total number of dependencies grew over time. ├─┬ arraybuffer.prototype.slice 1.0.2 │ └─┬ define-properties 1.2.1 │ └── define-data-property 1.1.0 ├─┬ function.prototype.name 1.1.6 │ └─┬ define-properties 1.2.1 │ └── define-data-property 1.1.0 ├─┬ globalthis 1.0.3 │ └─┬ define-properties 1.2.1 │ └── define-data-property 1.1.0 ├─┬ object.assign 4.1.4 │ └─┬ define-properties 1.2.1 │ └── define-data-property 1.1.0 ├─
1年前
記事のアイキャッチ画像
So you want to render colors in your terminal
Marvin Hagemeister
If you've been writing command line tools for other developers to use, there will come a time where the clarity of the output can be enhanced through colors. What appears to be simple on the surface gets a bit messy as soon as you dive into the details on how colors are supported in various terminal emulators. Funnily, much of this complexity is due to historical reasons which have been kept alive to this date.Humble beginningsIn the early days of computing there were no terminal colors. Everything was rendered in either black or white. Demand grew for more complex rendering in the terminal and that's how ANSI escape codes were born. They represent a special set of character sequences that control cursor location, color, background color, font styling, and other options.// This will print the string "foobar" in green to the terminalconsole.log("\u001b[32mfoobar\u001b[39m");...which looks like this in the terminal:There is a catch though and that is that only a total of 16 colors are su
2年前
記事のアイキャッチ画像
Speeding up the JavaScript ecosystem - draft-js emoji plugin
Marvin Hagemeister
I received a very interesting issue via email from Josh Goldberg regarding a website that froze for about 2-3s. The website uses the draft-js rich text editor for some inputs and he was able to narrow it down to something going wrong in the emoji plugin for draft-js. So we decided to hop on a call and continue debugging together.Capturing a quick recording via Chrome's profiler confirms the initial suspicions. Something is up with the emoji plugin. We can see a lot of frequent function calls at the bottom that take up the majority of time. Unfortunately, Chrome's profiler doesn't have some sort of "left-heavy" visualization like speedscope does. This makes it a little harder to see which function is worth investigating. The "left-heavy" is nicer for that as it merges similar callstacks into one.Each individual call seemed fine and always ended up with calling into the regex engine. But the high number of calls were enough of a reason for concern. A cool thing about Chrome's profiler is
2年前
記事のアイキャッチ画像
Speeding up the JavaScript ecosystem - npm scripts
Marvin Hagemeister
If you’re working with JavaScript you’ve likely used the "scripts" field in package.json to set up common tasks for your project. Those scripts can be executed with npm run on the terminal. I noticed that I opted more and more to just call the underlying command directly instead of shelling out to npm run, mostly because it's noticeably faster. But what makes them so much slower in comparison? Time for a profiling session!Loading only the code you needWhat many developers don’t know is that the npm CLI is a standard JavaScript file and can be invoked like any other .js file. On macOS and Linux you can get the full path to the npm cli by running which npm. Dumping that file to the terminal reveals that it's a boring standard .js file. The only special thing is the first line which tells your shell with which program the current file can be executed with. Since we're dealing with a JavaScript file that would be node.Because it's just a .js file we can rely on all the usual ways to genera
2年前
記事のアイキャッチ画像
Speeding up the JavaScript ecosystem - eslint
Marvin Hagemeister
We've talked quite a bit about linting in the past two posts of this series, so I thought it's time to give eslint the proper limelight it deserves. Overall eslint is so flexible, that you can even swap out the parser for a completely different one. That's not a rare scenario either as with the rise of JSX and TypeScript that is frequently done. Enriched by a healthy ecosystem of plugins and presets, there is probably a rule for every use case out there and if there isn't, the excellent documentation guides you on how to create your own rules. That's one thing I want to highlight here as it's a project that has stood the test of time.But this also poses a problem for performance profiling as due to the vastness of the configuration flexibility two projects can have a widely different experience when it comes to linting performance. We need to start somewhere though, so I figured what better way to start our investigation than to look at the linting setup used in the eslint repository i
2年前
記事のアイキャッチ画像
Speeding up the JavaScript ecosystem - module resolution
Marvin Hagemeister
In part 1 of this series we found a few ways to speed various libraries used in JavaScript tools. Whilst those low level patches moved the total build time number by a good chunk, I was wondering if there is something more fundamental in our tooling that can be improved. Something that has a greater impact on the total time of common JavaScript tasks like bundling, testing and linting.So over the next couple of days I collected about a dozen CPU profiles from various tasks and tools that are commonly used in our industry. After a bit of inspection, I came across a repeating pattern that was present in every profile I looked at and affected the total runtime of these tasks by as much as 30%. It’s such a critical and influential part of our infrastructure that it deserves to have its own blog post.That critical piece is called module resolution. And in all the traces I looked at it took more time in total than parsing source code.The cost of capturing stack tracesIt all started when I no
2年前
記事のアイキャッチ画像
Speeding up the JavaScript ecosystem - one library at a time
Marvin Hagemeister
Whilst the trend is seemingly to rewrite every JavaScript build tool in other languages such as Rust or Go, the current JavaScript-based tools could be a lot faster. The build pipeline in a typical frontend project is usually composed of many different tools working together. But the diversification of tools makes it a little harder to spot performance problems for tooling maintainers as they need to know which tools their own ones are frequently used with.Although JavaScript is certainly slower than Rust or Go from a pure language point of view, the current JavaScript tools could be improved considerably. Sure JavaScript is slower, but it shouldn't be that slow in comparison as it is today. JIT engines are crazy fast these days!Curiosity lead me down the path of spending some time profiling common JavaScript-based tools to kinda see where all that time was spent. Let's start with PostCSS, a very popular parser and transpiler for anything CSS.Saving 4.6s in PostCSSThere is a very usefu
2年前
記事のアイキャッチ画像
Running 1000 tests in 1s
Marvin Hagemeister
Recently, I tweeted that the whole test suite for Preact, a modern framework to build web apps, runs in approximately 1s. It's composed of 1003 tests at the time of this writing that run in the browser and assert against the real DOM.Here is a video of what that looks like:That tweet struck a nerve, because those numbers aren't even close to be achievable in any of the popular test runners in the frontend space right now. Many newer web developers have never experienced a fast test suite that runs in 1s. Naturally, the question popped up as to what it is that made Preact's test suite so fast.Establishing a baselineBefore we begin, we should have a rough baseline of how fast it could be in theory. To do that we need to know what kind of tests we want to run. In Preact's case it's mostly about asserting that it rendered the correct output. For that we prefer to test against the real DOM inside one or potentially many browsers to ensure compatibility. The majority of test cases look simil
2年前
記事のアイキャッチ画像
How to skew benchmarks in your favour
Marvin Hagemeister
A common rite of passage for every frontend framework is to compare the execution speed with the established players. After all you want to know if your efforts paid off. Where does your framework positions itself on the performance scale? Maybe you want to make this the main selling point of your framework, so benchmarks can be a motivating factor in squeezing just that little bit more speed out of your code. Whatever your motivation is, let me tell you: Benchmarking is hard and has lots of gotchas. A lot things that can introduce bias to a particular solution.I thought I'd be fun to share all the things everyone of us did wrong with getting credible benchmark results at some point in our career. Benchmarking can be addictive and those numbers pretty deceiving if your not careful. I know, because I've been there. I made the same mistakes as outlined in this post.Let's dive into how accidental bias is introduced in framework benchmarks.Outdated versionsYou've been working on your frame
2年前
記事のアイキャッチ画像
A look at web components
Marvin Hagemeister
About a week ago, I spent time looking at web components, specifically how we can ease converting Preact components. One of my friends is playing around with them at work and it seemed like a fun challenge to take on. Personally I've never used web components before that. I just didn't have the chance to work on a project which required them so far. So my understanding of them was formed through word of mouth and the usual hot takes that are shared on twitter. But despite that I was geniously curious about them.To recap: Our preact-custom-element adapter is a small bridge that can turn any Preact component into a web component via a simple function call:import register from "preact-custom-element";const Greeting = props => <p>Hello, {props.name}!</p>;register(Greeting, "x-greeting", ["name"]);Why web components?The main benefit of web components is that you can render them from any other framework as well as in plain HTML. This is useful if you're building a design system and have to s
4年前
記事のアイキャッチ画像
Portals considered harmful
Marvin Hagemeister
So today I spent a few hours going through various GitHub repos to see how the Portal component in various virtual-dom Frameworks is used (and abused?) in the wild. It's always a fun thing to do for anyone working on frameworks, because users usually discover new ways of how to use features in ways it wasn't intended by the authors. Sometimes something cool comes out of and sometimes so good that the framework will cater to that use case. It's not all just sunshine and roses though because sometimes using features in unintended scenarios may break stuff. This is the story of the Portal component.To recap: A Portal allows you to jump from the current DOM node to a new container and continue rendering from there. A common use case for that are Modals that we may want to render just before the closing </body>-tag. Another use case are tooltip components that need to be positioned freely.Let's imagine we have this HTML:<body><!-- We'll render our app here --><div id="app"></div><!-- Modals
4年前
記事のアイキャッチ画像
Stencil Store Challenge
Marvin Hagemeister
When Manu from the Stencil team pinged me on Twitter regarding a code golfing challenge, I just couldn't resist! So I thought this could be a good exercise to apply techniques we use while working on Preact to a codebase that's foreign to me.Getting readyAfter the usual git clone and npm install we're good to go. Before we dive in immediately, we should get ourself familar with the overall goal the code is trying to clear. From the looks of it we're dealing with an extension to the Stencil framework which attempts to make state management easier. For that they rely on smaller stores where each property can be observed. This library holds the cold for that.sOverall I'm getting a Svelte vibe here as they have a similar concept in their frameworks. I expect more frameworks follow in the coming months, but only time will tell for sure. The codebase is authored in TypeScript which is a godsend whenever you have to get familiar with foreign code!ReadabilityThe first things I usually do is to
5年前
記事のアイキャッチ画像
State of Preact Devtools #2
Marvin Hagemeister
A little bit of time has past since the announcementthat we are working on our very own devtools extension. A lot hashappend since then and I'd love to share some news about what'shappening behind the scenes. The good news: I'd consider the currentcode feature-complete and the next will be the polishing phase. It'ssafe to say that things are progressing nicely without any hiccups.Screenshot of the current state of the extension.The main star of the show is obviously the element viewer itself.Having a way to peek at the component tree is incredibly helpful tounderstand how the screen was rendered. On the surface sucha atree-view seems easy enough to write down, but when I compared all thedevtools from other frameworks and browsers I noticed lots of UXdifferences, which in my opinion can really make (or break) theusefulness of the devtools.Pressing the right arrow key here should uncollapse the tree and move the selection to the first child.Personally, the arrow keys get a lot of usage o
5年前
記事のアイキャッチ画像
State of Preact Devtools
Marvin Hagemeister
As many of you know the react team released a revamped devtools extension recently. The improvements are countless and not just for endusers, but also the protocol has changed substaintially for the better. Whereas the previous iteration always asked for the information of the complete tree and every detail at once, the new version follows more a pull based approach. It only requests the data it needs and only when it's needed. That's a stark contrast!Another neat change is that the mappings for elements now happens on the consumer side. When you're attaching devtools to a virtual-DOM-based framework you need to notify the extension of every change in the tree or duplicate some of the reconciliation when element order changes. Both approaches are valid, but whatever you choose, you need to have a way to match old and new elements. You need to know that the just rendered element matches exactly a node on the other side.The previous version of react-devtools was great for its time, but k
5年前
記事のアイキャッチ画像
Hooks vs Classes a few months later
Marvin Hagemeister
It's been a while now that the hooks concept took over the frontend world bystorm. Originally researched by the very talented React team it solves somelongstanding issues surrounding the best way to make behaviours easily composableand shareable. A lot has been written about what they are and how to use them,so I won't repeat that here. Instead I'd love to share the maintainersperspective on them.Once we announced our first Preact X alpha many users immediately jumped on thehooks bandwaggon, and for good reasons! Since that release we've receivedsignificant less bug reports and support questions on slack regarding the useof components. With hooks there simply aren't many possiblities to tread downthe wrong path anymore. It's a solid API that in my experience is even easierto understand for newcomers, which is always a plus!So what are the footguns users run into with the Class-API you ask? For that Icollected some of the more frequent snippets we received in the last months.Initializin
5年前
記事のアイキャッチ画像
Applying Preact minification tricks to CSS
Marvin Hagemeister
One thing we're known for over at Preact is making code a lot smaller than itoriginally was. In fact the tiny size is one of the main strengths of Preact. A few days ago a friend of mine Jan Willem Henckelshared a link to CSSBattle, where users can competeagainst each other in trying to replicate an image with just CSS and HTML.The kicker? You have to use as few characters as possible to get the most points!Currently he holds the top spot with a mere 56 characters for rendering the following page:It's a greenish rectangle inside a brown canvas. Not the prettiest for sure, butenough to get us going. Intrigued and always up for a code challenge, I gave ita go. Although I mainly write JavaScript or TypeScript these days I was curiousto see how much of the knowledge gained from working on Preact can be appliedto other areas. The mindset for minifying and optimizing code in general shouldbe very similar in theory, but we're dealing with a different language. Some ofthe optimization tricks f
6年前
記事のアイキャッチ画像
The Double Encoded VNode
Marvin Hagemeister
Someone in our Preact Slack channel (join here) was running into an issue where the page would be blank as soon as the page loaded. Luckily we got a nice exception logged in the browser console. Error stack traces are awesome as they point you directly to the portion where the error occured and allow you to follow back the trail to see how the error came to be.preact.mjs:256 Uncaught DOMException: Failed to execute 'createElement' on 'Document': The tag name provided ('[object Object]') is not a valid name. at C (webpack-internal:///../node_modules/preact/dist/preact.mjs:213:155) at x (webpack-internal:///../node_modules/preact/dist/preact.mjs:175:16) at b (webpack-internal:///../node_modules/preact/dist/preact.mjs:108:83) at j (webpack-internal:///../node_modules/preact/dist/preact.mjs:262:32) at eval (webpack-internal:///./main.debug.tsx:9:54) at Module../main.debug.tsx (http://localhost:8080/app.bundle.js:1126:1) at __webpack_require__ (http://localhost:8080/app.bundle.js:724:30) at
6年前
記事のアイキャッチ画像
When should I use preact compat?
Marvin Hagemeister
What's the difference between preact and preact/compat? That's a great and one of the more popular questions if you check out the preact tag on StackOverflow. Preact advertises itself as the thinnest possible virtual-dom 3kB abstraction over the real DOM with a react-like API 🚀. This sentence is quite a mouthful, but the key is the last part: "react-like API".While the high-level concepts are very similar in any virtual-dom based library, we all implemented them differently. The beauty about this is that as a user you likely won't notice it and can reuse your existing knowledge for building modern Single-Page-Applications (=SPA). This typically includes reyling on several third-party libraries that are available on npm and oh boy are there many of them!So we looked at ways on how we can leverage existing libraries. We didn't want to put the workload on the community by asking them to rewrite everything for Preact and instead decided to provide a compatibility layer that sits above Pre...
6年前
記事のアイキャッチ画像
Preact's best kept secret
Marvin Hagemeister
I though I'd start the blog with something special that not many Preact users may know about. It's one of the least known features and also one of the most powerful ways to extend Preact: Option-Hooks. They allow anyone to plug into our reconciler and extend Preact without needing to make any modifications in our core. This power is what enables us to ship various addons like preact/compat and preact/hooks to name a few. They've been in Preact since the very early days reaching back to 2015 ✅.⚠ Note: Internally they've always been referred to as hooks in our code base. They are not to be confused with the recent hooks feature that was introduced by the react team.At the time of this writing we expose 9 hooks for the various reconciliation stages:options.vnode: Allows to mutate a vnode just before passing it to the reconcileroptions.commit: Called when a whole vnode-tree is mounted or updatedoptions.unmount: Called when a vnode will be removed from the DOM.options.diff: Called right bef
6年前