Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build/react wrapper #85

Merged
merged 22 commits into from
Feb 24, 2022
Merged

Conversation

cjpillsbury
Copy link
Collaborator

@cjpillsbury cjpillsbury commented Oct 27, 2021

First pass implementation of DIY React wrapper "compiler" that creates React component wrappers for each identified custom element to reduce friction for React devs working with media-chrome.

Also updates media-chrome to correctly identify itself as "type": "module" per new node/npm ESM support.

To Build/Use:

  • run yarn build
  • run yarn build:react
  • confirm modules were generated in ./dist/react/*

To test (for the PR before the release):

  • Create a react app using e.g. create-react-app
  • Add media-chrome as a dependency
  • Clone media-chrome if you haven't already, check out this branch (you will have to set up my fork as an additional remote), and npm link media-chrome to the newly-created react app project.
  • Add the react wrapper implementation of components to your react app and confirm they work as expected. Below is an example use case.

Some problems it solves:

  • Automatically translates boolean props
  • Automatically removes undefined props
  • Automatically converts style objects into CSS style strings
  • Automatically renames react-specific native prop names to the appropriate native attribute equivalents
  • More intuitive import usage.
  • component names follow standard React component naming conventions.
  • resolves update so classes get correctly applied with Styled JSX #24 via wrapper implementation

Example Usage demonstrating different "react-expected" behaviors:

import styled from "styled-components";
import {
  MediaController,
  MediaControlBar,
  MediaPlayButton,
  MediaSeekBackwardButton,
  MediaSeekForwardButton,
  MediaMuteButton,
  MediaVolumeRange,
  MediaTimeRange,
  MediaTimeDisplay,
  MediaCaptionsButton,
  MediaPlaybackRateButton,
  MediaPipButton,
  MediaFullscreenButton,
} from "media-chrome/dist/react";

const StyledMediaPlayButton = styled(MediaPlayButton)`
  --media-button-icon-height: 24px;
`;

function App() {
  return (
    <MediaController>
      <video
        slot="media"
        muted
        src="https://stream.mux.com/DS00Spx1CV902MCtPj5WknGlR102V5HFkDe/high.mp4"
        crossOrigin=""
      >
        <track
          label="thumbnails"
          default
          kind="metadata"
          crossOrigin=""
          src="https://image.mux.com/DS00Spx1CV902MCtPj5WknGlR102V5HFkDe/storyboard.vtt"
        ></track>
        <track
          label="English"
          kind="captions"
          srcLang="en"
          crossOrigin=""
          src="./vtt/en-cc.vtt"
        ></track>
      </video>
      <MediaControlBar>
        <StyledMediaPlayButton>
          <svg
            slot="play"
            viewBox="0 0 17 15"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              d="M16.5 8C16.7 7.9 16.9 7.79999 16.7 7.89999C16.5 7.89999 16.5 8 16.3 8C15.9 7.9 15.5 7.7 14.9 7.5C14.9 7.5 15.1 7.60001 15.3 7.60001C15.3 7.50001 15.1 7.49999 14.9 7.39999C14.7 7.39999 14.5 7.30001 14.3 7.20001C14.3 7.20001 14.3 7.20001 14.5 7.20001L14.3 7.10001C14.1 7.10001 14.1 7 14.1 7L12.9 6.70001C12.9 6.70001 12.9 6.70001 12.7 6.60001C13.1 6.70001 13.9 6.99999 13.7 6.89999C13.7 6.79999 11.9 6.39999 11.9 6.29999C11.7 6.29999 11.7 6.19999 11.7 6.29999C11.5 6.19999 11.9 6.30001 11.7 6.20001V6.10001C11.3 6.00001 11.1 5.79999 10.7 5.79999C10.9 5.79999 10.9 5.79999 11.1 5.79999C11.1 5.79999 11.3 5.89999 11.3 5.79999C11.1 5.69999 11.1 5.70001 10.7 5.60001V5.5C10.7 5.4 9.9 5.30001 9.7 5.10001C9.5 5.10001 9.7 5.10001 9.5 5.10001C9.3 4.90001 9.1 4.80001 8.7 4.70001C8.5 4.70001 8.5 4.69999 8.7 4.79999L8.5 4.70001C8.9 4.70001 8.1 4.4 8.5 4.5C8.5 4.5 8.3 4.39999 8.1 4.29999H7.7C7.5 4.19999 7.5 4.20001 7.3 4.10001C7.3 4.10001 7.3 4.10001 7.5 4.10001C7.1 4.00001 7.3 3.89999 7.1 3.79999C6.9 3.69999 6.5 3.60001 6.5 3.60001C6.3 3.50001 6.3 3.49999 6.3 3.39999L6.1 3.29999C6.7 3.59999 7.5 3.80001 8.1 4.10001C7.5 3.70001 6.7 3.3 6.1 3C5.7 2.8 5.3 2.70001 4.9 2.60001C4.3 2.40001 3.5 2.20001 3.1 2.10001C3.1 1.80001 0 0.5 0 0.5C0 0.9 0.2 1.30001 0.2 1.70001C0.2 2.30001 0.4 2.89999 0.4 3.39999C0.4 3.59999 0.4 3.8 0.4 4C0.4 4.2 0.4 4.3 0.4 4.5C0.4 5.2 0.4 5.8 0.2 6.5C0.2 7.3 0 9.30001 0 10.1C0 10.2 0 10.3 0 10.4C0 10.2 0 10.2 0 10C0.2 10.5 0.2 11 0.2 11.3C0.2 11.2 0.4 11.2 0.4 11V11.2C0.6 11.4 0.6 11.8 0.6 12.1C0.6 12 0.4 12.3 0.4 12.5V12.4C0.4 13.1 0.4 13.9 0.4 14.5C0.6 14.4 1 14.3 1.2 14.2C1.4 14.1 1.4 14.1 1.6 14.1L2.8 13.6C2.6 13.7 3.8 13.3 3.2 13.6C3.6 13.4 4.2 13.2 4.4 13C4.6 12.9 5 12.8 5.2 12.6C7.2 11.8 9.3 11 11.1 10.2C11.7 9.90001 12.3 9.59999 13.1 9.29999C13.3 9.19999 13.7 8.99999 13.9 8.89999L15.7 8.20001C16.1 8.30001 16.7 8.29999 16.5 8.29999C16.1 8.19999 16.3 8.2 16.5 8C16.7 8.1 16.7 7.99999 16.7 7.89999L16.9 7.79999C16.9 8.19999 16.7 8.1 16.5 8ZM14.7 7.39999C14.7 7.39999 14.7 7.29999 14.7 7.39999C14.9 7.39999 14.9 7.39999 14.7 7.39999ZM16.5 8.60001C16.5 8.60001 16.7 8.60001 16.7 8.70001C16.7 8.60001 16.7 8.60001 16.5 8.60001C16.7 8.60001 16.3 8.60001 15.9 8.60001H16.1C16.3 8.50001 16.3 8.50001 16.5 8.60001Z"
              fill="white"
            />
          </svg>
          <svg
            slot="pause"
            viewBox="0 0 11 17"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              d="M10.9 15.6006V15.4006V15.2006V14.7006C10.9 14.4006 10.9 14.0006 10.9 13.7006C10.9 12.9006 10.9 12.2006 10.8 11.5006C10.8 10.1006 10.7 8.90059 10.7 8.20059V8.00058C10.7 7.70058 10.7 7.40059 10.7 7.10059C10.7 6.20059 10.6 5.30059 10.5 4.60059C10.4 4.20059 10.4 3.70057 10.3 3.30057C10.3 3.50057 10.2 3.70057 10.2 3.90057C10.2 3.40057 10.2 3.00057 10.1 2.80057C10 2.20057 9.90001 2.40057 9.70001 2.80057C9.70001 2.90057 9.60001 2.90057 9.60001 2.90057C9.60001 2.40057 9.70001 1.80059 9.70001 1.70059C9.60001 1.40059 9.50001 2.30058 9.50001 2.00058C9.50001 2.30058 9.50001 2.50057 9.50001 2.80057C9.50001 2.60057 9.50001 2.30058 9.50001 2.00058C9.50001 2.00058 9.50001 2.40059 9.50001 2.70059C9.50001 3.00059 9.50001 3.20059 9.50001 3.20059C9.50001 3.10059 9.60001 2.30059 9.50001 2.10059C9.50001 2.10059 9.50001 2.50059 9.50001 2.70059C9.50001 2.40059 9.50001 2.10057 9.40001 1.90057C9.40001 1.80057 9.40001 1.80057 9.50001 1.80057L9.60001 1.70059C9.70001 1.70059 9.70001 1.60059 9.70001 1.60059C9.80001 1.60059 9.80001 1.50058 9.70001 1.50058C9.70001 1.50058 9.60001 1.50058 9.50001 1.50058C9.40001 1.50058 9.40001 1.50058 9.30001 1.50058H9.00001V1.60059V1.50058C9.00001 1.50058 9.00001 1.50058 9.10001 1.50058C9.20001 1.50058 9.20001 1.50057 9.30001 1.40057C9.40001 1.40057 9.50001 1.30057 9.70001 1.30057C9.90001 1.20057 10.2 1.10059 10.4 1.10059C10.2 1.20059 9.80001 1.30057 9.60001 1.40057L10.3 1.10059H10.2C10 1.20059 9.80001 1.20057 9.60001 1.30057L10.1 1.10059C10 1.10059 9.80001 1.10059 9.60001 1.20059C9.40001 1.20059 9.30001 1.30057 9.10001 1.30057L8.80001 1.40057C8.70001 1.40057 8.80001 1.50059 8.70001 1.60059C8.70001 1.90059 8.70001 2.20057 8.70001 2.40057C8.70001 3.40057 8.70001 4.40057 8.70001 5.30057C8.70001 6.20057 8.70001 7.10059 8.70001 8.10059C8.70001 8.10059 8.80001 8.00059 8.80001 8.20059C8.90001 8.70059 8.70001 9.10058 8.80001 9.50058H8.90001C8.90001 9.90058 8.90001 10.3006 8.80001 10.7006C8.80001 11.4006 8.90001 12.1006 8.90001 12.9006C8.90001 12.9006 8.90001 12.9006 9.00001 12.9006C9.00001 13.2006 9.00001 13.6006 9.00001 14.0006V14.2006C9.00001 14.3006 9.00001 14.3006 9.10001 14.3006C9.10001 14.5006 9.10001 14.7006 9.10001 14.9006C9.10001 15.0006 9.10001 15.1006 9.10001 15.2006C9.10001 15.5006 9.10001 15.7006 9.10001 16.0006H9.20001C9.20001 16.1006 9.20001 16.2006 9.20001 16.4006L10.7 16.2006C10.8 15.9006 10.8 15.8006 10.9 15.6006Z"
              fill="white"
            />
            <path
              d="M2.2 15.6004V15.4004V15.2004V14.7004C2.2 14.4004 2.2 14.0004 2.2 13.7004C2.2 12.9004 2.2 12.2004 2.1 11.5004C2.1 10.1004 2 8.90041 2 8.20041V8.0004C2 7.7004 2 7.4004 2 7.1004C2 6.2004 1.9 5.3004 1.8 4.6004C1.7 4.2004 1.7 3.70038 1.6 3.30038C1.6 3.50038 1.5 3.70039 1.5 3.90039C1.5 3.40039 1.5 3.00038 1.4 2.80038C1.3 2.20038 1.2 2.40038 1 2.80038C1 2.90038 0.9 2.90039 0.9 2.90039C0.9 2.40039 1 1.80041 1 1.70041C0.9 1.40041 0.8 2.3004 0.8 2.0004C0.8 2.3004 0.8 2.50038 0.8 2.80038C0.8 2.60038 0.8 2.3004 0.8 2.0004C0.8 2.0004 0.8 2.40041 0.8 2.70041C0.8 3.00041 0.8 3.20041 0.8 3.20041C0.8 3.10041 0.9 2.3004 0.8 2.1004C0.8 2.1004 0.8 2.50041 0.8 2.70041C0.8 2.40041 0.8 2.10039 0.7 1.90039C0.7 1.80039 0.7 1.80038 0.8 1.80038L0.6 1.5004C0.7 1.5004 0.7 1.40039 0.7 1.40039C0.8 1.40039 0.8 1.30038 0.7 1.30038C0.7 1.30038 0.6 1.30038 0.5 1.30038C0.4 1.30038 0.4 1.30038 0.3 1.30038H0.2V1.40039V1.30038C0.2 1.30038 0.2 1.30038 0.3 1.30038C0.4 1.30038 0.4 1.30041 0.5 1.20041C0.6 1.20041 0.7 1.1004 0.9 1.1004C1.1 1.0004 1.4 0.900391 1.6 0.900391C1.4 1.00039 1 1.10041 0.8 1.20041L1.5 0.900391H1.4C1.2 1.00039 1 1.0004 0.8 1.1004L1.3 0.900391C1.2 0.900391 1 0.900397 0.8 1.0004C0.6 1.0004 0.5 1.1004 0.3 1.1004L0 1.30038C0 1.30038 0 1.4004 0 1.5004C0 1.8004 0 2.10038 0 2.30038C0 3.30038 0 4.30041 0 5.20041C0 6.10041 0 7.0004 0 8.0004C0 8.0004 0.0999999 7.9004 0.0999999 8.1004C0.2 8.6004 -9.68575e-08 9.00039 0.0999999 9.40039H0.2C0.2 9.80039 0.2 10.2004 0.0999999 10.6004C0.0999999 11.3004 0.2 12.0004 0.2 12.8004C0.2 12.8004 0.2 12.8004 0.3 12.8004C0.3 13.1004 0.3 13.5004 0.3 13.9004V14.1004C0.3 14.2004 0.3 14.2004 0.4 14.2004C0.4 14.4004 0.4 14.6004 0.4 14.8004C0.4 14.9004 0.4 15.0004 0.4 15.1004C0.4 15.4004 0.4 15.6004 0.4 15.9004H0.5C0.5 16.0004 0.5 16.1004 0.5 16.3004L2 16.1004C2.1 15.9004 2.1 15.8004 2.2 15.6004Z"
              fill="white"
            />
          </svg>
        </StyledMediaPlayButton>
        <MediaSeekBackwardButton></MediaSeekBackwardButton>
        <MediaSeekForwardButton></MediaSeekForwardButton>
        <MediaMuteButton></MediaMuteButton>
        <MediaVolumeRange></MediaVolumeRange>
        <MediaTimeRange></MediaTimeRange>
        <MediaTimeDisplay show-duration={true} remaining />
        <MediaCaptionsButton></MediaCaptionsButton>
        <MediaPlaybackRateButton></MediaPlaybackRateButton>
        <MediaPipButton></MediaPipButton>
        <MediaFullscreenButton></MediaFullscreenButton>
      </MediaControlBar>
    </MediaController>
  );
}

export default App;

(Partial) Example of "compiled" wrapper module:

import React from "react";
import "../index.js";
import { toNativeProps } from "./common/utils";

const MediaChromeButton = ({ children, ...props }) => {
  return React.createElement('media-chrome-button', toNativeProps(props), children);
};

export { MediaChromeButton };

const MediaController = ({ children, ...props }) => {
  return React.createElement('media-controller', toNativeProps(props), children);
};

export { MediaController };

const MediaChromeRange = ({ children, ...props }) => {
  return React.createElement('media-chrome-range', toNativeProps(props), children);
};

export { MediaChromeRange };

//  ... etc.

@vercel
Copy link

vercel bot commented Oct 27, 2021

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: https://vercel.com/mux/media-chrome/dXMPndGfpV3Ds9StYF55aqLUkwb3
✅ Preview: https://media-chrome-git-fork-cjpillsbury-build-react-wrapper-mux.vercel.app

@@ -0,0 +1,54 @@
const ReactPropToAttrNameMap = {
className: 'class',
classname: 'class',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

@dylanjha
Copy link
Collaborator

Reviewing the code and playing around with this, here's what I'm finding:

✅ I'm on board with this pattern:

import {
  MediaController,
  MediaControlBar,
// etc...
} from "media-chrome/dist/react";

It would be nice if it wasn't nested under dist/, but we could live with that.

✅ I'm able to follow the generator code and what's going on there in order to make this happen
✅ This is not a "pure" react component, it's a react component that wraps the web component

QA'ing this was a bit difficult, as I couldn't get this working in a simple examples/ file. In order to QA i created a next.js app and imported the ES Modules via a file URL, which is supported in Next 12

It worked, and I got a player 🎉

it-works_2021-12-14_03-48-59

Open questions:

❓ In your example MediaTimeDisplay show-duration={true} remaining -- why is it show-duration={true} and not showDuration I thought that toNativeProps & toNativeAttrName would handle that conversion, will it not?
❓ How can we best add documentation for this?
❓ How can we best add an example for this?
❓ It feels like there's some missing tests here to cover the React codegen
❓ Now that React 18 has experimental support for web components twitter, github, how will that affect things?
❓ Is there any way that we can get TypeScript support through these React builds? I foresee folks using this in a TypeScript project and then being frustrated that there's no types for these React components.

@mmcc pinging you to get some feedback here since Heff is out. TLDR is:

  • This code generates React component wrappers from the source of the underlying web components
  • All React components are built into a separate module in dist/react/index.js
  • There is no dependency on React directly, importing the React components assumes you have a React environment
  • This is a different strategy than what we're taking with @mux-elements, in mux-elements we publish separate distinct packages and instead of wrapping the web components the React versions are actually pure React Components (we can talk about the tradeoffs, but just calling it out here)

@heff
Copy link
Collaborator

heff commented Feb 5, 2022

What's left to do on this one to get it merged? Any new ideas to apply from the research of how others are handling it?

@dylanjha
Copy link
Collaborator

dylanjha commented Feb 8, 2022

@cjpillsbury looking good -- tested out and was able to get a nextjs app in examples/ (excluding nextjs example from the publishing step, via npmignore, but it is handy for dev testing against the react builds): cjpillsbury#5

Following up on my open questions above:

In your example MediaTimeDisplay show-duration={true} remaining -- why is it show-duration={true} and not showDuration I thought that toNativeProps & toNativeAttrName would handle that conversion, will it not?

Discussed synchronously, we're changing the behavior to use camel case convention

How can we best add documentation for this?

A "usage in React" section that links to another .md file, I think is the lowest friction path?

How can we best add an example for this?

Done

It feels like there's some missing tests here to cover the React codegen

Would be good to have some test coverage on this stuff

Now that React 18 has experimental support for web components twitter, facebook/react#11347, how will that affect things?

Not worried about this right now

Is there any way that we can get TypeScript support through these React builds? I foresee folks using this in a TypeScript project and then being frustrated that there's no types for these React components.

Opening a new discussion, we would basically need this to generate proper TS support: #158

Copy link
Collaborator

@dylanjha dylanjha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

@cjpillsbury cjpillsbury merged commit cc14b9a into muxinc:main Feb 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

update so classes get correctly applied with Styled JSX
3 participants