nhn / tui.editor

🍞📝 Markdown WYSIWYG Editor. GFM Standard + Chart & UML Extensible.
http://ui.toast.com/tui-editor
MIT License
17.23k stars 1.76k forks source link

3.0 | Support for server side rendering of viewer component in ReactJs, NextJs or any other environment, which supports server side rendering. #1323

Open bdevg opened 3 years ago

bdevg commented 3 years ago

SSR: server side rendering I saw, there are lot of people, who are using this amazing package in React/NextJs, and they tried to render markdown on server. But, they all faces same issue. It would be great to support SSR in next version (3.0).

Current Behavior

Currently when we import and use this viewer in nextjs, we can see the error.

import { Viewer} from '@toast-ui/react-editor';

// use 
<Viewer initialValue="# SSR"/>

then we get this error

ReferenceError: window is not defined

Usually, we don't want to render editor on the server.

Use case

We store markdown content on the server. When page is requested from client side, then we first render markdown content on the server, after that we sent page to the client.

why SSR

expected behavior

There are two components exported from editor, Editor and Viewer. Editor has two part, Editor and Preview. We can avoid SSR in Editor and Preview. Because, preview will becomes much slow.

But, we can bring SSR support for Viewer. This component is used to render markdown content, and can be used to render markdown on server.

related issues

https://github.com/nhn/tui.editor/issues/1222 https://github.com/nhn/tui.grid/issues/935 https://github.com/nhn/tui.editor/issues/1291 https://github.com/nhn/tui.editor/issues/875 https://github.com/nhn/tui.editor/issues/95

https://stackoverflow.com/questions/55151041/window-is-not-defined-in-next-js-react-app/57848309#57848309

excitedbox commented 3 years ago

I think SSR is a HUGE negative for large applications. Client side rendering lets you utilize the users compute resources instead of the server's. It makes a big difference in hosting cost for a large website. One of my old projects had over 70k visitors a day on a shared hosting account and that would not have been possible with SSR. I used GD for images because perl is much faster than JS. JS is already not a very optimized language and moving rendering server side only causes problems. Client side it is ok to waste some resources because there is more than enough for 1 user even on mobile. There is a reason most websites try to cache as much as possible and lately there has been a move to serving static sites with the bare minimum of dynamic content being produced on the server.

PS. JS was developed to run client side in order to remove strain from the server. Everything else could already be done in Perl/CGI, PHP, C/C++, Python, Ruby, Java, ASP, etc. JS was a better option for Applets and Flash which both had security issues. All those other languages are much faster and in many cases easier to develop.

seonim-ryu commented 3 years ago

@bdevg Checking the window object being used in the Viewer, it is being used for the following two reasons.

viewer

But I think the function you want to implement is the purpose of rendering the edited data in the editor. If this is correct, I think you will not use the Viewer, but rather parse and render using the @toast-ui/toastmark we provide. Here's how to use it.

import { Parser, createRenderHTML } from '@toast-ui/toastmark';

const parser = new Parser();
const renderHTML = createRenderHTML({ gfm: true });

const content = '# heading';

const root = parser.parse(content);
const html = renderHTML(root); // Parsing result html string

...

First of all, it seems to be solved if you use the library above for your purpose of use. And in order to apply for SSR, there are many things to consider, such as method and scope of support. I think I can give you an answer after getting the results through research. And even if it is applied, I think it can be applied after the 3.0 major version update.

bdevg commented 3 years ago

@excitedbox

I think SSR is a HUGE negative for large applications. Client side rendering lets you utilize the users compute resources instead of the server's

SSR can also save computing resources of server, if we use static rendering.

Benefit of SSR if we use static rendering

  1. fast page loading
  2. SEO boost
  3. reduced cost of server and database
  4. Improved FCP and LCP
  5. improved TTFB(time to first byte)
  6. faset page load for low end devices as well

Benefit of SSR if we use server side rendering (on demand)

  1. fast page loading
  2. SEO boost
  3. Improved FCP and LCP
  4. faset page load for low end devices as well

