<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Evgen Mykhailenko</title>
    <description>The latest articles on DEV Community by Evgen Mykhailenko (@mykhailen).</description>
    <link>https://dev.to/mykhailen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2827639%2F1647fdd0-1615-49c8-b5fc-1d998a5bac97.jpg</url>
      <title>DEV Community: Evgen Mykhailenko</title>
      <link>https://dev.to/mykhailen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mykhailen"/>
    <language>en</language>
    <item>
      <title>TypeScript 7.1 Can Now Import Almost Anything. So I Made It Import C++, C, and Rust.</title>
      <dc:creator>Evgen Mykhailenko</dc:creator>
      <pubDate>Tue, 27 Oct 2026 14:07:27 +0000</pubDate>
      <link>https://dev.to/mykhailen/typescript-71-can-now-import-almost-anything-so-i-made-it-import-c-c-and-rust-3h25</link>
      <guid>https://dev.to/mykhailen/typescript-71-can-now-import-almost-anything-so-i-made-it-import-c-c-and-rust-3h25</guid>
      <description>&lt;p&gt;I've been following the development of &lt;strong&gt;Content Mappers&lt;/strong&gt; in TypeScript for quite a while.&lt;/p&gt;

&lt;p&gt;Partly because I'm excited about what they make possible, but also because I've been working on another TypeScript extension project that relies heavily on &lt;a href="https://volarjs.dev/" rel="noopener noreferrer"&gt;Volar.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And God, that's a lot of code to maintain just to get all the language features working correctly and keep everything in sync with TypeScript.&lt;/p&gt;

&lt;p&gt;But with &lt;strong&gt;TypeScript 7.1&lt;/strong&gt;, we finally have an official extension mechanism built directly into the compiler.&lt;/p&gt;

&lt;p&gt;And I think this opens up some pretty interesting possibilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if TypeScript could import anything?
&lt;/h2&gt;

&lt;p&gt;Imagine being able to write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Columns&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./data.csv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;exif&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./photo.jpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./config.yml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And not just import these files as strings or &lt;code&gt;any&lt;/code&gt;, but have TypeScript actually understand their contents.&lt;/p&gt;

&lt;p&gt;Imagine getting autocomplete for your YAML configuration, properly inferred columns from a CSV file, or type information extracted directly from another file format.&lt;/p&gt;

&lt;p&gt;No manually maintained &lt;code&gt;.d.ts&lt;/code&gt; files. No generic &lt;code&gt;declare module "*.csv"&lt;/code&gt; that treats every CSV file identically.&lt;/p&gt;

&lt;p&gt;Just types derived from the actual content.&lt;/p&gt;

&lt;p&gt;This is the kind of thing &lt;strong&gt;Content Mappers&lt;/strong&gt; make possible.&lt;/p&gt;

&lt;p&gt;And it's not entirely theoretical. &lt;a href="https://github.com/colinhacks/motherload" rel="noopener noreferrer"&gt;Motherload&lt;/a&gt;, from Colin McDonnell, is already exploring typed imports for several formats. Same for his another work - &lt;a href="https://reylo.dev/" rel="noopener noreferrer"&gt;Reylo&lt;/a&gt;, it also utilizes the same approach.  &lt;/p&gt;

&lt;p&gt;But how does this actually work?&lt;/p&gt;

&lt;h2&gt;
  
  
  How TypeScript 7.1 Content Mappers work
&lt;/h2&gt;

&lt;p&gt;Until now, TypeScript tooling has mostly been built around TypeScript and JavaScript files.&lt;/p&gt;

&lt;p&gt;Frameworks like Vue, Svelte, and Astro needed additional tooling to make TypeScript understand code embedded inside their custom file formats.&lt;/p&gt;

&lt;p&gt;That's where projects like Volar.js came in.&lt;/p&gt;

&lt;p&gt;Volar made it possible to provide TypeScript language features inside non-TypeScript files, including autocomplete, diagnostics, go-to-definition, and references.&lt;/p&gt;

&lt;p&gt;However, integrating this with TypeScript's compiler and language server wasn't exactly straightforward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript 7.1 introduces Content Mappers as a first-class extension point.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The idea is surprisingly simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register a Content Mapper for one or more file extensions.&lt;/li&gt;
&lt;li&gt;When TypeScript encounters a matching file, it asks the mapper to process it.&lt;/li&gt;
&lt;li&gt;The mapper produces a virtual TypeScript representation of the file.&lt;/li&gt;
&lt;li&gt;TypeScript uses that representation for type checking and language services.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But here's the really interesting part.&lt;/p&gt;

