© Vasan Subramanian 2019
Vasan SubramanianPro MERN Stackhttps://doi.org/10.1007/978-1-4842-4391-6_2

2. Hello World

Vasan Subramanian1 
(1)
Bangalore, Karnataka, India
 

As is customary, we will start with a Hello World application, something that is a bare minimum and uses most of the MERN components. The main purpose of any Hello World is to show the basic characteristics of the technology or stack that we are using, and the tools required to get it up and running.

In this Hello World application, we’ll use React to render a simple page and use Node.js and Express to serve that page from a web server. This will let you learn the fundamentals of these technologies. This will also give you some basic familiarity with nvm, npm, and JSX transformation—some tools that we’ll use a lot as we go along.

Server-Less Hello World

To quickly get off the ground , let’s write a simple piece of code in a single HTML file that uses React to display a simple page on the browser. No installations, downloads, or server! All you need is a modern browser that can run the code that we write.

Let’s start creating this HTML file and call it index.html. You can use your favorite editor and save this file anywhere on your file system. Let’s start with the basic HTML tags such as <html>, <head>, and <body>. Then, let’s include the React library.

No surprise, the React library is available as a JavaScript file that we can include in the HTML file using the <script> tag. It comes in two parts: the first is the React core module, the one that is responsible for dealing with React components, their state manipulation, etc. The second is the ReactDOM module, which deals with converting React components to a DOM that a browser can understand. These two libraries can be found in unpkg, a Content Delivery Network (CDN) that makes all open-source JavaScript libraries available online. Let’s use the development (as opposed to production) version of the libraries from the following URLs:
These two scripts can be included in the <head> section using <script> tags like this:
...
  <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
...
Next, within the body, let’s create a <div> that will eventually hold any React elements that we will create. This can be an empty <div>, but it needs an ID, say, content, to identify and get a handle in the JavaScript code.
...
  <div id="content"></div>
...
To create the React element, the createElement() function of the React module needs to be called. This is quite similar to the JavaScript document.createElement() function , but has an additional feature that allows nesting of elements. The function takes up to three arguments and its prototype is as follows:
React.createElement(type, [props], [...children])

The type can be any HTML tag such as the string 'div', or a React component (which we will start creating in the next chapter). props is an object containing HTML attributes or custom component properties. The last parameter(s) is zero or more children elements, which again are created using the createElement() function itself.

For the Hello World application, let’s create a very simple nested element—a <div> with a title attribute (just to show how attributes work) that contains a heading with the words “Hello World!” Here is the JavaScript code snippet for creating our first React element, which will go inside a <script> tag within the body:
...
    const element = React.createElement('div', {title: 'Outer div'},
      React.createElement('h1', null, 'Hello World!')
    );
...

Note

We are using ES2015+ features of JavaScript in this book, and in this snippet, we used the const keyword. This should work in all modern browsers as is. If you are using an older browser such as Internet Explorer 10, you will need to change const to var. Toward the end of the chapter, we will discuss how to support older browsers, but until then, please use one of the modern browsers to test.

A React element (the result of a React.createElement() call) is a JavaScript object that represents what is shown on screen. Since it can be a nested collection of other elements and can depict everything on the entire screen, it is also called the virtual DOM . Note that this is not yet the real DOM, which is in the browser’s memory and that is why it is called a virtual DOM. It resides in the JavaScript engine’s memory as a deeply nested set of React elements, which are also JavaScript objects. A React element contains not just the details of what DOM elements need to be created, but also some additional information about the tree that helps in optimization.

Each of these React elements needs to be transferred to the real DOM for the user interface to be constructed on screen. To do this, a series of document.createElement() calls needs to be made corresponding to each of the React elements. The ReactDOM does this when the ReactDOM.render() function is called. This function takes in as arguments the element that needs to be rendered and the DOM element that it needs to be placed under.

We already constructed the element that needs to be rendered using React.createElement(). As for the containing element, we have a <div> that we created in the body, which is the target where the new element needs to be placed. We can get the parent's handle by calling document.getElementByID() , as we would have done using regular JavaScript. Let’s do that and render the Hello World React element:
...
    ReactDOM.render(element, document.getElementById('content'));
...
Let's put all this together in index.html. The contents of this file is shown in Listing 2-1.
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>Pro MERN Stack</title>
  <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
</head>
<body>
  <div id="contents"></div>
  <script>
    const element = React.createElement('div', {title: 'Outer div'},
      React.createElement('h1', null, 'Hello World!')
    );
    ReactDOM.render(element, document.getElementById('content'));
  </script>
</body>
</html>
Listing 2-1

index.html: Server-less Hello World

You can test this file by opening it in a browser. It may take a few seconds to load the React libraries, but soon enough, you should see the browser displaying the caption, as seen in Figure 2-1. You should also be able to hover over the text or anywhere to its right side within the boundaries of the outer div, and you should be able to see the tooltip “Outer div” pop up.
../images/426054_2_En_2_Chapter/426054_2_En_2_Fig1_HTML.jpg
Figure 2-1

A Hello World written in React

Exercise: Server-Less Hello World

  1. 1.

    Try to add a class to the h1 element (you will also need to define the class in a <style> section within the <head> section to test whether it works). Hint: Search for “how to specify class in jsx” in stackoverflow.com. Can you explain this?

     
  2. 2.

    Inspect the element variable in the developer console. What do you see? If you were to call this tree something, what would you call it?

     

Answers are available at the end of the chapter.

JSX

