This issue is a work in progress design document, core members may edit this description anytime. It tries to outline the design of the feature but is not authoritive. This means implementors are free to find an implementation that allows us to ship the issue.
We've split this implementation effort into three parts as it turned out to have significant complexity.
Having the ability to demonstrate interactive components from a user provided component library inside design system documentation is immensively useful.
Context and scope
User components are components that are "talked about" inside the design system and used wherever the design system is applied. These kind of components are provided by the user and stored next to the DDT.
Documentation components are components that are provided by us by default (Playground is a built-in documentation component).
User components will usually be placed in a playground, where they can be interacted with. Playgrounds can be created by using the Playground documentation component.
<Playground>
<MyTextField />
</Playground>
Goals
Users can embed React user components into documents inside a playground
Users must be able to embed a user component by simply providing i.e. <Avatar> through familiar JSX syntax
camelCased props are supported on user components
Event handlers are automatically provided and must not be manually configured or passed into the user component
Our example component library should be loadable by DSK for demonstration purposes
Must be able to load CSS and JS and other assets required by the component library
We may support non-scalar values in props of user components
Keep runtime nature of DSK
Reuses Playground component
Entirely isolates styles and ... with iFrames!
Playground has fixed size
Playground provides an action log console
Non-Goals
Older browsers support
Support for old bundle formats
Get into other people's build business
Support for web components, vue components
Support multiple component libraries
More configuration
Support automatic event handlers for custom events
Support user components outside playgrounds
Support "real coding" inside playgrounds
User components outside playgrounds
Documentation components inside playgrounds
Add additional namespacing, we don't allow user components outside playgrounds, so they are automatically namespaced by the fact they are inside the playground (only user components can exist in playgrounds)
TBD
Do we inject state handlers?
Overview
Along with the documents that form the design system (the DDT) users may provide a path to a component library, containing components that can be used inside the documents. These are stored outside the DDT in a separate path.
Users will need to provide us with a static folder that (depending on which implementation approach we use) we can use for loading components and serving other assets. The JavaScript bundle may be provided as an ES module bundle.
This is how users will provide the path to a component library on startup:
The contents of path/to/component-libary/build will need to be served as-is by the backend under an URL prefix i.e. /components/.
Assuming the user provided component library contains an Avatar component, the user will now begin to document the component in a markdown document called readme.md inside an 00_Avatar folder. The user wants to provide an interactive demo of the component and includes a playground.
Using the builtin frontend and through the web interface a user views the Avatar node and the readme.md document. The backend will receive an API request for the DDT tree node that contains that document.
GET /tree/Avatar
While building the API response (using the code in internal/ddt/node.go) the backend will process the document (using ìnternal/ddt/node_doc.go) to convert it to a HTML respresentation viaNodeDoc.HTML()`.
After receiving the API response the frontend will begin to render the document's markup. It will use the DocTransfomer to transform the HTML markup into DOM tree that is mountable by React. While doing so it will also ensure that documentation components (including any Playground) provided by DSK itself are mounted.
When documentation components are mounted they usually use their content as the component's children.
Currently the playgound is no exception in that. However we'll change that behavior for the playground. Its contents should not be touched by the transformer and kept as literal text content, similar to the contents of a CodeBlock.
The component will render an iframe maybe along with additonal controls (i.e. action console logs) inside or outside the iframe.
The component will compute a hash over it's contents and use that to construct an URL which will be used as the source for the iframe: sha1('<Playground><Avatar /></Playground>') -> f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
The alogrithm that computes the hash is build around an existing hashing algorithm sha1. Sha1 is already used in other places so it might be a good choice.
Using the hash the playground component will construct the following iframe source URL /tree/Avatar/_playgrounds/f1d2d2f924e986ac86fdf7b36c94bcdf32beec15.html.
As we want to make it as easy as possible for users (i.e. must not provide any configuration) we imply code when providing the contents for a playground.
We might at a later point allow users to opt out of that easy mode and do real programming inside playgrounds. For the MVP we assume all contents are React components with JSX syntax from the component library provided at startup.
These contents of a playground:
<Avatar />
are treated as JSX with additional implied code.
import { ReactDOM } from 'playground-runtime.js'; // We'll provide that library.
import { Avatar } from '/components/main.js'; // Assuming this is the main entry point and ESM compatible.
ReactDOM.render(
<Avatar />
document.getElementById('root')
);
after running it through esbuild will result in:
// We don't bundle the imports with the playground contents.
import { ReactDOM, React } from 'playground-runtime.js';
import { Avatar } from '/components/main.js';
ReactDOM.render(
React.createElement(Avatar, null),
document.getElementById('root')
);
Playground Runtime
The playground runtime bundles/provides libraries that can be used by the playground code. Currently this is only ReactDOM + React. The library code must be made available at a URL by the backend i.e. /_js/playground-runtime.js as an ES module.
The runtime will also provide means that can inspect the component's props and inject common event handlers, that are automatically connected to the playground's action log.
(a) Users provide a well known entry point manifest and ES modules.
(b) One possible approach is that we leverage esbuild to combine our frontend code and their component code (provided as a prebuilt bundle) in memory and on request to i.e. /static/js/main.js. Our frontend code is already baked into the binary (but it must be our source) and can be read from memory from there. We'll have to replace our frontend's (CRA) webpack with ebuild.
(c) Another approach would try to keep the component bundle separate.
(d) We could leverage federated bundles:
Multiple separate builds should form a single application. These separate builds should not have dependencies between each other, so they can be developed and deployed individually. This is often known as Micro-Frontends, but is not limited to that.
How can we know what props a component can/must receive?
This issue is a work in progress design document, core members may edit this description anytime. It tries to outline the design of the feature but is not authoritive. This means implementors are free to find an implementation that allows us to ship the issue.
We've split this implementation effort into three parts as it turned out to have significant complexity.
Having the ability to demonstrate interactive components from a user provided component library inside design system documentation is immensively useful.
Context and scope
User components are components that are "talked about" inside the design system and used wherever the design system is applied. These kind of components are provided by the user and stored next to the DDT.
Documentation components are components that are provided by us by default (
Playground
is a built-in documentation component).User components will usually be placed in a playground, where they can be interacted with. Playgrounds can be created by using the
Playground
documentation component.Goals
<Avatar>
through familiar JSX syntaxNon-Goals
TBD
Overview
Along with the documents that form the design system (the DDT) users may provide a path to a component library, containing components that can be used inside the documents. These are stored outside the DDT in a separate path.
Users will need to provide us with a static folder that (depending on which implementation approach we use) we can use for loading components and serving other assets. The JavaScript bundle may be provided as an ES module bundle.
This is how users will provide the path to a component library on startup:
The contents of
path/to/component-libary/build
will need to be served as-is by the backend under an URL prefix i.e./components/
.Assuming the user provided component library contains an
Avatar
component, the user will now begin to document the component in a markdown document calledreadme.md
inside an00_Avatar
folder. The user wants to provide an interactive demo of the component and includes a playground.Using the builtin frontend and through the web interface a user views the
Avatar
node and thereadme.md
document. The backend will receive an API request for the DDT tree node that contains that document.While building the API response (using the code in
internal/ddt/node.go
) the backend will process the document (using ìnternal/ddt/node_doc.go) to convert it to a HTML respresentation via
NodeDoc.HTML()`.After receiving the API response the frontend will begin to render the document's markup. It will use the
DocTransfomer
to transform the HTML markup into DOM tree that is mountable by React. While doing so it will also ensure that documentation components (including anyPlayground
) provided by DSK itself are mounted.When documentation components are mounted they usually use their content as the component's
children
.Currently the playgound is no exception in that. However we'll change that behavior for the playground. Its contents should not be touched by the transformer and kept as literal text content, similar to the contents of a CodeBlock.
The component will render an iframe maybe along with additonal controls (i.e. action console logs) inside or outside the iframe.
The component will compute a hash over it's contents and use that to construct an URL which will be used as the source for the iframe:
sha1('<Playground><Avatar /></Playground>')
->f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
The alogrithm that computes the hash is build around an existing hashing algorithm
sha1
. Sha1 is already used in other places so it might be a good choice.Using the hash the playground component will construct the following iframe source URL
/tree/Avatar/_playgrounds/f1d2d2f924e986ac86fdf7b36c94bcdf32beec15.html
.When the backend receives the request for the HTML document it will:
Avatar
nodeNodeDoc.findComponentsIn..
The iframe HTML
The iframe's HTMl is a simple document comparable to the ones provided by default by CRA. It consists of:
Implied playground code
As we want to make it as easy as possible for users (i.e. must not provide any configuration) we imply code when providing the contents for a playground.
We might at a later point allow users to opt out of that easy mode and do real programming inside playgrounds. For the MVP we assume all contents are React components with JSX syntax from the component library provided at startup.
These contents of a playground:
are treated as JSX with additional implied code.
after running it through esbuild will result in:
Playground Runtime
The playground runtime bundles/provides libraries that can be used by the playground code. Currently this is only ReactDOM + React. The library code must be made available at a URL by the backend i.e.
/_js/playground-runtime.js
as an ES module.The runtime will also provide means that can inspect the component's props and inject common event handlers, that are automatically connected to the playground's action log.
Ideas Pool
How can we load a user component?
(a) Users provide a well known entry point manifest and ES modules.
(b) One possible approach is that we leverage esbuild to combine our frontend code and their component code (provided as a prebuilt bundle) in memory and on request to i.e.
/static/js/main.js
. Our frontend code is already baked into the binary (but it must be our source) and can be read from memory from there. We'll have to replace our frontend's (CRA) webpack with ebuild.(c) Another approach would try to keep the component bundle separate.
(d) We could leverage federated bundles:
How can we know what props a component can/must receive?
(a) Inspect the component object we've pulled out of the bundle in part I and reflect which handlers we need to pass. See: https://storybook.js.org/docs/react/essentials/actions
Sources