Compare commits

...

56 Commits

Author SHA1 Message Date
Kamran Ahmed
83504f495b Merge branch 'master' into content/spring-boot 2023-01-12 14:38:38 +04:00
Kamran Ahmed
fc6e8048a7 Update content files 2023-01-12 14:35:57 +04:00
Kamran Ahmed
8264c4509f Update canonicals 2023-01-11 22:28:41 +04:00
mouaaz
fa9d8d6656 Initial commit 2023-01-11 21:49:06 +05:00
Kamran Ahmed
6c8aea98da Rename Software Design and Architecture Roadmap 2023-01-11 20:12:49 +04:00
Kamran Ahmed
64ccd02d53 Fix broken popup 2023-01-11 17:12:19 +04:00
Kamran Ahmed
f8c1c6278b Refactor HTML event handlers 2023-01-11 00:08:49 +04:00
Kamran Ahmed
4786265e04 Refactor event tracking implementation 2023-01-10 23:57:41 +04:00
Kamran Ahmed
8badf383b2 Responsiveness changes 2023-01-10 23:26:29 +04:00
Kamran Ahmed
c4406b7649 Add meta text below roadmap topic for contribution 2023-01-10 19:39:43 +04:00
Kamran Ahmed
1e878069bc Add free eBook link 2023-01-10 19:11:49 +04:00
Kamran Ahmed
8234de2b8c Fix capitalization of word 2023-01-10 18:35:57 +04:00
Kamran Ahmed
3466708ed4 Add FAQs to the backend roadmap 2023-01-10 18:33:19 +04:00
Kamran Ahmed
b440fd9787 Add Spring Boot terminology content 2023-01-10 16:47:56 +04:00
Kamran Ahmed
9bc73ab738 Update FAQs on frontend developer roadmap 2023-01-10 16:10:56 +04:00
Kamran Ahmed
91c16a5e32 Minor FAQ change 2023-01-10 15:49:52 +04:00
Kamran Ahmed
1768150fb1 Fix color of the code items 2023-01-10 15:42:03 +04:00
Kamran Ahmed
4a1374c978 Add FAQs to the frontend roadmap 2023-01-10 15:39:10 +04:00
Kamran Ahmed
43df31b312 Remove duplicate go roadmap 2023-01-10 11:28:16 +04:00
Kamran Ahmed
2037edb2da Add content for spring configuration 2023-01-10 03:22:03 +04:00
Kamran Ahmed
de8a4d4acf Add build script 2023-01-10 03:12:25 +04:00
Kamran Ahmed
a67a27299e Add functionality to create content directory for a roadmap 2023-01-10 03:11:51 +04:00
Kamran Ahmed
5d164198d4 Delete migration scripts and grouping on roadmap 2023-01-10 02:58:07 +04:00
Kamran Ahmed
a76b9d9ac0 Add roadmap link to PDFs page 2023-01-09 19:56:24 +04:00
Kamran Ahmed
6ed83349ba Add spring boot roadmap link 2023-01-09 19:55:01 +04:00
Kamran Ahmed
1b21550e48 Add spring boot roadmap pdf 2023-01-09 19:53:08 +04:00
Kamran Ahmed
c1d0ff7ea2 Add spring boot roadmap 2023-01-09 19:51:47 +04:00
Kamran Ahmed
26125fc6d7 Add software architect roadmap 2023-01-09 19:40:44 +04:00
Kamran Ahmed
aff7d8eece Add events to the subscription forms 2023-01-09 19:36:45 +04:00
Kamran Ahmed
fd939f198a Fix popup not working on roadmaps 2023-01-09 18:01:49 +04:00
Kamran Ahmed
18e4804a51 Add command to collect links from roadmaps 2023-01-09 15:37:10 +04:00
Sricharan Krishnan
ed8bf11150 Add resources for React (#3264)
* [Build] React Roadmap

Additional Suggestions for 103 Refs
1. Web Dev Simplied - a youtube video that explains the use of refs and what it can do for us
2. A content blog from 'Dmitri Pavlutin' website

* [Build] 104-React Events

I've added an additional description text in the main markdown file to help other learners/readers
keep in mind how important it is to make sure that we write code that is easy to understand. Do take
a look.

* Update src/roadmaps/react/content/103-rendering/104-events.md

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
2023-01-09 14:02:22 +04:00
Fred Vasquez
61f088d42a Add content for GraphQL Introduction (#3268)
Adding introduction to topic and useful links.
2023-01-09 14:01:30 +04:00
Tilen Pogačnik
faee01b22d Fix: remove text highlight in links (#3273) 2023-01-09 14:00:52 +04:00
Kamran Ahmed
dc56ef6190 Remove broken link 2023-01-09 14:00:15 +04:00
Kamran Ahmed
f393a23994 Add script for upgrading dependencies 2023-01-09 13:59:34 +04:00
Kamran Ahmed
8e61330080 Rename sha to commitUrl 2023-01-08 06:48:51 +04:00
Kamran Ahmed
2c18529429 Add commit url meta attribute 2023-01-08 06:38:50 +04:00
Kamran Ahmed
88ff836bfb Fix canonicals 2023-01-08 06:29:38 +04:00
Kamran Ahmed
66cb4f9a06 Fix canonicals 2023-01-08 06:25:34 +04:00
Ansat
d9697b74fd fix: remove text highlight in links (#3265) 2023-01-08 00:16:21 +04:00
Kamran Ahmed
863b7fa08b Make signup page no-index 2023-01-07 13:45:31 +04:00
Kamran Ahmed
64078f9d1a Update homepage title 2023-01-07 13:44:10 +04:00
Kamran Ahmed
5f8ead3d2f Fix long titles, multiple headings and redirect links 2023-01-07 13:38:23 +04:00
Kamran Ahmed
cb16abc8e1 Add alt attributes to all images 2023-01-07 13:23:43 +04:00
Kamran Ahmed
52d00a0654 Add canonicals and fix og:url on all pages 2023-01-06 22:08:49 +04:00
Kamran Ahmed
d5495f7280 Reduce file sizes 2023-01-06 21:45:11 +04:00
Kamran Ahmed
564c9fdd4f Fix broken URLs 2023-01-06 21:42:58 +04:00
Kamran Ahmed
e75df0ef9e Fix broken roadmap URL 2023-01-06 21:41:13 +04:00
Kamran Ahmed
642cbbf6d3 Fix star count is displaying NaN 2023-01-06 18:51:13 +04:00
Hossein zare
032602ad3b Rename raywenderlich to Kodeco (#3243)
raywenderlich's team has changed their name to Kodeco.

https://www.kodeco.com/36641071-introducing-kodeco-the-new-raywenderlich-com
2023-01-06 15:54:36 +04:00
Benson Arafat
522f16957a Update content in Dart (#3244)
* Updated 100 Dart Basics 

Dart can also be used to build server and desktop applications.

* Updated 103 Functions

Explanation about dart functions
2023-01-06 15:52:59 +04:00
Sricharan Krishnan
1f3bf761cd Add resources for React (#3245)
* [Build] React Roadmap

1. Came across two other interesting topics related to props and state in react
2. Would like to suggest that we add these to the list as well as they highlight something important
3. One is from robinwieruch and the other from Dominik Dorfmeister (TkDodo)

* [Build] React Roadmap Contributions

1. Added one topic for Composition vs Inheritance
2. Added topics for Props vs State
3. Added topic for Render Props
2023-01-06 15:52:32 +04:00
Haril Song
f76f0ea1a6 Fix typo in gRPC (#3248) 2023-01-06 15:48:48 +04:00
Kamran Ahmed
a40457edc8 Rename README.md to readme.md 2023-01-05 21:15:41 +04:00
Kamran Ahmed
076db6dd0a Fix broken image link 2023-01-05 20:24:51 +04:00
183 changed files with 3377 additions and 994 deletions

View File

@@ -1,120 +0,0 @@
const fs = require('fs');
const path = require('path');
// 1 - Renames each readme.md to index.md
// e.g.
// before => roadmaps/frontend/content/internet/readme.md
// after => roadmaps/frontend/content/internet/index.md
//
// 2 - Replaces the resource tags with short codes
// e.g.
// <ResourceGroupTitle>Free Content</ResourceGroupTitle>
// <BadgeLink colorScheme='yellow' badgeText='Read' href='https://www.w3schools.com/css/'>W3Schools — Learn CSS</BadgeLink>
//
// {% resources %}
// {% Blog "https://www.w3schools.com/css/", "W3Schools — Learn CSS" %}
// {% endresources %}
//
// 3 - Removes the index.md file from within the content dir i.e. to avoid `/frontend` permalink for `/frontend/index.md`
// Because we have the `/frontend` permalink serving the actual roadmap and not any content
const roadmapsDir = path.join(__dirname, '../src/roadmaps');
const roadmapDirs = fs.readdirSync(roadmapsDir);
roadmapDirs.forEach((roadmapDirName) => {
const roadmapDirPath = path.join(roadmapsDir, roadmapDirName);
const contentDirPath = path.join(roadmapDirPath, 'content');
console.log(`[Start] == Migrating ${roadmapDirName}`);
if (!fs.existsSync(contentDirPath)) {
console.log(`Content dir not found ${roadmapDirName}/content`);
return;
}
function handleContentDir(parentDirPath) {
const dirChildrenNames = fs.readdirSync(parentDirPath);
dirChildrenNames.forEach((dirChildName) => {
let dirChildPath = path.join(parentDirPath, dirChildName);
// If directory, handle the children for it
if (fs.lstatSync(dirChildPath).isDirectory()) {
handleContentDir(dirChildPath);
}
//////////////////////////////////////////////////////////
// 1 - Rename directories to remove the numbers
//////////////////////////////////////////////////////////
// let newDirChildPath = path.join(
// path.dirname(dirChildPath),
// path.basename(dirChildPath).replace(/^\d+-/, '')
// );
// fs.renameSync(dirChildPath, dirChildPath);
//////////////////////////////////////////////////////////
// 1 - Rename readme.md to index.md
//////////////////////////////////////////////////////////
if (dirChildPath.endsWith('readme.md')) {
const newFilePath = path.join(path.dirname(dirChildPath), `index.md`);
fs.renameSync(dirChildPath, newFilePath);
dirChildPath = newFilePath;
}
//////////////////////////////////////////////////////////
// 2 - Replace the resource tags with short codes
//////////////////////////////////////////////////////////
if (fs.lstatSync(dirChildPath).isFile()) {
const fileContent = fs.readFileSync(dirChildPath, 'utf-8');
let resourceLinks = [...fileContent.matchAll(/<BadgeLink.+<\/BadgeLink>/g)].map(([fullMatch]) => {
// const resourceType = fullMatch.match(/badgeText=["'](.+?)["']/)[1];
const link = fullMatch.match(/href=["'](.+?)["']/)[1];
const text = fullMatch.match(/>([^<]+)<\/BadgeLink>$/)[1];
return `- [${text.replaceAll(/['"]/g, '')}](${link})`;
});
//////////////////////////////////////////////////////////////////////
// Replace the dedicated roadmap tag with the short code
//////////////////////////////////////////////////////////////////////
// prettier-ignore
const dedicatedRegex = /<DedicatedRoadmap\s*href=['"](.+?)['"]\s*title=['"](.+?)['"]\s*description=['"].+?['"]\s*\/>/;
const dedicatedMatches = fileContent.match(dedicatedRegex);
if (dedicatedMatches) {
const [, href, title] = dedicatedMatches;
resourceLinks = [`- [Visit Dedicated ${title}](${href})`, ...resourceLinks];
}
resourceLinks = ['Visit the following resources to learn more:\n', ...resourceLinks];
resourceLinks = resourceLinks.join('\n');
let newFileContent = fileContent.replace(
/<ResourceGroupTitle>([^<\/BadgeLink>]|\S|\s)+<\/BadgeLink>/,
resourceLinks
);
// In case if the resources were not wrapped in <ResourceGroupTitle>
newFileContent = newFileContent.replace(
/<BadgeLink([^<\/BadgeLink>]|\S|\s)+<\/BadgeLink>/,
resourceLinks
);
fs.writeFileSync(dirChildPath, newFileContent);
}
});
}
handleContentDir(contentDirPath);
// 3 - Removes the index.md file from within the content dir i.e. to avoid `/frontend` permalink for `/frontend/index.md`
// Because we have the `/frontend` permalink serving the actual roadmap and not any content
const contentRootFile = path.join(contentDirPath, '/index.md');
if (fs.existsSync(contentRootFile)) {
fs.rmSync(contentRootFile);
}
console.log(` == Migrated ${roadmapDirName}`);
});

View File

@@ -1,83 +0,0 @@
const fs = require('fs');
const path = require('path');
const yaml = require('json-to-pretty-yaml');
const contentDirPath = path.join(__dirname, './developer-roadmap/content');
const guides = require('./developer-roadmap/content/guides.json');
const authors = require('./developer-roadmap/content/authors.json');
const guideImagesDirPath = path.join(__dirname, './developer-roadmap/public/guides');
const newGuideImagesDirPath = path.join(__dirname, '../public/guides');
// Remove the guide images directory
if (fs.existsSync(newGuideImagesDirPath)) {
fs.rmSync(newGuideImagesDirPath, { recursive: true });
}
fs.cpSync(guideImagesDirPath, newGuideImagesDirPath, { recursive: true });
// Remove the old guides directory
const newGuidesDirPath = path.join(__dirname, '../src/guides');
if (fs.existsSync(newGuidesDirPath)) {
fs.rmSync(newGuidesDirPath, { recursive: true });
}
fs.mkdirSync(newGuidesDirPath);
guides.forEach((guide) => {
const { id: guideId } = guide;
const originalGuidePath = path.join(contentDirPath, 'guides', `${guideId}.md`);
const newGuidePath = path.join(__dirname, `../src/guides/${guideId}.md`);
const guideWithoutFrontmatter = fs.readFileSync(originalGuidePath, 'utf8');
fs.copyFileSync(originalGuidePath, newGuidePath);
const guideAuthor = authors.find((author) => author.username === guide.authorUsername);
const guideFrontMatter = yaml
.stringify({
title: guide.title,
description: guide.description,
author: {
name: guideAuthor.name,
url: `https://twitter.com/${guideAuthor.twitter}`,
imageUrl: `${guideAuthor.picture}`,
},
seo: {
title: `${guide.title} - roadmap.sh`,
description: guide.description,
},
isNew: guide.isNew,
type: guide.type,
date: guide.createdAt.replace(/T.*/, ''),
sitemap: {
priority: 0.7,
changefreq: 'weekly',
},
tags: ['guide', `${guide.type}-guide`, `guide-sitemap`],
})
.replace(/date: "(.+?)"/, 'date: $1');
const guideWithUpdatedUrls = guideWithoutFrontmatter
.replace(/\[\!\[\]\((.+?\.png)\)\]\((.+?\.png)\)/g, '[![]($1)]($2)')
.replace(/\[\!\[\]\((.+?\.svg)\)\]\((.+?\.svg)\)/g, '[![]($1)]($2)')
.replace(/\/http/g, 'http')
.replace(/]\(\/guides\/(.+?)\.png\)/g, '](/guides/$1.png)')
.replace(/<iframe/g, '<iframe class="w-full aspect-video mb-5"')
.replace(/<iframe(.+?)\s?\/>/g, '<iframe$1></iframe>');
const guideWithFrontmatter = `---\n${guideFrontMatter}---\n\n${guideWithUpdatedUrls}`;
console.log(`Writing guide ${guideId} to disk`);
fs.writeFileSync(newGuidePath, guideWithFrontmatter);
});
const oldAuthorAssetsPath = path.join(__dirname, 'developer-roadmap/public/authors');
const newAuthorAssetsPath = path.join(__dirname, '../public/authors');
if (fs.existsSync(newAuthorAssetsPath)) {
fs.rmSync(newAuthorAssetsPath, { recursive: true });
}
fs.cpSync(oldAuthorAssetsPath, newAuthorAssetsPath, { recursive: true });

163
bin/roadmap-content.cjs Normal file
View File

@@ -0,0 +1,163 @@
const fs = require('fs');
const path = require('path');
const CONTENT_DIR = path.join(__dirname, '../content');
// Directory containing the roadmaps
const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/roadmaps');
const roadmapId = process.argv[2];
const allowedRoadmapIds = fs.readdirSync(ROADMAP_CONTENT_DIR);
if (!roadmapId) {
console.error('roadmapId is required');
process.exit(1);
}
if (!allowedRoadmapIds.includes(roadmapId)) {
console.error(`Invalid roadmap key ${roadmapId}`);
console.error(`Allowed keys are ${allowedRoadmapIds.join(', ')}`);
process.exit(1);
}
// Directory holding the roadmap content files
const roadmapDirName = fs
.readdirSync(ROADMAP_CONTENT_DIR)
.find((dirName) => dirName.replace(/\d+-/, '') === roadmapId);
if (!roadmapDirName) {
console.error('Roadmap directory not found');
process.exit(1);
}
const roadmapDirPath = path.join(ROADMAP_CONTENT_DIR, roadmapDirName);
const roadmapContentDirPath = path.join(
ROADMAP_CONTENT_DIR,
roadmapDirName,
'content'
);
// If roadmap content already exists do not proceed as it would override the files
if (fs.existsSync(roadmapContentDirPath)) {
console.error(`Roadmap content already exists @ ${roadmapContentDirPath}`);
process.exit(1);
}
function prepareDirTree(control, dirTree, dirSortOrders) {
// Directories are only created for groups
if (control.typeID !== '__group__') {
return;
}
// e.g. 104-testing-your-apps:other-options
const controlName = control?.properties?.controlName || '';
// e.g. 104
const sortOrder = controlName.match(/^\d+/)?.[0];
// No directory for a group without control name
if (!controlName || !sortOrder) {
return;
}
// e.g. testing-your-apps:other-options
const controlNameWithoutSortOrder = controlName.replace(/^\d+-/, '');
// e.g. ['testing-your-apps', 'other-options']
const dirParts = controlNameWithoutSortOrder.split(':');
// Nest the dir path in the dirTree
let currDirTree = dirTree;
dirParts.forEach((dirPart) => {
currDirTree[dirPart] = currDirTree[dirPart] || {};
currDirTree = currDirTree[dirPart];
});
dirSortOrders[controlNameWithoutSortOrder] = Number(sortOrder);
const childrenControls = control.children.controls.control;
// No more children
if (childrenControls.length) {
childrenControls.forEach((childControl) => {
prepareDirTree(childControl, dirTree, dirSortOrders);
});
}
return { dirTree, dirSortOrders };
}
const roadmap = require(path.join(__dirname, `../public/jsons/${roadmapId}`));
const controls = roadmap.mockup.controls.control;
// Prepare the dir tree that we will be creating and also calculate the sort orders
const dirTree = {};
const dirSortOrders = {};
controls.forEach((control) => {
prepareDirTree(control, dirTree, dirSortOrders);
});
/**
* @param parentDir Parent directory in which directory is to be created
* @param dirTree Nested dir tree to be created
* @param sortOrders Mapping from groupName to sort order
* @param filePaths The mapping from groupName to file path
*/
function createDirTree(parentDir, dirTree, sortOrders, filePaths = {}) {
const childrenDirNames = Object.keys(dirTree);
const hasChildren = childrenDirNames.length !== 0;
// @todo write test for this, yolo for now
const groupName = parentDir
.replace(roadmapContentDirPath, '') // Remove base dir path
.replace(/(^\/)|(\/$)/g, '') // Remove trailing slashes
.replace(/(^\d+?-)/g, '') // Remove sorting information
.replaceAll('/', ':') // Replace slashes with `:`
.replace(/:\d+-/, ':');
const humanizedGroupName = groupName
.split(':')
.pop()
?.replaceAll('-', ' ')
.replace(/^\w/, ($0) => $0.toUpperCase());
const sortOrder = sortOrders[groupName] || '';
// Attach sorting information to dirname
// e.g. /roadmaps/100-frontend/content/internet
// ———> /roadmaps/100-frontend/content/103-internet
if (sortOrder) {
parentDir = parentDir.replace(/(.+?)([^\/]+)?$/, `$1${sortOrder}-$2`);
}
// If no children, create a file for this under the parent directory
if (!hasChildren) {
let fileName = `${parentDir}.md`;
fs.writeFileSync(fileName, `# ${humanizedGroupName}`);
filePaths[groupName || 'home'] = fileName.replace(CONTENT_DIR, '');
return filePaths;
}
// There *are* children, so create the parent as a directory
// and create `index.md` as the content file for this
fs.mkdirSync(parentDir);
let readmeFilePath = path.join(parentDir, 'index.md');
fs.writeFileSync(readmeFilePath, `# ${humanizedGroupName}`);
filePaths[groupName || 'home'] = readmeFilePath.replace(CONTENT_DIR, '');
// For each of the directory names, create a
// directory inside the given directory
childrenDirNames.forEach((dirName) => {
createDirTree(
path.join(parentDir, dirName),
dirTree[dirName],
dirSortOrders,
filePaths
);
});
return filePaths;
}
// Create directories and get back the paths for created directories
createDirTree(roadmapContentDirPath, dirTree, dirSortOrders);
console.log('Created roadmap content directory structure');

44
bin/roadmap-links.cjs Normal file
View File

@@ -0,0 +1,44 @@
const fs = require('fs');
const path = require('path');
const roadmapId = process.argv[2];
if (!roadmapId) {
console.error('Error: roadmapId is required');
}
const fullPath = path.join(__dirname, `../src/roadmaps/${roadmapId}`);
if (!fs.existsSync(fullPath)) {
console.error(`Error: path not found: ${fullPath}!`);
process.exit(1);
}
function readFiles(folderPath) {
const stats = fs.lstatSync(folderPath);
if (stats.isFile()) {
return [folderPath];
}
const folderContent = fs.readdirSync(folderPath);
let files = [];
for (const file of folderContent) {
const filePath = path.join(folderPath, file);
files = [...files, ...readFiles(filePath)];
}
return files;
}
const files = readFiles(fullPath);
let allLinks = [];
files.forEach((file) => {
const fileContent = fs.readFileSync(file, 'utf-8');
const matches = [...fileContent.matchAll(/\[[^\]]+]\((https?:\/\/[^)]+)\)/g)];
allLinks = [...allLinks, ...matches.map((match) => match[1])];
});
allLinks.map((link) => console.log(link));

View File

@@ -1,116 +0,0 @@
module.exports = {
angular: {
dimensions: {
width: 968,
height: 2277.8,
},
},
'aspnet-core': {
dimensions: {
width: 968,
height: 2773.45,
},
},
backend: {
dimensions: {
width: 968,
height: 2840.4,
},
},
blockchain: {
dimensions: {
width: 968,
height: 2173.87,
},
},
'computer-science': {
dimensions: {
width: 968,
height: 3009.05,
},
},
'design-system': {
dimensions: {
width: 968,
height: 2309.7,
},
},
devops: {
dimensions: {
width: 968,
height: 2527.46,
},
},
flutter: {
dimensions: {
width: 968,
height: 2042.2,
},
},
frontend: {
dimensions: {
width: 968,
height: 2734.48,
},
},
golang: {
dimensions: {
width: 968,
height: 1495.21,
},
},
java: {
dimensions: {
width: 968,
height: 1167.29,
},
},
javascript: {
dimensions: {
width: 968,
height: 2438.9,
},
},
nodejs: {
dimensions: {
width: 968,
height: 2474.06,
},
},
python: {
dimensions: {
width: 992,
height: 1259.03,
},
},
qa: {
dimensions: {
width: 968,
height: 2107.75,
},
},
react: {
dimensions: {
width: 968,
height: 1570.26,
},
},
'software-architect': {
dimensions: {
width: 968,
height: 1882.18,
},
},
'software-design-architecture': {
dimensions: {
width: 968,
height: 1764.66,
},
},
vue: {
dimensions: {
width: 968,
height: 1657.07,
},
},
};

View File

@@ -1,132 +0,0 @@
const fs = require('fs');
const path = require('path');
const yaml = require('json-to-pretty-yaml');
const roadmapMetas = require('./roadmap-metas.cjs');
const oldAssetsPath = path.join(__dirname, 'developer-roadmap/public');
const newAssetsPath = path.join(__dirname, '../public/');
// Create JSONs dir
const newJsonsPath = path.join(newAssetsPath, 'jsons');
if (fs.existsSync(newJsonsPath)) {
fs.rmSync(newJsonsPath, { recursive: true });
}
fs.mkdirSync(newJsonsPath);
// Create PDFs dir
const newPdfsPath = path.join(newAssetsPath, 'pdfs');
if (fs.existsSync(newPdfsPath)) {
fs.rmSync(newPdfsPath, { recursive: true });
}
fs.mkdirSync(newPdfsPath);
const oldRoadmapsDirPath = path.join(__dirname, 'developer-roadmap/content/roadmaps');
const newRoadmapsDirPath = path.join(__dirname, '../src/roadmaps');
if (fs.existsSync(newRoadmapsDirPath)) {
fs.rmSync(newRoadmapsDirPath, { recursive: true });
}
fs.mkdirSync(newRoadmapsDirPath);
const oldRoadmaps = fs
.readdirSync(oldRoadmapsDirPath)
.map((roadmapDirName) => path.join(oldRoadmapsDirPath, roadmapDirName));
const orderInfo = {};
const typeCounter = {
role: 1,
tool: 1,
};
// Calculate the sorting information for the roadmaps
oldRoadmaps.forEach((oldRoadmapPath) => {
const roadmapId = path.basename(oldRoadmapPath).replace(/\d+-/g, '').toLowerCase();
const oldRoadmapMeta = require(path.join(oldRoadmapPath, 'meta.json'));
orderInfo[roadmapId] = typeCounter[oldRoadmapMeta.type];
typeCounter[oldRoadmapMeta.type] += 1;
});
// Iterate and create new roadmaps
oldRoadmaps.forEach((oldRoadmapPath) => {
const roadmapId = path.basename(oldRoadmapPath).replace(/\d+-/g, '').toLowerCase();
const metaToMerge = roadmapMetas[roadmapId] ?? {};
const oldRoadmapMeta = require(path.join(oldRoadmapPath, 'meta.json'));
const isTextual = oldRoadmapMeta?.landingPath?.endsWith('.md');
const hasContentDir = fs.existsSync(path.join(oldRoadmapPath, 'content'));
const roadmapFileContent = isTextual
? fs.readFileSync(path.join(oldRoadmapPath, oldRoadmapMeta.landingPath), 'utf8')
: '';
const roadmapFileContentWithUpdatedUrls = roadmapFileContent
.replace(/\[\!\[\]\((.+?\.png)\)\]\((.+?\.png)\)/g, '[![](/assets$1)](/assets$2)')
.replace(/\[\!\[\]\((.+?\.svg)\)\]\((.+?\.svg)\)/g, '[![](/assets$1)](/assets$2)')
.replace(/\[\!\[\]\((.+?\.svg)\)\]\((.+?\.png)\)/g, '[![](/assets$1)](/assets$2)')
.replace(/assetshttp\//g, 'http')
.replace(/assetshttps:\/\//g, 'https://')
.replace(/\/http/g, 'http')
.replace(/]\(\/roadmaps\/(.+?)\.png\)/g, '](/assets/roadmaps/$1.png)')
.replace(/]\(\/roadmaps\/(.+?)\.svg\)/g, '](/assets/roadmaps/$1.svg)')
.replace(/<iframe/g, '<iframe class="w-full aspect-video mb-5"')
.replace(/<iframe(.+?)\s?\/>/g, '<iframe$1></iframe>');
const hasJson = fs.existsSync(path.join(oldAssetsPath, `/project/${roadmapId}.json`));
const newRoadmapMeta = {
...( hasJson ? { jsonUrl: `/jsons/${roadmapId}.json`} : {}),
pdfUrl: `/pdfs/${roadmapId}.pdf`,
order: orderInfo[roadmapId],
featuredTitle:
oldRoadmapMeta.featuredTitle === 'Software Design and Architecture'
? 'Software Design'
: oldRoadmapMeta.featuredTitle,
featuredDescription: oldRoadmapMeta.featuredDescription,
title: oldRoadmapMeta.title,
description: oldRoadmapMeta.description,
isNew: oldRoadmapMeta.isNew,
hasTopics: hasContentDir,
...metaToMerge,
seo: oldRoadmapMeta.seo,
relatedRoadmaps: oldRoadmapMeta.relatedRoadmaps,
sitemap: {
priority: 1,
changefreq: 'monthly',
},
tags: ['roadmap', 'main-sitemap', `${oldRoadmapMeta.type === 'tool' ? 'skill' : oldRoadmapMeta.type}-roadmap`],
};
const frontmatter = yaml.stringify(newRoadmapMeta);
const newRoadmapDirPath = path.join(newRoadmapsDirPath, roadmapId);
const newRoadmapFilePath = path.join(newRoadmapDirPath, `/${roadmapId}.md`);
fs.mkdirSync(newRoadmapDirPath);
fs.writeFileSync(newRoadmapFilePath, `---\n${frontmatter}---\n\n${roadmapFileContentWithUpdatedUrls}`);
const jsonFile = path.join(oldAssetsPath, oldRoadmapMeta.jsonUrl || '/unknown');
const pdfFile = path.join(oldAssetsPath, oldRoadmapMeta.pdfUrl || '/unknown');
if (fs.existsSync(jsonFile)) {
fs.copyFileSync(jsonFile, path.join(newJsonsPath, `${roadmapId}.json`));
}
if (fs.existsSync(pdfFile)) {
fs.copyFileSync(pdfFile, path.join(newPdfsPath, `${roadmapId}.pdf`));
}
// Copy the content directory
const oldRoadmapContentDir = path.join(oldRoadmapPath, 'content');
if (fs.existsSync(oldRoadmapContentDir)) {
fs.cpSync(oldRoadmapContentDir, path.join(newRoadmapDirPath, 'content'), { recursive: true });
}
});
const roadmapAssets = path.join(oldAssetsPath, 'roadmaps');
if (fs.existsSync(roadmapAssets)) {
fs.cpSync(roadmapAssets, path.join(newAssetsPath, 'roadmaps'), { recursive: true });
}

View File

@@ -1,30 +0,0 @@
#!/usr/bin/env bash
set -e
# Change working directory to the directory of this script
cd "$(dirname "$0")"
if [ ! -d "./developer-roadmap" ]; then
git clone --depth 1 -b master git@github.com:kamranahmedse/developer-roadmap.git
fi
echo "Removing old directories"
rm -rf ../src/videos
rm -rf ../src/guides
rm -rf ../src/roadmaps
rm -rf ../public/jsons
rm -rf ../public/pdfs
echo "=== Migrating Roadmaps ==="
node roadmap-migrator.cjs
echo "=== Migrating Content ==="
node content-migrator.cjs
echo "=== Migrating Guides ==="
node guide-migrator.cjs
echo "=== Migrating Videos ==="
node video-migrator.cjs

View File

@@ -1,58 +0,0 @@
const fs = require('fs');
const path = require('path');
const yaml = require('json-to-pretty-yaml');
const contentDirPath = path.join(__dirname, './developer-roadmap/content');
const videos = require('./developer-roadmap/content/videos.json');
// Remove the old videos directory
const newVideosDirPath = path.join(__dirname, '../src/videos');
if (fs.existsSync(newVideosDirPath)) {
fs.rmSync(newVideosDirPath, { recursive: true });
}
fs.mkdirSync(newVideosDirPath);
videos.forEach((video) => {
const { id: videoId } = video;
const originalVideoPath = path.join(
contentDirPath,
'videos',
`${videoId}.md`
);
const newVideoPath = path.join(__dirname, `../src/videos/${videoId}.md`);
const videoWithoutFrontmatter = fs.readFileSync(originalVideoPath, 'utf8');
fs.copyFileSync(originalVideoPath, newVideoPath);
const videoFrontMatter = yaml
.stringify({
title: video.title,
description: video.description,
duration: video.duration,
isNew: video.isNew,
date: video.createdAt.replace(/T.*/, ''),
author: {
name: 'Kamran Ahmed',
url: `https://twitter.com/kamranahmedse`,
imageUrl: `/authors/kamranahmedse.jpeg`,
},
sitemap: {
priority: 0.7,
changefreq: 'weekly',
},
tags: ['video', `video-sitemap`],
})
.replace(/date: "(.+?)"/, 'date: $1');
const videoWithIframeClass = videoWithoutFrontmatter
.replace(/<iframe/g, '<iframe class="w-full aspect-video mb-5"')
.replace(/<iframe(.+?)\s?\/>/g, '<iframe$1></iframe>');
const videoWithFrontmatter = `---\n${videoFrontMatter}---\n\n${videoWithIframeClass}`;
console.log(`Writing video ${videoId} to disk`);
fs.writeFileSync(newVideoPath, videoWithFrontmatter);
});

View File

@@ -10,8 +10,10 @@
"preview": "astro preview",
"astro": "astro",
"deploy": "NODE_DEBUG=gh-pages gh-pages -d dist -t",
"sync-content": "sh ./bin/sync-content.sh",
"compress:jsons": "node bin/compress-jsons.cjs"
"compress:jsons": "node bin/compress-jsons.cjs",
"upgrade": "ncu -u",
"roadmap-links": "node bin/roadmap-links.cjs",
"roadmap-content": "node bin/roadmap-content.cjs"
},
"dependencies": {
"@astrojs/sitemap": "^1.0.0",
@@ -20,6 +22,7 @@
"astro-compress": "^1.1.24",
"astro-critters": "^1.1.24",
"node-html-parser": "^6.1.4",
"npm-check-updates": "^16.6.2",
"rehype-external-links": "^2.0.1",
"roadmap-renderer": "^1.0.1",
"tailwindcss": "^3.2.4"

1402
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 835 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

File diff suppressed because one or more lines are too long

Binary file not shown.

BIN
public/pdfs/spring-boot.pdf Normal file

Binary file not shown.

View File

@@ -1,5 +1,5 @@
<p align="center">
<img src="public/brand.png" height="128">
<img src="public/images/brand.png" height="128">
<h2 align="center"><a href="https://roadmap.sh">roadmap.sh</a></h2>
<p align="center">Community driven roadmaps, articles and resources for developers<p>
<p align="center">
@@ -48,6 +48,7 @@ Here is the list of available roadmaps with more being actively worked upon.
- [Python Roadmap](https://roadmap.sh/python)
- [Go Roadmap](https://roadmap.sh/golang)
- [Java Roadmap](https://roadmap.sh/java)
- [Spring Boot Roadmap](https://roadmap.sh/spring-boot)
- [Design System Roadmap](https://roadmap.sh/design-system)
- [DBA Roadmap](https://roadmap.sh/postgresql-dba)
- [Blockchain Roadmap](https://roadmap.sh/blockchain)

View File

@@ -2,7 +2,8 @@
---
<script src='./analytics.js'></script>
<script async src='https://www.googletagmanager.com/gtag/js?id=UA-139582634-1'></script>
<script async src='https://www.googletagmanager.com/gtag/js?id=UA-139582634-1'
></script>
<script is:inline>
// @ts-nocheck
window.dataLayer = window.dataLayer || [];
@@ -12,4 +13,29 @@
gtag('js', new Date());
gtag('config', 'UA-139582634-1');
document.addEventListener('click', (e) => {
let trackEl = e.target;
if (!trackEl.getAttribute('ga-category')) {
trackEl = trackEl.closest('[ga-category]');
}
if (!trackEl) {
return;
}
const category = trackEl.getAttribute('ga-category');
const action = trackEl.getAttribute('ga-action');
const label = trackEl.getAttribute('ga-label');
if (!category) {
return;
}
window.fireEvent({
category,
action,
label,
});
});
</script>

View File

@@ -34,8 +34,21 @@ import CaptchaFields from './Captcha/CaptchaFields.astro';
type='submit'
name='submit'
class='text-white bg-gradient-to-r from-amber-700 to-blue-800 hover:from-amber-800 hover:to-blue-900 font-regular rounded-md text-md px-5 py-2.5 w-full text-center mr-2'
submit-download-form
>
Send Link
</button>
</form>
</Popup>
<script>
document
.querySelector('[submit-download-form]')
?.addEventListener('click', () => {
window.fireEvent({
category: 'Subscription',
action: 'Submitted Popup Form',
label: 'Download Roadmap Popup',
});
});
</script>

View File

@@ -0,0 +1,3 @@
<div class='text-sm sm:text-base leading-relaxed text-left p-2 sm:p-4 text-md text-gray-800 border-t border-t-gray-300 bg-gray-100 rounded-bl-md rounded-br-md'>
<slot />
</div>

View File

@@ -0,0 +1,13 @@
<div class='border-t bg-gray-100'>
<div class='container'>
<div class='flex justify-between relative -top-5'>
<h1 class='text-sm sm:text-base font-medium py-1 px-3 border bg-white rounded-md'>
Frequently Asked Questions
</h1>
</div>
<div class='flex flex-col gap-1 pb-8'>
<slot />
</div>
</div>
</div>

View File

@@ -0,0 +1,42 @@
---
import Icon from '../Icon.astro';
export interface Props {
question: string;
isActive?: boolean;
}
const { question, isActive = false } = Astro.props;
---
<div
class='faq-item bg-white border rounded-md hover:bg-gray-50 border-gray-300'
>
<button
faq-question
class='flex flex-row justify-between items-center p-2 sm:p-3 w-full'
>
<span class='text-sm sm:text-base text-left font-medium'>{question}</span>
<Icon icon='down' class='h-6 hidden sm:block text-gray-400' />
</button>
<div class:list={['answer', { hidden: !isActive }]} faq-answer>
<slot />
</div>
</div>
<script>
document.querySelectorAll('[faq-question]').forEach((el) => {
el.addEventListener('click', () => {
// Hide any other visible answers
document.querySelectorAll('[faq-answer]').forEach((element) => {
element.classList.add('hidden');
});
// Show the current answer
const answer = el.nextElementSibling;
if (answer) {
answer.classList.remove('hidden');
}
});
});
</script>

View File

@@ -84,23 +84,29 @@ import Icon from './Icon.astro';
<div class='text-gray-400 text-sm'>
<p>
<a
href='https://thenewstack.io/category/devops/'
href='https://thenewstack.io/category/devops?utm_source=roadmap.sh&utm_medium=Referral&utm_campaign=Footer'
target='_blank'
onclick="window.fireEvent({ category: 'PartnerClick', action: `TNS Referral`, label: `TNS Referral - Footer` })"
ga-category='PartnerClick'
ga-action='TNS Referral'
ga-label='TNS Referral - Footer'
class='text-gray-400 hover:text-white'>DevOps</a
>
<span class='mx-1.5'>&middot;</span>
<a
href='https://thenewstack.io/category/kubernetes/'
href='https://thenewstack.io/category/kubernetes?utm_source=roadmap.sh&utm_medium=Referral&utm_campaign=Footer'
target='_blank'
onclick="window.fireEvent({ category: 'PartnerClick', action: `TNS Referral`, label: `TNS Referral - Footer` })"
ga-category='PartnerClick'
ga-action='TNS Referral'
ga-label='TNS Referral - Footer'
class='text-gray-400 hover:text-white'>Kubernetes</a
>
<span class='mx-1.5'>&middot;</span>
<a
href='https://thenewstack.io/category/cloud-native/'
href='https://thenewstack.io/category/cloud-native?utm_source=roadmap.sh&utm_medium=Referral&utm_campaign=Footer'
target='_blank'
onclick="window.fireEvent({ category: 'PartnerClick', action: `TNS Referral`, label: `TNS Referral - Footer` })"
ga-category='PartnerClick'
ga-action='TNS Referral'
ga-label='TNS Referral - Footer'
class='text-gray-400 hover:text-white'>Cloud-Native</a
>
</p>

View File

@@ -20,7 +20,11 @@ const { author } = frontmatter;
target='_blank'
class='font-medium hover:text-gray-600 inline-flex items-center hover:underline'
>
<img src={author.imageUrl} class='w-5 h-5 inline mr-2 rounded-full' />
<img
alt={author.name}
src={author.imageUrl}
class='w-5 h-5 inline mr-2 rounded-full'
/>
{author.name}
</a>
<span class='mx-1.5'>&middot;</span>

View File

@@ -1,10 +1,10 @@
---
import DownloadPopup from "../DownloadPopup.astro";
import Loader from "../Loader.astro";
import ShareIcons from "../ShareIcons.astro";
import SubscribePopup from "../SubscribePopup.astro";
import TopicOverlay from "../TopicOverlay.astro";
import "./InteractiveRoadmap.css";
import DownloadPopup from '../DownloadPopup.astro';
import Loader from '../Loader.astro';
import ShareIcons from '../ShareIcons.astro';
import SubscribePopup from '../SubscribePopup.astro';
import TopicOverlay from '../TopicOverlay.astro';
import './InteractiveRoadmap.css';
export interface Props {
roadmapId: string;
@@ -16,31 +16,32 @@ export interface Props {
};
}
const { roadmapId, jsonUrl, dimensions = null, description } =
Astro.props;
const { roadmapId, jsonUrl, dimensions = null, description } = Astro.props;
---
<link
rel="preload"
href="/fonts/balsamiq.woff2"
as="font"
type="font/woff2"
rel='preload'
href='/fonts/balsamiq.woff2'
as='font'
type='font/woff2'
crossorigin
slot="after-header"
slot='after-header'
/>
<div class="bg-gray-50 py-4 sm:py-12">
<div class="max-w-[1000px] container relative">
<div class='bg-gray-50 py-4 sm:py-12'>
<div class='max-w-[1000px] container relative'>
<ShareIcons
description={description}
pageUrl={`https://roadmap.sh/${roadmapId}`}
/>
<DownloadPopup />
<SubscribePopup />
<TopicOverlay />
<TopicOverlay roadmapId={roadmapId} />
<div
id="roadmap-svg"
style={dimensions ? `--aspect-ratio:${dimensions.width}/${dimensions.height}` : null}
id='roadmap-svg'
style={dimensions
? `--aspect-ratio:${dimensions.width}/${dimensions.height}`
: null}
data-roadmap-id={roadmapId}
data-json-url={jsonUrl}
>
@@ -49,4 +50,4 @@ const { roadmapId, jsonUrl, dimensions = null, description } =
</div>
</div>
<script src="./roadmap.js"></script>
<script src='./roadmap.js'></script>

View File

@@ -1,6 +1,6 @@
import { wireframeJSONToSVG } from "roadmap-renderer";
import { Topic } from "./topic";
import { Sharer } from "./sharer";
import { wireframeJSONToSVG } from 'roadmap-renderer';
import { Topic } from './topic';
import { Sharer } from './sharer';
/**
* @typedef {{ roadmapId: string, jsonUrl: string }} RoadmapConfig
@@ -11,10 +11,10 @@ export class Roadmap {
* @param {RoadmapConfig} config
*/
constructor() {
this.roadmapId = "";
this.jsonUrl = "";
this.roadmapId = '';
this.jsonUrl = '';
this.containerId = "roadmap-svg";
this.containerId = 'roadmap-svg';
this.init = this.init.bind(this);
this.onDOMLoaded = this.onDOMLoaded.bind(this);
@@ -28,10 +28,16 @@ export class Roadmap {
}
prepareConfig() {
if (!this.containerEl) {
return false;
}
const dataset = this.containerEl.dataset;
this.roadmapId = dataset.roadmapId;
this.jsonUrl = dataset.jsonUrl;
return true;
}
/**
@@ -40,7 +46,7 @@ export class Roadmap {
*/
fetchRoadmapSvg(jsonUrl) {
if (!jsonUrl) {
console.error("jsonUrl not defined in frontmatter");
console.error('jsonUrl not defined in frontmatter');
return null;
}
@@ -50,13 +56,15 @@ export class Roadmap {
})
.then(function (json) {
return wireframeJSONToSVG(json, {
fontURL: "/fonts/balsamiq.woff2",
fontURL: '/fonts/balsamiq.woff2',
});
});
}
onDOMLoaded() {
this.prepareConfig();
if (!this.prepareConfig()) {
return;
}
this.fetchRoadmapSvg(this.jsonUrl)
.then((svg) => {
@@ -66,8 +74,8 @@ export class Roadmap {
}
handleRoadmapClick(e) {
const targetGroup = e.target.closest("g") || {};
const groupId = targetGroup.dataset ? targetGroup.dataset.groupId : "";
const targetGroup = e.target.closest('g') || {};
const groupId = targetGroup.dataset ? targetGroup.dataset.groupId : '';
if (!groupId) {
return;
}
@@ -75,7 +83,7 @@ export class Roadmap {
e.stopImmediatePropagation();
window.dispatchEvent(
new CustomEvent("topic.click", {
new CustomEvent('topic.click', {
detail: {
topicId: groupId,
roadmapId: this.roadmapId,
@@ -85,8 +93,8 @@ export class Roadmap {
}
init() {
window.addEventListener("DOMContentLoaded", this.onDOMLoaded);
window.addEventListener("click", this.handleRoadmapClick);
window.addEventListener('DOMContentLoaded', this.onDOMLoaded);
window.addEventListener('click', this.handleRoadmapClick);
}
}

View File

@@ -3,7 +3,7 @@ export class Sharer {
this.init = this.init.bind(this);
this.onScroll = this.onScroll.bind(this);
this.shareIconsId = "page-share-icons";
this.shareIconsId = 'page-share-icons';
}
get shareIconsEl() {
@@ -12,14 +12,18 @@ export class Sharer {
onScroll() {
if (window.scrollY < 100 || window.innerWidth < 1050) {
this.shareIconsEl.classList.add("hidden");
this.shareIconsEl.classList.add('hidden');
return null;
}
this.shareIconsEl.classList.remove("hidden");
this.shareIconsEl.classList.remove('hidden');
}
init() {
window.addEventListener("scroll", this.onScroll, { passive: true });
if (!this.shareIconsEl) {
return;
}
window.addEventListener('scroll', this.onScroll, { passive: true });
}
}
}

View File

@@ -1,204 +1,219 @@
export class Topic {
constructor() {
this.overlayId = 'topic-overlay';
this.contentId = 'topic-content';
this.loaderId = 'topic-loader';
this.topicBodyId = 'topic-body';
this.topicActionsId = 'topic-actions';
this.markTopicDoneId = 'mark-topic-done';
this.markTopicPendingId = 'mark-topic-pending';
this.closeTopicId = 'close-topic';
this.activeRoadmapId = null;
this.activeTopicId = null;
this.handleTopicClick = this.handleTopicClick.bind(this);
this.close = this.close.bind(this);
this.resetDOM = this.resetDOM.bind(this);
this.populate = this.populate.bind(this);
this.handleOverlayClick = this.handleOverlayClick.bind(this);
this.markAsDone = this.markAsDone.bind(this);
this.markAsPending = this.markAsPending.bind(this);
this.queryRoadmapElementsByTopicId = this.queryRoadmapElementsByTopicId.bind(this);
this.init = this.init.bind(this);
constructor() {
this.overlayId = 'topic-overlay';
this.contentId = 'topic-content';
this.loaderId = 'topic-loader';
this.topicBodyId = 'topic-body';
this.topicActionsId = 'topic-actions';
this.markTopicDoneId = 'mark-topic-done';
this.markTopicPendingId = 'mark-topic-pending';
this.closeTopicId = 'close-topic';
this.contributionTextId = 'contrib-meta';
this.activeRoadmapId = null;
this.activeTopicId = null;
this.handleTopicClick = this.handleTopicClick.bind(this);
this.close = this.close.bind(this);
this.resetDOM = this.resetDOM.bind(this);
this.populate = this.populate.bind(this);
this.handleOverlayClick = this.handleOverlayClick.bind(this);
this.markAsDone = this.markAsDone.bind(this);
this.markAsPending = this.markAsPending.bind(this);
this.queryRoadmapElementsByTopicId =
this.queryRoadmapElementsByTopicId.bind(this);
this.init = this.init.bind(this);
}
get loaderEl() {
return document.getElementById(this.loaderId);
}
get markTopicDoneEl() {
return document.getElementById(this.markTopicDoneId);
}
get markTopicPendingEl() {
return document.getElementById(this.markTopicPendingId);
}
get topicActionsEl() {
return document.getElementById(this.topicActionsId);
}
get contributionTextEl() {
return document.getElementById(this.contributionTextId);
}
get contentEl() {
return document.getElementById(this.contentId);
}
get overlayEl() {
return document.getElementById(this.overlayId);
}
resetDOM(hideOverlay = false) {
if (hideOverlay) {
this.overlayEl.classList.add('hidden');
} else {
this.overlayEl.classList.remove('hidden');
}
get loaderEl() {
return document.getElementById(this.loaderId);
}
get markTopicDoneEl() {
return document.getElementById(this.markTopicDoneId);
}
get markTopicPendingEl() {
return document.getElementById(this.markTopicPendingId);
}
get topicActionsEl() {
return document.getElementById(this.topicActionsId);
}
get contentEl() {
return document.getElementById(this.contentId);
}
get overlayEl() {
return document.getElementById(this.overlayId);
}
resetDOM(hideOverlay = false) {
if (hideOverlay) {
this.overlayEl.classList.add('hidden');
} else {
this.overlayEl.classList.remove('hidden');
}
this.loaderEl.classList.remove('hidden'); // Show loader
this.topicActionsEl.classList.add('hidden'); // Hide Actions
this.contentEl.replaceChildren(''); // Remove content
}
close() {
this.resetDOM(true);
this.activeRoadmapId = null;
this.activeTopicId = null;
}
/**
* @param {string | HTMLElement} html
*/
populate(html) {
this.contentEl.replaceChildren(html);
this.loaderEl.classList.add('hidden');
this.topicActionsEl.classList.remove('hidden');
const normalizedGroup = (this.activeTopicId || '').replace(/^\d+-/, '');
const isDone = localStorage.getItem(normalizedGroup) === 'done';
if (isDone) {
this.markTopicDoneEl.classList.add('hidden');
this.markTopicPendingEl.classList.remove('hidden');
} else {
this.markTopicDoneEl.classList.remove('hidden');
this.markTopicPendingEl.classList.add('hidden');
}
}
fetchTopicHtml(roadmapId, topicId) {
const topicPartial = topicId.replace(/^\d+-/, '').replaceAll(/:/g, '/');
const fullUrl = `/${roadmapId}/${topicPartial}/`;
return fetch(fullUrl)
.then((res) => {
return res.text();
})
.then((topicHtml) => {
// It's full HTML with page body, head etc.
// We only need the inner HTML of the #main-content
const node = new DOMParser().parseFromString(topicHtml, 'text/html');
return node.getElementById('main-content');
});
}
handleTopicClick(e) {
const { roadmapId, topicId } = e.detail;
if (!topicId || !roadmapId) {
console.log('Missing topic or roadmap: ', e.detail);
return;
}
this.activeRoadmapId = roadmapId;
this.activeTopicId = topicId;
if (/^ext_link/.test(topicId)) {
window.open(`https://${topicId.replace('ext_link:', '')}`);
return;
}
this.resetDOM();
this.fetchTopicHtml(roadmapId, topicId)
.then((content) => {
this.populate(content);
})
.catch((e) => {
console.error(e);
this.populate('Error loading the content!');
});
}
queryRoadmapElementsByTopicId(topicId) {
const elements = document.querySelectorAll(`[data-group-id$="-${topicId}"]`);
const matchingElements = [];
elements.forEach((element) => {
const foundGroupId = element?.dataset?.groupId || '';
const validGroupRegex = new RegExp(`^\\d+-${topicId}$`);
if (validGroupRegex.test(foundGroupId)) {
matchingElements.push(element);
}
});
return matchingElements;
}
markAsDone(topicId) {
const updatedTopicId = topicId.replace(/^\d+-/, '');
localStorage.setItem(updatedTopicId, 'done');
this.queryRoadmapElementsByTopicId(updatedTopicId).forEach((item) => {
item?.classList?.add('done');
});
}
markAsPending(topicId) {
const updatedTopicId = topicId.replace(/^\d+-/, '');
localStorage.removeItem(updatedTopicId);
this.queryRoadmapElementsByTopicId(updatedTopicId).forEach((item) => {
item?.classList?.remove('done');
});
}
handleOverlayClick(e) {
const isClickedInsideTopic = e.target.closest(`#${this.topicBodyId}`);
if (!isClickedInsideTopic) {
this.close();
return;
}
const isClickedDone = e.target.id === this.markTopicDoneId || e.target.closest(`#${this.markTopicDoneId}`);
if (isClickedDone) {
this.markAsDone(this.activeTopicId);
this.close();
}
const isClickedPending = e.target.id === this.markTopicPendingId || e.target.closest(`#${this.markTopicPendingId}`);
if (isClickedPending) {
this.markAsPending(this.activeTopicId);
this.close();
}
const isClickedClose = e.target.id === this.closeTopicId || e.target.closest(`#${this.closeTopicId}`);
if (isClickedClose) {
this.close();
}
}
init() {
window.addEventListener('topic.click', this.handleTopicClick);
window.addEventListener('click', this.handleOverlayClick);
window.addEventListener('keydown', (e) => {
if (e.key.toLowerCase() === 'escape') {
this.close();
}
});
this.loaderEl.classList.remove('hidden'); // Show loader
this.topicActionsEl.classList.add('hidden'); // Hide Actions
this.contributionTextEl.classList.add('hidden'); // Hide contribution text
this.contentEl.replaceChildren(''); // Remove content
}
close() {
this.resetDOM(true);
this.activeRoadmapId = null;
this.activeTopicId = null;
}
/**
* @param {string | HTMLElement} html
*/
populate(html) {
this.contentEl.replaceChildren(html);
this.loaderEl.classList.add('hidden');
this.topicActionsEl.classList.remove('hidden');
this.contributionTextEl.classList.remove('hidden');
const normalizedGroup = (this.activeTopicId || '').replace(/^\d+-/, '');
const isDone = localStorage.getItem(normalizedGroup) === 'done';
if (isDone) {
this.markTopicDoneEl.classList.add('hidden');
this.markTopicPendingEl.classList.remove('hidden');
} else {
this.markTopicDoneEl.classList.remove('hidden');
this.markTopicPendingEl.classList.add('hidden');
}
}
fetchTopicHtml(roadmapId, topicId) {
const topicPartial = topicId.replace(/^\d+-/, '').replaceAll(/:/g, '/');
const fullUrl = `/${roadmapId}/${topicPartial}/`;
return fetch(fullUrl)
.then((res) => {
return res.text();
})
.then((topicHtml) => {
// It's full HTML with page body, head etc.
// We only need the inner HTML of the #main-content
const node = new DOMParser().parseFromString(topicHtml, 'text/html');
return node.getElementById('main-content');
});
}
handleTopicClick(e) {
const { roadmapId, topicId } = e.detail;
if (!topicId || !roadmapId) {
console.log('Missing topic or roadmap: ', e.detail);
return;
}
this.activeRoadmapId = roadmapId;
this.activeTopicId = topicId;
if (/^ext_link/.test(topicId)) {
window.open(`https://${topicId.replace('ext_link:', '')}`);
return;
}
this.resetDOM();
this.fetchTopicHtml(roadmapId, topicId)
.then((content) => {
this.populate(content);
})
.catch((e) => {
console.error(e);
this.populate('Error loading the content!');
});
}
queryRoadmapElementsByTopicId(topicId) {
const elements = document.querySelectorAll(
`[data-group-id$="-${topicId}"]`
);
const matchingElements = [];
elements.forEach((element) => {
const foundGroupId = element?.dataset?.groupId || '';
const validGroupRegex = new RegExp(`^\\d+-${topicId}$`);
if (validGroupRegex.test(foundGroupId)) {
matchingElements.push(element);
}
});
return matchingElements;
}
markAsDone(topicId) {
const updatedTopicId = topicId.replace(/^\d+-/, '');
localStorage.setItem(updatedTopicId, 'done');
this.queryRoadmapElementsByTopicId(updatedTopicId).forEach((item) => {
item?.classList?.add('done');
});
}
markAsPending(topicId) {
const updatedTopicId = topicId.replace(/^\d+-/, '');
localStorage.removeItem(updatedTopicId);
this.queryRoadmapElementsByTopicId(updatedTopicId).forEach((item) => {
item?.classList?.remove('done');
});
}
handleOverlayClick(e) {
const isClickedInsideTopic = e.target.closest(`#${this.topicBodyId}`);
if (!isClickedInsideTopic) {
this.close();
return;
}
const isClickedDone =
e.target.id === this.markTopicDoneId ||
e.target.closest(`#${this.markTopicDoneId}`);
if (isClickedDone) {
this.markAsDone(this.activeTopicId);
this.close();
}
const isClickedPending =
e.target.id === this.markTopicPendingId ||
e.target.closest(`#${this.markTopicPendingId}`);
if (isClickedPending) {
this.markAsPending(this.activeTopicId);
this.close();
}
const isClickedClose =
e.target.id === this.closeTopicId ||
e.target.closest(`#${this.closeTopicId}`);
if (isClickedClose) {
this.close();
}
}
init() {
window.addEventListener('topic.click', this.handleTopicClick);
window.addEventListener('click', this.handleOverlayClick);
window.addEventListener('keydown', (e) => {
if (e.key.toLowerCase() === 'escape') {
this.close();
}
});
}
}

View File

@@ -1,9 +1,24 @@
---
import "../styles/prism.css";
import '../styles/prism.css';
import DownloadPopup from './DownloadPopup.astro';
import ShareIcons from './ShareIcons.astro';
import SubscribePopup from './SubscribePopup.astro';
export interface Props {
roadmapId: string;
description: string;
}
const { roadmapId, description } = Astro.props;
---
<div class="bg-gray-50 py-4 sm:py-10">
<div class="container prose prose-headings:mt-4 prose-headings:mb-2 prose-p:mb-0.5">
<div class='bg-gray-50 py-4 sm:py-10'>
<div
class='container prose prose-headings:mt-4 prose-headings:mb-2 prose-p:mb-0.5 relative prose-code:text-white'
>
<DownloadPopup />
<SubscribePopup />
<slot />
</div>
</div>

View File

@@ -32,24 +32,22 @@ import Icon from './Icon.astro';
<!-- Mobile Navigation Button -->
<button
id='show-mobile-navigation'
class='text-gray-400 hover:text-gray-50 block sm:hidden cursor-pointer'
aria-label='Menu'
onclick="document.getElementById('mobile-navigation').classList.remove('hidden');"
show-mobile-nav
>
<Icon icon='hamburger' />
</button>
<!-- Mobile Navigation Items -->
<div
id='mobile-navigation'
class='fixed top-0 bottom-0 left-0 right-0 z-40 bg-slate-900 items-center flex hidden'
mobile-nav
>
<button
id='close-mobile-navigation'
close-mobile-nav
class='text-gray-400 hover:text-gray-50 block cursor-pointer absolute top-6 right-6'
aria-label='Close Menu'
onclick="document.getElementById('mobile-navigation').classList.add('hidden');"
>
<Icon icon='close' />
</button>
@@ -80,3 +78,15 @@ import Icon from './Icon.astro';
</div>
</nav>
</div>
<script>
document.querySelector('[show-mobile-nav]')?.addEventListener('click', () => {
document.querySelector('[mobile-nav]')?.classList.remove('hidden');
});
document
.querySelector('[close-mobile-nav]')
?.addEventListener('click', () => {
document.querySelector('[mobile-nav]')?.classList.add('hidden');
});
</script>

View File

@@ -7,7 +7,7 @@ const starCount = await getFormattedStars('kamranahmedse/developer-roadmap');
<div class='py-6 sm:py-16 border-b border-t text-left sm:text-center bg-white'>
<div class='max-w-[600px] container'>
<h1 class='text-3xl sm:text-5xl font-bold'>Open Source</h1>
<h2 class='text-2xl sm:text-5xl font-bold'>Open Source</h2>
<p class='text-gray-600 text-sm sm:text-lg leading-relaxed my-2.5 sm:my-5'>
The project is OpenSource, <a
href='https://github.com/search?o=desc&q=stars%3A%3E100000&s=stars&type=Repositories'

View File

@@ -21,8 +21,7 @@ const { id, title, subtitle } = Astro.props;
<div class='relative bg-white rounded-lg shadow popup-body'>
<button
type='button'
class='absolute top-3 right-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center'
onclick='this.closest(".popup").classList.add("hidden")'
class='absolute top-3 right-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center popup-close'
>
<Icon icon='close' />
<span class='sr-only'>Close popup</span>

View File

@@ -31,8 +31,9 @@ export class Popup {
const target = e.target;
const popupBody = target.closest('.popup-body');
const closestPopup = target.closest('.popup');
const closeBtn = target.closest('.popup-close');
if (popupBody) {
if (!closeBtn && popupBody) {
return;
}

View File

@@ -16,7 +16,7 @@ const roadmapTitle =
<div
class:list={[
'mt-4 sm:mt-7 border rounded-md mb-0',
'mt-4 sm:mt-7 border-0 sm:border rounded-md mb-0',
{
'sm:-mb-[82px]': hasTNSBanner,
'sm:-mb-[65px]': !hasTNSBanner,
@@ -29,10 +29,12 @@ const roadmapTitle =
<p class='text-sm'>
Get the latest {roadmapTitle} news from our sister site{' '}
<a
href='https://thenewstack.io'
href='https://thenewstack.io?utm_source=roadmap.sh&utm_medium=Referral&utm_campaign=Alert'
target='_blank'
class='font-semibold underline'
onclick="window.fireEvent({ category: 'PartnerClick', action: `TNS Referral`, label: `TNS Referral - Roadmap` })"
ga-category='PartnerClick'
ga-action='TNS Referral'
ga-label='TNS Referral - Roadmap'
>
TheNewStack.io
</a>

View File

@@ -1,8 +1,8 @@
---
import Icon from "./Icon.astro";
import ResourcesAlert from "./ResourcesAlert.astro";
import TopicSearch from "./TopicSearch/TopicSearch.astro";
import YouTubeAlert from "./YouTubeAlert.astro";
import Icon from './Icon.astro';
import ResourcesAlert from './ResourcesAlert.astro';
import TopicSearch from './TopicSearch/TopicSearch.astro';
import YouTubeAlert from './YouTubeAlert.astro';
export interface Props {
title: string;
@@ -22,48 +22,57 @@ const {
hasTopics = false,
} = Astro.props;
const isRoadmapReady = !isUpcoming;
---
<div class="border-b">
<div class="py-5 sm:py-12 container relative">
<div class='border-b'>
<div class='py-5 sm:py-12 container relative'>
<YouTubeAlert />
<div class="mt-0 mb-3 sm:mb-6 sm:mt-4">
<h1 class="text-2xl sm:text-4xl mb-0.5 sm:mb-2 font-bold">
<div class='mt-0 mb-3 sm:mb-6 sm:mt-4'>
<h1 class='text-2xl sm:text-4xl mb-0.5 sm:mb-2 font-bold'>
{title}
</h1>
<p class="text-gray-500 text-sm sm:text-lg">{description}</p>
<p class='text-gray-500 text-sm sm:text-lg'>{description}</p>
</div>
<div class="flex justify-between">
<div class="flex gap-1 sm:gap-2">
<div class='flex justify-between'>
<div class='flex gap-1 sm:gap-2'>
{
!hasSearch && (
<>
<a href='/roadmaps' class='bg-gray-500 py-1.5 px-3 rounded-md text-white text-xs sm:text-sm font-medium hover:bg-gray-600' aria-label="Back to All Roadmaps">
&larr;<span class='hidden sm:inline'>&nbsp;All Roadmaps</span>
</a>
<a
href='/roadmaps/'
class='bg-gray-500 py-1.5 px-3 rounded-md text-white text-xs sm:text-sm font-medium hover:bg-gray-600'
aria-label='Back to All Roadmaps'
>
&larr;<span class='hidden sm:inline'>&nbsp;All Roadmaps</span>
</a>
{isRoadmapReady && (
<button
data-popup="download-popup"
class="inline-flex items-center justify-center bg-yellow-400 py-1.5 px-3 text-xs sm:text-sm font-medium rounded-md hover:bg-yellow-500"
aria-label="Download Roadmap"
data-popup='download-popup'
class='inline-flex items-center justify-center bg-yellow-400 py-1.5 px-3 text-xs sm:text-sm font-medium rounded-md hover:bg-yellow-500'
aria-label='Download Roadmap'
ga-category='Subscription'
ga-action='Clicked Popup Opener'
ga-label='Download Roadmap Popup'
>
<Icon icon="download" />
<span class="hidden sm:inline ml-2">Download</span>
<Icon icon='download' />
<span class='hidden sm:inline ml-2'>Download</span>
</button>
)}
<button
data-popup="subscribe-popup"
class="inline-flex items-center justify-center bg-yellow-400 py-1.5 px-3 text-xs sm:text-sm font-medium rounded-md hover:bg-yellow-500"
aria-label="Subscribe for Updates"
data-popup='subscribe-popup'
class='inline-flex items-center justify-center bg-yellow-400 py-1.5 px-3 text-xs sm:text-sm font-medium rounded-md hover:bg-yellow-500'
aria-label='Subscribe for Updates'
ga-category='Subscription'
ga-action='Clicked Popup Opener'
ga-label='Subscribe Roadmap Popup'
>
<Icon icon="email" />
<span class="ml-2">Subscribe</span>
<Icon icon='email' />
<span class='ml-2'>Subscribe</span>
</button>
</>
)
@@ -73,11 +82,11 @@ const isRoadmapReady = !isUpcoming;
hasSearch && (
<a
href={`/${roadmapId}/`}
class="bg-gray-500 py-1.5 px-3 rounded-md text-white text-xs sm:text-sm font-medium hover:bg-gray-600"
aria-label="Back to Visual Roadmap"
class='bg-gray-500 py-1.5 px-3 rounded-md text-white text-xs sm:text-sm font-medium hover:bg-gray-600'
aria-label='Back to Visual Roadmap'
>
&larr;
<span class="inline">&nbsp;Visual Roadmap</span>
<span class='inline'>&nbsp;Visual Roadmap</span>
</a>
)
}
@@ -87,13 +96,13 @@ const isRoadmapReady = !isUpcoming;
isRoadmapReady && (
<a
href={`https://github.com/kamranahmedse/developer-roadmap/issues/new?title=[Suggestion] ${title}`}
target="_blank"
class="inline-flex items-center justify-center bg-gray-500 text-white py-1.5 px-3 text-xs sm:text-sm font-medium rounded-md hover:bg-gray-600"
aria-label="Suggest Changes"
target='_blank'
class='inline-flex items-center justify-center bg-gray-500 text-white py-1.5 px-3 text-xs sm:text-sm font-medium rounded-md hover:bg-gray-600'
aria-label='Suggest Changes'
>
<Icon icon="comment" class="h-3 w-3" />
<span class="ml-2 hidden sm:inline">Suggest Changes</span>
<span class="ml-2 inline sm:hidden">Suggest</span>
<Icon icon='comment' class='h-3 w-3' />
<span class='ml-2 hidden sm:inline'>Suggest Changes</span>
<span class='ml-2 inline sm:hidden'>Suggest</span>
</a>
)
}

View File

@@ -1,5 +1,6 @@
---
import type { GAEventType } from '../Analytics/analytics';
import Icon from '../Icon.astro';
export type SponsorType = {
url: string;
@@ -25,10 +26,19 @@ const {
id='sponsor-ad'
target='_blank'
rel='noopener sponsored'
onclick={event ? `window.fireEvent(${JSON.stringify(event)})` : ''}
ga-category={event?.category}
ga-action={event?.action}
ga-label={event?.label}
class='fixed bottom-[15px] right-[20px] outline-transparent z-50 bg-white max-w-[330px] shadow-lg outline-0 hidden'
>
<img src={imageUrl} class='w-[100px] lg:w-[130px]' alt='Sponsor Banner' />
<button
class='absolute top-1.5 right-1.5 text-gray-300 hover:text-gray-800'
aria-label='Close'
close-sponsor
>
<Icon icon='close' class='h-4' />
</button>
<img src={imageUrl} class='h-[150px] lg:h-[169px]' alt='Sponsor Banner' />
<span class='text-sm flex flex-col justify-between'>
<span class='p-[10px]'>
<span class='font-semibold mb-0.5 block'>{title}</span>
@@ -37,3 +47,11 @@ const {
<span class='sponsor-footer'>Partner Content</span>
</span>
</a>
<script>
document.querySelector('[close-sponsor]')?.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
document.getElementById('sponsor-ad')?.classList.add('hidden');
});
</script>

View File

@@ -33,6 +33,9 @@ import CaptchaFields from './Captcha/CaptchaFields.astro';
type='submit'
name='submit'
class='text-white bg-gradient-to-r from-amber-700 to-blue-800 hover:from-amber-800 hover:to-blue-900 font-regular rounded-md text-md px-5 py-2.5 w-full text-center mr-2'
ga-category='Subscription'
ga-action='Submitted Popup Form'
ga-label='Subscribe Roadmap Popup'
>
Subscribe
</button>

View File

@@ -1,31 +1,69 @@
---
import Icon from "./Icon.astro";
import Loader from "./Loader.astro";
import Icon from './Icon.astro';
import Loader from './Loader.astro';
export interface Props {
roadmapId: string;
}
const { roadmapId } = Astro.props;
const githubLink = `https://github.com/kamranahmedse/developer-roadmap/tree/master/src/roadmaps/${roadmapId}/content`;
---
<div id='topic-overlay' class='hidden'>
<div class="fixed top-0 right-0 z-40 h-screen p-4 sm:p-6 overflow-y-auto bg-white w-full sm:max-w-[600px]" tabindex="-1" id='topic-body'>
<div id='topic-loader' class='hidden'>
<Loader />
</div>
<div id='topic-actions' class='hidden mb-2'>
<button id='mark-topic-done' class='bg-green-600 text-white p-1 px-2 text-sm rounded-md hover:bg-green-700 inline-flex items-center'>
<Icon icon="check" /> <span class='ml-2'>Mark as Done</span>
</button>
<button id='mark-topic-pending' class='hidden bg-red-600 text-white p-1 px-2 text-sm rounded-md hover:bg-red-700 inline-flex items-center'>
<Icon icon="reset" /> <span class='ml-2'>Mark as Pending</span>
</button>
<button type="button" id='close-topic' class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 absolute top-2.5 right-2.5 inline-flex items-center">
<Icon icon="close" />
</button>
</div>
<div id='topic-content' class='prose prose-h1:mt-7 prose-h1:mb-2.5 prose-p:mt-0 prose-p:mb-2 prose-li:m-0 prose-li:mb-0.5 prose-h2:mb-3 prose-h2:mt-0'></div>
<div
class='fixed top-0 right-0 z-40 h-screen p-4 sm:p-6 overflow-y-auto bg-white w-full sm:max-w-[600px]'
tabindex='-1'
id='topic-body'
>
<div id='topic-loader' class='hidden'>
<Loader />
</div>
<div class="bg-gray-900 bg-opacity-50 dark:bg-opacity-80 fixed inset-0 z-30"></div>
<div id='topic-actions' class='hidden mb-2'>
<button
id='mark-topic-done'
class='bg-green-600 text-white p-1 px-2 text-sm rounded-md hover:bg-green-700 inline-flex items-center'
>
<Icon icon='check' />
<span class='ml-2'>Mark as Done</span>
</button>
<button
id='mark-topic-pending'
class='hidden bg-red-600 text-white p-1 px-2 text-sm rounded-md hover:bg-red-700 inline-flex items-center'
>
<Icon icon='reset' />
<span class='ml-2'>Mark as Pending</span>
</button>
<button
type='button'
id='close-topic'
class='text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 absolute top-2.5 right-2.5 inline-flex items-center'
>
<Icon icon='close' />
</button>
</div>
<div
id='topic-content'
class='prose prose-h1:mt-7 prose-h1:mb-2.5 prose-p:mt-0 prose-p:mb-2 prose-li:m-0 prose-li:mb-0.5 prose-h2:mb-3 prose-h2:mt-0'
>
</div>
<p
id='contrib-meta'
class='text-gray-400 text-sm border-t pt-3 mt-10 hidden'
>
We are still working on this page. You can contribute by submitting a
brief description and a few links to learn more about this topic <a
target='_blank'
class='underline text-blue-700'
href={githubLink}>on GitHub repository.</a
>.
</p>
</div>
<div class='bg-gray-900 bg-opacity-50 dark:bg-opacity-80 fixed inset-0 z-30'>
</div>
</div>

View File

@@ -20,7 +20,11 @@ const { author } = frontmatter;
target='_blank'
class='font-medium hover:text-gray-600 inline-flex items-center hover:underline'
>
<img src={author.imageUrl} class='w-5 h-5 inline mr-2 rounded-full' />
<img
alt={author.name}
src={author.imageUrl}
class='w-5 h-5 inline mr-2 rounded-full'
/>
{author.name}
</a>
<span class='mx-1.5'>&middot;</span>

View File

@@ -1,12 +1,33 @@
---
import Icon from "./Icon.astro";
import Icon from './Icon.astro';
---
<div class='sticky top-0 border-b border-b-yellow-300 z-10 flex h-[37px]' id='sticky-youtube-banner'>
<a href='https://youtube.com/theroadmap?sub_confirmation=1' target='_blank' class='flex bg-yellow-200 text-center flex-1 items-center justify-center text-sm hover:bg-yellow-300 outline-0 '>
<Icon icon="youtube" class="mr-2" /> We now have a YouTube Channel.&nbsp;<span class='hidden sm:inline'>Subscribe for the video content.</span>
</a>
<button class='text-yellow-500 bg-yellow-200 hover:text-yellow-900 hover:bg-yellow-400 outline-0 px-2' onclick='this.parentElement.classList.add("hidden")' aria-label="Close">
<Icon icon="close" />
</button>
</div>
<div
class='sticky top-0 border-b border-b-yellow-300 z-10 flex h-[37px]'
youtube-banner
>
<a
href='https://youtube.com/theroadmap?sub_confirmation=1'
target='_blank'
class='flex bg-yellow-200 text-center flex-1 items-center justify-center text-sm hover:bg-yellow-300 outline-0'
>
<Icon icon='youtube' class='mr-2' /> We now have a YouTube Channel.&nbsp;<span
class='hidden sm:inline'>Subscribe for the video content.</span
>
</a>
<button
class='text-yellow-500 bg-yellow-200 hover:text-yellow-900 hover:bg-yellow-400 outline-0 px-2'
aria-label='Close'
close-youtube-banner
>
<Icon icon='close' />
</button>
</div>
<script>
document
.querySelector('[close-youtube-banner]')
?.addEventListener('click', () => {
document.querySelector('[youtube-banner]').remove();
});
</script>

8
src/env.d.ts vendored
View File

@@ -1 +1,9 @@
/// <reference types="astro/client" />
interface ImportMetaEnv {
GITHUB_SHA: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@@ -22,5 +22,5 @@ tags:
Asymptotic notation is the standard way of measuring the time and space that an algorithm will consume as the input grows. In one of my last guides, I covered "Big-O notation" and a lot of you asked for a similar one for Asymptotic notation. You can find the [previous guide here](/guides/big-o-notation).
[![](/guides/asymptotic-notation.png)](/guides/asymptotic-notation.png)
[![Asymptotic Notation](/guides/asymptotic-notation.png)](/guides/asymptotic-notation.png)

View File

@@ -20,5 +20,5 @@ tags:
- "guide-sitemap"
---
[![](/guides/avoid-render-blocking-javascript-with-async-defer.png)](/guides/avoid-render-blocking-javascript-with-async-defer.png)
[![Avoid Render Blocking with Async and Defer](/guides/avoid-render-blocking-javascript-with-async-defer.png)](/guides/avoid-render-blocking-javascript-with-async-defer.png)

View File

@@ -20,5 +20,5 @@ tags:
- "guide-sitemap"
---
[![](/guides/basic-authentication.png)](/guides/basic-authentication.png)
[![Basic Authentication](/guides/basic-authentication.png)](/guides/basic-authentication.png)

View File

@@ -22,5 +22,5 @@ tags:
Big-O notation is the mathematical notation that helps analyse the algorithms to get an idea about how they might perform as the input grows. The image below explains Big-O in a simple way without using any fancy terminology.
[![](/guides/big-o-notation.png)](/guides/big-o-notation.png)
[![Big-O Notation](/guides/big-o-notation.png)](/guides/big-o-notation.png)

View File

@@ -20,5 +20,5 @@ tags:
- "guide-sitemap"
---
[![](/guides/character-encodings.png)](/guides/character-encodings.png)
[![Character Encodings](/guides/character-encodings.png)](/guides/character-encodings.png)

View File

@@ -22,5 +22,5 @@ tags:
The image below details the differences between the continuous integration and continuous delivery. Also, here is the [accompanying video on implementing that with GitHub actions](https://www.youtube.com/watch?v=nyKZTKQS_EQ).
[![](/guides/ci-cd.png)](/guides/ci-cd.png)
[![CI vs CD](/guides/ci-cd.png)](/guides/ci-cd.png)

View File

@@ -20,5 +20,5 @@ tags:
- "guide-sitemap"
---
[![](/guides/dhcp.png)](/guides/dhcp.png)
[![DHCP in One Picture](/guides/dhcp.png)](/guides/dhcp.png)

View File

@@ -24,4 +24,4 @@ DNS or Domain Name System is one of the fundamental blocks of the internet. As a
DNS at its simplest is like a phonebook on your mobile phone. Whenever you have to call one of your contacts, you can either dial their number from your memory or use their name which will then be used by your mobile phone to search their number in your phone book to call them. Every time you make a new friend, or your existing friend gets a mobile phone, you have to memorize their phone number or save it in your phonebook to be able to call them later on. DNS or Domain Name System, in a similar fashion, is a mechanism that allows you to browse websites on the internet. Just like your mobile phone does not know how to call without knowing the phone number, your browser does not know how to open a website just by the domain name; it needs to know the IP Address for the website to open. You can either type the IP Address to open, or provide the domain name and press enter which will then be used by your browser to find the IP address by going through several hoops. The picture below is the illustration of how your browser finds a website on the internet.
[![](https://i.imgur.com/z9rwm5A.png)](https://i.imgur.com/z9rwm5A.png)
[![DNS in One Picture](https://i.imgur.com/z9rwm5A.png)](https://i.imgur.com/z9rwm5A.png)

View File

@@ -31,7 +31,7 @@ Given the name "Basic Authentication", you should not confuse Basic Authenticati
Because it is a part of the HTTP specifications, all the browsers have native support for "HTTP Basic Authentication". Given below is the screenshot from the implementation in Google Chrome.
![](/guides/basic-authentication/chrome-basic-auth.png)
![Chrome Basic Authentication](/guides/basic-authentication/chrome-basic-auth.png)
## How does it Work?
@@ -52,7 +52,7 @@ The browser might use Realm to cache the credential. In the future, when there i
## Step 2
Upon receiving the response from the server, the browser will notice the `WWW-Authenticate` header and will show the authentication popup.
![](/guides/basic-authentication/chrome-basic-auth.png)
![Chrome Basic Authentication](/guides/basic-authentication/chrome-basic-auth.png)
## Step 3
After the user submits the credentials through this authentication popup, the browser will automatically encode the credentials using the `base64` encoding and send them in the `Authorization` header of the same request.

View File

@@ -39,7 +39,7 @@ Before we get into further details, let me give you an overview of the terms tha
- **Cache Validation** is the process of contacting the server to check the validity of the cached content and get it updated for when it is going to expire
- **Cache Invalidation** is the process of removing any stale content available in the cache
![](https://i.imgur.com/9MjlzvW.png)
![Web Cache](https://i.imgur.com/9MjlzvW.png)
### Caching Locations
@@ -75,21 +75,30 @@ Although you can control the reverse proxy caches (since it is implemented by yo
So, how do we control the web cache? Whenever the server emits some response, it is accompanied by some HTTP headers to guide the caches on whether and how to cache this response. The content provider is the one that has to make sure to return proper HTTP headers to force the caches on how to cache the content.
- [Expires](#expires)
- [Pragma](#pragma)
- [Cache-Control](#cache-control)
- [private](#private)
- [public](#public)
- [no-store](#no-store)
- [no-cache](#no-cache)
- [max-age: seconds](#max-age)
- [s-maxage: seconds](#s-maxage)
- [must-revalidate](#must-revalidate)
- [proxy-revalidate](#proxy-revalidate)
- [Mixing Values](#mixing-values)
- [Introduction](#introduction)
- [Caching Locations](#caching-locations)
- [Browser Cache](#browser-cache)
- [Proxy Cache](#proxy-cache)
- [Reverse Proxy Cache](#reverse-proxy-cache)
- [Caching Headers](#caching-headers)
- [Expires](#expires)
- [Pragma](#pragma)
- [Cache-Control](#cache-control)
- [private](#private)
- [public](#public)
- [no-store](#no-store)
- [no-cache](#no-cache)
- [max-age: seconds](#max-age-seconds)
- [s-maxage: seconds](#s-maxage-seconds)
- [must-revalidate](#must-revalidate)
- [proxy-revalidate](#proxy-revalidate)
- [Mixing Values](#mixing-values)
- [Validators](#validators)
- [ETag](#etag)
- [Last-Modified](#last-modified)
- [Where do I start?](#where-do-i-start)
- [Utilizing Server](#utilizing-server)
- [Caching Recommendations](#caching-recommendations)
#### Expires

View File

@@ -95,7 +95,7 @@ Three-way handshake in its simplest form is that all the `TCP` connections begin
Once the three-way handshake is completed, the data sharing between the client and server may begin. It should be noted that the client may start sending the application data as soon as it dispatches the last `ACK` packet but the server will still have to wait for the `ACK` packet to be received in order to fulfill the request.
![](http://i.imgur.com/uERG2G2.png)
![Three-way Handshake](https://i.imgur.com/uERG2G2.png)
> Please note that there is a minor issue with the image, the last `ACK` packet sent by the client to end the handshake contains only `y+1` i.e. it should have been `ACK:y+1` instead of `ACK: x+1, y+1`
@@ -131,7 +131,7 @@ After merely 3 years of `HTTP/1.0`, the next version i.e. `HTTP/1.1` was release
- New status codes
- ..and more
I am not going to dwell about all the `HTTP/1.1` features in this post as it is a topic in itself and you can already find a lot about it. The one such document that I would recommend you to read is [Key differences between `HTTP/1.0` and HTTP/1.1](http://www.ra.ethz.ch/cdstore/www8/data/2136/pdf/pd1.pdf) and here is the link to [original RFC](https://tools.ietf.org/html/rfc2616) for the overachievers.
I am not going to dwell about all the `HTTP/1.1` features in this post as it is a topic in itself and you can already find a lot about it. The one such document that I would recommend you to read is [Key differences between `HTTP/1.0` and HTTP/1.1](https://www.ra.ethz.ch/cdstore/www8/data/2136/pdf/pd1.pdf) and here is the link to [original RFC](https://tools.ietf.org/html/rfc2616) for the overachievers.
`HTTP/1.1` was introduced in 1999 and it had been a standard for many years. Although, it improved alot over its predecessor; with the web changing everyday, it started to show its age. Loading a web page these days is more resource-intensive than it ever was. A simple webpage these days has to open more than 30 connections. Well `HTTP/1.1` has persistent connections, then why so many connections? you say! The reason is, in `HTTP/1.1` it can only have one outstanding connection at any moment of time. `HTTP/1.1` tried to fix this by introducing pipelining but it didn't completely address the issue because of the **head-of-line blocking** where a slow or heavy request may block the requests behind and once a request gets stuck in a pipeline, it will have to wait for the next requests to be fulfilled. To overcome these shortcomings of `HTTP/1.1`, the developers started implementing the workarounds, for example use of spritesheets, encoded images in CSS, single humongous CSS/Javascript files, [domain sharding](https://www.maxcdn.com/one/visual-glossary/domain-sharding-2/) etc.
@@ -162,7 +162,7 @@ By now, you must be convinced that why we needed another revision of the HTTP pr
- Request Prioritization
- Security
![](http://i.imgur.com/S85j8gg.png)
![HTTP Model](https://i.imgur.com/S85j8gg.png)
#### 1. Binary Protocol
@@ -187,7 +187,7 @@ Since `HTTP/2` is now a binary protocol and as I said above that it uses frames
It was part of a separate RFC which was specifically aimed at optimizing the sent headers. The essence of it is that when we are constantly accessing the server from a same client there is alot of redundant data that we are sending in the headers over and over, and sometimes there might be cookies increasing the headers size which results in bandwidth usage and increased latency. To overcome this, `HTTP/2` introduced header compression.
![](http://i.imgur.com/3IPWXvR.png)
![Header Compression](https://i.imgur.com/3IPWXvR.png)
Unlike request and response, headers are not compressed in `gzip` or `compress` etc formats but there is a different mechanism in place for header compression which is literal values are encoded using Huffman code and a headers table is maintained by the client and server and both the client and server omit any repetitive headers (e.g. user agent etc) in the subsequent requests and reference them using the headers table maintained by both.
@@ -210,8 +210,8 @@ Without any priority information, server processes the requests asynchronously i
There was extensive discussion on whether security (through `TLS`) should be made mandatory for `HTTP/2` or not. In the end, it was decided not to make it mandatory. However, most vendors stated that they will only support `HTTP/2` when it is used over `TLS`. So, although `HTTP/2` doesn't require encryption by specs but it has kind of become mandatory by default anyway. With that out of the way, `HTTP/2` when implemented over `TLS` does impose some requirements i.e. `TLS` version `1.2` or higher must be used, there must be a certain level of minimum key sizes, ephemeral keys are required etc.
`HTTP/2` is here and it has already [surpassed SPDY in adaption](http://caniuse.com/#search=http2) which is gradually increasing. `HTTP/2` has alot to offer in terms of performance gain and it is about time we should start using it.
`HTTP/2` is here and it has already [surpassed SPDY in adaption](https://caniuse.com/#search=http2) which is gradually increasing. `HTTP/2` has alot to offer in terms of performance gain and it is about time we should start using it.
For anyone interested in further details here is the [link to specs](https:/http2.github.iohttp2-spec) and a link [demonstrating the performance benefits of `HTTP/2`](http://www.http2demo.io/).
For anyone interested in further details here is the [link to specs](https:/http2.github.iohttp2-spec) and a link [demonstrating the performance benefits of `HTTP/2`](https://www.http2demo.io/).
And that about wraps it up. Until next time! stay tuned.

View File

@@ -20,5 +20,5 @@ tags:
- "guide-sitemap"
---
[![](/guides/jwt-authentication.png)](/guides/jwt-authentication.png)
[![JWT Authentication](/guides/jwt-authentication.png)](/guides/jwt-authentication.png)

View File

@@ -20,5 +20,5 @@ tags:
- "guide-sitemap"
---
[![](/guides/oauth.png)](/guides/oauth.png)
[![OAuth - Open Authorization](/guides/oauth.png)](/guides/oauth.png)

View File

@@ -22,5 +22,5 @@ tags:
Random numbers are everywhere from computer games to lottery systems, graphics software, statistical sampling, computer simulation and cryptography. Graphic below is a quick explanation to how the random numbers are generated and why they may not be truly random.
[![](/guides/random-numbers.png)](/guides/random-numbers.png)
[![Random Numbers](/guides/random-numbers.png)](/guides/random-numbers.png)

View File

@@ -22,5 +22,5 @@ tags:
The chart below aims to give you a really basic understanding of how the capability of a DBMS is increased to handle a growing amount of load.
[![](/guides/scaling-databases.svg)](/guides/scaling-databases.svg)
[![Scaling Databases](/guides/scaling-databases.svg)](/guides/scaling-databases.svg)

View File

@@ -20,5 +20,5 @@ tags:
- "guide-sitemap"
---
[![](/guides/session-authentication.png)](/guides/session-authentication.png)
[![Session Authentication](/guides/session-authentication.png)](/guides/session-authentication.png)

View File

@@ -20,5 +20,5 @@ tags:
- "guide-sitemap"
---
[![](/guides/ssl-tls-https-ssh.png)](/guides/ssl-tls-https-ssh.png)
[![SSL vs TLS vs HTTPs vs SSH](/guides/ssl-tls-https-ssh.png)](/guides/ssl-tls-https-ssh.png)

View File

@@ -20,5 +20,5 @@ tags:
- "guide-sitemap"
---
[![](/guides/sso.png)](/guides/sso.png)
[![SSO](/guides/sso.png)](/guides/sso.png)

View File

@@ -20,5 +20,5 @@ tags:
- "guide-sitemap"
---
[![](/guides/token-authentication.png)](/guides/token-authentication.png)
[![Token based Authentication](/guides/token-authentication.png)](/guides/token-authentication.png)

View File

@@ -20,5 +20,5 @@ tags:
- "guide-sitemap"
---
[![](/guides/unfamiliar-codebase.png)](/guides/unfamiliar-codebase.png)
[![Unfamiliar Codebase](/guides/unfamiliar-codebase.png)](/guides/unfamiliar-codebase.png)

View File

@@ -20,5 +20,5 @@ tags:
- "guide-sitemap"
---
[![](/guides/web-vitals.png)](/guides/web-vitals.png)
[![Web Vitals](/guides/web-vitals.png)](/guides/web-vitals.png)

View File

@@ -81,4 +81,4 @@ Cybersecurity refers to the protective measures against criminal activity accomp
<iframe class="w-full aspect-video mb-5" width="100%" height="400" src="https://www.youtube.com/embed/AuYNXgO_f3Y" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
And that wraps it up for this article. To learn more about the Internet, [Kamran Ahmed](https://twitter.com/kamranahmedse) has a nice little guide on [DNS: How a website is found on the Internet](/guides/dns-in-one-picture). Also, go through the episodes of [howdns.works](https://howdns.works/) and read this [cartoon intro to DNS over HTTPS](https://hacks.mozilla.org/2018/05/a-cartoon-intro-to-dns-over-https/).
And that wraps it up for this article. To learn more about the Internet, [Kamran Ahmed](https://twitter.com/kamranahmedse) has a nice little guide on [DNS: How a website is found on the Internet](/guides/dns-in-one-picture/). Also, go through the episodes of [howdns.works](https://howdns.works/) and read this [cartoon intro to DNS over HTTPS](https://hacks.mozilla.org/2018/05/a-cartoon-intro-to-dns-over-https/).

View File

@@ -20,5 +20,5 @@ tags:
- "guide-sitemap"
---
[![](/guides/sli-slo-sla.jpeg)](/guides/sli-slo-sla.jpeg)
[![SLI vs SLO vs SLA](/guides/sli-slo-sla.jpeg)](/guides/sli-slo-sla.jpeg)

4
src/icons/down.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path d="m16.843 10.211c.108-.141.157-.3.157-.456 0-.389-.306-.755-.749-.755h-8.501c-.445 0-.75.367-.75.755 0 .157.05.316.159.457 1.203 1.554 3.252 4.199 4.258 5.498.142.184.36.29.592.29.23 0 .449-.107.591-.291 1.002-1.299 3.044-3.945 4.243-5.498z" fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 433 B

View File

@@ -14,6 +14,7 @@ export interface Props {
description?: string;
keywords?: string[];
noIndex?: boolean;
permalink?: string;
sponsor?: SponsorType;
}
@@ -22,8 +23,16 @@ const {
description = siteConfig.description,
keywords = siteConfig.keywords,
noIndex = false,
permalink = '',
sponsor,
} = Astro.props;
// Remove trailing slashes to consider the page as canonical
const currentPageAbsoluteUrl = `https://roadmap.sh${permalink}`;
const commitUrl = `https://github.com/kamranahmedse/developer-roadmap/commit/${
import.meta.env.GITHUB_SHA
}`;
---
<!DOCTYPE html>
@@ -31,6 +40,7 @@ const {
<head>
<meta charset='UTF-8' />
<meta name='generator' content={Astro.generator} />
<meta name='commit' content={commitUrl} />
<title>{title}</title>
<meta name='description' content={description} />
<meta name='author' content='Kamran Ahmed' />
@@ -53,7 +63,9 @@ const {
<meta property='og:title' content={title} />
<meta property='og:description' content={description} />
<meta property='og:type' content='website' />
<meta property='og:url' content='https://roadmap.sh' />
<meta property='og:url' content={currentPageAbsoluteUrl} />
<link rel='canonical' href={currentPageAbsoluteUrl} />
<meta name='mobile-web-app-capable' content='yes' />
<meta name='apple-mobile-web-app-capable' content='yes' />

View File

@@ -2,6 +2,7 @@ const formatter = Intl.NumberFormat('en-US', {
notation: 'compact',
});
const defaultStarCount = 224000;
let starCount: number | undefined = undefined;
export async function countStars(
@@ -15,10 +16,10 @@ export async function countStars(
const repoData = await fetch(`https://api.github.com/repos/${repo}`);
const json = await repoData.json();
starCount = json.stargazers_count * 1;
starCount = json.stargazers_count * 1 || defaultStarCount;
} catch (e) {
console.log('Failed to fetch stars', e);
starCount = 224000;
starCount = defaultStarCount;
}
return starCount;
@@ -27,11 +28,7 @@ export async function countStars(
export async function getFormattedStars(
repo = 'kamranahmedse/developer-roadmap'
): Promise<string> {
if (import.meta.env.DEV) {
return '224k';
}
const stars = await countStars(repo);
const stars = import.meta.env.DEV ? defaultStarCount : await countStars(repo);
return formatter.format(stars);
}

View File

@@ -3,7 +3,7 @@ import Icon from '../components/Icon.astro';
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title='Page not found'>
<BaseLayout title='Page not found' permalink={'/404/'}>
<div class='bg-gray-100'>
<div
class='py-10 md:py-32 container flex flex-col md:flex-row items-center justify-center gap-7 '

View File

@@ -15,16 +15,24 @@ 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 TopicFileType;
---
<BaseLayout title={`${heading} - roadmap.sh`} description={`Free resources to learn ${heading} in ${roadmap.featuredTitle}. Everything you need to know about ${heading} and how it realtes to ${roadmap.featuredTitle}.`} noIndex={true}>
<BaseLayout
title={`${heading} - roadmap.sh`}
description={`Free resources to learn ${heading} in ${roadmap.featuredTitle}. Everything you need to know about ${heading} and how it realtes to ${roadmap.featuredTitle}.`}
noIndex={true}
permalink={`/${topicId}/`}
>
<RoadmapBanner roadmapId={roadmapId} roadmap={roadmap} />
<div class="bg-gray-50">
<div class='bg-gray-50'>
<Breadcrumbs breadcrumbs={breadcrumbs} roadmapId={roadmapId} />
<div class="container pb-16 prose prose-p:mt-0 prose-h1:mb-4 prose-h2:mb-3 prose-h2:mt-0">
<main id="main-content">
<div
class='container pb-16 prose prose-p:mt-0 prose-h1:mb-4 prose-h2:mb-3 prose-h2:mt-0'
>
<main id='main-content'>
<file.Content />
</main>
</div>

View File

@@ -1,5 +1,6 @@
---
import CaptchaScripts from '../../components/Captcha/CaptchaScripts.astro';
import FAQs from '../../components/FAQs.astro';
import InteractiveRoadmap from '../../components/InteractiveRoadmap/InteractiveRoadmap.astro';
import MarkdownRoadmap from '../../components/MarkdownRoadmap.astro';
import RoadmapHeader from '../../components/RoadmapHeader.astro';
@@ -21,10 +22,12 @@ interface Params extends Record<string, string | undefined> {
const { roadmapId } = Astro.params as Params;
const roadmapFile = await import(`../../roadmaps/${roadmapId}/${roadmapId}.md`);
const questions = await import (`../../roadmaps/${roadmapId}/faqs.astro`);
const roadmapData = roadmapFile.frontmatter as RoadmapFrontmatter;
---
<BaseLayout
permalink={`/${roadmapId}/`}
title={roadmapData?.seo?.title}
description={roadmapData.seo.description}
keywords={roadmapData.seo.keywords}
@@ -52,7 +55,10 @@ const roadmapData = roadmapFile.frontmatter as RoadmapFrontmatter;
{
!roadmapData.isUpcoming && !roadmapData.jsonUrl && (
<MarkdownRoadmap>
<MarkdownRoadmap
roadmapId={roadmapId}
description={roadmapData.description}
>
<roadmapFile.Content />
</MarkdownRoadmap>
)
@@ -60,5 +66,6 @@ const roadmapData = roadmapFile.frontmatter as RoadmapFrontmatter;
{roadmapData.isUpcoming && <UpcomingRoadmap />}
<questions.default />
<CaptchaScripts slot='after-footer' />
</BaseLayout>

View File

@@ -22,7 +22,12 @@ const roadmapFile = await import(`../../roadmaps/${roadmapId}/${roadmapId}.md`);
const roadmapData = roadmapFile.frontmatter as RoadmapFrontmatter;
---
<BaseLayout title={`${roadmapData.title} Topics`} description={roadmapData.seo.description} keywords={roadmapData.seo.keywords}>
<BaseLayout
title={`${roadmapData.title} Topics`}
description={roadmapData.seo.description}
keywords={roadmapData.seo.keywords}
permalink={`/${roadmapId}/topics/`}
>
<RoadmapHeader
description={roadmapData.description}
title={`${roadmapData.featuredTitle} Topics`}
@@ -31,8 +36,8 @@ const roadmapData = roadmapFile.frontmatter as RoadmapFrontmatter;
hasTopics={false}
/>
<div class="bg-gray-50 pt-5 pb-8 sm:pt-10 sm:pb-16">
<div class="container">
<div class='bg-gray-50 pt-5 pb-8 sm:pt-10 sm:pb-16'>
<div class='container'>
{
topics.map((topic) => {
// Breadcrumbs have three additional items e.g.

View File

@@ -2,7 +2,7 @@
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title='About roadmap.sh'>
<BaseLayout title='About roadmap.sh' permalink={'/about/'}>
<div class='bg-white border-b pt-7 pb-7 sm:pt-12 sm:pb-10'>
<div class='container'>
<div class='flex items-center'>

View File

@@ -22,7 +22,11 @@ const { guide } = Astro.props;
const { frontmatter: guideData } = guide;
---
<BaseLayout title={guideData.seo.title} description={guideData.seo.description}>
<BaseLayout
title={guideData.seo.title}
description={guideData.seo.description}
permalink={`/guides/${guideId}/`}
>
<GuideHeader guide={guide} />
<div class='bg-gray-50 py-5 sm:py-10'>

View File

@@ -7,7 +7,11 @@ import { getAllGuides } from '../../lib/guide';
const guides = await getAllGuides();
---
<BaseLayout title='Guides - roadmap.sh' description={'Detailed guides on Software Engineering Topics'}>
<BaseLayout
title='Guides - roadmap.sh'
description={'Detailed guides on Software Engineering Topics'}
permalink={`/guides/`}
>
<SimplePageHeader
title='Guides'
description='Succinct graphical explanations to engineering topics.'

View File

@@ -13,7 +13,7 @@ const guides = await getAllGuides();
const videos = await getAllVideos();
---
<BaseLayout title='Developer Roadmaps' description={"Community driven roadmaps, articles and guides for developers to grow in their career."}>
<BaseLayout title='Developer Roadmaps - roadmap.sh' description={"Community driven roadmaps, articles and guides for developers to grow in their career."} permalink={'/'}>
<div class='bg-gradient-to-b from-slate-900 to-black'>
<div class='border-b border-b-slate-900'>
<div

View File

@@ -15,6 +15,7 @@ Here is the list of PDF links for each of the roadmaps.
* **QA Roadmap** - [Roadmap Link](https://roadmap.sh/qa) / [PDF Link](https://roadmap.sh/pdfs/qa.pdf)
* **ASP.NET Core Roadmap** - [Roadmap Link](https://roadmap.sh/aspnet-core) / [PDF Link](https://roadmap.sh/pdfs/aspnet-core.pdf)
* **Flutter Roadmap** - [Roadmap Link](https://roadmap.sh/flutter) / [PDF Link](https://roadmap.sh/pdfs/flutter.pdf)
* **Go Roadmap** - [Roadmap Link](https://roadmap.sh/golang) / [PDF Link](https://roadmap.sh/pdfs/golang.pdf)
* **Software Architect Roadmap** - [Roadmap Link](https://roadmap.sh/software-architect) / [PDF Link](https://roadmap.sh/pdfs/software-architect.pdf)
* **Software Design and Architecture Roadmap** - [Roadmap Link](https://roadmap.sh/software-design-architecture) / [PDF Link](https://roadmap.sh/pdfs/software-design-architecture.pdf)
* **JavaScript Roadmap** - [Roadmap Link](https://roadmap.sh/javascript) / [PDF Link](https://roadmap.sh/pdfs/javascript.pdf)
@@ -25,6 +26,6 @@ Here is the list of PDF links for each of the roadmaps.
* **Vue Roadmap** - [Roadmap Link](https://roadmap.sh/vue) / [PDF Link](https://roadmap.sh/pdfs/vue.pdf)
* **Design System Roadmap** - [Roadmap Link](https://roadmap.sh/design-system) / [PDF Link](https://roadmap.sh/pdfs/design-system.pdf)
* **Blockchain Roadmap** - [Roadmap Link](https://roadmap.sh/blockchain) / [PDF Link](https://roadmap.sh/pdfs/blockchain.pdf)
* **Go Roadmap** - [Roadmap Link](https://roadmap.sh/golang) / [PDF Link](https://roadmap.sh/pdfs/go.pdf)
* **Java Roadmap** - [Roadmap Link](https://roadmap.sh/java) / [PDF Link](https://roadmap.sh/pdfs/java.pdf)
* **Spring Boot Roadmap** - [Roadmap Link](https://roadmap.sh/spring-boot) / [PDF Link](https://roadmap.sh/pdfs/spring-boot.pdf)
* **Python Roadmap** - [Roadmap Link](https://roadmap.sh/python) / [PDF Link](https://roadmap.sh/pdfs/python.pdf)

View File

@@ -1,11 +1,12 @@
---
layout: ../layouts/MarkdownLayout.astro
title: Privacy Policy - roadmap.sh
noIndex: true
---
# Privacy Policy
By using or accessing the Services in any manner, you acknowledge that you accept the practices and policies outlined in this Privacy Policy, and you hereby consent that we will collect, use, and share your information in the following ways. Remember that your use of roadmap.shs Services is at all times subject to the [Terms of Use](/terms), which incorporates this Privacy Policy. Any terms we use in this Policy without defining them have the definitions given to them in the Terms of Use.
By using or accessing the Services in any manner, you acknowledge that you accept the practices and policies outlined in this Privacy Policy, and you hereby consent that we will collect, use, and share your information in the following ways. Remember that your use of roadmap.shs Services is at all times subject to the [Terms of Use](/terms/), which incorporates this Privacy Policy. Any terms we use in this Policy without defining them have the definitions given to them in the Terms of Use.
## What does this Privacy Policy cover?

View File

@@ -8,16 +8,20 @@ const roleRoadmaps = await getRoadmapsByTag('role-roadmap');
const skillRoadmaps = await getRoadmapsByTag('skill-roadmap');
---
<BaseLayout title="Developer Roadmaps" description={"Step by step guides and paths to learn different tools or technologies"}>
<BaseLayout
title='Developer Roadmaps'
description={'Step by step guides and paths to learn different tools or technologies'}
permalink={'/roadmaps/'}
>
<SimplePageHeader
title="Developer Roadmaps"
description="Step by step guides and paths to learn different tools or technologies"
title='Developer Roadmaps'
description='Step by step guides and paths to learn different tools or technologies'
showYouTubeAlert={true}
/>
<div class="bg-gray-100 pt-4 pb-14 sm:pt-8 sm:pb-16">
<div class="container">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-0.5 sm:gap-3">
<div class='bg-gray-100 pt-4 pb-14 sm:pt-8 sm:pb-16'>
<div class='container'>
<div class='grid grid-cols-1 sm:grid-cols-2 gap-0.5 sm:gap-3'>
{
roleRoadmaps.map((roleRoadmap) => (
<GridRoadmapItem roadmap={roleRoadmap} />

View File

@@ -7,6 +7,8 @@ import BaseLayout from '../layouts/BaseLayout.astro';
<BaseLayout
title='Signup - roadmap.sh'
description='Register yourself to receive occasional emails about new roadmaps, updates, guides and videos'
permalink={'/signup/'}
noIndex={true}
>
<div class='container'>
<div

View File

@@ -1,6 +1,7 @@
---
layout: ../layouts/MarkdownLayout.astro
title: Terms and Conditions - roadmap.sh
noIndex: true
---
# Terms of Service
@@ -21,7 +22,7 @@ Except for changes by us as described here, no other amendment or modification o
## Do these terms cover privacy?
You can view the current roadmap.sh [Privacy Policy here](/privacy).
You can view the current roadmap.sh [Privacy Policy here](/privacy/).
The Childrens Online Privacy Protection Act (“COPPA”) requires that online service providers obtain parental consent before they knowingly collect personally identifiable information online from children who are under 13. We do not knowingly collect or solicit personally identifiable information from children under 13. If you are a child under 13, please do not attempt to register for the Services or send any personal information about yourself to us. If we learn we have collected personal information from a child under 13, we will delete that information as quickly as possible. If you believe that a child under 13 may have provided us personal information, please contact us at kamranahmed.se@gmail.com.

View File

@@ -24,6 +24,7 @@ const { video } = Astro.props;
<BaseLayout
title={video.frontmatter.title}
description={video.frontmatter.description}
permalink={`/videos/${videoId}/`}
>
<VideoHeader video={video} />

View File

@@ -10,6 +10,7 @@ const videos = await getAllVideos();
<BaseLayout
title='Illustrated Videos - roadmap.sh'
description={'Graphical video demonstrations on software engineering topics.'}
permalink={`/videos/`}
>
<SimplePageHeader
title='Videos'

View File

@@ -119,7 +119,7 @@ I would highly recommend watching [this free course](https://www.udacity.com/cou
* [Developing Android Apps with Kotlin](https://www.udacity.com/course/developing-android-apps-with-kotlin--ud9012)
* [Android Basics in Kotlin](https://developer.android.com/courses/android-basics-kotlin/course)
* [Android Developer Guides](https://developer.android.com/guide)
* [Raywenderlich](https://www.raywenderlich.com)
* [Kodeco](https://www.kodeco.com)
## Wrap Up

View File

View File

@@ -4,5 +4,5 @@ A schematic is a template-based code generator that supports complex logic. It i
Visit the following resources to learn more:
- [Angular Website](https://angular.io/guide/schematics#:~:text=A%20schematic%20is%20a%20template,collections%20and%20installed%20with%20npm.)
- [Angular Website](https://angular.io/guide/schematics)
- [Angular Blog](https://blog.angular.io/schematics-an-introduction-dc1dfbc2a2b2?gi=ad9571373944)

View File

@@ -4,4 +4,4 @@ Angular processes all data bindings once for each JavaScript event cycle, from t
Visit the following resources to learn more:
- [Angular.io Website](https://angular.io/guide/architecture-components#:~:text=Angular%20processes%20all%20data%20bindings,between%20parent%20and%20child%20components.)
- [Angular.io Website](https://angular.io/guide/architecture-components)

View File

View File

View File

@@ -11,7 +11,7 @@ dimensions:
width: 968
height: 2840.4
seo:
title: "Backend Developer Roadmap: Learn to become a modern backend developer"
title: "Backend Developer Roadmap"
description: "Learn to become a modern backend developer using this roadmap. Community driven, articles, resources, guides, interview questions, quizzes for modern backend development."
keywords:
- "backend roadmap 2023"

View File

@@ -0,0 +1,10 @@
# Git
[Git](https://git-scm.com/) is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.
Visit the following resources to learn more:
- [Learn Git on the command line](https://github.com/jlord/git-it-electron)
- [Version Control System Introduction](https://www.youtube.com/watch?v=zbKdDsNNOhg)
- [Git & GitHub Crash Course For Beginners](https://www.youtube.com/watch?v=SWYqp7iY_Tc)
- [Learn Git in 20 Minutes](https://youtu.be/Y9XZQO1n_7c?t=21)

View File

@@ -10,5 +10,4 @@ Service meshes are commonly used in cloud-native architectures and are often man
Visit the following resources to learn more:
- [What is a Service Mesh?](https://www.nginx.com/blog/what-is-a-service-mesh/)
- [Service Mesh Explained - Cloud Native Computing Foundation (CNCF)](https://www.cncf.io/blog/2018/05/02/service-mesh-explained/)
- [What is a Service Mesh?](https://www.nginx.com/blog/what-is-a-service-mesh/)

View File

@@ -0,0 +1,140 @@
---
import Answer from '../../components/FAQs/Answer.astro';
import FAQs from '../../components/FAQs/FAQs.astro';
import Question from '../../components/FAQs/Question.astro';
const salaryLink =
'https://www.glassdoor.com/Salaries/united-states-backend-developer-salary-SRCH_IL.0,13_IN1_KO14,31.htm';
---
<FAQs>
<Question isActive question='What is Backend Development?'>
<Answer>
<p class='mb-3'>
Backend web development is the part of web development that deals with
the server-side of a web application. This includes creating and
managing the server-side logic, connecting the application to a
database, creating server-side APIs, handling user authentication and
authorization, and processing and responding to user requests. It often
involves the use of programming languages such as Python, Java, Ruby,
PHP, JavaScript (Node.js), and .NET languages.
</p>
<p>
A backend developer is responsible for the development of server-side
components of a web application i.e. working with databases, handling
requests, creating server-side APIs that can be consumed by frontend
developers to retrieve and manipulate data, ensuring the scalability of
the systems i.e. making sure that the backend can handle a high volume
of traffic and is performant, integrating external services like payment
gateways, message queues, cloud services, etc.
</p>
</Answer>
</Question>
<Question question='How to become a Backend Developer?'>
<Answer>
<p class='mb-3'>
If you are a complete beginner who is just getting started, you can
start by learning <span class='bg-yellow-100'
>a backend programming language</span
> such as Python, Ruby, Java, Go etc. Once you have got the basic to intermediate
understanding of the language, learn about <span class='bg-yellow-100'
>the package manager</span
> for that language and learn how to install and use external packages into
your applications. Learn the basics of some <span class='bg-yellow-100'
>relational database</span
> e.g. PostgreSQL and learn how to run simple CRUD operations. Optionally,
you can pick up and learn a <span class='bg-yellow-100'
>web framework</span
> for the language of your choice as well. Learn how to build a <span
class='bg-yellow-100'>simple RESTful API</span
> and implement simple Authentication/Authorization into it. While you are
learning all the items mentioned above, don't forget to learn about <span
class='bg-yellow-100'>Git and GitHub</span
> as well.
</p>
<p class='mb-3'>
After following all the instructions above, you can start applying for
the entry level backend developer jobs. Also, look at the backend
developer roadmap above to get an idea about the landscape and see what
else you are missing. A degree in computer science or related field is
not always necessary but networking, building a portfolio and actively
seeking internships, junior developer positions or consulting can help
to start and advance a career as a backend developer.
</p>
<p>
Note: remember to make a lot of projects while you are learning to
solidify your understanding of the concepts. Also, it's important to
have the attitude of continuous learning to improve your skills and be
prepared for the fast-paced technology evolution in the industry.
</p>
</Answer>
</Question>
<Question question='How long does it take to become a Backend Developer?'>
<Answer>
<p class='mb-3'>
The amount of time it takes to become a backend developer can vary
depending on several factors, such as your learning pace, previous
experience and the amount of time you are able to dedicate to learning.
</p>
<p>
If you have a background in computer science or a related field, and
have experience with programming, you may be able to become a backend
developer relatively quickly, potentially within a few months. However,
if you are starting with little or no prior experience or education in
computer science, it may take longer to develop the necessary skills and
gain the experience needed to be a proficient backend developer. It
could take anywhere from 6 months to 2 years.
</p>
</Answer>
</Question>
<Question question='What are the Backend Developer salaries?'>
<Answer>
<p class='mb-3'>
Backend developer salaries can vary depending on factors such as
location, experience, and company size. According to data from
Glassdoor, the average base salary for a backend developer in the United
States is around $92,000 per year. However, this number can vary greatly
depending on location, with the highest-paying cities such as San
Francisco, Seattle and New York having an average salary of $120,000 to
$135,000 per year.
</p>
<p class='mb-3'>
It's important to keep in mind that these are just averages, and
salaries can vary greatly depending on factors such as experience level,
specific skills, and the company you work for. With more experience and
specific skills relevant to the job you are applying for you can expect
to earn more.
</p>
<p>
It is worth looking at a range of resources, including salary surveys,
and job boards to get a general understanding of the current market in
your location and experience level. Also try reaching out to other
professionals in the field and getting an understanding of their own
experience and salary ranges.
</p>
</Answer>
</Question>
<Question question='Should I learn everything listed on the Backend Roadmap?'>
<Answer>
<p class='mb-3'>
This roadmap contains everything that you might encounter while working
as a Backend Developer. You may not need everything listed on this
roadmap to get into the industry; every job is different and most of the
jobs will require a subset of the items on the roadmap. However, knowing
what you don't know is as important as knowing things, so you can use
this roadmap to get an idea of what you are missing as well.
</p>
<p>
If you are a beginner who is just getting started, don't feel
overwhelmed by looking at this roadmap. Look at the answer to the FAQ
"How to become a Backend Developer?"
</p>
</Answer>
</Question>
</FAQs>

View File

View File

@@ -1,4 +1,4 @@
# gPRC
# gRPC
gRPC is a platform agnostic serialization protocol that is used to communicate between services. Designed by Google in 2015, it is a modern alternative to REST APIs. It is a binary protocol that uses HTTP/2 as a transport layer. It is a high performance, open source, general-purpose RPC framework that puts mobile and HTTP/2 first.

View File

View File

View File

@@ -1,7 +1,7 @@
---
jsonUrl: "/jsons/design-system.json"
pdfUrl: "/pdfs/design-system.pdf"
order: 10
order: 11
featuredTitle: "Design System"
featuredDescription: "Step by step guide to building a modern Design System"
title: "Design System"

View File

View File

@@ -11,14 +11,14 @@ dimensions:
width: 968
height: 2527.46
sponsor:
url: "https://www.getambassador.io/edge-stack-guide-v4?utm_source=roadmap.sh&utm_medium=ebook&utm_campaign=edgestack-guide"
url: "https://www.tigera.io/lp/kubernetes-security-and-observability-ebook/?utm_campaign=O%27Reilly-book-final-release&utm_medium=web&utm_source=Insight-Partners-popup"
title: "Free eBook"
imageUrl: "https://i.imgur.com/0bH1Vl6.png"
description: "Learn about API Gateways, Microservices, Load Balancing, and more with this free eBook."
imageUrl: "/images/devops-ebook.png"
description: "Learn how to secure and troubleshoot your cloud-native applications with this free eBook."
event:
category: "SponsorClick"
action: "Ambassador EBook Redirect"
label: "Clicked Ambassador EBook Link"
action: "Tigera EBook Redirect"
label: "Clicked Tigera EBook Link"
seo:
title: "DevOps Roadmap: Learn to become a DevOps Engineer or SRE"
description: "Community driven, articles, resources, guides, interview questions, quizzes for DevOps. Learn to become a modern DevOps engineer by following the steps, skills, resources and guides listed in this roadmap."

View File

Some files were not shown because too many files have changed in this diff Show More