&lt;p&gt;The mapper can also provide &lt;strong&gt;source mappings between the virtual TypeScript code and the original file&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So TypeScript doesn't just understand the generated declarations. It can connect them back to their original source locations.&lt;/p&gt;

&lt;p&gt;That means things like hover information, diagnostics, go-to-definition, references, and rename can work across file formats.&lt;/p&gt;

&lt;p&gt;For example, imagine a Content Mapper that processes this C++ function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// math.cpp&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It could expose the following virtual TypeScript declaration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now TypeScript knows the function exists, what arguments it accepts, and what it returns.&lt;/p&gt;

&lt;p&gt;And because the mapper knows where &lt;code&gt;add&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, and &lt;code&gt;b&lt;/code&gt; came from in the original C++ source, editor navigation can work across the language boundary.&lt;/p&gt;

&lt;p&gt;Pretty cool, right?&lt;/p&gt;

&lt;p&gt;Of course, Content Mappers don't automatically make arbitrary files executable. They solve the TypeScript understanding and tooling side of the problem.&lt;/p&gt;

&lt;p&gt;Actually running those imports is still the responsibility of the runtime or another loader.&lt;/p&gt;

&lt;p&gt;And that's where I started thinking about another problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about native code?
&lt;/h2&gt;

&lt;p&gt;I'm currently building a small inference engine, and some of its functionality lives in C++.&lt;/p&gt;

&lt;p&gt;One of my biggest headaches has been integrating that native code with Node.js.&lt;/p&gt;

&lt;p&gt;Normally, if you want to call a C++ function from Node.js, you need to create native bindings using Node-API (N-API).&lt;/p&gt;

&lt;p&gt;It works, but it also means writing extra glue code, compiling addons, keeping TypeScript declarations in sync, and dealing with the build process.&lt;/p&gt;

&lt;p&gt;There are libraries like &lt;a href="https://koffi.dev/" rel="noopener noreferrer"&gt;Koffi&lt;/a&gt; that make native interoperability easier, and they're definitely interesting.&lt;/p&gt;

&lt;p&gt;But I wanted something slightly different.&lt;/p&gt;

&lt;p&gt;I wanted the native source itself to be the source of truth.&lt;/p&gt;

&lt;p&gt;What if TypeScript could read C++ function signatures directly?&lt;/p&gt;

&lt;p&gt;What if I could get IntelliSense, type checking, and go-to-definition without maintaining another layer of definitions?&lt;/p&gt;

&lt;p&gt;And what if the native bindings could be generated and compiled automatically?&lt;/p&gt;

&lt;p&gt;Essentially:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if importing a C++ function felt exactly like importing another TypeScript function?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Well, I decided to try building it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing ts-polyglot
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/mykhailen/ts-polyglot" rel="noopener noreferrer"&gt;&lt;strong&gt;ts-polyglot&lt;/strong&gt;&lt;/a&gt; is an experimental project that lets you import functions from &lt;strong&gt;C, C++, and Rust source files directly into TypeScript&lt;/strong&gt;, with inferred types and native execution through Node.js.&lt;/p&gt;

&lt;p&gt;Here's what that looks like.&lt;/p&gt;

&lt;p&gt;First, a regular C function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// add.c&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A C++ function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// greet.cpp&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello, "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a Rust function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// square.rs&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, from TypeScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./add.c&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;greet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./greet.cpp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;square&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./square.rs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;          &lt;span class="c1"&gt;// 5&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TypeScript&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Hello, TypeScript&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;          &lt;span class="c1"&gt;// 36&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;No handwritten N-API wrappers. No manually maintained TypeScript declarations. No separate native build command.&lt;/p&gt;

&lt;p&gt;And these aren't untyped calls.&lt;/p&gt;

&lt;p&gt;TypeScript understands the imported signatures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// ✅&lt;/span&gt;
&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ❌ TypeScript error&lt;/span&gt;

&lt;span class="nf"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// ✅&lt;/span&gt;
&lt;span class="nf"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// ❌ TypeScript error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You also get IntelliSense, hover information, and navigation to the original native source.&lt;/p&gt;

&lt;p&gt;But functions are only part of the story.&lt;/p&gt;

&lt;h2&gt;
  
  
  We can import native structs too
&lt;/h2&gt;

&lt;p&gt;One thing I really wanted was to avoid manually recreating C++ data structures in TypeScript.&lt;/p&gt;

