Slate: Improve Slate's `readOnly` bundle size [ Discussion ]

Created on 25 Jan 2018  路  18Comments  路  Source: ianstormtaylor/slate

The easiest, and I would imagine the most common way to interpret the data that Slate Editor has produced is to set readOnly prop in Slate to true. Doing that means there's no need to force HTML down React's throat and we're using a well-tested data structure that's easy to store, especially in databases like MongoDB.

The problem with this approach, however is the download size for the user. In my case it's almost 80KB (GZipped, almost 300KB actual) of data that she has to receive, decompress, parse and execute. Much of which might not be necessary at all.
screen shot 2018-01-25 at 9 17 27 am

Here are the things that I feel might not be necessary if the <Editor /> is in read-only mode:

  • history.js, change.js and changes - since no changes are being made to the document, there's no history to store and process
  • text.js - if I understand this correctly it's a utility module that lets you export plain text compiled from the Editor's data; which isn't necessary to display content
  • immutable.js - don't know if this could be trimmed as this tool seems to be deep within the core of Slate, but perhaps if we don't use history and changes?
  • react-portal - maybe?

I'd imagine there are a few ways of doing this:

  • use conditional imports within Slate for a few large packages (although I'd imagine this could introduce a whole bunch of browser requests and get ugly, eventually): historyIsNeeded && import("history.js").then(history => { /* ... */ })
  • break Slate into more packages (sounds painful)
  • create a fork with an absolute minimum code required to render, like slate-lite and maintain it separately or as a module within this repo with Learna
  • change how the package is being minified and tree-shaken (perhaps there are some signals we can create to let WebPack know that under read-only we won't need all the code (guessing that #1568 is related here)

Would love to know if anyone here has any thoughts or ideas about this. Thank you!

discussion

Most helpful comment

I think it would be incredibly useful if Slate provided a "Reader" component themselves that renders the same as an Editor without the overhead of the editing functions.

All 18 comments

@dmitrizzle Thanks for creating this issue, I've been meaning to do the same since I also use Slate to render documents and posts (in read-only) mode for non-authors. Upvote for conditional imports and/or Webpack changes.

Something like slate-react-lite-render would be really nice to have, should improve SSR performance as well.

-1 for this.

It's not a good idea to render your article with Slate's Editor, for you need to store Slate's value to database.

The structure of Slate.Value.toJSON() is unstable. If you store it into your database, it will be difficult to upgrade Slate, for newer Slate will not work with old data format.

Of course, you can write an adapter to convert old data format to newer data format, but the adapter will become more and more complicated after upgrading Slate several times.

What I do in my project is:

  1. Convert Slate's value to JsonML when I need to store it to database.
  2. Render JsonML to React.Element with jsonml-to-react-element when I need to render articles.

My solution is fast and lite enough, and it support SSR(with ReactDOMServer.renderToStaticMarkup), too.

I'll definitely check out JsonML, thanks for the tip. Although storing Slate's Value has never been a problem for me and is a common practice amongst the users. In my use case users often edit their content, which would make maintaining and depending on another library an overhead.

There's nothing inherently wrong with storing Slate's JSON directly in your database. These days the library is getting pretty solid, and the JSON structure shouldn't change too drastically in the future I don't think.

Really you're doing the same thing if you decide on an arbitrary custom implementation, it's just more explicitly "your own thing", but with the added cost of an extra conversion layer which can be cumbersome.

@ianstormtaylor 馃憤

@dmitrizzle At GitBook, we are storing Slate's JSON in our database. We use our own deserializer/serializer because it started with a legacy version of Slate.

We added a schema_version and format_version to the JSON. It helps us version the documents. basically if the schema_version equal the latest one, we don't need to normalize the document after deserialization (=> Better perfs).

@SamyPesse that's a cool idea!

@ianstormtaylor Curious about your thoughts on the original proposal(s) by @dmitrizzle?

I'd be open to a way of doing it that doesn't add much complexity to Slate's codebase鈥攏ot sure if there's an easy way though.

It seems like...

  • For most of the use cases, if the read-only mode is toggleable, then you're going to need to have downloaded the non-read-only logic/dependencies anyways. I imagine that's at least 50% of cases.
  • And then another good chunk would probably be that they have read-only documents in some parts of their app, and the non-read-only editors equivalents elsewhere. Such that the optimization is really just for code splitting purposes, and seems not that important.
  • And then finally there's the cases where you had non-toggleable read-only documents rendering without ever needing an editor... but this seems like a small percentage of use cases? And in that case you should be able to fairly easily "roll your own".

I think the logic for rendering a read-only editor isn't much more complicated that a recursive React loop? (Although I'm thinking about this from the perspective of rendering Slate JSON, not rendering Slate Value objects, which is easier.)

I think people chiming in with details of their exact use cases would be beneficial. In the abstract this doesn't seem like something worth prioritizing otherwise.

@ianstormtaylor thanks for sharing your perspective.

I'll kickstart the ask by chiming in with my usecase: I am building a community product in which folks can create posts using Slate. Profile summaries also use Slate. These posts and profiles are going to be presented in read-only mode for all non-authors, which includes folks coming through search, etc. Reducing the size of the initial download for those pages (outside of using caching) would help with the performance.

The toggle to read-only in my mind would fetch a different component which would lead to new dependencies being downloaded/imported.

Given that, I've liked the idea of using conditional imports or specific imports for edit-features.

I have three similar use cases:

  1. A package that provides a customizable & themeable component which contains Slate, bunch of plugins, utilities and components. The same package provides <Reader /> component which takes a few options and returns Slate's <Editor readOnly /> component for users to render their Value in React.
  2. A blog that accepts submissions using the above package and renders articles using the above <Reader /> component. In this case, most users will be only downloading <Reader /> component, which could greatly benefit from some size reduction.
  3. A blog that uses the above package to accept entries but renders the HTML that via Rails app. This step isn't complete but that's the plan.

I would imagine that the fastest useable implementation for Slate is to have <Editor /> as a tool to make Value and <Editor readOnly /> to render it in React. There are definitely many more ways that this could be done, however, this method requires no additional tools and is much more flexible than storing HTML as a string. That's why I thought giving readOnly prop extra powers of deflation could yield a lot of benefits for most users.


In terms of ensuring that the size is actually affected, I propose we add a few if statements that check for readOnly prop and only load a required dependency if false or undefined:

//...
  if(this.props.editor.props.readOnly){
    import("../change.js").then(Change => {
      // code that requires `Change` to function in an interactive Editor situation
    })
  }
  else {
    // read-only does not need `Change` to properly render content
  }
//...

@dmitrizzle what about creating a "React serializer" for Slate, which renders to React components instead of to HTML or plain text. That way you can easily render values to a read-only representation.

I personally think about it like this:

  1. Editing mode.
  2. Read-only editing mode.
  3. Reading mode.

The reason being that "read-only editing mode" is almost exactly like the editing mode, but without editability. You might have input or textarea or select still visible but that become disabled, or any number of other features that still allude to the fact that you're in an editing environment, but that you currently can't edit.

Whereas the "reading mode" is altogether a different thing, because you can completely eliminate things like input, textarea, select, and instead render the data slightly different to be best consumed while reading.


That said, I understand that in certain cases maybe people will have use cases where they want the rendering to be exactly the same, and share all components. In that case, it seems best to use something like slate-lite-renderer, and maybe someone should champion creating a library for that.

That sounds totally reasonable, Ian. I would love to have a package that would strictly render the Slate's Value, though I wouldn't know where to start to make something like this happen.

I just started playing around with Slate but as I understand, you'll save JSON (not Value) to database:

{
  document: {
    nodes: [
      {
        object: 'block',
        type: 'paragraph',
        nodes: [
          {
            object: 'text',
            leaves: [
              {
                text: 'Million brackets for this small thing.'
              }
            ]
          }
        ]
      }
    ]
  }
}

Im not so sure about universal serializer because JSON could include app specific data (or maybe not, Im new) but it should be relatively easy to render yourself if you understand the data and types you have:

render() {
    return (
      <Fragment>
        {json.document.nodes.map((node1, i) => {
          if (node1.type === 'paragraph') {
            return (
              <p key={i}>
                {(node1.nodes && node1.nodes.length > 0) &&
                  node1.nodes.map(node2 => {
                    if (node2.object === 'text' && node2.leaves) {
                      if (node2.leaves.length > 0) {
                        return node2.leaves.map(leaf => leaf.text);
                      }
                    }
                  })
                }
              </p>
            );
          }
          // cases for other types
        })}
      </Fragment>
    );
}

I do hope that this package goes through weight loss, API is sooo damn good compared to Draft.js and it's currently bigger (few Kb gzipped, ~20Kb parsed). Big thanks to authors and contributors.

I've since fully implemented code by @wmertens from his Gist to create a simple reader component.

When using french-press-editor (a layer on top of Slate for zero config RTF component) reading Slate JSON is as easy as <Reader value={EXAMPLE_VALUE} /> and by weight it's less than 10KB, _before_ minification and g-zip.

@dmitrizzle nice, that's the best way to do it! I'm going to close this out.

I think it would be incredibly useful if Slate provided a "Reader" component themselves that renders the same as an Editor without the overhead of the editing functions.

Was this page helpful?
0 / 5 - 0 ratings