Skip to content

phurytw/universal-react-redux-typescript-starter-kit

 
 

Repository files navigation

Universal React Redux starter kit with TypeScript and Webpack 2

A minimal starter kit with React, Redux, server side rendering with React-Router 4, hot reloading, and Webpack 2. 100% TypeScript.

Quick start

git clone https://github.com/lith-light-g/universal-react-redux-typescript-starter-kit.git <directory_name> cd <directory_name> npm install npm start 

This will start the demo application available at http://localhost:3000. At this point you can delete the src/example directory and you will get a blank page! (and a few errors you will have to fix 😔 in src/index.tsx, src/reducer.ts and src/routes.tsx)

Packages used

And that's about it!

Explanation

Most of the code is just the React/Redux application. The action creators and reducers are located in the src/example/modules and src/modules directories following the convention proposed here. The root reducer is located in src/reducer.ts.

Everything that is server side is located in src/server.tsx which has a single express application that is responsible for serving the webpack bundle, HMR, and server side rendering.

npm scripts and compiled JavaScript files

ts-node allows TypeScript to be executed without compiling it in the file system. It is used to start the dev server, to compile the webpack bundle, and/or the server application using only Webpack. It also allows us to have a Webpack config in TypeScript. The only JavaScript files we get are for the production environment when building the application.

Hot Module Replacement and Hot reloading

In your webpack config you can see that when not in production the following entries are added: react-hot-loader/patch and webpack-hot-middleware/client. Because of webpack.optimize.CommonsChunkPlugin they'll be compiled into a separate JavaScript file that I named hot.js unless I'm mistaken...

In src/server.tsx, when not in production we have webpack-dev-middleware and webpack-hot-middleware used by the only express application. They're reponsible of compiling and sending the webpack bundle.

Then finally in src/index.tsx you will see:

if (module.hot) { module.hot.accept("./routes", () => { const App: any = require("./routes").default; render(App); }); }

This is what will be called whenever your webpack bundle is updated. This will re-render your application with the changes.

CSS Styles

In production we use a separate CSS file that we get thanks to extract-text-webpack-plugin. It allows us to send the CSS from a simple link tag rather than via the bundle avoiding having to wait for the bundle to load.

However, since the HMR updates the bundle it wouldn't reflect your style changes if you used a separate CSS file so we use style-loader in development in order to enable hot reloading with CSS.

Server side rendering with async data fetching

In short this is how server side rendering is done:

  1. Find matched Route components
  2. Fetch data
  3. Render the components as HTML code
  4. Send the HTML code as the HTTP response

It's very similar to what is explained in react-router's documentation.

Routes

In the routes.tsx we have a bunch of routes in an array as plain JS objects. They will be used by react-router-config's matchRoutes and renderRoutes.

Of course, we can still use the Route component in our application they will be rendered server side but we won't be able to retrieve them for server side data fetching.

Initializing the Redux store and async data fetching

In src/server.tsx, the final request handler is where server side rendering is done.

We start by creating a store that will hold our application's data which we will eventually pass to the client. The setIsServerSide call allows us to notify the components that we are in a server side context.

We get the matched routes with react-router-config's matchRoutes.

In each of our components that require data to be fetched we have a fetchData static method. These methods will dispatch the necessary data to the Redux store.

i.e. in src/example/components/Main.tsx we fetch the matched GitHub user by passing the dispatch method of our store and the Route params. Obviously, the fetchData parameters need to be the same across all components.

This method must return a Promise so we can wait for data that needs to be fetched asynchronously with Promise.all. After async data fetching has been done we can finally render the application.

Keep in mind that the componentWillMount method of each component will be called when rendering. But because we can't await promises created in there we can't use it for async actions. Awaiting the fetchData calls also allows us to have an up-to-date store before rendering.

Rendering

We can now render the application as a string with an up-to-date store.

Since our routes (previously obtained with matchRoutes) are not real Route components but react-router-config routes, renderRoutes is needed to get the components. The components are wrapped in a StaticRouter component to pass the request URL and get catch any redirections and then wrapped in a Provider to pass the application's state to our components. The whole thing is passed as a parameter in a renderToString call.

