DEV Community

Cover image for Chapter 1: setup, CSS, version control and SASS
Ross Angus
Ross Angus

Posted on • Edited on

Chapter 1: setup, CSS, version control and SASS

Cover image by Rodolfo QuirΓ³s

Recap

Last time, we introduced you to a whole bunch of different software you'll need to get started with Node. This gang:

Imagine these are your new work colleagues. Some of them we'll be spending a lot of time with. Perhaps eating lunch together, maybe going out for drinks after work. Others we just needed briefly and can casually discard like yesterday's socks. Some will make awkward eye-contact with us, but we'll ignore them completely.

Today, let's get to know GitHub a little better and get our new project set up.

Create new GitHub repo

(these instructions assume you have both a GitHub account and have downloaded and installed Github Desktop)

Open Github Desktop and from the File menu, select New repository. The Name field will be the name of the folder you'll create, so although it's a little counter-intuitive, you should create the repository in the root of your GitHub folder. For example, mine is here:

C:\Users\Ross-Angus\Documents\GitHub
Enter fullscreen mode Exit fullscreen mode

Think up a name for your test project. Personally, I'd keep your name lower-case and without spaces (use hyphens instead, if you like). But perhaps this isn't relevant any more and I've just got into the habit. While it's possible to change the name later, it's the sort of thing where the old name might keep cropping up and annoy you, so choose well.

I've called my version node-js-for-developers. At the end of each chapter, I'll include a link to a snapshot of my code so you can compare it to your own, in case you run into problems.

Open your new folder

From your IDE, browse to your new folder and open it. The only file inside should be a .gitattributes file.

This is a configuration file for GIT. We won't be editing this. Don't open it. I forbid it!

Allow Node to move in

Open a terminal in VS Code (from the View menu, select Terminal) and type:

npm init
Enter fullscreen mode Exit fullscreen mode

The terminal will ask you a series of questions. If you feel inclined, pretend you're in the film Wargames. If you press enter, rather than answering the questions, the default answer will be chosen. Most of the defaults are sensible and don't worry - we can always change them later.

Let's break down this npm init command. We've already talked about package managers like winget but npm is a package manager we're going to use more than once. npm is also used to run commands in Node. Which is what we're doing now.

By typing npm, we're switching the terminal to npm mode, where it has access to other methods.

Calling init is one of these methods. init is short for "initiation". It sets up the current directory as the root of a new Node application. That boils down to creating a new file called package.json. We're going to spend a lot of time editing package.json. In many ways, it's the heart of the application.

Open package.json in VS Code. This is the sort of boilerplate for the repo. Or perhaps like a library card for a book, if you remember the last century. Note how your default answers to the quiz Node just made you take appear inside package.json. This is where you can change them.

This is what mine looks like currently:

{
  "name": "node-js-for-developers",
  "version": "1.0.0",
  "description": "A course on Node.js for front end developers",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Ross Angus",
  "license": "ISC"
}
Enter fullscreen mode Exit fullscreen mode

