All Projects → colinmeinke → universal-js

colinmeinke / universal-js

Licence: other
A universal Javascript starter kit inc. React, Redux, Redux Dev Tools, Universal Redux Router, CSS Modules, hot module reloading, Babel for ES2015+ and ESLint

Programming Languages

javascript
184084 projects - #8 most used programming language
CSS
56736 projects

UniversalJS

A universal Javascript starter kit inc. React, Redux, Redux Dev Tools, Universal Redux Router, CSS Modules, hot module reloading, Babel for ES2015+ and ESLint.


Test status Dependencies status

Demonstrates

Read more about the reasoning behind these decisions.

Although this setup is far from simple or minimal, I have purposefully avoided all but what I see as core for a large project. For example, authentication and data fetching is outside the scope of this starter kit.

Usage

mkdir new-project; cd new-project
git clone [email protected]:colinmeinke/universal-js.git .
npm install
npm run build:dev
npm run start:dev

Structure

Entry points

The /src directory is where all of the code for the app lives.

The build process npm run build:dev or npm run build:pro will compile this code and output it into the /dist directory.

Files that will be directly requested by the browser are then copied into their appropriate directory within /static.

There are two files in the root of the /src directory:

  1. server.js
  2. client.js

These two files are the entry points into the app for their respective environments.

server.js will listen for HTTP requests sent from a client to the server. On receiving a request it will render the response on the server and send it back to the client to display.

client.js will kick in if Javascript is enabled and initialises in the browser. This will hydrate the app on the client, and thereafter handle requests itself, removing the need for additional requests to the server.

Shared code

The /src/common directory is where all of the shared code lives for both server and client. This is the beauty of universal Javascript – shared code.

Within are three directories for Redux actions, reducers and store configuration/actions, /reducers, /store.

Components

The /components directory also resides within the /common directory. This is where all React components live. Both presentational and container (those that connect to the Redux store or have access to its dispatch method).

Each presentational component lives within its own directory within /components. The directory is named after the component in question, e.g. <Button /> would be: /components/Button/....

Connecting components to the Redux store is done by adding a file to the root of the /components directory. This file is also named after the component, e.g. /components/Button.js.

This structure means that all components can be imported into any file as follows:

import Button from './components/Button';

The file importing the component does not need to know if it is a presentational or a container component.

This is because of how imports resolve. It will first look for a file within the /components directory called Button.js. If that does not exist, it will then look for the index.js file within a directory called /Button.

|-- common
    |-- components
        |-- Button
            |-- index.js
            |-- base.css
        |-- Button.js

Terms, concepts and reasoning

Progressive enhancement

I get worried when I see very complex things getting built, things that are reliant of JavaScript. While its true that very few people are going to turn off JavaScript, the JavaScript can still fail. That can be okay if you're building in the right way. It will fall back to what's underneath the JavaScript.

- Jeremy Keith

Server-side rendering

If progressive enhancement is an aim, then we must provide the core experience of our app to users who have disabled Javascript in their browser, or situations where client-side Javascript has failed.

This necessitates that we render the same app on the server as we might on the client. The response from the server should be usable regardless of whether the client-side Javascript kicks in. That's just a bonus!

Server-side rendering isn't just about progressive enhancement and accessibility. It's also a huge win for performance and SEO.

Universal Javascript

If we are rendering the same experience on both the server and the client, then it follows that we should use the same language to build both.

When we use the same language for everything, it means we can abstract common code and share it between environments. Huge wins for maintainability, testing, cognitive load ... the list goes on and on.

NodeJS

NodeJS makes universal Javascript possible by running Javascript on the server.

Express

Express runs in a NodeJS environment and makes it easy to handle HTTP requests to the server and send a response.

React

At its core, this is a React app. React is how we write our components, render the user interface and keep it in sync with the state of the app.

React also makes rendering on the server really easy.

Flux

