Skip to content
On this page

Template structure

This section is about the file structure. You will learn how the different files are organized, but most important of all, how to manage your project with Gulp. Let's dive into the folder structure.

Global structure

sh
root
├── src/
   ├── img/
   ├── js/
      ├── libs/
      ├── main.js
   ├── layouts/
   ├── pages/
   └── partials/
   └── scss/
├── bulma-css-vars.config.js
├── gulpfile.js
└── package.json

The directory structure can seem unusual for someone who is not used to build tools. We will break each part of it, so you understand how everything works.

Assets

sh
root
├── src/
   ├── img/
   ├── js/
      ├── libs/
      └── main.js
   ├── scss/
      ├── abstracts/
      ├── base/
      ├── bulma-generated/
      ├── components/
      ├── css-variables/
      ├── forms/
      ├── layout/
      ├── utilities/
      └── main-rtl.scss
      └── main.scss

The main reason we are working with yarn (or npm) and gulp is that we want to keep our development process clean, as well as our dependencies management. This kind of setup highly increases the project's code maintainability and scalability. Always prefer the yarn (or npm) method when adding new assets. Project assets are all the files needed for the project to run properly, like CSS files, images and javascript files.

Layouts folder

sh
root
├── src/
   └── layouts/

All the html layouts resides in this folder. For a more concise code, this project uses a flat file compiler named Panini, by Zurb. Panini makes it easy to break your layouts into several reusable components, preventing you to go through every page to make your changes. You can find more information about zurb/panini by visiting the official repository. The project follows the panini pattern for the html file structure.

The layouts folder holds all the template layouts. It is mandatory to have at least one layout, and it should always be named default.html. Additional layouts can have the names you want, but don't forget the .html extension. A layout acts as container providing the same base for a set of similar pages. Finally notice the {{> body}} call that is present in each layout file. This is a reference to append the rest of the page content.

Layout files

The layouts/ folder generally holds one layout file, but there are cases where there can be more than one:

  • default.html : The default layout generally used by all the project pages.

Pages folder

sh
root
├── src/
   └── pages/

The pages folder holds all the template pages. Pages are focused on content. They are related to a layout and automatically appended to it by panini when the page is served. You will always find the same statement at the top of each page's code :

yaml
---
layout: default
title: This is the page title
---

The layout statement tells panini wich layout to use to serve the page. The title element is a string that gets inserted as the page title when panini has finished assembling the page parts together. Now that we had a look to panini's basic features let's dive into the other html files :

Page files

All html pages live in this folder. Each one of this pages is linked ot one of the existing layouts and makes use of partials living in the src/partials/ folder.

Partials folder

sh
root
├── src/
   └── partials/

The partials folder holds all your html partials. Partials are chunks of code that you want to reuse as is accross your application : it can be a button, a navbar, a content section or whatever you want. Note that you can create as many subfolders as you want to organize your partials. You simply have to make sure that your partial names are unique. Partials are named like html files : navbar.html. When you want to call a partial in one of your layouts or pages use the following expression : {{> partial-name}}. You don't have to mention the path, even if it is nested in several subfolders, panini will find it. Also note that you don't have to add the html extension in your partial call.

Partial files

sh
root
├── src/
   ├── partials/
      ├── partial1.html/
      ├── partial2.html/
      └── partial3.html/

Images

root
├── src/
│   └── img/

All the project images live in this folder. It can have as many subfolders as you want. When you build the projects, all these images are automatically transfered to the right location by Gulp.

Js

root
├── src/
│   ├── js/
│   │   ├── libs/
│   │   └── main.js

All javascript files live in the js folder. We dropped jQuery support quite some time ago and are now using the ES6 syntax to handle javascript functions. You should be familiar with the bare bones of ES6 when working with this template. Dropping jQuery doesn't mean we didn't replace it with something else. In fact we did, we are now using Alpine JS, a powerful dependency free javascript declarative framework, similar to Vue, but with less complexity. You can learn the basics of Alpine JS by reading the documentation. Alpine works very well in a simple setup where you declare your scripts in the same HTML page. However, since we are not importing the library from a CDN, but rather from node modules, we use a little more complex but significantelly more solid setup for alpine inside an ES 6 compatible environment. In the following example, we instantiate Alpine and load some additional plugins. The Intersect plugin makes it easy to interact with DOM elements when they go in / out of the viewport. The Fern plugin builds upon Alpine native stores and make them session persistent.