The simple element that we created in the previous section was easy to write using React.createElement() calls . But imagine writing a deeply nested hierarchy of elements and components: it can get pretty complex. Also, the actual DOM can’t be easily visualized when using the function calls as it can be visualized if it were plain HTML.

To address this, React has a markup language called JSX, which stands for JavaScript XML. JSX looks very much like HTML, but there are some differences. So, instead of the React.createElement() calls , JSX can be used to construct an element or an element hierarchy and make it look very much like HTML. For the simple Hello World element that we created, in fact, there is no difference between HTML and JSX . So, let’s just write it as HTML and assign it to the element, replacing the React.CreateElement() calls:
...
    const element = (
      <div title="Outer div">
        <h1>Hello World!</h1>
      </div>
    );
...

Note that although it is strikingly close to HTML syntax, it is not HTML. Note also that the markup is not enclosed within quotes, so it is not a string that can be used as an innerHTML either. It is JSX, and it can be freely mixed with JavaScript.

Now, given all the differences and the complexity in comparison to HTML, why do you need to learn JSX? What value does it add? Why not write the JavaScript itself directly? One of the things I talked about in the Introduction chapter is that MERN has just one language throughout; isn’t this contrary to that?

As we explore React further, you will soon find that the differences between HTML and JSX are not earth-shaking and they are very logical. You will not need to remember a lot or need to look up things as long as you understand and internalize the logic. Though writing JavaScript directly to create the virtual DOM elements is indeed an option, I find that it is quite tedious and doesn’t help me visualize the DOM.

Further, since you probably already know the basic HTML syntax, writing JSX will probably work best. It is easy to understand how the screen will look when you read JSX, because it is very similar to HTML . So, for the rest of the book, we use JSX.

But browsers’ JavaScript engines don’t understand JSX. It has to be transformed into regular JavaScript based React.createElement() calls . To do this, a compiler is needed. The compiler that does this (and can do a lot more, in fact) is Babel. We should ideally pre-compile the code and inject it into the browser, but for prototyping purposes, Babel provides a standalone compiler that can be used in the browser. This is available as a JavaScript file, as usual, at unpkg. Let’s include this script within the <head> section of index.html like this:
...
  <script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
...
But the compiler also needs to be told which scripts have to be transformed. It looks for the attribute type="text/babel" in all scripts and transforms and runs any script with this attribute. So let’s add this attribute to the main script for Babel to do its job. Here is the code snippet for doing that:
...
  <script type="text/babel">
...
The complete set of changes to use JSX is shown in Listing 2-2.
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>Pro MERN Stack</title>
  <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
</head>
<body>
  <div id="contents"></div>
  <script type="text/babel">
    const element = React.createElement('div', {title: 'Outer div'},
      React.createElement('h1', null, 'Hello World!')
    );
    const element = (
      <div title="Outer div">
        <h1>Hello World!</h1>
      </div>
    );
    ReactDOM.render(element, document.getElementById('contents'));
  </script>
</body>
</html>
Listing 2-2

index.html: Changes for Using JSX

Note

