🏠 Home • 🔥 Introduction • 🚀 Getting started • 📍 Checkpoint
First of all... what is a web server project? For us, a web app is a project that will handle with the routing, get route requests, server-side rendering and more...
Now we are going to install some dev dependencies....
npm install --save-dev --save-exact --legacy-peer-deps @s-ui/bundler @s-ui/lint @s-ui/mono @s-ui/ssr
@s-ui/bundler
@s-ui/lint
@s-ui/mono
@s-ui/ssr
In the case of our dependencies we always use the major version and always the exact version.
-
@s-ui/bundler - The responsible for bundling with webpack, ...
-
@s-ui/ssr - The responsible of set our server-side rendering server creating our server folder, passing and getting context and more...
-
@s-ui/lint - The responsible of handle with our linting.
-
@s-ui/mono - The one that will handle our commits and release flow.
But also we need production dependencies (add them all because you will use them later), you could check the most common dependencies in a web app.
// package.json
...
"dependencies": {
"@s-ui/domain": "1",
"@s-ui/hoc": "1",
"@s-ui/i18n": "1",
"@s-ui/react-context": "1",
"@s-ui/react-head": "1",
"@s-ui/react-initial-props": "2",
"@s-ui/react-router": "1",
"prop-types": "15.6",
"react": "17",
"react-dom": "17"
},
"devDependencies": {
"@s-ui/bundler": "9",
"@s-ui/lint": "4",
"@s-ui/mono": "2",
"@s-ui/ssr": "8"
},
...
Now that we have the packages installed let's add some scripts to our package.json.
"scripts": {
"co": "sui-mono commit",
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "sui-bundler dev",
"ssr": "sui-bundler build -C && sui-ssr build -C && node server/index.js --inspect `pbpaste`"
}
And that's it! We have our project ready to develop!
First of all, we are going to set the folder structure of our project.
src/
└── pages/
└── .gitkeep
└── app.js
└── contextFactory.js
└── index.html
└── index.scss
└── routes.js
Creating our INDEX.HTML
Inside our index.html file we are going to put the next code:
<html>
<head></head>
<body>
<div id="app"><!-- APP --></div>
</body>
</html>
This code will be used by our SSR logic to inject our rendered website inside the app div.
<!-- APP -->
is not trivial. It MUST HAVE THIS COMMENT for server-side replacement purposes. More info on the code of the tool
Creating our INDEX.SCSS
file
Leave it commented for now, we will cover the styles part in the next section.
// @import "~frontend-mv--uilib-theme/lib/index";
// Rest of your styles for the pages below
Creating our CONTEXTFACTORY.JS
file
First of all, what is a context factory and why do we use it?
A context factory is just a file that will return an ASYNC function with ALL the elements that we expect to be injected as a context in our react components. For example, imagine that we want to have i18n and domain in all our components as they will use or not at any moment.
So you would put them in the contextFactory file. We return async functions by convention EVEN IF THEY DON'T IMPLEMENT ANY ASYNC OPERATION.
So... our context file could look like this (Don't forget to link the package to make it work):
import domain from "<marketplace-domain-package>";
export default () => Promise.resolve({ domain });
Don't worry if you don't know how to get your domain. Ask your assigned mentor at this point if you have doubts.
We will cover the creation of the domain package later, in the meantime to make the web app work create the file as this
//import domain from "<marketplace-domain-package>";
export default () =>
Promise.resolve({
/* domain */
});
Later we will revisit this file to import the proper package!
Creating our ROUTES.JS
file
At this moment and as an example we are going to put only the index route because how react-router works, is not the big thing here.
import { loadPage } from "@s-ui/react-initial-props";
import { IndexRoute, Route } from "@s-ui/react-router";
import contextFactory from "./contextFactory.js";
const LoadHomePage = loadPage(
contextFactory,
() => import(/* webpackChunkName: "HomePage" */ "./pages/Home/index.js"),
);
export default (
<Route>
<Route path="/">
<IndexRoute getComponent={LoadHomePage} />
</Route>
</Route>
);
Okay, a lot of stuff is happening here.
First of all notice that we are importing these two things here:
import { loadPage } from "@s-ui/react-initial-props";
import contextFactory from "./contextFactory.js";
That will be used to inject context to the page.
The second big thing here is that we are doing a webpack dynamic importation:
const LoadHomePage = loadPage(
contextFactory,
() => import(/* webpackChunkName: "HomePage" */ "./pages/Home/index.js"),
);
This is important so we will not download the chunk if we didn't navigate to the page.
The last thing that we are doing is to load the page component through a sui-react-initial-props method called loadPage
.
Load page will work in the following manner:
-
1 - If we are on client
- 1.1 - Check if we have the
__INITIAL_PROPS__
injected by SSR- 1.1.1 - Inject them to our page.
- 1.1 - Check if we have the
-
2 - If we are in server
- 2.1 - Return the component and pass the initial props from the context
Creating our APP.JS
file (our entrypoint)
import ReactDOM from "react-dom";
import { withContext } from "@s-ui/hoc";
import Context from "@s-ui/react-context";
import { createClientContextFactoryParams } from "@s-ui/react-initial-props";
import { match, Router } from "@s-ui/react-router";
import contextFactory from "./contextFactory.js";
import routes from "./routes.js";
import "./index.scss";
export default contextFactory(createClientContextFactoryParams()).then(
(context) => {
match({ routes }, (err, _, renderProps) => {
if (err) console.error(err); // eslint-disable-line no-console
const App = withContext(context)(Router);
ReactDOM.render(
<Context.Provider value={context}>
<App {...renderProps} />
</Context.Provider>,
document.getElementById("app"),
);
});
},
);
Let's explain what's happening here:
contextFactory(createClientContextFactoryParams()).then(context => {
We are calling our contextFactory, and we are passing to them some params that we expect that usually are going to be used by our contextFactory.
createClientContextFactoryParams
will return an object with the next params:
{
cookies: document.cookie,
isClient: true,
pathName: window.location.pathname,
userAgent: window.navigator.userAgent
}
After that, we'll call react-router match
which has the purpose of being a kind of 'middleware' between a user url access and their final page component.
After that, we call withContext
HOC passing the context and the react-router Router component. This will set the childContextTypes
to our desired components getting as a prop our contextObject and iterating through the keys you can see the code that does that here
Then the last thing that we need now to have a little working example is to create the Home page component.
Under pages folder create another folder called Home, then create a file called index.js
in it and paste the following example:
function HomePage() {
return (
<>
<h1>Home page test title</h1>
</>
);
}
HomePage.displayName = "HomePage";
export default HomePage;
And that's it!
Run:
> npm run ssr
This will launch a website in your localhost:3000
.
You can use sui-bundler
in development mode to start your webapp locally and test it. In order to do that, you can use the following command:
npm run dev
This will launch a website in your localhost:3000
. Like the components in sui-studio, you will need to add your dependencies in your package.json
file and link the ones you want to use inside your pages. sui-bundler
also supports linking packages locally with the link-package
option:
npm run dev -- --link-package=/absolute_path/to/npm_package
npm run dev -- --link-package=../relative_path/to/npm_package
As with the components linking, you can use relative or absolute paths. If you want to link more than one package, you should add the link-package
multiple times:
npm run dev -- --link-package=/absolute_path/frontend-mv--uilib-components/components/header/search \
--link-package=/absolute_path/frontend-mv--uilib-components/components/header/title \
--link-package=/absolute_path/frontend-mv--uilib-components/components/another/component \
--link-package=/absolute_path/frontend-mv--uilib-theme/ \
--link-package=/absolute_path/frontend-mv--lib-movies/
...