mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-01-17 22:28:32 +01:00
Restructure routes, add tests and make pathmap dynamic
This commit is contained in:
parent
159741f0af
commit
796f1cdac0
84
__tests__/path-map.spec.js
Normal file
84
__tests__/path-map.spec.js
Normal file
@ -0,0 +1,84 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const guides = require('../data/guides');
|
||||
const roadmaps = require('../data/roadmaps');
|
||||
|
||||
const {
|
||||
getPageRoutes,
|
||||
getGuideRoutes,
|
||||
getRoadmapRoutes
|
||||
} = require("../path-map");
|
||||
|
||||
describe("Build scripts tests", () => {
|
||||
test('it should generate valid pathmap for pages', () => {
|
||||
const pageRoutes = getPageRoutes();
|
||||
|
||||
expect(pageRoutes).toEqual({
|
||||
'/': { page: '/index' },
|
||||
'/about': { page: '/about' },
|
||||
'/privacy': { page: '/privacy' },
|
||||
'/terms': { page: '/terms' },
|
||||
'/guides': { page: '/guides/index' },
|
||||
'/roadmaps': { page: '/roadmaps' },
|
||||
});
|
||||
});
|
||||
|
||||
test('it should generate valid guides pathmap', () => {
|
||||
const expectedGuideRoutes = guides.reduce((acc, guide) => {
|
||||
const [,, slug] = guide.url.split('/');
|
||||
return {
|
||||
...acc,
|
||||
[guide.url]: {
|
||||
page: '/guides/[guide]',
|
||||
query: slug,
|
||||
}
|
||||
};
|
||||
}, {});
|
||||
|
||||
// Valid path map is generated
|
||||
expect(expectedGuideRoutes).toEqual(getGuideRoutes());
|
||||
|
||||
const pageFilePath = path.join(__dirname, '../pages/guides/[guide].js');
|
||||
const foundFilePath = fs.existsSync(pageFilePath) ? pageFilePath : '';
|
||||
|
||||
// Given page component exists
|
||||
expect(foundFilePath).toEqual(pageFilePath);
|
||||
});
|
||||
|
||||
test('it should have markdown file for each guide', () => {
|
||||
guides.forEach(guide => {
|
||||
const [,, slug] = guide.url.split('/');
|
||||
|
||||
const expectedFile = path.join(__dirname, `../data/guides/${slug}.md`);
|
||||
const foundFile = fs.existsSync(expectedFile) ? expectedFile : '';
|
||||
|
||||
expect(foundFile).toEqual(expectedFile);
|
||||
})
|
||||
});
|
||||
|
||||
test('it should generate valid roadmap routes', () => {
|
||||
const expectedPathMap = roadmaps.reduce((roadmapAcc, roadmap) => {
|
||||
// Routes for each of the versions of this roadmap
|
||||
const versionRoutes = roadmap.versions.reduce((versionAcc, version) => ({
|
||||
...versionAcc,
|
||||
[`${roadmap.url}/${version}`]: {
|
||||
page: '/[roadmap]/[version]',
|
||||
query: `${roadmap.url.split('/')[1]}/${version}`,
|
||||
}
|
||||
}), {});
|
||||
|
||||
// Route for the route roadmap itself
|
||||
return {
|
||||
...roadmapAcc,
|
||||
[roadmap.url]: {
|
||||
page: '/[roadmap]/index',
|
||||
query: roadmap.url.split('/')[1]
|
||||
},
|
||||
// Expected roadmap for versions
|
||||
...versionRoutes
|
||||
};
|
||||
}, {});
|
||||
|
||||
expect(getRoadmapRoutes()).toEqual(expectedPathMap);
|
||||
})
|
||||
});
|
@ -16,7 +16,7 @@ const FeaturedGuides = () => (
|
||||
{ guides
|
||||
.filter(({ featured }) => featured)
|
||||
.map(guide => (
|
||||
<GuideBlock guide={ guide } key={ guide.slug } />
|
||||
<GuideBlock guide={ guide } key={ guide.url } />
|
||||
)) }
|
||||
</div>
|
||||
</div>
|
||||
|
@ -21,7 +21,7 @@ const FeaturedRoadmaps = () => (
|
||||
{ roadmaps
|
||||
.filter(({ featured }) => featured)
|
||||
.map(roadmap => (
|
||||
<RoadmapBlock roadmap={ roadmap } key={ roadmap.slug } />
|
||||
<RoadmapBlock roadmap={ roadmap } key={ roadmap.url } />
|
||||
)) }
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6,7 +6,7 @@ const GuideBlock = ({ guide }) => {
|
||||
const author = findByUsername(guide.author) || {};
|
||||
return (
|
||||
<div className="col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12 grid-item-container">
|
||||
<Link href={ guide.slug } passHref>
|
||||
<Link href={ guide.url } passHref>
|
||||
<BlockLink>
|
||||
<BlockTitle>{ guide.title }</BlockTitle>
|
||||
<BlockSubtitle>{ guide.featuredDescription || guide.description }</BlockSubtitle>
|
||||
|
@ -3,7 +3,7 @@ import { BlockLink, BlockSubtitle, BlockTitle } from './style';
|
||||
|
||||
const RoadmapBlock = ({ roadmap }) => (
|
||||
<div className="col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12 grid-item-container">
|
||||
<Link href={ roadmap.slug } passHref>
|
||||
<Link href={ roadmap.url } passHref>
|
||||
<BlockLink>
|
||||
<BlockTitle>{ roadmap.title }</BlockTitle>
|
||||
<BlockSubtitle>{ roadmap.featuredDescription || roadmap.description }</BlockSubtitle>
|
||||
@ -12,4 +12,4 @@ const RoadmapBlock = ({ roadmap }) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
export default RoadmapBlock;
|
||||
export default RoadmapBlock;
|
||||
|
@ -24,7 +24,7 @@ const RoadmapSummary = ({ roadmap }) => (
|
||||
<Description>{ roadmap.description }</Description>
|
||||
<VersionList className="border-bottom">
|
||||
{ (roadmap.versions || []).map(versionItem => (
|
||||
<Link href={ `${roadmap.slug}/${versionItem}` } passHref key={ versionItem }>
|
||||
<Link href={ `${roadmap.url}/${versionItem}` } passHref key={ versionItem }>
|
||||
<VersionLink className={ classNames({
|
||||
active: isActiveRoadmap(versionItem, roadmap.version),
|
||||
}) }>{ versionItem } Version</VersionLink>
|
||||
@ -40,4 +40,4 @@ const RoadmapSummary = ({ roadmap }) => (
|
||||
</SummaryContainer>
|
||||
);
|
||||
|
||||
export default RoadmapSummary;
|
||||
export default RoadmapSummary;
|
||||
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
"title": "Design Patterns for Humans",
|
||||
"description": "A language agnostic, ultra-simplified explanation to design patterns",
|
||||
"slug": "/guides/design-patterns-for-humans",
|
||||
"url": "/guides/design-patterns-for-humans",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"createdAt": "June 12, 2019",
|
||||
@ -11,7 +11,7 @@
|
||||
{
|
||||
"title": "Learn Regex",
|
||||
"description": "An easy to understand guide on regular expressions with real world examples",
|
||||
"slug": "/guides/learn-regex",
|
||||
"url": "/guides/learn-regex",
|
||||
"featured": true,
|
||||
"author": "ziishaned",
|
||||
"createdDate": "June 19, 2019",
|
||||
@ -20,7 +20,7 @@
|
||||
{
|
||||
"title": "Bash Guide",
|
||||
"description": "Easy to understand guide for bash with real world usage examples.",
|
||||
"slug": "/guides/bash-guide",
|
||||
"url": "/guides/bash-guide",
|
||||
"featured": true,
|
||||
"author": "idnan",
|
||||
"createdAt": "May 18, 2018",
|
||||
@ -29,7 +29,7 @@
|
||||
{
|
||||
"title": "DNS in One Picture",
|
||||
"description": "Quick illustrative guide on how a website is found on the internet.",
|
||||
"slug": "/guides/dns-in-one-picture",
|
||||
"url": "/guides/dns-in-one-picture",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"createdAt": "May 11, 2018",
|
||||
@ -38,7 +38,7 @@
|
||||
{
|
||||
"title": "Using React Hooks",
|
||||
"description": "Start using React hooks in your react applications today with this guide.",
|
||||
"slug": "/guides/using-react-hooks",
|
||||
"url": "/guides/using-react-hooks",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"createdAt": "October 22, 2019",
|
||||
@ -47,7 +47,7 @@
|
||||
{
|
||||
"title": "HTTP Caching",
|
||||
"description": "Everything you need to know about web caching",
|
||||
"slug": "/guides/http-caching",
|
||||
"url": "/guides/http-caching",
|
||||
"featured": true,
|
||||
"author": "kamranahmedse",
|
||||
"updatedAt": "November 01, 2019",
|
||||
|
0
data/guides/bash-guide.md
Normal file
0
data/guides/bash-guide.md
Normal file
0
data/guides/dns-in-one-picture.md
Normal file
0
data/guides/dns-in-one-picture.md
Normal file
0
data/guides/http-caching.md
Normal file
0
data/guides/http-caching.md
Normal file
0
data/guides/using-react-hooks.md
Normal file
0
data/guides/using-react-hooks.md
Normal file
@ -3,7 +3,7 @@
|
||||
"title": "Frontend Developer",
|
||||
"description": "Step by step guide to becoming a modern frontend developer",
|
||||
"featuredDescription": "Step by step guide to becoming a modern frontend developer in 2019",
|
||||
"slug": "/roadmaps/frontend",
|
||||
"url": "/frontend",
|
||||
"picture": "/static/roadmaps/{version}/frontend.png",
|
||||
"featured": true,
|
||||
"versions": [
|
||||
@ -16,7 +16,7 @@
|
||||
"title": "Backend Developer",
|
||||
"description": "Step by step guide to becoming a modern backend developer",
|
||||
"featuredDescription": "Step by step guide to becoming a modern backend developer in 2019",
|
||||
"slug": "/roadmaps/backend",
|
||||
"url": "/backend",
|
||||
"picture": "/static/roadmaps/{version}/backend.png",
|
||||
"featured": true,
|
||||
"versions": [
|
||||
@ -29,7 +29,7 @@
|
||||
"title": "DevOps Roadmap",
|
||||
"description": "Step by step guide for DevOps or any other Operations Role",
|
||||
"featuredDescription": "Step by step guide to become an SRE or for any operations role in 2019",
|
||||
"slug": "/roadmaps/devops",
|
||||
"url": "/devops",
|
||||
"picture": "/static/roadmaps/{version}/devops.png",
|
||||
"featured": true,
|
||||
"versions": [
|
||||
@ -38,4 +38,4 @@
|
||||
"2017"
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
@ -1,7 +1,7 @@
|
||||
import guides from "data/guides";
|
||||
|
||||
export const getRequestedGuide = req => {
|
||||
const guide = guides.find(guide => guide.slug === req.url);
|
||||
const guide = guides.find(guide => guide.url === req.url);
|
||||
if (!guide) {
|
||||
return null;
|
||||
}
|
||||
@ -9,7 +9,7 @@ export const getRequestedGuide = req => {
|
||||
// We will use this URL format to find the relevant markdown
|
||||
// file inside the `/data` directory. For example `/guides/learn-regex`
|
||||
// has to have `/guides/learn-regex.md` file inside the `data` directory
|
||||
const path = guide.slug.replace(/^\//, '');
|
||||
const path = guide.url.replace(/^\//, '');
|
||||
|
||||
try {
|
||||
return {
|
||||
|
@ -3,23 +3,18 @@ import roadmaps from "data/roadmaps";
|
||||
export const getRequestedRoadmap = req => {
|
||||
// Considering it a new roadmap URL e.g. `/roadmaps/frontend`
|
||||
const currentUrl = req.url.replace(/\/$/, '');
|
||||
// Considering it a legacy URL e.g. converting `/frontend` to `roadmap.sh/roadmaps/frontend`
|
||||
const legacyUrl = `/roadmaps${currentUrl}`;
|
||||
// Get the roadmap version out of the URL e.g. `/roadmaps/frontend/2019`
|
||||
const [foundVersion = ''] = currentUrl.match(/(\d+|latest)$/) || ['latest'];
|
||||
const foundVersionRegex = new RegExp(`\/?${foundVersion}$`);
|
||||
// Remove version from the URL because slugs in roadmaps list don't have versions
|
||||
const newUrlWithoutVersion = currentUrl.replace(foundVersionRegex, '');
|
||||
const legacyUrlWithoutVersion = legacyUrl.replace(foundVersionRegex, '');
|
||||
// Remove version from the URL because urls in roadmaps list don't have versions
|
||||
const urlWithoutVersion = currentUrl.replace(foundVersionRegex, '');
|
||||
|
||||
const urlToSlugList = [
|
||||
currentUrl,
|
||||
legacyUrl,
|
||||
newUrlWithoutVersion,
|
||||
legacyUrlWithoutVersion,
|
||||
urlWithoutVersion,
|
||||
];
|
||||
|
||||
const foundRoadmap = roadmaps.find(roadmap => urlToSlugList.includes(roadmap.slug));
|
||||
const foundRoadmap = roadmaps.find(roadmap => urlToSlugList.includes(roadmap.url));
|
||||
if (!foundRoadmap) {
|
||||
return null;
|
||||
}
|
||||
|
@ -2,6 +2,11 @@ const path = require('path');
|
||||
const withSass = require('@zeit/next-sass');
|
||||
const withCSS = require('@zeit/next-css');
|
||||
const rehypePrism = require('@mapbox/rehype-prism');
|
||||
const {
|
||||
getPageRoutes,
|
||||
getGuideRoutes,
|
||||
getRoadmapRoutes,
|
||||
} = require("./path-map");
|
||||
|
||||
const withMDX = require('@next/mdx')({
|
||||
extension: /\.(md|mdx)?$/,
|
||||
@ -12,22 +17,10 @@ const withMDX = require('@next/mdx')({
|
||||
|
||||
const options = {
|
||||
exportPathMap: () => {
|
||||
// @todo make it dynamic for pages, authors and guides
|
||||
return {
|
||||
'/': { page: '/' },
|
||||
'/about': { page: '/about' },
|
||||
'/privacy': { page: '/privacy' },
|
||||
'/terms': { page: '/terms' },
|
||||
'/roadmaps': { page: '/roadmaps' },
|
||||
'/guides': { page: '/guides' },
|
||||
|
||||
'/guides/design-patterns-for-humans': { page: '/guides/[guide]', query: "design-patterns-for-humans" },
|
||||
'/frontend': { page: '/[fallback]', query: "frontend" },
|
||||
'/backend': { page: '/[fallback]', query: "backend" },
|
||||
'/devops': { page: '/[fallback]', query: "devops" },
|
||||
'/roadmaps/frontend': { page: '/roadmaps/[roadmap]', query: "frontend" },
|
||||
'/roadmaps/backend': { page: '/roadmaps/[roadmap]', query: "backend" },
|
||||
'/roadmaps/devops': { page: '/roadmaps/[roadmap]', query: "devops" },
|
||||
...getPageRoutes(),
|
||||
...getGuideRoutes(),
|
||||
...getRoadmapRoutes(),
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -7,7 +7,9 @@
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"html": "next export"
|
||||
"html": "next export",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.22",
|
||||
@ -35,6 +37,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-styled-components": "^1.10.6",
|
||||
"glob": "^7.1.5"
|
||||
"glob": "^7.1.5",
|
||||
"jest": "^24.9.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
import OldRoadmap from './index';
|
||||
|
||||
export default OldRoadmap;
|
@ -1,22 +0,0 @@
|
||||
import Error from 'next/error';
|
||||
import Roadmap from 'pages/roadmaps/[roadmap]/index';
|
||||
import { serverOnlyProps } from 'lib/server';
|
||||
import { getRequestedRoadmap } from 'lib/roadmap';
|
||||
|
||||
// Fallback page to handle the old roadmap pages implementation
|
||||
const OldRoadmap = ({ roadmap }) => {
|
||||
if (roadmap) {
|
||||
return <Roadmap roadmap={ roadmap } />
|
||||
}
|
||||
|
||||
return <Error statusCode={ 404 } />;
|
||||
};
|
||||
|
||||
OldRoadmap.getInitialProps = serverOnlyProps(({ req }) => {
|
||||
return {
|
||||
roadmap: getRequestedRoadmap(req),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
export default OldRoadmap;
|
@ -1,3 +1,3 @@
|
||||
import Roadmap from './index';
|
||||
|
||||
export default Roadmap;
|
||||
export default Roadmap;
|
@ -1,10 +1,10 @@
|
||||
import Error from 'next/error';
|
||||
import DefaultLayout from 'layouts/default';
|
||||
import { serverOnlyProps } from 'lib/server';
|
||||
import PageHeader from 'components/page-header';
|
||||
import PageFooter from 'components/page-footer';
|
||||
import { getRequestedRoadmap } from 'lib/roadmap';
|
||||
import RoadmapSummary from 'components/roadmap-summary';
|
||||
import { serverOnlyProps } from 'lib/server';
|
||||
import { getRequestedRoadmap } from 'lib/roadmap';
|
||||
|
||||
const Roadmap = ({ roadmap }) => {
|
||||
if (!roadmap) {
|
@ -1,7 +1,7 @@
|
||||
import DefaultLayout from 'layouts/default/index';
|
||||
import PageHeader from 'components/page-header/index';
|
||||
|
||||
const Roadmap = () => (
|
||||
const RoadmapsList = () => (
|
||||
<DefaultLayout>
|
||||
<PageHeader />
|
||||
<div className="container">
|
||||
@ -10,4 +10,4 @@ const Roadmap = () => (
|
||||
</DefaultLayout>
|
||||
);
|
||||
|
||||
export default Roadmap;
|
||||
export default RoadmapsList;
|
58
path-map.js
58
path-map.js
@ -1,7 +1,9 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const glob = require('glob');
|
||||
|
||||
const guides = require('./data/guides.json');
|
||||
const roadmaps = require('./data/roadmaps');
|
||||
|
||||
const PAGES_PATH = path.join(__dirname, 'pages');
|
||||
|
||||
/**
|
||||
@ -21,7 +23,6 @@ const getPageRoutes = () => {
|
||||
});
|
||||
|
||||
const pageRoutes = {};
|
||||
|
||||
files.forEach(file => {
|
||||
const pageName = file.replace(PAGES_PATH, '').replace('.js', '');
|
||||
const pagePath = pageName.replace('/index', '') || '/';
|
||||
@ -32,4 +33,55 @@ const getPageRoutes = () => {
|
||||
return pageRoutes;
|
||||
};
|
||||
|
||||
console.log(getPageRoutes());
|
||||
/**
|
||||
* Generates routes for guide pages
|
||||
* @returns {*}
|
||||
*/
|
||||
const getGuideRoutes = () => {
|
||||
return guides.reduce((acc, guide) => {
|
||||
const [, , slug] = guide.url.split('/');
|
||||
return {
|
||||
...acc,
|
||||
[guide.url]: {
|
||||
page: '/guides/[guide]',
|
||||
query: slug,
|
||||
}
|
||||
};
|
||||
}, {});
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates routes for each of the roadmap and it's respective versions
|
||||
* @returns {*}
|
||||
*/
|
||||
const getRoadmapRoutes = () => {
|
||||
return roadmaps.reduce((roadmapRoutes, roadmap) => {
|
||||
const [, slug] = roadmap.url.split('/');
|
||||
|
||||
return {
|
||||
...roadmapRoutes,
|
||||
// Default roadmap path i.e. `{ '/frontend': { page: '/[roadmap]/index', query: 'frontend' }`
|
||||
[roadmap.url]: {
|
||||
page: '/[roadmap]/index',
|
||||
query: slug
|
||||
},
|
||||
// Route for each of the versions of this roadmap i.e.
|
||||
// `{ '/frontend/2019': { page: '/[roadmap]/[version]', query: 'frontend/2019' } }`
|
||||
...(roadmap.versions.reduce((versionRoutes, version) => {
|
||||
return {
|
||||
...versionRoutes,
|
||||
[`${roadmap.url}/${version}`]: {
|
||||
page: '/[roadmap]/[version]',
|
||||
query: `${slug}/${version}`
|
||||
}
|
||||
};
|
||||
}, {})),
|
||||
};
|
||||
}, {});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getPageRoutes,
|
||||
getGuideRoutes,
|
||||
getRoadmapRoutes,
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user