Although no effort has been spared to ensure that all code listings are accurate, there may be typos or even corrections that did not make it to the book before it went to press. So, always rely on the GitHub repository ( https://github.com/vasansr/pro-mern-stack-2 ) as the tested and up-to-date source for all code listings, especially if something does not work as expected.

When you test this set of changes, you will find no difference in the appearance of the page, but it could be a wee bit slower due to the compilation done by Babel. But don’t worry. We’ll soon switch to compiling JSX at build-time rather than at runtime to get rid of the performance impact. Note that the code will not work on older browsers yet; you may get errors in the script babel.min.js.

The file index.html can be found under a directory called public in the GitHub repository; this is where the file will eventually end up.

Exercise: JSX

  1. 1.

    Remove type="text/babel" from the script. What happens when you load index.html? Can you explain why? Put back type="text/babel" but remove the Babel JavaScript library. What happens now?

     
  2. 2.

    We used the minified version of Babel, but not for React and ReactDOM. Can you guess why? Switch to the production minified version and introduce a runtime error in React (check out the unpkg.com website for the names of the production versions of these libraries). For example, introduce a typo in the ID of the content node so there is no place to mount the component. What happens?

     

Answers are available at the end of the chapter.

Project Setup

The server-less setup allowed you to get familiarized with React without any installations or firing up a server. But as you may have noticed, it's good neither for development nor for production. During development, some additional time is introduced for loading the scripts from a Content Delivery Network or CDN. If you take a look at the size of each of the scripts using the Network tab of the browser's developer console, you'll see that the babel compiler (even the minified version) is quite large. On production, especially in larger projects, runtime compilation of JSX to JavaScript will slow down the page loading and affect user experience.

So, let's get a little organized and serve all files from an HTTP server. We will, of course, use some other components of the MERN stack to achieve this. But before we do all that, let’s set up our project and folders in which we will be saving the files and installing libraries.

The commands that we will be typing in the shell have been collected together in a file called commands.md in the root of the GitHub repository.

Note

If you find that something is not working as expected when typing a command, cross-check the commands with the same in the GitHub repository ( https://github.com/vasansr/pro-mern-stack-2 ). This is because typos may have been introduced during the production of the book, or last-minute corrections may have missed making it to the book. The GitHub repository, on the other hand, reflects the most up-to-date and tested set of code and commands.

nvm

To start, let's install nvm. This stands for Node Version Manager, that tool makes installation and switching between multiple versions of Node.js easy. Node.js can be installed directly without nvm, but I've found that having nvm installed at the very beginning made my life easier when I had to start a new project and I wanted to use the latest and greatest version of Node.js at that point in time. At the same time, I did not want to switch to the newest version for some other of my large projects, for fear of breaking things in those projects.

To install nvm, if you are using Mac OS or any Linux-based distribution, follow the instructions on nvm's GitHub page. This can be found at https://github.com/creationix/nvm . Windows users can follow nvm for Windows (search for it in your favorite search engine) or install Node.js directly without nvm. Generally, I advise Windows users to install a Linux virtual machine (VM), preferably using Vagrant, and do all the server-side coding within the VM. This usually works best, especially because the code is finally deployed almost always on a Linux server, and having the same environment for development works best.

One tricky thing about nvm is knowing how it initializes your PATH. This works differently on different operating systems, so make sure you read up the nuances. Essentially, it adds a few lines to your shell's initialization scripts so that the next time you open a shell, your PATH is initialized and executes nvm's initialization scripts. This lets nvm know about the different versions of Node.js that are installed, and the path to the default executable.

For this reason, it's always a good idea to start a new shell right after you install nvm rather than continue in the shell that you installed it. Once you get the right path for your nvm, things follow smoothly.

You may choose to install Node.js directly, without nvm, and that is fine too. But the rest of the chapter assumes that you have installed nvm.

Node.js

Now that we have installed nvm, let's install Node.js using nvm. There are many versions of Node.js available (do check out the website, https://nodejs.org ), but for the purpose of the book, we’ll choose the latest Long Term Support (LTS), which happens to be a minor version of 10:
$ nvm install 10

The LTS version is assured to have support for a longer term than other versions. This means that although feature upgrades cannot be expected, security and performance fixes that are backward compatible can be expected. Also newer minor versions can be installed without worrying about breaking existing code.

Now that we have installed Node.js, let’s make that version the default for the future.
$ nvm alias default 10
Otherwise, the next time you enter the shell, node will not be in the PATH, or we make pick the previously installed default version. You can confirm the version of node that's been installed as your default by typing the following in a new shell or terminal:
$ node --version

Also, do make sure that any new shell also shows the newest version. (Note that the Windows version of nvm does not support the alias command. You may have to do nvm use 10 each time you open a new shell.)

Installing Node.js via nvm will also install npm, the package manager. If you are installing Node.js directly, ensure that you have installed a compatible version of npm as well. You can confirm this by noting down the version of npm that was installed along with Node.js:
$ npm --version
It should show version 6 something. npm may prompt you that there is a newer version of npm available and ask you to install that. In any case, let’s install the npm version that we wish to use in the book by specifying the version like this:
$ npm install –g npm@6

Please ensure you don’t miss the –g flag. It tells npm to install itself globally, that is, available to all projects. To double check, run npm --version again .

Project

Before we install any third-party packages with npm, it's a good idea to initialize the project. With npm, even an application is considered a package. A package defines various attributes of the application. One such important attribute is a list of other packages that the application depends upon. This will change over time, as we find a need to use libraries as we make progress with the application.

To start with, we need at least a placeholder where these things will be saved and initialized. Let’s create a directory called pro-mern-stack-2 to host the application. Let’s initialize the project from within this directory like this:
$ npm init

Most questions that this command asks of you should be easy to answer. The defaults will work fine too. From now on, you should be in the project directory for all shell commands, especially npm commands (which I'll describe next). This will ensure that all changes and installations are localized to the project directory.

npm

To install anything using npm, the command to be used is npm install <package>. To start off, and because we need an HTTP server, let's install Express using npm. Installing Express is as simple as:
$ npm install express
Once done, you will notice that it says many packages are installed. This is because it installs all other packages that Express depends upon as well. Now, let’s uninstall and install it again with a specific version. In this book, we use version 4, so let’s specify that version when installing.
$ npm uninstall express
$ npm install express@4

Note

Specifying the major version alone (in this case 4) will suffice while installing a package. Which means that you may install a minor version that is not the same as what was used when writing this book. In the rare case that this is causing a problem, look up the specific version of the package within package.json in the GitHub repository. Then, specify that exact while installing the package, for example, npm install [email protected].

npm is extremely powerful, and its options are vast. For the moment, we will concern ourselves only with the installation of packages and a few other useful things. The location of the installed files under the project directory is a conscious choice that the makers of npm made. This has the following effect:
  1. 1.

    All installations are local to the project directory. This means that a different project can use a different version of any of the installed packages. This may, at first, seem unnecessary and feel like a lot of duplication. But you will really appreciate this feature of npm when you start multiple Node.js projects and don't want to deal with a package upgrade that you don't need. Further, you will notice that the entire Express package, including all dependencies, is just 1.8MB. With packages being so tiny, excessive disk usage is not a concern at all.

     
  2. 2.

    A package's dependencies are also isolated within the package. Thus, two packages depending on different versions of a common package could be installed, and they would each have their own copy and therefore work flawlessly.

     
  3. 3.

    Administrator (superuser) rights are not needed to install a package.

     

There is, of course, an option to install packages globally, and sometimes it is useful to do this. One use case is when a command-line utility is packaged as an npm package. In such cases, having the command-line available regardless of the working directory is quite useful. In such cases, the –g option of npm install can be used to install a package globally and made available everywhere.

If you had installed Node.js via nvm, a global install will use your own home directory and make the package available to all projects within your home directory. You don’t need superuser or administrator rights to install a package globally. On the other hand, if you had installed Node.js directly, making it available for all users on your computer, you will need superuser or administrator rights.

At this point in time, it is a good idea to look at the GitHub repository ( https://github.com/vasansr/pro-mern-stack-2 ) again, especially to view the differences from the previous step. In this section we only added new files, so the only differences you will see are the new files.

Exercise: Project Setup

  1. 1.

    When was package.json created? If you can't guess, inspect the contents; that should give you a hint. Still can't figure out? Go back and re-do your steps. Start with creation of the project directory and look at the directory contents at each step.

     
  2. 2.

    Uninstall Express, but use the option --no-save. Now, just type npm install. What happens? Add another dependency, say, MongoDB, manually to package.json this time. Use version as “latest”. Now, type npm install. What happens?

     
  3. 3.

    Use --save-dev while installing any new package. What difference do you see in package.json? What difference do you think it will make?

     
  4. 4.

    Where do you think the packages files are installed? Type npm ls --depth=0 to check all the currently installed packages. Clean up any packages you do not need.

     

Play around with npm installation and un-installation a bit. This will be useful in general. Learn more about npm version syntax from the documentation: https://docs.npmjs.com/files/package.json#dependencies .

Answers are available at the end of the chapter.

Note

Although it is a good practice to check in the package.json.lock file so that the exact versions installed can be shared among team members, I have excluded it from the repository to keep the diffs concise and readable. When you start a team project using the MERN stack, you should check in this file in your Git repository.

Express

Express, if you remember in the introduction in the previous chapter, is the best way to run an HTTP server in the Node.js environment. For starters, we'll use Express to serve only static files. This is so that we get used to what Express does, without getting into a lot of server-side coding. We’ll serve the index.html file that we created in the previous section via Express.

We had installed Express in the previous step, but to ensure it’s there, let’s execute the npm command for installing it again. This command does nothing if the package is installed, so when in doubt, we can always run it again.
$ npm install express@4
To start using Express, let’s import the module and use the top-level function that the module exports, in order to instantiate an application. This can be done using the following code:
...
const express = require('express');
...

require is a JavaScript keyword specific to Node.js, and this is used to import other modules. This keyword is not part of browser-side JavaScript because there was no concept of including other JavaScript files. All needed scripts were included directly in HTML files only. ES2015 specification came up with a way to include other files using the import keyword, but before the specification came out, Node.js had to invent its own using require. It’s also known as the CommonJS way of including other modules.

In the previous line, we loaded up the module called express and saved the top-level thing that the module exports, in the constant named express. Node.js allows the thing to be a function, an object, or whatever can fit into a variable. The type and form of what the module exports is really up to the module, and the documentation of the module will tell you how to use it.

In the case of Express, the module exports a function that can be used to instantiate an application. We just assigned this function to the variable express .

Note

We used the ES2015 const keyword to define the variable express. This makes the variable non-assignable after the first declaration. For variables that may be assigned a new value, the keyword let can be used in place of const.

An Express application is web server that listens on a specific IP address and port. Multiple applications can be created, which listen on different ports, but we won’t do that, as we need just a single server. Let’s instantiate the one and only application by calling the express() function :
...
const app = express();
...

Now that we have a handle to the application, let’s set it up. Express is a framework that does minimal work by itself; instead, it gets most of the job done by functions called middleware. A middleware is a function that takes in an HTTP request and response object, plus the next middleware function in the chain. The function can look at and modify the request and response objects, respond to requests, or decide to continue with middleware chain by calling the next middleware function.

At this point in time, we need something that looks at a request and returns the contents of a file based on the request URL’s path. The built-in express.static function generates a middleware function that does just this. It responds to a request by trying to match the request URL with a file under a directory specified by the parameter to the generator function. If a file exists, it returns the contents of the file as the response, if not, it chains to the next middleware function. This is how we can create the middleware:
...
const fileServerMiddleware = express.static('public');
...

The argument to the static() function is the directory where the middleware should look for the files, relative to where the app is being run. For the application we’ll build as part of this book, we’ll store all static files in the public directory under the root of our project. Let’s create this new directory, public, under the project root and move index.html that we created in the previous section to this new directory.

Now, for the application to use the static middleware, we need to mount it on the application. Middleware in an Express application can be mounted using the use() method of the application. The first argument to this method is the base URL of any HTTP request to match. The second argument is the middleware function itself. Thus, to use the static middleware, we can do this :
...
app.use('/', fileServerMiddleware);
...

The first argument is optional and defaults to '/' if not specified, so we could skip it too.

Finally, now that the application is set up, we’ll need to start the server and let it serve HTTP requests. The listen() method of the application starts the server and waits eternally for requests. It takes in a port number as the first argument. Let’s use the port 3000, an arbitrary port. We won’t use port 80, the usual HTTP port, because to listen to that port we need to have administrative (superuser) privileges.

The listen() method also takes another argument, an optional callback that can be called when the server has been successfully started. Let’s supply an anonymous function that just prints a message that the server has been started, like this:
...
app.listen(3000, function () {
  console.log('App started on port 3000');
});
...
Let’s put all this together in a file called server.js in the root directory of the project. Listing 2-3 shows the final server code, with the use() call merged with the creation of the middleware in a single line, and skipping the optional first argument, the mount point .
const express = require('express');
const app = express();
app.use(express.static('public'));
app.listen(3000, function () {
  console.log('App started on port 3000');
});
Listing 2-3

server.js: Express Server

Now, we are ready to start the web server and serve index.html. If you are looking at the GitHub repository for the code, you will find server.js under a directory called server. But at this point, the file needs to be in the root of the project directory.

To start the server, run it using the Node.js runtime like this, at the root directory of the project:
$ node server.js

You should see a message saying the application has started on port 3000. Now, open your browser and type http://localhost:3000/index.html in the URL bar. You should see the same Hello World page that we created in the previous section. If you see a 404 error message, maybe you have not moved index.html to the public directory .

The static middleware function served the contents of the index.html file from the public directory, as it matched the request URL. But it is also smart enough to translate a request to / (the root of the website) and respond by looking for an index.html in the directory. This is similar to what other static web servers such as Apache do. Thus, typing just http://localhost:3000/ is good enough to get the Hello World page.

To start the server, we had to supply the name of the entry point (server.js) to Node.js. This may not be something that is easy to remember or to tell other users of the project. If there were many files in our project, how does anyone know which is the file that starts the server? Fortunately, there is a convention used in all Node.js projects: npm scripts are used to do common tasks. The following command line is an alternative way to start the server:
$ npm start

When this is executed, npm looks for the file server.js and runs it using Node.js. So, let’s stop the server (using Ctrl+C on the command shell) and restart the server using npm start. You should see the same message saying the server has been started.

But what if we had a different starting point for the server? In fact, we want all the server-related files to go into a directory called server. So, let’s create that directory and move server.js into that directory.

Now, if you run npm start, it will fail with an error. That’s because npm looks for server.js in the root directory and doesn’t find it. In order to let npm know that the entry point for the server is server.js within the sub-directory called server, an entry needs to be added to the scripts section of package.json. Listing 2-4 shows these changes.
...
  "main": "index.js",
  "scripts": {
    "start": "node server/server.js",
    "test": "echo "Error: no test specified" && exit 1"
  },
...
Listing 2-4

package.json: Changes for Start Script

Thus, if the server starting point is anything other than server.js in the root directory, the full command line has to be specified in the scripts section of package.json.

Note that there is also a field called main in package.json. When we initialized this file, the value of this field was set to index.js automatically. This field is not meant for indicating the starting point of the server. Instead, if the package was a module (as opposed to an application), index.js would have been the file to load when the project is imported as a module using require() in other projects. Since this project is not a module that can be imported in other projects, this field is not of any interest to us, and neither do we have index.js in the source code.

Now, we can use npm start and see the familiar application started message and test it out one final time. At this point in time, it’s good to check out the GitHub repository and look at the diffs for this section. In particular, take a look at changes in an existing file, package.json, to familiarize yourself with how changes within a file are shown in the book and how the same changes are seen in GitHub as diffs.

Exercise: Express

  1. 1.

    Change the name of the index.html file to something else, say, hello.html. How does this affect the application?

     
  2. 2.

    If you wanted all static files to be accessed by a prefixed URL, for example /public, what change would you make? Hint: Take a look at the Express documentation for static files at https://expressjs.com/en/starter/static-files.html .

     

Answers are available at the end of the chapter.

Separate Script File

In all the previous sections, the transformation of JSX to JavaScript happens at runtime. This is inefficient and quite unnecessary. Let’s instead move the transformation to the build stage in our development, so that we can deploy a ready-to-use distribution of the application.

As the first step, we need to separate out the JSX and JavaScript from the all-in-one index.html and refer to it as an external script. This way, we can keep the HTML as pure HTML and all the script that needs compilation in a separate file. Let’s call this external script App.jsx and place it in the public directory, so that it can be referred to as /App.jsx from the browser. The contents of the new script file, of course, won’t have the <script> tag enclosing it. And, within index.html, let’s replace the inline script with a reference to an external source like this:
...
  <script type="text/babel" src="/App.jsx"></script>
...
Note that the script type text/babel continues to be needed, since the JSX compilation happens in the browser using the Babel library. The newly modified files are listed in Listings 2-5 and 2-6.
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>Pro MERN Stack</title>
  <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
</head>
<body>
  <div id="contents"></div>
  <script type="text/babel" src="/App.jsx"></script>
</body>
</html>
Listing 2-5

index.html: Separate HTML and JSX

const element = (
  <div title="Outer div">
    <h1>Hello World!</h1>
  </div>
);
ReactDOM.render(element, document.getElementById('contents'));
Listing 2-6

App.jsx: JSX Part Separated Out from the HTML

At this point, the app should continue to work as before. If you point your browser to http://localhost:3000, you should see the same Hello World message. But we have only separated the files; we have not moved the transform to build time. The JSX continues to get transformed by the Babel library script, which was executed in the browser. We’ll move the transform to build time in the next section.

JSX Transform

Let’s now create a new directory to keep all the JSX files, which will be transformed into plain JavaScript and into the public folder. Let's call this directory src and move App.jsx into this directory.

For the transformation, we'll need to install some Babel tools. We need the core Babel library and a command-line interface (CLI) to do the transform. Let’s install these using the command:
$ npm install --save-dev @babel/core@7 @babel/cli@7
To ensure we have the Babel compiler available as a command-line executable, let’s try to execute the command babel on the command line and check the version that is installed using the --version option. Since it is not a global install, Babel will not be available in the path. We have to specifically invoke it from its installed location like this:
$ node_modules/.bin/babel --version
This should give an output similar to this, but the minor version may be different for you, for example, 7.2.5 instead of 7.2.3:
7.2.3 (@babel/core 7.2.2)
We could have installed @babel/cli globally using the --global (or –g) option of npm. That way, we would have had access to the command in any directory, without having to prefix the path. But as discussed earlier, it's good practice to keep all installations local to a project. This is so that we don't have to deal with version differences of a package across projects. Further, the latest version of npm gives us a convenient command called npx, which resolves the correct local path of any executable. This command is available only in npm version 6 onwards. Let’s use this command to check the Babel version:
$ npx babel --version
Next, to transform JSX syntax into regular JavaScript, we need a preset (a kind of plugin that Babel uses). That’s because Babel is capable of doing a lot of other transforms (which we’ll cover in the next section) and many presets not part of the Babel core library are shipped as a different package. The JSX transform preset is one such preset called preset-react, so let’s install it.
$ npm install --save-dev @babel/preset-react@7
Now we're ready to transform App.jsx into pure JavaScript. The babel command-line takes an input directory with the source files and the presets that are applicable and the output directory as options. For the Hello World application, the source files are in the src directory, so we want the output of the transform to be in the public directory and we want to apply the JSX transform preset, @babel/react. Here’s the command line for that:
$ npx babel src --presets @babel/react --out-dir public

If you look at the output directory public, you will see that there is a new file called App.js in there. If you open the file in your editor, you can see that the JSX elements have been converted to React.createElement() calls . Note that the Babel compilation automatically used the extension .js for the output file, which indicates that it is pure JavaScript.

Now, we will need to change the reference in index.html to reflect the new extension and remove the script type specification since it is pure JavaScript. Further, we'll no longer need the runtime transformer to be loaded in index.html, so we can get rid of the babel-core script library specification. These changes are shown in Listing 2-7.
...
  <script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
...
  <body>
    <div id="contents"></div
    <script src="/App.jsx" type="text/babel"></script>
    script src="/App.js"></script>
  </body>
...
Listing 2-7

index.html: Change in Script Name and Type

If you test this set of changes, you should see that things work as before. For good measure, you could use the browser's developer console to ensure it is App.js that's being fetched, and not App.jsx. The developer console can be found on most browsers; you may need to look at your browser’s documentation for instructions to get to it.

Exercise: JSX Transform

  1. 1.

    Inspect the contents of App.js, the output of the transform. You’ll find it in the public directory. What do you see?

     
  2. 2.

    Why did we use --save-dev while installing babel-cli? Hint: Read the npm documentation for the CLI command for install at https://docs.npmjs.com/cli/install .

     

Answers are available at the end of the chapter.

Older Browsers Support

I’d mentioned earlier that the JavaScript code will work in all modern browsers that support ES2015. But what if we need to support older browsers, for example, Internet Explorer? The older browsers don’t support things like Arrow Functions and the Array.from() method . In fact, running the code at this point in IE 11 or earlier should throw a console error message saying Object.assign is not a function.

Let’s make some changes to the JavaScript and React to use some of these advanced ES2015 features. Then, let’s make changes to support all these features in older browsers as well. To use ES2015 features, instead of showing a message with Hello World in it, let’s create an array of continents and construct a message with each continent included in it.
...
const continents = ['Africa','America','Asia','Australia','Europe'];
...
Now, let’s use the Array.from() method to construct a new array with a Hello in front of each continent’s name and exclamation mark at the end. To do this, we will use the map() method of array, taking in an arrow function. We will use string interpolation instead of concatenating the strings. Array.from(), Arrow Functions, and string interpolation are both ES2015 features. Using the new mapped array, let’s construct the message, which just joins the new array. Here’s the code snippet:
...
const helloContinents = Array.from(continents, c => `Hello ${c}!`);
const message = helloContinents.join(' ');
...
Now, instead of the hard-coded Hello World message, let’s use the constructed message variable inside the heading element. Similar to ES2015 string interpolation using back-ticks, JSX lets us use any JavaScript expression by enclosing it in curly braces. These will be replaced by the value of the expression. This works not only for HTML text nodes but also within attributes. For example, the class name for an element can be a JavaScript variable. Let’s use this feature to set the message to be displayed in the heading.
...
    <h1>{message}</h1>
...
The full source code of the modified App.jsx is shown in Listing 2-8.
const continents = ['Africa','America','Asia','Australia','Europe'];
const helloContinents = Array.from(continents, c => `Hello ${c}!`);
const message = helloContinents.join(' ');
const element = (
  <div title="Outer div">
    <h1>{message}</h1>
  </div>
);
ReactDOM.render(element, document.getElementById('contents'));
Listing 2-8

App.jsx: Changes to Show the World with ES2015 Features

If you transform this using Babel, restart the server and point a browser at it. You will find that it works on most modern browsers. But, if you look at the transformed file, App.js, you will see that the JavaScript itself has not been changed, only the JSX has been replaced with React.createElement() calls . This is sure to fail on older browsers that neither recognize the arrow function syntax or the Array.from() method .

Babel comes to the rescue again. I talked about other transforms that Babel is capable of, and this includes transforming newer JavaScript features into older JavaScript, that is, ES5. Just like the react preset for JSX transformation, there is a plugin for each of these features. For example, there is a plugin called plugin-transform-arrow-functions . We can install this plugin and use it in addition to the React preset like this:
$ npm install --no-save @babel/plugin-transform-arrow-functions@7
We used the --no-save install option because this is a temporary installation, and we don’t want package.json to change because of the temporary installation. Let’s use this plugin and transform the source file like this:
$ npx babel src --presets @babel/react
--plugins=@babel/plugin-transform-arrow-functions --out-dir public

Now if you inspect the output of the transform, App.js, you will see that arrow functions have been replaced with regular functions.

That’s great, but how does one know which plugins have to be used? It’s going to be tedious to find out which browsers support what syntax and what transforms we’ve to choose for each. Fortunately, Babel does the job of automatically figuring this out via a preset called preset-env. This preset lets us specify the target browsers that we need to support and automatically applies all the transforms and plugins that are required to support those browsers.

So, let’s uninstall the transform-arrow-function preset and install instead the env preset that includes all the other plugins.
$ npm uninstall @babel/plugin-transform-arrow-functions@7
$ npm install --save-dev @babel/preset-env@7

Instead of using the command-line (which can get quite lengthy if we do use it), let’s specify the presets that need to be used in a configuration file. Babel looks for this in a file called .babelrc. In fact, there can be a .babelrc file in different directories, and the settings for files in that directory can be specified in each of those separately. Since we have all the client-side code in the directory called src, let’s create this file in that directory.

The .babelrc file is a JSON file, which can contain presets as well as plugins. Presets are specified as an array. We can specify both the presets as strings in this array like this:
...
{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}
The preset preset-env needs further configuration to specify target browsers and their versions. This can be done by using an array with the first element as the preset name followed by its options. For preset-env, the option that we will use is called targets, which is an object with the key as the browser name and the value as its targeted version:
  ["@babel/preset-env", {
    "targets": {
      "safari": "10",
       ...
     }
  }]
Let’s include support for IE version 11 and slightly older versions of the other popular browsers. The complete configuration file is shown in Listing 2-9.
{
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        "ie": "11",
        "edge": "15",
        "safari": "10",
        "firefox": "50",
        "chrome": "49"
      }
    }],
    "@babel/preset-react"
  ]
}
Listing 2-9