Let's go through each of these, although NPM has a more detailed article.

  • name - this is the name of the directory for your project. Think of it as the short, computer-friendly version of your project name.
  • version - we'll talk about version control at the end of this lesson but this allows you to give an arbitrary number to the version. Is it bad to admit I tend to ignore this field?
  • description - this text appears when users search for your package on npm. Think of it like a summary of your project.
  • main - this is the entry point of our application. During this course, we're going to install a lot of packages which will help us to perform certain tasks. These packages will live in a folder called node_modules. Each package is a project in its own right and has its own dependences (yes, sometimes there's another node_modules directory inside the package's folder inside node_modules. How deep does the rabbit hole go? No-one knows). When Node is deciding how to load these packages, it needs an entry point to the package. That's what this node is for. Hey - guess what main defaults to, if you don't declare it? That's right - index.js. So this is useless and can be deleted. Thanks for coming to my TED talk.
  • scripts - we're going to spend a lot of time inside this particular node of package.json. scripts lets us give nicknames to long, complicated commands we'd usually run within the terminal. We can add as many as we want (and I'm going to add plenty). More on this later.
  • author - your name should be here. Next chance you get, change your profession on your passport to "Author" and refuse to elaborate.
  • license - this is the software licence which you're assigning to your code. It defaults to ISC which I'm going to paraphrase as "you can use this but if you die doing so, it's not my fault".

JSON files

package.json is written in json format, or JavaScript Object Notation to it's mum. It's a data format which should be familiar if you've worked with JavaScript objects. However, where it differs from JavaScript objects is it's a little more strict. In JavaScript, you could create an object like this:

const myObject = {
  firstName: "Joseph",
  surName: "Merrick",
  born: "1862-08-05",
  died: "1890-04-11",
  catchphrase: "I am not an object, I am a human being!",
  human: true,
};
Enter fullscreen mode Exit fullscreen mode

If we expressed this data as JSON, we would need to place all the name properties in quotes like this:

{
  "firstName": "Joseph",
  "surName": "Merrick",
  "born": "1862-08-05",
  "died": "1890-04-11",
  "catchphrase": "I am not an object, I am a human being!",
  "human": true
}
Enter fullscreen mode Exit fullscreen mode

We also couldn't have a comma on the last name/value pair, so instead of "human": true, we have "human": true.

Here's a more complicated example, with more data-types. Look at what happens to the final node within nested objects:

{
  "text-example": "This is a text example",
  "array-example": ["This value is an Array", 4, 3, 2, 1, 0],
  "object-example": {
    "nested-text": "This example contains json inside your json",
    "boolean-example": true,
    "nested-integer": 1000,
    "nested-array": [true, true, true, true],
    "last-node": "As this is the last node inside the nested object, it has no comma to the right ->"
  },
  "final-node": "No comma to the right->"
}
Enter fullscreen mode Exit fullscreen mode

Our old pal VS Code will probably throw up some wiggly red lines if we do it wrong, so look out for them. If you're struggling to see why it doesn't work, try an online JSON Validator and see if it pushes you in the right direction.

With that done, let's start working on the CSS of our site.

Handling CSS

Legacy CSS

In some legacy sites you might work on during your career, you might encounter CSS files which are thousands of lines long. You probably won't have time to refactor this code (most likely you're just adding a quick fix) but when projects reach this level of complexity, the best thing we can do is to nuke them from orbit and rebuild from scratch.

How can we do this better from the start?

If we split out CSS into different files where each one is concerned with a different part of our application then in theory, when we remove a component, we can remove its CSS at the same time and not impact any other part of the site.

Of course, we could do this with existing technology by simply importing many different CSS files into each page, like this:

<!doctype html>
<html lang="en-GB">
  <head>
    <title>Lots of CSS files</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/css/fonts.css">
    <link rel="stylesheet" href="/css/grid.css">
    <link rel="stylesheet" href="/css/header.css">
    <link rel="stylesheet" href="/css/footer.css">
    <link rel="stylesheet" href="/css/typefaces.css">
    <link rel="stylesheet" href="/css/heading.css">
    <link rel="stylesheet" href="/css/bodytext.css">
    <link rel="stylesheet" href="/css/call-to-action.css">
  </head>
  <body>
    ...
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

This would work! But here's the downsides:

  • Unless the site is served using HTTP2, each of those CSS files adds a little bit of overhead to the browser, as it needs to handshake a new connection each time to the server
  • Each of those files has lots of whitespace which helps us, the developers, see what's going on but is irrelevant to the browser. Why force our users to download whitespace?
  • Each CSS file requires a new link tag to be put into the relevant pages. If we do remove a CSS file, all those pages need to be updated.

In addition to this, we might want to use some of the power of SASS on our site.


What's SASS?
SASS is an extension of CSS which allows us to do stuff which wasn't originally build into CSS. It's been so influential that CSS has copied some of its homework.

One of its many features is that it lets us nest rules inside each other which has some advantages:

  • It lets us specify and class at the top level and the nested rule automatically inherits that classname
  • If we need to change a class name, this can be done at the root just once
  • It can be used to reflect the structure of the HTML, meaning there's a closer relationship between the HTML and CSS

However, it's also possible to accidentally generate a lot of CSS with relatively simple SCSS rules, so it should be used with care.



So a good solution to this problem looks like this:

  • We can create as many CSS (or SCSS) files as we like, in a nested directory structure if we want
  • When we remove an SCSS file, the final CSS file will reflect this
  • We can use comments in these files which won't reach the live site (this only works in SCSS, when we use comments which start with //)
  • All this SCSS is converted into a single CSS file
  • The CSS file has the minimum amount of whitespace in it

Splitting up our CSS

The first problem we want to solve is to have our CSS split into different parts which do different jobs. There's lots of different theories about the best way to do this but I'm fond of a mixture of Atomic Design for global styles and BEM for styles which only exist on one component.

That's quite a lot of homework I've just given you! What's more important than learning a methodology and enforcing it rigidly is adapting to how things are done in your current workplace.

