web-component-analyzer icon indicating copy to clipboard operation
web-component-analyzer copied to clipboard

Programatic usage to integrate with storybook

Open hsablonniere opened this issue 5 years ago • 14 comments

Hey @runem,

This project awesome :smile:

I'm using https://storybook.js.org/ to work on my components (and to showcase them). I would love to use your project to extract my JSDoc into some kind of object I could then format to HTML. This means I need direct access without the CLI part.

I tried to use your code without the CLI but I did not manage to go very far. Would you be interested to guide me so we could provide this kind of interaction?

hsablonniere avatar Apr 24 '19 13:04 hsablonniere

What I did in the end (but I'm not satisfied with this) is:

  • Use CLI to generate MD
  • Import MD into storybook

The problems are:

  • I want more control on the formatting (HTML but also filtering private stuffs _private etc...
  • I also want this to be run each time storybook's webpack dev server rebuilds...

hsablonniere avatar Apr 24 '19 13:04 hsablonniere

Hi, thanks for opening this issue :-) I think your use case is very exciting!

First of all, I'm not very experienced with storybook, so I don't know how to extend stories on compile time. Therefore there might be a better solution to the problem than what I'm proposing here.

Analyze with the CLI

A fast an easy approach for me was to extend the storyboard Webpack config with a custom plugin (see storybook documentation). It regenerates the markdown files by running the CLI command on each file change, and only when a file from src changes, to prevent infinite recompilation.

const { AnalyzeCliCommand } = require("web-component-analyzer");

config.plugins.push({
  apply: function (compiler) {
    let relevantChangedFiles = null;

    compiler.hooks.watchRun.tap('AnalyzeComponents', async (comp) => {
      const changedFiles = Object.keys(comp.watchFileSystem.watcher.mtimes);
      relevantChangedFiles = changedFiles.filter(fileName => fileName.includes("/src/"));
    });

    compiler.hooks.done.tap('AnalyzeComponents', async () => {
      if (relevantChangedFiles == null || relevantChangedFiles.length > 0)
        new AnalyzeCliCommand().run({ format: "md", outDir: "stories" }, "src/**/*.js")
    });
  }
});

Markdown files are emitted to the "stories" folder and can be required by stories.

A problem with this solution is that it rebuilds everything, and not only changed files. In addition it doesn't solve customising the output. I have been considering allowing hooking up custom transformers to control the formatting, so this could be a solution to your problem. It would be interesting to know more about how you want to control the formatting/output.

Analyze without CLI

The core analyzer is built by analyzing the output of the typescript compiler. Therefore it requires you to have access to a Typescript program. Here's an example of how to use it without the CLI:

import { analyzeComponents } from "web-component-analyzer";
analyzeComponents(sourceFile, {checker: program.getTypeChecker()})

What I tried: I played around with this approach by extending storybook with the ts-loader Webpack loader (see storybook documentation) and providing a custom transformer. This looked something like the following code. However I stopped going this way because of problems regarding finding class declarations but I might be exploring this approach again. In addition I might be making a custom Webpack plugin that automatically regenerates only changed components.

const { analyzeComponents } = require("web-component-analyzer");

config.module.rules.push({
      test: /\.js?$/,
      use: [
        {
          loader: 'ts-loader',
          options: {
            transpileOnly: true,
            getCustomTransformers: (program) => ({ after: [
              (context) => {
                return (sourceFile) => {
                  if (sourceFile.fileName.startsWith(join(__dirname, "../src"))) {
                    const result = analyzeComponents(sourceFile, {checker: program.getTypeChecker()});
                    // Do something with the result here
                  }

                  return sourceFile;
                }
              }
            ] })
          },
        }
      ]
  });

runem avatar Apr 28 '19 20:04 runem

Thanks very much for your answer. This is promising. I may have time to look at this next week, I'll keep your up to date.

hsablonniere avatar May 03 '19 09:05 hsablonniere

I tried the second approach. I had to create a tsconfig.json file but your code example helped me a lot. I manage to output the result, now I need to figure out how to give it to storybook.

I remember using this with Vue: https://github.com/tuchk4/storybook-readme

It extracts docs from <docs></docs> blocks and gives it to storybook. In the end, I'm trying to do something similar.

hsablonniere avatar May 03 '19 15:05 hsablonniere

Is there a way that would abstract the fact that web-component-analyzer uses TypeScript? Something like analyse('some Custom Element code')...

hsablonniere avatar May 03 '19 15:05 hsablonniere

I guess, you would need file path etc so maybe raw string would not be enough.

hsablonniere avatar May 03 '19 15:05 hsablonniere

Regarding analyzing raw text, maybe you can find inspiration in this file which I use for testing raw code: https://github.com/runem/web-component-analyzer/blob/master/test/helpers/analyze-text.ts . It basically creates program with virtual files which can then be analyzed 👍

runem avatar Jun 26 '19 17:06 runem

Storybook docs mode now supports showing properties based on custom-elements.json and it works very nice with web-component-analyzer 👍

https://github.com/storybookjs/storybook/tree/next/addons/docs/web-components

Maybe someone can make a tutorial on howto hook it into the webpack refresh/rebuild "loop" 🤗

daKmoR avatar Oct 29 '19 01:10 daKmoR

I cannot promise I'll work on this next week, but I definitely want to ;-)

It will be a great opportunity to see how I can also hook into the process to add info I need:

  • Docs for methods
  • List of translation keys
  • List of images assets
  • Size of the file
  • List of deps (and size)

hsablonniere avatar Oct 31 '19 14:10 hsablonniere

Update:

So, yesterday, I tried a very "hacky POC integration" (directly using wca in storybook's preview.js).

It took around 2~3 seconds to generate MD+JSON in the browser for 56 components but I was able to integrate the MD output in the notes addon and the JSON output in the docs addon.

The problem is: it's a bit slow. I don't need to run this on each reload for all components. The answer would be a webpack loader I guess, I need to dive into this soon...

Regarding the list of additional info I wanted to add (see last comment), I still want them the but now "docs for methods" is auto handled in the markdown output ;-)

We integrated the new addon docs page in our storybook and sill have the notes addon with wca's markdown output. We want to stop having this "double doc" but the markdown ouput has methods, event types, CSS custom props types and the docs page props table does not group properties and attributes :-(

I'll try to see how I can improve all of this and keep you up to date ;-)

hsablonniere avatar Apr 28 '20 10:04 hsablonniere

I fell into the same trap having Storybook.js doc and inline JSDoc's. (Also a weird scroll hijacking bug I could no fix in Storybook.js).

I ultimately decided to bin off storybook.js and simply render out the documentation using remark to custom build document pages.

Live example

kylewelsby avatar Apr 28 '20 10:04 kylewelsby

@kylewelsby Nice looking docs page ;-)

hsablonniere avatar Apr 28 '20 10:04 hsablonniere

@hsablonniere Did you manage to get any further with storybook and the wca plugin?

reno1979 avatar Feb 19 '21 07:02 reno1979

@reno1979 nope :-(

hsablonniere avatar Apr 11 '21 21:04 hsablonniere