src/.babelrc: Presets Configured for JSX and ES5 Transform

Now, the babel command can be run without specifying any presets on the command-line:
$ npx babel src --out-dir public

If you run this command and then inspect the generated App.js, you will find that the arrow function has been replaced with a regular function and also that the string interpolation has been replaced with a string concatenation. If you take out the line ie: "11" from the configuration file and re-run the transform, you’ll find that these transformations are no longer there in the output file, because the browsers that we’re targeting already support these features natively.

But even with these transformations, if you test on an Internet Explorer version 11, the code will still not work. That’s because it’s not just the transformations; there are some built-ins such as Array.find() that just aren’t there in the browser. Note that no amount of compilation or transformation can add a bunch of code like the Array.find() implementation. We really need these implementations to be available at runtime as a library of functions.

All such function implementations to supplement the missing implementation in older browsers are called polyfills. Babel transforms can only deal with syntactic changes, but these polyfills are required to add these new functions’ implementations. Babel provides these polyfills too, which can just be included in the HTML file to make these functions available. The Babel polyfill can be found in unpkg, so let’s include that in index.html. Listing 2-10 shows the changes to index.html for including the polyfill.
...
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/polyfill@7/dist/polyfill.min.js"></script>
</head>
...
Listing 2-10

index.html: Changes for Including Babel Polyfill

