<?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>Forem: qingkuai</title>
    <description>The latest articles on Forem by qingkuai (@qingkuai).</description>
    <link>https://forem.com/qingkuai</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3866240%2Fdeaa7599-7901-466d-ac86-c3d63efc2d02.png</url>
      <title>Forem: qingkuai</title>
      <link>https://forem.com/qingkuai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/qingkuai"/>
    <language>en</language>
    <item>
      <title>TypeScript in Vue/Svelte Is Great, Until It Isn't: Fixing the Missing Type Flow</title>
      <dc:creator>qingkuai</dc:creator>
      <pubDate>Tue, 07 Apr 2026 16:58:40 +0000</pubDate>
      <link>https://forem.com/qingkuai/typescript-in-vuesvelte-is-great-until-it-isnt-fixing-the-missing-type-flow-2m44</link>
      <guid>https://forem.com/qingkuai/typescript-in-vuesvelte-is-great-until-it-isnt-fixing-the-missing-type-flow-2m44</guid>
      <description>&lt;p&gt;Frontend component syntax is often split into two families: template-language frameworks (Vue, Svelte, Qingkuai) and JSX/TSX frameworks (React, Solid).&lt;/p&gt;

&lt;p&gt;Template-language frameworks usually give you a cleaner script block for native JS/TS and a concise template syntax for rendering. JSX/TSX gives you maximum flexibility because the whole file is expression-friendly JavaScript, but it also blends markup, style, and logic more aggressively.&lt;/p&gt;

&lt;p&gt;Both models are valid. This article focuses on one long-standing pain point in template-language frameworks: &lt;strong&gt;how to deliver a truly strong TypeScript experience&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Scope note: observations about framework behavior are based on the setups I tested while writing this article (April 2026), and may differ across toolchain versions.&lt;/p&gt;




&lt;h2&gt;
  
  
  1) Type Declarations Inside Components
&lt;/h2&gt;

&lt;p&gt;Mainstream template frameworks already support TypeScript well, but some important workflows still feel awkward. The first one is &lt;code&gt;props&lt;/code&gt; typing.&lt;/p&gt;

&lt;p&gt;In Vue and Svelte, the common approach is a compiler-level declaration mechanism (terminology differs by framework). It works for simple props, but in generic scenarios you often need extra syntax on the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag itself, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;generics=&lt;/span&gt;&lt;span class="s"&gt;"T extends { id: number; name: string }"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This partially breaks the core promise of template languages: a clean JS/TS authoring environment inside the script block.&lt;/p&gt;

&lt;p&gt;There is also hidden cost around visibility rules. In practice, external imported types are usually reachable from &lt;code&gt;generics&lt;/code&gt;, while types declared inside the script block may not be.&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%2Fqthogxtr7iev5myyzvj3.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%2Fqthogxtr7iev5myyzvj3.png" alt="Svelte access external types" width="800" height="195"&gt;&lt;/a&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%2Fsa58tlddo0p9tpqa0hwo.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%2Fsa58tlddo0p9tpqa0hwo.png" alt="Vue access internal types" width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My working hypothesis is that this is related to generic-component export shape handling: the language service may need to treat the default export as a function, and because &lt;code&gt;import&lt;/code&gt; declarations are module-top-level only, they get lifted outside that function scope, creating the visibility mismatch.&lt;/p&gt;

&lt;p&gt;This hypothesis is based on observed behavior, not on confirmed framework-internal implementation details.&lt;/p&gt;

&lt;p&gt;Regardless of exact implementation details, the result is more cognitive overhead.&lt;/p&gt;

&lt;p&gt;One practical design choice is to keep &lt;code&gt;Props&lt;/code&gt; as a global component type declaration. If you declare &lt;code&gt;Props&lt;/code&gt;, you have declared your prop types. This keeps the script authoring flow close to plain TS.&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="c1"&gt;// TypeScript project&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ExternalData&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;./types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;Props&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Data&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;ExternalData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful side effect: in pure JavaScript projects, you can still declare component prop types via JSDoc and keep type checking/completion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * @template {{id: number; name: string}} T
 * @typedef {object} Props
 * @property {T} data
 */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2) Passing Generic Type Arguments at Call Sites
&lt;/h2&gt;

&lt;p&gt;Another major pain point is generic arguments from the caller side.&lt;/p&gt;

&lt;p&gt;In both &lt;code&gt;Vue&lt;/code&gt; and &lt;code&gt;Svelte&lt;/code&gt;, in the setups I tested, trying to pass generic type arguments directly at the usage site leads to parser errors in template syntax. That means even with clear business context, callers cannot explicitly pass generic arguments to narrow and proactively constrain component types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// desired call-site intent (illustrative)&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UserSummary&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&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="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="c1"&gt;// expected: caller pins the generic boundary to UserSummary&lt;/span&gt;
&lt;span class="c1"&gt;// observed in tested Vue/Svelte template setups:&lt;/span&gt;
&lt;span class="c1"&gt;// parser error for this syntax, and no equivalent&lt;/span&gt;
&lt;span class="c1"&gt;// explicit generic argument channel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not just a syntax preference issue. It directly impacts type precision in real-world component composition.&lt;/p&gt;




&lt;h2&gt;
  
  
  3) Slot Context Type Inference
&lt;/h2&gt;

&lt;p&gt;Template languages have a structural advantage here.&lt;/p&gt;

&lt;p&gt;Most template systems define slot outlets with a &lt;code&gt;slot&lt;/code&gt; element and attach outbound data directly on that outlet. That gives the compiler a natural anchor for slot context inference.&lt;/p&gt;

