Compare commits

..

104 Commits

Author SHA1 Message Date
syedmouaazfarrukh
5abd6f2927 Adding content to graphql 2023-01-21 09:12:31 -08:00
syedmouaazfarrukh
c2ce8ed50a Adding content to 104-validation, 107-pagination 2023-01-20 11:30:52 -08:00
syedmouaazfarrukh
6b836f4912 Adding content to 106-serving-over-internet 2023-01-20 11:24:42 -08:00
syedmouaazfarrukh
7c14032d45 Adding content to 102-graphql-over-sse 2023-01-20 11:21:23 -08:00
syedmouaazfarrukh
463e047849 Adding content to 101-graphql-over-websockets 2023-01-20 11:10:23 -08:00
syedmouaazfarrukh
b074e64854 Adding content to 100-graphql-over-http 2023-01-20 11:04:14 -08:00
syedmouaazfarrukh
9ac1c1c8cb Adding content to 109-backend-implementations 2023-01-20 10:53:07 -08:00
syedmouaazfarrukh
e3a6fda3c7 Adding content to 105-execution 2023-01-20 10:39:27 -08:00
syedmouaazfarrukh
1ca0afbed9 Adding content to 101-resolvers 2023-01-20 10:30:01 -08:00
syedmouaazfarrukh
f16bd09a47 Adding content to 104-schema 2023-01-20 10:13:44 -08:00
syedmouaazfarrukh
f5fcf1dccc Adding content to 108-frontend-implementation 2023-01-19 09:53:47 -08:00
syedmouaazfarrukh
ac8711a0aa Adding content to 103-subscriptions 2023-01-19 09:38:34 -08:00
syedmouaazfarrukh
4745811114 Adding content to 102-mutations 2023-01-19 08:50:07 -08:00
syedmouaazfarrukh
12c1773965 Adding content to 101-graphql-queries 2023-01-19 08:31:42 -08:00
syedmouaazfarrukh
ff0215673c Adding content to 100-graphql-introduction 2023-01-19 06:55:54 -08:00
Kamran Ahmed
f5e980d8ec Add functionality to add note to the roadmaps 2023-01-19 15:05:25 +04:00
Kamran Ahmed
6187b1dc52 Update isNew tags 2023-01-19 14:16:03 +04:00
syedmouaazfarrukh
a3b8b5653a Add content to system-design (#3323) 2023-01-19 14:10:10 +04:00
Kamran Ahmed
8f8e2f41d8 Rearrange JSON files 2023-01-18 19:47:47 +04:00
Kamran Ahmed
89a436a5b7 Add content for making API calls using http moduel 2023-01-18 17:00:04 +04:00
Zied Chekir
231e295f01 Add resource links to blockchain roadmap (#3317)
I would suggest those articles since it is an advanced subject. the first one explains the math behind zk-rollups and the second one is Vitalik ( Founder of Ethereum) explaining how snarks work.
2023-01-18 16:56:03 +04:00
Kamran Ahmed
64e20e9fc1 Add links to roadmaps from FAQs on the frontend roadmap 2023-01-18 16:19:21 +04:00
Kamran Ahmed
621f841fbf Add a guide about consistency patterns 2023-01-18 02:46:59 +04:00
Kamran Ahmed
c61afb15bc Add content in consistency patterns 2023-01-18 02:35:24 +04:00
RaifAR
595f3680be fix: typo in the word development (#3320)
Co-authored-by: Raif Abdul Razak <raif@Raifs-MacBook-Air.local>
2023-01-17 22:57:02 +04:00
Kamran Ahmed
ee65c56bf3 Add content to availability vs consistency 2023-01-17 20:25:06 +04:00
Kamran Ahmed
a2c339f2d5 Add system design roadmap content 2023-01-17 18:49:53 +04:00
Kamran Ahmed
a3031a2371 Add system design roadmap 2023-01-17 18:06:43 +04:00
Kamran Ahmed
952169ec8e Add AWS guide link 2023-01-16 19:53:25 +04:00
Kamran Ahmed
fbd82ce215 Remove pluralsight links 2023-01-15 15:37:16 +04:00
Kamran Ahmed
35f61e876e Fix broken URL 2023-01-15 15:36:19 +04:00
syedmouaazfarrukh
bb9878fdb7 Add content to aspnet-core roadmap (#3294)
* Initial commit

* Initial commit

* Initial commit

* Initial commit

* Initial commit

* Initial commit

* Initial commit

* Initial commit

* Initital commit

* Initial commit

* Initial commit

* Committing 107-databases

* Content added in aspnet-core/content/108-log-frameworks

* Content added in aspnet-core/content/109-api-clients

* Content added in aspnet-core/content/110-real-time-communication

* Content added in aspnet-core/content/111-object-mapping

* Content added in aspnet-core/content/112-task-scheduling

* Fix Eager Loading heading

* Fix lazy loading heading

* Update src/roadmaps/aspnet-core/content/104-orm/103-nhibernate.md

* Update src/roadmaps/aspnet-core/content/100-basics-of-csharp/101-dotnet.md

* Update src/roadmaps/aspnet-core/content/103-basics-of-aspnet-core/101-rest.md

* Update src/roadmaps/aspnet-core/content/109-api-clients/102-graphql/100-graphql-dotnet.md

* Update src/roadmaps/aspnet-core/content/103-basics-of-aspnet-core/index.md

* Update src/roadmaps/aspnet-core/content/104-orm/100-entity-framework-core/102-change-tracker-api.md

* Update src/roadmaps/aspnet-core/content/104-orm/102-repodb.md

* Update src/roadmaps/aspnet-core/content/104-orm/102-repodb.md

* Update src/roadmaps/aspnet-core/content/105-dependency-injection/102-life-cycles/index.md

* Update src/roadmaps/aspnet-core/content/105-dependency-injection/index.md

* Update src/roadmaps/aspnet-core/content/109-api-clients/102-graphql/100-graphql-dotnet.md

* Update src/roadmaps/aspnet-core/content/109-api-clients/102-graphql/index.md

* Update src/roadmaps/aspnet-core/content/110-real-time-communication/101-singlar-core.md

* Update src/roadmaps/aspnet-core/content/111-object-mapping/100-atuo-mapper.md

* Update src/roadmaps/aspnet-core/content/107-databases/100-search-engines/100-elasticsearch.md

* Update src/roadmaps/aspnet-core/content/107-databases/102-relational/101-postgresql.md

* Update src/roadmaps/aspnet-core/content/107-databases/102-relational/103-mysql.md

* Update src/roadmaps/aspnet-core/content/107-databases/103-nosql/101-mongodb.md

* Update src/roadmaps/aspnet-core/content/108-log-frameworks/101-nlog.md

* Update src/roadmaps/aspnet-core/content/108-log-frameworks/100-serilog.md

* Update src/roadmaps/aspnet-core/content/108-log-frameworks/102-log-management-system/100-elk-stack.md

* Update src/roadmaps/aspnet-core/content/108-log-frameworks/102-log-management-system/104-elmah.md

* Update src/roadmaps/aspnet-core/content/108-log-frameworks/index.md

* Update src/roadmaps/aspnet-core/content/109-api-clients/100-rest/101-odata.md

* Update src/roadmaps/aspnet-core/content/109-api-clients/100-rest/index.md

* Update src/roadmaps/aspnet-core/content/109-api-clients/102-graphql/100-graphql-dotnet.md

* Update src/roadmaps/aspnet-core/content/109-api-clients/101-grpc.md

* Adding content to 115-ci-cd

* Adding content to 116-client-side-libraries

* Adding content to 117-template-engines

* Adding content to 118-good-to-know-libraries

* Adding content to 113-testing

* Adding content to 114-microservices

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
2023-01-15 15:35:54 +04:00
Kamran Ahmed
ee843cc9e2 Add tests for roadmaps, guides and videos 2023-01-15 15:26:58 +04:00
Kamran Ahmed
cbd79ef299 Add tests for roadmap pages and homepage 2023-01-15 15:23:27 +04:00
Kamran Ahmed
af9e266190 Update dependencies 2023-01-15 15:01:34 +04:00
Kamran Ahmed
0cbd401071 Remove critters 2023-01-15 14:32:37 +04:00
kamranahmedse
0929d40bd0 chore: update dependencies to latest 2023-01-15 14:32:12 +04:00
Kamran Ahmed
927aa0a066 Update twitter URL 2023-01-14 18:14:01 +04:00
Kamran Ahmed
85eff7f894 Fix invalid faq schema 2023-01-14 12:11:16 +04:00
Kamran Ahmed
11695f4b05 Add json-ld schema to all roadmaps 2023-01-14 01:20:57 +04:00
Kamran Ahmed
aebee9b3a3 Add json-ld schema to the backend roadmap and refactor 2023-01-14 00:58:45 +04:00
Kamran Ahmed
6b52baf093 Add json-ld schema to the roadmap pages 2023-01-14 00:32:14 +04:00
Kamran Ahmed
6922fd826f Remove prism file 2023-01-13 15:48:26 +04:00
Kamran Ahmed
ec29e1836e Update configuration for colors 2023-01-13 14:49:59 +04:00
Kamran Ahmed
dca9eb32cd Remove prism file 2023-01-13 14:45:44 +04:00
Kamran Ahmed
4b681c6317 Add json-ld schema to frontend/backend roadmaps 2023-01-13 12:37:45 +04:00
Kamran Ahmed
9c24ff23e3 Guide code not showing proper bg 2023-01-12 19:12:26 +04:00
Kamran Ahmed
cdc87a99e1 Add ambassador eBook link 2023-01-12 18:48:11 +04:00
Kamran Ahmed
ea16e99598 Add ambassador eBook link 2023-01-12 18:46:49 +04:00
Kamran Ahmed
ba86e8a6b1 Update content headings 2023-01-12 14:41:54 +04:00
syedmouaazfarrukh
5f23d4c7eb Add content to Spring Boot roadmap (#3285)
* Initial commit

* Update content files

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
2023-01-12 14:38:50 +04:00
Kamran Ahmed
8264c4509f Update canonicals 2023-01-11 22:28:41 +04: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
652 changed files with 7690 additions and 1643 deletions

4
.gitignore vendored
View File

@@ -20,3 +20,7 @@ pnpm-debug.log*
# macOS-specific files
.DS_Store
/test-results/
/playwright-report/
/playwright/.cache/
tests-examples

View File

@@ -9,6 +9,9 @@ import { serializeSitemap, shouldIndexPage } from './sitemap.mjs';
export default defineConfig({
site: 'https://roadmap.sh',
markdown: {
shikiConfig: {
theme: 'dracula'
},
rehypePlugins: [
[
rehypeExternalLinks,

View File

@@ -2,13 +2,18 @@ const fs = require('node:fs');
const path = require('node:path');
const jsonsDir = path.join(process.cwd(), 'public/jsons');
const jsonFiles = fs.readdirSync(jsonsDir);
const childJsonDirs = fs.readdirSync(jsonsDir);
jsonFiles.forEach((jsonFileName) => {
console.log(`Compressing ${jsonFileName}...`);
childJsonDirs.forEach((childJsonDir) => {
const fullChildJsonDirPath = path.join(jsonsDir, childJsonDir);
const jsonFiles = fs.readdirSync(fullChildJsonDirPath);
const jsonFilePath = path.join(jsonsDir, jsonFileName);
const json = require(jsonFilePath);
jsonFiles.forEach((jsonFileName) => {
console.log(`Compressing ${jsonFileName}...`);
fs.writeFileSync(jsonFilePath, JSON.stringify(json));
const jsonFilePath = path.join(fullChildJsonDirPath, jsonFileName);
const json = require(jsonFilePath);
fs.writeFileSync(jsonFilePath, JSON.stringify(json));
});
});

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/roadmaps/${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

@@ -3,10 +3,10 @@
First of all thank you for considering to contribute. Please look at the details below:
- [Contribution](#contribution)
- [New Roadmaps](#new-roadmaps)
- [Existing Roadmaps](#existing-roadmaps)
- [Adding Content](#adding-content)
- [Guidelines](#guidelines)
- [New Roadmaps](#new-roadmaps)
- [Existing Roadmaps](#existing-roadmaps)
- [Adding Content](#adding-content)
- [Guidelines](#guidelines)
## New Roadmaps
@@ -23,7 +23,7 @@ For the existing roadmaps, please follow the details listed for the nature of co
## Adding Content
Find [the content directory inside the relevant roadmap](https://github.com/kamranahmedse/roadmap-astro/tree/master/src/roadmaps). Please keep the following guidelines in mind when submitting content:
Find [the content directory inside the relevant roadmap](https://github.com/kamranahmedse/developer-roadmap/tree/master/src/roadmaps). Please keep the following guidelines in mind when submitting content:
- Content must be in English.
- Put a brief description about the topic on top of the file and the a list of links below with each link having title of the URL.

View File

@@ -10,25 +10,30 @@
"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",
"test:e2e": "playwright test"
},
"dependencies": {
"@astrojs/sitemap": "^1.0.0",
"@astrojs/tailwind": "^2.1.3",
"astro": "^1.8.0",
"astro-compress": "^1.1.24",
"astro-critters": "^1.1.24",
"astro": "^1.9.2",
"astro-compress": "^1.1.27",
"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"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.8",
"@playwright/test": "^1.29.2",
"@tailwindcss/typography": "^0.5.9",
"gh-pages": "^4.0.0",
"json-to-pretty-yaml": "^1.2.2",
"prettier": "^2.8.1",
"prettier-plugin-astro": "^0.7.0"
"markdown-it": "^13.0.1",
"prettier": "^2.8.3",
"prettier-plugin-astro": "^0.7.2"
}
}

108
playwright.config.ts Normal file
View File

@@ -0,0 +1,108 @@
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
const config: PlaywrightTestConfig = {
testDir: './tests',
/* Maximum time one test can run for. */
timeout: 30 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 5000,
},
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
// {
// name: 'firefox',
// use: {
// ...devices['Desktop Firefox'],
// },
// },
// {
// name: 'webkit',
// use: {
// ...devices['Desktop Safari'],
// },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: {
// ...devices['Pixel 5'],
// },
// },
// {
// name: 'Mobile Safari',
// use: {
// ...devices['iPhone 12'],
// },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: {
// channel: 'msedge',
// },
// },
// {
// name: 'Google Chrome',
// use: {
// channel: 'chrome',
// },
// },
],
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
// outputDir: 'test-results/',
/* Run your local dev server before starting the tests */
webServer: {
command: 'npm run dev',
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
};
export default config;

1583
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: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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.

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 [&>p:not(:last-child)]:mb-3 [&>p>a]:underline [&>p>a]:text-blue-500'>
<slot />
</div>

View File

@@ -0,0 +1,42 @@
---
import { markdownToHtml } from '../../lib/markdown';
import Answer from './Answer.astro';
import Question from './Question.astro';
export type FAQType = {
question: string;
answer: string[];
};
export interface Props {
faqs: FAQType[];
}
const { faqs } = Astro.props;
if (faqs.length === 0) {
return '';
}
---
<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'>
{
faqs.map((faq, questionIndex) => (
<Question isActive={questionIndex === 0} question={faq.question}>
<Answer>
{faq.answer.map((answer) => (
<p set:html={markdownToHtml(answer)} />
))}
</Answer>
</Question>
))
}
</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

@@ -43,7 +43,7 @@ import Icon from './Icon.astro';
<span class='text-gray-400 mx-2'>by</span>
<a
class='bg-blue-600 text-sm py-1 px-1.5 font-regular hover:bg-blue-700 rounded-md'
href='https://twitter.com/kamranahmedse'
href='https://twitter.com/intent/user?screen_name=kamranahmedse'
target='_blank'
>
<span class='hidden sm:inline'>@kamranahmedse</span>
@@ -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,22 @@
---
import "../styles/prism.css";
import DownloadPopup from './DownloadPopup.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-2'>
<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,69 +1,73 @@
---
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 RoadmapNote from './RoadmapNote.astro';
import TopicSearch from './TopicSearch/TopicSearch.astro';
import YouTubeAlert from './YouTubeAlert.astro';
export interface Props {
title: string;
description: string;
note?: string;
roadmapId: string;
isUpcoming?: boolean;
hasSearch?: boolean;
hasTopics?: boolean;
}
const {
title,
description,
roadmapId,
isUpcoming = false,
hasSearch = false,
hasTopics = false,
} = Astro.props;
const { title, description, roadmapId, isUpcoming = false, hasSearch = false, note, 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 +77,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 +91,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>
)
}
@@ -105,3 +109,5 @@ const isRoadmapReady = !isUpcoming;
{hasSearch && <TopicSearch />}
</div>
</div>
{note && <RoadmapNote text={note} />}

View File

@@ -0,0 +1,19 @@
---
import { Debug } from 'astro/components';
import { markdownToHtml } from '../lib/markdown';
export interface Props {
text: string;
}
const { text } = Astro.props;
---
<div class='bg-gray-50'>
<div class='container pt-1'>
<p
class='text-sm bg-yellow-100 text-yellow-900 p-2 rounded-md mt-2 sm:mt-5 mb-0 sm:-mb-6 z-10 relative [&>a]:underline'
set:html={markdownToHtml(text.trim())}
/>
</div>
</div>

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

@@ -0,0 +1,76 @@
---
title: "Consistency Patterns"
description: "Everything you need to know about Week, Strong and Eventual Consistency"
author:
name: "Kamran Ahmed"
url: "https://twitter.com/kamranahmedse"
imageUrl: "/authors/kamranahmedse.jpeg"
seo:
title: "Consistency Patterns - roadmap.sh"
description: "Everything you need to know about Week, Strong and Eventual Consistency"
isNew: true
canonicalUrl: "https://cs.fyi/guide/consistency-patterns-week-strong-eventual/"
type: "textual"
date: 2023-01-18
sitemap:
priority: 0.7
changefreq: "weekly"
tags:
- "guide"
- "visual-guide"
- "guide-sitemap"
---
Before we talk about the Consistency Patterns, we should know what a distributed system is. Simply put, a distributed system is a system that consists of more than one components, and each component is responsible for one part of the application.
> A distributed system is a system whose components are located on different networked computers, which communicate and coordinate their actions by passing messages to one another. The components interact with one another in order to achieve a common goal. - Wikipedia
## Distributed Systems
Imagine we have an e-commerce application where we are selling books. This application may consist of multiple different components. For example, one server might be responsible for the accounts, another might be responsible for the payments, one might be responsible for storing orders, one might be responsible for loyalty points and relevant functionalities, and another might be responsible for maintaining the books inventory and so on.
![Book Store - Distributed System](https://i.imgur.com/VHATt7a.png)
Now, if a user buys a book, there might be different services involved in placing the order; order service for storing the order, payment service for handling the payments, and inventory service for keeping the stock of that ordered book up to date. This is an example of a distributed system, an application that consists of multiple different components, each of which is responsible for a different part of the application.
## Why is Consistency Important?
When working with distributed systems, we need to think about managing the data across different servers. If we take the above example of the e-commerce application, we can see that the inventory service must have up-to-date stock information for the ordered items if the user places an order. Now, there might be two different users looking at the same book. Now imagine if one of the customers places a successful order, and before the inventory service can update the stock, the second customer also places the order for the same book. In that case, when the inventory wasn't updated, we will have the wrong stock information when the second order was placed, i.e., the ordered book may or may not be available in stock. This is where different consistency patterns come into play. They help ensure that the data is consistent across the application.
## Consistency Patterns
Consistency patterns refer to the ways in which data is stored and managed in a distributed system and how that data is made available to users and applications. There are three main types of consistency patterns:
* Strong consistency
* Weak consistency
* Eventual Consistency
Each of these patterns has its own advantages and disadvantages, and the choice of which pattern to use will depend on the specific requirements of the application or system.
### Strong Consistency
> After an update is made to the data, it will be immediately visible to any subsequent read operations. The data is replicated in a synchronous manner, ensuring that all copies of the data are updated at the same time.
In a strong consistency system, any updates to some data are immediately propagated to all locations. This ensures that all locations have the same version of the data, but it also means that the system is not highly available and has high latency.
An example of strong consistency is a financial system where users can transfer money between accounts. The system is designed for **high data integrity**, so the data is stored in a single location and updates to that data are immediately propagated to all other locations. This ensures that all users and applications are working with the same, accurate data. For instance, when a user initiates a transfer of funds from one account to another, the system immediately updates the balance of both accounts and all other system components are immediately aware of the change. This ensures that all users can see the updated balance of both accounts and prevents any discrepancies.
### Weak Consistency
> After an update is made to the data, it is not guaranteed that any subsequent read operation will immediately reflect the changes made. The read **may or may not** see the recent write.
In a weakly consistent system, updates to the data may not be immediately propagated. This can lead to inconsistencies and conflicts between different versions of the data, but it also allows for **high availability and low latency**.
Another example of weak consistency is a gaming platform where users can play online multiplayer games. When a user plays a game, their actions are immediately visible to other players in the same data center, but if there was a lag or temporary connectoin loss, the actions may not be seen by some of the users and the game will continue. This can lead to inconsistencies between different versions of the game state, but it also allows for a high level of availability and low latency.
### Eventual Consistency
> Eventual consistency is a form of Weak Consistency. After an update is made to the data, it will be eventually visible to any subsequent read operations. The data is replicated in an asynchronous manner, ensuring that all copies of the data are eventually updated.
In an eventually consistent system, data is typically stored in multiple locations, and updates to that data are eventually propagated to all locations. This means that the system is highly available and has low latency, but it also means that there may be inconsistencies and conflicts between different versions of the data.
An example of eventual consistency is a social media platform where users can post updates, comments, and messages. The platform is designed for high availability and low latency, so the data is stored in multiple data centers around the world. When a user posts an update, the update is immediately visible to other users in the same data center, but it may take some time for the update to propagate to other data centers. This means that some users may see the update while others may not, depending on which data center they are connected to. This can lead to inconsistencies between different versions of the data, but it also allows for a high level of availability and low latency.
## Conclusion
In conclusion, consistency patterns play a crucial role in distributed systems, and the choice of which pattern to use will depend on the specific requirements of the application or system. Each pattern has its own advantages and disadvantages, and each is more suitable for different use cases. Weak consistency is suitable for systems that require high availability and low latency, strong consistency is suitable for systems that require high data integrity, and eventual consistency is suitable for systems that require both high availability and high data integrity.

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,7 +14,10 @@ export interface Props {
description?: string;
keywords?: string[];
noIndex?: boolean;
canonicalUrl?: string;
permalink?: string;
sponsor?: SponsorType;
jsonLd?: Record<string, unknown>[];
}
const {
@@ -22,8 +25,18 @@ const {
description = siteConfig.description,
keywords = siteConfig.keywords,
noIndex = false,
permalink = '',
canonicalUrl: givenCanonical = '',
sponsor,
jsonLd = [],
} = Astro.props;
// Remove trailing slashes to consider the page as canonical
const currentPageAbsoluteUrl = `https://roadmap.sh${permalink}`;
const canonicalUrl = givenCanonical || currentPageAbsoluteUrl;
const commitUrl = `https://github.com/kamranahmedse/developer-roadmap/commit/${import.meta.env.GITHUB_SHA}`;
---
<!DOCTYPE html>
@@ -31,6 +44,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,47 +67,29 @@ 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={canonicalUrl} />
<meta name='mobile-web-app-capable' content='yes' />
<meta name='apple-mobile-web-app-capable' content='yes' />
<meta
name='apple-mobile-web-app-status-bar-style'
content='black-translucent'
/>
<meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' />
<meta name='apple-mobile-web-app-title' content='roadmap.sh' />
<meta name='application-name' content='roadmap.sh' />
<link
rel='apple-touch-icon'
sizes='180x180'
href='/manifest/apple-touch-icon.png'
/>
<link rel='apple-touch-icon' sizes='180x180' href='/manifest/apple-touch-icon.png' />
<meta name='msapplication-TileColor' content='#101010' />
<meta name='theme-color' content='#848a9a' />
<link rel='manifest' href='/manifest/manifest.json' />
<link
rel='icon'
type='image/png'
sizes='32x32'
href='/manifest/icon32.png'
/>
<link
rel='icon'
type='image/png'
sizes='16x16'
href='/manifest/icon16.png'
/>
<link
rel='shortcut icon'
href='/manifest/favicon.ico'
type='image/x-icon'
/>
<link rel='icon' type='image/png' sizes='32x32' href='/manifest/icon32.png' />
<link rel='icon' type='image/png' sizes='16x16' href='/manifest/icon16.png' />
<link rel='shortcut icon' href='/manifest/favicon.ico' type='image/x-icon' />
<link rel='icon' href='/manifest/favicon.ico' type='image/x-icon' />
<slot name='after-header' />
{jsonLd.length > 0 && <script type='application/ld+json' set:html={JSON.stringify(jsonLd)} />}
</head>
<body>
<YouTubeBanner />

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);
}

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