Now, the code can work on Internet Explorer as well. Figure 2-2 shows how the new Hello World screen should look.
../images/426054_2_En_2_Chapter/426054_2_En_2_Fig2_HTML.jpg
Figure 2-2

The new Hello World screen

Exercise: Older Browser Support

  1. 1.

    Try to format the message as one per line by using <br /> to join the individual messages instead of a space. Are you able to do it? Why not?

     

Answers are available at the end of the chapter.

Automate

Apart from being able to start the project using npm start, npm has the ability to define other custom commands. This is especially useful when there are many command line parameters for the command and typing them out on the shell becomes tedious. (Didn’t I say npm was powerful? This is one of the things that it does, even though this is not real package manager functionality.) These custom commands can be specified in the scripts section of package.json. These can then be run using npm run <script> from the console.

Let’s add a script called compile whose command line is the Babel command line to do all the transforms. We don’t need the npx prefix because npm automatically figures out the location of commands that are part of any locally installed packages. The addition we’ll need to do in package.json is thus:
...
    "compile": "babel src --out-dir public",
...
The transform can now be run like this:
$ npm run compile

After this, if you run npm start again to start the server, you can see any changes to App.jsx reflected in the application.

Note

Avoid using npm sub-command names that are also npm first-level commands such as build and rebuild, as this leads to silent errors if and when you leave out the run in the npm command.

