Github Metadata with Jekyll and JavaScript


Last week Github announced that organization and project metadata would be available to Jekyll templates in the site.github namespace, rather than relying on client-side API calls.

Their documentation lacks real world code examples, so here is what I used to restructure Shopify’s Open Source page (mostly) away from JS API calls in favour of Github metadata. Be warned, this data is only available when the page is live, so I had to work on hidden code on our live page until it was bug free.

Edit: Shortly after posting this, Githubber @BenBalter mentioned that you can simply use {{ site.github | jsonify }} to get some JS-friendly data. I’ve left my original approach below, but the better way is to use the jsonify helper.

For context, we wanted to showcase a defined list of Shopify’s public repos and sort them according to their star gazer statistics. For this, I needed to get the metadata into JavaScript. Since the metadata is only available on Jekyll templates, I added the following to index.html before the link to my external JS file.

Original Approach:

<script>
  var repos = [];

  {% for repo in site.github.public_repositories %}
    var repo = {
      name: "{{repo.name}}",
      html_url: "{{repo.html_url}}",
      description: "{{repo.description}}",
      homepage: "{{repo.homepage}}",
      language: "{{repo.language}}",
      stars: {{repo.stargazers_count}},
      forks: {{repo.forks_count}},
      fork: {{repo.fork}}
    };
    repos.push(repo);
  {% endfor %}
</script>

Better Approach

<script>
  var repos = {{ site.github.public_repositories | jsonify }};
</script>

repos is globally available so we can use it in our external JS. We want the ability to add repos not directly owned by Shopify (that we still manage) to the collection. For that we call the API and add the results to our previous repos array.

// Add custom repos by full_name. Take the org/user and repo name
// - e.g. batmanjs/batman from https://github.com/batmanjs/batman
var customRepos = [
 'batmanjs/batman'
]

for (var i = customRepos.length - 1; i >= 0; i--) {
  repo = customRepos[i];

  var uri = 'https://api.github.com/repos/'+ repo +'?callback=?';

  $.getJSON(uri, function(result) {
    if (result.meta.status == 403) {
      // If we hit the limit, just pass on the current repos we have
      o.addRepos(repos);
      return;
    }

    // Add api data to repos array
    repos = repos.concat(result.data);

    customApiCalls++;
    if (customApiCalls == customRepos.length) {
      // If the custom repo ajax calls are done, move one
      o.addRepos(repos);
    }
  });
};

I use Handlebars.js to template the data and add it to the page. The code below also ignores forked repos, only includes repos we define (opt-in rather than opt-out), create a repo language class, and finally sort by star gazers count.

addRepos: function(repos) {
  var o = this,
      repoCount = 0;

  var items = [],
      item = {},
      data = {}
      source   = $('#repoTemplate').html(),
      template = Handlebars.compile(source);

  $.each(repos, function (i, repo) {

    // Ignore forked repos
    if (o.$ignoreForks && repo.fork) {
      return;
    }

    // Opt-in repos (name) and custom repos (full_name) only
    if ( optInRepos.indexOf(repo.name) > -1 || customRepos.indexOf(repo.full_name) > -1) {
      repoCount = repoCount + 1;
    } else {
      return;
    }

    // Update repo language if manually defined
    if ( repo.name in customRepoLanguage ) {
      repo.language = customRepoLanguage[repo.name];
      repo.languageClass = (customRepoLanguage[repo.name] || '').toLowerCase();
    } else {
      repo.languageClass = (repo.language || '').toLowerCase();
    }

    // Make sure homepage URLs start with http. If not, add them
    if (repo.homepage && repo.homepage.substring(0, 4) != "http") {
      repo.homepage = 'http://' + repo.homepage;
    }

    item = {
      url: repo.html_url,
      name: repo.name,
      language: repo.language,
      languageClass: repo.languageClass,
      description: repo.description,
      stars: repo.stargazers_count ? repo.stargazers_count : 0,
      forks: repo.forks_count ? repo.forks_count : 0,
      avatar: repo.name in customRepoAvatar ? customRepoAvatar[repo.name] : null,
      homepage: repo.homepage
    };

    items.push(item);
  });

  // Sort by stars
  items.sort(function(a,b) {
    if (a.stars < b.stars) return 1;
    if (b.stars < a.stars) return -1;
    return 0;
  });

  // Create handlebars.js data
  data = { items: items };

  // Append handlebars templates
  o.$repoContainer.addClass('is-loaded').append(template(data));

  // Display public repo count of opt-in repos (minus forks)
  $('#countRepos').removeClass('is-loading').text(repoCount);
}

Lastly, it’s worth noting that this data is included in site.github namespace is made available to Jekyll when your site is generated. That means the data won’t be updated in the generated static files unless the site is rebuilt. Get the most up to date information by querying the API.



One comment on “Github Metadata with Jekyll and JavaScript


  1. John July 13th, 2016
    at 2:31 am

    reply

    I have created a index.html file which contains the following contents.

    var repos = {{ site.github.public_repositories | jsonify }};

    and in my github-repo.js, I have

    console.log(repos);

    but I’m getting an error saying repos is not defined.

Leave a Reply