For example, if we order our CSS rules any old way, it's difficult to spot when a declaration has been repeated, especially when the rule gets long:

.logo {
  position: absolute;
  top: 0;
  left: 0;
  border: none;
  opacity: 0.5;
  width: 200px;
  height: 150px;
  z-index: 1;
  border: none;
}
Enter fullscreen mode Exit fullscreen mode

(Fun quiz: find the repeated declaration in the above rule. Now do this with any legacy CSS you encounter for the rest of your career)

I got used to ordering my declarations alphabetically, to ensure this repetition didn't happen. But then I worked on a project which enforced the Airbnb CSS / Sass Styleguide, which had a different take. And I couldn't even check code in which didn't match their method.

Try and keep an open mind about these things, because they're liable to change. And if you're in a position to change things, do so with compassion.

Make some sub-folders

With all this in mind, let's set up some directories for the files we're going to be creating. Our application will have two parts:

  1. The source folder
  2. The distribution folder

We're going to do all our edits on files within the source folder, then packages we're going to install in a bit will automatically copy these over to the distribution folder in a format which is web-ready. For example, an SCSS rule such as this:

p {
  button {
    border: none;
    padding: 0;
  }
}
Enter fullscreen mode Exit fullscreen mode

... will end up as the following CSS:

p button{border:none;padding:0}
Enter fullscreen mode Exit fullscreen mode

This kind of file is really hard to read, but downloads slightly faster.

However, we're not going to use the words "source" and "distribution", we're going to use their street names: src and dist. Your application directory should look like this:

πŸ—€ dist
  πŸ—€ css
πŸ—€ src
  πŸ—€ scss
.gitattributes
package.json
Enter fullscreen mode Exit fullscreen mode

Just empty folders are fine for now.

Create a landing page

A good place to start is the HTML5 boilerplate project. We just need a simple HTML page for now. Like this one:

<!doctype html>
<html class="no-js" lang="en-GB">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Hello Worm</title>
</head>

<body>
  <p>Hello Worm</p>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

Create a new file inside the dist folder by right-clicking on it and selecting New File from the pop-up menu. Call the file index.html and then open it in VS Code. Paste the HTML markup above into it and save it.

Install our first package: Sass

Right, we've had a peek at SCSS. Let's install it!

Installing

In the terminal, type:

npm install --save-dev sass
Enter fullscreen mode Exit fullscreen mode

Let's break down what each part of this command does:

  • npm - this invokes Node Package Manager. Think of this as switching the terminal to NPM mode (but just for the length of time it takes the command to execute).
  • install - this calls the install method, which is built in to NPM. It's a bit like calling a function, but this function lives within the NPM world.
  • --save-dev - this is a "flag" (flags start with a hyphen). NPM gives us access to many JavaScript libraries, some of which are useful just while we're developing, and some of which are useful on our actual live site. This particular package (sass) is only useful while we're developing. None of the code which helps us convert scss to css should end up on the live site. So this flag ensures that remains the case. We can see this the effect of this on package.json in a moment.
  • sass - this is the name of the package on the NPM database.

What's changed?

Let's look at package.json once more - the new node looks like this:

"devDependencies": {
  "sass": "^1.83.4"
}
Enter fullscreen mode Exit fullscreen mode

As we've used the --save-dev flag, the package is inside a node called devDependencies. This means that the code which Sass uses won't end up on the live site.

That number (1.83.4, in my example) represents what version number of the package is installed. Note that packages age like milk - they can and will go out of date, develop security holes, fall out of development, get replaced by different packages. It's basically a nightmare.

The caret (^) symbol (yes, I had to look that word up) means "this version of sass is supported, along with all future versions".

What we've done with the install is to dump a bunch of code into a new folder called node_modules. You can think of this folder as "here be dragons". You should not edit any code inside here or check this folder into your repo. All of this code is pulled down remotely from NPM and when another developer pulls down your code, they will pull down the contents of node_modules afresh from NPM, rather than getting it from your repo.

This means your repo remains relatively small, but node_modules can get big. Like, really big. It's scary how big it gets. Try not to think about it too much, or you'll start to cry (speaking from experience).

Hide node_modules and dist from GIT

Because of what I've just said, we really don't want to check node_modules into GIT. And by "really" I mean "I forgot about this bit, the first time I tried this".

Here's what you need to do: create a new file in the root of your project called .gitignore. Here's what should be inside it:

node_modules
dist
Enter fullscreen mode Exit fullscreen mode