&lt;p&gt;Consider this simple C++ file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// geometry.cpp&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Point&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;distanceSquared&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
           &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With ts-polyglot, we can import both the function &lt;strong&gt;and the native struct type&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;distanceSquared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./geometry.cpp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;distanceSquared&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 25&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TypeScript understands &lt;code&gt;Point&lt;/code&gt; automatically.&lt;/p&gt;

&lt;p&gt;Under the hood, the Content Mapper generates the equivalent of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;distanceSquared&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But you never have to write or maintain that interface yourself.&lt;/p&gt;

&lt;p&gt;Change the native struct, and the generated TypeScript representation changes with it.&lt;/p&gt;

&lt;p&gt;This also works with supported nested structs and array fields.&lt;/p&gt;

&lt;p&gt;And importantly, &lt;code&gt;Point&lt;/code&gt; isn't just a type-checking trick. When you call &lt;code&gt;distanceSquared(point)&lt;/code&gt;, ts-polyglot converts the JavaScript object into the native struct expected by C++.&lt;/p&gt;

&lt;p&gt;Which brings us to the more interesting implementation details.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do we discover which native functions can be imported?
&lt;/h2&gt;

&lt;p&gt;This was one of the first interesting challenges.&lt;/p&gt;

&lt;p&gt;C, C++, and Rust have very different syntax, and simply searching for function definitions with regular expressions obviously isn't going to work.&lt;/p&gt;

&lt;p&gt;We need to understand the structure of the source code.&lt;/p&gt;

&lt;p&gt;For that, ts-polyglot uses &lt;strong&gt;&lt;a href="https://tree-sitter.github.io/tree-sitter/" rel="noopener noreferrer"&gt;Tree-sitter&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Tree-sitter is a parser system that can generate syntax trees for many programming languages.&lt;/p&gt;

&lt;p&gt;We use its C, C++, and Rust grammars to parse native source files and find declarations that we can expose to TypeScript.&lt;/p&gt;

&lt;p&gt;The process looks roughly like this:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Parse the native source
&lt;/h3&gt;

&lt;p&gt;When ts-polyglot encounters a native file, it passes the source through the appropriate Tree-sitter parser.&lt;/p&gt;

&lt;p&gt;Tree-sitter gives us a syntax tree containing function definitions, parameter declarations, return types, structs, and other language constructs.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Point&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;distanceSquared&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
           &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this, we can extract information equivalent to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Struct: Point
  x: double
  y: double

Function: distanceSquared
  Parameters:
    point: Point
  Returns:
    double
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important thing is that we're reading the actual native source, not a separate declaration file.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Find supported functions
&lt;/h3&gt;

&lt;p&gt;Next, we traverse the syntax tree looking for functions that can be imported.&lt;/p&gt;

&lt;p&gt;It's not limited to simple top-level functions.&lt;/p&gt;

&lt;p&gt;For example, ts-polyglot can discover supported C++ functions inside namespaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also handles functions inside &lt;code&gt;extern "C"&lt;/code&gt; blocks and public functions inside Rust modules.&lt;/p&gt;

&lt;p&gt;But finding a function isn't enough.&lt;/p&gt;

&lt;p&gt;We also need to determine whether its parameters and return type can actually cross the JavaScript/native boundary.&lt;/p&gt;

&lt;p&gt;A function that accepts a &lt;code&gt;double&lt;/code&gt; is relatively straightforward.&lt;/p&gt;

&lt;p&gt;A function that accepts an arbitrary pointer, a complicated template, or a mutable reference is a very different problem.&lt;/p&gt;

&lt;p&gt;So before exposing a function, we inspect its signature and check whether every parameter and the return value have a supported type mapping.&lt;/p&gt;

&lt;p&gt;If the function contains unsupported types, we skip it rather than generating a TypeScript declaration that we can't actually support at runtime.&lt;/p&gt;

&lt;p&gt;This means you can have a native source file containing many different functions, and ts-polyglot will expose the subset it knows how to handle.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Resolve native types into TypeScript types
&lt;/h3&gt;

&lt;p&gt;Once we know a function can be imported, we generate its TypeScript signature.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;isPositive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isPositive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For structs, we inspect the fields and recursively map their types too.&lt;/p&gt;

&lt;p&gt;This is where things become more complicated, because native types don't always have a direct JavaScript equivalent.&lt;/p&gt;

&lt;p&gt;And that leads to the next big topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type mapping: C++ types aren't JavaScript types
&lt;/h2&gt;

&lt;p&gt;TypeScript has &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;boolean&lt;/code&gt;, &lt;code&gt;bigint&lt;/code&gt;, arrays, objects, and a few other useful abstractions.&lt;/p&gt;

