diff --git a/public/jsons/best-practices/frontend-performance.json b/public/jsons/best-practices/frontend-performance.json
index fce289593..9c8e73e2b 100644
--- a/public/jsons/best-practices/frontend-performance.json
+++ b/public/jsons/best-practices/frontend-performance.json
@@ -1 +1,2032 @@
-{"mockup":{"controls":{"control":[{"ID":"14805","typeID":"TextArea","zOrder":"7","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"245","y":"461"},{"ID":"14806","typeID":"Label","zOrder":"8","measuredW":"251","measuredH":"28","x":"291","y":"464","properties":{"size":"20","text":"Minimize number of iframes"}},{"ID":"14813","typeID":"Label","zOrder":"9","measuredW":"327","measuredH":"40","x":"676","y":"216","properties":{"text":"Frontend Performance","size":"32"}},{"ID":"14848","typeID":"Canvas","zOrder":"10","w":"361","h":"150","measuredW":"100","measuredH":"70","x":"1089","y":"148"},{"ID":"14849","typeID":"Label","zOrder":"11","measuredW":"332","measuredH":"26","x":"1105","y":"166","properties":{"text":"Find the detailed version of this checklist","size":"18"}},{"ID":"14850","typeID":"Label","zOrder":"12","measuredW":"318","measuredH":"26","x":"1105","y":"194","properties":{"size":"18","text":"With details on how to implement these"}},{"ID":"14853","typeID":"Canvas","zOrder":"15","w":"373","h":"169","measuredW":"100","measuredH":"70","x":"226","y":"138"},{"ID":"14854","typeID":"__group__","zOrder":"16","measuredW":"191","measuredH":"27","w":"191","h":"27","x":"256","y":"212","properties":{"controlName":"ext_link:roadmap.sh/frontend"},"children":{"controls":{"control":[{"ID":"0","typeID":"Label","zOrder":"0","measuredW":"158","measuredH":"26","x":"33","y":"0","properties":{"size":"18","text":"Frontend Roadmap"}},{"ID":"1","typeID":"__group__","zOrder":"1","measuredW":"24","measuredH":"24","w":"24","h":"24","x":"0","y":"3","children":{"controls":{"control":[{"ID":"0","typeID":"Icon","zOrder":"0","measuredW":"24","measuredH":"24","x":"0","y":"0","properties":{"color":"16777215","icon":{"ID":"circle","size":"small"}}},{"ID":"1","typeID":"Icon","zOrder":"1","measuredW":"24","measuredH":"24","x":"0","y":"0","properties":{"color":"10066329","icon":{"ID":"check-circle","size":"small"}}}]}}}]}}},{"ID":"14855","typeID":"__group__","zOrder":"17","measuredW":"202","measuredH":"27","w":"202","h":"27","x":"256","y":"248","properties":{"controlName":"ext_link:roadmap.sh/javascript"},"children":{"controls":{"control":[{"ID":"0","typeID":"Label","zOrder":"0","measuredW":"169","measuredH":"26","x":"33","y":"0","properties":{"size":"18","text":"JavaScript Roadmap"}},{"ID":"1","typeID":"__group__","zOrder":"1","measuredW":"24","measuredH":"24","w":"24","h":"24","x":"0","y":"3","children":{"controls":{"control":[{"ID":"0","typeID":"Icon","zOrder":"0","measuredW":"24","measuredH":"24","x":"0","y":"0","properties":{"color":"16777215","icon":{"ID":"circle","size":"small"}}},{"ID":"1","typeID":"Icon","zOrder":"1","measuredW":"24","measuredH":"24","x":"0","y":"0","properties":{"color":"10066329","icon":{"ID":"check-circle","size":"small"}}}]}}}]}}},{"ID":"14856","typeID":"Label","zOrder":"18","measuredW":"209","measuredH":"32","x":"256","y":"162","properties":{"size":"24","text":"Related Roadmaps"}},{"ID":"14857","typeID":"Arrow","zOrder":"19","w":"1","h":"76","measuredW":"150","measuredH":"100","x":"802","y":"124","properties":{"curvature":"0","leftArrow":"false","rightArrow":"false","p0":{"x":0,"y":0},"p1":{"x":0.49999999999999994,"y":0},"p2":{"x":0,"y":76.17275043077757},"stroke":"dotted","color":"10027263"}},{"ID":"14882","typeID":"TextArea","zOrder":"20","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"849","y":"461"},{"ID":"14883","typeID":"Label","zOrder":"21","measuredW":"481","measuredH":"28","x":"897","y":"462","properties":{"size":"20","text":"Minified HTML - Remove comments and whitespaces"}},{"ID":"14915","typeID":"TextArea","zOrder":"22","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"849","y":"500"},{"ID":"14916","typeID":"Label","zOrder":"23","measuredW":"271","measuredH":"28","x":"897","y":"501","properties":{"text":"Use Content Delivery Network","size":"20"}},{"ID":"14920","typeID":"TextArea","zOrder":"24","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"850","y":"1108"},{"ID":"14921","typeID":"Label","zOrder":"25","measuredW":"274","measuredH":"28","x":"893","y":"1111","properties":{"size":"20","text":"Pre-load URLs where possible"}},{"ID":"14922","typeID":"TextArea","zOrder":"26","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"245","y":"501"},{"ID":"14923","typeID":"Label","zOrder":"27","measuredW":"461","measuredH":"28","x":"291","y":"504","properties":{"size":"20","text":"Minified CSS - Remove comments, whitespaces etc"}},{"ID":"14926","typeID":"TextArea","zOrder":"28","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"850","y":"1147"},{"ID":"14928","typeID":"Label","zOrder":"29","measuredW":"307","measuredH":"28","x":"893","y":"1148","properties":{"size":"20","text":"Concatenate CSS into a single file"}},{"ID":"14929","typeID":"TextArea","zOrder":"30","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"245","y":"540"},{"ID":"14930","typeID":"Label","zOrder":"31","measuredW":"237","measuredH":"28","x":"291","y":"543","properties":{"size":"20","text":"CSS files are non-blocking"}},{"ID":"14931","typeID":"TextArea","zOrder":"32","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"850","y":"1190"},{"ID":"14932","typeID":"Label","zOrder":"33","measuredW":"191","measuredH":"28","x":"894","y":"1191","properties":{"size":"20","text":"Remove unused CSS"}},{"ID":"14933","typeID":"TextArea","zOrder":"34","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"245","y":"579"},{"ID":"14934","typeID":"Label","zOrder":"35","measuredW":"389","measuredH":"28","x":"291","y":"582","properties":{"size":"20","text":"Inline the Critical CSS (above the fold CSS)"}},{"ID":"14935","typeID":"TextArea","zOrder":"36","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"245","y":"619"},{"ID":"14936","typeID":"Label","zOrder":"37","measuredW":"299","measuredH":"28","x":"291","y":"622","properties":{"size":"20","text":"Avoid the embedded / inline CSS"}},{"ID":"14937","typeID":"TextArea","zOrder":"38","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"245","y":"658"},{"ID":"14938","typeID":"Label","zOrder":"39","measuredW":"279","measuredH":"28","x":"291","y":"661","properties":{"size":"20","text":"Analyse stylesheets complexity"}},{"ID":"14939","typeID":"TextArea","zOrder":"40","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"850","y":"1232"},{"ID":"14940","typeID":"Label","zOrder":"41","measuredW":"213","measuredH":"28","x":"894","y":"1235","properties":{"size":"20","text":"Use WOFF2 font format"}},{"ID":"14941","typeID":"TextArea","zOrder":"42","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"850","y":"1272"},{"ID":"14942","typeID":"Label","zOrder":"43","measuredW":"362","measuredH":"28","x":"895","y":"1275","properties":{"size":"20","text":"Use preconnect to load your fonts faster"}},{"ID":"14943","typeID":"TextArea","zOrder":"44","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"850","y":"1311"},{"ID":"14944","typeID":"Label","zOrder":"45","measuredW":"326","measuredH":"28","x":"895","y":"1314","properties":{"size":"20","text":"Keep the web font size under 300kb"}},{"ID":"14945","typeID":"TextArea","zOrder":"46","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"850","y":"1351"},{"ID":"14946","typeID":"Label","zOrder":"47","measuredW":"269","measuredH":"28","x":"895","y":"1354","properties":{"size":"20","text":"Prevent Flash or Invisible Text"}},{"ID":"14947","typeID":"TextArea","zOrder":"48","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"245","y":"696"},{"ID":"14948","typeID":"Label","zOrder":"49","measuredW":"458","measuredH":"28","x":"291","y":"697","properties":{"size":"20","text":"Compress your images / keep the image count low"}},{"ID":"14949","typeID":"TextArea","zOrder":"50","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"245","y":"736"},{"ID":"14950","typeID":"Label","zOrder":"51","measuredW":"362","measuredH":"28","x":"291","y":"737","properties":{"size":"20","text":"Choose your image format appropriately"}},{"ID":"14951","typeID":"TextArea","zOrder":"52","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"849","y":"540"},{"ID":"14952","typeID":"Label","zOrder":"53","measuredW":"477","measuredH":"28","x":"897","y":"541","properties":{"size":"20","text":"Prefer using vector image rather than bitmap images"}},{"ID":"14953","typeID":"TextArea","zOrder":"54","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"849","y":"580"},{"ID":"14954","typeID":"Label","zOrder":"55","measuredW":"501","measuredH":"28","x":"897","y":"581","properties":{"size":"20","text":"Set width and height attributes on images (aspect ratio)"}},{"ID":"14955","typeID":"TextArea","zOrder":"56","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"849","y":"620"},{"ID":"14956","typeID":"Label","zOrder":"57","measuredW":"251","measuredH":"28","x":"897","y":"621","properties":{"size":"20","text":"Avoid using Base64 images"}},{"ID":"14957","typeID":"TextArea","zOrder":"58","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"849","y":"660"},{"ID":"14958","typeID":"Label","zOrder":"59","measuredW":"311","measuredH":"28","x":"897","y":"661","properties":{"size":"20","text":"Offscreen images are loaded lazily"}},{"ID":"14959","typeID":"TextArea","zOrder":"60","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"849","y":"700"},{"ID":"14960","typeID":"Label","zOrder":"61","measuredW":"516","measuredH":"28","x":"897","y":"701","properties":{"size":"20","text":"Ensure to serve images that are close to your display size"}},{"ID":"14961","typeID":"TextArea","zOrder":"62","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"245","y":"777"},{"ID":"14962","typeID":"Label","zOrder":"63","measuredW":"194","measuredH":"28","x":"291","y":"778","properties":{"size":"20","text":"Minify your JavaScript"}},{"ID":"14964","typeID":"TextArea","zOrder":"64","w":"29","h":"30","measuredW":"200","measuredH":"140","x":"849","y":"740"},{"ID":"14965","typeID":"Label","zOrder":"65","measuredW":"450","measuredH":"28","x":"897","y":"741","properties":{"size":"20","text":"Avoid multiple inline JavaScript snippets
+
diff --git a/src/components/InteractiveRoadmap/InteractiveRoadmap.css b/src/components/FrameRenderer/FrameRenderer.css
similarity index 100%
rename from src/components/InteractiveRoadmap/InteractiveRoadmap.css
rename to src/components/FrameRenderer/FrameRenderer.css
diff --git a/src/components/InteractiveRoadmap/roadmap.js b/src/components/FrameRenderer/renderer.js
similarity index 96%
rename from src/components/InteractiveRoadmap/roadmap.js
rename to src/components/FrameRenderer/renderer.js
index 60c4e4ee1..4ea986c31 100644
--- a/src/components/InteractiveRoadmap/roadmap.js
+++ b/src/components/FrameRenderer/renderer.js
@@ -6,7 +6,7 @@ import { Sharer } from './sharer';
* @typedef {{ roadmapId: string, jsonUrl: string }} RoadmapConfig
*/
-export class Roadmap {
+export class Renderer {
/**
* @param {RoadmapConfig} config
*/
@@ -98,8 +98,8 @@ export class Roadmap {
}
}
-const roadmap = new Roadmap();
-roadmap.init();
+const renderer = new Renderer();
+renderer.init();
// Initialize the topic loader
const topic = new Topic();
diff --git a/src/components/InteractiveRoadmap/sharer.js b/src/components/FrameRenderer/sharer.js
similarity index 100%
rename from src/components/InteractiveRoadmap/sharer.js
rename to src/components/FrameRenderer/sharer.js
diff --git a/src/components/InteractiveRoadmap/topic.js b/src/components/FrameRenderer/topic.js
similarity index 100%
rename from src/components/InteractiveRoadmap/topic.js
rename to src/components/FrameRenderer/topic.js
diff --git a/src/lib/best-practice-topic.ts b/src/lib/best-practice-topic.ts
new file mode 100644
index 000000000..208e8c17d
--- /dev/null
+++ b/src/lib/best-practice-topic.ts
@@ -0,0 +1,71 @@
+import type { MarkdownFileType } from './file';
+import type { BestPracticeFrontmatter } from './best-pratice';
+
+// Generates URL from the topic file path e.g.
+// -> /src/best-practices/frontend-performance/content/100-use-https-everywhere
+// /best-practices/frontend-performance/use-https-everywhere
+// -> /src/best-practices/frontend-performance/content/102-use-cdn-for-static-assets
+// /best-practices/use-cdn-for-static-assets
+function generateTopicUrl(filePath: string) {
+ return filePath
+ .replace('/src/best-practices/', '/') // Remove the base `/src/best-practices` from path
+ .replace('/content', '') // Remove the `/[bestPracticeId]/content`
+ .replace(/\/\d+-/g, '/') // Remove ordering info `/101-ecosystem`
+ .replace(/\/index\.md$/, '') // Make the `/index.md` to become the parent folder only
+ .replace(/\.md$/, ''); // Remove `.md` from the end of file
+}
+
+interface TopicFileType {
+ url: string;
+ heading: string;
+ file: MarkdownFileType;
+ bestPractice: BestPracticeFrontmatter;
+ bestPracticeId: string;
+}
+
+/**
+ * Gets all the topic files available for all the best practices
+ * @returns Hashmap containing the topic slug and the topic file content
+ */
+async function getTopicFiles(): Promise> {
+ const contentFiles = await import.meta.glob('/src/best-practices/*/content/**/*.md', {
+ eager: true,
+ });
+
+ const mapping: Record = {};
+
+ for (let filePath of Object.keys(contentFiles)) {
+ const fileContent: MarkdownFileType = contentFiles[filePath] as any;
+ const fileHeadings = fileContent.getHeadings();
+ const firstHeading = fileHeadings[0];
+
+ const [, bestPracticeId] = filePath.match(/^\/src\/best-practices\/(.+)?\/content\/(.+)?$/) || [];
+ const topicUrl = generateTopicUrl(filePath);
+
+ const currentBestPractice = await import(`../best-practices/${bestPracticeId}/${bestPracticeId}.md`);
+
+ mapping[topicUrl] = {
+ url: topicUrl,
+ heading: firstHeading?.text,
+ file: fileContent,
+ bestPractice: currentBestPractice.frontmatter,
+ bestPracticeId: bestPracticeId,
+ };
+ }
+
+ return mapping;
+}
+
+/**
+ * Gets the the topics for a given best practice
+ *
+ * @param bestPracticeId BestPractice id for which you want the topics
+ *
+ * @returns Promise
+ */
+export async function getTopicsByBestPracticeId(bestPracticeId: string): Promise {
+ const topicFileMapping = await getTopicFiles();
+ const allTopics = Object.values(topicFileMapping);
+
+ return allTopics.filter((topic) => topic.bestPracticeId === bestPracticeId);
+}
diff --git a/src/lib/topic.ts b/src/lib/roadmap-topic.ts
similarity index 83%
rename from src/lib/topic.ts
rename to src/lib/roadmap-topic.ts
index 2c15fc248..75553652b 100644
--- a/src/lib/topic.ts
+++ b/src/lib/roadmap-topic.ts
@@ -21,10 +21,7 @@ function generateTopicUrl(filePath: string) {
* @param topicUrl Topic URL for which breadcrumbs are required
* @param topicFiles Topic file mapping to read the topic data from
*/
-function generateBreadcrumbs(
- topicUrl: string,
- topicFiles: Record
-): BreadcrumbItem[] {
+function generateBreadcrumbs(topicUrl: string, topicFiles: Record): BreadcrumbItem[] {
// We need to collect all the pages with permalinks to generate breadcrumbs
// e.g. /backend/internet/how-does-internet-work/http
// /backend
@@ -43,7 +40,7 @@ function generateBreadcrumbs(
// -> [ '' ]
// -> [ '', 'vue' ]
if (subLinks.length > 2) {
- breadcrumbUrls.push(subLinks.join('/'));
+ breadcrumbUrls.push(subLinks.join('/'));
}
}
@@ -64,7 +61,7 @@ export type BreadcrumbItem = {
url: string;
};
-export interface TopicFileType {
+export interface RoadmapTopicFileType {
url: string;
heading: string;
file: MarkdownFileType;
@@ -77,28 +74,22 @@ export interface TopicFileType {
* Gets all the topic files available for all the roadmaps
* @returns Hashmap containing the topic slug and the topic file content
*/
-export async function getTopicFiles(): Promise> {
- const contentFiles = await import.meta.glob(
- '/src/roadmaps/*/content/**/*.md',
- {
- eager: true,
- }
- );
+export async function getRoadmapTopicFiles(): Promise> {
+ const contentFiles = await import.meta.glob('/src/roadmaps/*/content/**/*.md', {
+ eager: true,
+ });
- const mapping: Record = {};
+ const mapping: Record = {};
for (let filePath of Object.keys(contentFiles)) {
const fileContent: MarkdownFileType = contentFiles[filePath] as any;
const fileHeadings = fileContent.getHeadings();
const firstHeading = fileHeadings[0];
- const [, roadmapId] =
- filePath.match(/^\/src\/roadmaps\/(.+)?\/content\/(.+)?$/) || [];
+ const [, roadmapId] = filePath.match(/^\/src\/roadmaps\/(.+)?\/content\/(.+)?$/) || [];
const topicUrl = generateTopicUrl(filePath);
- const currentRoadmap = await import(
- `../roadmaps/${roadmapId}/${roadmapId}.md`
- );
+ const currentRoadmap = await import(`../roadmaps/${roadmapId}/${roadmapId}.md`);
mapping[topicUrl] = {
url: topicUrl,
@@ -112,11 +103,7 @@ export async function getTopicFiles(): Promise> {
// Populate breadcrumbs inside the mapping
Object.keys(mapping).forEach((topicUrl) => {
- const {
- roadmap: currentRoadmap,
- roadmapId,
- file: currentTopic,
- } = mapping[topicUrl];
+ const { roadmap: currentRoadmap, roadmapId, file: currentTopic } = mapping[topicUrl];
const roadmapUrl = `/${roadmapId}`;
// Breadcrumbs for the file
@@ -170,15 +157,15 @@ export async function getTopicFiles(): Promise> {
// '/frontend/version-control-systems/basic-usage-of-git',
// '/frontend/version-control-systems'
// ]
-export async function sortTopics(topics: TopicFileType[]): Promise {
- let sortedTopics: TopicFileType[] = [];
+async function sortTopics(topics: RoadmapTopicFileType[]): Promise {
+ let sortedTopics: RoadmapTopicFileType[] = [];
// For each of the topic, find its place in the sorted topics
for (let i = 0; i < topics.length; i++) {
const currTopic = topics[i];
const currUrl = currTopic.url;
let isPlaced = false;
-
+
// Find the first sorted topic which starts with the current topic
for (let j = 0; j < sortedTopics.length; j++) {
const sortedTopic = sortedTopics[j];
@@ -205,12 +192,10 @@ export async function sortTopics(topics: TopicFileType[]): Promise
*/
-export async function getTopicsByRoadmapId(roadmapId: string): Promise {
- const topicFileMapping = await getTopicFiles();
+export async function getTopicsByRoadmapId(roadmapId: string): Promise {
+ const topicFileMapping = await getRoadmapTopicFiles();
const allTopics = Object.values(topicFileMapping);
- const roadmapTopics = allTopics.filter(
- (topic) => topic.roadmapId === roadmapId
- );
+ const roadmapTopics = allTopics.filter((topic) => topic.roadmapId === roadmapId);
return sortTopics(roadmapTopics);
-}
\ No newline at end of file
+}
diff --git a/src/pages/[roadmapId]/[...topicId].astro b/src/pages/[roadmapId]/[...topicId].astro
index e205eae1e..6bbb9da57 100644
--- a/src/pages/[roadmapId]/[...topicId].astro
+++ b/src/pages/[roadmapId]/[...topicId].astro
@@ -2,10 +2,10 @@
import Breadcrumbs from '../../components/Breadcrumbs.astro';
import RoadmapBanner from '../../components/RoadmapBanner.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
-import { getTopicFiles, TopicFileType } from '../../lib/topic';
+import { getRoadmapTopicFiles, RoadmapTopicFileType } from '../../lib/roadmap-topic';
export async function getStaticPaths() {
- const topicPathMapping = await getTopicFiles();
+ const topicPathMapping = await getRoadmapTopicFiles();
return Object.keys(topicPathMapping).map((topicSlug) => {
const topicDetails = topicPathMapping[topicSlug];
@@ -23,7 +23,7 @@ export async function getStaticPaths() {
}
const { topicId } = Astro.params;
-const { file, breadcrumbs, roadmapId, roadmap, heading } = Astro.props as TopicFileType;
+const { file, breadcrumbs, roadmapId, roadmap, heading } = Astro.props as RoadmapTopicFileType;
---
{
roadmapId: string;
diff --git a/src/pages/best-practices/[bestPracticeId].astro b/src/pages/best-practices/[bestPracticeId].astro
index 6651ff4d0..81c81c7e1 100644
--- a/src/pages/best-practices/[bestPracticeId].astro
+++ b/src/pages/best-practices/[bestPracticeId].astro
@@ -1,11 +1,11 @@
---
import BestPracticeHeader from '../../components/BestPracticeHeader.astro';
import CaptchaScripts from '../../components/Captcha/CaptchaScripts.astro';
-import InteractiveRoadmap from '../../components/InteractiveRoadmap/InteractiveRoadmap.astro';
+import FrameRenderer from '../../components/FrameRenderer/FrameRenderer.astro';
import MarkdownFile from '../../components/MarkdownFile.astro';
import UpcomingForm from '../../components/UpcomingForm.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
-import { BestPracticeFrontmatter,getBestPracticeIds } from '../../lib/best-pratice';
+import { BestPracticeFrontmatter, getBestPracticeIds } from '../../lib/best-pratice';
import { generateArticleSchema } from '../../lib/jsonld-schema';
export async function getStaticPaths() {
@@ -59,7 +59,7 @@ if (bestPracticeData.schema) {
{
!bestPracticeData.isUpcoming && bestPracticeData.jsonUrl && (
-