Organizing Your Code

Organizing Your Code into Multiple Files is a Good Practice

When we start building larger applications or projects with JavaScript it is a good idea to organize our code in separate files.

A number of conventions exist for how to best organize your JavaScript files. In general though, once a file starts to get too large or do too many different things, we split it up into smaller files where all of the code is related.

In our final project we will follow a "Component Architecture" pattern for organizing our code. In this pattern each portion of our website or application is called a "component" and saved in it's own file.

Something as simple as a button would likely be considered a "component" and code for it saved in it's own file. Something more complex, like a video player, may be broken down into several components like "video", "controls" and "description". Then "controls" may be broken down into even smaller components of "button" and "volumeControl" etc.

This chapter focuses on how we can share JavaScript across multiple files. Then in the project we will dig deeper into how we can organize our files at a practical level.

Sharing JavaScript Between Files via the Browser API

The simplest way to have JavaScript from one file accessible in another file is to link to them in the order you need them in your HTML.

Take a look at this HTML file and how it links to the JavaScript files:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Code Organization - JavaScript Explained</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="config.js"></script>
<script src="data.js"></script>
<script src="app.js"></script>
</html>

Code from config.js is available to data.js and index.js . Data from data.js is available in index.js.

However, code from index.js is not available to data.js and code from data.js or index.js is not available to config.js.

This happens because because of the inner workings of the Browser API, not because of anything in JavaScript itself.

The JavaScript language actually has another way that it designates for code to be shared between separate files.

Imports and Exports

JavaScript has something called imports and exports which allow code to be shared between files.

Below is a simple example of imports and exports in action.

Image we have the following code organization:

config.js
index.js
index.html

Our config.js file is going to be used in several other files, so after we setup our config object we set it up to be exported like so:

const config = {
name: `JavaScript Explained`,
url: `https://javascriptexplained.com`,
author: `Zac Gordon`
};
export default config;

Then we can import this file into our index.js file like so:

import config from "./config";
function init() {
const markup = `<h1>Welcome to ${config.name}</h1>`;
document.querySelector(`#app`).insertAdjacentHTML(`beforeend`, markup);
}
init();

Now, we only need to link to index.js in our index.html and config.js will be imported automatically as well.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>JavaScript Explained</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="index.js"></script>
</html>

Although this is completely valid JavaScript, it will not work in the browsers at this time.

The reason this will not work is that the Browser API does not have access or ability to read and combine files from a server.

So, we must leverage a tool to map all of our imports and exports and bundle all of our code down into one file (or a few files) that it does have access to read.

Default Exports vs Named Exports

There are two types of exports we can do in JavaScript: default exports and named exports.

A default export looks like the following:

const config = {
name: `JavaScript Explained`,
url: `https://javascriptexplained.com`,
};
export default config;

This is then imported as follows:

import config from "./config"

This is most common when we have a file that only exports one variable or function.

It is also possible to have a file export multiple values. These are called named exports:

export const name = `JavaScript Explained`;
export const url = `https://javascriptexplained.com`;

These are imported a little differently, like so:

import { name, url } from "./config";

Notice that named exports need to appear within curly braces when imported. This is the primary difference between default exports and named imports.

You can also have both default and named imports in the same file, however, this is less common.

When we default export a variable we have to define the variable on one line and export it on another.

const name = `JavaScript Explained`;
export default name;

However, when we default export a function, we can do it on one line. This has to do with how JavaScript processes functions slightly differently than variables.

export default function Posts() {
console.log( `Posts` );
}

It is also possible to have a file that contains both default exports and named exports like the following:

default export Posts() {
console.log( `Log Posts` )
}
export initPosts() {
// Setup event handlers here
}

These would then be imported into another file like so:

import Posts, { initPosts } from "./components/posts"

Depending on the JavaScript project you are working with, you may see just default exports, just named exports or a combination of both.

Bundler Tools

A bundler tool is one that understands how imports and exports work in JavaScript. It will create a map of how all of our files fit together and then bundle them down into one large single file.

We can then link to the final bundled file in our HTML.

The bundler tool we will use in this book is called Parcel. Webpack is another popular one.

In the future, we will likely not need bundler tools for our JavaScript imports and exports. If you choose to write JavaScript without imports and exports you do not need a bundler tool. However, modern JavaScript uses imports, exports and bundlers as part of the normal workflow.

A Range of Helpful Developer Tools Exist for JavaScript

When we start off coding JavaScript we come across our first helpful developer tool: a code editor. We may have installed this on our computer or found one running online in a browser.

From there we will likely move on to find other helpful developer tools. Some of these can integrate with our code editor, such as a command line tool. Some of these tools exist as stand alone applications, such as testing suites.