&lt;p&gt;C, C++, and Rust have much richer and sometimes very different type systems.&lt;/p&gt;

&lt;p&gt;So we need explicit rules for translating native types into TypeScript types.&lt;/p&gt;

&lt;p&gt;Here's a simplified overview of what ts-polyglot currently supports:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Native type&lt;/th&gt;
&lt;th&gt;TypeScript type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;int&lt;/code&gt;, &lt;code&gt;int32_t&lt;/code&gt;, Rust &lt;code&gt;i32&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;float&lt;/code&gt;, &lt;code&gt;double&lt;/code&gt;, Rust &lt;code&gt;f64&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;std::string&lt;/code&gt;, Rust &lt;code&gt;String&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;int64_t&lt;/code&gt;, Rust &lt;code&gt;i64&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bigint&lt;/code&gt; for results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;struct Point&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;interface Point&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;std::vector&amp;lt;double&amp;gt;&lt;/code&gt;, &lt;code&gt;Vec&amp;lt;f64&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number[]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unsigned byte arrays&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Uint8Array&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixed-size arrays&lt;/td&gt;
&lt;td&gt;TypeScript tuples&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There are some important details here.&lt;/p&gt;

&lt;p&gt;For example, 64-bit integers are represented as &lt;code&gt;bigint&lt;/code&gt; for return values and struct fields, while parameters can accept &lt;code&gt;number | bigint&lt;/code&gt; where supported.&lt;/p&gt;

&lt;p&gt;Array parameters are typed as readonly arrays, while returned arrays are mutable.&lt;/p&gt;

&lt;p&gt;And some native types have no safe mapping yet.&lt;/p&gt;

&lt;p&gt;You can find the full table of supported mappings here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://ts-polyglot.dev/#types" rel="noopener noreferrer"&gt;https://ts-polyglot.dev/#types&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But generating TypeScript types is only half of the problem.&lt;/p&gt;

&lt;p&gt;At runtime, we also need to convert values in both directions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type marshaling: getting values across the native boundary
&lt;/h2&gt;

&lt;p&gt;This is probably one of the trickiest parts of native interoperability.&lt;/p&gt;

&lt;p&gt;When TypeScript calls a C++ function, JavaScript values can't simply be passed directly into native code.&lt;/p&gt;

&lt;p&gt;They have to be converted into their corresponding native representations.&lt;/p&gt;

&lt;p&gt;This process is called &lt;strong&gt;type marshaling&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's go back to our &lt;code&gt;Point&lt;/code&gt; example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nf"&gt;distanceSquared&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the TypeScript side, this is just a plain JavaScript object.&lt;/p&gt;

&lt;p&gt;But our C++ function expects a native &lt;code&gt;Point&lt;/code&gt; struct.&lt;/p&gt;

&lt;p&gt;So the generated Node-API binding needs to do something conceptually like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receive the JavaScript object.&lt;/li&gt;
&lt;li&gt;Verify that it has the expected properties.&lt;/li&gt;
&lt;li&gt;Read &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; and validate their types.&lt;/li&gt;
&lt;li&gt;Convert both values into native &lt;code&gt;double&lt;/code&gt; values.&lt;/li&gt;
&lt;li&gt;Construct a native &lt;code&gt;Point&lt;/code&gt; struct.&lt;/li&gt;
&lt;li&gt;Call the actual C++ function.&lt;/li&gt;
&lt;li&gt;Convert the returned &lt;code&gt;double&lt;/code&gt; back into a JavaScript &lt;code&gt;number&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And we do the opposite when a native function returns a struct.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;Point&lt;/span&gt; &lt;span class="nf"&gt;createPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From TypeScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;createPoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./geometry.cpp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// { x: 10, y: 20 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated binding reads the native struct's fields and creates a new JavaScript object.&lt;/p&gt;

&lt;p&gt;No manually written conversion code.&lt;/p&gt;

&lt;p&gt;The same principle applies to supported strings, arrays, nested structs, and other types.&lt;/p&gt;

&lt;p&gt;There are some important safety details too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conversions are checked at runtime.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you pass a string to a function that expects an integer, the generated binding throws a &lt;code&gt;TypeError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you pass a number outside the native integer's supported range, it throws a &lt;code&gt;RangeError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Arrays and structs cross the boundary by value, with their contents copied. JavaScript objects aren't simply reinterpreted as native memory.&lt;/p&gt;

&lt;p&gt;This is important because JavaScript and native code have very different memory models and ownership rules.&lt;/p&gt;

&lt;p&gt;We want an import to feel like a normal TypeScript function call without silently introducing unsafe conversions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some mappings are smarter than simple type replacement
&lt;/h3&gt;

