{"id":12680,"date":"2025-04-25T18:20:50","date_gmt":"2025-04-25T18:20:50","guid":{"rendered":"https:\/\/cheesecakelabs.com\/blog\/"},"modified":"2025-04-25T21:39:31","modified_gmt":"2025-04-25T21:39:31","slug":"how-to-build-framework-agnostic-web-sdks","status":"publish","type":"post","link":"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/","title":{"rendered":"How to Build a Scalable Framework-Agnostic Web SDKs: Step by Step"},"content":{"rendered":"\n<p>In today\u2019s diverse frontend ecosystem, building tools that cater to a single framework can limit their reach and usability. Not only this, you may also want to target the whole frontend ecosystem, without limitations related to libs and frameworks.<\/p>\n\n\n\n<p><strong>Framework-agnostic SDKs offer a solution by ensuring compatibility across multiple frameworks while maintaining performance and simplicity.<\/strong><\/p>\n\n\n\n<p><strong>This blog post aims to guide you through the process of creating a framework-agnostic web SDK, from initial setup to deployment.<\/strong> Whether you&#8217;re a seasoned developer or new to SDK design, you&#8217;ll gain insights and practical tips to make your SDK accessible to developers everywhere.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What Does Framework-Agnostic Mean?<\/h2>\n\n\n\n<p><strong>A framework-agnostic SDK is designed to work seamlessly across multiple frameworks, such as React, Vue, Angular, or even vanilla JavaScript.<\/strong><\/p>\n\n\n\n<p>The goal is to maximize usability and minimize dependency on specific tools or libraries, ensuring that developers working in various ecosystems can integrate your SDK without friction.<\/p>\n\n\n\n<p><strong>This approach not only broadens your SDK&#8217;s audience but also simplifies maintenance and reduces versioning headaches.<\/strong><\/p>\n\n\n\n<p>Key aspects of a framework-agnostic SDK:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Neutral Core:<\/strong> Avoid dependencies on specific frameworks. For example, instead of using React-specific hooks, rely on plain JavaScript for core logic.<\/li>\n\n\n\n<li><strong>Adaptability:<\/strong> Offer optional framework-specific wrappers (e.g., a React provider or Vue plugin) for ease of use in specific environments.<\/li>\n\n\n\n<li><strong>Portability:<\/strong> Ensure compatibility with browsers, Node.js, and tools like <a href=\"https:\/\/webpack.js.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Webpack<\/a>, Rollup, or Vite.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Why Build Framework-Agnostic SDKs?<\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1200\" height=\"711\" src=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Why-Build-Framework-Agnostic-SDKs-1200x711.png\" alt=\"Why Build Framework-Agnostic SDKs\" class=\"wp-image-12688\" srcset=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Why-Build-Framework-Agnostic-SDKs-1200x711.png 1200w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Why-Build-Framework-Agnostic-SDKs-600x355.png 600w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Why-Build-Framework-Agnostic-SDKs-768x455.png 768w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Why-Build-Framework-Agnostic-SDKs-1536x910.png 1536w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Why-Build-Framework-Agnostic-SDKs-760x450.png 760w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Why-Build-Framework-Agnostic-SDKs.png 1763w\" sizes=\"(max-width: 1200px) 100vw, 1200px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Choosing the Right Tools and Technologies<\/h2>\n\n\n\n<p>Selecting the right stack is critical when building a framework-agnostic SDK. Here\u2019s a breakdown:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Language:<\/strong> Use <strong>TypeScript<\/strong> for type safety and a better developer experience. Type annotations make your SDK more robust and self-documenting.<\/li>\n\n\n\n<li><strong>Bundler:<\/strong> Tools like <strong><a href=\"https:\/\/rollupjs.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Rollup<\/a><\/strong> or <strong><a href=\"https:\/\/vite.dev\/\" target=\"_blank\" rel=\"noreferrer noopener\">Vite<\/a><\/strong> are optimized for building libraries. They support tree-shaking, multiple output formats (ESM, CommonJS, UMD), and plugins for enhanced functionality. In this guide, we will be using Vite.<\/li>\n\n\n\n<li><strong>Standards:<\/strong> Leverage modern JavaScript features like ES Modules and async\/await, but ensure compatibility with older environments through transpilers like Babel, or compile the code with older targets in mind.<\/li>\n\n\n\n<li><strong>Testing Frameworks:<\/strong>\n<ul class=\"wp-block-list\">\n<li><strong>Unit Testing:<\/strong> Use <strong>Jest<\/strong> or <strong>Vitest<\/strong> for core logic.<\/li>\n\n\n\n<li><strong>Integration Testing:<\/strong> Tools like <strong><a href=\"https:\/\/playwright.dev\/\" target=\"_blank\" rel=\"noreferrer noopener\">Playwright<\/a><\/strong> or <strong><a href=\"https:\/\/www.cypress.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Cypress<\/a><\/strong> can verify SDK behavior in real-world scenarios across frameworks.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Documentation Tools:<\/strong> Generate rich, user-friendly documentation with <strong><a href=\"https:\/\/storybook.js.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Storybook<\/a><\/strong>, <strong><a href=\"https:\/\/docusaurus.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Docusaurus<\/a><\/strong>, or <strong><a href=\"https:\/\/typedoc.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Typedoc<\/a><\/strong>.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Core Considerations and API Design<\/h2>\n\n\n\n<p>A well-designed API is the cornerstone of a successful SDK. So focus on:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Simplicity:<\/strong> Keep function names intuitive and parameters consistent.<\/li>\n\n\n\n<li><strong>Flexibility:<\/strong> Allow customization without overwhelming users with options.<\/li>\n\n\n\n<li><strong>Minimalism:<\/strong> Start with essential features and expand based on user feedback.<\/li>\n\n\n\n<li><strong>Error Handling:<\/strong> Provide meaningful error messages and edge-case handling to avoid confusion during implementation.<\/li>\n<\/ul>\n\n\n\n<p><strong>Modularity<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li class=\"has-default-color has-text-color has-link-color wp-elements-41748a573b73092634a4652cf26d0916\">Structure your SDK into independent modules, allowing users to import only the features they need. <br><br><strong>Example:<\/strong> Instead of<code> import { FullSDK } from 'sdk';, enable import { SpecificFeature } from 'sdk';.<\/code><\/li>\n<\/ul>\n\n\n\n<p><strong>Backward Compatibility<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Use <strong>semantic versioning<\/strong> to communicate breaking changes.<\/li>\n\n\n\n<li>Maintain deprecated features for at least one major version before removing them.<\/li>\n<\/ul>\n\n\n\n<p><strong>Documentation<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Code Examples:<\/strong> Include ready-to-use examples for common use cases.<\/li>\n\n\n\n<li><strong>Setup Guides:<\/strong> Cover integration steps for popular frameworks.<\/li>\n\n\n\n<li><strong>API Reference:<\/strong> Provide detailed descriptions of each method, parameter, and return value.<\/li>\n<\/ul>\n\n\n\n<p>Let\u2019s give a quick example of what a framework-agnostic SDK would look like.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Keyboard Shortcut SDK<\/h2>\n\n\n\n<p>For this example, we will create an SDK for managing custom keyboard shortcuts across a web application, allowing developers to easily bind and unbind shortcuts to specific actions.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Main Features<\/strong><\/h3>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li><strong>Register Global Shortcuts<\/strong>\n<ul class=\"wp-block-list\">\n<li>Easily assign global keyboard shortcuts (e.g., <code>Ctrl + S<\/code>) to trigger specific functions.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Context-Specific Shortcuts<\/strong>\n<ul class=\"wp-block-list\">\n<li>Enable shortcuts only when certain conditions are met (e.g., when a modal or a specific element is active).<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Unregister Shortcuts<\/strong>\n<ul class=\"wp-block-list\">\n<li>Dynamically remove shortcuts when they&#8217;re no longer needed.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Visual feedback<\/strong>\n<ul class=\"wp-block-list\">\n<li>Show an opt-in temporary tooltip when registering the shortcuts.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<p>A good way to start is to define how you will use the SDK. Given the features above, we should use the SDK as follows:<\/p>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>1. Import and Initialize the SDK<\/strong><\/h4>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { initialize } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/keyboardShortcutSDK'<\/span>;\n\n\ninitialize();<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\"><strong>2. Register Global Shortcuts<\/strong><\/h4>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">&lt;code&gt;<span class=\"hljs-keyword\">import<\/span> { keyboardShortcutSDK } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/keyboardShortcutSDK'<\/span>;\n\nkeyboardShortcutSDK().register(<span class=\"hljs-string\">'Ctrl+S'<\/span>, () =&gt; {\n\u00a0\u00a0<span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'Save triggered!'<\/span>);\n\u00a0\u00a0alert(<span class=\"hljs-string\">'Document saved!'<\/span>);\n});&lt;<span class=\"hljs-regexp\">\/code&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\"><strong>3. Register Context-Specific Shortcuts<\/strong><\/h4>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { keyboardShortcutSDK } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/keyboardShortcutSDK'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> modalIsOpen = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">'#myModal'<\/span>).classList.contains(<span class=\"hljs-string\">'open'<\/span>);\n\nkeyboardShortcutSDK.register(<span class=\"hljs-string\">'Escape'<\/span>, () =&gt; {\n\u00a0\u00a0<span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'Modal closed!'<\/span>);\n\u00a0\u00a0<span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">'#myModal'<\/span>).classList.remove(<span class=\"hljs-string\">'open'<\/span>);\n}, { <span class=\"hljs-attr\">condition<\/span>: modalIsOpen });<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\"><strong>4. Unregister Shortcuts<\/strong><\/h4>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { keyboardShortcutSDK } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/keyboardShortcutSDK'<\/span>;\n\nkeyboardShortcutSDK.unregister(<span class=\"hljs-string\">'Ctrl+S'<\/span>); <span class=\"hljs-comment\">\/\/ Remove the save shortcut<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>5. Show a tooltip when registering a shortcut<\/strong><\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { keyboardShortcutSDK } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/keyboardShortcutSDK'<\/span>;\n\nkeyboardShortcutSDK.register(<span class=\"hljs-string\">'Ctrl+S'<\/span>, () =&gt; {\n\u00a0\u00a0<span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'Save triggered!'<\/span>);\n\u00a0\u00a0alert(<span class=\"hljs-string\">'Document saved!'<\/span>);\n}, { <span class=\"hljs-attr\">feedback<\/span>: { <span class=\"hljs-attr\">enabled<\/span>: <span class=\"hljs-literal\">true<\/span>, <span class=\"hljs-attr\">message<\/span>: <span class=\"hljs-string\">\"Press 'Ctrl+S' to save.\"<\/span> } });<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>For example purposes, the SDK will be whole exported in <code>keyboardShortcutSDK<\/code>, but keep in mind modularity when creating your SDK, as we saw in the core considerations session.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Initializing Vite project<\/h3>\n\n\n\n<p>Let\u2019s initialize the Vite project:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">npm create vite@latest<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Give it a name when prompted. Select \u2018Vanilla\u2019 when prompted for a framework (which means no specific framework will be used), and then choose TypeScript as the variant.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1200\" height=\"879\" src=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.19.22-1200x879.png\" alt=\"Initializing Vite project\" class=\"wp-image-12706\" srcset=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.19.22-1200x879.png 1200w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.19.22-600x439.png 600w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.19.22-768x562.png 768w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.19.22-760x557.png 760w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.19.22.png 1420w\" sizes=\"(max-width: 1200px) 100vw, 1200px\" \/><\/figure>\n\n\n\n<p>Follow the instructions given by Vite\u2019s cli: go to the folder created with the project name and run `npm install` (or use any package manager you want).<\/p>\n\n\n\n<p>After that, you can run <code>npm run dev<\/code>, and open <a href=\"http:\/\/localhost:5173\" target=\"_blank\" rel=\"noreferrer noopener\"><u>http:\/\/localhost:5173<\/u><\/a> in your browser to see the following:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Fun fact: Do you know why Vite uses the port 5173? If you try to read it as letters, you will read SITE!<\/p>\n<\/blockquote>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1200\" height=\"763\" src=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.22.29-1200x763.png\" alt=\"\" class=\"wp-image-12708\" srcset=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.22.29-1200x763.png 1200w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.22.29-600x382.png 600w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.22.29-768x488.png 768w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.22.29-1536x977.png 1536w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.22.29-760x483.png 760w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.22.29.png 1920w\" sizes=\"(max-width: 1200px) 100vw, 1200px\" \/><\/figure>\n\n\n\n<p>If you open your project in the editor\/IDE of your choice, you will see the following structure:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-medium\"><img decoding=\"async\" width=\"600\" height=\"515\" src=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.26.43-600x515.png\" alt=\"\" class=\"wp-image-12710\" srcset=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.26.43-600x515.png 600w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.26.43-768x659.png 768w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.26.43-760x652.png 760w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.26.43.png 774w\" sizes=\"(max-width: 600px) 100vw, 600px\" \/><\/figure>\n<\/div>\n\n\n<p>The first thing to do is to tweak the configurations.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Update TSConfig<\/h4>\n\n\n\n<p>Open <code>tsconfig.json<\/code> file, and add the following to the <code>compilerOptions<\/code> object:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-string\">\"declaration\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n<span class=\"hljs-string\">\"declarationMap\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n<span class=\"hljs-string\">\"sourceMap\"<\/span>: <span class=\"hljs-literal\">true<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Those configs will export the declaration files for the lib when compiling, and also will export the source maps, which help with debugging steps.<\/p>\n\n\n\n<p>Also, add the <code>app<\/code> folder to the <code>include<\/code> array. We will be creating this folder in the next steps. The configuration file should look like the following:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">{\n  <span class=\"hljs-string\">\"compilerOptions\"<\/span>: {\n    <span class=\"hljs-string\">\"target\"<\/span>: <span class=\"hljs-string\">\"ES2020\"<\/span>,\n    <span class=\"hljs-string\">\"useDefineForClassFields\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-string\">\"module\"<\/span>: <span class=\"hljs-string\">\"ESNext\"<\/span>,\n    <span class=\"hljs-string\">\"lib\"<\/span>: &#91;\n      <span class=\"hljs-string\">\"ES2020\"<\/span>,\n      <span class=\"hljs-string\">\"DOM\"<\/span>,\n      <span class=\"hljs-string\">\"DOM.Iterable\"<\/span>\n    ],\n    <span class=\"hljs-string\">\"skipLibCheck\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-string\">\"moduleResolution\"<\/span>: <span class=\"hljs-string\">\"bundler\"<\/span>,\n    <span class=\"hljs-string\">\"allowImportingTsExtensions\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-string\">\"isolatedModules\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-string\">\"moduleDetection\"<\/span>: <span class=\"hljs-string\">\"force\"<\/span>,\n    <span class=\"hljs-string\">\"noEmit\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-string\">\"strict\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-string\">\"noUnusedLocals\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-string\">\"noUnusedParameters\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-string\">\"noFallthroughCasesInSwitch\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-string\">\"noUncheckedSideEffectImports\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-string\">\"declaration\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-string\">\"declarationMap\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-string\">\"sourceMap\"<\/span>: <span class=\"hljs-literal\">true<\/span>\n  },\n  <span class=\"hljs-string\">\"include\"<\/span>: &#91;\n    <span class=\"hljs-string\">\"src\"<\/span>,\n    <span class=\"hljs-string\">\"app\"<\/span>\n  ]\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>After that, create a new file called `tsconfig.build.json` with the following content:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">{\n  <span class=\"hljs-string\">\"extends\"<\/span>: <span class=\"hljs-string\">\".\/tsconfig.json\"<\/span>,\n  <span class=\"hljs-string\">\"include\"<\/span>: &#91;\n    <span class=\"hljs-string\">\"src\"<\/span>\n  ]\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>When building, this will override the <code>include<\/code> list to include only the SDK source code.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Update Vite config<\/h4>\n\n\n\n<p>Now, let\u2019s create a file called <code>vite.config.ts<\/code>. This is the file responsible for the configurations of the Vite project. It\u2019s here where we will configure Vite to the lib mode.<\/p>\n\n\n\n<p>Before creating it, let\u2019s add the development dependencies we will need:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>@types\/node<\/code>: types for node packages<\/li>\n\n\n\n<li><code>vite-plugin-dts<\/code>: the Vite plugin responsible for generating our <code>*.d.ts files<\/code>:<\/li>\n<\/ul>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">npm i -D @types\/node@latest vite-plugin-dts<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Then, create the <code>vite.config.ts<\/code> file with the following contents:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { resolve } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"path\"<\/span>\n<span class=\"hljs-keyword\">import<\/span> { defineConfig } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"vite\"<\/span>\n<span class=\"hljs-keyword\">import<\/span> dts <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"vite-plugin-dts\"<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> defineConfig({\n  <span class=\"hljs-attr\">plugins<\/span>: &#91;dts({ <span class=\"hljs-attr\">include<\/span>: &#91;<span class=\"hljs-string\">\"src\"<\/span>] })],\n  <span class=\"hljs-attr\">build<\/span>: {\n    <span class=\"hljs-attr\">copyPublicDir<\/span>: <span class=\"hljs-literal\">false<\/span>,\n    <span class=\"hljs-attr\">lib<\/span>: {\n      <span class=\"hljs-attr\">entry<\/span>: resolve(__dirname, <span class=\"hljs-string\">\"src\/index.ts\"<\/span>),\n      <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"keyboard-shortcut-sdk\"<\/span>,\n      <span class=\"hljs-attr\">formats<\/span>: &#91;<span class=\"hljs-string\">\"es\"<\/span>, <span class=\"hljs-string\">\"cjs\"<\/span>, <span class=\"hljs-string\">\"umd\"<\/span>],\n      <span class=\"hljs-attr\">fileName<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">ext<\/span>) =&gt;<\/span> <span class=\"hljs-string\">`index.<span class=\"hljs-subst\">${ext}<\/span>.js`<\/span>,\n    },\n    <span class=\"hljs-attr\">sourcemap<\/span>: <span class=\"hljs-literal\">true<\/span>,\n  },\n})<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Moving things around<\/h4>\n\n\n\n<p>Till now, you have had a file structure like this:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-medium\"><img decoding=\"async\" width=\"516\" height=\"600\" src=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.45.51-516x600.png\" alt=\"\" class=\"wp-image-12712\" srcset=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.45.51-516x600.png 516w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Screenshot-2025-02-18-at-17.45.51.png 648w\" sizes=\"(max-width: 516px) 100vw, 516px\" \/><\/figure>\n<\/div>\n\n\n<p>We need to move what we don\u2019t want in the SDK to the app folder. Let\u2019s then create this folder and move the <code>index.html<\/code> file there.<\/p>\n\n\n\n<p>Also, let\u2019s remove the code we will not be using:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Remove the files:<code> counter.ts<\/code>, <code>style.css<\/code>, and <code>typescript.svg<\/code> from the <code>src<\/code> folder<\/li>\n<\/ul>\n\n\n\n<p>Rename <code>main.ts<\/code> to <code>index.ts<\/code>, and update the content with the following:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">helloAnything<\/span>(<span class=\"hljs-params\">thing: string<\/span>): <span class=\"hljs-title\">string<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">`Hello, <span class=\"hljs-subst\">${thing}<\/span>!`<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now you can observe that if you try to start the server again, you\u2019ll see nothing in the browser. But if you try to access the route <code>\/app\/index.html<\/code>, the <code>index.html<\/code> file will load. Let\u2019s fix that. In the <code>vite.config.ts file<\/code>, add the following to the <code>plugins<\/code> list:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">{\n\u00a0\u00a0<span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"deep-index\"<\/span>,\n\u00a0\u00a0configureServer(server) {\n\u00a0\u00a0\u00a0\u00a0server.middlewares.use(<span class=\"hljs-function\">(<span class=\"hljs-params\">req, res, next<\/span>) =&gt;<\/span> {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">if<\/span> (req.url === <span class=\"hljs-string\">\"\/\"<\/span>) {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0req.url = <span class=\"hljs-string\">\"\/app\/index.html\"<\/span>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0next()\n\u00a0\u00a0\u00a0\u00a0})\n\u00a0\u00a0},\n},<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Also, inside the <code>app<\/code> folder, create a new file called <code>main.ts<\/code>, and add the following content:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { helloAnything } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/src\"<\/span>\n\n<span class=\"hljs-built_in\">console<\/span>.log(helloAnything(<span class=\"hljs-string\">\"world\"<\/span>))<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And update the script importing in the <code>index.html<\/code> accordingly:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">&lt;script type=<span class=\"hljs-string\">\"module\"<\/span> src=<span class=\"hljs-string\">\"\/app\/main.ts\"<\/span>&gt;&lt;<span class=\"hljs-regexp\">\/script&gt;\u00a0 &lt;!-- it was \/<\/span>src\/main.ts before updating --&gt;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now, you are able to open the webpage. It will be a blank screen. If you open the console, you should see <code>Hello, world!<\/code> printed.<\/p>\n\n\n\n<p>And this is the file structure now:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-medium\"><img decoding=\"async\" width=\"552\" height=\"600\" src=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250218-210552-552x600.png\" alt=\"\" class=\"wp-image-12696\" srcset=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250218-210552-552x600.png 552w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250218-210552.png 628w\" sizes=\"(max-width: 552px) 100vw, 552px\" \/><\/figure>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\">Coding main features<\/h3>\n\n\n\n<p>Now that we have the project structure prepared, let\u2019s develop the features of the SDK.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Initialize SDK<\/h4>\n\n\n\n<p>Let\u2019s create the <code>initializeSdk<\/code> function. It will serve only to create the SDK instance to be used later. But you can use this function to save an access token, for example, or save some initial configs that your solution could be using. Let\u2019s create the <code>domain<\/code> folder, where we will be adding all our business logic. <\/p>\n\n\n\n<p>And inside that, let\u2019s add an <code>sdk<\/code> folder, focused on the SDK main class. There, add the <code>index.ts<\/code> and put the following code:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">KeyboardShortcutSDK<\/span> <\/span>{\n  private <span class=\"hljs-keyword\">static<\/span> instance: KeyboardShortcutSDK\n\n  <span class=\"hljs-keyword\">static<\/span> getInstance() {\n    <span class=\"hljs-keyword\">if<\/span> (!KeyboardShortcutSDK.instance) {\n      <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">\"KeyboardShortcutSDK is not initialized\"<\/span>)\n    }\n    <span class=\"hljs-keyword\">return<\/span> KeyboardShortcutSDK.instance\n  }\n\n  <span class=\"hljs-keyword\">static<\/span> initialize() {\n    KeyboardShortcutSDK.instance = <span class=\"hljs-keyword\">new<\/span> KeyboardShortcutSDK()\n    <span class=\"hljs-keyword\">return<\/span> KeyboardShortcutSDK.instance\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now, update the <code>src\/index.ts<\/code> file content to the following:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { KeyboardShortcutSDK } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/domain\/sdk\"<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">initializeSdk<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> KeyboardShortcutSDK.initialize()\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">keyboardShortcutSDK<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> KeyboardShortcutSDK.getInstance()\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You will see that the IDE will start complaining about <code>app\/main.ts<\/code>. Let\u2019s update it also to call our initialize function:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { initializeSdk } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/src\"<\/span>\n\ninitializeSdk()<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Register Shortcuts method<\/h4>\n\n\n\n<p>Now that we already have the SDK class, let\u2019s add the function to register the shortcuts.<\/p>\n\n\n\n<p>Add this type somewhere (I added it before the SDK class definition):<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">type KeyboardShortcut = {\n  <span class=\"hljs-attr\">key<\/span>: string\n  <span class=\"hljs-attr\">ctrlKey<\/span>: boolean\n  <span class=\"hljs-attr\">shiftKey<\/span>: boolean\n  <span class=\"hljs-attr\">altKey<\/span>: boolean\n  <span class=\"hljs-attr\">metaKey<\/span>: boolean\n  <span class=\"hljs-attr\">callback<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> <span class=\"hljs-keyword\">void<\/span>\n  options?: {\n    condition?: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> boolean\n    feedback?: string\n  }\n  <span class=\"hljs-attr\">id<\/span>: string\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Add the following code to the SDK class:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">  private shortcuts: KeyboardShortcut&#91;] = &#91;]\n  private initializedShortcutsListener = <span class=\"hljs-literal\">false<\/span>\n  \n  private parseShortcut(shortcut: string) {\n    <span class=\"hljs-keyword\">const<\/span> shortcutPieces = shortcut.toLowerCase().split(<span class=\"hljs-string\">\"+\"<\/span>)\n    <span class=\"hljs-keyword\">if<\/span> (shortcutPieces.length &gt; <span class=\"hljs-number\">2<\/span>) {\n      <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(\n        <span class=\"hljs-string\">'Invalid shortcut. Use at most a modifier and a key. Example: \"ctrl+s\"'<\/span>\n      )\n    }\n\n    <span class=\"hljs-keyword\">const<\/span> &#91;modifierOrKey, key] = shortcutPieces\n\n    <span class=\"hljs-keyword\">const<\/span> ctrlKey = modifierOrKey === <span class=\"hljs-string\">\"ctrl\"<\/span>\n    <span class=\"hljs-keyword\">const<\/span> shiftKey = modifierOrKey === <span class=\"hljs-string\">\"shift\"<\/span>\n    <span class=\"hljs-keyword\">const<\/span> altKey = modifierOrKey === <span class=\"hljs-string\">\"alt\"<\/span>\n    <span class=\"hljs-keyword\">const<\/span> metaKey = modifierOrKey === <span class=\"hljs-string\">\"meta\"<\/span>\n    <span class=\"hljs-keyword\">return<\/span> {\n      <span class=\"hljs-attr\">key<\/span>: key || modifierOrKey,\n      ctrlKey,\n      shiftKey,\n      altKey,\n      metaKey,\n      <span class=\"hljs-attr\">id<\/span>: shortcut,\n    }\n  }\n\n  private initializeShortcutsListener() {\n    <span class=\"hljs-keyword\">const<\/span> onKeyPressed = <span class=\"hljs-function\">(<span class=\"hljs-params\">event: KeyboardEvent<\/span>) =&gt;<\/span> {\n      <span class=\"hljs-keyword\">const<\/span> shortcut = <span class=\"hljs-keyword\">this<\/span>.shortcuts.find(<span class=\"hljs-function\">(<span class=\"hljs-params\">shortcut<\/span>) =&gt;<\/span> {\n        <span class=\"hljs-keyword\">return<\/span> (\n          shortcut.key === event.key &amp;&amp;\n          shortcut.ctrlKey === event.ctrlKey &amp;&amp;\n          shortcut.shiftKey === event.shiftKey &amp;&amp;\n          shortcut.altKey === event.altKey &amp;&amp;\n          shortcut.metaKey === event.metaKey\n        )\n      })\n      \n      <span class=\"hljs-keyword\">const<\/span> shouldRun = shortcut?.options?.condition?.() ?? <span class=\"hljs-literal\">true<\/span>\n\n      <span class=\"hljs-keyword\">if<\/span> (shortcut &amp;&amp; shouldRun) {\n        event.preventDefault()\n        shortcut.callback()\n      }\n    }\n\n    <span class=\"hljs-built_in\">document<\/span>.addEventListener(<span class=\"hljs-string\">\"keydown\"<\/span>, onKeyPressed)\n  }\n\n  register(\n    shortcut: string,\n    <span class=\"hljs-attr\">callback<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> <span class=\"hljs-keyword\">void<\/span>,\n    options?: {\n      condition?: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> boolean\n      feedback?: string\n    }\n  ) {\n    <span class=\"hljs-keyword\">this<\/span>.shortcuts.push({\n      ...this.parseShortcut(shortcut),\n      callback,\n      options,\n    })\n\n    <span class=\"hljs-keyword\">if<\/span> (!<span class=\"hljs-keyword\">this<\/span>.initializedShortcutsListener) {\n      <span class=\"hljs-keyword\">this<\/span>.initializedShortcutsListener = <span class=\"hljs-literal\">true<\/span>\n      <span class=\"hljs-keyword\">this<\/span>.initializeShortcutsListener()\n    }\n  }<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Explanation:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Shortcuts will store all registered shortcuts<\/li>\n\n\n\n<li><code>initializedShortcutsListener<\/code> is a flag to check if we have initialized the listener for shortcuts<\/li>\n\n\n\n<li><code>initializeShortcutsListener()<\/code> will initialize the listener the first time a shortcut is registered.<\/li>\n\n\n\n<li><code>parseShortcut()<\/code> will parse the string passed in the <code>register<\/code> method to check for modifiers. For simplicity, we will allow only shortcuts with a key or modifier+key.<\/li>\n\n\n\n<li><code>register()<\/code> is the main method where we will be registering shortcuts. It accepts, respectively:\n<ul class=\"wp-block-list\">\n<li>A string, which will be parsed, representing the shortcut<\/li>\n\n\n\n<li>A callback, which will be called when the shortcut is triggered<\/li>\n\n\n\n<li>An options object, which is optional, and has a condition property, which is a callback that is executed to check if the shortcut should run or not, and a feedback property, which is a string, and, if present, the SDK will show a toast every time the shortcut triggers.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p>Now we can register our first shortcut in the <code>app\/main.ts<\/code> file:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { initializeSdk, keyboardShortcutSDK } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/src\"<\/span>\n\ninitializeSdk()\n\nkeyboardShortcutSDK().register(<span class=\"hljs-string\">\"ctrl+s\"<\/span>, () =&gt; {\n  alert(<span class=\"hljs-string\">\"Saved!\"<\/span>)\n})<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If you run the project and press \u201cCtrl+S\u201c on the keyboard, you should see the alert appear.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Adding feedback<\/h4>\n\n\n\n<p>Let\u2019s add a feedback code for when the shortcut is triggered.<\/p>\n\n\n\n<p>First of all, we will be using TailwindCSS to demonstrate CSS injection. Following the instructions from <a href=\"https:\/\/tailwindcss.com\/docs\/installation\/using-vite\" target=\"_blank\" rel=\"noreferrer noopener\">TailwindCSS website<\/a>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Install the necessary packages:<\/li>\n<\/ul>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:100%\"><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">&lt;code&gt;npm install -D tailwindcss @tailwindcss\/vite&lt;<span class=\"hljs-regexp\">\/code&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n<\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Add the plugin to <code>vite.config<\/code>:\n<ul class=\"wp-block-list\">\n<li>Add import:<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:100%\"><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">plugins: &#91;\n  tailwindcss(),\n  dts(...),\n  ...\n]<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n<\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Add it to the plugins list:<\/li>\n<\/ul>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:100%\"><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">&lt;code&gt;plugins: &#91; tailwindcss(), dts(...), ... ]&lt;<span class=\"hljs-regexp\">\/code&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n<\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Create a styles.css file into <code>src<\/code> and add the import to tailwind:<\/li>\n<\/ul>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:100%\"><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">&lt;code&gt;@<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">\"tailwindcss\"<\/span>&lt;<span class=\"hljs-regexp\">\/code&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n<\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Add the import to <code>styles.css<\/code> to the <code>src\/index.ts file<\/code>:<\/li>\n<\/ul>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:100%\"><pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">&lt;code&gt;<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">'.\/styles.css'<\/span>&lt;<span class=\"hljs-regexp\">\/code&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre><\/div>\n<\/div>\n\n\n\n<p>Let\u2019s then also add some content to our example app. Change the <code>body<\/code> content of your <code>index.html<\/code> to the following:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">&lt;body&gt;\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"w-full h-screen bg-slate-800 text-white flex flex-col justify-center items-center\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"text-2xl font-bold\"<\/span>&gt;<\/span>Keyboard Shortcuts SDK<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h3<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"text-lg italic\"<\/span>&gt;<\/span>Example app<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h3<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"register-save-shortcut\"<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"mt-10 px-4 py-2 rounded-md bg-amber-700\"<\/span>&gt;<\/span>Register Save shortcut<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"save-shortcut-content\"<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"hidden mt-10 text-gray-300\"<\/span>&gt;<\/span>\n      Press\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"text-white font-bold\"<\/span>&gt;<\/span>\"Ctrl+S\"<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n      for a shortcut demonstration\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"module\"<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"\/app\/main.ts\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span><\/span>\n&lt;<span class=\"hljs-regexp\">\/body&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>It should now render the following page:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1200\" height=\"763\" src=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-194734-1200x763.png\" alt=\"\" class=\"wp-image-12700\" srcset=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-194734-1200x763.png 1200w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-194734-600x382.png 600w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-194734-768x488.png 768w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-194734-1536x977.png 1536w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-194734-760x483.png 760w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-194734.png 1920w\" sizes=\"(max-width: 1200px) 100vw, 1200px\" \/><\/figure>\n\n\n\n<p>Let\u2019s then add the <code>vite-plugin-css-injected-by-js dependency<\/code>, which will be responsible for adding the generated styles of our lib injected in our JS output:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">npm i -D vite-plugin-css-injected-by-js<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And update the plugins list in Vite:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> cssInjectedByJsPlugin <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"vite-plugin-css-injected-by-js\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-30\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">plugins: &#91;\n  tailwindcss(),\n  cssInjectedByJsPlugin(),\n  dts(...),\n  ...\n]<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-30\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now, if you run <code>npm run build<\/code> the result should contain a function that adds the styles dynamically. Like the following:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1200\" height=\"249\" src=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-190446-1200x249.png\" alt=\"\" class=\"wp-image-12698\" srcset=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-190446-1200x249.png 1200w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-190446-600x124.png 600w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-190446-768x159.png 768w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-190446-1536x319.png 1536w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-190446-760x158.png 760w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-190446.png 1764w\" sizes=\"(max-width: 1200px) 100vw, 1200px\" \/><\/figure>\n\n\n\n<p>But if you search for the class <code>w-full<\/code>, which we are using in our <code>index.html<\/code> file (which is an example page only), it will be in our generated file. And we don\u2019t want this. We want to include only styles from our library in our output. To do that, because of the way Tailwind v4 is designed, we need to have multiple <code>styles.css<\/code> files, one for development purposes with our example page, and another for building the SDK.<\/p>\n\n\n\n<p>Let\u2019s rename the <code>styles.css<\/code> file to <code>styles.dev.css<\/code> and keep the content as is. Also, let\u2019s create another styles.css file now, with the following content:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@import<\/span> <span class=\"hljs-string\">\"tailwindcss\"<\/span> source(none);\n\n<span class=\"hljs-keyword\">@source<\/span> <span class=\"hljs-string\">\"..\/src\"<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>source(none)<\/code> will change the content that tailwind expects to check to be nothing (by default, it will check for everything except for git-ignored files and some others &#8211; refer to Tailwind documentation). After this, we do a <code>@source \"..\/src\";<\/code> which tells Tailwind that we want to include the <code>src<\/code> folder.<\/p>\n\n\n\n<p>Also, to load one file or another dynamically, let\u2019s create the <code>loadStyles.ts<\/code> file with the following:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-32\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">loadStyles<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">import<\/span>.meta.env.DEV) {\n    <span class=\"hljs-keyword\">import<\/span>(<span class=\"hljs-string\">\".\/styles.dev.css\"<\/span>)\n  } <span class=\"hljs-keyword\">else<\/span> {\n    <span class=\"hljs-keyword\">import<\/span>(<span class=\"hljs-string\">\".\/styles.css\"<\/span>)\n  }\n}\n\nloadStyles()\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-32\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the <code>index.ts<\/code> file, change the import to <code>.\/styles.css<\/code> file to be:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-33\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">\".\/loadStyles\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-33\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now, if you run <code>npm run build<\/code>, you will not see the <code>w-full<\/code> class name present in the output files anymore. This way, we can add feedback for when a shortcut is registered and have only the desired styles in the output files.<\/p>\n\n\n\n<p>Given this, let\u2019s update our SDK to show a toast when the user passes a feedback string that shows up when the shortcut is registered.<\/p>\n\n\n\n<p>Add the following method to our KeyboardShortcutSDK class:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-34\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">  private shortcutAddedFeedback(feedback: string) {\n    <span class=\"hljs-keyword\">const<\/span> toast = <span class=\"hljs-built_in\">document<\/span>.createElement(<span class=\"hljs-string\">\"div\"<\/span>)\n    toast.innerHTML = <span class=\"hljs-string\">`\n      &lt;div class=\"fixed inset-x-0 top-4 flex items-end justify-center px-4 py-6 pointer-events-none sm:items-start sm:px-6 sm:py-4 sm:justify-end sm:space-x-4 sm:top-6 sm:right-6\"&gt;\n        &lt;div class=\"z-50 flex items-center w-full max-w-xs p-4 text-gray-500 bg-white rounded-lg shadow-sm dark:text-gray-400 dark:bg-gray-900\" role=\"alert\"&gt;\n          &lt;div class=\"ms-3 text-sm font-normal\"&gt;<span class=\"hljs-subst\">${feedback}<\/span>&lt;\/div&gt;\n        &lt;\/div&gt;\n      &lt;\/div&gt;\n    `<\/span>\n    <span class=\"hljs-built_in\">document<\/span>.body.appendChild(toast)\n    setTimeout(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      <span class=\"hljs-built_in\">document<\/span>.body.removeChild(toast)\n    }, <span class=\"hljs-number\">3000<\/span>)\n  }<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-34\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Also, update the <code>register<\/code> method to call it when the <code>options.feedback<\/code> is defined. Add the following lines to the end of the register method:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-35\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">    <span class=\"hljs-keyword\">if<\/span> (options?.feedback) {\n      <span class=\"hljs-keyword\">this<\/span>.shortcutAddedFeedback(options.feedback)\n    }<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-35\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>To make it in action, let\u2019s also update our <code>app\/main.ts<\/code> to call the register only when the user clicks on the \u201cRegister Save shortcut\u201c button, and to include a feedback string:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-36\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { initializeSdk, keyboardShortcutSDK } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/src\"<\/span>\n\ninitializeSdk()\n\n<span class=\"hljs-keyword\">const<\/span> buttonToRegisterShortcut = <span class=\"hljs-built_in\">document<\/span>.getElementById(\n  <span class=\"hljs-string\">\"register-save-shortcut\"<\/span>\n)\n<span class=\"hljs-keyword\">const<\/span> saveShortcutContent = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">\"save-shortcut-content\"<\/span>)\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">registerShortcut<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  keyboardShortcutSDK().register(\n    <span class=\"hljs-string\">\"ctrl+s\"<\/span>,\n    () =&gt; {\n      alert(<span class=\"hljs-string\">\"Saved!\"<\/span>)\n    },\n    {\n      <span class=\"hljs-attr\">feedback<\/span>: <span class=\"hljs-string\">'Shortcut \"ctrl+s\" registered'<\/span>,\n    }\n  )\n\n  saveShortcutContent?.classList.remove(<span class=\"hljs-string\">\"hidden\"<\/span>)\n  buttonToRegisterShortcut?.classList.add(<span class=\"hljs-string\">\"hidden\"<\/span>)\n}\n\nbuttonToRegisterShortcut?.addEventListener(<span class=\"hljs-string\">\"click\"<\/span>, registerShortcut)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-36\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now, if you click on the button, you will have the following:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1200\" height=\"763\" src=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-195705-1200x763.png\" alt=\"\" class=\"wp-image-12702\" srcset=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-195705-1200x763.png 1200w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-195705-600x382.png 600w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-195705-768x488.png 768w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-195705-1536x977.png 1536w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-195705-760x483.png 760w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250307-195705.png 1920w\" sizes=\"(max-width: 1200px) 100vw, 1200px\" \/><\/figure>\n\n\n\n<p>Now, if you run <code>npm run build<\/code>, it will only include the styles you have in the SDK source code.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Unregistering<\/h4>\n\n\n\n<p>To make an <code>unregister<\/code> method, let\u2019s add the following to the KeyboardShortcutSDK class:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-37\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">  unregister(shortcut: string) {\n    <span class=\"hljs-keyword\">const<\/span> index = <span class=\"hljs-keyword\">this<\/span>.shortcuts.findIndex(<span class=\"hljs-function\">(<span class=\"hljs-params\">s<\/span>) =&gt;<\/span> s.id === shortcut)\n    <span class=\"hljs-keyword\">if<\/span> (index !== <span class=\"hljs-number\">-1<\/span>) {\n      <span class=\"hljs-keyword\">this<\/span>.shortcuts.splice(index, <span class=\"hljs-number\">1<\/span>)\n    }\n  }<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-37\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Selecting what will be delivered with our SDK<\/h4>\n\n\n\n<p>NPM has the <code>npm pack<\/code> command, which is responsible for generating a .tgz file with the package contents and everything deliverable in your package.<\/p>\n\n\n\n<p>If you run this command and check the contents inside the .tgz file, you will see that everything was included in the package, including the development app and the src folder.<\/p>\n\n\n\n<p>Since we are compiling everything, we want to deliver only the compiled files, so that we can keep the package as small as it can. To fix it, let\u2019s add something to the package.json file.<\/p>\n\n\n\n<p>Add the following to your package.json:<\/p>\n\n\n<pre class=\"wp-block-code alignwide\" aria-describedby=\"shcb-language-38\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">  <span class=\"hljs-string\">\"main\"<\/span>: <span class=\"hljs-string\">\"dist\/index.es.js\"<\/span>,\n  <span class=\"hljs-string\">\"module\"<\/span>: <span class=\"hljs-string\">\"dist\/index.es.js\"<\/span>,\n  <span class=\"hljs-string\">\"types\"<\/span>: <span class=\"hljs-string\">\"dist\/index.d.ts\"<\/span>,\n  <span class=\"hljs-string\">\"files\"<\/span>: &#91;\n    <span class=\"hljs-string\">\"dist\"<\/span>\n  ],<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-38\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now, if you run <code>npm run build<\/code>, <code>npm pack again<\/code>, and check the .tgz file content, you will have something like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"1920\" height=\"1020\" src=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250310-141441.png\" alt=\"\" class=\"wp-image-12704\" srcset=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250310-141441.png 1920w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250310-141441-600x319.png 600w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250310-141441-1200x638.png 1200w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250310-141441-768x408.png 768w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250310-141441-1536x816.png 1536w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/image-20250310-141441-760x404.png 760w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/figure>\n\n\n\n<p>You can also use the <code>.npmignore<\/code> file to exclude some files from the final package. It works similarly to the <code>.gitignore<\/code> file.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Next Steps and Good Practices<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">Adding a README<\/h4>\n\n\n\n<p>A good practice is to keep an updated README file for your SDK\/Library. Let\u2019s add one to ours:<\/p>\n\n\n<pre class=\"wp-block-code alignwide has-default-color has-white-background-color has-text-color has-background has-link-color wp-elements-d373845b334881b2cf9ea28b3b19d1d1\" aria-describedby=\"shcb-language-39\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"># Keyboard Shortcut SDK\n\nThis project is a simple SDK <span class=\"hljs-keyword\">for<\/span> registering and handling keyboard shortcuts <span class=\"hljs-keyword\">in<\/span> a web application. It provides an easy-to-use interface <span class=\"hljs-keyword\">for<\/span> defining keyboard shortcuts and their associated actions.\n\n## Table <span class=\"hljs-keyword\">of<\/span> Contents\n\n- &#91;Installation](#installation)\n- &#91;Usage](#usage)\n- &#91;Development](#development)\n- &#91;Build](#build)\n- &#91;License](#license)\n\n## Installation\n\nTo install the dependencies, <span class=\"hljs-attr\">run<\/span>:\n\n<span class=\"hljs-string\">``<\/span><span class=\"hljs-string\">`bash\nnpm install\n`<\/span><span class=\"hljs-string\">``<\/span>\n\n## Usage\n\nTo start the development server, <span class=\"hljs-attr\">run<\/span>:\n\n<span class=\"hljs-string\">``<\/span><span class=\"hljs-string\">`bash\nnpm run dev\n`<\/span><span class=\"hljs-string\">``<\/span>\n\nThis will start a Vite development server and open the example app <span class=\"hljs-keyword\">in<\/span> your <span class=\"hljs-keyword\">default<\/span> browser.\n\n### Registering a Shortcut\n\nTo register a keyboard shortcut, use the <span class=\"hljs-string\">`keyboardShortcutSDK().register`<\/span> method. For example:\n\n<span class=\"hljs-string\">``<\/span><span class=\"hljs-string\">`ts\nimport { keyboardShortcutSDK } from \"..\/src\"\n\nkeyboardShortcutSDK().register(\n  \"ctrl+s\",\n  () =&gt; {\n    alert(\"Saved!\")\n  },\n  {\n    feedback: 'Shortcut \"ctrl+s\" registered',\n  }\n)\n`<\/span><span class=\"hljs-string\">``<\/span>\n\n### <span class=\"hljs-string\">`register`<\/span> Method\n\nThe <span class=\"hljs-string\">`register`<\/span> method allows you to register a custom keyboard shortcut that can trigger specific actions within your application.\n\n#### Parameters\n\n- <span class=\"hljs-string\">`shortcut`<\/span> (string): The keyboard shortcut to register (e.g., <span class=\"hljs-string\">'Ctrl+S'<\/span>).\n- <span class=\"hljs-string\">`callback`<\/span> (<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span>): <span class=\"hljs-title\">The<\/span> <span class=\"hljs-title\">function<\/span> <span class=\"hljs-title\">to<\/span> <span class=\"hljs-title\">be<\/span> <span class=\"hljs-title\">executed<\/span> <span class=\"hljs-title\">when<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">shortcut<\/span> <span class=\"hljs-title\">is<\/span> <span class=\"hljs-title\">pressed<\/span>.\n- `<span class=\"hljs-title\">options<\/span>` (<span class=\"hljs-params\">object, optional<\/span>): <span class=\"hljs-title\">Optional<\/span> <span class=\"hljs-title\">settings<\/span> <span class=\"hljs-title\">for<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">shortcut<\/span> <span class=\"hljs-title\">registration<\/span>.\n  - `<span class=\"hljs-title\">condition<\/span>` (<span class=\"hljs-params\">(<\/span>) =&gt; <span class=\"hljs-title\">boolean<\/span>, <span class=\"hljs-title\">optional<\/span>): <span class=\"hljs-title\">If<\/span> <span class=\"hljs-title\">it<\/span> <span class=\"hljs-title\">returns<\/span> <span class=\"hljs-title\">true<\/span> <span class=\"hljs-title\">when<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">shortcut<\/span> <span class=\"hljs-title\">is<\/span> <span class=\"hljs-title\">called<\/span>, <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">shortcut<\/span> <span class=\"hljs-title\">callback<\/span> <span class=\"hljs-title\">will<\/span> <span class=\"hljs-title\">run<\/span>. <span class=\"hljs-title\">Otherwise<\/span>, <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">callback<\/span> <span class=\"hljs-title\">is<\/span> <span class=\"hljs-title\">not<\/span> <span class=\"hljs-title\">triggered<\/span>.\n  - `<span class=\"hljs-title\">feedback<\/span>` (<span class=\"hljs-params\">string, optional<\/span>): <span class=\"hljs-title\">A<\/span> <span class=\"hljs-title\">feedback<\/span> <span class=\"hljs-title\">string<\/span> <span class=\"hljs-title\">that<\/span> <span class=\"hljs-title\">will<\/span> <span class=\"hljs-title\">appear<\/span> <span class=\"hljs-title\">in<\/span> <span class=\"hljs-title\">a<\/span> <span class=\"hljs-title\">toast<\/span> <span class=\"hljs-title\">when<\/span> <span class=\"hljs-title\">registering<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">shortcut<\/span>.\n\n#### <span class=\"hljs-title\">Examples<\/span>\n\n```<span class=\"hljs-title\">ts<\/span>\n\/\/ <span class=\"hljs-title\">Register<\/span> <span class=\"hljs-title\">a<\/span> '<span class=\"hljs-title\">Ctrl<\/span>+<span class=\"hljs-title\">S<\/span>' <span class=\"hljs-title\">shortcut<\/span> <span class=\"hljs-title\">that<\/span> <span class=\"hljs-title\">saves<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">current<\/span> <span class=\"hljs-title\">document<\/span>\n<span class=\"hljs-title\">KeyboardShortcutSDK<\/span>.<span class=\"hljs-title\">register<\/span>(<span class=\"hljs-params\"><span class=\"hljs-string\">'Ctrl+S'<\/span>, saveDocument<\/span>);\n\n\/\/ <span class=\"hljs-title\">Register<\/span> <span class=\"hljs-title\">a<\/span> '<span class=\"hljs-title\">Ctrl<\/span>+<span class=\"hljs-title\">Z<\/span>' <span class=\"hljs-title\">shortcut<\/span> <span class=\"hljs-title\">that<\/span> <span class=\"hljs-title\">undoes<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">last<\/span> <span class=\"hljs-title\">action<\/span>, <span class=\"hljs-title\">and<\/span> <span class=\"hljs-title\">has<\/span> <span class=\"hljs-title\">a<\/span> <span class=\"hljs-title\">feedback<\/span>\n<span class=\"hljs-title\">KeyboardShortcutSDK<\/span>.<span class=\"hljs-title\">register<\/span>(<span class=\"hljs-params\"><span class=\"hljs-string\">'Ctrl+Z'<\/span>, undoLastAction, { feedback: <span class=\"hljs-string\">\"Ctrl+Z shortcut registered!\"<\/span> }<\/span>);\n\n\/\/ <span class=\"hljs-title\">Register<\/span> <span class=\"hljs-title\">a<\/span> '<span class=\"hljs-title\">Ctrl<\/span>+<span class=\"hljs-title\">X<\/span>' <span class=\"hljs-title\">shortcut<\/span> <span class=\"hljs-title\">that<\/span> <span class=\"hljs-title\">removes<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">last<\/span> <span class=\"hljs-title\">action<\/span> <span class=\"hljs-title\">only<\/span> <span class=\"hljs-title\">if<\/span> <span class=\"hljs-title\">it<\/span> <span class=\"hljs-title\">exists<\/span>\n<span class=\"hljs-title\">KeyboardShortcutSDK<\/span>.<span class=\"hljs-title\">register<\/span>(<span class=\"hljs-params\"><span class=\"hljs-string\">'Ctrl+X'<\/span>, removeLastAction, { condition: lastActionExists }<\/span>);\n```\n\n## <span class=\"hljs-title\">Development<\/span>\n\n### <span class=\"hljs-title\">Project<\/span> <span class=\"hljs-title\">Structure<\/span>\n\n- `<span class=\"hljs-title\">src<\/span>\/`: <span class=\"hljs-title\">Contains<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">source<\/span> <span class=\"hljs-title\">code<\/span> <span class=\"hljs-title\">for<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">SDK<\/span>.\n- `<span class=\"hljs-title\">app<\/span>\/`: <span class=\"hljs-title\">Contains<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">example<\/span> <span class=\"hljs-title\">application<\/span> <span class=\"hljs-title\">demonstrating<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">usage<\/span> <span class=\"hljs-title\">of<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">SDK<\/span>.\n- `<span class=\"hljs-title\">dist<\/span>\/`: <span class=\"hljs-title\">The<\/span> <span class=\"hljs-title\">output<\/span> <span class=\"hljs-title\">directory<\/span> <span class=\"hljs-title\">for<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">build<\/span> <span class=\"hljs-title\">process<\/span>.\n\n### <span class=\"hljs-title\">Scripts<\/span>\n\n- `<span class=\"hljs-title\">npm<\/span> <span class=\"hljs-title\">run<\/span> <span class=\"hljs-title\">dev<\/span>`: <span class=\"hljs-title\">Starts<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">development<\/span> <span class=\"hljs-title\">server<\/span>.\n- `<span class=\"hljs-title\">npm<\/span> <span class=\"hljs-title\">run<\/span> <span class=\"hljs-title\">build<\/span>`: <span class=\"hljs-title\">Builds<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">project<\/span>.\n\n### <span class=\"hljs-title\">TypeScript<\/span> <span class=\"hljs-title\">Configuration<\/span>\n\n<span class=\"hljs-title\">The<\/span> <span class=\"hljs-title\">project<\/span> <span class=\"hljs-title\">uses<\/span> <span class=\"hljs-title\">TypeScript<\/span> <span class=\"hljs-title\">for<\/span> <span class=\"hljs-title\">type<\/span> <span class=\"hljs-title\">checking<\/span>. <span class=\"hljs-title\">The<\/span> <span class=\"hljs-title\">configuration<\/span> <span class=\"hljs-title\">files<\/span> <span class=\"hljs-title\">are<\/span>:\n\n- `<span class=\"hljs-title\">tsconfig<\/span>.<span class=\"hljs-title\">json<\/span>`: <span class=\"hljs-title\">Main<\/span> <span class=\"hljs-title\">TypeScript<\/span> <span class=\"hljs-title\">configuration<\/span>.\n- `<span class=\"hljs-title\">tsconfig<\/span>.<span class=\"hljs-title\">build<\/span>.<span class=\"hljs-title\">json<\/span>`: <span class=\"hljs-title\">TypeScript<\/span> <span class=\"hljs-title\">configuration<\/span> <span class=\"hljs-title\">for<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">build<\/span> <span class=\"hljs-title\">process<\/span>.\n\n## <span class=\"hljs-title\">Build<\/span>\n\n<span class=\"hljs-title\">To<\/span> <span class=\"hljs-title\">build<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">project<\/span>, <span class=\"hljs-title\">run<\/span>:\n\n```<span class=\"hljs-title\">bash<\/span>\n<span class=\"hljs-title\">npm<\/span> <span class=\"hljs-title\">run<\/span> <span class=\"hljs-title\">build<\/span>\n```\n\n<span class=\"hljs-title\">This<\/span> <span class=\"hljs-title\">will<\/span> <span class=\"hljs-title\">generate<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">output<\/span> <span class=\"hljs-title\">files<\/span> <span class=\"hljs-title\">in<\/span> <span class=\"hljs-title\">the<\/span> `<span class=\"hljs-title\">dist<\/span>\/` <span class=\"hljs-title\">directory<\/span>.\n\n## <span class=\"hljs-title\">License<\/span>\n\n<span class=\"hljs-title\">This<\/span> <span class=\"hljs-title\">project<\/span> <span class=\"hljs-title\">is<\/span> <span class=\"hljs-title\">licensed<\/span> <span class=\"hljs-title\">under<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">MIT<\/span> <span class=\"hljs-title\">License<\/span>.\n<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-39\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h4 class=\"wp-block-heading\">Best practices and additional information<\/h4>\n\n\n\n<p>Some more good practices and additional information you should keep in mind when building an SDK:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Add tests: <\/strong>Tests are critical to maintain consistency between version management in an SDK. They also help keep the quality of your source code.<\/li>\n\n\n\n<li><strong>Add documentation:<\/strong> As the SDK grows, probably a README file will not be enough in terms of documentation. The use of a tool that helps build documentation, like Typedoc, may be a good option, but you can keep markdown files inside a <code>docs<\/code> folder too.<\/li>\n\n\n\n<li><strong>Code Styling: <\/strong>The use of tools like <a href=\"https:\/\/eslint.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">ESLint<\/a>, <a href=\"https:\/\/prettier.io\/\">Prettier<\/a>, and <a href=\"https:\/\/editorconfig.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Editor Config<\/a> is very recommended for any project. In an SDK context, it is even more recommended, since if you are open-sourcing it, many people can contribute to the code, and with these tools, the code can be kept in the same style. Tools like <a href=\"https:\/\/typicode.github.io\/husky\/\" target=\"_blank\" rel=\"noreferrer noopener\">Husky<\/a> and <a href=\"https:\/\/github.com\/lint-staged\/lint-staged\" target=\"_blank\" rel=\"noreferrer noopener\">Lint Staged<\/a> are strongly recommended also.<\/li>\n\n\n\n<li><strong>Package publishing: <\/strong>You can refer to the <a href=\"https:\/\/docs.npmjs.com\/creating-and-publishing-unscoped-public-packages\" target=\"_blank\" rel=\"noreferrer noopener\">NPM documentation<\/a> on publishing your package on the NPM Registry.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Framework-agnostic SDKs represent a leap toward inclusive and sustainable software development. By adhering to best practices and focusing on universal design principles, you can create tools that empower developers, regardless of their framework preferences.<\/p>\n\n\n\n<p>Start building your framework-agnostic SDK today, and watch as your tool becomes a cornerstone of countless projects!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In today\u2019s diverse frontend ecosystem, building tools that cater to a single framework can limit their reach and usability. Not only this, you may also want to target the whole frontend ecosystem, without limitations related to libs and frameworks. Framework-agnostic SDKs offer a solution by ensuring compatibility across multiple frameworks while maintaining performance and simplicity. [&hellip;]<\/p>\n","protected":false},"author":89,"featured_media":12685,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[432],"tags":[1312,1259,1199],"class_list":["post-12680","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-engineering","tag-front-end-development","tag-scalable-application","tag-software-development"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>How to Build a Scalable Framework-Agnostic Web SDKs: Step by Step<\/title>\n<meta name=\"description\" content=\"Check all the steps to build a Framework-Agnostic Web SDKs that scale across React, Vue, and more. Learn best practices, tools, and tips.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to Build a Scalable Framework-Agnostic Web SDKs: Step by Step\" \/>\n<meta property=\"og:description\" content=\"Check all the steps to build a Framework-Agnostic Web SDKs that scale across React, Vue, and more. Learn best practices, tools, and tips.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/\" \/>\n<meta property=\"og:site_name\" content=\"Cheesecake Labs\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/cheesecakelabs\" \/>\n<meta property=\"article:published_time\" content=\"2025-04-25T18:20:50+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-04-25T21:39:31+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Framework-Agnostic-Web-SDKs.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"860\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Cheesecake Labs\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@cheesecakelabs\" \/>\n<meta name=\"twitter:site\" content=\"@cheesecakelabs\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/\"},\"author\":{\"name\":\"Italo Menezes\"},\"headline\":\"How to Build a Scalable Framework-Agnostic Web SDKs: Step by Step\",\"datePublished\":\"2025-04-25T18:20:50+00:00\",\"dateModified\":\"2025-04-25T21:39:31+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/\"},\"wordCount\":2346,\"image\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Framework-Agnostic-Web-SDKs.jpg\",\"keywords\":[\"front-end development\",\"scalable application\",\"software development\"],\"articleSection\":[\"Engineering\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/\",\"url\":\"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/\",\"name\":\"How to Build a Scalable Framework-Agnostic Web SDKs: Step by Step\",\"isPartOf\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Framework-Agnostic-Web-SDKs.jpg\",\"datePublished\":\"2025-04-25T18:20:50+00:00\",\"dateModified\":\"2025-04-25T21:39:31+00:00\",\"author\":{\"@type\":\"person\",\"name\":\"Italo Menezes\"},\"description\":\"Check all the steps to build a Framework-Agnostic Web SDKs that scale across React, Vue, and more. Learn best practices, tools, and tips.\",\"breadcrumb\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/#primaryimage\",\"url\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Framework-Agnostic-Web-SDKs.jpg\",\"contentUrl\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Framework-Agnostic-Web-SDKs.jpg\",\"width\":1920,\"height\":860},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cheesecakelabs.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to Build a Scalable Framework-Agnostic Web SDKs: Step by Step\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/#website\",\"url\":\"https:\/\/cheesecakelabs.com\/blog\/\",\"name\":\"Cheesecake Labs\",\"description\":\"Nearshore outsourcing company for Web and Mobile design and engineering services, and staff augmentation for startups and enterprises..\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cheesecakelabs.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"name\":\"Italo Menezes\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/italo-menezes.jpeg\",\"contentUrl\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/italo-menezes.jpeg\",\"caption\":\"Italo Menezes\"},\"url\":\"https:\/\/cheesecakelabs.com\/blog\/autor\/italo-menezes\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"How to Build a Scalable Framework-Agnostic Web SDKs: Step by Step","description":"Check all the steps to build a Framework-Agnostic Web SDKs that scale across React, Vue, and more. Learn best practices, tools, and tips.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/","og_locale":"en_US","og_type":"article","og_title":"How to Build a Scalable Framework-Agnostic Web SDKs: Step by Step","og_description":"Check all the steps to build a Framework-Agnostic Web SDKs that scale across React, Vue, and more. Learn best practices, tools, and tips.","og_url":"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/","og_site_name":"Cheesecake Labs","article_publisher":"https:\/\/www.facebook.com\/cheesecakelabs","article_published_time":"2025-04-25T18:20:50+00:00","article_modified_time":"2025-04-25T21:39:31+00:00","og_image":[{"width":1920,"height":860,"url":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Framework-Agnostic-Web-SDKs.jpg","type":"image\/jpeg"}],"author":"Cheesecake Labs","twitter_card":"summary_large_image","twitter_creator":"@cheesecakelabs","twitter_site":"@cheesecakelabs","twitter_misc":{"Written by":null,"Est. reading time":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/#article","isPartOf":{"@id":"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/"},"author":{"name":"Italo Menezes"},"headline":"How to Build a Scalable Framework-Agnostic Web SDKs: Step by Step","datePublished":"2025-04-25T18:20:50+00:00","dateModified":"2025-04-25T21:39:31+00:00","mainEntityOfPage":{"@id":"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/"},"wordCount":2346,"image":{"@id":"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/#primaryimage"},"thumbnailUrl":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Framework-Agnostic-Web-SDKs.jpg","keywords":["front-end development","scalable application","software development"],"articleSection":["Engineering"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/","url":"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/","name":"How to Build a Scalable Framework-Agnostic Web SDKs: Step by Step","isPartOf":{"@id":"https:\/\/cheesecakelabs.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/#primaryimage"},"image":{"@id":"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/#primaryimage"},"thumbnailUrl":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Framework-Agnostic-Web-SDKs.jpg","datePublished":"2025-04-25T18:20:50+00:00","dateModified":"2025-04-25T21:39:31+00:00","author":{"@type":"person","name":"Italo Menezes"},"description":"Check all the steps to build a Framework-Agnostic Web SDKs that scale across React, Vue, and more. Learn best practices, tools, and tips.","breadcrumb":{"@id":"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/#primaryimage","url":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Framework-Agnostic-Web-SDKs.jpg","contentUrl":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/Framework-Agnostic-Web-SDKs.jpg","width":1920,"height":860},{"@type":"BreadcrumbList","@id":"https:\/\/cheesecakelabs.com\/blog\/how-to-build-framework-agnostic-web-sdks\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cheesecakelabs.com\/blog\/"},{"@type":"ListItem","position":2,"name":"How to Build a Scalable Framework-Agnostic Web SDKs: Step by Step"}]},{"@type":"WebSite","@id":"https:\/\/cheesecakelabs.com\/blog\/#website","url":"https:\/\/cheesecakelabs.com\/blog\/","name":"Cheesecake Labs","description":"Nearshore outsourcing company for Web and Mobile design and engineering services, and staff augmentation for startups and enterprises..","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cheesecakelabs.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","name":"Italo Menezes","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cheesecakelabs.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/italo-menezes.jpeg","contentUrl":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2025\/04\/italo-menezes.jpeg","caption":"Italo Menezes"},"url":"https:\/\/cheesecakelabs.com\/blog\/autor\/italo-menezes\/"}]}},"_links":{"self":[{"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/posts\/12680","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/users\/89"}],"replies":[{"embeddable":true,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/comments?post=12680"}],"version-history":[{"count":4,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/posts\/12680\/revisions"}],"predecessor-version":[{"id":12714,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/posts\/12680\/revisions\/12714"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/media\/12685"}],"wp:attachment":[{"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/media?parent=12680"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/categories?post=12680"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/tags?post=12680"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}