seek-oss / playroom

Design with JSX, powered by your own component library.
MIT License
4.45k stars 183 forks source link

Integration with StencilJS and Web Components? #156

Open chris-dura opened 4 years ago

chris-dura commented 4 years ago

It'd be really great to get this working with native Web Components. Or even, in particular, StencilJS uses TypeScript and is JSX "enabled", at least on some level. Stencil can also output to React components.

Is it possible that this project could be made to support components written in other frameworks, or other JSX-enabled syntaxes?

If it is possible, at a high-level, what would we have to do to make a compatible input for Playroom?

BrunnerLivio commented 4 years ago

We use StencilJS + React integration with Playroom. Unfortunately, I can't share the repository.

Basically what you need to do is build the library with the Stencil compiler and reference your destination folder as described in the documentation. In our case this looks something like this:

module.exports = {
  // I use an absolute path because the library is part of the node_modules folder.
  // In your case most probably its a relative part.
  components: "@design-system/react",
  themes: "@design-system/react/dist-transpiled/themes",
  webpackConfig: () => ({
    module: {
      rules: [
        {
          test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
          use: [
            {
              loader: "file-loader"
            }
          ]
        },
        {
          test: /\.jsx?$/,
          include: __dirname,
          exclude: /node_modules/,
          use: {
            loader: require.resolve("babel-loader"),
            options: {
              presets: [
                [
                  require.resolve("@babel/preset-env"),
                  {
                    targets: {
                      node: "10"
                    }
                  }
                ],
                require.resolve("@babel/preset-react")
              ]
            }
          }
        }
      ]
    }
  })
  ...

Theming is a more tricky one. The way it works with our design system is the "Ionic"-way. Basically you need to set a mode attribute to e.g. the root body element

<body mode="dark">
...

In order to integrate it with the Playroom, I simply created a src/themes/index.ts file which contains the following code. This file needs to be configured as well in the Playroom config (see above). Basically this file will provide all the possible theming names.

export const lightTheme = "light";
export const darkTheme = "dark";

and the FrameComponent.js of Playroom can then load the theme via the given strings.

import React from "react";

import "style-loader!css-loader!@design-system/core/dist/web/web.css";

export default ({ children, theme }) => {
  return <div mode={theme}>{children}</div>;
};

The gist of it is, basically, Playroom does not care what you pass as your constants or how you integrate it. So even if you have a different way of theming your components, it should be feasible.

Preview

I hope this helps? :)

Edit:

Oh I forgot to mention we also have a Rollup configuration for the React package.

import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';

export default {
  input: 'dist-transpiled/index.js',
  output: [
    {
      file: 'dist/index.esm.js',
      format: 'es',
      sourcemap: true
    },
    {
      file: 'dist/index.js',
      format: 'commonjs',
      preferConst: true,
      sourcemap: true
    }
  ],
  external: (id) => !/^(\.|\/)/.test(id),
  plugins: [
    resolve(),
    sourcemaps()
  ]
};

But that is independent to Playroom off course. No need to use specifically Rollup.

Edit 2: Judging by the length of this reply, I think it would be worth a blog post haha

chris-dura commented 4 years ago

@BrunnerLivio -- Thanks! I wasn't expecting an answer so quickly, so it might be a minute before I can try this out, but a few questions to clarify --

// playroom.config.js

module.exports = {
  components: "@design-system/react",
  ...
}
  1. I've never output anything from Stencil other than the native web components, so I assume that @design-system/react is what's generated by using the react-output-target that Stencil has in their documentation? https://stenciljs.com/docs/react#stencil-config-setup

  2. Also I'm not sure exactly how/where that rollup config comes into play? I've never used rollup in general, and haven't needed to for my library output (just being native web components). Is that rollup config needed as part of the stencil configuration to add React as an output target?

BrunnerLivio commented 4 years ago

@chris-dura

I've never output anything from Stencil other than the native web components, so I assume that @design-system/react is what's generated by using the react-output-target that Stencil has in their documentation?

Not quite sure to be honest. What I have used it a similar structure as Ionic handles it. See here. You will find the rollup config. I think react-output-target does the same/similar.

But essentially, it does not really matter. It should also work directly with native Webcomponents as you can see in my picture above. Just make sure you use the correct bundle to load.

chris-dura commented 4 years ago

But essentially, it does not really matter. It should also work directly with native Webcomponents as you can see in my picture above. Just make sure you use the correct bundle to load.

Hey @BrunnerLivio -- do you have any idea which is the "correct" native Web Components bundle generated by Stencil? Targeting the react package output from @stencil/react-output-target seems to work, at least at first glance.

// playroom.config.js
module.exports = {
  components: '../my-library-react/dist/components.js',
  outputPath: './dist/playroom'
};

But, when I try to just target the default Stencil dist bundles, most of the time playroom will build and run, but as soon as I type < I get an error šŸ˜•

// playroom.config.js
module.exports = {
  components: './dist/my-library.js',
  outputPath: './dist/playroom'
};

