This is the code for opencollective.foundation.
opencollective.foundation is a static site built with Eleventy, a JavaScript-based static site generator. Eleventy compiles template code written in EJS, content written in Markdown, and styles written in LESS, into pure HTML and CSS. Being a static site, there is no backend, database, or (much) frontend rendering. At opencollective.foundation/admin, Netlify CMS provides a GUI to edit content. This is really just a fancy interface that commits changes to Markdown files to Git, as there is no database like in popular CMSes such as WordPress. The Admin site is authenticated using GitHub OAuth. The site is deployed to Vercel, a static site host, and served via their CDNs.
(Note that Netlify is a competitor to Vercel. We do not use Netlify for deployments, only their open source CMS library.)
This site was built with the node version specified in .nvmrc
.
npm install
npm run serve
And open the URL shown.
Vercel manages automatic deployment of changes to master
, and creates preview branch deploys automatically.
See Vercel documentation for more information.
Git hooks will be automatically installed by husky
to lint files with prettier
upon saving. Unfortunately it's not great at .ejs
files, so try to keep those tidy yourself.
This is a single-page site that uses a sectional layout. That is, the page is rendered by concatenating together items in Eleventy collections in one page (implemented in /_includes/sectionalLayout.ejs
). The collection items are the /home
directory. They are rendered in the order of the position
attribute of each item. The navigation is generated from these items, exempting those with hideInNavigation: true
.
Markdown files are editable in the CMS, ejs
files are not, but allow a developer to write custom HTML for when a custom layout is needed. Alternating ejs and Markdown sections is one way to intersperse editable and non-editable content.
LESS files are in /styles
. /styles/styles.less
is the entrypoint and must import all other files.
/styles/variables
contains declarations of reusable colors, fonts, etc. Please try to always reuse a variable rather than using literal colors anywhere else.
/styles/partials
has larger blocks of reusable styles.
Most other files map 1:1 with an item of content. They should be wrapped in a scoping block that ensures they only affect the element they intend to style, and shouldn't be reused.
/assets/uploads
contains files uploaded into the CMS's media manager.
/assets/images
- static images not managed by the CMS
/assets/scripts
- a small amount of JavaScript
While the site is mostly static, there are a few cases for which we need client-side JS.
- There are a few interactive elements like the photos carousel and some hide/show buttons.
- Client-side JS is also used when editable Markdown needs to be modified, such as converting the "services" table into a very different DOM structure for its mobile view.
- And it's also used to localize date-times to display in the locale and timezone of the user.
But mainly this is a static site, so for simplicity and performance it does not use an interactive library like React or Vue.
There is no transpiler or polyfills, so party like it's 2012 🕺🏻 ! Check CanIUse for what JS features have cross-browser support and please be careful! We do not have a browser matrix but try to write compatible code. We are not supporting Internet Explorer, so ES6 should be okay.
This is an image-heavy site.
It's important that content creators be able to add high-quality imagery without without hurting the site's performance.
Images are resized as part of the build process using the eleventy-img
plugin.
The helper function image()
in _data/image.js
generates a picture
tag with various sizes.
Content editors should provide high-quality imagery and allow our resizer to do its job.
The resizing process is the slowest part of the build. If anyone wants to improve caching, please have a go!
Some content on the site like the blog, set of hosted collectives, and events are pulled from external sources at build time.
These functions are in _data
.
A Github Actions job (.github/workflows
) triggers a regular re-deploy of the site to refresh this content.
Netlify CMS is loaded from a CDN in /admin/index.html
.
The configuration in in /admin/config.yml
. This file specifies what files are editable in the UI.
For sections, we use "folder collections", meaning each item is a file in a folder.
For other data that doesn't have a clear "content body", we use "file collections" which mean all the collection items are in a JSON file in /_data
.
See the Netlify CMS documentation for details.
This is one of those "okay…let me explain" moments, because it's a doozy.
The Markdown editor that comes with Netlify CMS is lacking, specifically in its support for editing tables, and we have a large table that content editors want to be able to change. So we plug in a more advanced Markdown editor "Toast UI".
To do this I (@jboolean) publish a separate npm package (https://github.com/jboolean/netlify-cms-widget-markdown-toast-ui) that re-packages the Toast UI React library as a Netlify plugin. Then, in src/scripts.11ty
.js we run a little Webpack build to compile Netlify CMS (since the pre-compiled CDN version doesn't allow the plugin to be installed.). In main.js
we initialize Netlify CMS and register the plugin, and this script is included in the admin page at admin/index.html
.
The data folder is a convenient way to store arbitrary data for use in templates. There are a couple things in here: actual data and utility functions.
Data files like featuredCollectives.json
are editable in the CMS and used in templates.
The second, hacky use of _data
is to make JavaScript functions available to templates.
This includes _.js
which exports lodash, and markdown.js
which provides a function to render markdown (Eleventy automatically renders Markdown when it's the body of a Markdown file, but not if it's just a string in a JSON blob in /_data
).
If we need some custom layout but also need a section to be editable, it's possible to define custom markdown blocks that turn into CSS classes. These are defined in the markdown-it configuration in .eleventy.js
and the styles are in markdown-containers.less
.
There are a few backend lambda functions in /api
. These support Oauth for the CMS and the newsletter sign-up form.