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 && ( -