In this chapter we are going to look at a few common development tools that will help us in building our final project as well come in handy when working on production level JavaScript projects.

The src and dist Directories

When we work with imports, exports and build tools we end up with two versions of our JavaScript:

  1. The original source code that we edit

  2. The final bundled file(s) that is minified and loaded in the browser

Commonly we place all of our source code inside of a folder called src. Then our final bundled code goes into a file called dist. Then names of these files can vary, but this naming convention is quite common.

This will commonly create a file structure like this:

├── /dist
| ├── index.js
| ├── index.html
├── /src
| |── config.js
| |── data.js
| └── index.js
└── index.html

We have all of the JavaScript files we want to edit and work with in our src folder. Then we have our final bundled code in our dist folder.

Our main index.html file would link to our src/index.js file and get everything bundled all together.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>JavaScript Explained</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="src/index.js"></script>
</html>

Then our bundler tool would also create a bundled HTML file and place it inside the dist folder. This final index.html file would look something like this:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>
JavaScript Explained</title></head><body> <div id="app"></div></body>
<script src="index.js"></script></html>

You can see for one that the final HTML file is minified. It is also linked to the dist/index.js file, which would also be minified.

Now that we have a theoretical understanding of how imports, exports and bundlers work, let's take a more practical look at how to setup and work with bundlers.

Bundlers Are Command Line Tools

A command line tool is a piece of software that does not have a graphical user interface, or it's own application that you open and interact with.

Bundlers fit into this category of software and generally require us to type commands in a command line interface.

Luckily, code editors such as VS Code and others include a command line tool in the editor so we can easily call the commands we will need for our bundlers.

If you have not used the command line before it can be a little intimidating, however, it is not difficult. There are only a handful of commands you need to know, and they are all quite short.

We will learn everything we need to know as we go along here, but you can also check out the free course at commandlinebasics.com if you want to learn more.

Setting Up Parcel

Parcel has gained in popularity because of it's ease to use. However, it does still require we have a few things up and running on our computer.

The first thing we need is the Node programming language, which is actually just JavaScript running on your computer rather than in your web browser. We also need NPM, a package manager used for installing tools like Parcel. Both of these come installed on Macs and can easily be installed on PCs as well via instructions at nodejs.org.

Once we have Node and NPM installed we can open a new folder in our code editor (I am using VS Code) and open the command line tool (View > Terminal).

Then type the following command:

npm init -y

This will setup a package.json file that some command line tools use for tracking configurations and versions.

Then run this command, which will install parcel as part of our project:

npm install parcel-bundler --save-dev

If you are using a computer where you do not have root level access, you may need to add sudo to the beginning of your npm install commands. This will prompt you for your password and should grant NPM the access it needs to install packages for your project.

The command above would then look like this:

sudo npm install parcel-bundler --save-dev

Now open up your package.json file and where you see the "scripts" section, replace that with the following:

"scripts": {
"dev": "parcel src/index.js",
"build": "parcel build src/index.js"
},

This will let you now run two new commands:

npm run dev

This command will tell parcel to bundle your code, starting with the src/index.js file and bundle it, along with any files imported. The bundled code will be saved to dist/index.js. Then Parcel will continue to watch your files for any changes and run the process every time a file changes.

npm run build

This command does the same thing as the previous command, however, it only executes the command once. It will not continue watching for changes and re-bundle the code. This command is meant to be run when you are ready to ship your code to production.

Although the Parcel documentation recommends setting up the dev script, it is common practice to call this start instead of dev. We can then use a shortcut and call the following:

npm start

Notice that we do not need to type npm run start just npm start.

Setting Up Our Files to Bundle

Now we are going to setup a simple example project to demonstrate how to use Parcel in practice.

Create a file structure in the same project folder as above with the following files:

├── /src
| |── config.js
| |── data.js
| └── index.js
├── index.html
└── package.json

Now place the following code in the config.js file:

const config = {
name: `JavaScript Explained`,
url: `https://javascriptexplained.com`,
author: `Zac Gordon`
};
export default config;

Then place the following code in your data.js file:

var posts = [
{
id: 1,
title: `Hello JavaScript`
},
{
id: 2,
title: `Hello Code Organization`
},
{
id: 3,
title: `Hello Tooling!`
}
];

Finally, add the following to your index.js file:

import config from "./config";
import data from "./data";
function init() {
let markup = `<h1>Welcome to ${config.name}</h1>`;
markup += `<h2>Posts</h2>`;
markup += `<ul>`;
data.forEach(post => (markup += `<li>${post.title}</li>`));
markup += `</ul>`;
document.querySelector(`#app`).insertAdjacentHTML(`beforeend`, markup);
}
init();

Now we are ready to try bundling our code. This will take our three JavaScript files and combine them into one minified file.