Redirections

After rendering if a single Redirect component has been rendered the context object passed in the StaticRouter will have an url which allows us to know if the user needs to be redirected and where to redirect. Otherwise we can send the rendered application to the client.

Head tag and React-Helmet

We have only rendered the application just like we would with a ReactDOM.render but this doesn't set the <head> tag. In order to set the <head> tag server side we need to use react-helmet.

We start by calling renderStatic before rendering which will return an object. When rendering every Helmet usage will update this object.

After rendering we can use the object that has updated head tags (such as <title>, <link>, etc.) in the final markup.

Final HTML code and sending it

We just have to put everything together and send it back to the client. I choose to create the HTML code with the JSX syntax but then we need to use renderToStaticMarkup with it.

We fill the <head> tag with the help of the Helmet object and the application's HTML in the root element. We add a polyfill.io script so we can use fetch client-side and a script containing our Redux state that will be used when initializing the store client side. serialize-javascript is needed for safety purposes you can read more about it here.

The webpack generated scripts are also added note that we don't add the hot.js in production since it is the bit relevant to HMR and hot reloading and we don't want that in production.

Then finally in /src/index.tsx, we get our Redux state then we initialize our store with it.

tslint

The tslint.json config is taken from piotrwitek's React & Redux in TypeScript - Static Typing Guide.

More stuff

Testing

If you wish to write tests I recommend that you use ts-node with your test framework.

We can run mocha tests with ts-node with this command:

mocha --compilers ts:ts-node/register,tsx:ts-node/register <files> 

We can also use the --fast fast option in ts-node for faster compilation. For this we need to create a register JavaScript file similar to ts-node that you should be located at node_modules/ts-node/register.js except that we will add the fast option:

require("ts-node").register({ fast: true });

You can find an example of an unit test in the mocha branch a test file a located in src/example/components/__tests__/About.test.tsx. I added a test script in package.json with the updated command to run mocha with the new register file (that I named ts-node-register.js):

mocha --compilers ts:./ts-node-register.js,tsx:./ts-node-register.js src/**/__tests__/*.ts* 

In order to run the tests I need the required packages:

npm i -D mocha chai enzyme react-test-renderer @types/mocha @types/chai @types/enzyme 

Then we can simply run the test script:

npm test 

Separate webpack dev server

Separate back-end / Proxying requests

CSS Modules

Server side redirection (i.e authentication required pages)

Visual Studio Code (VSCode) debugging

You can debug in the VSCode editor adding those configurations to your.vscode/launch.json file:

{ "version": "0.2.0", "configurations": [ { "type": "node2", "request": "attach", "name": "Node", "address": "localhost", "port": 9229, "restart": true, "localRoot": "${workspaceRoot}" }, { "name": "Chrome", "type": "chrome", "request": "attach", "port": 9222, "url": "http://localhost:3000", "webRoot": "${workspaceRoot}" } ] }

Start the application with ts-node --inspect <file> and launch the Node VSCode debugger. It will automatically attach the debugger to your application instance.

In order to debug the client application you need to install the vscode-chrome-debug extension, then run Chrome with the --remote-debugging-port=9222 argument and open client application in http://localhost:3000, and then run the Chrome debugger in VSCode.

Babel

There is no any Babel insanity because it is not required if you have set target to es5 and jsx to react in your tsconfig.json. However, if you wish to use Babel (i.e. for plugins) this is what you can do:

Install Babel and friends:

npm i -S babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-2 

Then add it after the awesome-typescript-loader like so:

{ test: /\.tsx?$/, use: ["babel-loader", "awesome-typescript-loader"], exclude: /node_modules/ }

You'll need to create a .babelrc file with this (more explanation here)

{ "presets": [ [ "es2015", { "modules": false } ], "stage-2", "react" ], "plugins": [ "react-hot-loader/babel" ] }

About

A minimal starter kit with React, Redux, server side rendering, hot reloading, and Webpack 2. 100% TypeScript.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published