.gitignore is another configuration file, like package.json or (don't look at it!) gitattributes. It (and stop me if I'm going too fast here) contains a list of files and directories which GIT should ignore.

That's it. GIT will now ignore any files inside the node_modules or dist directories. You can add other files and paths in this file in the future, should you need to. You might notice that your new dist folder has turned dark grey in VS Code. This indicates that you've done everything right and the contents of dist are being ignored.

Wait, why are we ignoring dist?

The important files in the repo are the source files. All files in the dist directory will (eventually) be derived purely from the source.

If a different developer downloaded this repo, they'd need to install it (basically download their own version of node_modules), then run it, in order to generate the contents of the dist folder.

Don't worry: we'll cover all this again. Unless I forget.

File setup

SASS takes scss files and transforms them into CSS files. Specifically, one CSS file.

We're going to use it to split our CSS up into lots of different files, then squash them all together into an unreadable mess (just like I showed earlier), but just for the live site. Let's do some setup!

Inside the src/scss folder, create two files:

  • main.scss
  • _fonts.scss

_fonts.scss should look like this:

$font-system: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;

body {
  font-family: $font-system;
}
Enter fullscreen mode Exit fullscreen mode

This is a very simple Sass file which assigns a long string (the list of local typefaces) to a variable ($font-system) and them applies it to the body tag. Variables are native to CSS now, but let's pretend that isn't true.

main.scss should look like this:

@use '_fonts';
Enter fullscreen mode Exit fullscreen mode

This imports the whole of the _fonts.scss file into the main.scss file. Note that any @use rules you add to your Sass files should appear before any actual CSS code. But that won't be an issue with us, as we probably won't be mixing the two in the same file.

Note the underscore as well. We'll come back to that later. (My editor informs me that foreshadowing helps to develop narrative tension. Is this working? Are you feeling tense?)

Configuring

Using our new sass package in node_modules, we can convert scss files into css files, but our site doesn't know how to yet. Let's configure package.json so it knows how to do this.

The scripts part of package.json currently looks something like this:

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1"
}
Enter fullscreen mode Exit fullscreen mode

We need to change it, so it looks like this:

"scripts": {
  "sass-dev": "sass --watch --update --style=expanded src/scss:dist/css",
  "sass-prod": "sass --no-source-map --style=compressed src/scss:dist/css"
}
Enter fullscreen mode Exit fullscreen mode

I've set a little trap for you here - your scripts node in package.json won't be the final set of nodes in your file. That means that the closing brace (}) should have a comma after it. Hopefully the angry red wiggles inside VS Code should indicate where the error is.

What does this mean?

Each new node in the scripts object represents a command we can run from the command line. The name part (for example sass-dev or sass-prod) is a little like a variable in JavaScript - it's a shorthand we can use to refer to the value part. The value part (for example sass --watch --update --style=expanded src/scss:dist/css) is a whole command line which runs when we type out the first bit.

Let's break down exactly what the sass-dev command does:

  • sass - calls the sass executable
  • --watch - this is the "watch" flag. This means that the sass executable will watch a bunch of directories and files for any changes, then do something. This kind of behaviour is possible in Node, but not in native JavaScript.
  • --update - this checks the change date of the SCSS file against the CSS file it's going to create. If the SCSS is younger, then it overwrites the CSS file
  • --style=expanded - this is a flag (style) with an argument (expanded) passed to it. This defines what kind of CSS will be generated. An expanded style will use what is called a source map which means even though the browser is parsing CSS, when we inspect an element, we'll see the source SCSS instead! This will be really helpful for debugging.
  • src/scss:dist/css - these are two paths, separated by a colon. Translated into English, this means "look inside the src/scss directory for your source files, then output them to the dist/css directory".

The underscore

Remember how the scss source had two files, main.scss and _fonts.scss? Here's the clever bit: Sass will ignore all scss files which start with an underscore. It will take the remaining files and replicate them in the target directory (in our case, dist/css). But because we've pulled the _fonts.scss file into main.scss using @use, it gets added in anyway.

Testing it out

We've defined two custom commands. Let's try the sass-prod one out. In a terminal, type:

npm run sass-prod
Enter fullscreen mode Exit fullscreen mode

Quick reminder: this switches the terminal to Node Package Manager mode, calls the run function, then executes the sass-prod. npm will look inside package.json for a matching name inside the scripts node and attempt to run it.

This is invaluable when you're downloading a new project and you don't know how to make it start up. Check inside package.json to see what it does (I mean, the documentation should tell you, but it might not).

