You want to build a tiny website with just HTML, CSS, and JavaScript. You don't want to import a ton of libraries or use a framework that performs ultra enhanced low latency rendering under the hood. You want a website that has some styling, maybe makes a request to an API, and that you can build, manage, and deploy simply. So let's build that.
Here's a video version of this post if you'd rather watch us build the website:
First, let's set the foundation with technically the only 3 files you need to make up a website.
Let's pull up our command line and navigate to where we'd like to create our new project.
Then we'll create the directory:
mkdir awesome-site
cd awesome-site
Now we can create three files:
touch index.html
touch main.css
touch main.js
And let's fill in our files like so:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Example</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body>
<h1>Hello from HTML!</h1>
<script src="main.js"></script>
</body>
</html>
h1 {
color: magenta;
}
console.log('Hello from JavaScript!');
Now if we open index.html
we will see Hello from HTML!
in magenta and that's it, we have a website.
At this point, we have the bare minimum, but we want to continue to add features to our website. To help with that we want to use other developer's libraries.
So, how can we import a library that we can use in our website?
There are plenty of ways you can import a library. You can directly download a JavaScript file and add it the same way we are using main.js
, you can include the JavaScript file from a CDN in your HTML, and you can setup a package manager.
We're going to look at setting up a package manager called NPM (Node Package Manager) because this will automatically download the necessary files as well as help manage dependencies going forward.
Let's setup NPM in our repo
npm init -y
Running this command we are creating a package.json
file with default values.
Now we will install a package called moment.js a library that helps with date formatting.
npm install moment
If we look at our package.json
file now we will see that moment has been added to our dependencies
{
"name": "awesome-site",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"moment": "^2.29.0"
}
}
To use moment first we'll need to include the moment.min.js
file using a script tag in our index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Example</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body>
<h1>Hello from HTML!</h1>
<script src="node_modules/moment/min/moment.min.js"></script>
<script src="main.js"></script>
</body>
</html>
Notice that we are adding moment.min.js
above where we include main.js
. This way we will load moment before we use the library in main.js
.
Lets start using moment by changing main.js
const now = moment().format('MMMM Do YYYY, h:mm:ss a');
console.log(now); // September 30th 2020, 8:20:12 pm <- The time I am actually writing this
When you open index.html
moment will be loaded and the current time will be logged in the format defined above.
But wait, is there more we can do?
JavaScript does not provide a way to import code from one file to another. Right now when we want to import and use a library we have to include the JavaScript file from node_modules
with an exact path to the entry point file for the library inside our HTML. When we include the library in our HTML the JavaScript file is loaded into our HTML and will be defined as a global variable for files loaded after to use.
Not only is this inefficient but we'll also have a bug if we don't add our script tag in our HTML or if we have our tags in the incorrect order.
So what's the alternative?
We're using NPM right now which is the package manager for node.js. Node.js implements CommonJS modules which allow JavaScript to import and export code across files.
This is what our previous example looks like using node.js modules, Instead of including the moment library in our HTML with an HTML script tag we can load the library in our main.js
file:
const moment = require('moment');
const now = moment().format('MMMM Do YYYY, h:mm:ss a');
console.log(now);
This looks great but if we try to use this right now we will get this error:
Uncaught ReferenceError: require is not defined
The browser does not have access to the file system which means loading files is tricky.
To fix this error and be able to access the file system we need a module bundler. A JavaScript module bundler is a tool that will create an output of your files that is browser compatible. A module bundler will find all the require
statements and replace them with the context of each required file.
It's awesome but can be complicated. So let's use a tool that takes every complication out of the equation.
Enter Parcel.
Parcel is a web application bundler that is going to handle a bunch of things for us that previously we would have to set up ourselves.
Parcel will bundle all our JS, CSS, HTML, file assets, etc into a smaller set of files we can use to run our code. During the bundling Parcel will also transform our files so we can use the require
or even the import
syntax.
Parcel has other features you should check out too
Let's install Parcel in our project
npm install parcel-bundler --save-dev
This will add the parcel-builder
module as a dev dependency which is a module that is only required during development.
Now we'll add two scripts to our package.json
{
"name": "awesome-site",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"dev": "parcel index.html",
"build": "parcel build index.html --public-url ./"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"moment": "^2.29.0"
},
"devDependencies": {
"parcel-bundler": "^1.12.4"
}
}
The dev
script we added uses the parcel
command and an entry file. This will be used during development and starts a server for us to use with hot-reloading.
The build
script uses parcel build
which will build the website to a /dist
folder which is where our site will be served from when we deploy it.
Now that we have Parcel set up we can use the require
statement in our main.js
file.
Our main.js
file will look like this now:
const moment = require('moment');
// The newer import statement will also work
// import moment from 'moment'
const now = moment().format('MMMM Do YYYY, h:mm:ss a');
console.log(now);
And we can exclude the moment script tag from our HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Example</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body>
<h1>Hello from HTML!</h1>
<script src="main.js"></script>
</body>
</html>
We can now run our dev script npm run dev
and open http://localhost:1234 we will see our website and if we have the console open we will see the current time logged as well!
(Also try updating any of the HTML, CSS, or JS and you'll see that the website will reload automatically)
Our small website is all set up and ready for us to host the code on our service of choice (GitHub, GitLab, Bitbucket, Etc) and to deploy the site to the world.
Take this starting point and use it as a testing ground for your next project. Some other interesting extensions to this project would be to add PostCSS, use Sass, as well as add various ways to deploy.