Skip to content

Support embedded JavaScript/TypeScript #648

Open
@remcohaszing

Description

@remcohaszing

There are several frameworks / languages in JavaScript land that contain embedded JavaScript or TypeScript. Some languages that come to mind are:

  • Astro (Volar)
  • Ember (Volar)
  • HTML script tags (Not yet imlemented)
  • Markdown code blocks (Not yet imlemented)
  • MDX (Volar)
  • Svelte (Custom)
  • Vue (Volar)

I am the maintainer of the MDX language tooling, so I can best sketch the situation from that point of view. MDX is basically markdown with JSX tags. Just like TypeScript or JSX, it gets compiled to JavaScript.

Currently Volar offers a solution for this. To support this, Volar has an API to abstract away creating a TypeScript plugin. This works ok, but TypeScript plugins depend on monkey-patching TypeScript provided objects. If the TypeScript team gets more picky on the public interface, that might break all of these integrations.

I think maybe this rewrite to Go would be a good opportunity to also think of an interface to add builtin support for such languages.

Volar plugins work by converting text content to a virtual file with mapped JavaScript or TypeScript content. For example, this:

export function Local() {}

<div local={<Local />} injected={<Injected />} string="string" boolean />

<div local={<Local>{null}</Local>} injected={<Injected>{null}</Injected>} string="string" boolean>
  paragraph
</div>

becomes this, with some mappings:

/* @jsxRuntime automatic
@jsxImportSource react */
export function Local() {}


/**
 * @internal
 *   **Do not use.** This function is generated by MDX for internal use.
 *
 * @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props
 *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.
 */
function _createMdxContent(props) {
  /**
   * @internal
   *   **Do not use.** This variable is generated by MDX for internal use.
   */
  const _components = {
    // @ts-ignore
    .../** @type {0 extends 1 & MDXProvidedComponents ? {} : MDXProvidedComponents} */ ({}),
    ...props.components,
    /** The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component. */
    props,
    /** {@link Local} */
    Local
  }
  _components
  return <>
    <div local={<Local />} injected={<_components.Injected />} string="string" boolean />
    <div local={<Local>{null}</Local>} injected={<_components.Injected>{null}</_components.Injected>} string="string" boolean>
    <>
    {''}
    </>
    </div>
  </>
}

/**
 * Render the MDX contents.
 *
 * @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props
 *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.
 */
export default function MDXContent(props) {
  return <_createMdxContent {...props} />
}

// @ts-ignore
/** @typedef {(void extends Props ? {} : Props) & {components?: {}}} MDXContentProps */

This is not actually what compiled MDX looks like. It’s similar, but it contains injected helper code to help / trick TypeScript.

I think TypeScript could provide a similar API. Some pseudo code:

import { Plugin } from 'tsgo'

const plugin: Plugin = (typescript) => {
  return (project) => {
    project.registerFileType({
      extension: ['.mdx'],  // For the compiler
      languageID: ['mdx'],  // For the language server
      scriptKind: typescript.ScriptKind.JSX,  // describes the virtual file content
      parse(content) {
        // Turn the MDX string content into something TypeScript understands
      }
    })
  }
}

export default plugin

There are still many open ends here:

  • Should the CLI support plugins? There’s definitely user demand.
  • Should emitting be supported? This is useful for some, but not all languages. Also the content that provides the best TypeScript experience often doesn’t match the compile output exactly.
  • Should emitting type definitions be supported? This is useful for some, but not all languages.
  • What should the source content be parsed into? A string, like Volar? Or a TypeScript AST?
  • Nodes from the virtual code might not exist in the source file.
  • What languages should be supported? Currently it’s all JavaScript, but MDX also has a Rust API which might be useful to speed things up.

Activity

NullVoxPopuli

NullVoxPopuli commented on May 13, 2025

@NullVoxPopuli

emitting type definitions would be hugely useful -- not just for library authoring (for ember's .gts files (or .svelte, .vue, etc)), but also for having a way to interact with typedoc and other tsc-using tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @NullVoxPopuli@remcohaszing@jakebailey

        Issue actions

          Support embedded JavaScript/TypeScript · Issue #648 · microsoft/typescript-go