&lt;p&gt;One example I particularly like is C arrays.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally, you'd need to pass both the array and its length.&lt;/p&gt;

&lt;p&gt;But ts-polyglot can recognize supported C array-and-length parameter patterns.&lt;/p&gt;

&lt;p&gt;So the exposed TypeScript signature becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you can call it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// 20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated binding takes care of calculating and passing the length.&lt;/p&gt;

&lt;p&gt;It's a small example, but it demonstrates that type mapping doesn't have to be just a one-to-one translation of native types.&lt;/p&gt;

&lt;p&gt;We can expose a more natural TypeScript API while preserving the original native function.&lt;/p&gt;

&lt;h2&gt;
  
  
  From source file to working native import
&lt;/h2&gt;

&lt;p&gt;Now we have two important pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Function discovery and type mapping&lt;/strong&gt;, which tell TypeScript what functions exist and how to use them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type marshaling&lt;/strong&gt;, which tells the runtime how to safely call those functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how ts-polyglot connects everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  At development time: the Content Mapper
&lt;/h3&gt;

&lt;p&gt;When TypeScript encounters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;distanceSquared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./geometry.cpp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our Content Mapper uses Tree-sitter to parse the C++ file, discover supported functions and structs, and generate their virtual TypeScript declarations.&lt;/p&gt;

&lt;p&gt;It also returns source mappings so TypeScript can associate symbols in those declarations with their original positions in the C++ file.&lt;/p&gt;

&lt;p&gt;Nothing needs to be compiled at this stage.&lt;/p&gt;

&lt;p&gt;TypeScript just gets the information it needs for type checking and editor features.&lt;/p&gt;

&lt;h3&gt;
  
  
  At runtime: the Node.js import hook
&lt;/h3&gt;

&lt;p&gt;When Node.js encounters the same import, a different part of ts-polyglot takes over.&lt;/p&gt;

&lt;p&gt;The runtime hook:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parses the native source using the same signature discovery implementation.&lt;/li&gt;
&lt;li&gt;Generates Node-API bindings for the supported functions.&lt;/li&gt;
&lt;li&gt;Generates the necessary type marshaling code.&lt;/li&gt;
&lt;li&gt;Compiles the native code and generated bindings into a &lt;code&gt;.node&lt;/code&gt; addon.&lt;/li&gt;
&lt;li&gt;Loads the addon and exposes its functions as JavaScript exports.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For Rust, the build process also involves compiling the Rust code into a static library and linking it with the generated addon.&lt;/p&gt;

&lt;p&gt;The resulting builds are cached, so subsequent imports of unchanged source files don't need to compile everything again.&lt;/p&gt;

&lt;p&gt;And there's an important detail here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Both the Content Mapper and runtime use the same Tree-sitter-based signature parser.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So the types TypeScript checks are derived from the same native signatures that the runtime uses when generating bindings.&lt;/p&gt;

&lt;p&gt;This is especially useful because otherwise we'd effectively be maintaining two different interpretations of the same native code.&lt;/p&gt;

&lt;p&gt;The goal is to have a single source of truth: the native source file itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;The easiest way to create a sample project is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create ts-polyglot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This scaffolds a project with C, C++, and Rust examples.&lt;/p&gt;

&lt;p&gt;For an existing project, install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;ts-polyglot
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; typescript@7.1.0-beta
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then register the Content Mapper in &lt;code&gt;tsconfig.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nodenext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"noEmit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contentMappers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ts-polyglot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"extensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;".c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".cpp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".rs"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type-check your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx tsc &lt;span class="nt"&gt;--runExternalCode&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--runExternalCode&lt;/code&gt; flag is required because Content Mappers are external executable programs.&lt;/p&gt;

&lt;p&gt;And run your TypeScript file with the native import hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--import&lt;/span&gt; ts-polyglot/register main.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need Node.js 22.18+, a native C/C++ compiler, and &lt;code&gt;rustc&lt;/code&gt; if you're importing Rust.&lt;/p&gt;

&lt;p&gt;For VS Code, use the TypeScript (Native Preview) extension, select your workspace's TypeScript 7.1 version, and enable the mapper in a trusted workspace.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's still early
&lt;/h2&gt;

&lt;p&gt;This is very much an experimental project.&lt;/p&gt;

&lt;p&gt;There are lots of interesting problems we haven't solved yet.&lt;/p&gt;

&lt;p&gt;Native interoperability gets complicated quickly, especially when pointers, ownership, lifetimes, mutable references, callbacks, and more complicated C++ and Rust constructs enter the picture.&lt;/p&gt;

