Using Literate Programming to make CSS+HTML+JS+ELisp play nice together
Among the features of Emacs’ Org-Mode that I love is its support for literate programming. For an good intro to all this, checkout this page. I wanted to take advantage of this to hopefully make it easier to work with the CSS, HTML, JavaScript, and Emacs Lisp that powers this site.
Noweb
The first place I used the noweb support of org-mode was in including my HTML headers the setup of the org-publish-project-alist
for this site which looks something like (note that this is just a snippet of org-publish-project-alist
):
#+begin_src html <link href=\"/styles.css\" rel=\"stylesheet\" type=\"text/css\"> <script src=\"/scripts.js\" type=\"text/javascript\"></script> #+end_src #+begin_src emacs-lisp :noweb no-export (setq org-publish-project-alist `( ("pages" :base-directory "~/Documents/website/" :publishing-directory "~/Documents/www/" :recursive t :base-extension "org" :publishing-function org-html-publish-to-html :html-doctype "html5" :html-head " <<html-header>> " )) #+end_src
Notice that <<html-header>>
is on it’s own line completely left justified. This is because by no web references will include whatever is before the reference before every line in the referenced block.
For example if I had done the natural thing and written:
:html-head "<<html-header>>"
It would have expanded to
:html-head "<link href="/styles.css" rel="stylesheet" type="text/css">" :html-head "<script src="/scripts.js" type="text/javascript"></script>"
Which of course in invalid html.
Notice also that we must escape all the double quotes in the html block because that block is actually going into a elisp string. This is annoying as it’s not actually valid html. To fix this we can borrow an idea from here and create a sort of function reference which will take care of escaping the quotes:
#+begin_src elisp :var name="" (let* ((named-element (org-element-map (org-element-parse-buffer) org-element-all-elements (lambda (element) (when (string= (org-element-property :name element) name) element)) nil t)) (result (org-element-property :value named-element))) (format "\"%s\"" (replace-regexp-in-string "\\\"" "\\\\\"" result))) ;; escape quote #+end_src
Tangling
I often find it annoying that I have to switch between multiple files to work on HTML, CSS, and JS.
Of course I could just put them in <style>
and <script>
tags in the HTML header, but that’s kind of inefficient and tangling is the perfect solution!
All I have to do is have a css block in the org file with all my configuration for the website and tell it where to tangle out the CSS when I execute org-babel-tangle
#+begin_src css :tangle ~/Documents/www/styles.css body { font-family: "Helvetica Neue", Helvetica, sans-serif; color: var(--base); background-color: var(--bg1); /* color: white; background-color: rgb(29, 31, 33); ,*/ width: 50%; margin-left:auto; margin-right:auto; } #+end_src