Writing Storybook Stories with TSX for Stencil

Yes, you can!

If you want to write your stencil stories for storybook using TSX (the Typescript version of JSX), this is actually possible. A bit of tweaking, and you should go to go. BUT - this is still a very preliminary work, and I expect it to suffer quite few bumps in different scenarios. Nevertheless, I thought it it worth sharing, as I know people were looking for a solution.

This is assuming you are using the html version of Storybook with Stencil. You can find a working version in my stencil-one demo repo. (This is my demo repo for unit testing in Stencil, so feel free to look around).

These are the main versions I worked with. I have no idea if it will be compatible with other versions.

  • Stencil: 2.15

  • Storybook: 6

  • webpack: 5

  • babel-loader: 8

Step 1: Write your story

This is what a story should look like, using CSF3 (Component Story Format). Obviously, it should be in a file named .tsx, so it will go thru the right tooling. Also, change the path that looks for your stories to include .tsx files. (stories option in the main file).

/** @jsx h */
/** @jsxRuntime classic */

import {h} from '@stencil/core';

export default {
  title: 'My Basic',
};

export const Default = {
  render: () => (<my-basic first="Millie" last="Brown"></my-basic>)
};

Note the @jsx directives. They seem to be necessary.

Step 2: Configure Webpack for jsx

We are going to tweak .storybook/main so it will include the babel jsx transformer. In order to do that we will find the rule that handles tsx and add the @babel/plugin-transform-react-jsx as plugin with the pragma: {'h'}.

  webpackFinal: async (config) => {
    const tsxRule = config.module.rules.find(rule => 'a.tsx'.match(rule.test));

    if(tsxRule) {
        const options = tsxRule.use[0].options;
        options.plugins = [
          ['@babel/plugin-transform-react-jsx', {
            prgama: 'h'
          }],
          ...options.plugins
        ];
    }
    return config;
  }

Step 3: Render the stories using Stencil's renderVDom

The last part is changes to the storybook/preview code. Create a function that will be used as a decorator and will render elements to the DOM.

const stencilWrapper = (storyFn, context) => {
  const host = document.createElement('div');
  stencilClient.renderVdom(
    {
      $ancestorComponent$: undefined,
      $flags$: 0,
      $modeName$: undefined,
      $cmpMeta$: {
        $flags$: 0,
        $tagName$: 'div',  
      },
      $hostElement$: host,
    },
    storyFn(context)
  );
  return host.children[0];
}

And then activate it: addDecorator(stencilWrapper);

You will also need to make sure that you are calling the defineCustomElements (which you should, anyway) in the preview code or the preview-manager html file.

As mentioned above, this is still prone to errors. If you have any suggestions, improvements, bugs fixes etc, please comment.