When we work on the client-side code and change the source files frequently, we have to manually recompile it for every change. Wouldn’t it be nice if someone could detect these changes for us and recompile the source into JavaScript? Well, Babel supports this out of the box via the --watch option. To make use of it, let’s add another script called watch with this additional option to the Babel command line:
...
    "watch": "babel src --out-dir public --watch --verbose"
...

It is essentially the same command as compile, but with two extra command line options, --watch and --verbose. The first option instructs Babel to watch for changes in source files, and the second one causes it to print out a line in the console whenever a change causes a recompilation. This is just to give a reassurance that a compile has in fact taken place whenever a change is made, provided you keep a watch on the console running this command.

A similar restart on changes to the server code can be affected by using a wrapper command called nodemon . This command restarts Node.js with the command specified whenever there is a change in a set of files. You may also find by searching the Internet that forever is another package that can be used to achieve the same goal. Typically, forever is used to restart the server on crashes rather than watch for changes to files. The best practice is to use nodemon during development (where watching for changes is the real need) and forever on production (where restarting on crashes is the need). So, let’s install nodemon now:
$ npm install nodemon@1
Now, let’s use nodemon to start the server instead of Node.js in the script specification for start in package.json. The command nodemon also needs an option to indicate which files or directory to watch changes for using the -w option. Since all the server files are going to be in the directory called server, we can use -w server to make nodemon restart Node.js when any file in that directory changes. So, the new command for the start script within package.json will now be:
...
    "start": "nodemon -w server server/server.js"