For a large companies as well, if they can fulfil their requirement using static rendering, then they can save cost of server.

use case

Consider a situation for a blog website. They can make use of static rendering. If a website has 70k visitors per day, then see below how much cost they can save.

visitors = 70000 per day
// 70k visitors means, we are 70k times communicating with database. 
// We are fetching data from database, then this data is being served to the client. 

request in one year 70000*365= 25550000 = 25550k

If we use SSR then, we only need to communicate 70k in a year insted of 25550k times.

// the number could be much larger than this.

70k visitors a day on a shared hosting account

Probably those people are not using shared hosting account, who uses NodeJs as a server. They use cloud deployment to host their sites.

JS is already not a very optimized language and moving rendering server side only causes problems Every language has their own proc and cons. If we look at NodeJs then it is very optimized for real time communication, or blog website who requires less server processing.

JS was developed to run client side

But now, JS is also growing as server side technology (NodeJs)

lately there has been a move to serving static sites with the bare minimum of dynamic content being produced on the server.

With Introduction of NextJs, we can create hybrid application.

Consider a case where people uses Gatsby, NextJs, ReactJs or any other technology which supports SSR.

bdevg commented 3 years ago

@seonim-ryu

To bind an event when clicking a marker in the task list in the Viewer

Why not this feature can be optional, or this editor use this feature only if window object is available.

but rather parse and render using the @toast-ui/toastmark we provide.

I am trying

bdevg commented 3 years ago

@seonim-ryu This woked, but how can we use plugins. chart, youtubePlugin, etc.

I can't see any documentation on https://www.npmjs.com/package/@toast-ui/toastmark

<Viewer
            initialValue={initialValue}                       
            usageStatistics={true}
            ref={viewerRef}
            language = "en"
            plugins={[[chart,chartOptions],youtubePlugin,iframePlugin, [uml,umlOptions],[codeSyntaxHightlight, { hljs }],colorSyntaxPlugin,tableMergeCell]}
        />
seonim-ryu commented 3 years ago

@bdevg

This woked, but how can we use plugins. chart, youtubePlugin, etc.

Oops... If you're using a plugin, there's a problem. First, you have to customize and implement parsers and renderers, and then import and use them. In the uploaded zip file, there are files customized to use the table-merged-cell plugin on the server. These files are required for each plugin.

import { Parser, createRenderHTML } from '@toast-ui/toastmark';

import { parser as customParser } from './parser'; // customizing parser
import { renderer as customRenderer } from './renderer'; // customizing renderer

const content = `
| @cols=2:merged |
| --- | --- |
| table | table2 |

| @cols=2:merged | @cols=2:merged |
| --- | --- | --- | --- |
| table | table2 | table | table2 |
`;

const parser = new Parser({ customParser });
const renderHTML = createRenderHTML({ gfm: true, convertors: customRenderer });

const root = parser.parse(content);
const html = renderHTML(root);

...

So I think I need to find another solution.

First of all, it seems necessary to handle changing the window object in question so that it can only be accessed in the global environment. The problem part when accessing the window object in the Viewer is seen with this code.

https://github.com/nhn/tui.editor/blob/301f5e6ac36b75dfc6bb40eb35fcb3b474fb8142/apps/editor/src/js/viewer.js#L144

If possible, I think you could edit that part of the editor file and test it once. Would you like to try it?

bdevg commented 3 years ago

@seonim-ryu

have to customize and implement parsers and renderers

How can i create custom parsers and renderers. I don't have any experience in this field.

If possible, I think you could edit that part of the editor file and test it once. Would you like to try it?

I would like to try it, but what should i edit. By looking at the code, it's very difficult to understand. If you can provide code snippet to edit and change, then I'll try.

Checking the window object being used in the Viewer, it is being used for the following two reasons.

This can be solved if we apply function like this.

if(window is available){
     // if window object is available then only do this, otherwise not.
    - To bind an event when clicking a marker in the task list in the Viewer
     - To access window.localStorage for GA
}

Thanks again