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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
prevwong opened this issue Apr 4, 2023 · 21 comments
Open

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

prevwong opened this issue Apr 4, 2023 · 21 comments

Comments

@prevwong
Copy link
Owner

prevwong commented Apr 4, 2023

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:-

  • Render an HTML output based on the props given
  • Supports JS expressions
  • Hold stateful values
  • Render an element conditionally based on an expression
  • Iterate through an array and render an element multiple times for each item in the array

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:

reka-demo-user-components2.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.

reka-demo-collab2-faster.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:

reka-demo-comments2.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:

reka-demo-externals.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:-

  • Integrating Craft's events and drag-n-drop system with Reka
    • Much of the logic in existing event system to handle drag-n-drop can be reused
    • We may need to introduce a way to handle drag-n-drop rules (ie: canDrag, canDrop etc) for the user-designed components in Reka.
  • Introducing higher-level methods to interact with Reka
    • We already have these now in Craft, in the form of actions and query. Perhaps, adding these to work with Reka would make it less necessary for consumers to interact with the AST directly.
  • Adding undo/redo
  • Linked Nodes(?)
    • Currently this is not supported within Reka and we need to evaluate if we should continue to support it (or if there's a better way to implement it); considering that it is often difficult to maintain and causes confusion with developers.
  • Backwards compatibility(?)
    • Ideally, it would be great to have all page editors built with the current Craft EditorState to just work with new Craft with the integrated Reka AST.
    • This may involve providing some drop-in legacy adapter that enables the existing EditorState to be transformed into the AST.

Putting it all together

reka-demo01-output1.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! 鉂わ笍

@prevwong prevwong pinned this issue Apr 4, 2023
This was referenced Apr 4, 2023
@neelansh15
Copy link

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
Copy link

hugominas commented Apr 4, 2023

@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
Copy link

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
Copy link

hananint commented Apr 4, 2023

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

@prevwong
Copy link
Owner Author

prevwong commented Apr 6, 2023

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
Copy link

nicosh commented Apr 6, 2023

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
Copy link

Very cool!

@graham-saunders
Copy link

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

@jorgegonzalez
Copy link

jorgegonzalez commented Oct 18, 2023

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
Copy link

Hi everyone :)

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

@mwaaas
Copy link

mwaaas commented Mar 24, 2024

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

@Criztiandev
Copy link

Yo is there any update to craft.js ?

@SOG-web
Copy link

SOG-web commented Apr 21, 2024

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
Copy link

mwaaas commented May 6, 2024

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

@Hahlh
Copy link
Contributor

Hahlh commented May 6, 2024

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
Copy link
Owner Author

prevwong commented May 7, 2024

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

@hugominas
Copy link

@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
Copy link

hananint commented May 7, 2024

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

@Hahlh
Copy link
Contributor

Hahlh commented May 7, 2024

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
Copy link
Owner Author

prevwong commented May 8, 2024

@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
Copy link

oyatek commented May 23, 2024

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.

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

No branches or pull requests