Sorry if this is the wrong place - or if there is some explanation elsewhere, can you please point me in that direction? If there is not, then I think it would be useful to others to have some discussion about this topic.
I really like this theme, and would love to use it! In particular, I love the clean page transitions that look very nearly as good as e.g. Ajax-loaded content in a SPA with good CSS transitions.
But I am a bit offput by the large amount of inline Javascript that is included directly in every page.
I haven't examined this JS sufficiently to determine if it is somehow custom to each page, and perhaps won't be quite so much depending on particular page?
I do realize I can/should minify pages - I did do that as an experiment with the demo content, but it still makes a big load of JS! But I see that minification does not save much - for example, about/index.html goes from 65k down to 49k. Of course gzip serving will further reduce transfer size considerably.
I wonder if you could discuss/explain your architectural choice here to put all this Javascript in each page? (And not in separate linked JS file). I mean - it is about 1000 lines of JS code (in addition to linked JS files).
I do realize it is vanillaJS, and so should perform very well once loaded and parsed. But it is traversing the network for each page load until cached. Aside from network transfer, maybe modern browsers are starting to cache code fragments and so parsing/compilation can be avoided?
Is this part a work in progress? Or considered sufficiently performant that it's not worth the effort?
Hello @jtara
I'm glad to hear that you like the theme.
First of all, I want you to know that I'm still learning about the Web.
So the structure of the theme may not perfect.
And the code you found from the theme is my best right now(my current limitation).
Is this part a work in progress? Or considered sufficiently performant that it's not worth the effort?
Maybe?.
I think you pointed the js file at theme/zzo/layouts/partials/head/scripts.html.
This file is mainly used for search, scroll, 'navbar', 'theme color change'. All of them needed all of the pages. So I made it load every page. I found that some shortcode logic is in the script.html file which is not needed to be loaded every page. But I thought it is not a big deal.
A possible solution in this case can be to extract all the js blocks as individual files and reference them when loading the pages.
so your script.html file will kind of look like this:
<script src="js/search.js"></script>
<script src="js/tabs.js"></script>
<script src="js/polyfill.js"></script>
<script src="js/navbar collapse.js"></script>
...
where fro example tabs.js will contain:
// ============================ tab ============================
document.querySelectorAll('.tab') ?
document.querySelectorAll('.tab').forEach(function(elem, idx) {
var containerId = elem.getAttribute('id');
var containerElem = elem;
var tabLinks = elem.querySelectorAll('.tab__link');
var tabContents = elem.querySelectorAll('.tab__content');
var ids = [];
tabLinks && tabLinks.length > 0 ?
tabLinks.forEach(function(link, index, self) {
link.onclick = function(e) {
for (var i = 0; i < self.length; i++) {
if (index === parseInt(i, 10)) {
if (!self[i].classList.contains('active')) {
self[i].classList.add('active');
tabContents[i].style.display = 'block';
}
} else {
self[i].classList.remove('active');
tabContents[i].style.display = 'none';
}
}
}
}) : null;
}) : null;
// =============================================================
This way the files will be downloaded only once and referenced from each file.
Thoughts?
@zzossig, thank you for pointing out where to find the code. I am brand new to Hugo (and goLang)! I have used a SSG in the past - Middleman - which was comfortable for me because I am a Ruby developer, and I like Middleman more than Jekyll. But I feel it is time to move on - I like the feature richness of Hugo, and also I want to learn GoLang, as I feel it is increasingly important e.g. for DevOps.
So, I see now what the issue is, and I have started to do a bit of research.
Normally, I think partials/head/scripts.html would be used to insert links to script or scripts in <head>, and maybe some small JS inline.
I now see why you inlined this code - it is because you need some template expansion within the code - the first instance is at line 238-241, then 545-555
It is only those two places, though.
There are a couple of ways to solve this.
I have had similar issues to face in a different context in the past. The most simple way - which I have to admit is not really that great, but has low overhead nevertheless - is to inline just the definition of some global variable or variables. I have typically done this by defining a single global hash object.
So, for example let's just look at the code starting at 238. You have:
{{ $tocFolding := $.Param "tocFolding" }}
var tocFolding = JSON.parse({{ $tocFolding | jsonify }});
{{ $tocLevels := ($.Param "tocLevels") }}
var tocLevels = JSON.parse({{ $tocLevels | jsonify }});
You could simply remove this code. (Let's ignore right now the code at 545-555).
Now, just move your inline JS to a JS file, and include the JS file at the top like the others.
But you still need a small bit of inline JS. Just define global variables. (I try to be neater, and define a single global hash, but for this small it and for demo purposes it is OK to use two here).
Now, your entire inline script is e.g.
<script>
"use strict";
{{ $tocFolding := $.Param "tocFolding" }}
window.tocFolding = JSON.parse({{ $tocFolding | jsonify }});
{{ $tocLevels := ($.Param "tocLevels") }}
window.tocLevels = JSON.parse({{ $tocLevels | jsonify }});
</script>
Note I use window. both because of strict and also just to make it clear that I am defining a global. But you do not need to change the rest of your existing code, since a bare variable reference with no corresponding var definition is the same as window. - that is global variables in browser JS are really just references to members of the window object. And strict will not object to the bare reference - it only objects to bare definition of globals.
The cleanest way, though, would appear to be by adding a Custom Output Format and use text (not HTML) template for the format.
Normally, .js files aren't processed as a template. And I would not recommend processing all .js files as templates, because some might actually have {{ ... }} legitimately (e.g. to define a nested hash.) But you could define a custom output format for, e.g. *.tmpl.js to be processed as text template and output to .js. If you should happen to have any nested hash definition like above, just space it out like { { ... } } with a space between the brackets to make sure it is not seen as a template tag.
So, now you write your JS that needs to have some template substitution in a *.tmpl.js file, and it will be processed by the template engine.
I may do some experiments. If I can get this cleanly extracted to a .js file (or files) I can fork and give you a pull request if you like. I have a bit of Hugo learning first though!
Finally, I agree with @nisrulz it would be good to modularize that code a bit and extract to a small number of separate JS files, just for good practice "separation of concerns".
If desired for efficiency, I believe they could all be brought together into one file as part of the pipeline while being maintained as separate files for clarity. But that is a "religious war" - one big JS file in the output or several (loaded asynchronously).
@nisrulz Yes, that is exactly what I thought that I should do next time
it is because you need some template expansion within the code
@jtara Yes! Now I remember that it is one of the reasons. That's why I use HTML for serving Javascript instead of js file.
And I appreciate your detailed explanations!
I understand what you mean the upper part(make an inline script that serving with HTML and make js file for the rest of the javascript code)
But I curious if it's possible to custom templating(*.tmpl.js)
I'll also try to do the suggestion
I could try experimenting with splitting your code if I get the time for JS part and probably open a PR, so we can have more concrete version to discuss a proper implementation.
Will also look into the templating part, I think I have some idea around that. Will update here.
See also:
https://gohugo.io/hugo-pipes/resource-from-template/
I think to serve templated JS from an asset directory, you will need the above, as well as defining a custom output format. That's because files in asset directories aren't normally considered for template processing, regardless of file naming pattern.
Not sure what would happen if you don't define the custom output format. I think that is necessary in any case, because it needs to be processed as a text template - not an HTML template.
I think this would be an appropriate custom output format definition:
mediaTypes:
text/javascript:
suffixes: tmpl.js
outputFormats:
javascript:
mediaType: text/javascript
isPlainText: true
Yet another simple approach is to just stuff a JSON string into a data- attribute, typically on <html>. This is something I've also done myself in the past.
close for inactivity
Most helpful comment
I could try experimenting with splitting your code if I get the time for JS part and probably open a PR, so we can have more concrete version to discuss a proper implementation.
Will also look into the templating part, I think I have some idea around that. Will update here.