...
The final set of scripts added or changed in package.json is shown in Listing 2-11.
...
  "scripts": {
    "start": "node server/server.js",
    "start": "nodemon -w server server/server.js",
    "compile": "babel src --out-dir public",
    "watch": "babel src --out-dir public --watch --verbose",
    "test": "echo "Error: no test specified" && exit 1"
  },
...
Listing 2-11

Package.json: Adding Scripts for Transformation

If you now run the new command using npm run watch , you will notice that it does one transform, but it doesn’t return to the shell. It’s actually waiting in a permanent loop, watching for changes to the source files. So, to run the server, another terminal is needed, where npm start can be executed.

If you make make a small change to App.jsx and save the file, you’ll see that App.js in the public directory is regenerated. And, when you refresh the browser, you can see those changes without having to manually recompile. You can also make any changes to server.js and see that the server starts, with a message on the console that says the server is being restarted.

Summary

In this chapter, you learned the basics of how React applications can be built. We started with a simple piece of code written in React JSX that we compiled at runtime, then we moved the compilation as well as serving the file to the server.

We used nvm to install Node.js; you saw how npm can be used not just to install Node.js packages, but also to save command-line instructions in conventional or easy-to-spot scripts. We then used Babel, to transpile, that is, transform or compile from one specification of the language to another to support older browsers. Babel also helped us transform JSX into pure JavaScript.