javascript
//Alpine JS and plugins import
import Alpine from 'alpinejs'
import intersect from '@alpinejs/intersect'
import Fern from '@ryangjchandler/fern'
window.Alpine = Alpine

//Init intersect plugin
Alpine.plugin(intersect)

//Init Fern plugin
Alpine.plugin(Fern)

//Init Fern persisted store
Alpine.persistedStore('app', {
    isLoggedIn: false,
})

//Start Alpine JS
Alpine.start()

Like we said earlier, It's a little bit more complex in this project since we are importing functions from separate JS files. The only thing you need to now when importing an Alpine JS function in your main.js file is that you need to add it to the window object for it to work properly. Otherwise, you'll get undefined. Here is a practical example:

javascript
//Import the function you need from the target file
import { initLandingNavbar } from './navbar/navbar-landing';

//Bind it to the window object to access it from anywhere
window.initLandingNavbar = initLandingNavbar;

This can quickly become very verbose in your main file. Therefore, we decided to make those imports in nested index.js barrel files. An example is src/js/libs/components/index.js. In this file, every component js file is imported and bound to the global window object. Here is the sample code from that file:

javascript
//Global imports
import { initLandingNavbar } from './navbar/navbar-landing';
import { initNavbar, initNavbarLight } from './navbar/navbar';
import { initNavbarMobile } from './navbar/navbar-mobile';
import { initNavbarBottom } from './navbar/navbar-bottom';
import { initBackToTop } from './backtotop/backtotop';

//Accordions imports
import { initAccordion } from './accordion/accordion';

//Global bind to window object
window.initLandingNavbar = initLandingNavbar;
window.initNavbar = initNavbar;
window.initNavbarLight = initNavbarLight;
window.initNavbarMobile = initNavbarMobile;
window.initNavbarBottom = initNavbarBottom;
window.initBackToTop = initBackToTop;

//Accordions bind to window object
window.initAccordion = initAccordion;

Then, inside the main.js you can easily re-import barrel files to have all your functions loaded in:

javascript
import './libs/components'
import './libs/forms'
import './libs/sections'

You know know everything about how to structure your Javascript files in this project.

Scss

root
├── src/
│   ├── scss/
│   │   ├── abstracts/
│   │   ├── base/
│   │   ├── bulma-generated/
│   │   ├── components/
│   │   ├── css-variables/
│   │   ├── forms/
│   │   ├── layout/
│   │   ├── utilities/
│   │   └── main-rtl.scss
│   │   └── main.scss

This template relies on the powerful Sass features, letting you handle complex styles in a breeze. The project relies on a modular scss structure. You need to import all the SCSS partials into your main SCSS stylesheet.

File Types

There are two main types of SCSS files : Core and Partials.

Partial files

Partial SCSS file names always start with an underscore like this: _variables.scss . They act as chunks of code that are imported into a core SCSS file. Not only this gives you great control over your code, but it also greatly enhances it's maintainability.

Core files

The main.scss and main-rtl.scss files centralize all the project styles. Every time you make a change in a partial file, it impacts the outputed dist/css/main.css (and dist/css/main-rtl.css) file resulting from the compilation process (wich is handled by Gulp, as we saw above). The main SCSS file starts by importing all the needed SCSS partials. Here is an example statement:

scss
//CSS vars setup
@import "./bulma-generated/generated-vars.sass";
@import "./css-variables/all";

//Additional variables
@import './abstracts/ltr';
@import './abstracts/variables';
@import './abstracts/mixins';

//Bulma
@import "../../node_modules/bulma-css-vars/bulma-cv-lib";

//Utilities
@import "utilities/all";

//Base
@import "base/all";

//Forms
@import "forms/all";

//Layout
@import "layout/all";

//Components
@import "components/all";

Notice how partials are imported. Whereas the actual partial file name starts with an underscore and ends with a .scss extension, when you write imports inside your main.scss file, you have to remove the undescore and the .scss extension.

Root files

There also a few files sitting at the root of the project that we need to discuss before getting into serious business:

  • gulpfile.js : Gulp will use this file to perform all the tasks it is supposed to accomplish.
  • package.json : Lists all your project's dependencies and gives useful metadata.
  • package-lock.json : Automatically generated for any operations where npm modifies either the node_modules tree, or package.json. It describes the exact tree that was generated, such that subsequent installs are able to generate identical trees, regardless of intermediate dependency updates.

All Rights Reserved