&lt;p&gt;At the moment, ts-polyglot focuses on supported functions with well-defined mappings for numbers, booleans, strings, plain structs, and arrays.&lt;/p&gt;

&lt;p&gt;Things like arbitrary pointers, mutable out-parameters, enums, function pointers, and various templates and generics aren't supported yet.&lt;/p&gt;

&lt;p&gt;Tree-sitter also isn't a full C++ compiler. It doesn't expand macros or perform complete semantic analysis, so there are limits to what we can discover directly from the source.&lt;/p&gt;

&lt;p&gt;And TypeScript 7.1's Content Mapper protocol is still in beta, so some integration details may change.&lt;/p&gt;

&lt;p&gt;But that's part of the experiment.&lt;/p&gt;

&lt;p&gt;How much native binding boilerplate can we eliminate if TypeScript understands the source directly and the runtime can generate the bridge automatically?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I think Content Mappers matter
&lt;/h2&gt;

&lt;p&gt;What excites me most about TypeScript 7.1 isn't just support for another file extension.&lt;/p&gt;

&lt;p&gt;It's the possibility of making TypeScript's type system and development tooling available to entirely different languages and file formats through a supported extension mechanism.&lt;/p&gt;

&lt;p&gt;Today we're talking about Vue components, CSV files, YAML configurations, and native functions.&lt;/p&gt;

&lt;p&gt;But the same concept could be applied to many other kinds of source files and domain-specific languages.&lt;/p&gt;

&lt;p&gt;We can start treating these files as first-class participants in a TypeScript project instead of maintaining separate declarations or building completely separate language tooling around them.&lt;/p&gt;

&lt;p&gt;And with an appropriate runtime integration, those imports can become executable too.&lt;/p&gt;

&lt;p&gt;For me, ts-polyglot is one experiment in that direction.&lt;/p&gt;

&lt;p&gt;It's still early, and there's plenty of work ahead, but I think there's something really interesting here.&lt;/p&gt;

&lt;p&gt;If you've worked with TypeScript language tooling, Volar, Node-API, native interoperability, Tree-sitter, or Content Mappers, I'd love to hear what you think.&lt;/p&gt;

&lt;p&gt;And if you want to try it or explore the implementation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/mykhailen/ts-polyglot" rel="noopener noreferrer"&gt;https://github.com/mykhailen/ts-polyglot&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://ts-polyglot.dev/" rel="noopener noreferrer"&gt;https://ts-polyglot.dev/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supported types:&lt;/strong&gt; &lt;a href="https://ts-polyglot.dev/#types" rel="noopener noreferrer"&gt;https://ts-polyglot.dev/#types&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript 7.1 announcement:&lt;/strong&gt; &lt;a href="https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/" rel="noopener noreferrer"&gt;https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback, ideas, and contributions are welcome!&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>node</category>
      <category>rust</category>
      <category>cpp</category>
    </item>
    <item>
      <title>I'm a developer. I don't need Postman to run HTTP requests</title>
      <dc:creator>Evgen Mykhailenko</dc:creator>
      <pubDate>Sat, 08 Feb 2025 03:15:10 +0000</pubDate>
      <link>https://dev.to/mykhailen/im-a-developer-i-dont-need-postman-to-run-http-requests-2830</link>
      <guid>https://dev.to/mykhailen/im-a-developer-i-dont-need-postman-to-run-http-requests-2830</guid>
      <description>&lt;p&gt;Don't get me wrong, Postman is a perfect tool in its niche, but it is still a UI tool.&lt;/p&gt;

&lt;p&gt;Starting from the moment Postman came to market, I've been searching for a way to execute my requests in the way I’m used to — by writing code.&lt;/p&gt;

&lt;p&gt;The first tool I tried was the REST Client VS Code extension. It was very close to what I needed - finally, I could run requests from my IDE. I didn’t need to choose a verb from a dropdown, and I could store the files in Git. &lt;/p&gt;

&lt;p&gt;But there were still some drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I didn’t like how requests opened in a separate tab. If I switched focus to that tab, the next time VS Code would open my file in that tab group.&lt;/li&gt;
&lt;li&gt;Some syntax constructions, like ###, looked weird to me. I could get used to them, but they still felt unnatural.&lt;/li&gt;
&lt;li&gt;I still had to specify the full URL, even when using variables. I couldn’t just define a base URL and then use &lt;code&gt;GET /users&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;I also wanted OpenAPI IntelliSense, but that still wasn’t possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, all of this led me to create my own language.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a href="https://httl.dev" rel="noopener noreferrer"&gt;HTTL&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;For now, it is available as a &lt;a href="https://marketplace.visualstudio.com/items?itemName=HTTL.httl-vscode" rel="noopener noreferrer"&gt;VS Code Extension&lt;/a&gt;&lt;br&gt;
but very soon, I plan to roll out a CLI and support for other IDEs.&lt;/p&gt;