Now open up the new file which has been created inside the dist/css directory. It should be our CSS, but all squashed up. This is the command we need to run when we've finished development and we want to generate the production-ready CSS which lives on the live site. It won't be generated with source maps and all unnecessary white space will be removed.


Source maps

Source maps are a way of telling the web browser exactly where a particular bit of code is coming from. Let's say we have a style sheet called call-to-action.scss which is being compiled into a CSS file called main.css. Under normal circumstances, when we inspected an element using the developer tools, it would tell us the CSS was coming from main.css. And if we're removing all of the white space from our CSS, it will also helpfully report that the CSS appears on line one (line one is 20,000 characters long).

We can use source maps during development (and only during development) to allow the browser to point us in the right direction. They're really cool and you'll quickly take them for granted.


Let's link our new CSS file into our two example pages. Inside the head tag, add:

<link rel="stylesheet" href="/css/main.css">
Enter fullscreen mode Exit fullscreen mode

Saving and versioning our work

Hopefully you've been saving your work as you go along (most stuff simply won't work if you don't). But now we've made a bit of progress, it's time to version our work. This takes a kind of snapshot of what we've done, so if we need to, we can return to it later.

Pointing GitHub Desktop at your files

We do this in GitHub Desktop. Open it and from the File menu, select the option Add local repository... Browse your computer for the right directory (it's probably in Documents > GitHub and whatever name you gave it) click the button Select Folder then on the next window, the Add repository button.

GitHub Desktop anatomy

The GitHub Desktop window has three parts to it. On the left is a list of all the changed files. These could be new files you've added or old files which have changed since you last checked them in. If you select any of these files, you can see the code changes on the right hand side.

This right hand side panel has two parts. If the file is already in the repo (none of our files are like this) then the left hand side will show what the file looked like before you changed it. Anything you've changed or deleted will be in scary red. On the right hand side is the same file with the new parts highlighted in green. All of our files are new, so they will all consist just of the green parts.

Finally, at the bottom right is a window for adding notes to your check-in. This has your avatar against it, to remind you that everyone will know what you typed and will judge you for it.

Commit messages, a political history

Developers take writing a good commit message very seriously and have Opinionsβ„’ about it. I wouldn't be surprised if at least one land war in Asia was triggered by a bad commit message.

Perhaps the take-away here is that a commit message isn't necessarily a summary of what you've changed - because developers can work that out from the files themselves. But it's perhaps better to say why you changed it.

For example, let's say you've been assigned a support ticket for an existing website. This ticket will have an ID and this is crucial to note in the message, as it will give anyone following you a breakdown of the whole job. But just in case the ticket is inaccessible, try and summarise why the change was made in a short message after the ID.

But before you get too attached to this methodology, remember that different work places will have different ways of doing this. One attempt to standardise the practice is Conventional Commits. Try and stay flexible and follow the best practice where you work.

Once you've completed all of these fields to your satisfaction, click on the Commit to main button.

Hang on - what's main?

A full understanding of version control, branching and merging is a little outside the scope of this course but one of the most powerful aspects of Git is that you can take a copy of the codebase off on a little detour, while the rest of your colleagues work on it in isolation. Then, once you've finished your work and someone has checked it over, it can be merged into the main trunk of the repository.

What we're doing here is considered beyond reckless - working on and checking into main directly. In the so-called real world, you'll be working on a branch of a branch of a branch. Someone much more important than a developer will merge your work into the main branch.

But you are (and you should feel a warm glow of satisfaction any moment now) the most important person in this project. You work on the main branch and live your best life.

Local and remote

Just one more small point to make about Git: you've successfully checked in your code now, but it still just lives on your local machine. This is fine, but not so good if you want to collaborate with other developers. In order for that to work you need to push your code to GitHub itself.

If you do that (and you should - aside from everything else, it's a useful backup), you can choose to make it public or private. So you shouldn't just save and commit your work, you also need to push it to GitHub.

Summary

We've done a lot in this lesson! We've:

  • created a new GitHub repo
  • initiated the repo
  • been introduced to JSON
  • learned how and why to split up CSS
  • added some directories and a landing page
  • installed our first package: SASS
  • hidden files from version control
  • written some SCSS files
  • written two package.json commands
  • generated our final CSS
  • checked our changes into GitHub

That's a lot. While we've only seen the output CSS inside VS Code, in the next chapter, we'll install another package which will let us see the results in the browser, just as it would appear on a server.

View Chapter 1 code snapshot on GitHub

Quiz

Top comments (0)