prevwong / craft.js

🚀 A React Framework for building extensible drag and drop page editors
https://craft.js.org
MIT License
7.51k stars 724 forks source link

Future of Craft.js - taking it to the next level! 🚀 #507

Open prevwong opened 1 year ago

prevwong commented 1 year ago

Background

Hey folks! Craft.js is about 3 years old now and in that time we've seen plenty of Craft-based page editor implementations in the wild. I'm incredibly grateful for everyone who has been using Craft, and for the feedback I have gotten all this while.

Having personally worked at some companies that used Craft has helped me to recognise the strengths and as well some of the shortcomings of the framework. One of those shortcomings that I felt was in the state management system itself.

While the current EditorState in Craft works pretty well, it falls short for more complex page editor requirements. Currently, it isn't quite possible to build complete UI components with Craft.

Let's take a look at how a typical UI component built in React looks like:

const posts = [...];

const MyComponent = (props) => {
    const [counter, setCounter] = useState(0);
    return (
        <div>
            <h1>Hello World!</h1>
            {
                props.enabled && <p>I'm enabled!</p>
            }
            {
                counter > 0 && (
                    <h2>I'm non-negative!</h2>
                )
            }
            {
                posts.map(post => <h2>{post.name}</h2>)
            }
            <button
                onClick={() => {
                    setCounter(counter + 1);
                }}>
                Increment
            </button>
        </div>
    )
}

A React component is able to do the following:-

The current Craft's EditorState is essentially the equivalent of building a single UI component without states and props; and with the ability of adding/reordering JSX templates and mutating simple prop values of those JSX templates. However, as seen in the React example above, much of what is possible with building UI components with libraries like React, isn't really replicable with a page builder built with Craft.

With that in mind, I spent the past couple of months trying to build a new state management system for Craft; one that could allow end-users of your page editors to build UI components that could be as complex as ones that developers could write in React code. Due to the complexity and the breaking changes that this may possibly introduce, this new state management system is currently being worked on as a separate project, called Reka.

Just to avoid confusion, Reka is not a replacement for Craft. It's simply intended to replace the internal state management system in Craft to enable the following benefits.

Benefits

User-designed Components

End-users of your page editor will now be able to design entire components and re-use them anywhere:

https://user-images.githubusercontent.com/16416929/229722236-ef8e21ab-ca6b-4a6c-8b59-509cf8ac754c.mp4

[
  {
    type: 'RekaComponent',
    name: 'Counter',
    props: [
      {
        type: 'ComponentProp',
        name: 'initalValue',
        init: { type: 'Literal', value: 0 },
      },
    ],
    state: [
      {
        type: 'Val',
        name: 'counter',
        init: { type: 'Identifier', name: 'initialValue' },
      },
    ],
    template: {
      type: 'TagTemplate',
      tag: 'p',
      props: {},
      children: [
        {
          type: 'TagTemplate',
          tag: 'text',
          props: { value: { type: 'Literal', value: 'My counter: ' } },
        },
        {
          type: 'TagTemplate',
          tag: 'text',
          props: { value: { type: 'Identifier', value: 'counter' } },
        },
      ],
    },
  },
  {
    type: 'RekaComponent',
    name: 'App',
    state: [],
    template: {
      type: 'TagTemplate',
      tag: 'div',
      props: {},
      children: [{ type: 'TagTemplate', component: 'Counter', props: {} }],
    },
  },
];

// which is the equivalent of the following React code:
const Counter = ({ initialValue = 0 }) => {
  const [counter, setCounter] = useState(initialValue);
  return <p>My Counter: {counter}</p>;
};

const App = () => {
  return (
    <div>
      <Counter initalValue={10} />
    </div>
  );
};

As seen above, we can now allow end-users to build UI components with states, props, expressions and nearly almost all the fundamentals that you would expect from a UI library like React.

In case you're wondering, yes - you can even render the same element from an array (like you can with .map in React)

Realtime Collaboration

Multiplayer is often a requirement for large page editor implementations and to support this, Reka provides an additional extension that enables multiplayer capabilities via Y.js CRDT which supports common protocols including Web Sockets and WebRTC.

https://user-images.githubusercontent.com/16416929/229785515-68553713-4453-4396-b941-9767aa15817e.mov

Extensible State

Another challenge that I personally faced with Craft when building page editors was the ability of storing additional data related to the editor; previously I would store these as part of the custom property of the ROOT node in Craft. With Reka, this is now achievable in a less awkward fashion with the use of Extensions. For example, let's say you want your end-users to be able to leave a comment on a template element; you can store these comments directly as part of the State:

https://user-images.githubusercontent.com/16416929/229728280-372bfed6-5c19-459c-add0-5c1275a4d8e9.mov

import { createExtension } from '@rekajs/core';
type CommentState = {
  comments: Array<{
    templateId: string; // Id of the Template element associated with the comment
    content: string;
  }>;
};
const CommentExtension = createExtension<CommentState>({
  key: 'comments',
  state: {
    // initial state
    comments: [],
  },
  init: (extension) => {
    // do whatever your extension may have to do here
    // ie: send some data to the backend or listen to some changes made in State
  },
});