Run the following command in the command line (make sure you are in the root folder of your project):

npm run dev

You should see some messages display that things are being bundled and have been done so successfully. You should also see a new dist/index.js file setup for you.

Now we can link to this bundled index file from our index.html file. Open the index.html file at the root of your project and add the following code:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>JavaScript Explained</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="dist/index.js"></script>
</html>

When you open your index.html file in the browser you should see the correct title and list of posts.

To stop Parcel from watching your files, type Crt + C in the command line. Now we are going to practice using the build command.

Open your data.js file and add in one more post:

var posts = [
{
id: 1,
title: `Hello JavaScript`
},
{
id: 2,
title: `Hello Code Organization`
},
{
id: 3,
title: `Hello Tooling!`
},
{
id: 4,
title: `Hello Bundling!`
}
];

Save that file and then run the following command:

npm run build

You should see a message that the files have bundled and when you open index.html in the browser you should see the updated code.

This workflow of using npm run dev while we are updating our code and then npm run build the final time when we are ready to ship to production is the modern development workflow for working with imports, exports and bundlers in JavaScript.

The index.js File

When we work with servers, they are often configured to recognize index.html as a file that should be loaded automatically.

We borrow from this naming convention when working with JavaScript. There is a common convention to name the primary JavaScript file in each folder index.js. These files will not load automatically, we are just borrowing the naming convention.

However, when we use imports we do get one benefit from using index.js. Let's imagine we had the following folder structure:

├── /src
| |── /components
| | |── /header
| | | └── index.js
| | |── /footer
| | | └── index.js
| | |── /menu
| | | └── index.js
| | |── /posts
| | | ├── index.js
| | | └── post.js
| └── index.js
├── index.html
└── package.json

When we import all of our code into our main index.js we can do a longhand format of this:

import header from "./components/posts/index.js"

Or, we can shorten it to just this:

import header from "./components/posts/"

If we wanted to link to the src/components/posts/post.js file we would have to include the file name, but we could leave off the .js since that is assumed. So including that file would look like this:

// Includes the index.js file
import header from "./components/posts/"
// Includes the post.js file
import header from "./components/posts/post"

This is very similar to the folder and file structure we will follow for our project in the next section.

Using the Bundler Development Server

Most bundlers include a development server that we can use to run our code. So rather than opening our index.html file directly in the browser, we would open a certain url like http://localhost and see our site running.

When we run npm run dev or npm start with Parcel, it will start a development server at http://localhost:1234 and automatically load our index.html for us.

The development server will also watch our files for any changes and automatically update the browser when any changes happen. This greatly speeds up the development process.

When we are done working on our code we have to run crt + C to stop our development server.

To get all of this working properly, we need to make an update to our package.json file.

"main": "index.html",
"scripts": {
"start": "parcel index.html",
"build": "parcel build index.html --public-url='./'"
},

Notice that we have switched our main source file from our index.js file to our index.html file. This will cause Parcel to start with our index.html file, look for any linked JavaScript or CSS files in that file and then bundle them and any of their imports.

We also see the addition of --public-url:'./' to our build command. The build command will duplicate our main index.html file and create a new index.html file in the dist folder. This extra command will ensure that all links still work.

Then we we are done with development we can run npm run build and ship everything in our dist folder to production.

An Introduction to Component Architecture

As a closing note here, I want to briefly introduce a way of organizing files your files called Component Architecture.

With this style of organization, each piece of user interface is broken down into the smallest piece possible. Then the code for that piece of the interface is stored in its own file.

So let's take for instance a Hero Section for a website. A Hero section may include a few pieces:

  1. The main wrapper

  2. A header

  3. A block of text

  4. A primary action button

  5. A possible secondary action button

We would then likely break this up into the following components:

├── /src
| |── /components
| | |── /hero
| | | └── index.js
| | |── /headline
| | | └── index.js
| | |── /text-block
| | | └── index.js
| | |── /button
| | | └── index.js
| └── index.js
├── index.html
└── package.json

The main hero/index.js file would contain our wrapper code and maybe overall styles for the hero. Then the headline/index.js would contain the header code. However, this file could also be used elsewhere throughout the site.

The same would be true for the text-blog/index.js. We could use this component in our hero, but also in other places throughout the site.

The two buttons we need would actually be one single component that could be re-used and customized with different styling for each use case of primary and secondary actions.

Here we see the primary goal of Component Architecture: make our code as easy to re-use as possible. We could even re-use some of these component in different projects.

Next Steps

Now that we have a basic understanding of how imports, exports, bundling tools and component architecture works, let's take some time to practice setting all of this up with some practice exercises.

This will make sure that we have all the skills we need to start building a larger application as our main project.