1

I am trying to convert this JavaScript code from atom/etch to TypeScript without breaking the public API.

It defines both a dom-function and a dom-array of functions (both have the same name dom):

// called by the following loop
function dom (tag, props, ...children) {
// ...
}

const HTML_TAGS = [
  'a', 'abbr'] // ... has more elements though
// similarly SVG-Tags is defined

// finds the array of functions
for (const tagName of HTML_TAGS) {
  dom[tagName] = (props, ...children) => {
    return dom(tagName, props, ...children)
  }
}

for (const tagName of SVG_TAGS) {
  dom[tagName] = (props, ...children) => {
    return dom(tagName, props, ...children)
  }
}

module.exports = dom

What is the equivalent TypeScript version of this?

Other packages use dom like dom.a(tag, props, childern) an example, or using @jsx etch.dom an example

By running dts-gen, I get a namespace called dom which contains all the functions defined inside the for-loop.

export namespace dom {
    function a(props: any, children: any): any;
    function abbr(props: any, children: any): any;
//...
}

Here is my branch.

1 Answer 1

1

I assume you're authoring a dom.d.ts type definition file for dom.js.

Namespace is not a proper type for dom. TS namespace in runtime JS is presented as plain object. But dom is both a callable function and an object with extra properties. Thus you should use an interface with callable signature to represent dom in TS.

dom.d.ts

interface EtchElement<T extends string, P = any> {
  tag: T;
  props: P;
  children: any[];
  ambiguous: any[];
}

type EtchCreateElement<T extends string, P> = (props: P, ...children: any[]) => EtchElement<T, P>;

interface EtchDOM {
  <T extends string, P>(tag: T, props: P, ...children: any[]): EtchElement<T, P>;
  div: EtchCreateElement<"div", any>;
  // ... more tags here
}

declare const dom: EtchDOM;

export = dom;

JSX Support

Now if you also intent to support JSX usage, you'll need to read through the official JSX guide first to learn the requirements. I'll highlight this excerption:

Intrinsic elements are looked up on the special interface JSX.IntrinsicElements. [...] if this interface is present, then the name of the intrinsic element is looked up as a property on the JSX.IntrinsicElements interface.

Put together, here's a modest type definition that works:

interface EtchElement<T extends string, P = any> {
  tag: T;
  props: P;
  children: any[];
  ambiguous: any[];
}

type EtchCreateElement<T extends string, P> = (props: P, ...children: any[]) => EtchElement<T, P>;

interface EtchDOM {
  <T extends string, P>(tag: T, props: P, ...children: any[]): EtchElement<T, P>;
  div: EtchCreateElement<"div", JSX.IntrinsicElements["div"]>;
  // ... more tags here
}

declare const dom: EtchDOM;

export = dom;

declare global {
  namespace JSX {
    interface Element extends EtchElement<any, any> {}
    interface IntrinsicElements {
      div: any; // constraint on props of "div" element
    }
  }
}

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for your detailed answer! Regarding your first assumption, I am trying to write the whole thing in TS: github.com/aminya/etch/tree/type_definitions/lib_src
@Amin you can merge my definition right into your .ts file. Copy paste, then change the export: const _dom: EtchDOM = dom as EtchDOM; export default _dom;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.