Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: pre render dynamic video sessions routes as well #32

Merged
merged 15 commits into from
Mar 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ testem.log
Thumbs.db

# Local Netlify folder
.netlify
.netlify

# Generated files for dynamic route pre rendering
routes.txt
src/functions/videos/case-sensitive-video-id-mapping.json
107 changes: 107 additions & 0 deletions misc/generate-routes-file-for-dynamic-routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/* The purpose of this script is to generate the `routes.txt` file that is used during
* the pre rendering at build time for dynamic route.
* For now we want to prerender all the video session dynamic routes
* More over netlify lower case all the urls so if we try to access directly to a
* session video on the address /sessions/rT0FUs7uUks netlify will redirect to /sessions/rt0fus7uuks
* And then our netlify function will not be able to fetch the right video ID.
* This js script also generates a plain `.json` mapping `case-sensitive-vide-id-mapping.json` located in `src/functions/utils`
* This mapping is then used to convert lower case video ids to case sensitive video Ids
* https://answers.netlify.com/t/my-url-paths-are-forced-into-lowercase/1659 */
const fetch = require('node-fetch');
const { ensureFileSync, writeFileSync, writeJsonSync } = require('fs-extra');
const DIST_DYNAMIC_ROUTES_FILENAME = 'routes.txt';
const DIST_MAPPER_JS_FILE =
'src/functions/videos/case-sensitive-video-id-mapping.json';
console.log('Generating routes.txt for dynamic routes pre rendering');

const getDistFilename = () => {
return DIST_DYNAMIC_ROUTES_FILENAME;
};

const getMapperFile = () => {
return DIST_MAPPER_JS_FILE;
};

let videoIds = [];

/**
* Fetch the playlist by id and map the response to return only the ids
* @returns {Promise<void>}
*/
const getVideoIds = async () => {
// If video ids already computed return them
if (videoIds && videoIds.length > 0) {
return videoIds;
}
// Fetch the playlist of ngx-darija
const playlist = await (
await fetch('http://localhost:8889/.netlify/functions/playlist')
).json();
if (playlist && playlist.error) {
throw new Error(`Error when fetching playlist: ${playlist.error.message}`);
} else {
const items = playlist.items;
console.log(`Got ${items.length} videos`);
videoIds = items.map(item => item.snippet.resourceId.videoId);
return videoIds;
}
};

/**
* Resolves with the content need for the `routes.txt` file.
* =>
* ``/sessions/34dwe
* ``/sessions/234dk2
* @returns {Promise<string>}
*/
const getDynamicRoutesFileContent = async () => {
// Get video ids
let fileContent = '';
const videoIds = await dynamicRoutesGenerator.getVideoIds();
// For each video append the dynamic route to the file content variable
videoIds.forEach(videoId => {
fileContent += `/sessions/${videoId}\n`;
});
return fileContent;
};

/**
* Computes and writes the `routes.txt` file
*/
const generateDynamicRoutesFile = async () => {
try {
const fileContent = await dynamicRoutesGenerator.getDynamicRoutesFileContent();
// Ensure file existing or create it
ensureFileSync(dynamicRoutesGenerator.getDistFilename());
// Write the file content
writeFileSync(dynamicRoutesGenerator.getDistFilename(), fileContent);
await dynamicRoutesGenerator.generateYoutubeVideoIdCaseSensitiveMapper();
} catch (e) {
console.error('Error when generating dynamic routes file', e);
process.exit(1);
}
};

/**
* Generates a mapper js file that exports a function that converts lower case youtube video IDs
* to their matching case sensitive IDs
*/
const generateYoutubeVideoIdCaseSensitiveMapper = async () => {
let mapping = {};
const videoIds = await dynamicRoutesGenerator.getVideoIds();
videoIds.forEach(videoId => {
mapping[videoId.toLowerCase()] = videoId;
});
ensureFileSync(dynamicRoutesGenerator.getMapperFile());
writeJsonSync(dynamicRoutesGenerator.getMapperFile(), mapping);
};

const dynamicRoutesGenerator = {
generateDynamicRoutesFile,
getVideoIds,
getDynamicRoutesFileContent,
getDistFilename,
getMapperFile,
generateYoutubeVideoIdCaseSensitiveMapper
};
module.exports = dynamicRoutesGenerator;
40 changes: 40 additions & 0 deletions misc/test/generate-routes-file-for-dynamic-routes.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const assert = require('assert');
const t = require('../generate-routes-file-for-dynamic-routes');
const { removeSync, readFile } = require('fs-extra');
const sinon = require('sinon');
const TEMP_DIST_FILENAME = 'tmp/test_routes.txt';
const TEMP_DIST_MAPPER_JS_FILE = 'tmp/mapping.json';
describe('generate-routes-file-for-dynamic-routes', () => {
sinon.stub(t, 'getVideoIds').callsFake(() => {
return ['1', '2', '3'];
});
describe('getDynamicRoutesFileContent', () => {
it('should generate dynamic routes file correctly', async () => {
const content = await t.getDynamicRoutesFileContent();
assert.strictEqual(content, '/sessions/1\n/sessions/2\n/sessions/3\n');
});
});

describe('generateDynamicRoutesFile', () => {
afterEach(() => {
// clean up
removeSync(TEMP_DIST_FILENAME);
removeSync(TEMP_DIST_MAPPER_JS_FILE);
});
it('should write file content into dist file', async () => {
sinon.stub(t, 'getDistFilename').callsFake(() => {
return TEMP_DIST_FILENAME;
});
sinon.stub(t, 'getMapperFile').callsFake(() => {
return TEMP_DIST_MAPPER_JS_FILE;
});
sinon.stub(t, 'getDynamicRoutesFileContent').callsFake(() => {
return 'Some test file content';
});

await t.generateDynamicRoutesFile();
const content = await readFile(TEMP_DIST_FILENAME, 'utf-8');
assert.strictEqual(content, 'Some test file content');
});
});
});
Loading