&lt;p&gt;In order to understand how this language happened and what the motivation behind it was, let's start from the basics and imagine how we could evolve the standard HTTP specification.&lt;/p&gt;

&lt;p&gt;I'll take this standard request to create a new user:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjvbwxmz30volo2khhcy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjvbwxmz30volo2khhcy.png" alt="Example 1" width="800" height="627"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First of all, let's do a little cleaning.&lt;/p&gt;

&lt;p&gt;Obviously, we don't need the &lt;code&gt;Content-Length&lt;/code&gt; header - the language should be able to calculate it by itself. We can also remove the &lt;code&gt;HTTP/1.1&lt;/code&gt; part and, for simplicity, use lowercase for verbs.&lt;/p&gt;

&lt;p&gt;Ok, now we have this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdk6s5g6d25nkaid8y7xq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdk6s5g6d25nkaid8y7xq.png" alt="Example 2" width="800" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not much better, but let's go ahead.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Host&lt;/code&gt; - this is an interesting one. While the HTTP spec requires it, the header does not indicate the communication protocol (e.g., HTTP or HTTPS), so we need to specify it directly somewhere else.&lt;/p&gt;

&lt;h4&gt;
  
  
  Option #1: Use a full url in the request line
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fei7eokp1xzy9d390uoh2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fei7eokp1xzy9d390uoh2.png" alt="Example 3" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But what if we have more than one request and we don't want to repeat ourselves?&lt;/p&gt;

&lt;h4&gt;
  
  
  Option #2: Use a kind of templating, like in the REST client.
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11rj0aa2n2ixjwol32j3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11rj0aa2n2ixjwol32j3.png" alt="Example 4" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, we have to include &lt;code&gt;{{baseHost}}&lt;/code&gt; in every request.&lt;/p&gt;

&lt;p&gt;But don't you think we can do better?&lt;/p&gt;

&lt;p&gt;That's why we're here - we need to introduce a new language construction that extends the standard HTTP protocol: &lt;strong&gt;a directive&lt;/strong&gt;. Here's one: &lt;code&gt;@base&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi6ujqmto303xfd4e3nf9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi6ujqmto303xfd4e3nf9.png" alt="Example 5" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It will allow us to avoid repeating the base URL and prevent the need to specify it every time in any subsequent request.&lt;/p&gt;

&lt;p&gt;Ok, see what we've got.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2mcyh65ats33f1xex1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2mcyh65ats33f1xex1f.png" alt="Example 6" width="800" height="627"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Slightly better, but let's move forward.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Content-Type&lt;/code&gt; - we can make it common for every request to avoid repeating ourselves again. We can simply move it out of the request construction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foklotmknrbooykqkpmhn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foklotmknrbooykqkpmhn.png" alt="Example 7" width="799" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this change, &lt;code&gt;Content-Type&lt;/code&gt; becomes a global header and will be automatically set for any subsequent requests. It's very convenient for the Authentication header.&lt;/p&gt;

&lt;p&gt;On a final note, we are sending JSON, so why do we need &lt;code&gt;Content-Type&lt;/code&gt; at all? The language should automatically set the header for obviously recognizable payloads.&lt;/p&gt;

&lt;p&gt;Additionally, we can use JavaScript object syntax to remove unnecessary quotes.&lt;/p&gt;

&lt;p&gt;And here is what &lt;a href="https://httl.dev" rel="noopener noreferrer"&gt;HTTL&lt;/a&gt; is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F87b6yz0v0lpfrp31nx46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F87b6yz0v0lpfrp31nx46.png" alt="Example 8" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Much cleaner, but having just one request might not be as impressive as handling a chain of requests.&lt;/p&gt;

&lt;p&gt;And yes, this is the next feature the language should support: &lt;strong&gt;request chaining&lt;/strong&gt;. &lt;/p&gt;




&lt;h2&gt;
  
  
  Request Chaining
&lt;/h2&gt;

&lt;p&gt;Let's consider a real example where we first need to get an authentication token and then use it in subsequent requests.&lt;/p&gt;

&lt;p&gt;With all we've done above, let's dive in.&lt;/p&gt;

