{"id":"l-at-5-3","courseId":"advanced-typescript","lang":"en","kind":"lesson","summary":"Describe JavaScript you did not write with declaration files, type globals and environment variables, and extend existing interfaces and modules with declaration merging and module augmentation.","html":"<p>Not everything your TypeScript touches was written in TypeScript. Cartwheel’s checkout loads an analytics snippet with a <code dir=\"ltr\">&lt;script&gt;</code> tag, uses an old internal shipping calculator written in JavaScript, reads environment variables through Vite, and runs on a lib that is a year behind the browsers it ships to. For each of these, you need to tell the compiler about code it cannot see. That is what declaration files and <code dir=\"ltr\">declare</code> are for.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">declare: it exists, trust me</h2>\n<p><code dir=\"ltr\">declare</code> describes something that exists at runtime but is defined somewhere else. It produces no JavaScript. A file ending in <code dir=\"ltr\">.d.ts</code> contains only such declarations:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">types/analytics.d.ts</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> AnalyticsEvent</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'checkout_started'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'order_placed'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'payment_failed'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">declare</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> cartwheelAnalytics</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  track</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">event</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> AnalyticsEvent</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">props</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Record</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">string</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> void</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">};</span></span></code></pre></div>\n<p>This file has no <code dir=\"ltr\">import</code> or <code dir=\"ltr\">export</code>, so it is a <strong>global script</strong>: everything in it is visible in every file of the project. Now <code dir=\"ltr\">cartwheelAnalytics.track('order_plaecd')</code> is a compile error, which the snippet’s documentation never managed.</p>\n<p>A declaration is an unchecked promise, just like the <code dir=\"ltr\">as</code> casts from earlier sections. If the snippet’s real API differs, TypeScript will happily trust your version. Keep declarations small, write only the parts you use, and put a comment with the library version you checked them against.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Typing a JavaScript package</h2>\n<p>Before writing any declarations for a package, check the two places types usually come from. Many packages ship their own (<code dir=\"ltr\">&quot;types&quot;</code> in their <code dir=\"ltr\">package.json</code>, or <code dir=\"ltr\">.d.ts</code> files beside the JavaScript). For others, the community maintains <code dir=\"ltr\">@types/</code> packages on DefinitelyTyped: <code dir=\"ltr\">npm install -D @types/legacy-shipping-calc</code> if one exists.</p>\n<p>For an internal package with neither, declare the module yourself:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">types/legacy-shipping-calc.d.ts</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">declare</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> module</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'legacy-shipping-calc'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Quote</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">carrier</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">feeCents</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">days</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> quote</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">country</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">weightGrams</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Quote</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[];</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>The shorthand <code dir=\"ltr\">declare module 'legacy-shipping-calc';</code> with no body also works, and makes every import <code dir=\"ltr\">any</code>. Treat it as a temporary escape hatch during a migration, not as a solution.</p>\n<p>Keep hand-written declarations together in one folder, such as <code dir=\"ltr\">types/</code>, and make sure your tsconfig’s <code dir=\"ltr\">include</code> covers it; a <code dir=\"ltr\">.d.ts</code> file the compiler never sees does nothing, and the resulting “Could not find a declaration file for module” error is confusing the first time. Note too that this file is global, like the analytics one: a <code dir=\"ltr\">declare module 'name'</code> block with a string name is how you describe a package from a script file.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Declaration merging</h2>\n<p>Some declarations with the same name combine instead of clashing. The one you will use most: <strong>interfaces merge</strong>.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">interface</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CheckoutConfig</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">currency</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">interface</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CheckoutConfig</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">giftWrap</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> boolean</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> config</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CheckoutConfig</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { currency: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'EGP'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, giftWrap: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// both required</span></span></code></pre></div>\n<p>Type aliases never merge; two <code dir=\"ltr\">type CheckoutConfig</code> declarations are a “Duplicate identifier” error. This is the main practical difference between <code dir=\"ltr\">type</code> and <code dir=\"ltr\">interface</code>, and it is why libraries that expect to be extended expose interfaces.</p>\n<p>Merging is how you fill gaps in the standard library. The playground in this course uses the ES2022 lib, so <code dir=\"ltr\">Array.prototype.findLast</code> (ES2023) is missing from its types even though every current browser has it. Merging into the global <code dir=\"ltr\">Array&lt;T&gt;</code> interface adds it:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">interface</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Array</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">T</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  findLast</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">predicate</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">value</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">index</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">array</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[]) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> unknown</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> undefined</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>In a real project, the better fix is the right <code dir=\"ltr\">lib</code> or <code dir=\"ltr\">target</code> setting. Merging is for the cases where config cannot express what the runtime actually has, such as a polyfill you ship yourself.</p>\n<p>It is also how Vite types your environment variables. Its client types declare a global <code dir=\"ltr\">ImportMetaEnv</code> interface, and your project merges into it:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">src/vite-env.d.ts</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/// &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">reference</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> types</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"vite/client\"</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\"> /></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">interface</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ImportMetaEnv</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  readonly</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> VITE_API_URL</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  readonly</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> VITE_PAYMENTS_KEY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<h2 id=\"sec-4\" tabindex=\"-1\">Module augmentation</h2>\n<p>To add to a type that lives <em>inside</em> a module, open the module with <code dir=\"ltr\">declare module</code> from a file that is itself a module:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">src/theme.d.ts</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'cartwheel-ui'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">declare</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> module</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'cartwheel-ui'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  interface</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Theme</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">    brand</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">primary</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">onPrimary</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>The <code dir=\"ltr\">Theme</code> interface inside <code dir=\"ltr\">cartwheel-ui</code> now has a <code dir=\"ltr\">brand</code> field everywhere it is used. To add to the global scope from a module file, use <code dir=\"ltr\">declare global { interface Window { dataLayer: unknown[] } }</code>.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Declaring when you meant to augment</span></p>\n<p>The same <code dir=\"ltr\">declare module 'cartwheel-ui' { … }</code> block means two different things depending on its file. In a file with at least one <code dir=\"ltr\">import</code> or <code dir=\"ltr\">export</code>, it <strong>augments</strong> the existing module. In a file with none, it <strong>declares</strong> a brand-new ambient module that shadows the real package, and every other export of <code dir=\"ltr\">cartwheel-ui</code> disappears. If an augmentation seems to wipe out a library, add <code dir=\"ltr\">export {}</code> or the import line, and check again.</p>\n</aside>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Publishing your own types</span></p>\n<p>If you publish a package, let TypeScript generate its declarations with <code dir=\"ltr\">&quot;declaration&quot;: true</code>. With <code dir=\"ltr\">isolatedDeclarations</code> (TypeScript 5.5+), the compiler requires explicit types on everything you export, so faster tools can generate <code dir=\"ltr\">.d.ts</code> files one file at a time without running the type checker. It is a good habit for any library’s public API anyway.</p>\n</aside>\n<p>In the exercise you will declare the analytics global, add <code dir=\"ltr\">findLast</code> to the playground’s lib, and extend a config interface by merging. Last lesson: bringing a whole JavaScript codebase across.</p>\n","toc":[{"id":"sec-1","text":"declare: it exists, trust me","level":2},{"id":"sec-2","text":"Typing a JavaScript package","level":2},{"id":"sec-3","text":"Declaration merging","level":2},{"id":"sec-4","text":"Module augmentation","level":2}],"takeaways":["A <code dir=\"ltr\">.d.ts</code> file contains only types; <code dir=\"ltr\">declare</code> describes something that exists at runtime but is defined elsewhere.","Check for built-in types or an <code dir=\"ltr\">@types/</code> package before writing declarations, and write only the parts of a library you use.","Interfaces with the same name in the same scope merge into one; type aliases never merge, which is why extensible library types are interfaces.","<code dir=\"ltr\">declare module 'pkg' { … }</code> in a file that has an import or export augments that module; in a file without one it declares a whole new ambient module.","<code dir=\"ltr\">declare global { … }</code> inside a module adds to the global scope, for example a property on <code dir=\"ltr\">Window</code> or a field on <code dir=\"ltr\">ImportMetaEnv</code>."],"further":[{"title":"Declaration Files - Introduction","url":"https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html"},{"title":"Declaration Merging","url":"https://www.typescriptlang.org/docs/handbook/declaration-merging.html"},{"title":"Env Variables and Modes (Vite)","url":"https://vite.dev/guide/env-and-mode.html"}],"quiz":[{"q":"<p>You add <code dir=\"ltr\">import { quote } from 'legacy-shipping-calc'</code>, a JavaScript package with no types. What should you try first?</p>","options":[{"text":"Write a full <code dir=\"ltr\">.d.ts</code> describing every export of the package.","why":"Writing declarations is the last resort, and even then you only need the parts you use."},{"text":"Add <code dir=\"ltr\">declare module 'legacy-shipping-calc';</code> so everything is <code dir=\"ltr\">any</code>.","why":"That silences the error by turning every import into <code dir=\"ltr\">any</code>, which hides exactly the mistakes types are meant to catch."},{"text":"Cast the import with <code dir=\"ltr\">as any</code> at each call site.","why":"The import itself fails to type-check first, and scattering <code dir=\"ltr\">any</code> makes things worse, not better."},{"text":"Check whether the package ships its own types or has an <code dir=\"ltr\">@types/legacy-shipping-calc</code> package.","why":"Correct. Most popular packages are typed one way or the other; only write declarations when neither exists."}],"answer":3},{"q":"<p>What is <code dir=\"ltr\">keyof CheckoutConfig</code> after these two declarations in the same scope?</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">interface</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CheckoutConfig</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">currency</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">interface</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CheckoutConfig</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">giftWrap</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> boolean</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">'currency' | 'giftWrap'</code>","why":"Correct. Interfaces with the same name in the same scope merge their members."},{"text":"<code dir=\"ltr\">'giftWrap'</code>, because the second declaration replaces the first","why":"Interfaces never replace each other; they merge. Duplicate type aliases, by contrast, are an error."},{"text":"It is an error, “Duplicate identifier ‘CheckoutConfig’”.","why":"That error is what you would get with two <code dir=\"ltr\">type</code> aliases. Interfaces are designed to merge."}],"answer":0},{"q":"<p>A file <code dir=\"ltr\">vite-env.d.ts</code> contains only <code dir=\"ltr\">interface ImportMetaEnv { readonly VITE_API_URL: string }</code> (plus Vite’s reference directive). What does it do?</p>","options":[{"text":"It replaces Vite’s <code dir=\"ltr\">ImportMetaEnv</code>, removing the built-in <code dir=\"ltr\">MODE</code> and <code dir=\"ltr\">DEV</code> fields.","why":"Interfaces merge, so Vite’s own fields stay and yours are added."},{"text":"It adds <code dir=\"ltr\">VITE_API_URL</code> to the existing global <code dir=\"ltr\">ImportMetaEnv</code>, so <code dir=\"ltr\">import.meta.env.VITE_API_URL</code> is a <code dir=\"ltr\">string</code>.","why":"Correct. The file has no imports or exports, so it is a global script and its interface merges with Vite’s global one."},{"text":"It defines the variable’s value at build time.","why":"Declarations never produce values; the value still comes from your <code dir=\"ltr\">.env</code> file."}],"answer":1},{"q":"<p><code dir=\"ltr\">types/ui.d.ts</code> contains only <code dir=\"ltr\">declare module 'cartwheel-ui' { interface Theme { brand: string } }</code>. Afterwards, every import from <code dir=\"ltr\">cartwheel-ui</code> is missing its other exports. Why?</p>","options":[{"text":"Interfaces cannot be declared inside <code dir=\"ltr\">declare module</code>.","why":"They can; module augmentation is mostly interfaces."},{"text":"The file needs a <code dir=\"ltr\">/// &lt;reference&gt;</code> directive.","why":"A reference directive does not change whether the block augments or declares a module."},{"text":"Without any import or export, the file is a script, so the block declares a brand-new ambient module that shadows the real one; add <code dir=\"ltr\">export {}</code> or an import.","why":"Correct. In a module file, <code dir=\"ltr\">declare module 'cartwheel-ui'</code> augments; in a script file, it declares from scratch."}],"answer":2}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"// ---- Vendor snippet (do not edit) ----\n// In the real app this arrives in a <script> tag; here we simulate it.\nconst vendorQueue: string[] = [];\n(globalThis as Record<string, unknown>).cartwheelAnalytics = {\n  sent: vendorQueue,\n  track(event: string, props: Record<string, string | number> = {}) {\n    vendorQueue.push(`${event} ${JSON.stringify(props)}`);\n  },\n};\n// ---------------------------------------\n\ntype AnalyticsEvent = 'checkout_started' | 'order_placed' | 'payment_failed';\n\n// \"Library\" config interface (pretend it lives in a package you cannot edit).\ninterface CheckoutConfig {\n  currency: string;\n}\n\n// Your declarations go here.\n\ntype Order = { id: number; status: 'processing' | 'shipped' | 'delivered'; totalCents: number };\n\nconst orders: Order[] = [\n  { id: 1041, status: 'shipped', totalCents: 4500 },\n  { id: 1042, status: 'shipped', totalCents: 18900 },\n  { id: 1043, status: 'processing', totalCents: 1200 },\n];\n\nfunction latestShipped(list: Order[]): Order | undefined {\n  return list.findLast((o) => o.status === 'shipped');\n}\n\nfunction placeOrder(order: Order): void {\n  cartwheelAnalytics.track('order_placed', { orderId: order.id, totalCents: order.totalCents });\n}\n\nconst config: CheckoutConfig = { currency: 'EGP', giftWrap: true };\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<keyof CheckoutConfig, 'currency' | 'giftWrap'>>;\ntype T2 = Expect<Equal<ReturnType<typeof latestShipped>, Order | undefined>>;\n\nfunction typeTests() {\n  // @ts-expect-error: not a known analytics event\n  cartwheelAnalytics.track('ordr_placed');\n  // @ts-expect-error: property values must be strings or numbers\n  cartwheelAnalytics.track('checkout_started', { express: true });\n}\n\nplaceOrder(latestShipped(orders)!);\nconsole.log(latestShipped(orders)?.id, cartwheelAnalytics.sent, config);\n"},"solution":{"ts":"// ---- Vendor snippet (do not edit) ----\n// In the real app this arrives in a <script> tag; here we simulate it.\nconst vendorQueue: string[] = [];\n(globalThis as Record<string, unknown>).cartwheelAnalytics = {\n  sent: vendorQueue,\n  track(event: string, props: Record<string, string | number> = {}) {\n    vendorQueue.push(`${event} ${JSON.stringify(props)}`);\n  },\n};\n// ---------------------------------------\n\ntype AnalyticsEvent = 'checkout_started' | 'order_placed' | 'payment_failed';\n\n// \"Library\" config interface (pretend it lives in a package you cannot edit).\ninterface CheckoutConfig {\n  currency: string;\n}\n\n// The analytics snippet, as far as we use it (checked against snippet v3).\ndeclare var cartwheelAnalytics: {\n  readonly sent: readonly string[];\n  track(event: AnalyticsEvent, props?: Record<string, string | number>): void;\n};\n\n// ES2023 method, present in every current browser but missing from the ES2022 lib.\ninterface Array<T> {\n  findLast(predicate: (value: T, index: number, array: T[]) => unknown): T | undefined;\n}\n\n// Merges with the library's CheckoutConfig.\ninterface CheckoutConfig {\n  giftWrap: boolean;\n}\n\ntype Order = { id: number; status: 'processing' | 'shipped' | 'delivered'; totalCents: number };\n\nconst orders: Order[] = [\n  { id: 1041, status: 'shipped', totalCents: 4500 },\n  { id: 1042, status: 'shipped', totalCents: 18900 },\n  { id: 1043, status: 'processing', totalCents: 1200 },\n];\n\nfunction latestShipped(list: Order[]): Order | undefined {\n  return list.findLast((o) => o.status === 'shipped');\n}\n\nfunction placeOrder(order: Order): void {\n  cartwheelAnalytics.track('order_placed', { orderId: order.id, totalCents: order.totalCents });\n}\n\nconst config: CheckoutConfig = { currency: 'EGP', giftWrap: true };\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<keyof CheckoutConfig, 'currency' | 'giftWrap'>>;\ntype T2 = Expect<Equal<ReturnType<typeof latestShipped>, Order | undefined>>;\n\nfunction typeTests() {\n  // @ts-expect-error: not a known analytics event\n  cartwheelAnalytics.track('ordr_placed');\n  // @ts-expect-error: property values must be strings or numbers\n  cartwheelAnalytics.track('checkout_started', { express: true });\n}\n\nplaceOrder(latestShipped(orders)!);\nconsole.log(latestShipped(orders)?.id, cartwheelAnalytics.sent, config);\n"},"checks":[{"id":"types","typecheck":true},{"id":"latest","test":"return latestShipped(orders).id === 1042 && latestShipped([]) === undefined;"},{"id":"tracks","test":"return cartwheelAnalytics.sent.some((s) => s.startsWith('order_placed') && s.includes('1042'));"}],"title":"Declare what the compiler can't see","prompt":"<p>The app code below already works at runtime, but it does not type-check. Without editing the app code or the tests, add declarations in the marked spot:</p>\n<ol>\n<li>Declare the global <code dir=\"ltr\">cartwheelAnalytics</code> from the vendor snippet: a <code dir=\"ltr\">track(event, props?)</code> method where <code dir=\"ltr\">event</code> is an <code dir=\"ltr\">AnalyticsEvent</code> and <code dir=\"ltr\">props</code> maps strings to strings or numbers, plus a read-only <code dir=\"ltr\">sent</code> list of strings.</li>\n<li>Add <code dir=\"ltr\">findLast</code> to arrays (it exists in the browser but not in the playground’s ES2022 lib), returning the element type or <code dir=\"ltr\">undefined</code>.</li>\n<li>Extend <code dir=\"ltr\">CheckoutConfig</code> with a required <code dir=\"ltr\">giftWrap: boolean</code>, without editing the original interface.</li>\n</ol>\n","hints":["A global that exists at runtime is described with <code dir=\"ltr\">declare var name: { … };</code> (this file is a script, so it is global).","Global lib interfaces can be extended by declaring an interface with the same name: <code dir=\"ltr\">interface Array&lt;T&gt; { findLast(…): T | undefined; }</code>.","Interfaces merge: write a second <code dir=\"ltr\">interface CheckoutConfig { giftWrap: boolean; }</code>."],"explanation":"<p><code dir=\"ltr\">declare var</code> tells TypeScript that <code dir=\"ltr\">cartwheelAnalytics</code> exists at runtime without emitting any code, and the precise <code dir=\"ltr\">AnalyticsEvent</code> parameter turns typos into compile errors that the snippet itself would never report. Because this file is a script, a second <code dir=\"ltr\">interface Array&lt;T&gt;</code> merges with the lib’s global <code dir=\"ltr\">Array</code>, filling the gap between the ES2022 lib and the browser. The second <code dir=\"ltr\">CheckoutConfig</code> declaration merges with the first, which is exactly how module augmentation extends a library’s interfaces. All three are unchecked promises about the runtime, so they should stay minimal and say what they were checked against.</p>\n","checkLabels":{"types":"The file type-checks with zero errors, including the type tests","latest":"<code dir=\"ltr\">latestShipped</code> returns the last shipped order","tracks":"<code dir=\"ltr\">placeOrder</code> sends an <code dir=\"ltr\">order_placed</code> event"}},"words":740,"readMinutes":4}