RenderCode(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.

Screen Shot 2020-06-22 at 12 24 41 PM
BrunnerLivio commented 4 years ago

@chris-dura Hard to debug this from my side. Though in your situation Iā€™d create a react app & ā€œinstallā€ your UI Kit there & see which bundle works with the React app. That bundle which works with the React app should work with Playroom as well.

chris-dura commented 4 years ago

Well, I think I've reached an impasse in getting the Web Components bundle into Playroom directly... I have a sense that there's just some Webpack config magic I'm missing šŸ§™ ... and since I hate configuring webpack (and have to move on to other things), I'm gonna cross my fingers and hope someone else solves it šŸ˜¬

I've put up an example minimal repo, if anyone stumbles on this thread and wants to take a crack at it: https://github.com/chris-dura/stencil-playroom

In that same example repo, I also had qualified success using the React output target from StencilJS. However, the autocomplete in Playroom seems to be way out of whack, because while it does seem to display the custom element Props I've defined (eg, first, middle, last), it also displays a metric ton of "native" HTML attributes/props...

Screen Shot 2020-06-26 at 11 31 44 AM

I'm just loading in the compiled bundle to Playroom's components, so again, I'm guessing I'll get better results maybe using the source .ts files instead, and probably need more Webpack magic, so who knows how far I'll get šŸ˜¬

gmlnchv commented 4 years ago

I can get Playroom to work with Stencil components by adding defineCustomElements(window) to the custom frameComponent.js:

import React from 'react';
import { defineCustomElements } from '../bundles/dist/loader';
import '../bundles/web/build/design-system.css';

defineCustomElements(window);

export default class FrameComponent extends React.Component {
  render() {
    const { children } = this.props;

    return <div>{children}</div>;
  }
}

where bundles/dist/loader is the output of the Stencil's type: 'dist' target.

I don't get props autocompletion but components do get rendered correctly. Haven't tried with a React wrapper yet.

GLoganDR commented 3 years ago

Iā€™ve tried everything to get this to work, and Iā€™m kind of confused about something with the webpackConfig. I think everything is correct.

Styles are coming up in Playroom for me, but itā€™s a blank page. I keep seeing ā€œReferenceError h is not defined.ā€ Even if I remove everything except for the most basic <div>Hello</div>, it still throws that error.

My main question here is, if I set components: './dist/my-library/my-library.esm.jsā€™, it always throws that error. And it doesnā€™t matter what the components is set to, it still throws that error. What would be causing that?

Anyone able to point me in the right direction? I really wish I could see an example configuration where playroom is used with Stencil just using Stencilā€™s dist output.

Screen Shot 2021-03-31 at 5 40 22 AM

I also thought that it could be some kind of messed up dependency causing the problem, or perhaps the configs for storybook are breaking it? So many questions, but I havenā€™t found any solid implementation examples thus far.

We use the monorepo structure from Ionic, but we have the storybook and playroom stuff inside of the core directory instead of on the root of the monorepo. Also, we separate out our CSS so it is usable on its own as a massive CSS file for people who do not wish to use the web components.

michaelwarren1106 commented 3 years ago

Ive started working on a PR to submit that would enable web components support if that is something the Seek folks are interested in.

My teammate at work has hacked it together locally and we've gotten playroom up and running in our design system that is entirely web components.

The approach he used was that the components.js file that is designed to export react components can just export nothing.

export default {}

but, import es6 web components alongside

import '@my-scope/my-web-component';

export default {}

just that alone takes care of the web components being able to be loaded and work in playroom, so the only step that needs to get handled is the hints stuff.

Currently we are completely rewriting (via super hacky build scripts at the moment) the internal componentsToHints() function that provides all the contextual info about component tag names, attributes, etc such that it just provides a static list that we generate ahead of time from our lib.

My PR, if i can get it working, can go either of two possible implementation patterns.

1. Expose the componentHints as a config option If this function, or even the object that it returns is exposed as config, then web component libs can generate their own hints and pass them to playroom via the config. Its a manual build step, but it works just fine

2. Expose a config option to pass a file location that is assumed to be one of the different formats for web component analyzers There are two "major" analyzers that I know of for web components, wca and a newer one from @open-wc. Theoretically, both formats could be supported and playroom could take that file location from config and do the parsing automatically to find attributes and values for hints

Having chatted with a few open-wc members and contributors in the past, the wca analyzer hasnt been updated since last September, so that tool might be dead/dying/un-supported. The newer one from open-wc is available, but doesnt quite have all the features that wca has, has a different format, and doesnt work as well , imo, for non-typescript-defined lit element web components.

Considering that background, I think my PR will just expose the hints object as a config object or function if that is agreeable to Seek folks. It'll be a manual step, but since the format of that hints object is already somewhat standardized, it can be typed and externalized so that devs know they are producing the right format when they use whatever tools they want to during build steps/scripts to generate hints that dont come from react tooling.

Thoughts?

chris-dura commented 3 years ago

Considering that background, I think my PR will just expose the hints object as a config object or function if that is agreeable to Seek folks. It'll be a manual step, but since the format of that hints object is already somewhat standardized, it can be typed and externalized so that devs know they are producing the right format when they use whatever tools they want to during build steps/scripts to generate hints that dont come from react tooling.

I'm not a Seek folk, but as a user, this approach may be more flexible, instead of relying on external analyzers with questionable roadmaps šŸ¤·