In this post, I want to explain how I created Ampjucks, a mix between AMP and Nunjucks. All the code is on my Github, ready to use, modify or whatever you want.
This project tries to solve one big problem: most of the AMP templates are a bunch of HTML files and nothing else. This is, obviously, useless because it is impossible to maintain a website like that. For example, what happens if I have to change the logo image. Do I have to change it in every single file of the template?
So a templating engine is perfect for this case. Also, I added some cool features that most of the projects need like, internationalization, Sass, live-reload changes, files minification, etc. All using Gulp.
Usage
Install everything needed and then run the server and watch for file changes in the 'src' folder. Go to localhost:8000.
npm i
npm run start
There are two more scripts, one to build the website and generate the dist folder (start script does the same), and the last one to validate all the HTML files with the AMP validator.
npm run build
npm run validate
Project files
This is a summary of the main files and folders:
├── gulpfile.js <-- Gulp tasks
├── package-lock.json
├── package.json
└── src
├── assets
├── styles <-- All scss files with mixins and functions
└── templates
├── i18n <-- Translations files
├── includes <-- Nunjucks includes like header and footer
├── pages
├── cart
└── index <-- Typical page structure
├── en
└── index.nunjucks <-- English config
├── es
└── index.nunjucks <-- Spanish config
├── index.content.nunjucks <-- Content file
└── index.scss <-- Index styles
└── layout.nunjucks <-- Main and most important layout
Gulp tasks
Here I'm going to explain the main tasks not all of them because some are self-explained.
renderTemplates
This renders all nunjucks templates and creates all HTML files in the dist folder. The files created follow this pattern '/lang/page.html'.
gulp.task('renderTemplates', function () {
return gulp.src(\['src/templates/pages/\*\*/!(\*.content)\*.+(html|nunjucks)'\],
{base: "src/templates"})
.pipe(nunjucksRender({
path: \['src'\],
envOptions: {
tags: {
variableStart: '{$',
variableEnd: '$}',
commentStart: '<&',
commentEnd: '&>'
}
}
}))
.pipe(rename(function (file) {
file.dirname = file.dirname.substring(file.dirname.lastIndexOf("/") + 1);
return file;
}))
.pipe(gulp.dest('dist'));
});
The purpose of the environment config is to avoid having conflicts with Mustache used in AMP in 'amp-list' component e.g.
sass
This task process all Sass files into CSS files and saves all of them in a temp folder inside 'src/styles'. The main layout file import these CSS files generated.
gulp.task('sass', function () {
return gulp.src('src/\*\*/\*.scss')
.pipe(sass.sync({outputStyle: 'compressed'}).on('error', sass.logError))
.pipe(rename({dirname: ''}))
.pipe(gulp.dest('src/styles/css/'));
});
sitemap
This creates the sitemap of the website.
gulp.task('sitemap', function () {
return gulp.src('dist/\*\*/\*.html', {
read: false })
.pipe(sitemap({
siteUrl: config.url + ':' + config.port
}))
.pipe(gulp.dest('dist'));
});
validate
To have AMP validated and a valid markup we use this task.
gulp.task('validate', function () {
return gulp.src('dist/\*\*/\*.html')
.pipe(gulpAmpValidator.validate())
.pipe(gulpAmpValidator.format())
.pipe(gulpAmpValidator.failAfterError());
});
Templates
Pages
Pages are the entry point and where the magic starts. Every page has its folder and contains one configuration file per language. Here I am going to explain the English configuration (pages/en/index.nunjucks).
{% set pageData = {
name: 'index.html',
lang: 'en',
title: 'Title of the page',
author: 'Ismael Ramos',
description: 'My description of the website',
keywords: 'some,keywords',
canonical: 'https://localhost:8000',
contentFile: 'templates/pages/index/index.content.nunjucks',
scripts: [
...
{
"src": "https://cdn.ampproject.org/v0/amp-bind-0.1.js",
"tags": {"attr": "custom-element", "val": "amp-bind"}
},
{
"src": "https://cdn.ampproject.org/v0.js"
}
],
css: [
"index"
],
structuredData: '{
"@context": "http://schema.org",
"@type": "WebSite",
"name": "This is the structured data config",
"alternateName": "",
"description": "",
"url": ""
}'
}
%}
{% extends "templates/i18n/" + pageData.lang + ".nunjucks" %}
The key contentFile is the path to the file that contains all the HTML code of the page (inserted in the main layout).
The key scripts is an array with all the script loaded in the page.
The key css is another array with the names of the CSS files that you want to load on the page. In this case, its name is index because we have a file named index.scss in the same folder.
Notice that we can use 'pageData' in every part of the templates. At the end of the configuration we always extend from the language files, which in turn, they extend from the main layout file.
layout.nunjucks
This is one of the most important files because it has the basic HMTL template and includes all the other templates, like header, footer, sidebar, all the CSS files, etc. Let's see some parts:
{%
set config = {
telephone: '+440000000000',
email: 'info@BS-LDN'
}
%}
{%
set urls = {
cart: {
es: 'cesta',
en: 'cart'
}
}
%}
Here we have two variables one with configuration parameters, like the phone and email of the website and other with the different URL names. Yes, we can localize the URLs, so when a user visits www.example.com/cart maybe the Spanish version could be www.example.com/cesta or www.example.com/es/cesta, It's up to you.
<head>
<meta charset="utf-8">
<title>{$ pageData.title $}</title>
...
<!--\*
\* JavaScripts to Include
\*\*-->
{%- for script in pageData.scripts -%}
{% set attrz = "" -%}
{% if script.tags.attr and script.tags.val -%}
{% set attrz = script.tags.attr + "=" + script.tags.val -%}
{%- endif %}
<script async {$ attrz $} src="{$ script.src $}"></script>
{%- endfor %}
i18n
This folder contains all the language files which are nunjucks variables, like this:
{% set texts =
{
products: 'Products',
...
}
%}
{% extends "templates/layout.nunjucks" %}
This way, we can have translations in a variable called 'texts' and use it on every page because it extends the main layout.
Includes
Here we have all the includes like header, sidebar, footer, favicons, etc. These files are included in the main layout. This allows us to not duplicate any code at all.
Styles
I decided to add some common functions and mixins with Sass and in general, all the project styles go in this folder. The gulp task will process these files like the other ones.
@function calculateRem($size) {
$remSize: $size / 16px;
@return #{$remSize}rem;
}
@mixin font-size($size) {
font-size: $size; //Fallback in px
font-size: calculateRem($size);
}
Demo
This project is deployed into Github Pages and you see it here This is how it looks like:
If you have any suggestions or questions please let me know on my GitHub repo.
I hope you’ve learned something new, and thanks for reading! If you've liked it, please share it!
More recent posts here! ismaelramos.dev#blog