We need a way to manage the state of our React components. Flux is a pattern that can be used to architect how state flows through our app and how we update state.

However, Flux itself is only an idea. You can't download or install it.

Redux

Redux is a Flux implementation. It is a library that you can download or install.

It's beautifully simple and stores all app state in a single object.

It allows us to treat our user interface as a pure function, which when passed identical state will always render identical output.

It also allows our state to be serializable, and therefore storable or shareable. This makes the possibility of things like undo, redo, debugging and cross-device syncing very achievable.

Universal Redux Router

Universal Redux Router is a router I built to turn URL params into first-class Redux state. For full documentation head over to that repository.

CSS Modules

As much as I love working with inline styles, and using the power of Javascript to output styles, there is a lot to be said for CSS Modules.

CSS Modules allow you to write CSS that is locally scoped. This means that a CSS file can use the same class names that are in another CSS file without worrying about clashing. When the CSS is output, each class gets a unique hash – no need to rely on long-winded naming conventions like BEM.

Most importantly for me is that you can extract the CSS written this way into an external style sheet. This means you still get styling even if Javascript fails on the client.

A downside for me was getting CSS Modules setup in the first place to work how I wanted. For more comprehensive documentation on how to setup CSS modules with Webpack, check out another repository of mine that describes exactly that.

PostCSS

PostCSS is a preprocesser, a bit like Sass or Less. The difference is it works on a plugin system.

With the right plugins installed PostCSS allows you to write future CSS, and compile it to something that works on today's browsers. This is its major strength for me – you can just write CSS.

CSS themes

If we write all user interface as components, all of our CSS can be split by component too.

I like to allow theming, giving each presentational component a base.css file and then overriding those styles with a ${theme-name}-theme.css file.

base.css typically contains base layout rules and a very simple grayscale color palette.

ES2015+

The Javascript features and syntax used within this repository follows the ES2015 spec.

Babel

The reason we don't have to care about browser support for the Javascript features and syntax we write, is because Babel takes care of transpiling our code to ES5. ES5 works on all modern browsers.

Build process

All build tasks are run using the surprisingly powerful scripts property built into npm.

Everything that can be done on the command line, can be done with scripts.

Here's a list of the some of the scripts I have setup:

  • npm run build:dev – build to run in a development environment.
  • npm run build:pro – build to run in a production environment.
  • npm run changelog – create or update a changelog.
  • npm run commit – create a conventional commit message.
  • npm run lint – lint the code.
  • npm run start:dev – start the server in a development environment.
  • npm run start:pro – start the server in a production environment.
  • npm run test – run the tests.

Webpack

Webpack is the powerhouse behind the build scripts npm run build:dev and npm run build:pro.

Webpack takes a Javascript file as an entry point. It runs through that file's dependencies, and its dependents' dependencies, bundling all that code into an output file.

In your Webpack config, you can tell Webpack to run various loaders on specific file types during bundle-time. For example, in this case we run all our Javascript files through the Babel loader to convert our ES2015+ features and syntax to regular ES5.

Loaders can be chained together, which can be very powerful.

For more information, check out the section on entry points above.

Hot module reloading

Part of a great development environment is not having to manually recompile your code and refresh your browser every time you make a change to your Javascript or CSS.

Hot module reloading solves this.

ESLint

Maintaining a consistent coding style is important, especially when there is more than one contributor.

npm run lint will run ESLint on the Javascript using standard style.

Commitizen

Commitizen helps us write conventional commit messages. When commiting code, instead of git commit -m "..." type npm run commit.

This guides us through the process of writing a conventional commit message by prompting us for various data about the changes we have made.

As well as maintaining consistent commit messages across the project, this can have other extremely useful benefits.

There are libraries such as sematic release or conventional changelog that can understand the conventional commit message syntax and run tasks based on that.

Help make this better

Issues and pull requests gratefully received!

I'm also on twitter @colinmeinke.

Thanks 🌟

License

ISC.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].