24
loading...
This website collects cookies to deliver better user experience
const Cache = require("@11ty/eleventy-cache-assets");
module.exports = async function() {
let url = "https://api.github.com/repos/11ty/eleventy";
/* This returns a promise */
return Cache(url, {
duration: "1d", // save for 1 day
type: "json" // we’ll parse JSON for you
});
};
--------
title: Beginner guitar lessons
name: beginner-guitar-lessons
id: PLA0cAQ-2uoeoJoFfUz9oq9qhmlnsjFRhU
--------
src/playlists
folder I create a playlists.json.{
"tags": ["playlist"],
"permalink": false
}
{%- if collections.playlists %}
{%- asyncEach collections.playlist in playlists | fetchYouTubePlaylists %}
{%- include 'partials/video-playlist.njk' %}
{%- endeach %}
{%- endif %}
If you are unfamiliar with template languages in 11ty you can read about them over here.
partials/video-playlist.njk
is later on in the article.fetchYouTubePlaylists
is where the magic happens and where we will start to use @11ty/eleventy-cache-assets
..eleventy.js
config file.eleventyConfig.addNunjucksAsyncFilter("fetchYouTubePlaylists", async (playlists, callback) => {
const data = await getPlaylists(playlists);
callback(null, data);
})
getPlaylists
is making a call to getPlaylistItem
which is where i'm actually doing the data caching.module.exports.getPlaylists = async (playlists) => {
if(!playlists) {
return [];
}
const lists = await Promise.all(playlists.map((async ({id, title, description}) => {
const content = await getPlaylistItem(id);
return {
title,
id,
description,
link: `https://www.youtube.com/playlist?list=${id}`,
...(content || {}),
};
})));
return lists;
}
getPlaylistItem
:const getPlaylistItem = async (playlistId) => {
const apiUrl = 'https://www.googleapis.com/youtube/v3/playlistItems';
const maxResults = 20;
const order = 'viewCount';
const url = `${apiUrl}?key=${apiKey}&part=${encodeURIComponent('snippet,contentDetails')}&type=video%2C%20playlist&maxResults=${maxResults}&playlistId=${playlistId}&order=${order}`;
console.log(`Fetching YouTube videos for playlist: ${playlistId}`);
const videos = await Cache(url, {
duration: "1d", // 1 day
type: "json" // also supports "text" or "buffer"
});
const videoIds = videos.items.map(({contentDetails}) => contentDetails.videoId);
const metaInfo = await fetchMetaInfo(videoIds);
return {
videos: await Promise.all(videos.items.map(async ({snippet, contentDetails}) => {
const hqThumbnail = snippet.thumbnails.maxres || snippet.thumbnails.high || snippet.thumbnails.medium || snippet.thumbnails.default;
const smallThumbnail = snippet.thumbnails.medium || snippet.thumbnails.default;
const defaultThumbnail = snippet.thumbnails.high;
return {
hqThumbnail,
smallThumbnail,
defaultThumbnail,
channelTitle: snippet.channelTitle,
channelId: snippet.channelId,
title: snippet.title,
id: contentDetails.videoId,
...(metaInfo[contentDetails.videoId] || {}),
}
})),
hasMore: Boolean(videos.nextPageToken)
}
};
You will want to store your API key as an environment variable e.g. const apiKey = process.env.YT_API_KEY;
. For production you can add this environment variable where ever you choose to build/host the site e.g. on Netlify.
fetchMetaInfo
fetches things like view count and likes, this is another API call which we would be concerned about if this was client side, but since it's build time, who cares!videos
for each playlist and a flag hasMore
if the playlist has more than then 20 items shown. In my HTML when I see this flag I add an link out to YouTube to watch the full playlist.<a>
to the YouTube videos, perhaps the thumbnail could open a tab to YouTube, this needs no JS at all, and is what I did:{%- if playlist -%}
{%- set firstVideo = playlist.videos[0] -%}
{%- set description = playlist.description or (playlist.templateContent | safe) %}
<youtube-playlist id="{{playlist.title | slug }}">
<div class="fallback" slot="fallback">
<div class="img-btn-wrapper">
<img decoding="async" loading="lazy" width="{{firstVideo.hqThumbnailWidth}}" height="{{firstVideo.hqThumbnaillWdith}}" src="{{firstVideo.hqThumbnailUrl}}" />
</div>
<a rel="noopener" title="Play playlist: {{playlist.title}}" class="" target="_blank" href="{{playlist.link}}"></a>
</div>
{%- for video in playlist.videos -%}
<li {{helpers.spread(video, "data-") | safe}}></li>
{%- endfor -%}
{%- if playlist.hasMore -%}
<a slot="more-link" href="{{playlist.link}}">Watch more on YouTube.</a>
{%- endif -%}
</youtube-playlist>
{%- endif -%}
youtube-playlist
Custom Element.<li>
list items as child content inside of my <youtube-playlist>
and when JavaScript loads move this content in the Shadow DOM, and make them look pretty/interactive.{%- if playlist -%}
{%- set firstVideo = playlist.videos[0] -%}
{%- set description = playlist.description or (playlist.templateContent | safe) %}
<youtube-playlist id="{{playlist.title | slug }}">
<a slot="heading" href="#{{playlist.title | slug }}"><h2>{{playlist.title | safe}}</h2></a>
<p slot="description">{{description}}</p>
<div class="fallback" slot="fallback">
<div class="img-btn-wrapper">
<img decoding="async" loading="lazy" width="{{firstVideo.hqThumbnailWidth}}" height="{{firstVideo.hqThumbnaillWdith}}" src="{{firstVideo.hqThumbnailUrl}}" />
<svg style="pointer-events:none;" class="playbtn" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<g transform="translate(-339 -150.484)">
<path fill="var(--White, #fff)" d="M-1978.639,24.261h0a1.555,1.555,0,0,1-1.555-1.551V9.291a1.555,1.555,0,0,1,1.555-1.551,1.527,1.527,0,0,1,.748.2l11.355,6.9a1.538,1.538,0,0,1,.793,1.362,1.526,1.526,0,0,1-.793,1.348l-11.355,6.516A1.52,1.52,0,0,1-1978.639,24.261Z" transform="translate(2329 150.484)"/>
<path fill="var(--Primary, #000)" d="M16.563.563a16,16,0,1,0,16,16A16,16,0,0,0,16.563.563Zm7.465,17.548L12.672,24.627a1.551,1.551,0,0,1-2.3-1.355V9.853a1.552,1.552,0,0,1,2.3-1.355l11.355,6.9A1.553,1.553,0,0,1,24.027,18.111Z" transform="translate(338.438 149.922)" />
</g>
</svg>
</div>
<a rel="noopener" title="Play playlist: {{playlist.title}}" class="" target="_blank" href="{{playlist.link}}"></a>
</div>
{%- for video in playlist.videos -%}
<li {{helpers.spread(video, "data-") | safe}}></li>
{%- endfor -%}
{%- if playlist.hasMore -%}
<a slot="more-link" href="{{playlist.link}}">Watch more on YouTube.</a>
{%- endif -%}
</youtube-playlist>
{%- endif -%}