npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@solid-primitives/jsx-tokenizer

v1.0.10

Published

A primitive to tokenize your solid-components to enable custom parsing.

Downloads

846

Readme

@solid-primitives/jsx-tokenizer

version stage

A set of primitives that help safely pass data through JSX to the parent component using token components.

This pattern is very useful when you want to use JSX to create a declarative API for your components. It lets you resolve the JSX structure and pass the data to the parent component without triggering rendering of the children - it puts the parent in control over what getting rendered.

  • createTokenizer — Creates a JSX Tokenizer that can be used to create multiple token components with the same id.
  • createToken — Creates a token component for passing custom data through JSX structure.
  • resolveTokens — Resolves passed JSX structure, searching for tokens with the given tokenizer id.
  • isToken — Checks if passed value is a token created by the corresponding jsx-tokenizer.

Installation

npm install @solid-primitives/jsx-tokenizer
# or
pnpm add @solid-primitives/jsx-tokenizer
# or
yarn add @solid-primitives/jsx-tokenizer

createTokenizer

Creates a JSX Tokenizer that can be used to create multiple token components with the same id and resolve their data from the JSX Element structure.

How to use it

createTokenizer takes an optional options param with name property to identify the parser during development.

It also a generic type representing the union of accepted token data.

import { createTokenizer, createToken, resolveTokens } from "@solid-primitives/jsx-tokenizer";

const Tokenizer = createTokenizer<Token1 | Token2>({
  name: "Example Tokenizer", // optional (used for warnings during development)
});

// lets you create multiple token components with the same id:
const MyTokenA = createToken(Tokenizer, props => ({ type: "A" }));

const MyTokenB = createToken(Tokenizer, props => ({ type: "B" }));

function ParentComponent(props) {
  const tokens = resolveTokens(Tokenizer, () => props.children);
  return (
    <ul>
      <For each={tokens()}>{token => <li>{token.data.type}</li>}</For>
    </ul>
  );
}

<ParentComponent>
  <MyTokenA />
  <MyTokenB />
</ParentComponent>;

createToken

Creates a token component for passing custom data through JSX structure.

The token component can be used as a normal component in JSX.

When resolved by resolveTokens it will return the data passed to it.

But when resolved normally (e.g. using the children() helper) it will return the fallback JSX Element.

How to use it

