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:
A basic 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:
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="main.js"></script>
</body>
</html>
main.css
h1 {
color: magenta;
}
main.js
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?
Importing a library
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?
Using a bundler
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
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.
Bringing it all together
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)
Wrapping up
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.
Top comments (25)
My main complaint about Parcel is the "regenerate runtime" error that pops up in the browser. Parcel doesn't use a configuration file, but this really means that configuration is scattered around in
babelrc
andbrowserslist
.But you did realize the solution? Either really install and import
regenerator-runtime
; or set browserslist higher.Yes, I changed browserslist - but it's mentioned nowhere in Parcel documentation.
It's a known bug (in GitHub issues), and very common too; but Parcel team seems to be too busy to fix it.
Would be interesting if you could elaborate what's the absolute minimum required CSS for a "working" website, such as CSS resets, typography, color scheme, responsive/grid, viewport settings etc.
I see so many people advocating "don't use a CSS framework, roll your own" but I'd like to see the required minimum CSS for a real world website demoed and explained somewhere.
Rolling your own "css framework" would be feasible if the product is fairly small or you have the required human resources to produce a maintainable, easy to use and well-documented stylesheet.
There is absololutely no harm in using a css framework and I personally don't think its even a remotely good idea to "roll your own" unless the aforementioned conditions have been met.
Maintaining css can be pretty annoying after all.
I agree with Mydrax that there is no reason not to use a good CSS framework nowadays.
For just minimal CSS for an MVP I'd use this bare minimum stylesheet:
github.com/andybrewer/mvp
Nice post! I've used Parcel since 2019 when a friend introduced me, and it's my favorite bundler. This is an awesome tutorial, however I would add two things. 1) Technically, JavaScript does provide a way to import and export code (in recent ES6), it's just not well supported. 2) Vercel is also a great place to host and even build Parcel sites in the cloud. Love your writing style as well! Have a great day 👋
Great addition! Funny enough after I wrote this I took a look at the new ES6 way of importing and exporting code as well as deployed an app with Vercel!
Thanks!
Why on earth would you use a bundler for a project of this size? Better still, just use native import features to pull in dependencies from a CDN like SkyPack (e.g.
import moment from 'https://cdn.skypack.dev/moment'
). If everyone loaded dependencies like this, the web could become much faster as many, many libs would be cached in browsersWow thats great. Some days back, I also made this small starter kit for static websites using Parcel. You can read about it here - dev.to/insanenaman/starter-kit-for...
That's great! I really like how your post was put together 👍
Glad you liked it.
Thanks a lot I was struggling with understand what and why a bundler is used in a project ..your post helped clear out some confusions. The project that i'm working on uses Webpack...do you think parcel works good for projects of medium to large codebase?
Pacharapol is spot on.
Parcel is faster and has some configuration (that I have not looked into deeply) but I have not tried it for my larger projects.
Webpack is not going away and is still great to use for any project of medium to large size. Mainly I would stick with anything that is working and try Parcel when you have a chance to experiment without breaking anything.
Parcel is supposedly faster than Webpack, but may sacrifice configurations.
And esbuild is even faster, but much less features.
moment
is now "legacy" because it lacks tree shaking. Don't use it.Yep in the new example code I went ahead and used Day.js. Great newer alternative.
I've used parcel in a few side projects and I love the fact it takes little to nothing to get started!
Ahh... Looks pretty nice though. My favorite Static-Site-Generator by using Markdown is 11ty. I would give it a try. Thanks.
Yes! Love Parcel. You can also use
import/export
keywords within your JavaScript source; Parcel will take care of these and transpile it down into something the browser can work with.Yea! So easy to use and love using the import syntax