diff --git a/README.md b/README.md index 6040e96..87b6162 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ The easiest way to move your React application to Server Side Rendering. ## Getting Started Modern JS applications are divided into 2 types: -- CSR - Client-Side rendering. The application will be displayed only after downloading and executing all the necessary JS code. Until then, the user will see a blank page. It degrades the UX and is bad for SEO. -- SSR - Server-Side rendering. The auxiliary server doesn't send a blank page, but a page with data. Thus, the user can immediately start working with the application, and SEO bots will index the page. +- CSR - *Client-Side rendering*. The application will be displayed only after downloading and executing all the necessary JS code. Until then, the user will see a blank page. It degrades the UX and is bad for SEO. +- SSR - *Server-Side rendering*. The auxiliary server doesn't send a blank page, but a page with data. Thus, the user can immediately start working with the application, and SEO bots will index the page. ## SSR @@ -32,7 +32,7 @@ Schematically, the SSR application looks like this: ## Problems -One of the key problems with SSR applications is asynchronous operations. JS is an asynchronous language, all requests to the server, on which our application data depends, are asynchronous. They affect the state of the system - these are side effects. Since content availability is critical for search engines, we must handle this asynchronous behavior. The React Server Renderer is designed as a synchronous operation that steps through our React-DOM step by step and turns it into HTML. +One of the key problem with SSR applications is asynchronous operations. JS is an asynchronous language, all requests to the server, on which our application data depends, are asynchronous. They affect the state of the system - these are side effects. Since content availability is critical for search engines, we must handle this asynchronous behavior. The React Server Renderer is designed as a synchronous operation that steps through our React-DOM step by step and turns it into HTML. The second problem is hydration. A process that allows us to associate the received HTML and the state of the application from the server with what will be built in the user's browser. @@ -40,10 +40,10 @@ The second problem is hydration. A process that allows us to associate the recei ## Motivation React currently has many solutions for building SSR applications. The most popular solution is **Next.JS**. This is a great framework with many possibilities, iSSR cannot replace it. But, **Next.JS** is about completely rewriting your existing application. **Next.JS** is a framework, which means you have to use its approaches. **iSSR** is just a small library that handles side effects and synchronizes state. -- You can very quickly migrate your existing application to SSR using iSSR without major changes. +- You can very quickly migrate your existing application to SSR using **iSSR** without major changes. - You can use any build system. - You can use any state management solution like Redux, Apollo, Mobx or native setState. -- You can use any other SSR libraries (for example @loadable, react-helmet ...). +- You can use any other SSR libraries (for example @loadable, react-helmet, etc). ## Using @@ -96,7 +96,13 @@ It's very simple, when we open the application it will load the todo list data f ```sh # NPM npm install @issr/core --save -npm install @babel/core @babel/preset-react @issr/babel-plugin babel-loader webpack webpack-cli nodemon-webpack-plugin --save-dev +npm install @issr/babel-plugin --save-dev +``` + +Webpack basic configuration for SSR: +```sh +# NPM +npm install @babel/core @babel/preset-react babel-loader webpack webpack-cli nodemon-webpack-plugin --save-dev ``` *For this example we should install node-fetch because native **fetch** is not supporting **node.js**. Also, for the server we will use express, but you can use any module* @@ -216,7 +222,7 @@ import { hydrate } from 'react-dom'; import createSsr from '@issr/core'; import { App } from './App'; -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); hydrate( @@ -228,7 +234,7 @@ hydrate( The code: ```js -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); ``` Associates the state executed on the server with the application on the client. For correct work *useSsrState* on the client diff --git a/examples/1-simple-without-backend/src/index.jsx b/examples/1-simple-without-backend/src/index.jsx index cda1e6b..c6fb514 100644 --- a/examples/1-simple-without-backend/src/index.jsx +++ b/examples/1-simple-without-backend/src/index.jsx @@ -22,7 +22,7 @@ const App = ({ children }) => { }; (async () => { - const [SSR, getState, effectCollection] = createSsr({}, { onlyClient: true }); + const SSR = createSsr({}, { onlyClient: true }); render( @@ -32,8 +32,8 @@ const App = ({ children }) => { , document.getElementById('root') ); - await effectCollection.runEffects(); - const [SSR2] = createSsr(getState(), { onlyClient: true }); + await SSR.effectCollection.runEffects(); + const SSR2 = createSsr(SSR.getState(), { onlyClient: true }); render( diff --git a/examples/10-redux-sagas/src/client.jsx b/examples/10-redux-sagas/src/client.jsx index 5050a37..2982de7 100644 --- a/examples/10-redux-sagas/src/client.jsx +++ b/examples/10-redux-sagas/src/client.jsx @@ -6,7 +6,7 @@ import createSsr from '@issr/core'; import createStore from './store'; import rest from './utils/rest'; -const [SSR] = createSsr(); +const SSR = createSsr(); const { store } = createStore({ rest, diff --git a/examples/10.1-redux-thunk/src/client.jsx b/examples/10.1-redux-thunk/src/client.jsx index b3297b1..0836c5e 100644 --- a/examples/10.1-redux-thunk/src/client.jsx +++ b/examples/10.1-redux-thunk/src/client.jsx @@ -5,7 +5,7 @@ import { App } from './App'; import createSsr from '@issr/core'; import createStore from './store'; -const [SSR] = createSsr(); +const SSR = createSsr(); const { store } = createStore({ initState: window.REDUX_DATA diff --git a/examples/11-apollo-graphql/src/client.jsx b/examples/11-apollo-graphql/src/client.jsx index 8c699f8..2519292 100644 --- a/examples/11-apollo-graphql/src/client.jsx +++ b/examples/11-apollo-graphql/src/client.jsx @@ -7,7 +7,7 @@ import { createHttpLink } from 'apollo-link-http'; import { App, resolvers, typeDefs } from './App'; import createSsr from '@issr/core'; -const [SSR] = createSsr(); +const SSR = createSsr(); const link = createHttpLink({ uri: 'http://localhost:3010' diff --git a/examples/12-dom-manipulations/src/client.jsx b/examples/12-dom-manipulations/src/client.jsx index bf8f93a..93d6e6c 100644 --- a/examples/12-dom-manipulations/src/client.jsx +++ b/examples/12-dom-manipulations/src/client.jsx @@ -3,7 +3,7 @@ import { hydrate } from 'react-dom'; import { App } from './App'; import createSsr from '@issr/core'; -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); hydrate( diff --git a/examples/13-nested-data-redux-graphql/src/client.jsx b/examples/13-nested-data-redux-graphql/src/client.jsx index 500ba32..2ba4474 100644 --- a/examples/13-nested-data-redux-graphql/src/client.jsx +++ b/examples/13-nested-data-redux-graphql/src/client.jsx @@ -11,7 +11,7 @@ import createSsr from '@issr/core'; import createStore from './store'; import rest from './utils/rest'; -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); const link = createHttpLink({ uri: 'http://localhost:3010' diff --git a/examples/14-nested-async-effect/src/client.jsx b/examples/14-nested-async-effect/src/client.jsx index bf8f93a..93d6e6c 100644 --- a/examples/14-nested-async-effect/src/client.jsx +++ b/examples/14-nested-async-effect/src/client.jsx @@ -3,7 +3,7 @@ import { hydrate } from 'react-dom'; import { App } from './App'; import createSsr from '@issr/core'; -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); hydrate( diff --git a/examples/15-nested-async-effect-2/src/client.jsx b/examples/15-nested-async-effect-2/src/client.jsx index bf8f93a..93d6e6c 100644 --- a/examples/15-nested-async-effect-2/src/client.jsx +++ b/examples/15-nested-async-effect-2/src/client.jsx @@ -3,7 +3,7 @@ import { hydrate } from 'react-dom'; import { App } from './App'; import createSsr from '@issr/core'; -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); hydrate( diff --git a/examples/16-mobx/src/client.jsx b/examples/16-mobx/src/client.jsx index 2c8a0d9..208cd56 100644 --- a/examples/16-mobx/src/client.jsx +++ b/examples/16-mobx/src/client.jsx @@ -4,7 +4,7 @@ import { CreateStoreProvider } from './Connect'; import { App } from './App'; import createSsr from '@issr/core'; -const [SSR] = createSsr(); +const SSR = createSsr(); const { StoreProvider } = CreateStoreProvider(window.MOBX_DATA); diff --git a/examples/17-pure-webpack-5/src/client.jsx b/examples/17-pure-webpack-5/src/client.jsx index bf8f93a..93d6e6c 100644 --- a/examples/17-pure-webpack-5/src/client.jsx +++ b/examples/17-pure-webpack-5/src/client.jsx @@ -3,7 +3,7 @@ import { hydrate } from 'react-dom'; import { App } from './App'; import createSsr from '@issr/core'; -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); hydrate( diff --git a/examples/2-simple-express/src/client.jsx b/examples/2-simple-express/src/client.jsx index bf8f93a..93d6e6c 100644 --- a/examples/2-simple-express/src/client.jsx +++ b/examples/2-simple-express/src/client.jsx @@ -3,7 +3,7 @@ import { hydrate } from 'react-dom'; import { App } from './App'; import createSsr from '@issr/core'; -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); hydrate( diff --git a/examples/3-simple-koa/src/client.jsx b/examples/3-simple-koa/src/client.jsx index bf8f93a..93d6e6c 100644 --- a/examples/3-simple-koa/src/client.jsx +++ b/examples/3-simple-koa/src/client.jsx @@ -3,7 +3,7 @@ import { hydrate } from 'react-dom'; import { App } from './App'; import createSsr from '@issr/core'; -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); hydrate( diff --git a/examples/4-koa-react-router/src/client.jsx b/examples/4-koa-react-router/src/client.jsx index bc9958b..1c53a44 100644 --- a/examples/4-koa-react-router/src/client.jsx +++ b/examples/4-koa-react-router/src/client.jsx @@ -5,7 +5,7 @@ import { createBrowserHistory } from 'history'; import { App } from './App'; import createSsr from '@issr/core'; -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); hydrate( diff --git a/examples/5-meta-tags/src/client.jsx b/examples/5-meta-tags/src/client.jsx index bc9958b..1c53a44 100644 --- a/examples/5-meta-tags/src/client.jsx +++ b/examples/5-meta-tags/src/client.jsx @@ -5,7 +5,7 @@ import { createBrowserHistory } from 'history'; import { App } from './App'; import createSsr from '@issr/core'; -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); hydrate( diff --git a/examples/5.1-meta-tags-helmet/src/client.jsx b/examples/5.1-meta-tags-helmet/src/client.jsx index 74cd7f5..ee334e9 100644 --- a/examples/5.1-meta-tags-helmet/src/client.jsx +++ b/examples/5.1-meta-tags-helmet/src/client.jsx @@ -6,7 +6,7 @@ import { createBrowserHistory } from 'history'; import { App } from './App'; import createSsr from '@issr/core'; -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); hydrate( diff --git a/examples/6-css-modules-other-styles/src/client.jsx b/examples/6-css-modules-other-styles/src/client.jsx index bc9958b..1c53a44 100644 --- a/examples/6-css-modules-other-styles/src/client.jsx +++ b/examples/6-css-modules-other-styles/src/client.jsx @@ -5,7 +5,7 @@ import { createBrowserHistory } from 'history'; import { App } from './App'; import createSsr from '@issr/core'; -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); hydrate( diff --git a/examples/7-loadable-components/src/client.jsx b/examples/7-loadable-components/src/client.jsx index 7a33df4..0c33e14 100644 --- a/examples/7-loadable-components/src/client.jsx +++ b/examples/7-loadable-components/src/client.jsx @@ -6,7 +6,7 @@ import { createBrowserHistory } from 'history'; import { App } from './App'; import createSsr from '@issr/core'; -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); loadableReady(() => ( hydrate( diff --git a/examples/8-error-handling/src/client.jsx b/examples/8-error-handling/src/client.jsx index bf8f93a..93d6e6c 100644 --- a/examples/8-error-handling/src/client.jsx +++ b/examples/8-error-handling/src/client.jsx @@ -3,7 +3,7 @@ import { hydrate } from 'react-dom'; import { App } from './App'; import createSsr from '@issr/core'; -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); hydrate( diff --git a/examples/9-live-reload/src/client.jsx b/examples/9-live-reload/src/client.jsx index bf8f93a..93d6e6c 100644 --- a/examples/9-live-reload/src/client.jsx +++ b/examples/9-live-reload/src/client.jsx @@ -3,7 +3,7 @@ import { hydrate } from 'react-dom'; import { App } from './App'; import createSsr from '@issr/core'; -const [SSR] = createSsr(window.SSR_DATA); +const SSR = createSsr(window.SSR_DATA); hydrate( diff --git a/packages/core/src/hooks.test.tsx b/packages/core/src/hooks.test.tsx index 43da781..baf73af 100644 --- a/packages/core/src/hooks.test.tsx +++ b/packages/core/src/hooks.test.tsx @@ -8,7 +8,7 @@ import createSsr from './iSSR'; describe('hooks tests', () => { test('useSsrEffect - Basic load on ready', async () => { - const [SSR, ,effectCollection] = createSsr(); + const SSR = createSsr(); let called = false; const App = (): JSX.Element => { @@ -30,13 +30,13 @@ describe('hooks tests', () => { ).html(); - await effectCollection.runEffects(); + await SSR.effectCollection.runEffects(); expect(called).toBe(true); }); test('useSsrState - Load state by source', async () => { - const [SSR] = createSsr({ + const SSR = createSsr({ 'custom-id': 'bar' }); @@ -61,7 +61,7 @@ describe('hooks tests', () => { }); test('useSsrState - use setState isomorphic', async () => { - const [SSR, getState, effectCollection] = createSsr(); + const SSR = createSsr(); const App = (): JSX.Element => { const [state, setState] = useSsrState('', 'state-0'); @@ -89,8 +89,8 @@ describe('hooks tests', () => { ) .html(); - await effectCollection.runEffects(); - const state = getState(); + await SSR.effectCollection.runEffects(); + const state = SSR.getState(); const key = Object.keys(state)[0]; diff --git a/packages/core/src/iSSR.tsx b/packages/core/src/iSSR.tsx index b6e6d83..35c6125 100644 --- a/packages/core/src/iSSR.tsx +++ b/packages/core/src/iSSR.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useEffect, isValidElement } from 'react'; +import React, { createContext, useEffect, isValidElement, FunctionComponent } from 'react'; import EffectCollection from './EffectCollection'; import { isBackend, clone } from './utils'; @@ -14,11 +14,10 @@ interface OptionsInterface { onlyClient?: boolean; } -type ReturnCreateISSR = [ - ({ children }: { children: JSX.Element }) => JSX.Element, - () => StateInterface, - EffectCollection -]; +interface ReturnCreateISSR extends FunctionComponent { + getState: () => StateInterface; + effectCollection: EffectCollection; +} interface IssrContextInterface { isLoading: () => boolean; @@ -69,25 +68,26 @@ const createSsr = (initState: InitStateInterface = {}, options: OptionsInterface const getState = (): StateInterface => clone(app.state); - return [ - ({ children }): JSX.Element => ( - - {children} - - - ), - getState, - effectCollection - ]; + const iSSR = ({ children }): JSX.Element => ( + + {children} + + + ); + + iSSR.getState = getState; + iSSR.effectCollection = effectCollection; + + return iSSR; }; export default createSsr; diff --git a/packages/core/src/server.tsx b/packages/core/src/server.tsx index 09a2a85..d087208 100644 --- a/packages/core/src/server.tsx +++ b/packages/core/src/server.tsx @@ -15,7 +15,7 @@ export const serverRender = async ( iteration: (count?: number) => JSX.Element, outsideEffects?: Function ): Promise => { - const [SSR, getState, effectCollection] = createSSR({ }); + const SSR = createSSR({ }); const renderNested = async (): Promise => { const App = await iteration(); @@ -25,7 +25,7 @@ export const serverRender = async ( )); - const waited = effectCollection.getWaited(); + const waited = SSR.effectCollection.getWaited(); if (typeof outsideEffects === 'function') { await outsideEffects(); @@ -36,7 +36,7 @@ export const serverRender = async ( } if (waited.length > 0) { - await effectCollection.runEffects(); + await SSR.effectCollection.runEffects(); return await renderNested(); } @@ -48,6 +48,6 @@ export const serverRender = async ( return { html, - state: getState() + state: SSR.getState() }; };