|
|
@ -8,17 +8,17 @@ |
|
|
|
#+OPTIONS: auto-id:t |
|
|
|
#+STARTUP: showeverything |
|
|
|
|
|
|
|
This article will dig a bit deeper about how I generate my static website. |
|
|
|
This article will dig a bit deeper about my =Makefile= based static website generator. |
|
|
|
In a [[https://her.esy.fun/posts/0017-static-blog-builder/index.html][previous article]] I just gave the rationale and an overview to do it |
|
|
|
yourself. |
|
|
|
Mainly it is very fast and portable. |
|
|
|
|
|
|
|
A few goal reached by my current build system are: |
|
|
|
A few goals reached by my current build system are: |
|
|
|
|
|
|
|
1. Be fast, try to make as few work as possible. |
|
|
|
1. Be fast and make the minimal amount of work as possible. |
|
|
|
I don't want to rebuild all the html pages if I only change one file. |
|
|
|
2. Source file format agnostic. You can use markdown, org-mode or even |
|
|
|
directly writing html. |
|
|
|
directly write html. |
|
|
|
3. Support gemini |
|
|
|
4. Optimize size: minify HTML, CSS, images |
|
|
|
5. Generate an index page listing the posts |
|
|
@ -38,7 +38,7 @@ To make those transformations I use very short a shell scripts. |
|
|
|
:CUSTOM_ID: -makefile--overview |
|
|
|
:END: |
|
|
|
|
|
|
|
A Makefile is constitued of rules. |
|
|
|
A Makefile is made out of rules. |
|
|
|
The first rule of your Makefile will be the default rule. |
|
|
|
The first rule of my Makefile is called =all=. |
|
|
|
|
|
|
@ -50,15 +50,14 @@ target: file1 file2 |
|
|
|
--output target |
|
|
|
#+end_src |
|
|
|
|
|
|
|
if =target= does not exists, then =make= will look at its |
|
|
|
dependencies. |
|
|
|
If any of its dependency need to be updated, it will run all the rules in |
|
|
|
the correct order to rebuild them, and finally run the script to build |
|
|
|
if =target= does not exists, then =make= will look at its dependencies. |
|
|
|
If any of its dependencies need to be updated, it will run all the rules in |
|
|
|
the correct order to rebuild them and finally run the script to build |
|
|
|
=target=. |
|
|
|
A file need to be updated if one of its dependency need to be updated or is |
|
|
|
A file needs to be updated if one of its dependency needs to be updated or is |
|
|
|
newer. |
|
|
|
|
|
|
|
The ususal case of =make= is about building a single binary out of many |
|
|
|
The usual use case of =make= is about building a single binary out of many |
|
|
|
source files. |
|
|
|
But for a static website, we need to generate a lot of files from a lot of |
|
|
|
files. |
|
|
@ -105,7 +104,7 @@ I have a block for: |
|
|
|
The rules to copy assets will be a good first example. |
|
|
|
|
|
|
|
1. find all assets in =src/= directory |
|
|
|
2. generate all assets from these file in =_site/= directory |
|
|
|
2. generate all assets from these files in =_site/= directory |
|
|
|
3. make this rule a dependency on the =all= rule. |
|
|
|
|
|
|
|
|
|
|
@ -123,44 +122,44 @@ ALL += assets |
|
|
|
OK, this looks terrible. |
|
|
|
But mainly: |
|
|
|
|
|
|
|
- ~SRC_ASSETS~ will contains the result of the command ~find~. |
|
|
|
- ~DST_ASSETS~ will contains the files of ~SRC_ASSETS~ but we replace the |
|
|
|
- ~SRC_ASSETS~ will contain the result of the command ~find~. |
|
|
|
- ~DST_ASSETS~ will contain the files of ~SRC_ASSETS~ but we replace the |
|
|
|
=src/= by =_site/=. |
|
|
|
- We create a generic rule; for all files matching the following pattern |
|
|
|
=_site/%=, look for the file =src/%= and if it is newer (in our case) |
|
|
|
then execute the following commmands: |
|
|
|
then execute the following commands: |
|
|
|
- create the directory to put =_site/%= in |
|
|
|
- copy the file |
|
|
|
|
|
|
|
About the line ~@mkdir -p "$(dir $@)"~: |
|
|
|
- the =@= at the start of the command simply means that we make this execution silent. |
|
|
|
- The =$@= is replaced by the target string. |
|
|
|
- And =$(dir $@)= will generate the dirname of =$@=. |
|
|
|
- And =$(dir $@)= will generate the folder name of =$@=. |
|
|
|
|
|
|
|
For the line with ~cp~ you just need to know that =$<= will represent the |
|
|
|
For the line with ~cp~, you just need to know that =~$<~= will represent the |
|
|
|
first dependency. |
|
|
|
|
|
|
|
So my Makefile is composed of similar blocks, where I replace the first |
|
|
|
find command to match specific files and where I use different building rule. |
|
|
|
An important point, is that the rule must be the most specific possible |
|
|
|
because make will use the most specific rule in case of ambiguity. |
|
|
|
So for example, the matching rule =_site/%: src/%= will match all files in |
|
|
|
My Makefile is composed of similar blocks, where I replace the first |
|
|
|
find command to match specific files and where I use different building rules. |
|
|
|
An important point is that the rules must be the most specific possible. |
|
|
|
This is because =make= will use the most specific rule in case of ambiguity. |
|
|
|
For example, the matching rule =_site/%: src/%= will match all files in |
|
|
|
the =src/= dir. |
|
|
|
But if we want to treat css file with another rule we could write: |
|
|
|
But if we want to treat =CSS= files with another rule we could write: |
|
|
|
|
|
|
|
#+begin_src makefile |
|
|
|
_site/%.css: src/%.css |
|
|
|
minify "$<" "$@" |
|
|
|
#+end_src |
|
|
|
|
|
|
|
And if the selected file is a css file, this rule will be selected. |
|
|
|
And if the selected file is a =CSS= file, this rule will be selected. |
|
|
|
|
|
|
|
** Prelude |
|
|
|
:PROPERTIES: |
|
|
|
:CUSTOM_ID: prelude |
|
|
|
:END: |
|
|
|
|
|
|
|
So to start I have a few predefined useful variables. |
|
|
|
I start with variables declarations: |
|
|
|
|
|
|
|
#+begin_src makefile |
|
|
|
all: site |
|
|
@ -183,7 +182,7 @@ NO_SRC_FILE := ! -name '*.org' |
|
|
|
:CUSTOM_ID: css |
|
|
|
:END: |
|
|
|
|
|
|
|
So here we go, the same simple pattern for CSS files. |
|
|
|
Here we go; the same simple pattern for CSS files. |
|
|
|
|
|
|
|
#+begin_src makefile |
|
|
|
# CSS |
|
|
@ -280,16 +279,15 @@ or the file, it will generate the dependencies. |
|
|
|
:CUSTOM_ID: indexes |
|
|
|
:END: |
|
|
|
|
|
|
|
One of the goal I have is to be as agnostic as possible regarding format. |
|
|
|
I know that the main destination format will be html. |
|
|
|
So as much as possible, I would like to use this format. |
|
|
|
So for every generated html file I will generate a clean XML file (via |
|
|
|
hxclean) so I will be able to get specific node of my HTML files. |
|
|
|
These XML files will constitute my "index". |
|
|
|
Of course this is not the most optimized index (I could have used sqlite |
|
|
|
for example) but it will already be quite helpful as the same index files |
|
|
|
will be used to build the homepage with the list of articles, and the RSS |
|
|
|
file. |
|
|
|
We often need indexes to build a website. |
|
|
|
Typically to list the latest articles, build the RSS file. |
|
|
|
So for sake of simplicity, I decided to build my index as a set of XML files. |
|
|
|
Of course, this could be optimizide, by using SQLite for example. |
|
|
|
But this will already be really fast. |
|
|
|
|
|
|
|
For every generated html file I will generate a clean XML file with |
|
|
|
=hxclean=. |
|
|
|
Once cleaned, it will be easy to access a specific node of in these XML files. |
|
|
|
|
|
|
|
#+begin_src makefile |
|
|
|
# INDEXES |
|
|
@ -308,24 +306,23 @@ indexcache: $(DST_XML_FILES) |
|
|
|
ALL += indexcache |
|
|
|
#+end_src |
|
|
|
|
|
|
|
So to resume this rule will generate for every file in =site/posts/*.html= |
|
|
|
a corresponding =xml= file (=hxclean= takes an HTML an try its best to make |
|
|
|
an XML out of it). |
|
|
|
This rule will generate for every file in =site/posts/*.html= a corresponding |
|
|
|
=xml= file (=hxclean= takes an HTML an try its best to make an XML out of it). |
|
|
|
|
|
|
|
** HTML Index |
|
|
|
:PROPERTIES: |
|
|
|
:CUSTOM_ID: html-index |
|
|
|
:END: |
|
|
|
|
|
|
|
So now we just want to generate the main =index.html= page at the root of |
|
|
|
Now we just want to generate the main =index.html= page at the root of |
|
|
|
the site. |
|
|
|
This page should list all articles by date in reverse order. |
|
|
|
|
|
|
|
So the first step is to take advantage of the cache index. |
|
|
|
The first step is to take advantage of the cache index. |
|
|
|
For every XML file I generated before I should generate the small HTML |
|
|
|
block I want for every entry. |
|
|
|
For this I use a script =mk-index-entry.sh=. |
|
|
|
He will use =hxclean= to retrieve the date and the title from the cached |
|
|
|
He will use =hxselect= to retrieve the date and the title from the cached |
|
|
|
XML files. |
|
|
|
Then generate a small file just containing the date and the link. |
|
|
|
|
|
|
@ -340,7 +337,7 @@ $(INDEX_CACHE_DIR)/%.index: $(INDEX_CACHE_DIR)/%.xml $(MK_INDEX_ENTRY) |
|
|
|
$(MK_INDEX_ENTRY) "$<" "$@" |
|
|
|
#+end_src |
|
|
|
|
|
|
|
which reads, for every =.xml= file generate a =.index= file with |
|
|
|
It means: for every =.xml= file generate a =.index= file with |
|
|
|
=mk-index-entry.sh=. |
|
|
|
|
|
|
|
#+begin_src sh |
|
|
@ -378,7 +375,7 @@ printf ": %-55s" "$title ($keywords)" |
|
|
|
echo " [${fg[green]}OK${reset_color}]" |
|
|
|
#+end_src |
|
|
|
|
|
|
|
Then I use these intermediate file to generate a single bigger index file. |
|
|
|
Then I use these intermediate files to generate a single bigger index file. |
|
|
|
|
|
|
|
#+begin_src makefile |
|
|
|
HTML_INDEX := $(DST_DIR)/index.html |
|
|
@ -393,13 +390,16 @@ ALL += index |
|
|
|
#+end_src |
|
|
|
|
|
|
|
This script is a big one, but it is not that complex. |
|
|
|
For every file, I generate a new file =DATE-dirname=, I sort them in |
|
|
|
reverse order and put their content in the middle of an HTML file. |
|
|
|
For every file, I generate a new file =DATE-dirname=. |
|
|
|
I sort them in reverse order and put their content in the middle of an HTML |
|
|
|
file. |
|
|
|
|
|
|
|
The important part is that it is only generated if the index change. |
|
|
|
So first part of the script handle the creation of file using the date in |
|
|
|
their file name which will help us sort them later. |
|
|
|
Important note: this file updates only if the index change. |
|
|
|
|
|
|
|
The first part of the script creates files with the creation date in their |
|
|
|
metadatas. |
|
|
|
The created file name will contain the creation date, this will be helpful |
|
|
|
later. |
|
|
|
|
|
|
|
#+begin_src sh |
|
|
|
#!/usr/bin/env zsh |
|
|
@ -480,11 +480,11 @@ echo "* HTML INDEX [done]" |
|
|
|
:CUSTOM_ID: rss |
|
|
|
:END: |
|
|
|
|
|
|
|
So for my RSS generation this is quite similar to the system I use to |
|
|
|
generate my index file. |
|
|
|
My RSS generation is similar to the system I used to generate the index |
|
|
|
file. |
|
|
|
I just slightly improved the rules. |
|
|
|
|
|
|
|
The makefile blocks look like: |
|
|
|
The =Makefile= blocks look like: |
|
|
|
|
|
|
|
#+begin_src makefile |
|
|
|
# RSS |
|
|
@ -553,7 +553,7 @@ gemini: $(DST_GMI_FILES) $(GMI_INDEX) $(GEM_ATOM) |
|
|
|
:CUSTOM_ID: images |
|
|
|
:END: |
|
|
|
|
|
|
|
For images, I try to convert all of them with imagemagick to compress them. |
|
|
|
For images, I try to compress them all with imagemagick. |
|
|
|
|
|
|
|
#+begin_src makefile |
|
|
|
# Images |
|
|
@ -586,7 +586,6 @@ ALL += $(DST_IMG_FILES) |
|
|
|
:END: |
|
|
|
|
|
|
|
A nice bonus is that I also deploy my website using make. |
|
|
|
And note I protect myself from Makefile temporary bugs for the =clean= rule. |
|
|
|
|
|
|
|
#+begin_src makefile |
|
|
|
# DEPLOY |
|
|
|