You also got a whiff of what Node.js with Express can do. We did not use MongoDB, the M in the MERN stack, but I hope you got a good view of the other components of the stack.

By now, you should also have gotten familiar with how the book’s GitHub repository is organized and the conventions used in the book. For every section, there is a testable set of code that you can compare your own typed out code with. Importantly, the diffs between each step are valuable to understand the exact changes that were made in each step. Once again, note that the code in the GitHub repository is the one to rely on, with up-to-date changes that couldn’t make it to the printed book. If you find that you have followed the book verbatim, yet things don’t work as expected, do consult the GitHub repository to see if the printed book’s errors have been corrected there.

In the next two chapters, we’ll dive deeper into React, then surface up to look at the big picture, dealing with APIs, MongoDB, and Express in later chapters.

Answers to Exercises

Exercise: Server-less Hello World

  1. 1.

    To specify a class in React.createElement(), we need to use { className: <name>} instead of {class: <name>}. This is because class is a reserved word in JavaScript, and we cannot use it as a field name in objects.

     
  2. 2.

    The element variable contains a nested tree of elements, which reflects what the DOM would contain. I would call this a virtual DOM, which is what it is indeed popularly referred to as.

     

Exercise: JSX

  1. 1.

    Removing the script type will cause the browser to treat it as regular JavaScript and we will see syntax errors on the console because JSX is not valid JavaScript. Removing the Babel compiler instead will cause the script to be ignored, since the browser does not recognize scripts of type text/babel, and it will ignore it. In either case, the application will not work.

     
  2. 2.

    A minified version of React hides or shortens runtime errors. A non-minified version gives full errors and helpful warnings as well.

     

Exercise: Project Setup

  1. 1.

    package.json was created when we created the project using npm init. In fact, all our responses to the prompts when we ran npm init were recorded in package.json.

     
  2. 2.

    When using --no-save, npm keeps the file package.json unchanged. Thus, package.json would have retained the dependency of Express. Running npm install without any further options or parameters installs all dependencies listed in package.json. Thus, you could add dependencies manually to package.json and just use npm install.

     
  3. 3.

    The --save-dev option adds the package in devDependencies instead of dependencies. The list of dev dependencies will not be installed in production, which is indicated by the environment variable NODE_ENV being set to the string production.

     
  4. 4.

    Package files are installed under the directory node_modules under the project. npm ls lists all the packages installed, in a tree-like manner. --depth=0 restricts the tree depth to the top-level packages. Deleting the entire node_modules directory is one way of ensuring you start clean.

     

Exercise: Express

  1. 1.

    The static file middleware does not specially treat hello.html as it did index.html, so you will have to access the application with the name of the file like this: http://localhost:3000/hello.html.

     
  2. 2.

    For accessing static files via a different mount point, specify that prefix in the middleware generated helper function as the first parameter. For example, app.use('/public', express.static('/public')).

     

Exercise: JSX Transform

  1. 1.

    App.js now contains pure JavaScript, with all JSX elements converted to React.createElement() calls. We couldn’t see this transform earlier when the transform happened in the browser.

     
  2. 2.

    When we deploy the code, we will only deploy a pre-built version of the application. That is, we will transform the JSX on a build server or our development environment and push out the resulting JavaScript to our production server. Thus, on the production server, we will not need the tools that are required to build the application. Therefore, we used --save-dev so that, on the production server, the package need not be installed.

     

Exercise: Older Browsers Support

  1. 1.

    React does this on purpose, to avoid cross-site scripting vulnerabilities. It is not easy to insert HTML markup, although there is a way using the dangerouslySetInnerHTML attribute of an element. The correct way to do this would be to compose an array of components. We will explore how to do this in later chapters.

     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.143.239