// Usage
reka.change(() => {
  reka.getExtension(CommentExtension).state.comments.push({
    templateId: '...',
    content: 'This button tag should be larger!!',
  });
});

Additional Functionalities

With the current Craft state system, you are already able to expose React components via the resolver prop so your end-users could use them. With Reka, you can continue to expose these React components but you're also able to expose other stateful values and JS functions that your end-users can interact with:

https://user-images.githubusercontent.com/16416929/229712736-4cd0d053-07e8-4f8e-b540-1ee45b5ad7a3.mp4

import * as React from 'react';
import confetti from 'canvas-confetti';

const Icon = (props) => {
    return <SomeIconLibrary icon={props.icon} />    
}

const reka = Reka.create({
   externals: {
       components: [
           t.externalComponent({
               name: "MyReactIcon",
               render: (props) => <Icon {...props} />
           })
       ],
       functions: () => ({
           doConfetti: () => {
               confetti();
           }
       }),
       states: {
           scrollTop: 0
       }
   }
});

Disadvantages

A much larger and complex data structure

The current Craft EditorState is a simple implicit tree data structure, whereas Reka is an AST. As such, a Reka AST for an equivalent EditorState is expected to be larger:

// EditorState
{
    id: "ROOT",
    data: {
      type: "div",
      props: {
          text: "Hello"
      }    
    }
}

// Reka AST
{
    id: "some-random-id",
    type: "RekaComponent",
    state: [],
    props: [],
    template: {
        type: "TagTemplate",
        tag: "div",
        props: {
            text: {
                type: "Literal",
                value: "Hello"
            }
        }
    }
}

In particular, you can see how the "props" of each element is now more verbose in the AST, since now it can be any expressions (ie: pointing to some variable, or concatenating values) and not just literals.

What's next?

Reka is a massive departure from Craft's current state system, hence I started this thread to get everyone's thoughts/concerns on it. Additionally, I've written up documentation along with examples for Reka so everyone could try it out for themselves.

Then, I would like to integrate Reka into Craft - which I will admit is easier said than done as this would be somewhat of a rewrite of Craft:-

Putting it all together

https://user-images.githubusercontent.com/16416929/229729268-7e2e3170-d48a-435d-b72b-dffa841e6e53.mov


That's it for now, please feel free to let me know your thoughts below!

If you or your organisation is using Craft.js and would like to support me for my efforts - please consider sponsoring! ❤️

neelansh15 commented 1 year ago

This is epic. Having tinkered with CraftJS, this feels more like a successor to it, having enabled anyone to create what a developer could

hugominas commented 1 year ago

@prevwong you have been busy :) Reka is very promising congratulations on all the work.

We are sponsoring your work and have used CraftJS extensively. For our solution we are managing state programatically and have never used Linked Nodes feature.

I must admit that Reka opens a new layer of customization which would be a great addition for our end users. Keep up the good work, count with us for testing and support!

akhrarovsaid commented 1 year ago

I've been following RekaJS for some time now and I have to say that I'm very impressed. Super useful. I'm very excited for the day when the internal state management in CraftJS can be fully replaced with Reka.

I'm also interested in how we can go about introducing a styling system for Reka as well? Using something like CSSTree or PostCSS to store styles in an AST and be able to modify styles on components on the fly in a responsive manner would also be very useful. Maybe it's something that we can add to the roadmap?

Thank you for your work on CraftJS and RekaJS - I've had an absolute pleasure working with them! 👍 💯

hananint commented 1 year ago

This is AMAZING. Thank you for the great work. Is RekaJs already integrated with craftJs?

prevwong commented 1 year ago

Thank you all for the kind words and for the continued support, I really appreciate it! ❤️

@hananint Not integrated yet, but that's the plan! 🤞

nicosh commented 1 year ago

Reka looks very promising, i also agree with @akhrarovsaid about introducing a styling system for Reka, but i wonder if the more complex reka structure will impact somehow on the bundle size / tree shaking / performance of the page (but i guess that used in combination with next.js and server components we can have good performances). Backwards compatibility imho is essential.

joshpinto6 commented 1 year ago

Very cool!

graham-saunders commented 1 year ago

This is incredible! I love that you're continually iterating to improve craft.js.

jorgegonzalez commented 10 months ago

This looks great. My biggest concern as someone building a product based on Craft.js would be backwards compatibility. I would hate to be stuck on an old Craft version and unable to upgrade due to breaking changes that could be a large undertaking to address.

john093e commented 9 months ago

Hi everyone :)

Wonderful work and amazing results !! I am also very interested, is there any updates on the integration with craft.js ?

mwaaas commented 5 months ago

is there any updates on the integration with craft.js ?

Criztiandev commented 4 months ago

Yo is there any update to craft.js ?

SOG-web commented 4 months ago

This is awesome and wonderful. Though just found out about this project, one thing that made me stick to craftjs is the ability to arrange and re-arrange the editor page the way I want, this feature is one of the things that made craftjs standout to me. I hope the feature will still be maintained as we move the the future of craftjs. Thanks so much for this project, you are awesome

