Skip to content

Proposal: Add a tags Proxy to Crank Core #391

Description

@zakarialaoui10

Motivation

While implementing crank-mdx, I needed a convenient way for the transpiler to generate element creation calls without having to explicitly import or define every HTML tag.

For example, the transpiler currently generates:

import { tags } from "crank-mdx/tags";

and Markdown such as:

# Heading 1

Paragraph

List:
- item 1
- item 2

can be transformed into:

export default () => {
	const { h1, p, ul, li } = tags;
	const __items__ = [];

	__items__.push(h1({}, "Heading 1"));
	__items__.push(p({}, "Paragraph"));
	__items__.push(p({}, "List:"));
	__items__.push(
		ul(
			{},
			li({}, p({}, "item 1")),
			li({}, p({}, "item 2"))
		)
	);

	return __items__;
};

To support this, I implemented a small tags proxy:

Why add it to Crank core?

The implementation is intentionally very small and does not introduce another rendering abstraction. It simply provides a dynamic interface to the existing createElement function.

Instead of maintaining a separate implementation in projects such as crank-mdx, Crank could expose this utility directly:

import { tags } from "@b9g/crank";

const { div, h1, p } = tags;

h1({}, "Hello");
p({}, "This is Crank");

This also makes the API useful outside of MDX. Potential use cases include:

  • Markdown/MDX transpilers
  • HTML-to-Crank transformations
  • template compilers
  • code generators
  • hyperscript-style APIs
  • applications that prefer tag functions over JSX
  • integrations with other markup languages

It complements createElement

I don't see tags as an alternative to createElement. Rather, it is a small convenience layer on top of it.

Conceptually:

tags.div(props, children...)
        │
        ▼
createElement("div", props, children...)
        │
        ▼
Crank Element

This keeps the actual element creation logic in one place.

Another benefit for transpilers

A transpiler can generate:

const { h1, p, ul, li } = tags;

instead of having to generate or maintain a static mapping such as:

import {
	h1,
	p,
	ul,
	li
} from "...";

The proxy also means that arbitrary/custom element names can be handled without requiring the core package to maintain a list of every possible tag.

For example:

Minimal API addition

The public API could simply be:

export { createElement } from "./...";
export { tags } from "./...";

There is no new component model, state system, renderer, or runtime dependency involved.

The implementation is essentially a cached dynamic wrapper around the existing createElement API.

crank-mdx

With tags available from Crank itself, crank-mdx would no longer need to ship its own implementation:

import { tags } from "@b9g/crank";

instead of:

import { tags } from "crank-mdx/tags";

This would make the generated output more directly tied to Crank's public API and allow other Crank-related transpilers and tools to reuse the same primitive.

Summary

I propose adding tags as a small utility to Crank core.

It provides a dynamic tag-function interface over createElement, requires very little code, does not change Crank's existing architecture, and is useful for transpilers and hyperscript-style usage beyond crank-mdx.

The main motivation came from crank-mdx, but the utility itself is independent of MDX and could be useful as a general part of Crank's element-creation API.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions