Project scaffolding + Webpack 2 + React + PostCSS — Step by step (1/2)

So, you are tired of reading boilerplates and every time you open them up, it seams so much more complicated than it should be? You came to the right place. This is the first of two posts that tries to explain the basics of compiling all these front end technologies into one complete and working project. These posts don’t try to expose in-depth of the aforementioned technologies. We already have plenty on the internet for that.

Ok, let’s get to it. Here is the plan.

  1. Project architecture
  2. Global, local dependencies and basic folder structure
  3. Webpack 2 basic configuration
  4. React
  5. PostCSS with some basic plugins for globals, grid, etc

We will cover steps 1, 2 and 3 in this post. The steps 4 and 5 are for the next.

Lets get to it.

1. Project architecture

For starters we should answer the following question:

  • What is a component?
A software component is a unit of composition with contractually specified interfaces and explicit context dependencies only. A software component can be deployed independently and is subject to composition by third parties. https://people.eecs.berkeley.edu/~newton/Classes/EE290sp99/lectures/ee290aSp994_1/tsld009.htm

So, a component should only be dependent in terms of the context where it applies.

Component = JS + Styles + Tests + Whatever is component dependent.

├── .gitignore
├── LICENCE
├── README.md
├── docs
├── package.json
├── public
│ ├── assets
│ └── dist
│ └── app.bundle.js
├── src
│ ├── app.js
│ ├── components
│ │ ├── component1
│ │ │ ├── README.md
│ │ │ ├── config.js
│ │ │ ├── logic.js
│ │ │ ├── logic.spec.js
│ │ │ └── style.sass
│ │ ├── component2
│ │ │ ├── logic.js
│ │ │ ├── logic.spec.js
│ │ │ └── style.sass
│ │ └── layout
│ │ ├── logic.js
│ │ ├── logic.spec.js
│ │ └── style.sass
│ └── core
│ ├── helpers
│ ├── navigation
│ │ ├── history.js
│ │ └── router.js
│ └── test
│ └── setup.js
├── targets
│ ├── firebase.js
│ └── heroku.js
├── tools
│ └── config
│ ├── paths.js
│ ├── plugins.js
│ └── webpack.config.js
├── utils
│ └── i18n
│ ├── en.json
│ └── pt.json
└── yarn.lock

The idea behind this folder structure is to isolate each component with their responsibilities and to be capable of identifying or correlate each section of the project just by looking to the directory name.

Notice that everything is a component, even the layout.

2. Global, local dependencies and basic folder structure

We are going to use yarn, well because it’s new and I want to give it a try. Someone told me that it’s faster, has cleaner syntax and in general follows the same road as npm. It also throws some goodies like licence inspection, interactive upgrades and so forth.

So, if you don’t have yarn, lets go get it. For other OS check the link.

brew update
brew install yarn

Lets go and create a directory to hold our project

mkdir webpack-2-starter && cd webpack-2-starter

Start a new project with yarn, this is similar to npm. Just follow along and answer the questions.

yarn init

Until here, nothing new. Lets install webpack 2 and the webpack dev server.

yarn global add webpack@2.1.0-beta.25 webpack-dev-server@2.1.0-beta.10
yarn add --dev webpack@2.1.0-beta.25 webpack-dev-server@2.1.0-beta.10

After this we can create our folder structure with the following commands.

mkdir -p dir1/dir2/dir3
touch dir1/dir2/dir3/file1

The mkdir with -p option is used to create the parents if they don’t exist. Very handy to create our folder structure. The touch command creates a file in the given path.

Don’t forget to exclude node_modules in .gitignore and commit your project.

3. Webpack 2 basic configuration

Like everything in the world of “computers”, we have inputs and outputs. Webpack is not so different. Lets add a basic configuration to webpack.config.js.


3.1 Inputs and outputs

'use strict';
const webpack = require('webpack');
const path = require('path');
const ASSET_PATH = require('./paths').ASSET_PATH;
module.exports = {
context: path.resolve(__dirname, '../../src'),
entry: {
app: "./app.js",
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, '../../public/dist'),
publicPath: ASSET_PATH // useful if we need a CDN
}
};

Context is used to set an absolute path to resolve configuration and loaders.

Entry serves as an entry point to the application.

Output defines the place where we put our bundle, and let us define the path and the filename which gets the name from the entry key we define, in this app.

We also add a file with the paths used on the configurations.

'use strict';
// Whatever comes as an environment variable, otherwise use root
const ASSET_PATH = process.env.ASSET_PATH || '/';
module.exports = {
ASSET_PATH: ASSET_PATH
}

Sometimes we want to customize the webpack build process. For that we can use plugins. DefinePlugin lets us define constants at compile time.

UglifyJsPlugin lets us mangle the code. Later we should separate dev from production configuration.

'use strict';
const webpack = require('webpack');
const paths = require('./paths');
// This makes it possible for us to safely use env vars on our code
const DefinePlugin = new webpack.DefinePlugin({
'process.env.ASSETS': JSON.stringify(paths.ASSETS)
});
const UglifyJsPlugin = new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: true
}
});
module.exports = {
DefinePlugin: DefinePlugin,
UglifyJsPlugin: UglifyJsPlugin
}

We use them on webpack.config.js file.

...
const plugins = require('./plugins');
module.exports = {
...
plugins: [
plugins.DefinePlugin,
plugins.UglifyJsPlugin
]
};

3.2 ES6

Luckily for us we don’t have to deal with an additional loader because Webpack 2 already does this for us, which is kinda nice.

{
"presets": [
["es2015", { "modules": false }]
]
}

{ modules: false } enables Tree Shaking to remove unused exports from your bundle to bring down the file size.

More info on webpack 2.

And thats it. In the next part we will focus on React + PostCSS.

You can see the result in https://github.com/luisantunesdeveloper/webpack-2-starter

Thanks for reading. See you next time.