Ein Blog

Posts mit Tag "javascript"

Ein interessanter Artikel über Web Streams: Web Streams Everywhere

Vor ein paar Jahren (2014) hat man Web Streams standardisiert. Das ist sowas wie die Streams aus Node.js, aber als Standard für den Browser. Stellt sich heraus: Bei einem fetch-Request muss man nicht zwingend await (oder .then()) nutzen, um die Antwort zu verarbeiten. Die Property response.body ist ein Stream, den man durch andere Dinge pipen kann.

Auch andere Klassen, wie z. B. Blob, haben eher weniger bekannte Stream-Schnittstellen. So kann man auch WebSockets als ReadableStream wrappen, um mit den Nachrichten einfacher umgehen zu können.

Ein sehr lohnender Artikel!

Aus meiner HTML-/JS-Quicktips-Reihe: Es gibt eine coole, neue URL-API.

Was mir bei einer CTF-Challenge gerade aufgefallen ist und wahrscheinlich dort für die Exploitation wichtig ist, ich aber bisher nicht auf dem Schirm hatte:

Bei JavaScripts str.replaceAll(a, b) werden in b Steuerzeichen wie $ interpretiert, auch, wenn a keine RegExp ist:

console.log("a_b_c_d_e".replaceAll("_", "$`"))
// 'aaba_bca_b_cda_b_c_de'

Kommt b also vom User, können damit Werte injiziert werden.

Für mich ist das unerwartetes Verhalten, da für mich nicht offensichtlich ist, dass der replacement-String in jedem Fall interpretiert wird. Gibt es für sowas Linter-Regeln?

24 Lesser-Known HTML Attributes You May Want to Use.

Die meisten waren schon hier im Blog, aber es ist ‘ne schöne Liste.

Neue Context2D-Canvas-Features. Darunter: Rounded Rectangles und Filter.

Ist alles eine sinnvolle Erweiterung, gibt aber (wie bei Google öfter üblich) nicht mal einen verabschiedeten Standard dafür. Oder einen, der irgendwo offiziell gelistet ist. Die Features sind aber noch hinter dem new-canvas-2d-api-Flag. Ein Google-Mitarbeiter hat Specs/MDN-Drafts bei sich in einem Repo.

Intl.NumberFormat .formatRange kann Zahlen- (und auch Währungs-) Ranges formatieren. Auszug aus MDN:

const nf = new Intl.NumberFormat("es-ES", {
  style: "currency",
  currency: "EUR",
  maximumFractionDigits: 0,
});

