question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Extending interface Data for better user TypeScript experience

See original GitHub issue

Description

Since I’m currently using the JSX plugin in combination with TypeScript for creating my blog, after talking to @oscarotero we came up with the idea of extending the Data interface with a PageData interface. To make a better user experience it makes sense to include already known default properties of Lume types in that interface.

Example of TS errors, when no type assertions/definitions exist for [index: string]: unknown.

(parameter) site: unknown
Object is of type 'unknown'.deno-ts(2571)
(parameter) title: unknown
Type 'unknown' is not assignable to type 'ComponentChildren'.deno-ts(2322)

This would reduce the needing of creating type assertions or type guards for those properties by the user.

Experience

While developing I was in need of the following missing but existing propperties in Data:

  • children: JSX.IntrinsicElements
  • comp: any since ProxyComponents did not work that great
  • paginate: Paginator
  • pagination: ?
  • results: unknown[] (pain)
  • search: Search

While developing as a workaround, I created a custom interface to extend Data and also override Pages and added the missing typings:

import type { Data, Helper, Page as BasePage } from "lume/core.ts";
// Create custom typings to override `PaginateResults` like described below

export interface PageData extends Data { ... }

export interface Page extends BasePage {
  data: PageData;
}

export interface Helpers {
  [key: string]: Helper;
}

// Now I could use inside my pages, templates and layouts (example of my layouts/posts.tsx):
import type { Helpers, Page, PageData } from "@types";

export default ({ comp, excerpt, title, results, pagination }: PageData, { url, date }: Helpers) => {
  ...
}

While I could easily use Search and Paginator, the following changes had to made on existing interfaces:

  1. PaginateResults["pagination"] had to be moved out of Paginator as it’s own interface

  2. ProxyComponents some how needed a modification. I could not get into and used any to overcome the ts error:

any
Property 'navbar' does not exist on type 'ProxyComponents | ComponentFunction'.
Property 'navbar' does not exist on type 'ComponentFunction'.deno-ts(2339)

Suggestions for restructuring existing types

  1. Move Pagination object out of PaginateResult
export interface Pagination {
  /** The current page number */
  page: number;

  /** The total number of pages */
  totalPages: number;

  /** The total number of elements */
  totalResults: number;

  /** The url of the previous page */
  previous: string | null;

  /** The url of the next page */
  next: string | null;
}

export interface PaginateResult<T = Page> {
  /** The page url */
  url: string;

  /** The page elements */
  results: T[];

  /** The pagination info */
  pagination: Pagination
  
  /**
   * (Maybe) important when defining `data.menu` or `data.type` inside `paginate()`
   * without I get TS error:
   * any
   * Property 'menu' does not exist on type 'PaginateResult<Page>'.deno-ts(2339)
   */
  [index: string]: unknown;
}
  1. I have no idea how to modify ProxyComponents or ComponentFunction to get rid of the error described above.

Suggestions for interface PageData

  1. Implementation of existing Lume types and default propperties
import type { Data, Page } from "lume/core.ts";
import type { MetaData } from "lume/plugins/metas.ts"
import type { Search } from "lume/plugins/search.ts";

// If restructuring would have been done already like descriped above
import type { Pagination, Paginator, PaginateResult } from "plugins/paginate.ts";

/**
 * Like described in https://lume.land/plugins/search/#description
 * Should be implemented as default
 */
export interface Menu {
  title: string;
  visible: boolean;
  order: number;
}

export interface PageData extends Data {
  /** The title of the page */
  title?: string;
  
  /**
   * The JSX children/content
   * In addition to make this work, we must modify all JSX plugins so they import `JSX` by default
   */
  children?: JSX.IntrinsicElements;
  
  /**
   * The available components
   * Maybe someone has an idea how to modify like described above to replace `any`
   */
  comp?: any;
  
  /** The menu for top level site navigation */
  menu?: Menu;
  
  /** The Metas plugin object */
  metas?: MetaData;

  /** The Pagination helper */
  paginate?: Paginator;

  /** The pagination info */
  pagination?: Pagination;

  /**
   * The Paginator results
   * Not sure this is best practice, but works.
   */
  results?: PaginateResult[];

  /** The Search helper */
  search?: Search;

  /**
   * The `site` object from root `_data.{ts,yaml}` when not using Metas plugin
   * Would be cool to integrate it somehow
   * I set my object `as const` and imported it, then used `typeof SiteMeta`
   */
  site?: any;
}

Conclusion

It would be awesome if we could restructure the interfaces like this. I’m not sure how many users are using JSX with TypeScript in Lume, but I think this will be a great improvement, since I struggled with custom type guards and also type assertions and didn’t like them.

In addition it would be cool to have two more docs on the Lume page:

  • Core > Navigation (which desrcribes the usage of menu, since it’s too deep nested/hidden at the moment)
  • Configuration > Typescript (a small guide on how to use with TypeScript, extend existing or implement custom typings)

@oscarotero, I appreciate that you take the time. Looking forward on your thoughts. If you need an example repo, let me know.

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:9 (9 by maintainers)

github_iconTop GitHub Comments

1reaction
oscaroterocommented, Aug 8, 2022

After chatting in Discord, the Windows error is fixed. Preact will be added in 1.11.0.

1reaction
oscaroterocommented, Aug 5, 2022

You can run deno upgrade --dev to upgrade Lume to the latest development version (the last commit). An additional way is, in your local clone of Lume repo, run deno task install.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Extending object-like types with interfaces in TypeScript
Option 2: Extending interfaces in TypeScript. Another way to expand interfaces in TypeScript is to mix one or more of them into a...
Read more >
How to Extend an Interface from a class in TypeScript
In this article, we will try to understand how we to extend an interface from a class in TypeScript with the help of...
Read more >
An Introduction to TypeScript Interfaces | by John Au-Yeung
Extending Interfaces. In TypeScript, interfaces can extend each other just like classes. This lets us copy the members of one interface to another...
Read more >
How To Use Interfaces in TypeScript | DigitalOcean
Interfaces in TypeScript enable you to represent and document various data structures. In this tutorial, you'll create interfaces, learn how ...
Read more >
Extending an interface in TypeScript - javascript - Stack Overflow
Interfaces don't get transpiled to JS, they're just there for defining types. You could create a new interface that would inherit from the ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found