{"id":4407,"date":"2024-12-30T13:33:32","date_gmt":"2024-12-30T18:33:32","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=4407"},"modified":"2024-12-30T13:33:33","modified_gmt":"2024-12-30T18:33:33","slug":"typescript-without-build-tools","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/typescript-without-build-tools\/","title":{"rendered":"TypeScript without Build Tools"},"content":{"rendered":"\n<p><strong>TL;DR:<\/strong> <a href=\"#the-tools\">There are a bunch of tools<\/a> these days that allow you to write in TypeScript and&#8230; <em>just not think about it<\/em> much more than that. You needn&#8217;t deal with converting that code into JavaScript yourself.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>Here&#8217;s what has me thinking about all that.<\/p>\n\n\n\n<p>We&#8217;ve got a monorepo at <a href=\"https:\/\/codepen.io\/\">work<\/a>.<\/p>\n\n\n\n<p>We&#8217;ve been moving to TypeScript for years, with mostly positive results. I&#8217;m fairly convinced that the code output from writing in TypeScript is better and the experience of writing and editing existing TypeScript code is better DX.<\/p>\n\n\n\n<p>It&#8217;s not without challenges though. I hate wasting time futzing with types when I just know it really isn&#8217;t improving the quality of my code I&#8217;m just fighting with a machine. This concern is multiplied when you see your team doing the same thing. <\/p>\n\n\n\n<p>Another challenge is the part where <strong>you actually have to produce JavaScript<\/strong>. This is not a difficult job in and of itself. There are loads of tools that do this from <a href=\"https:\/\/www.typescriptlang.org\/docs\/handbook\/compiler-options.html\">the canonical <code>tsc<\/code><\/a> to tools that do no type checking but can very speedily compile TS to JS (like <a href=\"https:\/\/esbuild.github.io\/content-types\/#typescript\">esbuild<\/a>). But sometimes, you still have to <em>do<\/em> it.<\/p>\n\n\n\n<p>One example from our monorepo is that we have some components that we&#8217;d like to write in <code>.tsx<\/code>, but they can be consumed by a number of different site building tools (e.g. a Next.js site, an Astro site, an classic Just Load React On The Page site, etc). The most sane way of handling this is processing the TS into JS ahead of time. That way whatever site that wants to import them can do it as native, normal JavaScript ESM stuff.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">monorepo<br>  website-1<br>  website-2<br>  packages<br>    components<br>      dist<br>        MyVeryImportantComponent<br>          index.js<br>      src<br>        MyVeryImportantComponent<br>          index.tsx   <\/pre>\n\n\n\n<p>Now I need to make a <strong>build process<\/strong> to do this. I&#8217;ll probably write a little script. <code>npm run process-packages<\/code> or whatever, fine. I&#8217;ll probably use <code>tsc<\/code> for this on purpose, knowing that it isn&#8217;t the fastest, but it&#8217;s the one that is capable of actually throwing errors when there are TypeScript problems. This is useful because I can call my script in a pre-commit hook in Git, for example, to prevent myself and my team from committing bad TypeScript. I can call the script in CI as well which is additional protection.<\/p>\n\n\n\n<p>So now I have <code>tsc<\/code> building from <code>src<\/code> to <code>dist<\/code>, great. But TypeScript explicitly <a href=\"https:\/\/github.com\/Microsoft\/TypeScript\/issues\/30835\">will never build an &#8220;other files&#8221; copy machine<\/a>. Well that&#8217;s annoying. What about my other stuff?<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">packages<br>  components<br>    src<br>      MyVeryImportantComponent<br>        index.tsx<br>        queries.graphql<br>        styles.module.scss<\/pre>\n\n\n\n<p>Now I have to wire up some <em>new<\/em> machine to copy anything that isn&#8217;t TypeScript over from <code>src<\/code> to <code>dist<\/code>. Blech. Fine, I&#8217;ll do it. <\/p>\n\n\n\n<p>But this is all complicated by the fact that this script we&#8217;re building needs to be able to run two ways:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>As a builder, where you call it and it does the job<\/li>\n\n\n\n<li>As a watcher, where it runs the build when any pertinent file changes<\/li>\n<\/ol>\n\n\n\n<p>It&#8217;s unacceptable DX to expect a developer to run a build command after every file change manually. So we have to build our own little watcher too. I guess it&#8217;s <a href=\"https:\/\/github.com\/paulmillr\/chokidar\">Chokidar<\/a> time? I hate piling on dependencies, but that&#8217;s the only watcher that&#8217;s ever felt truly reliable to me.<\/p>\n\n\n\n<p>I don&#8217;t mean to paint this picture too negatively. This is all doable. <em>This is the job.<\/em> There are tools to help with this that get better over time. But wow, it&#8217;s a lot when you consider it was just TypeScript, this invisible layer of special code helping syntax, that pushed us this far into toolsville. <\/p>\n\n\n\n<p>I&#8217;d rather focus on the positive stuff I&#8217;m seeing here. As the years tick by, and TypeScripts popularity remains high, the surrounding tools that deal in JavaScript are more and more fine with &#8220;just leave it as TypeScript.&#8221; I find that interesting! <\/p>\n\n\n\n<p>This &#8220;native&#8221; support of TypeScript is a product choice. <em>We know some of our customers write and prefer TypeScript so we&#8217;ll make it easier for them.<\/em> But there is no Official\u2122 way to do this. The product needs to decide how they are going to handle it. Are they going to check types for you and alert you (somehow? somewhere?) to type problems? Or are they leaving type problems to you, and if the code compiles to JavaScript, so it shall be. What version of TypeScript is it going to support? Is configuration necessary?  <\/p>\n\n\n\n<p>So what are these products?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-tools\">Tools &amp; Products That Let You Write TypeScript &#8220;Natively&#8221;<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Frameworks<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/docs.astro.build\/en\/guides\/typescript\/\">Astro<\/a>: <em>&#8220;Astro ships with built-in support for\u00a0<a href=\"https:\/\/www.typescriptlang.org\/\">TypeScript<\/a>. You can import\u00a0<code>.ts<\/code>\u00a0and\u00a0<code>.tsx<\/code>\u00a0files in your Astro project, write TypeScript code directly inside your\u00a0<a href=\"https:\/\/docs.astro.build\/en\/basics\/astro-components\/#the-component-script\">Astro component<\/a>, and even use an\u00a0<a href=\"https:\/\/docs.astro.build\/en\/guides\/configuring-astro\/#the-astro-config-file\"><code>astro.config.ts<\/code><\/a>\u00a0file for your Astro configuration if you like.&#8221;<\/em><\/li>\n\n\n\n<li><a href=\"https:\/\/nextjs.org\/docs\/pages\/api-reference\/config\/typescript\">Next.js<\/a>: <em>&#8220;Next.js comes with built-in TypeScript, automatically installing the necessary packages and configuring the proper settings&#8221;<\/em><\/li>\n\n\n\n<li>This is pretty common in UI meta frameworks&#8230; <a href=\"https:\/\/nuxt.com\/docs\/guide\/concepts\/typescript\">Nuxt<\/a>, <a href=\"https:\/\/remix.run\/docs\/hi\/main\/guides\/typescript\">Remix<\/a>, <a href=\"https:\/\/svelte.dev\/docs\/svelte\/typescript\">SvelteKit<\/a>, <a href=\"https:\/\/docs.redwoodjs.com\/docs\/typescript\/introduction\">Redwood<\/a>, etc. This, likely, is the thing that pushed other products to do the same. <\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Runtimes<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/docs.deno.com\/runtime\/fundamentals\/typescript\/\">Deno<\/a>: <em>&#8220;TypeScript is a first class language in Deno, just like JavaScript or WebAssembly.&#8221;<\/em> You can just <code>deno run script.ts<\/code>, deploy <code>.ts<\/code> files to their cloud service, and there is a type checking command. See what I mean about it being a product choice?<\/li>\n\n\n\n<li><a href=\"https:\/\/bun.sh\/docs\/runtime\/typescript\">Bun<\/a>: <em>&#8220;Bun treats TypeScript as a first-class citizen.&#8221;<\/em><\/li>\n\n\n\n<li><a href=\"https:\/\/developers.cloudflare.com\/workers\/languages\/typescript\/\">Cloudflare Workers:<\/a> <em>&#8220;TypeScript is a first-class language on Cloudflare Workers.&#8221;<\/em><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Bundlers<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/vite.dev\/guide\/features#typescript\">Vite<\/a>: <em>&#8220;Vite supports importing\u00a0<code>.ts<\/code>\u00a0files out of the box.&#8221;<\/em><\/li>\n\n\n\n<li><a href=\"https:\/\/esbuild.github.io\/content-types\/#typescript\">esbuild<\/a>: <em>&#8220;&#8230;esbuild has built-in support for parsing TypeScript syntax and discarding the type annotations.&#8221;<\/em><\/li>\n\n\n\n<li><a href=\"https:\/\/parceljs.org\/languages\/typescript\/\">Parcel<\/a>: <em>&#8220;Parcel supports TypeScript out of the box without any additional configuration.&#8221;<\/em><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Meta<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/tsx.is\/\">tsx<\/a>: The big thing here is the tsx project, or &#8220;TypeScript Execute&#8221;. A lot of times what you&#8217;re trying to do in Node is <code>node script.ts<\/code>, like in an npm script, but you can&#8217;t, because Node doesn&#8217;t support TypeScript &#8220;natively&#8221;.  But replace <code>node<\/code> with <code>tsx<\/code>, and it works. <\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Drumroll?<\/h2>\n\n\n\n<p>I started writing this post a few weeks ago, and now I&#8217;ve just seen: <a href=\"https:\/\/www.totaltypescript.com\/typescript-is-coming-to-node-23\">Node.js Now Supports TypeScript By Default<\/a>.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Node 23 will soon be able to run TypeScript files without any extra configuration.<\/p>\n\n\n\n<p>You can run\u00a0<code>node index.ts<\/code>\u00a0with no further flags<\/p>\n\n\n\n<p>Node will strip out the types using a version of\u00a0<code>swc<\/code>, then run the resulting code.<\/p>\n<\/blockquote>\n\n\n\n<p>Well there ya have it. Feels like we were on to something there eh?<\/p>\n\n\n\n<p>I feel like this pushed it more into Official\u2122 territory and now this will be less of a product-level choice and more of a &#8220;leverage what the technology already does&#8221; choice. For instance, products don&#8217;t have do anything special or build additional technology to support TypeScript, they&#8217;ll just do what Node does. And they won&#8217;t have to fret over &#8220;well should we do type checking?&#8221; because they can just follow in the steps of what the tool already does (just strip them). <\/p>\n","protected":false},"excerpt":{"rendered":"<p>You can build your own TypeScript build process, and you might want to if you need true type checking and compatibility with a wider ecosystem of tools. But lots of tools, including now Node itself, just accept TypeScript as if it were JavaScript. <\/p>\n","protected":false},"author":1,"featured_media":4899,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[3,147,39],"class_list":["post-4407","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-javascript","tag-node","tag-typography"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/12\/process-ts.png?fit=1024%2C538&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/4407","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=4407"}],"version-history":[{"count":7,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/4407\/revisions"}],"predecessor-version":[{"id":4902,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/4407\/revisions\/4902"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/4899"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=4407"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=4407"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=4407"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}