console.log(nf.formatRange(3, 5); // → "3-5 €"
console.log(nf.formatRange(2.9, 3.1); // → "~3 €"

Wird aber noch von keinem Browser unterstützt (caniuse).

Gerade ist dieses Peoposal in meiner Timeline aufgetaucht. Ein Author ist Daniel Rosenwasser, bekannt aus dem TypeScript-Projekt.

Idee: einen Großteil der TS-Syntax in JS aufnehmen, allerdings ohne Typüberprüfung. Die Type-Annotations sollen von der Engine komplett ignoriert werden, ist also an der Stelle ähnlich zu dem Type-Hints aus Python. Nur kann man bei Python noch zur Laufzeit auf die Typen zurückgreifen. Das würde hier auch entfallen.

Als MS mit TypeScript angefangen hat, hätte Anders geäußert, dass statische Typen in der JS-Welt nicht gern gesehen sind - zumindest nicht in der Kernsprache. In dem Proposal ist ein Screenshot aus The-State-Of-JavaScript. Dort wünschten sich die Teilnehmer der Umfrage - mit Abstand - statische Typisierung am meisten. Das sieht für mich aus, als hätte MS da eine ziemliche Erfolgsstory mit TypeScript hingelegt.

Das Proposal wurde nichtmal dem TC39 vorgelegt - es wird also noch sehr lange dauern, sollte etwas in dieser Richtung kommen. Falls es kommt, wäre das ein ziemlicher Game-Changer. Man erspart sich damit, .d.ts-Dateien mit auszuliefern. Da JS dann größtenteils quasi das ist, was TS heute ist, wird TS dadurch deutlich zurück gehen und vorrangig nur noch als Type-Checker für JS dienen. Es würde tatsächlich vieles vereinfachen.

Spannend!

Bald könnte eine native Barcode-API für den Browser kommen. Dazu kommt vielleicht auch noch Face-Detection und Text-Detection, läuft alles unter dem Namen ShapeDetection.

Das Proposal mit den Type-Annotations für JavaScript von neulich ist nun ein offizielles TC39-Proposal.

Das Change Array by Copy-Proposal ist nun in Stage 3 und wird damit umgesetzt.

Damit kommen jetzt folgende Funktionen:

const array = [1, 2, 3, 4];

const array0 = array.toReversed();
const array1 = array.toSorted(compareFn);
const array2 = array.toSpliced(start, deleteCount, ...items);
const array3 = array.with(index, value);

Früher musste man häufig auf doesitmutate.xyz nachschauen. Jetzt ist es etwas expliziter. TypedArrays (Uint8Array etc.) bekommen die Funktionen auch.

Function.prototype.once wrappt eine Funktion so, dass sie nur ein Mal aufgerufen wird. Beim wiederholtem Aufruf wird das Ergebnis vom ersten zurückgegeben.

Wo wir gerade bei Node.js waren. Wir hatten schon ein paar mal den AbortController hier. Da gibt es jetzt eine Convenience-Method: AbortSignal.timeout(delay).

Node.js kann die ab Version 17 und 16.14.

Interessant: Das Globale nonce-Attribut in HTML. Interessant für alle, die CSP machen.

JS-APIs, die kaum jemand kennt; heute: Dem Browser sagen, dass ein Textfeld für einen SMS-OPT-Code gedacht ist, mit WebOTP (MDN, Standard, Chrome-Blog).

Grundidee: Man setzt das autocomplete-Attribut des input-Elements auf one-time-code, macht dann ein bisschen JavaScript und hat ein bestimmtes Format in der SMS, die man verschickt.

Auf CanIUse ist es noch nicht gelistet. Mozilla hat auch noch keine Stellung dazu. Der Standard kommt offenbar ausschließlich von Google.

Das Proposal zum JSON.parse source text access ist gerade in Stage 3 gelandet.

Heutzutage kommt in JavaScript ja jedes Jahr ein neuer Standard, der die Jahreszahl im Namen hat. Früher hat man da ausschließlich hochgezählt. Der letzte, den man häufig unter seiner Zahl referenziert hat, war ES6. Vor ES6 gab es ES5, was 2009 kam. Damals war z. B. JSON.parse neu. Vor ES5 war der Standard… ES3, aus 1999.

Was war eigentlich mit ES4? Heutzutage gibt es ja z. B. React, bei dem man in JavaScript eine eigene an HTML angelehnte Syntax für (Virtual-)DOM-Elemente hat. In ES6 kam auch eine Syntax für Klassen. Für Interfaces und statische Typisierung wird heutzutage oft auch TypeScript eingesetzt. Alles drei sind Sachen, die in ES4 vorgesehen waren. Dazu kamen sogar noch mehr Datentyüpen für Zahlen (int, byte, decimal, etc.). Hier gibt es eine Übersicht an ES4-Features, die dann niemals kamen.

Warum kam es nie? Dieser Post beantwortet das: The Real Story Behind ECMAScript 4

npm 9 ist da.

Ich hatte mal einen Post verlinkt, in dem erklärt wurde, wann React re-rendert. Gerade habe ich diesen schönen Post gefunden: Why React Re-Renders.

Hier ist noch ein ergänzender Talk: https://www.youtube.com/watch?v=lGEMwh32soc

Neues aus TC39:

Es wird daran gearbeitet, Übersetzungen direkt ok Browser zu machen: Intl.MessageFormat und den Parser dazu.

Die Syntax dazu sieht so aus:

# Note! MF2 syntax is under development; this may still change

greeting = {Hello {$place}!}

new_notifications =
  match {$count}
  when 0   {You have no new notifications}
  when one {You have {$count} new notification}
  when *   {You have {$count} new notifications}

Schon ganz interessant. Das Format “MF2” wird allerdings nicht von den Browserherstellern standardisiert, sondern vom Unicode-Konsortium.

In Stage 3 sind jetzt:

Neues aus TC39:

Chrome hat bei den Lighthouse-Tools jetzt einen Bundle-Analyzer. Bisher hat man sowas ja über PLugins für sein Frotnend-Build-System machen müssen. Dass das im Browser ist, hat jetzt den Vorteil, dass es “einfach geht”. Zusätzlich kann der Browser auch noch anzeigen, welcher Code davon gar nicht verwendet wurde. So kann man sich noch besser fokussieren.

Bun hat jetzt Makros:

random.ts:

export function random() {
  return Math.random();
}

main.ts:

import { random } from './random.ts' with { type: 'macro' };

console.log(`Your random number is ${random()}`);

Output im gebundleten js-File:

$ bun build ./cli.tsx
console.log(`Your random number is ${0.6805550949689833}`);

Interessantes Feature, das im Bundler-Bereich ein paar Grenzen verschieben könnte. So sontan könnte man damit einfach alle Bundler-Plugins durch einfache es-module ersetzen. Das würde IMHO Build-Toolchains deutlich vereinfachen.

Zahlen: ESM vs CJS auf NPM.

Neu: HTML Sanitizer API, und die kommt mit Element.setHTML() für ein .innerHTML für untrusted Input.

Kaum ist das using (und async using) in JS standardisiert, kommt TypeScript mit support dafür.

Die Nightly-Version von TS im Playground kann es scheinbar noch nicht. Bin gespannt, zu sehen, wie das vim Compiler in ältere JS-Versionen gelowert wird.

Browser Debugging Tricks. Sind ein paar coole dabei. Besonders die copy-Funktion kannte ich noch nicht.

Noch mehr gibt es hier: Debugging Dynamic Content.

Für TS 5.4 steht im Iteration-Plan ein Champion zu Throw Expressions auf der Liste. Kommt von rbuckton, einem TS-Engineer bei MS. Das Feature ansich begrüße ich. C# hat das “Problem” auch so gelöst, womit man sehr viele Dinge abkürzen kann. Bei Java sind throws selbst mit den neuen Switch-Expressions weiterhin noch Statements, weshalb sie für Switch extra die Grammatik angepasst haben, um throw zu erlauben. Das würde for free kommen, wenn throw einfach eine Expression wäre.

Wenn ich das richtig sehe, steht das Proposal kurz vor Stage 3 und damit kurz vor der finalen Umsetzung in JS.

Die Promise-API hat jetzt eine withResolvers-Funktion.

const { promise, resolve, reject } = Promise.withResolvers();

Math.random() > 0.5 ? resolve("ok") : reject("not ok");

Is eine Kurzform dafür, dass man früher die Resolve-Funktion im Promise-Konstruktor nach außem assigned hat. Ältere Leute werden sich vielleicht erinnern, dass das ähnlich zu einem Deferred aus JQuery ist.

Mehr in diesem schönen Post.

Neuerungen bei den TC39-Proposals. Darunter ist auch Uint8Array.fromBase64. Das war dringend notwendig!

Eine interessante API, mit der man sich ein bisschen gefummel sparen kann: URLPattern. Deno kann es schon und hat ein paar Beispiele. Damit können bestimmt einige Web-Frameworks ihren Parser neu schreiben und nochmal schneller sein, sofern das URLPattern nativ implementiert wird. Vielleicht kann man das mit TS sogar so statisch typisieren, dass der die Variablen-Platzhalter mit teil des Typs sind.

(caniuse, Mozillas Bugtracker, Spec, Explainer)

Weil es aktuell viel gibt, was bei React passiert, hier eine kleine Liste an Dingen, die React in Zukunft deutlich verändern werden oder könnten:

  • Canary-Releases, wo der aktuelle effektiv React 19 ist. Enthält auch den neuen use-Hook.
  • Der React-Compiler und Form-Status:
    • React hat bisher alles zur Laufzeit gemacht, weshalb Memoization 1. von Hand und 2. sehr aufwändig implementiert werden musste. Andere Frameworks setzen einen Compiler ein, der das ganze automatisch optimiert. React Forget ist genau das, für React.
    • Actions machen das Arbeiten mit Formen etwas angenehmer. Dazu gibt es auch die neuen Hooks useFormStatus und useFormState.
  • react-strict-dom: Ein Vorhaben, ein Subset von HTML zu definieren, das auch kompatibel mit React Native ist. Ziel ist, dass Komponenten besser und zuverlässiger zwischen React-für’s-Web und React-Native wiederverwendet werden können.
  • StyleX: Nicht per se nur für React, aber React ist das, wofür es bei Facebook benutzt wird. Eine CSS-in-JS-Lösung, bei der das gesamte CSS in eine einzige Datei kompiliert wird (auch das von Lazy-Loaded-Components). Ziel ist möglichst geringer Runtime-Overhead und eine schmale API. Wird bei Facebook bereits auf Instagram, WhatsApp und Facebook benutzt, um ein Designsystem bereitzustellen. Hat auch einen optimierenden Compile-Schritt.

Alle hier gelisteten Sachen sind noch stark in Entwicklung und noch nicht produktionsreif. Nichtsdestotrotz gut zu sehen, dass immer noch fleißig an React geschraubt wird.

The navigator.sendBeacon(url, data?) method asynchronously sends an HTTP POST request containing a small amount of data to a web server.

Warum?

A problem with sending analytics is that a site often wants to send analytics when the user has finished with a page: for example, when the user navigates to another page. In this situation the browser may be about to unload the page, and in that case the browser may choose not to send asynchronous XMLHttpRequest requests.

Dieses Verhalten hat dazu geführt, dass es dann wieder Workarounds gab, die das Navigieren bewusst blockiert haben. Deshalb diese API.

Hier der Standard dazu. Und hier noch ein Post auf CSS-Tricks mit mehr Hintergründen. Zeigt auch nochmal das ping-Attribut an Links.

Ein sehr guter Post über Probleme beim Benchmarking in JavaScript: JavaScript Benchmarking Is a Mess

In den Kommentaren auf HN hat ein JSC-Entwickler noch ein paar mehr Insights gegeben, die er hatte, als er den GC, den JIT und die Runtime entwickelt hat.

Chrome baut an einer neuen API, mit der man Content preloaden kann: <script type="speculationrules">

I Love Monorepos—Except When They Are Annoying. Ich habe fast alles davon selbst schon erlebt oder wurde davon genervt. Der Artikel fasst es ziemlich gut zusammen.

Web Translator API.

const translator = await Translator.create({
  sourceLanguage: "en",
  targetLanguage: "ja",
});

const translation = await translator.translate(myTextString);
console.log(translation);

Mozilla will es scheinbar nicht. Google hat da auch noch ein paar mehr geplant.

Ich finde, die gehören nicht in den global-Namespace. Das sollte man ähnlich wie die Intl-APIs in einen Sub-Namespace schieben. Davon abgesehen will man diese APIs vielleicht auch nicht.

CookieStore API. Sieht für mich nach einem neueren document.cookie aus, analog zum LocalStorage und damit dem browsernativen Ersatz zu Librarys wie cookie-js.

WASM 3.0 ist fertig.

Hier ist noch eine Übersicht an Features, an denen für die Zukunft gearbeitet wird.

Da sind ein paar Features dabei, die sehr viel schmerz lösen könnten. Für lean-s3 experimentiere ich gerade damit, einen Parser schnell zu bekommen. Ein langsamer Teil davon ist das Scanning. Für den hab ich jetzt schon viel rumgehackt und ihn auf .indexOf portiert, wo es möglich war. Der Hintergrund dafür ist, dass .indexOf unter der Haube (sehr wahrscheinlich) Vektorinstruktionen und generell eine sehr intelligente String-Suche verwendet, die man mit einem einfachen while-(x)-i++-Loop nicht hinbekommt.

Die Daten kommen aber aus undici, entweder als JS-String oder als Buffer. Wenn man jetzt vor dieser Spec WASM zum Scannen/Parsen verwenden wollte, hätte man vor dem Scannen die Daten in den WASM-Memory-Space manuell kopieren müssen. Ein Memcopy der größe O(n) hätte denke ich sämtliche Vorteile durch schnelleres Scannen wieder zunichte gemacht, deshalb hab ich das gar nicht erst probiert.

Mit diesen neuen Specs (speziell “multiple Memories” und “JS Strings”) gibt es hier 2 neue Möglichkeiten:

1. JS-String-Referenz übergeben

Den Result-String der HTTP-Antwort aus undici als JS-String an WASM übergeben und dann mit den String-Builtins den String verarbeiten. Damit macht man dann effektiv noch das gleiche wie vorher, aber der “Klebecode” zwischen den .indexOf-Calls ist dann etwa schneller, da der nicht mehr auf JS-Quriks (siehe unten) prüfen muss.

2. Den Buffer als Memory übergeben

Man kann den Buffer (nicht den String) von Undici direkt als zusätzlichen Memory an WASM reichen. Das wär dann Zero-Copy.

Vorteil gebenüber 1.: Die Antwort wird höchstwahrscheinlich UTF-8 sein. JS selbst benutzt für Strings intern UTF-16, d.h. wenn man eine HTTP-Antwort bekommt, muss undici aus dem UTF-8 erstmal noch UTF-16 machen. Das Problem hatte ASP.NET auch mal und sie haben das über eine Erweiterung von System.Text.Json, die die Rohdaten ohne vorheriges re-encoden nach UTF-16 parsen kann, fixen können. Das UTF-16 verbraucht unnötig viel Speicher, was schlecht für Cache-Lokalität ist. Wenn man den Buffer direkt verarbeitet, kann man sich das Re-Encoden sparen.

Ein weiteret Vorteil: Man kann dann die String-Operationskonstrukte der jeweiligen WASM-Quellsprache verwenden. Die kennt der Compiler dann schon. D.h. statt das .charCodeAt über die JS-Builtin-Strings zu callen, verwendet man einfach das, was die jeweilige Sprache sowieso schon hat. Da diese Boundary dann nicht vorhanden ist, kann der Compiler hier sehr viel besser optimieren, vorallem über Funktionsgrenzen hinaus. Außerdem wird der Overhead deutlich geringer sein (bzw. nicht vorhanden), weil nicht zwischen WASM-Land und JS-Land gemarshallt werden muss.

2 hat aber auch einen Nachteil gegenüber 1: Da für das Decoding verschiedene APIs verwendet werden, kann hier eine Divergenz durch verschiedene Implementierungen entstehen. Z.B. könnte sich das charCodeAt in JS anders verhalten als das aus bspw. Rust, wenn der zu lesende Buffer ungültiges UTF-8 hat. Das könnte darin enden, dass die Rust-Version dann falsche String-Offsets beim Scannen zurück gibt. Vielleicht könnte man das auch “beheben”, indem man direkt den kompletten AST in WASM zusammen baut. Ob das mit den neuen Specs möglich ist, gilt es noch rauszufinden.

Wenn das Stable in Node.js ist, hat man ne Menge zum Spielen. Vielleicht könnten die Memories auch für WASM-auf-Microcontroller verwendet werden, wo es verschiedene Speicherregionen gibt, die beim Microcontroller verschiedene Zwecke haben. Oder vielleicht sogar für Memory-Mapped-IO?

Edit: So ganz 0-Copy wäre Methode 2 natürlich doch noch nicht. Undici kopiert die Daten ja aus dem Lesebuffer im Network-Code in den aggregierten Buffer, der am ende zurückgegeben wird. In C# hat man das mit dem Primitive ReadOnlySequence<T> und Systen.IO.Pipelines gelöst. Das ist im Prinzip eine API, bei dem eine ReadOnlySequence eine Verkettung der Buffer ist, die Undici mit den Daten aus dem Netzwerk füllt. Die Pipeline bietet dann eine Abstraktion, um aus dieser Bufferkette angenehmer zu lesen. So kann man wirklich sehr einfach parsen, ohne die gesamte Antwort in einen Buffer zu tun. Das müsste Undici aber auch supporten und das wär denke ich auch sinnvoll, wenn das eine JS-API wird, die intern nativ arbeitet. Vielleicht machen das ja die WebStreams (oder node:streams) intern so?

temporal_rs ist eine Rust-Implementierung, die als Backing-API für die JS-Temporal-API gebaut wurde. Benutzt wird sie jetzt scheinbar auch bald in V8, und damit auch in Chrome.

Falls Chrome bisher noch kein Rust hatte (eine Sprache, die von Mozilla für Firefox entwickelt wurde), haben sie es dann spätestens jetzt. Und das noch vor Carbon — eine Sprache, die Google extra für Chrome baut.

Tuples & Records sind bei TC39 ja auf das Composite-Objekt eingedampft worden, weil die Engine-Entwickler keine Lust hatten, 2 neue primitive Typen einzuführen. Das wäre für React etc. super praktisch gewesen, weil zusammengesetzte Typen mit Wertegleichheit sehr viel State-Memoization überflüssig gemacht hätte. Nebenbei hätte man mit ihnen auch zusammengesetzte Werte als Map/Set-Key benutzen können. Die Composites machen jetzt nur noch letzteres.

Das Composite ist dabei mehr ein Hack, als eine Spracherweiterung. Da wird für Maps/Sets einfach gesagt “wenn ein Key ein Composite ist, mach als Key-Vergleich nicht ===, sondern .equals” Und .equals implementiert dann einen Deep-Equal-Vergleich.

Dass man keine neuen primitiven Typen einführen will, finde ich ne komishce Ausrede. Genau das hat man vor gar nicht allzulanger Zeit für BigInt getan. Und da fragen sich viele mittlerweile, ob es das überhaupt wert war, weil es kaum jemand benutzt.

Jedenfalls sind BigInts auch primitive Wertetypen und können dementsprechend als Key verwendet werden. Wenn man seine Daten also in einen BigInt packt, kann mehrere Daten als Key nehmen. Das ist um einiges Speichereffizienter, als sie in einen String zu packen und es kann beliebig viele Daten erfassen (im Gegensatz zu number). Hier hat genau damit jemand rumgespielt.

Es war gerade wieder ein TC39-Meeting. Ich zitiere mal den gesamten Post:

Stages:

Wie immer: Ab Stage 2.7 kann shippen es langsam die Browser. Ab Stage 3 ist es quasi schon final.

Ich hab ja schon über den AbortController geschrieben. U. A. auch über die Convinience-Method AbortController.timeout(ms). Da gibt es jetzt noch etwas mehr von:

Einen Throw-Helper

AbortSignal.throwIfAborted()

Und bald gibt’s auch einen Combinator:

AbortSignal.any(signals)

Der ist noch in Entwicklung.

Neues aus den Standards:

Die schon in Stage 3 befindlichen Funktionen groupBy und groupyByToMap wurden noch umbenannt: Sie heißen jetzt group und groupToMap. Man hat eine JS-Lib gefunden, die den Array-Prototypen monkeypatched hat.

String.dedent, um Einrückungen von Template-Strings zu fixen.

Damit habe ich auch schon Probleme gehabt. Allerdings bin ich mir nicht sicher, ob man soeine Funktion mit guter DX umsetzen kann. In meinen Augen haben die immer Magie gemacht (sowas gibt es auch in Java und mittlerweile auch C#). Kennen tue ich das aus Xtend. Teilweise verändert sich das Verhalten, wenn man den String in einer eigenen Zeile anfängt oder es mit Tabs mischt. Vielleicht ist es aber auch ganz einfach.

Eine Erweiterung zu JSON.parse, mit der man feststellen kann, aus was für einem String-Wert ein geparster Wert kommt. Und gleichzeitig das Verhalten von JSON.stringify zu verändern. Colle Idee. Damit kann man basteln, dass man Timestamps nach JSON.parse schon parst. Damit spart man sich einen zweiten Verarbeitungsschritt. Oder man kann das Serialisieren von Timestamps in einen Unix-Timestamp umbiegen, wenn eine API das braucht.