Migration du site sur Hugo - Mise en place recherche
This post is part of the Migration Hugo series.
Jan 28, 2018
3 minute read

Migration du site sur Hugo - Mise en place recherche

Contexte

Dans la version précédente du site, j’avais quand même fini par ajouter une recherche faite via un bon vieux grep. Ce n’était certainement pas top (et cela avait peut être des incidences en termes de sécurité) mais un site sans recherche c’est un peu limite.

Au début je pensais passer par Google Search mais à priori ils arrêtent de fournir ce service et au final cela tombe bien car la mise en place de la recherche avec Hugo n’est pas si compliquée.

Je suis parti de cet article de blog. Je vais donc utiliser lunr et cela marche pas mal.

Comment faire ?

Générer un index au format Json

Pas très compliqué car Hugo est prêt pour ça il faut juste créer le fichier index.json dans le répertoire layout de mon thème :

[
    {{ range $i, $e := .Site.RegularPages }}

    {{- if $i }}, {{ end }}
        {
            "uri": {{ .Permalink | relURL | jsonify }},
            "title": {{ .Title | jsonify }},
            "language": {{ .Lang | jsonify }},
            "content": {{ .Plain | jsonify }},
            "tags": [
                {{- range $ii, $ee := .Params.tags }}
                {{- if $ii }}, {{- end }}
                    {{ $ee | jsonify }}
                {{- end }}
            ]
        }
    {{- end }}
]

Il faut ensuite modifier le config.toml :

[outputs]
home = ["HTML", "CSS", "RSS", "JSON"]

J’ai donc accès à /index.json et /fr/index.json. Ces deux fichiers contiennent tout le contenu de chaque page sans aucune balise HTML.

Générer un index spécifique à lunr

Cette opération pourrait se faire en live sur la page de recherche (côté client). Pour éviter de perdre du temps, je le génère statiquement via gulp :

gulp.task('lunr-index', () => {
  const documentsEn = JSON.parse(fs.readFileSync('public/index.json'));
  const documentsFr = JSON.parse(fs.readFileSync('public/fr/index.json'));

  var titleMap = {};

  let lunrIndex = lunr(function() {
        this.use(lunr.multiLanguage('en', 'fr'));

        this.field("title", {
            boost: 10
        });
        this.field("tags", {
            boost: 5
        });
        this.field("content");
        this.ref("uri");

        documentsEn.forEach(function(doc) {
            titleMap[doc.uri] = doc.title;
            this.add(doc);
        }, this);
        documentsFr.forEach(function(doc) {
            titleMap[doc.uri] = doc.title;
            this.add(doc);
        }, this);
    });

  fs.writeFileSync('public/js/lunr-index.json', JSON.stringify(lunrIndex));
  fs.writeFileSync('public/js/title-map.json', JSON.stringify(titleMap));
});

Cette tâche génère deux fichiers :

  • lunr-index.json : l’index qui va être utilisé par lunr.
  • title-map.json : un simple tableau json qui permet d’avoir le titre d’une page à partir de son URL.

Création d’une page de recherche

J’ai simplement créé le fichier search.html dans le répertoire layouts/_default et la partie intéressante est :

  • Chargement du fichier d’index
    // Start loading the json index as soon as possible
    fetchAsync('/js/lunr-index.json')
    .then(data => {
        //debugger;
        try {
            idx = lunr.Index.load(data);
            console.log('Search ready');
            searchBox.placeholder = 'Just start typing...';
            searchBox.disabled = false;
        }
        catch (e) {
            console.log (e);
        }
    })
    .catch(reason => console.log(reason.message));
  • Lancement d’une recherche
function processForm(e) {
    if (e.preventDefault) e.preventDefault();
    console.log('Search started');
    let res = idx.search(searchBox.value);

    // purge old results
    while (results.firstChild) {
        results.removeChild(results.firstChild);
    }
    res.forEach(function (el) {
        let liTag = document.createElement('li');
        let aTag = document.createElement('a');
        aTag.setAttribute('href', el.ref);
        aTag.innerHTML = el.ref;
        if (titleMap.hasOwnProperty(el.ref)) {
            aTag.innerHTML = titleMap[el.ref];
        }
        liTag.appendChild(aTag);
        results.appendChild(liTag);
    });
    return false;
}

Le fichier complet est visible ici.

Conclusion

Le fichier d’index de lunr fait 2,59Mo (environ 440Ko en gzip) pour environ 400 articles. Cela occasionne un léger délai lors de l’affichage de la page. Je trouve cela acceptable mais pour de plus gros sites que moi cela peut être génant.

TODO :

  • Pour l’instant je n’ai pas ajouté le lien à la page de recherche dans l’index.
  • Ce serait sympa d’avoir le résumé des articles en résultats.


comments powered by Disqus