createToken takes three parameters: (all are optional)

  • tokenizer - identity object returned by createTokenizer or other token component. If not passed, a new tokenizer id will be created. (If not passed, a new tokenizer id will be created.)
  • tokenData - function that returns the data of the token (if one isn't passed, props will be used as data)
  • render - function that returns the fallback JSX Element to render (If not passed, the token will render nothing and warn in development.)
import { createToken } from "@solid-primitives/jsx-tokenizer";

const TokenExample = createToken(
  // identity object returned by `createTokenizer` or other token component
  parser,
  // function that returns the data of the token - called when the token is resolved by `resolveTokens`
  (props: { id: string }) => {
    const value = Math.random();
    return {
      props,
      value,
    };
  },
  // function that returns the fallback JSX Element to render - called when the token rendered by Solid
  props => <span>{props.id}</span>,
);

This token can then be used inside JSX as a component:

const Child = () => {
  return <TokenExample id="id" />;
};

TokenExample is typed as a JSXElement, this is so TokenExample can be used in JSX without causing type-errors.

Using without tokenizer

If createToken is called without a tokenizer, it will create a new tokenizer id by itself. Then the token component can be used in resolveTokens as the tokenizer in the same way as if it was created with createTokenizer.

import { createToken, resolveTokens } from "@solid-primitives/jsx-tokenizer";

function Tabs<T>(props: { children: (Tab: Component<{ value: T }>) => JSX.Element; active: T }) {
  const Tab = createToken((props: { value: T }) => props.value);
  // resolveTokens will look for tokens created by Tab component
  const tokens = resolveTokens(Tab, () => props.children(Tab));
  return (
    <ul>
      <For each={tokens()}>
        {token => <li classList={{ active: token.data === props.active }}>{token.data}</li>}
      </For>
    </ul>
  );
}

// usage
<Tabs active="tab1">
  {Tab => (
    <>
      <Tab value="tab1" />
      <Tab value="tab2" />
    </>
  )}
</Tabs>;

resolveTokens

A function similar to Solid's children(). Resolves passed JSX structure, searching for tokens with the given tokenizer id.

How to use it

resolveTokens takes three parameters:

  • tokenizer - identity object returned by createTokenizer or a token component. An array of multiple tokenizers can be passed.
  • fn accessor that returns a JSX Element (e.g. () => props.children)
  • options options for the resolver:
    • includeJSXElements - if true, other JSX Elements will be included in the result array (default: false)

resolveTokens will return a signal that returns an array of resolved tokens and JSX Elements.

Token data is available on the data property of the token.

import { resolveTokens } from "@solid-primitives/jsx-tokenizer";

const tokens = resolveTokens(tokenizer, () => props.children);

createEffect(() => {
  tokens().forEach(token => {
    // token is a function that returns the JSX Element fallback
    // token.data is the data returned by the tokenData function
    console.log(token.data);
  });
});

// the return value of resolveTokens can be used in JSX (will render the fallback JSX Elements)
return <>{els()}</>;

Resolve JSX Elements with resolveTokens

If you want to resolve the JSX Elements as well, you can pass { includeJSXElements: true } as the third parameter to resolveTokens.

Use isToken to validate if a value is a token created by the corresponding jsx-tokenizer.

import { resolveTokens, isToken } from "@solid-primitives/jsx-tokenizer";

const els = resolveTokens(tokenizer, () => props.children, {
  includeJSXElements: true,
});

createEffect(() => {
  els().forEach(el => {
    if (!isToken(tokenizer, el)) {
      // el is a normal JSX Element
      return;
    }
    // token is a function that returns the JSX Element fallback
    // token.data is the data returned by the tokenData function
    console.log(token.data);
  });
});

// the return value of resolveTokens can be used in JSX
return <>{els()}</>;

Resolve multiple tokenizers

If you want to resolve multiple tokenizers at once, you can pass an array of tokenizers as the first parameter to resolveTokens.

import { resolveTokens } from "@solid-primitives/jsx-tokenizer";

const els = resolveTokens([tokenizer1, tokenizer2, MyTokenComponent], () => props.children);

Usage with Context API

Since resolveTokens is eagerly resolving the JSX structure, if you want to provide context for the tokens to be accessed in the tokenData function, you have to wrap resolveTokens with the provider:

function ParentComponent(props) {
  return (
    <MyContext.Provider value={{} /* some value */}>
      {untrack(() => {
        const tokens = resolveTokens(tokenizer, () => props.children);

        // handle tokens ...

        return <>{tokens()}</>;
      })}
    </MyContext.Provider>
  );
}

Also if you should be wary of placing context providers in between the component resolving the tokens and tokens passed as children. This will cause the context to be available in the tokenData function, but not necessarily when resolving the children of the tokens - as it might happen asynchronously under a different owner.

For example, @solidjs/router which uses the same pattern, will break if you put a context provider between the <Routes /> and <Route /> components.

// this will break
function App() {
  return (
    <Routes>
      <MyContext.Provider value={{} /* some value */}>
        {/*
          <Route> component prop is not rendered immediately, it is rendered within <Routes>
          as later time, so the context will not be available in Home component
        */}
        <Route path="/" component={Home} />
      </MyContext.Provider>
    </Routes>
  );
}

function Home() {
  const ctx = useContext(MyContext);
  ctx; // => undefined
}

// do this instead
function App() {
  return (
    <MyContext.Provider value={{} /* some value */}>
      <Routes>
        <Route path="/" component={Home} />
      </Routes>
    </MyContext.Provider>
  );
}

isToken

A function to validate if a value is a token created by the corresponding jsx-tokenizer.

How to use it

isToken takes a value, often this would be a JSXElement. The function returns false in case the value is not a token created by the corresponding jsx-tokenizer. In case the value is a token isToken returns the value cast to a token.

const token = props.children[0]; // value is typed as a JSXElement
if (!isToken(tokenizer, token)) return;
token; // token is typed as UnionOfAcceptedTokens

isToken can take an array of tokenizers as the first parameter. In this case it will return false if the value is not a token created by any of the tokenizers.

isToken([tokenizer1, tokenizer2, MyTokenComponent], token);

Demo

Live Example | Source Code

Changelog

See CHANGELOG.md