mwaaas commented 4 months ago

@prevwong Has the work started for integrating with reka js, any how I can help

Hahlh commented 4 months ago

Craft.js usage is also growing quite a lot!

Regular downloads 2024 in comparison with 2023 have roughly doubled and it's well on it's way to overtake grapejs if current trajectories continue.

image

prevwong commented 4 months ago

Hey folks, sorry for not posting any updates on here recently. Took me a while to actually get a prototype going, but here's a bit of a sneak peek of Reka and Craft working together!

This demo showcases a lot of the existing features from Craft (ie: dnd, selection and UI abstractions) along with the new features from Reka (ie: realtime collaboration, variables, states, repeating elements and conditional templates)

(Also: check out that unified drag and drop between the layers and component and the canvas! 😎 )

https://github.com/prevwong/craft.js/assets/16416929/b8f44739-b673-4cb6-93af-f759bef521f7

hugominas commented 4 months ago

@prevwong Amazing work! very fluid an intuitive. I hope it inherits the costumizations from craftjs that allowed for so many different implementations by the community.

hananint commented 3 months ago

@prevwong great work. Can't wait to try it. when can we ?

Hahlh commented 3 months ago

Hey folks, sorry for not posting any updates on here recently. Took me a while to actually get a prototype going, but here's a bit of a sneak peek of Reka and Craft working together!

This demo showcases a lot of the existing features from Craft (ie: dnd, selection and UI abstractions) along with the new features from Reka (ie: realtime collaboration, variables, states, repeating elements and conditional templates)

(Also: check out that unified drag and drop between the layers and component and the canvas! 😎 )

craft-reka.mp4

Awesome, thank you for sharing, Prev. That looks very promising, I am excited for Craft.js's future!

prevwong commented 3 months ago

@hugominas Yes, for sure! The goal is to achieve a similar level of ease-of-use that Craft currently offers 🚀


@hananint Still working/experimenting on the APIs at the moment, but hope to have something to try really soon. Will post more updates here!

oyatek commented 3 months ago

Hi,

Congratulations on the amazing job. We're thinking about starting using it in our product that has more than 10,000 active users around the world. I'm sure we will need to extend it in some ways. We will be happy to contribute the code to the project and become sponsors as well.

jorgegonzalez commented 2 months ago

Are LinkedNodes going to be compatible with Reka? @prevwong

prevwong commented 2 months ago

Are LinkedNodes going to be compatible with Reka? @prevwong

At the moment, it's not. The plan is to come up with a better alternative to Linked Nodes because in its current form - it causes quite a bit of confusion and it's not easy to maintain in a codebase.

Reka introduces slots as a way for components to accept child elements (just like props.children in React) but we can extend it to support something similar to Vue.js - named slots; this way a component could specify named slots (ie: editable areas within a component).

alihammad99 commented 2 months ago

Is Reka combined with Craft.js yet? By the way, I'm trying to create responsive frame devices button, I can't get the container's nodeId without selection or click event, and I can't select it in React directly, is there a way to handle responsive frame?

jorgegonzalez commented 2 months ago

Are LinkedNodes going to be compatible with Reka? @prevwong

At the moment, it's not. The plan is to come up with a better alternative to Linked Nodes because in its current form - it causes quite a bit of confusion and it's not easy to maintain in a codebase.

Reka introduces slots as a way for components to accept child elements (just like props.children in React) but we can extend it to support something similar to Vue.js - named slots; this way a component could specify named slots (ie: editable areas within a component).

Sounds good; I am curious what upgrading will look like if we're using LinkedNodes extensively?

fernando-deka commented 2 months ago

This looks awesome!! When can we expect an update on this @prevwong ?

MrVibe commented 2 months ago

Maybe its just me but Reka is really confusing. You've surpassed the levels of a Page builder by defining, variables &. logic into the application. This has made the application purely for developers / designers.

prevwong commented 2 months ago

@jorgegonzalez Based on what I have currently, it would be something like this:

const YourComponent = (props) => {
  const { connect } = useNode();
  return (
     <div ref={connect}>
        {props.slots.editableHeader}
    </div>
  )
}

YourComponent.craft = {
    slots: { 
       editableHeader: { 
          accepts: [Header]
       } 
    }
}

Basically, the definition of "linked nodes" would be made on the component schema level rather than in the template level.


@fernando-deka Don't have a fixed date yet, still juggling a couple of things. Hopefully in the next couple of months 🤞


@MrVibe Why is that confusing though? I don't think page builders are limited to being basic drag and drop HTML editors, so Reka provides functionalities that you would typically need in more advance/complex editors. Plus you don't have to use the new features that Reka provides if the editor you're designing doesn't need them, you could just use it as you use Craft currently.

andreiwow2 commented 1 month ago

Hey guys, I am looking to integrate a page builder into our project for internal use to build websites faster from already made components, while looking at craft.js I found this topic about Reka, I didn't really understand if Reka is meant to be a standalone solution or to be used together with Craft.js?

Also should I start now with Reka or just craft.js considering I havent done any progress yet?