Creating a front-end JavaScript project can be a daunting task due to the sheer volume of choices available while deciding the tech stack. First, you need to decide the JavaScript framework or library for your project. Do you plan to use the latest ES2015 language features in your code? If yes, then you need a transpiler because your browser probably doesn’t support them yet. Then you require a bundling tool to get your code loaded in the browser. You may want to minify the code for faster load time. To automate all these steps, you need a build script. You may want to deploy your project on a local web server during development. Some setup is required for that. Finally, you need to include some testing framework in your project to write unit tests.
In this post I’ll demonstrate all of the above steps by incorporating all the necessary tools in a simple React
project. I would use Babel
to compile the project and Browserify
to create the bundle. I would write a simple Grunt
script to automate these steps. I’ll use Express
, the web application framework provided by Nodejs for local deployment. For unit tests I’ll use the Mocha
framework, backed by the assertion libraries provided by Chai
. Because it is a React project, I’ll use Enzyme
library to test the React components.
While this is not the most up to date way to create a React project, you are likely to encounter many existing projects that follow a similar build process.
The complete source code for the project set up described in the rest of this post is present in this Github repo. This post is divided into four parts: setting up a simple React project, building the project, implementing unit tests and deploying the application on a local web server.
1. Setting up a simple React project
i. Create a new folder for the project. In command prompt, run the following command to create package.json
and provide basic project information:
npm init
ii. Add React dependencies in your package.json
using following command:
npm install react react-dom --save-dev
The --save-dev
option downloads the specified libraries in the node_modules folder in your project root folder and adds their name and version numbers in package.json
as devDependencies
.
iii. Create a new src
folder and add index.html
file in it with the following content:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href='http://fonts.googleapis.com/css?family=Roboto:400,300,700' rel='stylesheet' type='text/css'>
<link href="css/styles.css" rel="stylesheet" type="text/css">
</head>
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
</html>
iv. Create a sub-folder js
in src
and create our sole React component src/js/app.js
as following:
import React from 'react';
class App extends React.Component {
render() {
return (
<div><h2>Helloooo World!!</h2></div>
);
}
}
export default App;
index.html
.
v. Create index.js
in the src
folder that would simply render the app component inside a div tag in index.html
.
import ReactDOM from 'react-dom';
import React from 'react';
import App from './js/app';
ReactDOM.render(<App/>, document.getElementById("app"));
root
|____ node_modules
|____ package.json
|____ src
|____ index.html
|____ index.js
|____ js
|____ app.js
2. Building the project
In this part we would write a Grunt
script to automate the task of compiling the project and creating a bundle. The bundle would be saved in build
folder at the root level. We would use grunt plugins that would provide us some inbuilt tasks, which we can configure as per our requirement. Then we would write a custom grunt task to execute the plugin tasks in correct sequence.
i. Add grunt to you project with the following command:
npm install grunt --save-dev
ii. During our build process, our first step would be to delete the build
folder. We need to add grunt-contrib-clean
plugin to our project that would provide us with the clean
task.
npm install grunt-contrib-clean --save-dev
iii. We have the grunt clean
task now. But we need to tell it which folders to delete. We need to load the grunt-contrib-clean
task in the script using grunt.loadNpmTasks
method and configure this task to delete the build
folder using grunt.initConfig
method. So now let’s create Gruntfile.js
in the root folder.
var grunt = require('grunt');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.initConfig({
clean: ['build']
});
iv. Next step is to copy our index.html
as it is into the build
folder. We do this so that later when we want to locally deploy the project, we can serve the base html file directly from the build
folder along with the bundle. We need to add grunt-contrib-copy
plugin to our project and configure the source and destination locations of the file to be copied.
npm install grunt-contrib-copy --save-dev
The Gruntfile.js
would look like following:
var grunt = require('grunt');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.initConfig({
clean: ['build'],
copy: {
html: {
options: {
flatten: true
},
files: {
'build/index.html': 'src/index.html'
}
}
}
});
v. Now on to the compilation step. We would use Babel
to both compile React code into plain JavaScript, as well as, ES2015 language features into older JS syntax. Babel provides us with presets for these transformations. A Babel preset is a collection of plugins to perform a particular task. We can include the Babel presets relevant to us as following:
npm install babel-preset-react babel-preset-es2015 --save-dev
vi. Installing the Babel presets in our project isn’t enough. We need to enable them by adding .babelrc
file as following in the root folder.
{
"presets": ["react", "es2015"],
}
vii. We are going to use Browserify
as our bundling tool. It would first execute the transformation step and then it would create the bundle.js
. Since we are using Babel in the first step, we need to get it working with Browserify. So we would add Babelify
, a Babel transformer specifically created for Browserify, to our project.
npm install babelify --save-dev
We would also add the grunt-browserify
plugin.
npm install grunt-browserify --save-dev
viii. Now we need to configure the browserify
task in our grunt script. We would specify babelify
with appropriate presets as the transformer. We also need to specify the starting point of our React project. That would be index.js
, where we hook up our React component into index.html
. We also need to give the name and desired location of the bundle file.
var grunt = require('grunt');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-browserify');
grunt.initConfig({
clean: ['build'],
copy: {
html: {
options: {
flatten: true
},
files: {
'build/index.html': 'src/index.html'
}
}
},
browserify: {
dist: {
files: {
'build/bundle.js': ['src/index.js']
},
options: {
transform: [[ 'babelify', { 'presets': [ 'es2015', 'react' ] } ]]
}
}
}
});
Gruntfile.js
would look like this:
var grunt = require('grunt');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-browserify');
grunt.initConfig({
clean: ['build'],
copy: {
html: {
options: {
flatten: true
},
files: {
'build/index.html': 'src/index.html'
}
}
},
browserify: {
dist: {
files: {
'build/bundle.js': ['src/index.js']
},
options: {
transform: [[ 'babelify', { 'presets': [ 'es2015', 'react' ] } ]]
}
}
}
});
grunt.registerTask('build', ['clean', 'copy:html', 'browserify:dist']);
root
|____ .babelrc
|____ build
|____ Gruntfile.js
|____ node_modules
|____ package.json
|____ src
|____ index.html
|____ index.js
|____ js
|____ app.js
3. Adding unit tests
i. We would use Mocha
as our primary test runner. For an expressive assertion library we would use Chai
. We also need to add two other testing libraries – Enzyme
and react-addons-test-utils
. These are React specific. To run tests in a simulated browser environment, we need jsdom
.
npm install --save-dev mocha chai enzyme jsdom react-addons-test-utils
ii. We would create a new test
folder in our root folder. To test our components in a realistic browser environment, we need to create a setup file browser.js
in our test folder as following:
require('babel-register')();
var jsdom = require('jsdom').jsdom;
var exposedProperties = ['window', 'navigator', 'document'];
global.document = jsdom('');
global.window = document.defaultView;
Object.keys(document.defaultView).forEach((property) => {
if (typeof global[property] === 'undefined') {
exposedProperties.push(property);
global[property] = document.defaultView[property];
}
});
global.navigator = {
userAgent: 'node.js'
};
documentRef = document;
test
folder structure mirror the src
folder, we would create test/js
sub folder. Here, we can create an app.spec.js
file that would contain unit tests specific to the App
component. We can write a test case to assert the message being displayed by this component as following:
import React from 'react';
import { expect } from 'chai';
import { mount, shallow } from 'enzyme';
import App from '../../src/js/app';
describe('<App />', () => {
it('should render the hello world message', () => {
const wrapper = shallow(<App/>);
expect(wrapper.find('h2').text()).to.equal('Helloooo World!!');
});
});
scripts
block in package.json
. Now unit tests throughout the project can be run with npm test
command. Alternatively, a task to run the unit tests can also be created in the grunt script.
"scripts": {
"test": "mocha test/browser.js test/**/*.spec.js"
}
4. Deploying the application on a local web server
i. We will use Express
, a web application framework provided by Node.js.
npm install express --save
ii. Then we need to create a server.js
file in the root folder. In this file, we specify the location of the html that is to be served on the browser and the port number on which the server should run.
var express = require('express');
var app = express();
app.use("/", express.static("build"));
app.get('*', function(req, res) {
res.sendFile('index.html', { root: "build" });
});
app.listen("3000", function() {
console.log('Starting server..');
});
npm start
to start the server and open http://localhost:3000 on the browser.
The final project structure will look like this:
root
|____ .babelrc
|____ build
|____ Gruntfile.js
|____ node_modules
|____ package.json
|____ server.js
|____ src
| |____ index.html
| |____ index.js
| |____ js
| |____ app.js
|____ test
|____ browser.js
|____ js
|____ app.spec.js