mui / toolpad

Toolpad: Full stack components and low-code builder for dashboards and internal apps.
https://mui.com/toolpad/
MIT License
1.28k stars 283 forks source link

Better support micro-frontend #1183

Open oliviertassinari opened 2 years ago

oliviertassinari commented 2 years ago

Duplicates

Latest version

Summary 💡

Per the https://micro-frontends.org/ architecture, I would like to be able able to build some parts of my app in a low-code fashion with Toolpad, and others with React/pro-code. However, the current layout is optimized for a full scale application.

Examples 🌈

Here is how it could look without the padding left and right, the margin-top, and the overflow: scroll as a Zendesk plugin.

Before

Frame 2 (1)

After

Frame 1 (1)

Motivation 🔦

Save space

Janpot commented 2 years ago

We wrap the whole page in a MUI Container. Is this a matter of adding an option on the page to disable that Container?

edit: linking feature request: https://github.com/mui/mui-toolpad/issues/492

oliviertassinari commented 2 years ago

@Janpot yeah, it could be enough.

Another option would be to expose a React component that I can render inside the Zendesk extension (rather than an iframe), on which I can then apply CSS overrides 😁 or not come without these default styles, or have a prop to disable the style.

Janpot commented 1 year ago

Another option would be to expose a React component that I can render inside the Zendesk extension (rather than an iframe), on which I can then apply CSS overrides 😁 or not come without these default styles, or have a prop to disable the style.

@oliviertassinari How do you envision importing such component inside of the zendesk application? The zendesk scaffold app seems to be built upon webpack. Potential solutions I can currently think of (but am not particularly excited about) are:

  1. Use something like import-http in the zendesk app to import toolpad urls that resolve to react components for pages. This solution implies that all files hosted by toolpad will be cached locally to the project and the zendesk app won't automatically reflect updates when the app is updated on the toopad side. Instead the zendesk app will need to be rebuilt and published again.
  2. Use webpack module federation on toolpad side to host dynamic containers containing components for toolpad pages, and declare toolpad in zendesk as a remote. This solution is highly webpack specific and requires users to set up the module federation plugin. The zendesk app architecture may also not allow for usage of this plugin. I'm not up to date enough to know about the pitfalls with this approach, but I do know that the next.js integration didn't go easily and took a long time to get working. I'm also not quite sure about the status and adoption of this feature. I don't get the sense that it has much traction, but I may be in the wrong circles.
oliviertassinari commented 1 year ago
  1. Use something like import-http in the zendesk

Yeah, we could create our own plugin for webpack, then for vite, etc. Hopefully, we could design it in a way that shares a lot of the code. In https://www.notion.so/Low-code-directions-available-10aeeb10596d41ac97593c6928c361aa#2dfc43f29ef3401faec6929c78b351ee, I was initially thinking of something like import OrderInspector from './toolpad/OrderInspector/OrderInspector.page.yml?@mui/toolpad-loader'. My thought was that we could build the component on demand, once webpack asks for it, we could defer the request to the locally running instance of Toolpad. I assume there are also APIs to notify webpack that the module needs to be updated when there is a change.

How do you envision importing such component inside of the zendesk application?

@Janpot More options:

  1. Expose a runtime React component and sync the state of the UI with API calls. This is the approach used Builder.io, e.g. https://www.builder.io/c/docs/integrate-section-building, or Plasmic https://docs.plasmic.app/learn/nextjs-quickstart/, MakeShift https://www.makeswift.com/docs/guides/manual-setup. The problem with this approach is that unless we can lazily load components, this will be heavy on the page.
import { BuilderComponent, builder } from '@builder.io/react';

builder.init(YOUR_API_KEY); // Replace with your Public API Key.

export async function getStaticProps({ params }) {
  const urlPath = '/' + (params?.page?.join('/') || '');
  const announce = await builder.get('announcement-bar', { userAttributes: { urlPath } }).toPromise();

  return {
    props: {
      announce: announce || null,
    },
  };
}

export default function Page({ announce }) {
  return (
    <div>
      <YourHeader />
      <BuilderComponent model="announcement-bar" content={announce} />
      <TheRestOfYourPage />
    </div>
  );
}
  1. Have a _generated folder developers can import from. This is the approach used by https://www.convex.dev/. The code is committed in git (yarn.lock style). Changes done outside of Toolpad are erased.
import { useQuery } from "../convex/_generated/react";
import { useMutation } from "../convex/_generated/react";

function MyApp() {
  // data will be `undefined` while the query is first loading
  const data = useQuery("listMessages");
  const sendMessage = useMutation("sendMessage");
  const sendHello = () => sendMessage("Hello!", "me");
  return (
    <div>
      <MyListView data={data} />
      <button onClick={sendHello}>click me!</button>
    </div>
  );
}
Janpot commented 1 year ago

I propose we improve the story for iframe embedding as part of MVP.

Then leave exporting components for later. Personally, 4. makes most sense to me.

** Let's call this the Toolpad chrome, or Toolpad shell? It also includes header bar for instance. PWA manifest has display attribute which controls the level of browser chrome that is displayed.

oliviertassinari commented 1 year ago

Toolpad shell

This makes more sense to me, as a reference to app shell: https://developer.chrome.com/blog/app-shell/.

Query parameter

This would be more universal, it's not always possible or easy to add an iframe attribute. I'm not even sure that iframe attributes would fly, since iframe will likely be always for when the origin is different https://stackoverflow.com/questions/13570733/html5-data-attributes-in-iframe-element

Personally, 4. makes the most sense to me.

My only concern is that option 4. can't support lazy generation, which will be a scaling nightmare. If it's much simpler to pull off than option 1 then it sounds like a great tradeoff as an MVP to test if the feature itself makes sense.

Janpot commented 1 year ago

👍 "Toolpad shell" it is then.

Right, data attributes won't work then. My only concern with using query parameters to pass toolpad options to the application is that they may collide with user defined parameters. But maybe it's just a matter of namespacing them well. (?toolpad.display=standalone)

My only concern is that option 4. can't support lazy loading

What do you mean by lazy loading? Wouldn't it be possible to wrap whatever you want to lazy load in a component and import it with React.lazy?

oliviertassinari commented 1 year ago

What do you mean by lazy loading?

I have changed the terminology, it was confusing ~loading~ bundling.