&lt;p&gt;By contrast, &lt;code&gt;JSX/TSX&lt;/code&gt; frameworks do not have native slots. They usually emulate them with &lt;code&gt;children&lt;/code&gt;, which often requires manual function-type declarations for parameters and return values.&lt;/p&gt;

&lt;p&gt;In the setups I tested, mainstream template-language frameworks still do not provide full end-to-end automatic slot-context inference. Vue supports manual slot-context typing. Svelte4 used &lt;code&gt;slot&lt;/code&gt; but did not support direct slot-type annotation there; Svelte5 introduced &lt;code&gt;Snippet&lt;/code&gt;, but still depends on manual context typing.&lt;/p&gt;

&lt;p&gt;From an engineering perspective, though, automatic inference is absolutely feasible through a combined path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;compile-time static analysis,&lt;/li&gt;
&lt;li&gt;IR markers that preserve semantic structure,&lt;/li&gt;
&lt;li&gt;type extraction via TypeScript language services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this model, a component can avoid extra internal type annotations while callers still get full completion and inference. This can also work in pure JavaScript projects, because type recovery can be done in tooling rather than only from explicit TS annotations:&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%2Fclg8r2geqkpow97qp8t7.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%2Fclg8r2geqkpow97qp8t7.png" alt="Slot types inference" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The bigger win is IDE navigation quality. With proper slot-context inference, Go to Definition and Find References can jump to the real source definition instead of a detached type alias layer.&lt;/p&gt;

&lt;p&gt;In complex components, this saves a lot of time during reading and refactoring.&lt;/p&gt;




&lt;h2&gt;
  
  
  4) Component Export Type Readability
&lt;/h2&gt;

&lt;p&gt;Most template frameworks auto-infer component export types, which is good. But readability still varies.&lt;/p&gt;

&lt;p&gt;When inferred types are too verbose or leak internal framework types, developers lose confidence quickly. Better export type design can make hover information dramatically easier to understand.&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%2Fz24aqvlc4oefuvf7zdjm.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%2Fz24aqvlc4oefuvf7zdjm.png" alt="Svelte export types" width="800" height="166"&gt;&lt;/a&gt;&lt;br&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%2Fl1msuegpbsrbqkuizhn3.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%2Fl1msuegpbsrbqkuizhn3.png" alt="Vue export types" width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With a clearer export type structure, this can be improved significantly.&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%2Fe2a0tm5dg961g1jemfj5.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%2Fe2a0tm5dg961g1jemfj5.png" alt="Qingkuai export types" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Many developers also expect to hover directly on a component tag and see its type, but support here is still uneven across frameworks.&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%2Fwoo2dkavvtp2gotmub1f.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%2Fwoo2dkavvtp2gotmub1f.png" alt="Vue component tag hover" width="800" height="278"&gt;&lt;/a&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%2F8ihk0a1sbghdiblbvda4.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%2F8ihk0a1sbghdiblbvda4.png" alt="Svelte component tag hover" width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the language service extracts the final component type through TypeScript &lt;code&gt;TypeChecker&lt;/code&gt; and returns it directly in tag hover, implementation is straightforward and the developer experience improves immediately.&lt;/p&gt;

&lt;p&gt;Minimal illustration:&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%2Fl488lqel742jwo5ckewa.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%2Fl488lqel742jwo5ckewa.png" alt="Qingkuai component tag hover" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5) Why This Matters: Type Flow Continuity
&lt;/h2&gt;

&lt;p&gt;These topics may look separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;component type declarations,&lt;/li&gt;
&lt;li&gt;generic argument passing,&lt;/li&gt;
&lt;li&gt;slot context inference,&lt;/li&gt;
&lt;li&gt;export type readability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But they are really one problem: &lt;strong&gt;type-flow continuity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once type information breaks anywhere across this chain,&lt;/p&gt;

&lt;p&gt;component source -&amp;gt; compiler output -&amp;gt; language service -&amp;gt; IDE interaction,&lt;/p&gt;

&lt;p&gt;developers pay the cost through manual annotations, fragile conventions, and slower refactoring.&lt;/p&gt;

&lt;p&gt;A practical strategy is to co-design compiler and language service:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep template syntax lightweight and avoid adding authoring friction.&lt;/li&gt;
&lt;li&gt;Preserve enough semantic markers during compilation.&lt;/li&gt;
&lt;li&gt;Recover and rehydrate high-quality types in language tooling.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For framework authors, this means continuing to harden edge cases (complex generics, conditional types, cross-file symbol mapping) and validating stability/performance in larger codebases.&lt;/p&gt;




&lt;h2&gt;
  
  
  Limitations and Trade-offs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This article focuses on type-system ergonomics, not runtime performance benchmarking.&lt;/li&gt;
&lt;li&gt;Some claims are based on tool behavior observed in tested setups and should be re-validated in your own version matrix.&lt;/li&gt;
&lt;li&gt;Examples are intentionally minimal to show type-flow boundaries; production code may need additional constraints.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Template-language frameworks are not inherently weaker for TypeScript ergonomics. The key is whether the type system is treated as a first-class part of both language design and tooling architecture.&lt;/p&gt;

&lt;p&gt;If you want to evaluate these ideas hands-on, you can compare behavior quickly in docs at &lt;a href="https://cn.qingkuai.dev" rel="noopener noreferrer"&gt;https://cn.qingkuai.dev&lt;/a&gt; and the live playground at &lt;a href="https://try.qingkuai.dev" rel="noopener noreferrer"&gt;https://try.qingkuai.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>vue</category>
      <category>svelte</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