&lt;p&gt;This time, let's use the ReqRes.in API for this showcase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ficde8gexcwzry1w98py4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ficde8gexcwzry1w98py4.png" alt="Example 9" width="800" height="718"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are slowly moving to a new feature: &lt;strong&gt;variables&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Variables
&lt;/h2&gt;

&lt;p&gt;We can store the response in a variable and use it in a later request.&lt;br&gt;
Here is how:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa65jl0yzk9evt8g2u2wl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa65jl0yzk9evt8g2u2wl.png" alt="Example 10" width="800" height="688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firstly&lt;/strong&gt;, we introduced a new construction here -  &lt;code&gt;as auth&lt;/code&gt;. It tells the runtime to store the response body in this variable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secondly&lt;/strong&gt;, we set a global &lt;code&gt;Authorization&lt;/code&gt; header to allow any subsequent request to use this header.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Authorization: Bearer {auth.token}&lt;/code&gt; - a few notes here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For string interpolation, we can use single brackets {}.&lt;/li&gt;
&lt;li&gt;If the response is JSON, we can use dot notation to access nested fields.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;You may wonder how to access other elements of the response, such as headers, timings, status, etc. I plan to add this in future releases&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, we can complete the scenario of create, update, and delete steps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1fojtx9ga8fmy4s7p5cu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1fojtx9ga8fmy4s7p5cu.png" alt="Example 11" width="800" height="1240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's much better! We are not overwhelmed with unnecessary constructions like the base URL, common headers, etc. &lt;/p&gt;

&lt;p&gt;That's one of the main ideas of HTTL: to remove unnecessary constructions and leave only meaningful elements. It keeps the code clean and lets us focus more on logic rather than mechanics.&lt;/p&gt;

&lt;p&gt;Wait, Assert? Yes! How can we create a language without testing capabilities?&lt;/p&gt;




&lt;h2&gt;
  
  
  Asserting
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;assert&lt;/code&gt; keyword can be added to the end of any request to verify the following elements: status, headers, and body.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmu7xoy6zf6t9pqea1w4l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmu7xoy6zf6t9pqea1w4l.png" alt="Example 11" width="800" height="688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So far, it allows only strict equality, like &lt;code&gt;status: 200&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But stay tuned—the testing feature will evolve significantly in upcoming releases.&lt;/p&gt;




&lt;h2&gt;
  
  
  OpenAPI
&lt;/h2&gt;

&lt;p&gt;OpenAPI: we cannot overlook this important element of modern API development.&lt;/p&gt;

&lt;p&gt;This was my personal goal - to finally have IntelliSense for the OpenAPI spec. I’m a big fan of API documentation because it helps me make fewer mistakes.&lt;/p&gt;

&lt;p&gt;Since the HTTP spec does not include anything related to the OpenAPI spec, we need to introduce something new - the &lt;code&gt;@spec&lt;/code&gt; directive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnwlvjfyugs8r5mser3bl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnwlvjfyugs8r5mser3bl.png" alt="Example 12" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All we need to do is specify a URL to the openapi.json file, and now we can simply type &lt;code&gt;get /&lt;/code&gt; or invoke IDE IntelliSense, and the IDE will show all possible methods and URLs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvyx3li28tlfylfcls4jx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvyx3li28tlfylfcls4jx.png" alt="VSCode IntelliSense" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please note that the &lt;code&gt;@spec&lt;/code&gt; directive will automatically set the &lt;code&gt;@base&lt;/code&gt; URL based on the OpenAPI spec.&lt;/p&gt;




&lt;p&gt;So that's it for now! I hope you liked this journey.&lt;/p&gt;

&lt;p&gt;We haven't touched the module system, environment variables, and some cool features for VS Code, but let's leave those for the next articles. :)&lt;/p&gt;

&lt;p&gt;Also, if this is interesting, I can share more about the creation process, including ANTLR.js, the VS Code extension, implementing the Language Server Protocol (LSP), IDE IntelliSense, formatting the code, managing the runtime, and more, just let me know in the comments!&lt;/p&gt;

&lt;p&gt;Thank you and Happy coding!&lt;/p&gt;

&lt;p&gt;Nothing can stop you from reaching out to me :)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/httl-lang/HTTL" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bsky.app/profile/httl-lang.bsky.social" rel="noopener noreferrer"&gt;BlueSky&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://discord.gg/UQ6dmJpy" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=HTTL.httl-vscode" rel="noopener noreferrer"&gt;VSCode Marketplace&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rest</category>
      <category>api</category>
      <category>programminglanguages</category>
      <category>http</category>
    </item>
  </channel>
</rss>
