gnikyt Code ramblings.

Type definitions without emitting

Introduction

A Shopify-based project was assigned which was a backend app with frontend extensions. After completion of the backend, it was realized that the frontend extensions needed types from the backend.

Extensions with Shopify's setup does not allow importing relative packages. For example, if this was the loose structure:

index.ts index.test.ts

amount.ts amount.test.ts id.ts id.test.ts

BlockExtension.tsx

We would not be able to do something such as:

// extensions/order-stats/src/BlockExtension.tsx

import { type PaginatedRecords } from "../../../app/paginate";
import { type ShopifyOrderIdValue } from "../../../app/value/id";

This would produce an error and not compile. You could copy/paste the type definitions into the extension manually, but this is not a great idea.. what if the types you're importing change? They would be out of sync if you did not manually correct it.

A solution is to generate types based upon the app into a local package which the extensions can use.

types package

Create a directory at the root called types, then cd inside and run npm init. Once done, you should have a package.json inside similar to this:

{
  "name": "app-types",
  "private": true,
  "version": "1.0.0"
}

In my case, I named the package app-types, but you're free to name as you please.

Generation

Create a bin/app-typegen with the following contents:

#!/usr/bin/env bash

# List the files you would like to generate types for
srcs=("paginate/index" "value/id", "value/amount")
for src in "${srcs[@]}"; do
  # Generation to the types/ directory
  tsc -d "app/$src.ts" --declarationDir types
  # Remove Typescript-generated .js files
  for file in $(git status -s); do
    if [[ $file == *"app/"* ]] && [[ $file == *".js"* ]]; then
      rm "$file"
    fi
  done
done

What this script does is allow you to list out the files you would like to generate types for. It will then utilize tsc to generate the types and dump the d.ts files into the types directory we previously created. Next, it will check the status of git to remove the generated Javascript files as there appears to be no way to not emit Javascript files during generation.

Additionally, you can add a slot into your root package.json scripts:

"generate:app-types": "bin/app-typegen"

Then, you can simply run npm run generate:app-types manually or with your CI setup to (re)generate the types.

Importing

Modify the package.json for the package you would like to use the types. In my case, extensions/order-stats/package.json.

Adding, "app-types": "file:../../../types" to devDependencies, then running npm install.

Now, you'll be able to import the types without compilation errors or warnings as such:

// extensions/order-stats/src/BlockExtension.tsx

import { type PaginatedRecords } from "app-types/paginate/index";
import { type ShopifyOrderIdValue } from "app-types/value/id";