8.8 KiB
📐 Project architecture
Following diagram explain how metrics code is structured:
🗂️ Project structure
This section explain how metrics is structured.
source/app/metrics/contains metrics engine filessource/app/action/contains GitHub action filesindex.mjscontains GitHub action entry pointaction.ymlcontains GitHub action templated descriptor
source/app/web/contains web instance filesindex.mjscontains web instance entry pointinstance.mjscontains web instance source codesettings.example.jsoncontains web instance settings examplestatics/contains web instance static filesapp.jscontains web instance client source codeapp.placeholder.jscontains web instance placeholder mocked data
source/plugins/*/contains source code of pluginsREADME.mdcontains plugin documentationmetadata.ymlcontains plugin metadataexamples.ymlcontains plugin workflow examplesindex.mjscontains plugin source codequeries/contains plugin GraphQL queries
source/templates/*/contains templates filesREADME.mdcontains template documentationmetadata.ymlcontains template metadataexamples.ymlcontains template workflow examplesimage.svgcontains template image used to render metricsstyle.csscontains style used to render metricsfonts.csscontains additional fonts used to render metricstemplate.mjscontains template source code
tests/contains testsmetrics.test.jscontains metrics testerssource/app/mocks/contains mocked data filesapi/contains mocked api dataaxios/contains external REST APIs mocked datagithub/contains mocked GitHub api data
index.mjscontains mockers
Dockerfilecontains docker instructions used to build metrics imagepackage.jsoncontains dependencies and command line aliases
🎬 Behind the scenes
This section explore some topics which explain globally how metrics was designed and how it works.
💬 Creating SVGs images on-the-fly
metrics actually exploit the possibility of integrating HTML and CSS into SVGs, so basically creating these images is as simple as designing static web pages. It can even handle animations and transparency.
SVGs are templated through EJS framework to make the whole rendering process easier thanks to variables, conditional and loop statements. Only drawback is that it tends to make syntax coloration a bit confused because templates are often misinterpreted as HTML tags markers (<%= "EJS templating syntax" %>).
Images (and custom fonts) are encoded into base64 to prevent cross-origin requests, while also removing any external dependencies, although it tends to increase files sizes.
Since SVG renders differently depending on OS and browsers (system fonts, CSS support, ...), it's pretty hard to compute dynamically height. Previously, it was computed with ugly formulas, but as it wasn't scaling really well (especially since the introduction of variable content length plugins). It was often resulting in large empty blank spaces or really badly cropped image.
To solve this, metrics now spawns a puppeteer instance and directly render SVG in a browser environment (with all animations disabled). An hidden "marker" element is placed at the end of the image, and is used to resize image through its Y-offset.
Additional bonus of using pupeeter is that it can take screenshots, making it easy to convert SVGs to PNG output.
💬 Gathering external data from GitHub APIs and Third-Party services
metrics mostly use GitHub APIs since it is its primary target. Most of the time, data are retrieved through GraphQL to save APIs requests, but it sometimes fallback on REST for other features. Octokit SDKs are used to make it easier.
As for other external services (Twitter, Spotify, PageSpeed, ...), metrics use their respective APIs, usually making https requests through axios and by following their documentation. It would be overkill to install entire SDKs for these since plugins rarely uses more than 2/3 calls.
In last resort, pupeeter is seldom used to scrap websites, though its use tends to make things slow and unstable (as it'll break upon HTML structural changes).
💬 Web instance and GitHub action similarities
Historically, metrics used to be only a web service without any customization possible. The single input was a GitHub username, and was composed of what is now base content (along with languages and followup plugin, which is why they can be computed without any additional queries). That's why base content is handled a bit differently from plugins.
As it gathered more and more plugins over time, generating a single user's metrics was becoming costly both in terms of resources but also in APIs requests. It was thus decided to switch to GitHub Action. At first, it was just a way to explore possibilities of this GitHub feature, but now it's basically the full-experience of metrics (unless you use your own self-hosted instance).
Both web instance and Action actually use the same entrypoint so they basically have the same features. Action just format inputs into a query-like object (similarly to when url params are parsed by web instance), from which metrics compute the rendered image. It also makes testing easier, as test cases can be reused since only inputs differs.
📦 Packages reference
Below is a list of used packages.
- express/express.js and expressjs/compression
- To serve, compute and render a GitHub user's metrics
- nfriedly/express-rate-limit
- To apply rate limiting on server and avoid spams and hitting GitHub API's own rate limit
- octokit/graphql.js and octokit/rest.js
- To perform request to GitHub GraphQL API and GitHub REST API
- mde/ejs
- To render SVG images
- ptarjan/node-cache
- To cache generated content
- oliver-moran/jimp, foliojs/png.js and eugeneware/gifencoder
- To process images transformations
- svg/svgo
- To optimize generated SVG
- axios/axios
- To make HTTP/S requests
- actions/toolkit
- To build the GitHub Action
- vuejs/vue, egoist/vue-prism-component, prismjs/prism and zenorocha/clipboard.js
- To display server application
- puppeteer/puppeteer
- To scrape the web
- marudor/libxmljs2 and chrisbottin/xml-formatter
- To format, test and verify SVG validity
- facebook/jest and nodeca/js-yaml
- For unit testing
- marak/faker.js
- For mocking data
- steveukx/git-js
- For simple git operations
- twitter/twemoji-parser and IonicaBizau/emoji-name-map
- To parse and handle emojis/twemojis
- jshemas/openGraphScraper
- To retrieve open graphs metadata
- panosoft/node-chartist and gionkunz/chartist-js
- To display embed SVG charts
- rbren/rss-parser
- To parse RSS streams
- Nixinova/Linguist
- To analyze used languages
- markedjs/marked and apostrophecms/sanitize-html
- To render markdown blocks
- css/csso and FullHuman/purgecss
- To optimize and purge unused CSS
- isaacs/minimatch
- For file traversal
- node-fetch/node-fetch
- For
fetchpolyfill
- For
- eslint/eslint
- As linter

